Skip to content

Commit c11efbe

Browse files
authored
Merge pull request #85 from francoisfreitag/custom_user_username_field
Inspect User model to determine Django user main attribute
2 parents b9105c6 + 59ed742 commit c11efbe

File tree

5 files changed

+66
-32
lines changed

5 files changed

+66
-32
lines changed

README.rst

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -315,15 +315,16 @@ authentication. This assertion contains attributes about the user that
315315
was authenticated. It depends on the IdP configuration what exact
316316
attributes are sent to each SP it can talk to.
317317

318-
When such assertion is received on the Django side it is used to find
319-
a Django user and create a session for it. By default djangosaml2 will
320-
do a query on the User model with the 'username' attribute but you can
321-
change it to any other attribute of the User model. For example,
322-
you can do this lookup using the 'email' attribute. In order to do so
323-
you should set the following setting::
318+
When such assertion is received on the Django side it is used to find a Django
319+
user and create a session for it. By default djangosaml2 will do a query on the
320+
User model with the USERNAME_FIELD_ attribute but you can change it to any
321+
other attribute of the User model. For example, you can do this lookup using
322+
the 'email' attribute. In order to do so you should set the following setting::
324323

325324
SAML_DJANGO_USER_MAIN_ATTRIBUTE = 'email'
326325

326+
.. _USERNAME_FIELD: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#django.contrib.auth.models.CustomUser.USERNAME_FIELD
327+
327328
Please, use an unique attribute when setting this option. Otherwise
328329
the authentication process may fail because djangosaml2 will not know
329330
which Django user it should pick.

djangosaml2/backends.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323

2424
from djangosaml2.signals import pre_user_save
2525

26-
from . import settings as saml_settings
27-
2826
try:
2927
from django.contrib.auth.models import SiteProfileNotAvailable
3028
except ImportError:
@@ -85,8 +83,7 @@ def authenticate(self, request, session_info=None, attribute_mapping=None,
8583
use_name_id_as_username = getattr(
8684
settings, 'SAML_USE_NAME_ID_AS_USERNAME', False)
8785

88-
django_user_main_attribute = saml_settings.SAML_DJANGO_USER_MAIN_ATTRIBUTE
89-
django_user_main_attribute_lookup = saml_settings.SAML_DJANGO_USER_MAIN_ATTRIBUTE_LOOKUP
86+
django_user_main_attribute = self.get_django_user_main_attribute()
9087

9188
logger.debug('attributes: %s', attributes)
9289
saml_user = None
@@ -137,15 +134,19 @@ def clean_user_main_attribute(self, main_attribute):
137134
"""
138135
return main_attribute
139136

140-
def get_user_query_args(self, main_attribute):
141-
django_user_main_attribute = getattr(
142-
settings, 'SAML_DJANGO_USER_MAIN_ATTRIBUTE', 'username')
143-
django_user_main_attribute_lookup = getattr(
144-
settings, 'SAML_DJANGO_USER_MAIN_ATTRIBUTE_LOOKUP', '')
137+
def get_django_user_main_attribute(self):
138+
return getattr(
139+
settings,
140+
'SAML_DJANGO_USER_MAIN_ATTRIBUTE',
141+
getattr(get_saml_user_model(), 'USERNAME_FIELD', 'username'))
145142

146-
return {
147-
django_user_main_attribute + django_user_main_attribute_lookup: main_attribute
148-
}
143+
def get_django_user_main_attribute_lookup(self):
144+
return getattr(settings, 'SAML_DJANGO_USER_MAIN_ATTRIBUTE_LOOKUP', '')
145+
146+
def get_user_query_args(self, main_attribute):
147+
lookup = (self.get_django_user_main_attribute() +
148+
self.get_django_user_main_attribute_lookup())
149+
return {lookup: main_attribute}
149150

150151
def get_saml2_user(self, create, main_attribute, attributes, attribute_mapping):
151152
if create:
@@ -156,8 +157,7 @@ def get_saml2_user(self, create, main_attribute, attributes, attribute_mapping):
156157
def _get_or_create_saml2_user(self, main_attribute, attributes, attribute_mapping):
157158
logger.debug('Check if the user "%s" exists or create otherwise',
158159
main_attribute)
159-
django_user_main_attribute = saml_settings.SAML_DJANGO_USER_MAIN_ATTRIBUTE
160-
django_user_main_attribute_lookup = saml_settings.SAML_DJANGO_USER_MAIN_ATTRIBUTE_LOOKUP
160+
django_user_main_attribute = self.get_django_user_main_attribute()
161161
user_query_args = self.get_user_query_args(main_attribute)
162162
user_create_defaults = {django_user_main_attribute: main_attribute}
163163

@@ -180,7 +180,7 @@ def _get_or_create_saml2_user(self, main_attribute, attributes, attribute_mappin
180180

181181
def _get_saml2_user(self, main_attribute, attributes, attribute_mapping):
182182
User = get_saml_user_model()
183-
django_user_main_attribute = saml_settings.SAML_DJANGO_USER_MAIN_ATTRIBUTE
183+
django_user_main_attribute = self.get_django_user_main_attribute()
184184
user_query_args = self.get_user_query_args(main_attribute)
185185

186186
logger.debug('Retrieving existing user "%s"', main_attribute)

djangosaml2/settings.py

Lines changed: 0 additions & 6 deletions
This file was deleted.

tests/testprofiles/models.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515

1616
import django
1717
from django.db import models
18-
from django.contrib.auth.models import User
19-
from django.conf import settings
2018

2119
if django.VERSION < (1, 7):
2220
class TestProfile(models.Model):
@@ -29,6 +27,12 @@ def process_first_name(self, first_name):
2927
from django.contrib.auth.models import AbstractUser
3028
class TestUser(AbstractUser):
3129
age = models.CharField(max_length=100, blank=True)
32-
3330
def process_first_name(self, first_name):
3431
self.first_name = first_name[0]
32+
33+
class StandaloneUserModel(models.Model):
34+
"""
35+
Does not inherit from Django's base abstract user and does not define a
36+
USERNAME_FIELD.
37+
"""
38+
username = models.CharField(max_length=30, unique=True)

tests/testprofiles/tests.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
else:
2424
User = get_user_model()
2525

26-
from django.test import TestCase
26+
from django.contrib.auth.models import User as DjangoUserModel
27+
28+
from django.test import TestCase, override_settings
2729

2830
from djangosaml2.backends import Saml2Backend
2931

@@ -110,4 +112,37 @@ def test_update_user_empty_attribute(self):
110112
self.assertEquals(user.email, 'john@example.com')
111113
self.assertEquals(user.first_name, 'John')
112114
# empty attribute list: no update
113-
self.assertEquals(user.last_name, 'Smith')
115+
self.assertEquals(user.last_name, 'Smith')
116+
117+
def test_django_user_main_attribute(self):
118+
backend = Saml2Backend()
119+
120+
old_username_field = User.USERNAME_FIELD
121+
User.USERNAME_FIELD = 'slug'
122+
self.assertEquals(backend.get_django_user_main_attribute(), 'slug')
123+
User.USERNAME_FIELD = old_username_field
124+
125+
with override_settings(AUTH_USER_MODEL='auth.User'):
126+
self.assertEquals(
127+
DjangoUserModel.USERNAME_FIELD,
128+
backend.get_django_user_main_attribute())
129+
130+
with override_settings(
131+
AUTH_USER_MODEL='testprofiles.StandaloneUserModel'):
132+
self.assertEquals(
133+
backend.get_django_user_main_attribute(),
134+
'username')
135+
136+
with override_settings(SAML_DJANGO_USER_MAIN_ATTRIBUTE='foo'):
137+
self.assertEquals(backend.get_django_user_main_attribute(), 'foo')
138+
139+
def test_django_user_main_attribute_lookup(self):
140+
backend = Saml2Backend()
141+
142+
self.assertEquals(backend.get_django_user_main_attribute_lookup(), '')
143+
144+
with override_settings(
145+
SAML_DJANGO_USER_MAIN_ATTRIBUTE_LOOKUP='__iexact'):
146+
self.assertEquals(
147+
backend.get_django_user_main_attribute_lookup(),
148+
'__iexact')

0 commit comments

Comments
 (0)