backends.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. # Copyright (C) 2010-2012 Yaco Sistemas (http://www.yaco.es)
  2. # Copyright (C) 2009 Lorenzo Gil Sanchez <lorenzo.gil.sanchez@gmail.com>
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import logging
  16. from django.conf import settings
  17. from django.contrib import auth
  18. from django.contrib.auth.backends import ModelBackend
  19. from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
  20. from djangosaml2.signals import pre_user_save
  21. try:
  22. from django.contrib.auth.models import SiteProfileNotAvailable
  23. except ImportError:
  24. class SiteProfileNotAvailable(Exception):
  25. pass
  26. logger = logging.getLogger('djangosaml2')
  27. # Django 1.5 Custom user model
  28. try:
  29. User = auth.get_user_model()
  30. except AttributeError:
  31. User = auth.models.User
  32. class Saml2Backend(ModelBackend):
  33. def authenticate(self, session_info=None, attribute_mapping=None,
  34. create_unknown_user=True):
  35. if session_info is None or attribute_mapping is None:
  36. logger.error('Session info or attribute mapping are None')
  37. return None
  38. if not 'ava' in session_info:
  39. logger.error('"ava" key not found in session_info')
  40. return None
  41. attributes = session_info['ava']
  42. if not attributes:
  43. logger.error('The attributes dictionary is empty')
  44. use_name_id_as_username = getattr(
  45. settings, 'SAML_USE_NAME_ID_AS_USERNAME', False)
  46. django_user_main_attribute = getattr(
  47. settings, 'SAML_DJANGO_USER_MAIN_ATTRIBUTE', 'username')
  48. logger.debug('attributes: %s' % attributes)
  49. saml_user = None
  50. if use_name_id_as_username:
  51. if 'name_id' in session_info:
  52. logger.debug('name_id: %s' % session_info['name_id'])
  53. saml_user = session_info['name_id'].text
  54. else:
  55. logger.error('The nameid is not available. Cannot find user without a nameid.')
  56. else:
  57. logger.debug('attribute_mapping: %s' % attribute_mapping)
  58. for saml_attr, django_fields in attribute_mapping.items():
  59. if (django_user_main_attribute in django_fields
  60. and saml_attr in attributes):
  61. saml_user = attributes[saml_attr][0]
  62. if saml_user is None:
  63. logger.error('Could not find saml_user value')
  64. return None
  65. if not self.is_authorized(attributes, attribute_mapping):
  66. return None
  67. user = None
  68. main_attribute = self.clean_user_main_attribute(saml_user)
  69. user_query_args = {django_user_main_attribute: main_attribute}
  70. # Note that this could be accomplished in one try-except clause, but
  71. # instead we use get_or_create when creating unknown users since it has
  72. # built-in safeguards for multiple threads.
  73. if create_unknown_user:
  74. logger.debug('Check if the user "%s" exists or create otherwise'
  75. % main_attribute)
  76. try:
  77. user, created = User.objects.get_or_create(**user_query_args)
  78. except MultipleObjectsReturned:
  79. logger.error("There are more than one user with %s = %s" %
  80. (django_user_main_attribute, main_attribute))
  81. return None
  82. if created:
  83. logger.debug('New user created')
  84. user = self.configure_user(user, attributes, attribute_mapping)
  85. else:
  86. logger.debug('User updated')
  87. user = self.update_user(user, attributes, attribute_mapping)
  88. else:
  89. logger.debug('Retrieving existing user "%s"' % main_attribute)
  90. try:
  91. user = User.objects.get(**user_query_args)
  92. user = self.update_user(user, attributes, attribute_mapping)
  93. except User.DoesNotExist:
  94. logger.error('The user "%s" does not exist' % main_attribute)
  95. return None
  96. except MultipleObjectsReturned:
  97. logger.error("There are more than one user with %s = %s" %
  98. (django_user_main_attribute, main_attribute))
  99. return None
  100. return user
  101. def is_authorized(self, attributes, attribute_mapping):
  102. """Hook to allow custom authorization policies based on
  103. SAML attributes.
  104. """
  105. return True
  106. def clean_user_main_attribute(self, main_attribute):
  107. """Performs any cleaning on the user main attribute (which
  108. usually is "username") prior to using it to get or
  109. create the user object. Returns the cleaned attribute.
  110. By default, returns the attribute unchanged.
  111. """
  112. return main_attribute
  113. def configure_user(self, user, attributes, attribute_mapping):
  114. """Configures a user after creation and returns the updated user.
  115. By default, returns the user with his attributes updated.
  116. """
  117. user.set_unusable_password()
  118. return self.update_user(user, attributes, attribute_mapping,
  119. force_save=True)
  120. def update_user(self, user, attributes, attribute_mapping,
  121. force_save=False):
  122. """Update a user with a set of attributes and returns the updated user.
  123. By default it uses a mapping defined in the settings constant
  124. SAML_ATTRIBUTE_MAPPING. For each attribute, if the user object has
  125. that field defined it will be set, otherwise it will try to set
  126. it in the profile object.
  127. """
  128. if not attribute_mapping:
  129. return user
  130. try:
  131. profile = user.get_profile()
  132. except ObjectDoesNotExist:
  133. profile = None
  134. except SiteProfileNotAvailable:
  135. profile = None
  136. # Django 1.5 custom model assumed
  137. except AttributeError:
  138. profile = user
  139. user_modified = False
  140. profile_modified = False
  141. for saml_attr, django_attrs in attribute_mapping.items():
  142. try:
  143. for attr in django_attrs:
  144. if hasattr(user, attr):
  145. modified = self._set_attribute(
  146. user, attr, attributes[saml_attr][0])
  147. user_modified = user_modified or modified
  148. elif profile is not None and hasattr(profile, attr):
  149. modified = self._set_attribute(
  150. profile, attr, attributes[saml_attr][0])
  151. profile_modified = profile_modified or modified
  152. except KeyError:
  153. # the saml attribute is missing
  154. pass
  155. logger.debug('Sending the pre_save signal')
  156. signal_modified = any(
  157. [response for receiver, response
  158. in pre_user_save.send_robust(sender=user,
  159. attributes=attributes,
  160. user_modified=user_modified)]
  161. )
  162. if user_modified or signal_modified or force_save:
  163. user.save()
  164. if (profile is not None
  165. and (profile_modified or signal_modified or force_save)):
  166. profile.save()
  167. return user
  168. def _set_attribute(self, obj, attr, value):
  169. """Set an attribute of an object to a specific value.
  170. Return True if the attribute was changed and False otherwise.
  171. """
  172. field = obj._meta.get_field_by_name(attr)
  173. if len(value) > field[0].max_length:
  174. cleaned_value = value[:field[0].max_length]
  175. logger.warn('The attribute "%s" was trimmed from "%s" to "%s"' %
  176. (attr, value, cleaned_value))
  177. else:
  178. cleaned_value = value
  179. old_value = getattr(obj, attr)
  180. if cleaned_value != old_value:
  181. setattr(obj, attr, cleaned_value)
  182. return True
  183. return False