summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--asgard/settings.d/20-saml.conf4
-rw-r--r--meetingtools/saml.py174
2 files changed, 176 insertions, 2 deletions
diff --git a/asgard/settings.d/20-saml.conf b/asgard/settings.d/20-saml.conf
index 6a786fc..1565dd0 100644
--- a/asgard/settings.d/20-saml.conf
+++ b/asgard/settings.d/20-saml.conf
@@ -2,7 +2,7 @@
from django.conf import settings
from saml2 import BINDING_HTTP_POST, BINDING_HTTP_REDIRECT
-AUTH_PROFILE_MODULE = 'profile.UserProfile'
+AUTH_PROFILE_MODULE = 'meetingtools.apps.userprofile.UserProfile'
SAML_ATTRIBUTE_MAPPING = {
'username': ['eduPersonPrincipalName'],
@@ -19,6 +19,6 @@ SAML_METADATA_FILE = "/var/run/swamid-idp-transitive.xml"
SAML_CREATE_UNKNOWN_USER = True
SAML_CONFIG_LOADER = "meetingtools.apps.auth.asgard_sp_config"
-#AUTHENTICATION_BACKENDS += ['keybucket.utils.saml.TargetedUsernameSamlBackend']
+#AUTHENTICATION_BACKENDS += ['meetingtools.saml.Saml2Backend']
AUTO_REMOTE_SUPERUSERS = ['leifj@nordu.net'] \ No newline at end of file
diff --git a/meetingtools/saml.py b/meetingtools/saml.py
new file mode 100644
index 0000000..22bbe53
--- /dev/null
+++ b/meetingtools/saml.py
@@ -0,0 +1,174 @@
+from django.contrib.auth.backends import ModelBackend
+from django.contrib.auth.models import SiteProfileNotAvailable, User
+from django.core.exceptions import ObjectDoesNotExist
+import logging
+from saml2.saml import name_id_type__from_string
+from assurance.models import IdentityProvider
+
+logger = logging.getLogger('djangosaml2')
+
+__author__ = 'leifj'
+
+class Saml2Backend(ModelBackend):
+
+ """This backend is added automatically by the assertion_consumer_service
+ view.
+
+ Don't add it to settings.AUTHENTICATION_BACKENDS.
+ """
+
+ def _set(self,o,django_attr,saml_attrs,attributes):
+ for saml_attr in saml_attrs:
+ if attributes.has_key(saml_attr):
+ setattr(o, django_attr, attributes[saml_attr][0])
+ return True
+ return False
+
+ def get_saml_user(self,session_info,attribute_mapping):
+ attributes = session_info['ava']
+ if not attributes:
+ logger.error('The attributes dictionary is empty')
+
+ for saml_attr, django_fields in attribute_mapping.items():
+ if 'username' in django_fields and saml_attr in attributes:
+ return attributes[saml_attr][0]
+ return None
+
+ 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
+
+ print session_info
+
+ saml_user = self.get_saml_user(session_info,attribute_mapping)
+
+ if saml_user is None:
+ logger.error('Could not find saml_user value')
+ return None
+
+ user = None
+ username = self.clean_username(saml_user)
+
+ # 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' % username)
+ user, created = User.objects.get_or_create(username=username)
+ if created:
+ logger.debug('New user created')
+ user = self.configure_user(user, session_info, attribute_mapping)
+ else:
+ logger.debug('User updated')
+ user = self.update_user(user, session_info, attribute_mapping)
+ else:
+ logger.debug('Retrieving existing user "%s"' % username)
+ try:
+ user = User.objects.get(username=username)
+ user = self.update_user(user, session_info, attribute_mapping)
+ except User.DoesNotExist:
+ logger.error('The user "%s" does not exist' % username)
+ pass
+
+ return user
+
+ def clean_username(self, username):
+ """Performs any cleaning on the "username" prior to using it to get or
+ create the user object. Returns the cleaned username.
+
+ By default, returns the username unchanged.
+ """
+ return username
+
+ def configure_user(self, user, session_info, 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()
+ user = self.update_user(user, session_info, attribute_mapping, force_save=True)
+ try:
+ profile = user.get_profile()
+ if profile is not None and hasattr(profile,'idp'):
+ profile.idp = session_info['issuer']
+ profile.save()
+ #auto-populate idp table
+ idp_object,created = IdentityProvider.objects.get_or_create(uri=profile.idp)
+ except Exception:
+ pass
+
+ return user
+
+ def update_user(self, user, session_info, 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
+
+ attributes = session_info['ava']
+ if not attributes:
+ logger.error('The attributes dictionary is empty')
+
+ try:
+ profile = user.get_profile()
+ except ObjectDoesNotExist:
+ profile = None
+ except SiteProfileNotAvailable:
+ profile = None
+
+ user_modified = False
+ profile_modified = False
+ for django_attr,saml_attrs in attribute_mapping.items():
+ try:
+ if hasattr(user, django_attr):
+ user_modified = self._set(user,django_attr,saml_attrs,attributes)
+
+ elif profile is not None and hasattr(profile, django_attr):
+ profile_modified = self._set(profile,django_attr,saml_attrs,attributes)
+
+ except KeyError:
+ # the saml attribute is missing
+ pass
+
+ if user_modified or force_save:
+ user.save()
+
+ if profile_modified or force_save:
+ profile.save()
+
+ return user
+
+class TargetedUsernameSamlBackend(Saml2Backend):
+ def get_saml_user(self,session_info,attributes,attribute_mapping):
+
+ eptid = attributes.get('eduPersonTargetedID',None)
+ if eptid is not None:
+ try:
+ name_id_o = name_id_type__from_string(eptid)
+ return "%s!%s!%s" % (name_id_o.name_qualifier,name_id_o.sp_name_qualifier,name_id_o.text)
+ except Exception,ex:
+ logger.error(ex)
+ pass
+
+ username = None
+ print attribute_mapping
+ if attribute_mapping.has_key('username'):
+ for saml_attr in attribute_mapping['username']:
+ if attributes.has_key(saml_attr):
+ username = attributes[saml_attr][0]
+
+ if username is None:
+ return None
+
+ return username
+ #return "%s!%s!%s" % (session_info['issuer'],session_info.get('entity_id',""),username) \ No newline at end of file