Skip to content
Open
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
90 changes: 88 additions & 2 deletions eox_core/social_tpa_backends.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
"""
Extensions to the regular defined third party auth backends
"""
import base64
import hashlib
import logging

from django.conf import settings
from django.contrib.auth import get_user_model
from social_core.backends.open_id_connect import OpenIdConnectAuth
from social_core.exceptions import AuthMissingParameter
from social_core.exceptions import AuthException, AuthMissingParameter

from eox_core.edxapp_wrapper.configuration_helpers import get_configuration_helper

Expand All @@ -32,7 +34,7 @@ def __init__(self, *args, **kwargs):
try:
setattr(self, key, conf.get(key, getattr(self, key)))
except Exception: # pylint: disable=broad-except
LOG.error("Tried and failed to set property %s of a config-based-openidconnect", key)
LOG.warning("Tried and failed to set property %s of a config-based-openidconnect", key)

super().__init__(*args, **kwargs)

Expand Down Expand Up @@ -192,3 +194,87 @@ 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):
"""Create a new code verifier and store it in the session."""
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):
"""Retrieve the code verifier from the session."""
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):
"""Generate a code challenge from the code verifier."""
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):
"""Get the authentication parameters, adding PKCE parameters if enabled."""
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):
"""Get the authentication complete parameters, adding PKCE parameters if enabled."""
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'
Loading