Skip to content

integrated signum fetching and using it as optional username #4517

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 10, 2025
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 33 additions & 19 deletions docs/web/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -391,32 +391,46 @@ CodeChecker also supports OAuth-based authentication. The `authentication.method
* `username`

Field for the username.
* `email`

Field for the email.
* `fullname`
The `username` field defines what value will be used as the user's unique identifier (i.e. their "username") depending on the OAuth provider.

Field for the fullname.
Currently there are only 2 options for this:
* `username`
* `email`

Expected output for each provider:

`github`
* `username` - user's GitHub `login` will be user's identifier
* `email` - a request will be sent to fetch the primary email of account to be used as the user's identifier.
If it is hidden, an error will be thrown.

`google`
* Only supports `email`, and user's Gmail email will be considered his username.

`microsoft`
* `username` - Company's signum will be the user's identifier.
* `email` - an email associated with this Microsoft account will be used as user's identifier.
~~~{.json}
"method_oauth": {
"enabled": false,
"providers": {
"example_provider": {
"enabled": false,
"client_id": "client id",
"client_secret": "client secret",
"authorization_url": "https://accounts.google.com/o/oauth2/auth",
"callback_url": "http://localhost:8080/login/OAuthLogin/provider",
"token_url": "https://accounts.google.com/o/oauth2/token",
"user_info_url": "https://www.googleapis.com/oauth2/v1/userinfo",
"user_emails_url": "https://api.github.com/user/emails",
"scope": "openid email profile",
"user_info_mapping": {
"username": "email"
}
"enabled": false,
"providers": {
"example_provider": {
"enabled": false,
"client_id": "client id",
"client_secret": "client secret",
"authorization_url": "https://accounts.google.com/o/oauth2/auth",
"callback_url": "http://localhost:8080/login/OAuthLogin/provider",
"token_url": "https://accounts.google.com/o/oauth2/token",
"user_info_url": "https://www.googleapis.com/oauth2/v1/userinfo",
"user_emails_url": "https://api.github.com/user/emails",
"scope": "openid email profile",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am getting this error at authentication
User info fetch failed: "jwks_url'

I guess it is missing from the config file

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But why is it still missing from this authentication md .json example?

"user_info_mapping": {
"username": "email"
}
}
}
}
~~~

#### OAuth Details per each provider <a name ="oauth-details-per-each-provider"></a>
Expand Down
67 changes: 47 additions & 20 deletions web/server/codechecker_server/api/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from authlib.integrations.requests_client import OAuth2Session
from authlib.common.security import generate_token
from authlib.jose import JsonWebToken

from urllib.parse import urlparse, parse_qs

Expand Down Expand Up @@ -373,41 +374,67 @@ def performLogin(self, auth_method, auth_string):
# request group memberships for Microsoft
groups = []
if provider == 'microsoft':
access_token = oauth_token['access_token']
# decoding
id_token = oauth_token['id_token']
jwks_url = oauth_config["jwks_url"]

jwks_response = oauth2_session.get(jwks_url)
jwks_response.raise_for_status()
jwks_fetched = jwks_response.json()

jwt_decoder = JsonWebToken(['RS256'])
claims = jwt_decoder.decode(id_token, key=jwks_fetched)
claims.validate()

user_groups_url = oauth_config["user_groups_url"]
response = oauth2_session.get(user_groups_url).json()

for group in response["value"]:
if group.get("onPremisesSyncEnabled") and \
group.get("securityEnabled"):
groups.append(group["displayName"])
username = user_info[
oauth_config["user_info_mapping"]["username"]]
LOG.info("User info fetched, username: %s", username)

except Exception as ex:
LOG.error("User info fetch failed: %s", str(ex))
raise codechecker_api_shared.ttypes.RequestFailed(
codechecker_api_shared.ttypes.ErrorCode.AUTH_DENIED,
"User info fetch failed.")

username_key = oauth_config.get(
"user_info_mapping", {}).get("username")

# if the provider is github it fetches primary email
# from another api endpoint to maintain username as email
# consistency between GitHub and other providers
if provider == "github" and \
"localhost" not in \
user_info_url:
try:
user_emails_url = oauth_config["user_emails_url"]
for email in oauth2_session \
.get(user_emails_url).json():
if email['primary']:
username = email['email']
LOG.info("Primary email found: %s", username)
break
except Exception as ex:
LOG.error("Email fetch failed: %s", str(ex))
raise codechecker_api_shared.ttypes.RequestFailed(
codechecker_api_shared.ttypes.ErrorCode.AUTH_DENIED,
"Email fetch failed.")
try:
if provider == "github":
if username_key == "email":
if "localhost" not in \
user_info_url:
user_emails_url = \
oauth_config["user_emails_url"]
for email in oauth2_session \
.get(user_emails_url).json():
if email['primary']:
username = email['email']
LOG.info("Primary email found: %s",
username)
else:
username = user_info.get("login")
elif provider == "google":
username = user_info.get("email")
elif provider == "microsoft":
if username_key == "username":
username = claims.get("Signum")
else:
username = user_info.get("mail")
LOG.info("Username fetched, for username: %s", username)
except Exception as ex:
LOG.error("Username fetch failed: %s", str(ex))
raise codechecker_api_shared.ttypes.RequestFailed(
codechecker_api_shared.ttypes.ErrorCode.AUTH_DENIED,
"Username fetch failed, error: %s.", str(ex))

try:
access_token = oauth_token['access_token']
refresh_token = oauth_token['refresh_token']
Expand Down
2 changes: 1 addition & 1 deletion web/server/config/server_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
"user_info_url": "https://graph.microsoft.com/v1.0/me",
"scope": "User.Read email profile openid offline_access",
"user_info_mapping": {
"username": "mail"
"username": "email"
}
}
}
Expand Down
Loading