| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- # Copyright (C) 2010-2012 Yaco Sistemas (http://www.yaco.es)
- # Copyright (C) 2009 Lorenzo Gil Sanchez <lorenzo.gil.sanchez@gmail.com>
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- import logging
- from django.conf import settings
- from django.contrib import auth
- from django.contrib.auth.backends import ModelBackend
- from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
- from djangosaml2.signals import pre_user_save
- try:
- from django.contrib.auth.models import SiteProfileNotAvailable
- except ImportError:
- class SiteProfileNotAvailable(Exception):
- pass
- logger = logging.getLogger('djangosaml2')
- # Django 1.5 Custom user model
- try:
- User = auth.get_user_model()
- except AttributeError:
- User = auth.models.User
- class Saml2Backend(ModelBackend):
- def authenticate(self, session_info=None, attribute_mapping=None,
- create_unknown_user=True):
- if session_info is None or attribute_mapping is None:
- logger.error('Session info or attribute mapping are None')
- return None
- if not 'ava' in session_info:
- logger.error('"ava" key not found in session_info')
- return None
- attributes = session_info['ava']
- if not attributes:
- logger.error('The attributes dictionary is empty')
- use_name_id_as_username = getattr(
- settings, 'SAML_USE_NAME_ID_AS_USERNAME', False)
- django_user_main_attribute = getattr(
- settings, 'SAML_DJANGO_USER_MAIN_ATTRIBUTE', 'username')
- logger.debug('attributes: %s' % attributes)
- saml_user = None
- if use_name_id_as_username:
- if 'name_id' in session_info:
- logger.debug('name_id: %s' % session_info['name_id'])
- saml_user = session_info['name_id'].text
- else:
- logger.error('The nameid is not available. Cannot find user without a nameid.')
- else:
- logger.debug('attribute_mapping: %s' % attribute_mapping)
- for saml_attr, django_fields in attribute_mapping.items():
- if (django_user_main_attribute in django_fields
- and saml_attr in attributes):
- saml_user = attributes[saml_attr][0]
- if saml_user is None:
- logger.error('Could not find saml_user value')
- return None
- if not self.is_authorized(attributes, attribute_mapping):
- return None
- user = None
- main_attribute = self.clean_user_main_attribute(saml_user)
- user_query_args = {django_user_main_attribute: main_attribute}
- # Note that this could be accomplished in one try-except clause, but
- # instead we use get_or_create when creating unknown users since it has
- # built-in safeguards for multiple threads.
- if create_unknown_user:
- logger.debug('Check if the user "%s" exists or create otherwise'
- % main_attribute)
- try:
- user, created = User.objects.get_or_create(**user_query_args)
- except MultipleObjectsReturned:
- logger.error("There are more than one user with %s = %s" %
- (django_user_main_attribute, main_attribute))
- return None
- if created:
- logger.debug('New user created')
- user = self.configure_user(user, attributes, attribute_mapping)
- else:
- logger.debug('User updated')
- user = self.update_user(user, attributes, attribute_mapping)
- else:
- logger.debug('Retrieving existing user "%s"' % main_attribute)
- try:
- user = User.objects.get(**user_query_args)
- user = self.update_user(user, attributes, attribute_mapping)
- except User.DoesNotExist:
- logger.error('The user "%s" does not exist' % main_attribute)
- return None
- except MultipleObjectsReturned:
- logger.error("There are more than one user with %s = %s" %
- (django_user_main_attribute, main_attribute))
- return None
- return user
- def is_authorized(self, attributes, attribute_mapping):
- """Hook to allow custom authorization policies based on
- SAML attributes.
- """
- return True
- def clean_user_main_attribute(self, main_attribute):
- """Performs any cleaning on the user main attribute (which
- usually is "username") prior to using it to get or
- create the user object. Returns the cleaned attribute.
- By default, returns the attribute unchanged.
- """
- return main_attribute
- def configure_user(self, user, attributes, attribute_mapping):
- """Configures a user after creation and returns the updated user.
- By default, returns the user with his attributes updated.
- """
- user.set_unusable_password()
- return self.update_user(user, attributes, attribute_mapping,
- force_save=True)
- def update_user(self, user, attributes, attribute_mapping,
- force_save=False):
- """Update a user with a set of attributes and returns the updated user.
- By default it uses a mapping defined in the settings constant
- SAML_ATTRIBUTE_MAPPING. For each attribute, if the user object has
- that field defined it will be set, otherwise it will try to set
- it in the profile object.
- """
- if not attribute_mapping:
- return user
- try:
- profile = user.get_profile()
- except ObjectDoesNotExist:
- profile = None
- except SiteProfileNotAvailable:
- profile = None
- # Django 1.5 custom model assumed
- except AttributeError:
- profile = user
- user_modified = False
- profile_modified = False
- for saml_attr, django_attrs in attribute_mapping.items():
- try:
- for attr in django_attrs:
- if hasattr(user, attr):
- modified = self._set_attribute(
- user, attr, attributes[saml_attr][0])
- user_modified = user_modified or modified
- elif profile is not None and hasattr(profile, attr):
- modified = self._set_attribute(
- profile, attr, attributes[saml_attr][0])
- profile_modified = profile_modified or modified
- except KeyError:
- # the saml attribute is missing
- pass
- logger.debug('Sending the pre_save signal')
- signal_modified = any(
- [response for receiver, response
- in pre_user_save.send_robust(sender=user,
- attributes=attributes,
- user_modified=user_modified)]
- )
- if user_modified or signal_modified or force_save:
- user.save()
- if (profile is not None
- and (profile_modified or signal_modified or force_save)):
- profile.save()
- return user
- def _set_attribute(self, obj, attr, value):
- """Set an attribute of an object to a specific value.
- Return True if the attribute was changed and False otherwise.
- """
- field = obj._meta.get_field_by_name(attr)
- if len(value) > field[0].max_length:
- cleaned_value = value[:field[0].max_length]
- logger.warn('The attribute "%s" was trimmed from "%s" to "%s"' %
- (attr, value, cleaned_value))
- else:
- cleaned_value = value
- old_value = getattr(obj, attr)
- if cleaned_value != old_value:
- setattr(obj, attr, cleaned_value)
- return True
- return False
|