diff --git a/ssoauth/app_settings/defaults.py b/ssoauth/app_settings/defaults.py index 23fb520780e4eaa51526d593b74ad9821292ce03..37339892d14d5d10d87c47a20831a2aa44198617 100644 --- a/ssoauth/app_settings/defaults.py +++ b/ssoauth/app_settings/defaults.py @@ -33,6 +33,7 @@ SP_SLS_ENABLED = False # single log out creates too many problems, so it is dis SP_SLS_X_FRAME_OPTIONS = None # in case you encounter problems with SLS view not allowed inside of an iframe, e.g. "ALLOW-FROM idp-test.it.hs-hannover.de idp.hs-hannover.de" GROUPS_SAML_ATTRIBUTE = "IDMGroups" # this SAML attribute is expected to contain list of groups for a user +GROUP_RESOLVER = "ssoauth.auth_utils.groups_from_saml2_dn_list" # in case you want to override how groups are resolved for users """ Settings you might want to change on development (don't change them for production): diff --git a/ssoauth/auth_utils.py b/ssoauth/auth_utils.py index 98b4f7920e01c7733f207a51f93491f663469a67..b528e00a244ed9212cffedbb154a1c332c890c4b 100644 --- a/ssoauth/auth_utils.py +++ b/ssoauth/auth_utils.py @@ -7,6 +7,7 @@ from . import models from . import logger from . import app_settings import functools +import importlib def _validate_username(username): @@ -92,22 +93,41 @@ def update_user_data(user, surname=None, forename=None, email=None): user.save() -def set_user_groups(user, group_dn_list): +def set_user_groups(user, saml2_groups): """ Updates groups for the user. """ + # get the group resolver method + try: + resolver_path = app_settings.GROUP_RESOLVER.split(".") + assert len(resolver_path) >= 2, "need full path of the resolver method" + resolver_module_name = ".".join(resolver_path[:-1]) + resolver_method_name = resolver_path[-1] + resolver_module = importlib.import_module(resolver_module_name) + resolver_method = getattr(resolver_module, resolver_method_name) + except Exception as e: + raise ImportError("Could not import {r}. {e.__class__.__name__}: {e}".format(r=app_settings.GROUP_RESOLVER, e=e)) + # resolve the groups + groups = resolver_method(user, saml2_groups) + assert isinstance(groups, (list, tuple, set,)) and all(isinstance(g, Group) for g in groups), \ + "{r} instead of a list/tuple/set of Group objects returned: {garbage}".format(r=app_settings.GROUP_RESOLVER, garbage=groups) + # update user groups + if set(user.groups.all()) != set(groups): + user.groups.set(groups) + assert set(user.groups.all()) == set(groups) # dunno how relation.set() behaves, better safe than sorry + logger.info("Groups for {user} were updated.".format(user=user)) + # done + logger.info("User {user} is member of: {groups}".format(user=user, groups=set(str(g) for g in groups))) + + +def groups_from_saml2_dn_list(user, groups_dn_list): + """ This default group resolver returns list of Group objects based on DN list """ # using Q to create ignore-case DN lookup since DS is case insensitive - q_list = [Q(sso_mapping__dn__iexact=dn) for dn in group_dn_list] + q_list = [Q(sso_mapping__dn__iexact=dn) for dn in groups_dn_list] if q_list: q_combined = functools.reduce(lambda q1, q2: q1 | q2, q_list) groups = list(Group.objects.filter(q_combined)) else: groups = list() - # modify user groups - if set(user.groups.all()) != set(groups): - user.groups.set(groups) - assert set(user.groups.all()) == set(groups) # dunno how relation.set() behaves, better safe than sorry - logger.info("Groups for {user} are updated to: {groups}".format(user=user, groups=", ".join(g.name for g in groups))) - logger.debug("User {user} has {g_n} group(s) based on {dn_n} DN(s): {g_names}".format( - user=user, g_n=len(groups), g_names=", ".join(str(g) for g in groups) or "(none)", dn_n=len(group_dn_list))) + return groups def cleanup_direct_permissions(user):