Skip to content

Commit dab40da

Browse files
committed
wip #287
1 parent 7ea562f commit dab40da

File tree

5 files changed

+330
-34
lines changed

5 files changed

+330
-34
lines changed

CHANGES

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Changes
22
=======
33

4+
v1.3.0 (future)
5+
6+
- Add signals
7+
- Refactoring some classes to facilitate inheritance / customisation
8+
- Documentation for signals and inheritance / customisation
9+
410
v1.2.2 (2021-05-27)
511
-------------------
612

djangosaml2/backends.py

+54-29
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
from django.core.exceptions import (ImproperlyConfigured,
2525
MultipleObjectsReturned)
2626

27+
from .signals import authenticate, pre_user_save, post_user_save
28+
2729

2830
logger = logging.getLogger('djangosaml2')
2931

@@ -123,6 +125,13 @@ def authenticate(self, request, session_info=None, attribute_mapping=None, creat
123125

124126
if not self.is_authorized(attributes, attribute_mapping, idp_entityid, assertion_info):
125127
logger.error('Request not authorized')
128+
authenticate.send(sender=self,
129+
request=request,
130+
is_authorized=False,
131+
can_authenticate=None,
132+
user=None,
133+
user_created=None,
134+
attributes=attributes)
126135
return None
127136

128137
user_lookup_key, user_lookup_value = self._extract_user_identifier_params(
@@ -141,7 +150,15 @@ def authenticate(self, request, session_info=None, attribute_mapping=None, creat
141150
user = self._update_user(
142151
user, attributes, attribute_mapping, force_save=created)
143152

144-
if self.user_can_authenticate(user):
153+
can_authenticate = self.user_can_authenticate(user)
154+
authenticate.send(sender=self,
155+
request=request,
156+
is_authorized=True,
157+
can_authenticate=can_authenticate,
158+
user=user,
159+
user_created=created,
160+
attributes=attributes)
161+
if can_authenticate:
145162
return user
146163

147164
def _update_user(self, user, attributes: dict, attribute_mapping: dict, force_save: bool = False):
@@ -156,46 +173,54 @@ def _update_user(self, user, attributes: dict, attribute_mapping: dict, force_sa
156173
if not attribute_mapping:
157174
# Always save a brand new user instance
158175
if user.pk is None:
176+
pre_user_save.send(sender=self, user=user, attributes=attributes)
159177
user = self.save_user(user)
178+
post_user_save.send(sender=self, user=user, attributes=attributes)
160179
return user
161180

162181
# Lookup key
163-
user_lookup_key = self._user_lookup_attribute
182+
has_updated_fields = self.lookup_and_set_attributes(user, attributes, attribute_mapping)
183+
184+
if has_updated_fields or force_save:
185+
pre_user_save.send(sender=self, user=user, attributes=attributes)
186+
user = self.save_user(user)
187+
post_user_save.send(sender=self, user=user, attributes=attributes)
188+
189+
return user
190+
191+
# ################################################
192+
# Methods to override by end-users in subclasses #
193+
# ################################################
194+
195+
def lookup_and_set_attributes(self, user, attributes: dict, attribute_mapping: dict) -> bool:
164196
has_updated_fields = False
165197
for saml_attr, django_attrs in attribute_mapping.items():
166198
attr_value_list = attributes.get(saml_attr)
167199
if not attr_value_list:
168200
logger.debug(
169201
f'Could not find value for "{saml_attr}", not updating fields "{django_attrs}"')
170202
continue
171-
172203
for attr in django_attrs:
173-
if attr == user_lookup_key:
174-
# Don't update user_lookup_key (e.g. username) (issue #245)
175-
# It was just used to find/create this user and might have
176-
# been changed by `clean_user_main_attribute`
177-
continue
178-
elif hasattr(user, attr):
179-
user_attr = getattr(user, attr)
180-
if callable(user_attr):
181-
modified = user_attr(attr_value_list)
182-
else:
183-
modified = set_attribute(
184-
user, attr, attr_value_list[0])
185-
186-
has_updated_fields = has_updated_fields or modified
187-
else:
188-
logger.debug(
189-
f'Could not find attribute "{attr}" on user "{user}"')
190-
191-
if has_updated_fields or force_save:
192-
user = self.save_user(user)
193-
194-
return user
195-
196-
# ############################################
197-
# Hooks to override by end-users in subclasses
198-
# ############################################
204+
has_updated_fields = self.lookup_and_set_attribute(
205+
user, attr, attr_value_list
206+
) or has_updated_fields
207+
return has_updated_fields
208+
209+
def lookup_and_set_attribute(self, user, attr, attr_value_list) -> bool:
210+
if attr == self._user_lookup_attribute:
211+
# Don't update user_lookup_key (e.g. username) (issue #245)
212+
# It was just used to find/create this user and might have
213+
# been changed by `clean_user_main_attribute`
214+
return False
215+
elif hasattr(user, attr):
216+
user_attr = getattr(user, attr)
217+
if callable(user_attr):
218+
return user_attr(attr_value_list)
219+
else:
220+
return set_attribute(user, attr, attr_value_list[0])
221+
else:
222+
logger.debug(f'Could not find attribute "{attr}" on user "{user}"')
223+
return False
199224

200225
def clean_attributes(self, attributes: dict, idp_entityid: str, **kwargs) -> dict:
201226
""" Hook to clean or filter attributes from the SAML response. No-op by default. """

djangosaml2/signals.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414

1515
import django.dispatch
1616

17-
pre_user_save = django.dispatch.Signal(
18-
providing_args=['attributes', 'user_modified'])
19-
post_authenticated = django.dispatch.Signal(
20-
providing_args=['session_info', 'request'])
17+
pre_user_save = django.dispatch.Signal(providing_args=['user', 'attributes'])
18+
post_user_save = django.dispatch.Signal(providing_args=['user', 'attributes'])
19+
authenticate = django.dispatch.Signal(providing_args=[
20+
'request', 'is_authorized', 'can_authenticate', 'user', 'user_created', 'attributes'
21+
])

0 commit comments

Comments
 (0)