from collections import OrderedDict from fnmatch import fnmatch import logging from django.conf import settings from django.contrib.auth.middleware import RemoteUserMiddleware from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect from seaserv import seafile_api from shibboleth.app_settings import SHIB_ATTRIBUTE_MAP, LOGOUT_SESSION_KEY, SHIB_USER_HEADER from seahub import auth from seahub.base.accounts import User from seahub.base.sudo_mode import update_sudo_mode_ts from seahub.profile.models import Profile from seahub.utils.file_size import get_quota_from_string from seahub.utils.user_permissions import get_user_role # Get an instance of a logger logger = logging.getLogger(__name__) class ShibbolethRemoteUserMiddleware(RemoteUserMiddleware): """ Authentication Middleware for use with Shibboleth. Uses the recommended pattern for remote authentication from: http://code.djangoproject.com/svn/django/tags/releases/1.3/django/contrib/auth/middleware.py """ def __init__(self, *a, **kw): super(ShibbolethRemoteUserMiddleware, self).__init__(*a, **kw) def process_request(self, request): # AuthenticationMiddleware is required so that request.user exists. if not hasattr(request, 'user'): raise ImproperlyConfigured( "The Django remote user auth middleware requires the" " authentication middleware to be installed. Edit your" " MIDDLEWARE_CLASSES setting to insert" " 'django.contrib.auth.middleware.AuthenticationMiddleware'" " before the RemoteUserMiddleware class.") #To support logout. If this variable is True, do not #authenticate user and return now. if request.session.get(LOGOUT_SESSION_KEY) is True: return else: #Delete the shib reauth session key if present. request.session.pop(LOGOUT_SESSION_KEY, None) #Locate the remote user header. # import pprint; pprint.pprint(request.META) try: username = request.META[SHIB_USER_HEADER] except KeyError: # If specified header doesn't exist then return (leaving # request.user set to AnonymousUser by the # AuthenticationMiddleware). return # If the user is already authenticated and that user is the user we are # getting passed in the headers, then the correct user is already # persisted in the session and we don't need to continue. if request.user.is_authenticated(): if request.user.username == username: if request.user.is_staff: update_sudo_mode_ts(request) return # Make sure we have all required Shiboleth elements before proceeding. shib_meta, error = self.parse_attributes(request) # Add parsed attributes to the session. request.session['shib'] = shib_meta if error: raise ShibbolethValidationError("All required Shibboleth elements" " not found. %s" % shib_meta) # We are seeing this user for the first time in this session, attempt # to authenticate the user. user = auth.authenticate(remote_user=username, shib_meta=shib_meta) if user: if not user.is_active: return HttpResponseRedirect(reverse('shib_complete')) # User is valid. Set request.user and persist user in the session # by logging the user in. request.user = user auth.login(request, user) user.set_unusable_password() user.save() # call make profile. self.make_profile(user, shib_meta) user_role = self.update_user_role(user, shib_meta) if user_role: self.update_user_quota(user, user_role) #setup session. self.setup_session(request) request.shib_login = True def process_response(self, request, response): if getattr(request, 'shib_login', False): print '%s: set shibboleth cookie!' % id(self) self._set_auth_cookie(request, response) return response def _set_auth_cookie(self, request, response): from seahub.api2.utils import get_token_v1, get_token_v2 # generate tokenv2 using information in request params keys = ( 'platform', 'device_id', 'device_name', 'client_version', 'platform_version', ) if all(['shib_' + key in request.GET for key in keys]): platform = request.GET['shib_platform'] device_id = request.GET['shib_device_id'] device_name = request.GET['shib_device_name'] client_version = request.GET['shib_client_version'] platform_version = request.GET['shib_platform_version'] token = get_token_v2( request, request.user.username, platform, device_id, device_name, client_version, platform_version) elif all(['shib_' + key not in request.GET for key in keys]): token = get_token_v1(request.user.username) else: return response.set_cookie('seahub_auth', request.user.username + '@' + token.key) def make_profile(self, user, shib_meta): """ Extrat nickname(givenname surname), contact_email, institution from Shib attributs, and add those to user profile. """ # use `display_name` as nickname in shib_meta first nickname = shib_meta.get('display_name', None) if nickname is None: # otherwise, fallback to givenname plus surname in shib_meta givenname = shib_meta.get('givenname', '') surname = shib_meta.get('surname', '') nickname = "%s %s" % (givenname, surname) institution = shib_meta.get('institution', None) contact_email = shib_meta.get('contact_email', None) p = Profile.objects.get_profile_by_user(user.username) if not p: p = Profile(user=user.username) if nickname.strip(): # set nickname when it's not empty p.nickname = nickname if institution: p.institution = institution if contact_email: p.contact_email = contact_email p.save() def _get_role_by_affiliation(self, affiliation): try: role_map = settings.SHIBBOLETH_AFFILIATION_ROLE_MAP except AttributeError: return role = role_map.get(affiliation) if role: return role if role_map.get('patterns') is not None: joker_map = role_map.get('patterns') try: od = OrderedDict(joker_map) except Exception as e: logger.error(e) return for k in od: if fnmatch(affiliation, k): return od[k] return None def update_user_role(self, user, shib_meta): affiliation = shib_meta.get('affiliation', '') if not affiliation: return for e in affiliation.split(';'): role = self._get_role_by_affiliation(e) if role: User.objects.update_role(user.email, role) return role def update_user_quota(self, user, user_role): if user.permissions.role_quota(): quota = get_quota_from_string(user.permissions.role_quota()) logger.info('Set quota[%d] for user: %s, role[%s]' % (quota, user.username, user_role)) seafile_api.set_role_quota(user_role, quota) else: return def setup_session(self, request): """ If you want to add custom code to setup user sessions, you can extend this. """ return def parse_attributes(self, request): """ Parse the incoming Shibboleth attributes. From: https://github.com/russell/django-shibboleth/blob/master/django_shibboleth/utils.py Pull the mapped attributes from the apache headers. """ shib_attrs = {} error = False meta = request.META for header, attr in SHIB_ATTRIBUTE_MAP.items(): required, name = attr value = meta.get(header, None) shib_attrs[name] = value if not value or value == '': if required: error = True return shib_attrs, error class ShibbolethValidationError(Exception): pass