From 0b69943b676383ad6007061e4a42a6e423194976 Mon Sep 17 00:00:00 2001 From: Johan Castiblanco <51926076+johanv26@users.noreply.github.com> Date: Tue, 21 Nov 2023 12:31:17 -0500 Subject: [PATCH] feat: add pkce-openid-backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a generic backend based on ConfigurableOpenIdConnectAuth but with PKCE. This backend is inspired in the social-core way to implement PKCE. There is a current PR in work, but for the moment, that class is not merged and accessible. So after that is finished this has it code for `code_challenge` and `code_challenge_method`implementation. PR: https://github.com/python-social-auth/social-core/pull/856 Co-authored-by: Omar Al-Ithawi @ NELC <134635705+omar-nelc@users.noreply.github.com> Co-authored-by: Andrey CaƱon --- eox_core/social_tpa_backends.py | 81 +++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/eox_core/social_tpa_backends.py b/eox_core/social_tpa_backends.py index 62e058e6e..894a8891f 100644 --- a/eox_core/social_tpa_backends.py +++ b/eox_core/social_tpa_backends.py @@ -1,6 +1,8 @@ """ Extensions to the regular defined third party auth backends """ +import base64 +import hashlib import logging from django.conf import settings @@ -192,3 +194,82 @@ def get_user_id(self, *args, **kwargs): LOG.info("Updating uid: %s to %s", uid, slug_uid) return slug_uid + + +class BaseOAuth2PKCEMixin: + """ + TO-DO: Use the `from social_core.backends.oauth import BaseOAuth2PKCE` base class once the pull request is merged: https://github.com/python-social-auth/social-core/pull/856/files#diff-d44db201b48f2ec7cab2a0c981213a2991630567778cc6608d03fa0e3804e466R467 + Base class for providers using OAuth2 with Proof Key for Code Exchange (PKCE). + OAuth2 details at: + https://datatracker.ietf.org/doc/html/rfc6749 + PKCE details at: + https://datatracker.ietf.org/doc/html/rfc7636 + """ + + PKCE_DEFAULT_CODE_CHALLENGE_METHOD = "s256" + PKCE_DEFAULT_CODE_VERIFIER_LENGTH = 32 + DEFAULT_USE_PKCE = True + + def create_code_verifier(self): + name = f"{self.name}_code_verifier" + code_verifier_len = self.setting( + "PKCE_CODE_VERIFIER_LENGTH", default=self.PKCE_DEFAULT_CODE_VERIFIER_LENGTH + ) + code_verifier = self.strategy.random_string(code_verifier_len) + self.strategy.session_set(name, code_verifier) + return code_verifier + + def get_code_verifier(self): + name = f"{self.name}_code_verifier" + code_verifier = self.strategy.session_get(name) + return code_verifier + + def generate_code_challenge(self, code_verifier, challenge_method): + method = challenge_method.lower() + if method == "s256": + hashed = hashlib.sha256(code_verifier.encode()).digest() + encoded = base64.urlsafe_b64encode(hashed) + code_challenge = encoded.decode().replace("=", "") # remove padding + return code_challenge + if method == "plain": + return code_verifier + raise AuthException("Unsupported code challenge method.") + + def auth_params(self, state=None): + params = super().auth_params(state=state) + + if self.setting("USE_PKCE", default=self.DEFAULT_USE_PKCE): + code_challenge_method = self.setting( + "PKCE_CODE_CHALLENGE_METHOD", + default=self.PKCE_DEFAULT_CODE_CHALLENGE_METHOD, + ) + code_verifier = self.create_code_verifier() + code_challenge = self.generate_code_challenge( + code_verifier, code_challenge_method + ) + params["code_challenge_method"] = code_challenge_method + params["code_challenge"] = code_challenge + return params + + def auth_complete_params(self, state=None): + params = super().auth_complete_params(state=state) + + if self.setting("USE_PKCE", default=self.DEFAULT_USE_PKCE): + code_verifier = self.get_code_verifier() + params["code_verifier"] = code_verifier + + return params + + +class ConfigurableOpenIdConnectAuthPKCE(BaseOAuth2PKCEMixin, ConfigurableOpenIdConnectAuth): + """ + Generic backend based in ConfigurableOpenIdConnectAuth but + with PKCE. + This backend is inspired in the social-core way to implement PKCE. + There is a current PR in working, but for the moment, that class is not merged and accesible. + So after that is finished we use `BaseOAuth2PKCEMixin` for `code_challenge` and `code_challenge_method`implementation. + PR: https://github.com/python-social-auth/social-core/pull/856 + Block code: https://github.com/python-social-auth/social-core/pull/856/files#diff-d44db201b48f2ec7cab2a0c981213a2991630567778cc6608d03fa0e3804e466R467-R530 + + """ + name = 'config-based-openidconnect-PKCE'