Compare commits

...

4 Commits

Author SHA1 Message Date
Valentin Deniaud 1c3f3d887c utils: add method to check if a user has a role
gitea/django-mellon/pipeline/head Build started... Details
Along with a middleware to allow catching the exception it raises when
the user is missing roles, redirecting them appropriately.

A distinction is made between roles which are obtained at the SSO,
stored in session, and roles which the user could have, statically
stored in database.

todo: ce commit dépend totalement du provisionning tel qu'implémenté par
hobo, il faudrait améliorer ça
2019-07-17 16:26:15 +02:00
Valentin Deniaud 12a5327367 views: save is_staff in session 2019-05-21 12:06:04 +02:00
Valentin Deniaud 2aae735841 adapters: add a separate method to remove superuser flags
So that we can override it somewhere else.
2019-05-21 12:04:43 +02:00
Valentin Deniaud 0f26806791 views: handle role requests
Allows an application to request specific roles from the idp, using
"roles" query parameters.
2019-05-21 11:55:25 +02:00
5 changed files with 85 additions and 27 deletions

View File

@ -182,33 +182,20 @@ class DefaultAdapter(object):
user.save()
def provision_superuser(self, user, idp, saml_attributes):
superuser_mapping = utils.get_setting(idp, 'SUPERUSER_MAPPING')
if not superuser_mapping:
return
attribute_set = False
for key, values in superuser_mapping.items():
if key in saml_attributes:
if not isinstance(values, (tuple, list)):
values = [values]
values = set(values)
attribute_values = saml_attributes[key]
if not isinstance(attribute_values, (tuple, list)):
attribute_values = [attribute_values]
attribute_values = set(attribute_values)
if attribute_values & values:
if not (user.is_staff and user.is_superuser):
user.is_staff = True
user.is_superuser = True
attribute_set = True
self.logger.info('flag is_staff and is_superuser added to user %s', user)
break
if utils.has_superuser_flag(idp, saml_attributes):
if not (user.is_staff and user.is_superuser):
user.is_staff = True
user.is_superuser = True
user.save()
self.logger.info('flag is_staff and is_superuser added to user %s', user)
else:
if user.is_superuser or user.is_staff:
user.is_staff = False
user.is_superuser = False
self.logger.info('flag is_staff and is_superuser removed from user %s', user)
attribute_set = True
if attribute_set:
self.remove_superuser(user)
def remove_superuser(self, user):
if user.is_superuser or user.is_staff:
user.is_staff = False
user.is_superuser = False
self.logger.info('flag is_staff and is_superuser removed from user %s', user)
user.save()
def provision_groups(self, user, idp, saml_attributes):

4
mellon/exceptions.py Normal file
View File

@ -0,0 +1,4 @@
class RolesNotInSession(Exception):
def __init__(self, roles):
self.roles = roles

View File

@ -3,6 +3,7 @@ from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from . import app_settings, utils
from .exceptions import RolesNotInSession
PASSIVE_TRIED_COOKIE = 'MELLON_PASSIVE_TRIED'
@ -50,3 +51,10 @@ class PassiveAuthenticationMiddleware(object):
# prevent loops
response.set_cookie(PASSIVE_TRIED_COOKIE, value='1', max_age=None)
return response
class RolesRequestMiddleware(object):
def process_exception(self, request, exception):
if isinstance(exception, RolesNotInSession):
return HttpResponseRedirect(
utils.get_role_request_url(request, exception.roles))

View File

@ -6,7 +6,9 @@ import isodate
from xml.parsers import expat
from django.contrib import auth
from django.contrib.auth.models import Group
from django.core.urlresolvers import reverse
from django.http import QueryDict, HttpResponseRedirect
from django.template.loader import render_to_string
from django.utils.timezone import make_aware, now, make_naive, is_aware, get_default_timezone
from django.conf import settings
@ -14,6 +16,7 @@ from django.utils.six.moves.urllib.parse import urlparse
import lasso
from . import app_settings
from .exceptions import RolesNotInSession
def create_metadata(request):
@ -271,3 +274,49 @@ def get_local_path(request, url):
if request.META.get('SCRIPT_NAME'):
path = path[len(request.META['SCRIPT_NAME']):]
return path
def has_superuser_flag(idp, saml_attributes):
superuser_mapping = get_setting(idp, 'SUPERUSER_MAPPING')
if not superuser_mapping:
return False
for key, values in superuser_mapping.items():
if key in saml_attributes:
if not isinstance(values, (tuple, list)):
values = [values]
values = set(values)
attribute_values = saml_attributes[key]
if not isinstance(attribute_values, (tuple, list)):
attribute_values = [attribute_values]
attribute_values = set(attribute_values)
if attribute_values & values:
return True
return False
def user_has_roles(request, roles):
if request.user.is_staff and request.session.get('is_staff'):
return True
groups = set(roles).intersection(request.user.groups.all())
if not groups:
if request.user.is_staff:
raise RolesNotInSession(('staff',))
return False
role_uuids = {getattr(group, 'role').uuid for group in groups}
if not role_uuids:
return True
if set(request.session['mellon_session']['role-slug']) & role_uuids:
return True
raise RolesNotInSession(role_uuids)
def user_has_role(request, role):
return user_has_roles(request, {role})
def get_role_request_url(request, roles):
login_url = reverse(app_settings.LOGIN_URL)
q = QueryDict(mutable=True)
q.setlist('roles', roles)
q['next'] = request.get_full_path()
return '?'.join((login_url, q.urlencode(safe='/')))

View File

@ -19,6 +19,7 @@ from django.utils import six
from django.utils.encoding import force_text
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.db import transaction
from django.utils.six.moves.urllib.parse import urljoin
from django.utils.translation import ugettext as _
from . import app_settings, utils
@ -217,6 +218,8 @@ class LoginView(ProfileMixin, LogMixin, View):
if user is not None:
if user.is_active:
utils.login(request, user)
idp = self.get_idp(request)
request.session['is_staff'] = utils.has_superuser_flag(idp, attributes)
self.log.info('user %s (NameID is %r) logged in using SAML', user,
attributes['name_id_content'])
request.session['mellon_session'] = utils.flatten_datetime(attributes)
@ -375,6 +378,7 @@ class LoginView(ProfileMixin, LogMixin, View):
request, is_passive=request.GET.get('passive') == '1')
next_url = check_next_url(self.request, request.GET.get(REDIRECT_FIELD_NAME))
requested_roles = request.GET.getlist('roles')
idp = self.get_idp(request)
if idp is None:
return HttpResponseBadRequest('no idp found')
@ -394,7 +398,13 @@ class LoginView(ProfileMixin, LogMixin, View):
authn_request.isPassive = True
# configure requested AuthnClassRef
authn_classref = utils.get_setting(idp, 'AUTHN_CLASSREF')
if authn_classref:
if requested_roles:
prefix = 'https://entrouvert.com/authn-class-ref/role-uuid/' # TODO add setting
authn_classref = tuple(str(urljoin(prefix, role)) for role in requested_roles)
req_authncontext = lasso.Samlp2RequestedAuthnContext()
authn_request.requestedAuthnContext = req_authncontext
req_authncontext.authnContextClassRef = authn_classref
elif authn_classref:
authn_classref = tuple([str(x) for x in authn_classref])
req_authncontext = lasso.Samlp2RequestedAuthnContext()
authn_request.requestedAuthnContext = req_authncontext