Skip to content
Snippets Groups Projects
Commit c4b09e94 authored by Art's avatar Art :lizard:
Browse files

Despagettify hsh app compat and make it more straightforward

parent 485cda0c
No related branches found
No related tags found
No related merge requests found
...@@ -28,7 +28,6 @@ If you are not sure whether you need it: you don't need it. ...@@ -28,7 +28,6 @@ If you are not sure whether you need it: you don't need it.
Use this only if you want an actual SSO with SAML2. For extra details see the default settings in `ssoauth.app_settings.defaults`. You might also need SSL, try using `nginx` for it. Use this only if you want an actual SSO with SAML2. For extra details see the default settings in `ssoauth.app_settings.defaults`. You might also need SSL, try using `nginx` for it.
```python ```python
""" settings/dev.py """
import os, socket import os, socket
from django import urls from django import urls
......
...@@ -31,8 +31,6 @@ SSO_REQUIRED_OUTSIDE_MANAGE_PY = True # enabled to ensure that production (that ...@@ -31,8 +31,6 @@ SSO_REQUIRED_OUTSIDE_MANAGE_PY = True # enabled to ensure that production (that
SP_SLS_ENABLED = False # single log out creates too many problems, so it is disabled for now SP_SLS_ENABLED = False # single log out creates too many problems, so it is disabled for now
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" 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"
GROUP_RESOLVER = "ssoauth.auth_utils.groups_from_group_names" # in case you want to override how groups are resolved for users
LOGIN_PERM_CODENAME = None # None or str; value "can_log_in" will require this permission for users to log in LOGIN_PERM_CODENAME = None # None or str; value "can_log_in" will require this permission for users to log in
PREDEFINED_GROUPS = { PREDEFINED_GROUPS = {
...@@ -80,6 +78,7 @@ ATTRIBUTE_SURNAME = "urn:oid:2.5.4.4" # "sn" ...@@ -80,6 +78,7 @@ ATTRIBUTE_SURNAME = "urn:oid:2.5.4.4" # "sn"
ATTRIBUTE_ACCOUNT_UUID = "UUID" # custom stuff, this one has no OID ATTRIBUTE_ACCOUNT_UUID = "UUID" # custom stuff, this one has no OID
ATTRIBUTE_GROUPS = "urn:oid:1.3.6.1.4.1.5923.1.5.1.1" # "isMemberOf" ATTRIBUTE_GROUPS = "urn:oid:1.3.6.1.4.1.5923.1.5.1.1" # "isMemberOf"
GROUP_RESOLVER = None # deprecated
""" """
This information will be published in this SP metadata... This information will be published in this SP metadata...
......
from uuid import UUID
from django.db import transaction from django.db import transaction
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from uuid import UUID
from . import models from . import models
from . import logger from . import logger
from . import app_settings from . import app_settings
from . import SUPERUSER_PERM_CODENAME, STAFF_PERM_CODENAME from . import SUPERUSER_PERM_CODENAME, STAFF_PERM_CODENAME
import importlib from .extras import hsh_compat
def _validate_username(username): def _validate_username(username):
...@@ -96,20 +96,24 @@ def update_user_data(user, surname=None, forename=None, email=None): ...@@ -96,20 +96,24 @@ def update_user_data(user, surname=None, forename=None, email=None):
user.save() user.save()
def set_user_groups(user, raw_values): def _groups_from_group_names(group_names):
""" Updates groups for the user. """ """ This default group resolver returns list of Group objects based on group names """
# get the group resolver method groups = set(Group.objects.filter(name__in=group_names))
try: # log missing groups, might help to understand what is going on
resolver_path = app_settings.GROUP_RESOLVER.split(".") found_group_names = {g.name for g in groups}
assert len(resolver_path) >= 2, "need full path of the resolver method" missing_group_names = set(group_names) - found_group_names
resolver_module_name = ".".join(resolver_path[:-1]) if missing_group_names:
resolver_method_name = resolver_path[-1] n = len(missing_group_names)
resolver_module = importlib.import_module(resolver_module_name) logger.info("Received {n} names of nonexistent groups: {missing_group_names}".format(**locals()))
resolver_method = getattr(resolver_module, resolver_method_name) # done
except Exception as e: return groups
raise ImportError("Could not import {r}. {e.__class__.__name__}: {e}".format(r=app_settings.GROUP_RESOLVER, e=e))
# resolve the groups
groups = set(resolver_method(user, raw_values)) def set_user_groups(user, group_names):
if hsh_compat.HSH_APP_COMPAT_POSSIBLE:
logger.info("Resolving groups names for {user} directly from hsh app.".format(user=user))
group_names = hsh_compat.get_group_names_for_user(user)
groups = set(_groups_from_group_names(group_names))
# update user groups # update user groups
if set(user.groups.all()) != groups: if set(user.groups.all()) != groups:
user.groups.set(groups) user.groups.set(groups)
...@@ -119,17 +123,6 @@ def set_user_groups(user, raw_values): ...@@ -119,17 +123,6 @@ def set_user_groups(user, raw_values):
logger.info("User {user} is member of: {groups}".format(user=user, groups=[str(g) for g in groups])) logger.info("User {user} is member of: {groups}".format(user=user, groups=[str(g) for g in groups]))
def groups_from_group_names(user, saml2_raw_values):
""" This default group resolver returns list of Group objects based on group names as raw_values """
groups = set(Group.objects.filter(name__in=saml2_raw_values))
# perform some sanity checks
group_names_redundant = set(saml2_raw_values) - {g.name for g in groups}
if group_names_redundant:
logger.warning("Received redundant group names (clean up the configs): {0}".format(group_names_redundant))
# done
return groups
def cleanup_direct_permissions(user): def cleanup_direct_permissions(user):
if user.user_permissions.exists(): if user.user_permissions.exists():
logger.critical("Who attached permissions directly to {user} ?!?!".format(**locals())) logger.critical("Who attached permissions directly to {user} ?!?!".format(**locals()))
......
...@@ -136,3 +136,14 @@ def production_entity_id(app_configs, **kwargs): ...@@ -136,3 +136,14 @@ def production_entity_id(app_configs, **kwargs):
)) ))
return errors return errors
@register(Tags.compatibility)
def group_resolver_deprecated(app_configs, **kwargs):
errors = list()
if app_settings.GROUP_RESOLVER is not None:
errors.append(Warning(
"GROUP_RESOLVER is deprecated. If hsh app is present, groups are resolved from it automatically instead of the received SAML group names.",
obj=conf.settings,
))
return errors
"""
Compatibility features with the `hsh` app.
"""
from .. import logger
try:
from hsh.models import Account
HSH_APP_COMPAT_POSSIBLE = True
except ImportError:
HSH_APP_COMPAT_POSSIBLE = False
ALLOWED_AUTH_PROVIDERS = ["fh-h.de"]
GROUP_NAME_LOOKUP = dict(name__istartswith="WEB_")
def get_group_names_for_user(user):
"""
Resolve group names for the user directly from hsh app.
It's better to get groups directly from the database, instead of trusting AD data received via SAML2.
"""
try:
hsh_account = Account.objects.get(username=user.username, auth_provider__in=ALLOWED_AUTH_PROVIDERS)
except (Account.DoesNotExist, Account.MultipleObjectsReturned,) as e:
logger.error("hsh.Account not found for {user}. {e.__class__.__name__}: {e}".format(user=user, e=e))
return set()
hsh_groups = {hsh_account.auth_groups.filter(**GROUP_NAME_LOOKUP)}
return {g.name for g in hsh_groups}
...@@ -15,5 +15,5 @@ urlpatterns = ( ...@@ -15,5 +15,5 @@ urlpatterns = (
if conf.settings.DEBUG: if conf.settings.DEBUG:
# add the dev view # add the dev view
urlpatterns += ( urlpatterns += (
url(r"^dev/?$", views.DevView.as_view(), name="sso-dev"), url(r"^sso-dev/?$", views.DevView.as_view(), name="sso-dev"),
) )
...@@ -204,7 +204,7 @@ class ACSAuthNView(SAMLMixin, View): ...@@ -204,7 +204,7 @@ class ACSAuthNView(SAMLMixin, View):
) )
auth_utils.set_user_groups( auth_utils.set_user_groups(
user=user, user=user,
raw_values=get_attr(app_settings.ATTRIBUTE_GROUPS, nullable=True, multivalued=True) or list() group_names=get_attr(app_settings.ATTRIBUTE_GROUPS, nullable=True, multivalued=True) or list()
) )
# other necessary steps # other necessary steps
auth_utils.cleanup_direct_permissions(user=user) # in case somebody was acting funny auth_utils.cleanup_direct_permissions(user=user) # in case somebody was acting funny
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment