From 062c18efa0580dbca2b548c3092b6271324e7934 Mon Sep 17 00:00:00 2001 From: Antony PERIGAULT Date: Thu, 15 Mar 2018 17:37:32 +0100 Subject: [PATCH 1/5] 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 6ec222888539cff914a51ca1246d5ba7acfa8856 Mon Sep 17 00:00:00 2001 From: Antony PERIGAULT Date: Fri, 27 Apr 2018 12:05:18 +0200 Subject: [PATCH 2/5] 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 3b6ab6217bb0bc02443920cec9358b904653a398 Mon Sep 17 00:00:00 2001 From: Antony PERIGAULT Date: Wed, 2 May 2018 15:00:39 +0200 Subject: [PATCH 3/5] 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 53b5291a566a084f8a7d0f5d7b87102e3ff3e331 Mon Sep 17 00:00:00 2001 From: Antony PERIGAULT Date: Wed, 2 May 2018 16:25:44 +0200 Subject: [PATCH 4/5] 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', From 71730f9bc19359f3326d7c251390fb0efe8fb810 Mon Sep 17 00:00:00 2001 From: aperigault Date: Fri, 13 Jul 2018 15:33:37 +0200 Subject: [PATCH 5/5] Update SAML doc --- docs/auth/saml.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/auth/saml.md b/docs/auth/saml.md index 7af1730336..2af244acc8 100644 --- a/docs/auth/saml.md +++ b/docs/auth/saml.md @@ -21,18 +21,29 @@ Below is an example SAML attribute that embeds user organization membership in t HR Sales + + IT + HR + ``` Below, the corresponding AWX configuration. ``` { "saml_attr": "member-of", - "remove": true + "saml_admin_attr": "administrator-of", + "remove": true, + 'remove_admins': true } ``` **saml_attr:** The saml attribute name where the organization array can be found. + **remove:** True to remove user from all organizations before adding the user to the list of Organizations. False to keep the user in whatever Organization(s) they are in while adding the user to the Organization(s) in the SAML attribute. +**saml_admin_attr:** The saml attribute name where the organization administrators array can be found. + +**remove_admins:** True to remove user from all organizations that it is admin before adding the user to the list of Organizations admins. False to keep the user in whatever Organization(s) they are in as admin while adding the user as an Organization administrator in the SAML attribute. + **Example SAML Team Map** Below is another example of a SAML attribute that contains a Team membership in a list. ```