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 all 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
97 changes: 74 additions & 23 deletions docs/web/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,11 @@ CodeChecker also supports OAuth-based authentication. The `authentication.method

* `user_groups_url`

`Microsoft` specific field to request security groups that the user is member of.
`Microsoft`-specific field used to request security groups that the user is member of.

* `jwks_url`

`Microsoft`-specific field used to request public signing keys for decoding JWT tokens.

* `scope`

Expand All @@ -391,33 +395,80 @@ 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.
~~~{.json}
"method_oauth": {
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.

### 🔧 Example: OAuth Configuration for GitHub
~~~{.json}
"github": {
"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"
}
}
"client_id": "<ExampleClientID>",
"client_secret": "<ExampleClientSecret>",
"authorization_url": "https://github.com/login/oauth/authorize",
"callback_url": "https://<server_host>/login/OAuthLogin/github",
"token_url": "https://github.com/login/oauth/access_token",
"user_info_url": "https://api.github.com/user",
"user_emails_url": "https://api.github.com/user/emails",
"scope": "user:email",
"user_info_mapping": {
"username": "username"
}
}
~~~
~~~
### 🔧 Example: OAuth Configuration for Google
~~~{.json}
"google": {
"enabled": false,
"client_id": "<ExampleClientID>",
"client_secret": "<ExampleClientSecret>",
"authorization_url": "https://accounts.google.com/o/oauth2/auth",
"callback_url": "https://<server_host>/login/OAuthLogin/google",
"token_url": "https://accounts.google.com/o/oauth2/token",
"user_info_url": "https://www.googleapis.com/oauth2/v1/userinfo",
"scope": "openid email profile",
"user_info_mapping": {
"username": "email"
}
}
~~~
### 🔧 Example: OAuth Configuration for Microsoft
~~~{.json}
"microsoft": {
"enabled": false,
"client_id": "<ExampleClientID>",
"client_secret": "<ExampleClientSecret>",
"authorization_url": "https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize",
"callback_url": "https://<server_host>/login/OAuthLogin/microsoft",
"token_url": "https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token",
"user_groups_url": "https://graph.microsoft.com/v1.0/me/memberOf",
"jwks_url": "https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys",
"user_info_url": "https://graph.microsoft.com/v1.0/me",
"scope": "User.Read email profile openid offline_access",
"user_info_mapping": {
"username": "email"
}
}
~~~



#### OAuth Details per each provider <a name ="oauth-details-per-each-provider"></a>

Expand Down
70 changes: 50 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,70 @@ 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.debug(f"groups fetched for {username}, are: {groups}")

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: 2 additions & 0 deletions web/server/codechecker_server/session_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,8 @@ def create_session_oauth(self, provider: str,
if groups is None:
groups = []

LOG.debug(f"groups assigned to oauth_session: {groups}")

if not self.__is_method_enabled('oauth'):
return False

Expand Down
5 changes: 3 additions & 2 deletions web/server/config/server_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"user_emails_url": "https://api.github.com/user/emails",
"scope": "user:email",
"user_info_mapping": {
"username": "login"
"username": "username"
}
},
"google": {
Expand All @@ -92,10 +92,11 @@
"callback_url": "https://<server_host>/login/OAuthLogin/microsoft",
"token_url": "https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token",
"user_groups_url" : "https://graph.microsoft.com/v1.0/me/memberOf",
"jwks_url": "https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys",
"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