diff --git a/physionet-django/user/validators.py b/physionet-django/user/validators.py index a73b067f7..564095b94 100644 --- a/physionet-django/user/validators.py +++ b/physionet-django/user/validators.py @@ -1,4 +1,7 @@ import re +import requests +import jwt +import json from django.conf import settings from django.contrib.auth.validators import UnicodeUsernameValidator @@ -216,6 +219,36 @@ def validate_orcid_token(value): if not re.fullmatch(r'^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$', value): raise ValidationError('ORCID token is not in expected format.') +def validate_orcid_id_token(token): + """ + When openid scope is enabled then ORCID returns + access_token and signed id_token, this function validates id_token signature + """ + + jwks_url = "https://sandbox.orcid.org/oauth/jwks" # ORCID Sandbox JWKS URL + jwks = requests.get(jwks_url).json() + headers = jwt.get_unverified_header(token) + + public_keys = {} + for jwk in jwks['keys']: + kid = jwk['kid'] + public_keys[kid] = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(jwk)) + + rsa_key = public_keys[headers['kid']] + if rsa_key is None: + raise ValidationError('ORCID id_token is invalid.') + + try: + jwt.decode( + token, + rsa_key, + algorithms=['RS256'], + audience=settings.ORCID_CLIENT_ID, + issuer=settings.ORCID_DOMAIN + ) + except jwt.InvalidTokenError as e: + raise ValidationError('ORCID id_token is invalid.') + def validate_orcid_id(value): """ Validation to verify the ID returned during diff --git a/physionet-django/user/views.py b/physionet-django/user/views.py index 2e0134aa6..d8b04a8f2 100644 --- a/physionet-django/user/views.py +++ b/physionet-django/user/views.py @@ -531,7 +531,7 @@ def auth_orcid_login(request): auth_login(request, user, backend='user.backends.OrcidAuthBackend') - return redirect('home') + return redirect('login') def _fetch_and_validate_token(request, code, oauth_session): @@ -549,6 +549,7 @@ def _fetch_and_validate_token(request, code, oauth_session): try: validators.validate_orcid_token(token['access_token']) + validators.validate_orcid_id_token(token['id_token']) return True, token except ValidationError: messages.error(request, 'Validation Error: ORCID token validation failed.') diff --git a/pyproject.toml b/pyproject.toml index 0dd54420c..c90d3dabd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,7 @@ hdn-research-environment = "2.3.8" django-oauth-toolkit = "^2.2.0" django-cors-headers = "^3.14.0" urllib3 = "^1.26.19" +pyjwt = "^2.9.0" [tool.poetry.dev-dependencies] coverage = "^7.2.3"