Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom username claims #508

Merged
merged 2 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
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
14 changes: 11 additions & 3 deletions mozilla_django_oidc/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -20,10 +21,11 @@
LOGGER = logging.getLogger(__name__)


def default_username_algo(email):
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
:arg dic claims: the claims from your OIDC provider, currently unused

:returns: str/unicode

Expand Down Expand Up @@ -100,14 +102,20 @@ 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"""
Expand Down
38 changes: 38 additions & 0 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 protected]").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 protected]",
"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 protected]"),
)

@override_settings(OIDC_USE_NONCE=False)
@patch("mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws")
@patch("mozilla_django_oidc.auth.requests")
Expand Down Expand Up @@ -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
Loading