Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
DylannCordel committed Jun 7, 2021
1 parent 7ea562f commit e8ea3ec
Show file tree
Hide file tree
Showing 4 changed files with 330 additions and 34 deletions.
6 changes: 6 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Changes
=======

v1.3.0 (future)

- Add signals
- Refactoring some classes to facilitate inheritance / customisation
- Documentation for signals and inheritance / customisation

v1.2.2 (2021-05-27)
-------------------

Expand Down
85 changes: 55 additions & 30 deletions djangosaml2/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
from django.core.exceptions import (ImproperlyConfigured,
MultipleObjectsReturned)

from .signals import authenticate, pre_user_save, post_user_save


logger = logging.getLogger('djangosaml2')

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

if not self.is_authorized(attributes, attribute_mapping, idp_entityid, assertion_info):
logger.error('Request not authorized')
authenticate.send(sender=self,
request=request,
is_authorized=False,
can_authenticate=None,
user=None,
user_created=None,
attributes=attributes)
return None

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

if self.user_can_authenticate(user):
can_authenticate = self.user_can_authenticate(user)
authenticate.send(sender=self,
request=request,
is_authorized=True,
can_authenticate=can_authenticate,
user=user,
user_created=created,
attributes=attributes)
if can_authenticate:
return user

def _update_user(self, user, attributes: dict, attribute_mapping: dict, force_save: bool = False):
Expand All @@ -156,46 +173,54 @@ def _update_user(self, user, attributes: dict, attribute_mapping: dict, force_sa
if not attribute_mapping:
# Always save a brand new user instance
if user.pk is None:
pre_user_save.send(sender=self, user=user, attributes=attributes)
user = self.save_user(user)
post_user_save.send(sender=self, user=user, attributes=attributes)
return user

# Lookup key
user_lookup_key = self._user_lookup_attribute
has_updated_fields = self.lookup_and_set_attributes(user, attributes, attribute_mapping)

if has_updated_fields or force_save:
pre_user_save.send(sender=self, user=user, attributes=attributes)
user = self.save_user(user)
post_user_save.send(sender=self, user=user, attributes=attributes)

return user

# ################################################
# Methods to override by end-users in subclasses #
# ################################################

def lookup_and_set_attributes(self, user, attributes: dict, attribute_mapping: dict) -> bool:
has_updated_fields = False
for saml_attr, django_attrs in attribute_mapping.items():
attr_value_list = attributes.get(saml_attr)
if not attr_value_list:
logger.debug(
f'Could not find value for "{saml_attr}", not updating fields "{django_attrs}"')
continue

return has_updated_fields
for attr in django_attrs:
if attr == user_lookup_key:
# Don't update user_lookup_key (e.g. username) (issue #245)
# It was just used to find/create this user and might have
# been changed by `clean_user_main_attribute`
continue
elif hasattr(user, attr):
user_attr = getattr(user, attr)
if callable(user_attr):
modified = user_attr(attr_value_list)
else:
modified = set_attribute(
user, attr, attr_value_list[0])

has_updated_fields = has_updated_fields or modified
else:
logger.debug(
f'Could not find attribute "{attr}" on user "{user}"')

if has_updated_fields or force_save:
user = self.save_user(user)

return user

# ############################################
# Hooks to override by end-users in subclasses
# ############################################
has_updated_fields = has_updated_fields or self.lookup_and_set_attribute(
user, attr, attr_value_list
)
return has_updated_fields

def lookup_and_set_attribute(self, user, attr, attr_value_list) -> bool:
if attr == self._user_lookup_attribute:
# Don't update user_lookup_key (e.g. username) (issue #245)
# It was just used to find/create this user and might have
# been changed by `clean_user_main_attribute`
return False
elif hasattr(user, attr):
user_attr = getattr(user, attr)
if callable(user_attr):
return user_attr(attr_value_list)
else:
return set_attribute(user, attr, attr_value_list[0])
else:
logger.debug(f'Could not find attribute "{attr}" on user "{user}"')
return False

def clean_attributes(self, attributes: dict, idp_entityid: str, **kwargs) -> dict:
""" Hook to clean or filter attributes from the SAML response. No-op by default. """
Expand Down
9 changes: 5 additions & 4 deletions djangosaml2/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@

import django.dispatch

pre_user_save = django.dispatch.Signal(
providing_args=['attributes', 'user_modified'])
post_authenticated = django.dispatch.Signal(
providing_args=['session_info', 'request'])
pre_user_save = django.dispatch.Signal(providing_args=['user', 'attributes'])
post_user_save = django.dispatch.Signal(providing_args=['user', 'attributes'])
authenticate = django.dispatch.Signal(providing_args=[
'request', 'is_authorized', 'can_authenticate', 'user', 'user_created', 'attributes'
])
Loading

0 comments on commit e8ea3ec

Please sign in to comment.