From eab417cf8b7cca51e4b9face8fbeb99b631d2453 Mon Sep 17 00:00:00 2001 From: Antony PERIGAULT Date: Thu, 15 Mar 2018 17:37:32 +0100 Subject: [PATCH 1/4] Map users in organizations based on saml groups --- awx/sso/pipeline.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/awx/sso/pipeline.py b/awx/sso/pipeline.py index 23d603275f..8c89d629a0 100644 --- a/awx/sso/pipeline.py +++ b/awx/sso/pipeline.py @@ -54,7 +54,7 @@ def prevent_inactive_login(backend, details, user=None, *args, **kwargs): raise AuthInactive(backend) -def _update_m2m_from_expression(user, rel, expr, remove=True): +def _update_m2m_from_expression(user, rel, expr, remove=True, saml_team_names=False): ''' Helper function to update m2m relationship based on user matching one or more expressions. @@ -70,6 +70,9 @@ def _update_m2m_from_expression(user, rel, expr, remove=True): if isinstance(expr, (six.string_types, type(re.compile('')))): expr = [expr] for ex in expr: + if saml_team_names: + if ex in saml_team_names: + should_add = True if isinstance(ex, six.string_types): if user.username == ex or user.email == ex: should_add = True @@ -104,16 +107,24 @@ def update_user_orgs(backend, details, user=None, *args, **kwargs): except IndexError: continue + team_map = backend.setting('SOCIAL_AUTH_SAML_TEAM_ATTR') or {} + saml_team_names = False + if team_map.get('saml_attr'): + saml_team_names = set(kwargs + .get('response', {}) + .get('attributes', {}) + .get(team_map['saml_attr'], [])) + # Update org admins from expression(s). remove = bool(org_opts.get('remove', True)) admins_expr = org_opts.get('admins', None) remove_admins = bool(org_opts.get('remove_admins', remove)) - _update_m2m_from_expression(user, org.admin_role.members, admins_expr, remove_admins) + _update_m2m_from_expression(user, org.admin_role.members, admins_expr, remove_admins, saml_team_names) # Update org users from expression(s). users_expr = org_opts.get('users', None) remove_users = bool(org_opts.get('remove_users', remove)) - _update_m2m_from_expression(user, org.member_role.members, users_expr, remove_users) + _update_m2m_from_expression(user, org.member_role.members, users_expr, remove_users, saml_team_names) def update_user_teams(backend, details, user=None, *args, **kwargs): From 13cd57febaba7ffce8aa5a9808c151fe4320f056 Mon Sep 17 00:00:00 2001 From: Antony PERIGAULT Date: Fri, 27 Apr 2018 12:05:18 +0200 Subject: [PATCH 2/4] Revert "Map users in organizations based on saml groups" This reverts commit b4e0ff650165e6b0ab08d9a78be85f2f46182b94. --- awx/sso/pipeline.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/awx/sso/pipeline.py b/awx/sso/pipeline.py index 8c89d629a0..23d603275f 100644 --- a/awx/sso/pipeline.py +++ b/awx/sso/pipeline.py @@ -54,7 +54,7 @@ def prevent_inactive_login(backend, details, user=None, *args, **kwargs): raise AuthInactive(backend) -def _update_m2m_from_expression(user, rel, expr, remove=True, saml_team_names=False): +def _update_m2m_from_expression(user, rel, expr, remove=True): ''' Helper function to update m2m relationship based on user matching one or more expressions. @@ -70,9 +70,6 @@ def _update_m2m_from_expression(user, rel, expr, remove=True, saml_team_names=Fa if isinstance(expr, (six.string_types, type(re.compile('')))): expr = [expr] for ex in expr: - if saml_team_names: - if ex in saml_team_names: - should_add = True if isinstance(ex, six.string_types): if user.username == ex or user.email == ex: should_add = True @@ -107,24 +104,16 @@ def update_user_orgs(backend, details, user=None, *args, **kwargs): except IndexError: continue - team_map = backend.setting('SOCIAL_AUTH_SAML_TEAM_ATTR') or {} - saml_team_names = False - if team_map.get('saml_attr'): - saml_team_names = set(kwargs - .get('response', {}) - .get('attributes', {}) - .get(team_map['saml_attr'], [])) - # Update org admins from expression(s). remove = bool(org_opts.get('remove', True)) admins_expr = org_opts.get('admins', None) remove_admins = bool(org_opts.get('remove_admins', remove)) - _update_m2m_from_expression(user, org.admin_role.members, admins_expr, remove_admins, saml_team_names) + _update_m2m_from_expression(user, org.admin_role.members, admins_expr, remove_admins) # Update org users from expression(s). users_expr = org_opts.get('users', None) remove_users = bool(org_opts.get('remove_users', remove)) - _update_m2m_from_expression(user, org.member_role.members, users_expr, remove_users, saml_team_names) + _update_m2m_from_expression(user, org.member_role.members, users_expr, remove_users) def update_user_teams(backend, details, user=None, *args, **kwargs): From 9bfac4f44b49e5c2cf33d707def908a7dc04025b Mon Sep 17 00:00:00 2001 From: Antony PERIGAULT Date: Wed, 2 May 2018 15:00:39 +0200 Subject: [PATCH 3/4] New feature: Add SAML users as organization admins --- awx/sso/conf.py | 2 ++ awx/sso/pipeline.py | 54 ++++++++++++++++++++++++++++----------------- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/awx/sso/conf.py b/awx/sso/conf.py index 75178d6d12..c9393160b2 100644 --- a/awx/sso/conf.py +++ b/awx/sso/conf.py @@ -1196,7 +1196,9 @@ register( category_slug='saml', placeholder=collections.OrderedDict([ ('saml_attr', 'organization'), + ('saml_admin_attr', 'organization_admin'), ('remove', True), + ('remove_admins', True), ]), feature_required='enterprise_auth', ) diff --git a/awx/sso/pipeline.py b/awx/sso/pipeline.py index 23d603275f..420e4e1930 100644 --- a/awx/sso/pipeline.py +++ b/awx/sso/pipeline.py @@ -82,6 +82,33 @@ def _update_m2m_from_expression(user, rel, expr, remove=True): rel.remove(user) +def _update_org_from_attr(user, rel, attr, remove, remove_admins): + from awx.main.models import Organization + multiple_orgs = feature_enabled('multiple_organizations') + + org_ids = [] + + for org_name in attr: + if multiple_orgs: + org = Organization.objects.get_or_create(name=org_name)[0] + else: + try: + org = Organization.objects.order_by('pk')[0] + except IndexError: + continue + + org_ids.append(org.id) + getattr(org, rel).members.add(user) + + if remove: + [o.member_role.members.remove(user) for o in + Organization.objects.filter(Q(member_role__members=user) & ~Q(id__in=org_ids))] + + if remove_admins: + [o.admin_role.members.remove(user) for o in + Organization.objects.filter(Q(admin_role__members=user) & ~Q(id__in=org_ids))] + + def update_user_orgs(backend, details, user=None, *args, **kwargs): ''' Update organization memberships for the given user based on mapping rules @@ -150,32 +177,19 @@ def update_user_teams(backend, details, user=None, *args, **kwargs): def update_user_orgs_by_saml_attr(backend, details, user=None, *args, **kwargs): if not user: return - from awx.main.models import Organization from django.conf import settings - multiple_orgs = feature_enabled('multiple_organizations') org_map = settings.SOCIAL_AUTH_SAML_ORGANIZATION_ATTR - if org_map.get('saml_attr') is None: + if org_map.get('saml_attr') is None and org_map.get('saml_admin_attr') is None: return + remove = bool(org_map.get('remove', True)) + remove_admins = bool(org_map.get('remove_admins', True)) + attr_values = kwargs.get('response', {}).get('attributes', {}).get(org_map['saml_attr'], []) + attr_admin_values = kwargs.get('response', {}).get('attributes', {}).get(org_map['saml_admin_attr'], []) - org_ids = [] - - for org_name in attr_values: - if multiple_orgs: - org = Organization.objects.get_or_create(name=org_name)[0] - else: - try: - org = Organization.objects.order_by('pk')[0] - except IndexError: - continue - - org_ids.append(org.id) - org.member_role.members.add(user) - - if org_map.get('remove', True): - [o.member_role.members.remove(user) for o in - Organization.objects.filter(Q(member_role__members=user) & ~Q(id__in=org_ids))] + _update_org_from_attr(user, "member_role", attr_values, remove, False) + _update_org_from_attr(user, "admin_role", attr_admin_values, False, remove_admins) def update_user_teams_by_saml_attr(backend, details, user=None, *args, **kwargs): From 4090fe6d11982ac90350f0efd133d9eb115ea846 Mon Sep 17 00:00:00 2001 From: Antony PERIGAULT Date: Wed, 2 May 2018 16:25:44 +0200 Subject: [PATCH 4/4] Fix functional tests --- awx/sso/tests/functional/test_pipeline.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/awx/sso/tests/functional/test_pipeline.py b/awx/sso/tests/functional/test_pipeline.py index 0e2abe67a3..57a6eed7bb 100644 --- a/awx/sso/tests/functional/test_pipeline.py +++ b/awx/sso/tests/functional/test_pipeline.py @@ -149,6 +149,7 @@ class TestSAMLAttr(): 'idp_name': u'idp', 'attributes': { 'memberOf': ['Default1', 'Default2'], + 'admins': ['Default3'], 'groups': ['Blue', 'Red'], 'User.email': ['cmeyers@redhat.com'], 'User.LastName': ['Meyers'], @@ -176,7 +177,9 @@ class TestSAMLAttr(): class MockSettings(): SOCIAL_AUTH_SAML_ORGANIZATION_ATTR = { 'saml_attr': 'memberOf', + 'saml_admin_attr': 'admins', 'remove': True, + 'remove_admins': True, } SOCIAL_AUTH_SAML_TEAM_ATTR = { 'saml_attr': 'groups',