Skip to content

Commit 9817539

Browse files
authored
Merge pull request Ericsson#4517 from feyruzb/feature/get-signum-as-username
integrated signum fetching and using it as optional username
2 parents ac1a3f0 + cefbcb3 commit 9817539

File tree

4 files changed

+129
-45
lines changed

4 files changed

+129
-45
lines changed

docs/web/authentication.md

Lines changed: 74 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,11 @@ CodeChecker also supports OAuth-based authentication. The `authentication.method
378378

379379
* `user_groups_url`
380380

381-
`Microsoft` specific field to request security groups that the user is member of.
381+
`Microsoft`-specific field used to request security groups that the user is member of.
382+
383+
* `jwks_url`
384+
385+
`Microsoft`-specific field used to request public signing keys for decoding JWT tokens.
382386

383387
* `scope`
384388

@@ -391,33 +395,80 @@ CodeChecker also supports OAuth-based authentication. The `authentication.method
391395
* `username`
392396

393397
Field for the username.
394-
* `email`
395398

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

399-
Field for the fullname.
400-
~~~{.json}
401-
"method_oauth": {
401+
Currently there are only 2 options for this:
402+
* `username`
403+
* `email`
404+
405+
Expected output for each provider:
406+
407+
`github`
408+
* `username` - user's GitHub `login` will be user's identifier
409+
* `email` - a request will be sent to fetch the primary email of account to be used as the user's identifier.
410+
If it is hidden, an error will be thrown.
411+
412+
`google`
413+
* Only supports `email`, and user's Gmail email will be considered his username.
414+
415+
`microsoft`
416+
* `username` - Company's signum will be the user's identifier.
417+
* `email` - an email associated with this Microsoft account will be used as user's identifier.
418+
419+
### 🔧 Example: OAuth Configuration for GitHub
420+
~~~{.json}
421+
"github": {
402422
"enabled": false,
403-
"providers": {
404-
"example_provider": {
405-
"enabled": false,
406-
"client_id": "client id",
407-
"client_secret": "client secret",
408-
"authorization_url": "https://accounts.google.com/o/oauth2/auth",
409-
"callback_url": "http://localhost:8080/login/OAuthLogin/provider",
410-
"token_url": "https://accounts.google.com/o/oauth2/token",
411-
"user_info_url": "https://www.googleapis.com/oauth2/v1/userinfo",
412-
"user_emails_url": "https://api.github.com/user/emails",
413-
"scope": "openid email profile",
414-
"user_info_mapping": {
415-
"username": "email"
416-
}
417-
}
423+
"client_id": "<ExampleClientID>",
424+
"client_secret": "<ExampleClientSecret>",
425+
"authorization_url": "https://github.com/login/oauth/authorize",
426+
"callback_url": "https://<server_host>/login/OAuthLogin/github",
427+
"token_url": "https://github.com/login/oauth/access_token",
428+
"user_info_url": "https://api.github.com/user",
429+
"user_emails_url": "https://api.github.com/user/emails",
430+
"scope": "user:email",
431+
"user_info_mapping": {
432+
"username": "username"
418433
}
419434
}
420-
~~~
435+
~~~
436+
### 🔧 Example: OAuth Configuration for Google
437+
~~~{.json}
438+
"google": {
439+
"enabled": false,
440+
"client_id": "<ExampleClientID>",
441+
"client_secret": "<ExampleClientSecret>",
442+
"authorization_url": "https://accounts.google.com/o/oauth2/auth",
443+
"callback_url": "https://<server_host>/login/OAuthLogin/google",
444+
"token_url": "https://accounts.google.com/o/oauth2/token",
445+
"user_info_url": "https://www.googleapis.com/oauth2/v1/userinfo",
446+
"scope": "openid email profile",
447+
"user_info_mapping": {
448+
"username": "email"
449+
}
450+
}
451+
~~~
452+
### 🔧 Example: OAuth Configuration for Microsoft
453+
~~~{.json}
454+
"microsoft": {
455+
"enabled": false,
456+
"client_id": "<ExampleClientID>",
457+
"client_secret": "<ExampleClientSecret>",
458+
"authorization_url": "https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize",
459+
"callback_url": "https://<server_host>/login/OAuthLogin/microsoft",
460+
"token_url": "https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token",
461+
"user_groups_url": "https://graph.microsoft.com/v1.0/me/memberOf",
462+
"jwks_url": "https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys",
463+
"user_info_url": "https://graph.microsoft.com/v1.0/me",
464+
"scope": "User.Read email profile openid offline_access",
465+
"user_info_mapping": {
466+
"username": "email"
467+
}
468+
}
469+
~~~
470+
471+
421472
422473
#### OAuth Details per each provider <a name ="oauth-details-per-each-provider"></a>
423474

web/server/codechecker_server/api/authentication.py

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from authlib.integrations.requests_client import OAuth2Session
1515
from authlib.common.security import generate_token
16+
from authlib.jose import JsonWebToken
1617

1718
from urllib.parse import urlparse, parse_qs
1819

@@ -373,41 +374,70 @@ def performLogin(self, auth_method, auth_string):
373374
# request group memberships for Microsoft
374375
groups = []
375376
if provider == 'microsoft':
376-
access_token = oauth_token['access_token']
377+
# decoding
378+
id_token = oauth_token['id_token']
379+
jwks_url = oauth_config["jwks_url"]
380+
381+
jwks_response = oauth2_session.get(jwks_url)
382+
jwks_response.raise_for_status()
383+
jwks_fetched = jwks_response.json()
384+
385+
jwt_decoder = JsonWebToken(['RS256'])
386+
claims = jwt_decoder.decode(id_token, key=jwks_fetched)
387+
claims.validate()
388+
377389
user_groups_url = oauth_config["user_groups_url"]
378390
response = oauth2_session.get(user_groups_url).json()
391+
379392
for group in response["value"]:
380393
if group.get("onPremisesSyncEnabled") and \
381394
group.get("securityEnabled"):
382395
groups.append(group["displayName"])
383-
username = user_info[
384-
oauth_config["user_info_mapping"]["username"]]
385-
LOG.info("User info fetched, username: %s", username)
396+
386397
except Exception as ex:
387398
LOG.error("User info fetch failed: %s", str(ex))
388399
raise codechecker_api_shared.ttypes.RequestFailed(
389400
codechecker_api_shared.ttypes.ErrorCode.AUTH_DENIED,
390401
"User info fetch failed.")
391402

403+
username_key = oauth_config.get(
404+
"user_info_mapping", {}).get("username")
405+
392406
# if the provider is github it fetches primary email
393407
# from another api endpoint to maintain username as email
394408
# consistency between GitHub and other providers
395-
if provider == "github" and \
396-
"localhost" not in \
397-
user_info_url:
398-
try:
399-
user_emails_url = oauth_config["user_emails_url"]
400-
for email in oauth2_session \
401-
.get(user_emails_url).json():
402-
if email['primary']:
403-
username = email['email']
404-
LOG.info("Primary email found: %s", username)
405-
break
406-
except Exception as ex:
407-
LOG.error("Email fetch failed: %s", str(ex))
408-
raise codechecker_api_shared.ttypes.RequestFailed(
409-
codechecker_api_shared.ttypes.ErrorCode.AUTH_DENIED,
410-
"Email fetch failed.")
409+
try:
410+
if provider == "github":
411+
if username_key == "email":
412+
if "localhost" not in \
413+
user_info_url:
414+
user_emails_url = \
415+
oauth_config["user_emails_url"]
416+
for email in oauth2_session \
417+
.get(user_emails_url).json():
418+
if email['primary']:
419+
username = email['email']
420+
LOG.info("Primary email found: %s",
421+
username)
422+
else:
423+
username = user_info.get("login")
424+
elif provider == "google":
425+
username = user_info.get("email")
426+
elif provider == "microsoft":
427+
if username_key == "username":
428+
username = claims.get("Signum")
429+
else:
430+
username = user_info.get("mail")
431+
432+
LOG.debug(f"groups fetched for {username}, are: {groups}")
433+
434+
LOG.info("Username fetched, for username: %s", username)
435+
except Exception as ex:
436+
LOG.error("Username fetch failed: %s", str(ex))
437+
raise codechecker_api_shared.ttypes.RequestFailed(
438+
codechecker_api_shared.ttypes.ErrorCode.AUTH_DENIED,
439+
"Username fetch failed, error: %s.", str(ex))
440+
411441
try:
412442
access_token = oauth_token['access_token']
413443
refresh_token = oauth_token['refresh_token']

web/server/codechecker_server/session_manager.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,8 @@ def create_session_oauth(self, provider: str,
710710
if groups is None:
711711
groups = []
712712

713+
LOG.debug(f"groups assigned to oauth_session: {groups}")
714+
713715
if not self.__is_method_enabled('oauth'):
714716
return False
715717

web/server/config/server_config.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
"user_emails_url": "https://api.github.com/user/emails",
6969
"scope": "user:email",
7070
"user_info_mapping": {
71-
"username": "login"
71+
"username": "username"
7272
}
7373
},
7474
"google": {
@@ -92,10 +92,11 @@
9292
"callback_url": "https://<server_host>/login/OAuthLogin/microsoft",
9393
"token_url": "https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token",
9494
"user_groups_url" : "https://graph.microsoft.com/v1.0/me/memberOf",
95+
"jwks_url": "https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys",
9596
"user_info_url": "https://graph.microsoft.com/v1.0/me",
9697
"scope": "User.Read email profile openid offline_access",
9798
"user_info_mapping": {
98-
"username": "mail"
99+
"username": "email"
99100
}
100101
}
101102
}

0 commit comments

Comments
 (0)