From c4dd4ae3fd3623319067328750c04529238325ed Mon Sep 17 00:00:00 2001 From: Quentin BEY Date: Thu, 13 Mar 2025 14:04:25 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(oauth2)=20force=20JWT=20signed=20f?= =?UTF-8?q?or=20/userinfo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ProConnect requires the userinfo endpoint to return a signed JWT. --- CHANGELOG.md | 1 + src/backend/mailbox_oauth2/validators.py | 30 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 489e50b..f8c896e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to ### Fixed +- 🐛(oauth2) force JWT signed for /userinfo #804 - 🐛(oauth2) add ProConnect scopes #802 - 🐛(domains) use a dedicated mail to invite user to manage domain - 🐛(mailbox) fix mailbox creation email language diff --git a/src/backend/mailbox_oauth2/validators.py b/src/backend/mailbox_oauth2/validators.py index 4ecece8..2a84f00 100644 --- a/src/backend/mailbox_oauth2/validators.py +++ b/src/backend/mailbox_oauth2/validators.py @@ -5,6 +5,10 @@ Contains all related code for OIDC authentication using people as an Identity Provider. """ +import json + +from jwcrypto import jwt +from oauth2_provider.models import AbstractApplication from oauth2_provider.oauth2_validators import OAuth2Validator @@ -216,3 +220,29 @@ class ProConnectValidator(BaseValidator): bool: True if PKCE is required, False otherwise. """ return False + + def get_userinfo_claims(self, request): + """ + Generates and saves a new JWT for this request, and returns it as the + current user's claims. + + This is overridden to enforce JWT signing, we use `finalize_id_token` like code. + """ + claims, _expiration_time = self.get_id_token_dictionary( + request.access_token, None, request + ) + + header = { + "typ": "JWT", + "alg": request.client.algorithm, + } + # RS256 consumers expect a kid in the header for verifying the token + if request.client.algorithm == AbstractApplication.RS256_ALGORITHM: + header["kid"] = request.client.jwk_key.thumbprint() + + jwt_token = jwt.JWT( + header=json.dumps(header, default=str), + claims=json.dumps(claims, default=str), + ) + jwt_token.make_signed_token(request.client.jwk_key) + return jwt_token.serialize()