summaryrefslogtreecommitdiff
path: root/coip
diff options
context:
space:
mode:
Diffstat (limited to 'coip')
-rw-r--r--coip/apps/auth/__init__.py79
-rw-r--r--coip/apps/utils/__init__.py0
-rw-r--r--coip/apps/utils/saml.py160
3 files changed, 239 insertions, 0 deletions
diff --git a/coip/apps/auth/__init__.py b/coip/apps/auth/__init__.py
index e69de29..06effdf 100644
--- a/coip/apps/auth/__init__.py
+++ b/coip/apps/auth/__init__.py
@@ -0,0 +1,79 @@
+__author__ = 'leifj'
+
+from django.conf import settings
+from saml2.config import SPConfig
+import copy
+from saml2 import BINDING_HTTP_POST, BINDING_HTTP_REDIRECT
+
+import logging
+logging.basicConfig()
+logger = logging.getLogger("djangosaml2")
+logger.setLevel(logging.DEBUG)
+
+def asgard_sp_config(request=None):
+ host = "localhost"
+ if request != None:
+ host = request.get_host().replace(":","-")
+ x= {
+ # your entity id, usually your subdomain plus the url to the metadata view
+ 'entityid': 'https://coip.app.nordu.net/saml2/sp/metadata',
+ # directory with attribute mapping
+ "attribute_map_dir" : "%s/saml2/attributemaps" % settings.BASE_DIR,
+ # this block states what services we provide
+ 'service': {
+ # we are just a lonely SP
+ 'sp' : {
+ 'name': 'COIP',
+ 'endpoints': {
+ # url and binding to the assertion consumer service view
+ # do not change the binding osettingsr service name
+ 'assertion_consumer_service': [
+ ('https://coip.app.nordu.net/saml2/sp/acs/',
+ BINDING_HTTP_POST),
+ ],
+ # url and binding to the single logout service view
+ # do not change the binding or service name
+ 'single_logout_service': [
+ ('https://coip.app.nordu.net/saml2/sp/ls/',
+ BINDING_HTTP_REDIRECT),
+ ],
+ },
+ # attributes that this project need to identify a user
+ 'required_attributes': ['eduPersonPrincipalName','displayName'],
+ }
+ },
+
+ # where the remote metadata is stored
+ #'metadata': { 'remote': [{'url':'http://md.swamid.se/md/swamid-idp.xml',
+ # 'cert':'%s/saml2/credentials/md-signer.crt' % settings.BASE_DIR}] },
+ 'metadata': {'local': [settings.SAML_METADATA_FILE]},
+
+ # set to 1 to output debugging information
+ 'debug': 1,
+
+ # certificate
+ "key_file" : "%s/%s.key" % (settings.SSL_KEY_DIR,host),
+ "cert_file" : "%s/%s.crt" % (settings.SSL_CRT_DIR,host),
+ # own metadata settings
+ 'contact_person': [
+ {'given_name': 'Leif',
+ 'sur_name': 'Johansson',
+ 'company': 'NORDUnet',
+ 'email_address': 'leifj@nordu.net',
+ 'contact_type': 'technical'},
+ {'given_name': 'Johan',
+ 'sur_name': 'Berggren',
+ 'company': 'NORDUnet',
+ 'email_address': 'jbn@nordu.net',
+ 'contact_type': 'technical'},
+ ],
+ # you can set multilanguage information here
+ 'organization': {
+ 'name': [('NORDUNet', 'en')],
+ 'display_name': [('NORDUnet A/S', 'en')],
+ 'url': [('http://www.nordu.net', 'en')],
+ }
+ }
+ c = SPConfig()
+ c.load(copy.deepcopy(x))
+ return c
diff --git a/coip/apps/utils/__init__.py b/coip/apps/utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/coip/apps/utils/__init__.py
diff --git a/coip/apps/utils/saml.py b/coip/apps/utils/saml.py
new file mode 100644
index 0000000..bad3d60
--- /dev/null
+++ b/coip/apps/utils/saml.py
@@ -0,0 +1,160 @@
+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
+
+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,attributes,attribute_mapping):
+ 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
+
+ attributes = session_info['ava']
+ if not attributes:
+ logger.error('The attributes dictionary is empty')
+
+ saml_user = self.get_saml_user(session_info,attributes,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, attributes, attribute_mapping)
+ else:
+ logger.debug('User updated')
+ user = self.update_user(user, attributes, attribute_mapping)
+ else:
+ logger.debug('Retrieving existing user "%s"' % username)
+ try:
+ user = User.objects.get(username=username)
+ user = self.update_user(user, attributes, 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, 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
+
+ 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