From 8b7b81c2719e0c7909d57bf7334d3c43a738e629 Mon Sep 17 00:00:00 2001 From: erosert <47104514+EduardRosert@users.noreply.github.com> Date: Thu, 1 Jun 2023 12:21:04 +0100 Subject: [PATCH 1/2] added claims support to custom username algo --- mozilla_django_oidc/auth.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/mozilla_django_oidc/auth.py b/mozilla_django_oidc/auth.py index 5a94147a..0b50b0c1 100644 --- a/mozilla_django_oidc/auth.py +++ b/mozilla_django_oidc/auth.py @@ -3,6 +3,7 @@ import json import logging +import inspect import requests from django.contrib.auth import get_user_model from django.contrib.auth.backends import ModelBackend @@ -20,10 +21,11 @@ LOGGER = logging.getLogger(__name__) -def default_username_algo(email): +def default_username_algo(email, claims): """Generate username for the Django user. :arg str/unicode email: the email address to use to generate a username + :arg dic claims: the claims from your OIDC provider, currently unused :returns: str/unicode @@ -100,14 +102,21 @@ def get_username(self, claims): """Generate username based on claims.""" # bluntly stolen from django-browserid # https://github.com/mozilla/django-browserid/blob/master/django_browserid/auth.py + username_algo = self.get_settings("OIDC_USERNAME_ALGO", None) if username_algo: if isinstance(username_algo, str): username_algo = import_string(username_algo) - return username_algo(claims.get("email")) + if len(inspect.getfullargspec(username_algo).args) == 1: + # this is for backwards compatibility only + return username_algo(claims.get("email")) + else: + # also pass the claims to the custom user name algo + return username_algo(claims.get("email"), claims) + - return default_username_algo(claims.get("email")) + return default_username_algo(claims.get("email"), claims) def update_user(self, user, claims): """Update existing user with new claims, if necessary save, and return user""" From 6be570a4b5c0dc13f2338d24b8fa23cc103483d9 Mon Sep 17 00:00:00 2001 From: Victor Date: Thu, 1 Jun 2023 13:41:31 +0100 Subject: [PATCH 2/2] add test --- mozilla_django_oidc/auth.py | 3 +-- tests/test_auth.py | 38 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/mozilla_django_oidc/auth.py b/mozilla_django_oidc/auth.py index 0b50b0c1..1ff2c3f7 100644 --- a/mozilla_django_oidc/auth.py +++ b/mozilla_django_oidc/auth.py @@ -21,7 +21,7 @@ LOGGER = logging.getLogger(__name__) -def default_username_algo(email, claims): +def default_username_algo(email, claims=None): """Generate username for the Django user. :arg str/unicode email: the email address to use to generate a username @@ -115,7 +115,6 @@ def get_username(self, claims): # also pass the claims to the custom user name algo return username_algo(claims.get("email"), claims) - return default_username_algo(claims.get("email"), claims) def update_user(self, user, claims): diff --git a/tests/test_auth.py b/tests/test_auth.py index 2bb83e47..5fce7719 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -736,6 +736,38 @@ def test_custom_username_algo_dotted_path(self, request_mock, jws_mock): User.objects.get(username="dotted_username_algo"), ) + @override_settings( + OIDC_USE_NONCE=False, + OIDC_USERNAME_ALGO="tests.test_auth.dotted_username_algo_callback_with_claims", + ) + @patch("mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws") + @patch("mozilla_django_oidc.auth.requests") + def test_dotted_username_algo_callback_with_claims(self, request_mock, jws_mock): + """Test user creation with custom username algorithm with a dotted path.""" + auth_request = RequestFactory().get("/foo", {"code": "foo", "state": "bar"}) + auth_request.session = {} + + self.assertEqual(User.objects.filter(email="email@example.com").exists(), False) + jws_mock.return_value = json.dumps({"nonce": "nonce"}).encode("utf-8") + domain = "django.con" + get_json_mock = Mock() + get_json_mock.json.return_value = { + "nickname": "a_username", + "email": "email@example.com", + "domain": domain, + } + request_mock.get.return_value = get_json_mock + post_json_mock = Mock() + post_json_mock.json.return_value = { + "id_token": "id_token", + "access_token": "access_granted", + } + request_mock.post.return_value = post_json_mock + self.assertEqual( + self.backend.authenticate(request=auth_request), + User.objects.get(username=f"{domain}/email@example.com"), + ) + @override_settings(OIDC_USE_NONCE=False) @patch("mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws") @patch("mozilla_django_oidc.auth.requests") @@ -1170,3 +1202,9 @@ def test_returns_true_custom_claims(self, patch_logger, patch_settings): def dotted_username_algo_callback(email): return "dotted_username_algo" + + +def dotted_username_algo_callback_with_claims(email, claims=None): + domain = claims["domain"] + username = f"{domain}/{email}" + return username