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): 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', 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. ```