diff --git a/awx/main/backend.py b/awx/main/backend.py index 112726f4fb..082d3543eb 100644 --- a/awx/main/backend.py +++ b/awx/main/backend.py @@ -13,6 +13,7 @@ class LDAPSettings(BaseLDAPSettings): defaults = dict(BaseLDAPSettings.defaults.items() + { 'ORGANIZATION_MAP': {}, + 'TEAM_MAP': {}, }.items()) class LDAPBackend(BaseLDAPBackend): @@ -83,10 +84,10 @@ def _update_m2m_from_groups(user, ldap_user, rel, opts, remove=False): @receiver(populate_user) def on_populate_user(sender, **kwargs): ''' - Handle signal from LDAP backend to populate the user object. Update user's - organization membership according to their LDAP groups. + Handle signal from LDAP backend to populate the user object. Update user + organization/team memberships according to their LDAP groups. ''' - from awx.main.models import Organization + from awx.main.models import Organization, Team user = kwargs['user'] ldap_user = kwargs['ldap_user'] backend = ldap_user.backend @@ -105,6 +106,18 @@ def on_populate_user(sender, **kwargs): _update_m2m_from_groups(user, ldap_user, org.users, users_opts, remove_users) + # Update team membership based on group memberships. + team_map = getattr(backend.settings, 'TEAM_MAP', {}) + for team_name, team_opts in team_map.items(): + if 'organization' not in team_opts: + continue + org, created = Organization.objects.get_or_create(name=team_opts['organization']) + team, created = Team.objects.get_or_create(name=team_name, organization=org) + users_opts = team_opts.get('users', None) + remove = bool(team_opts.get('remove', False)) + _update_m2m_from_groups(user, ldap_user, team.users, users_opts, + remove) + # Update user profile to store LDAP DN. profile = user.profile if profile.ldap_dn != ldap_user.dn: diff --git a/awx/main/tests/users.py b/awx/main/tests/users.py index b5ca0629ef..a70a126f41 100644 --- a/awx/main/tests/users.py +++ b/awx/main/tests/users.py @@ -854,6 +854,39 @@ class LdapTest(BaseTest): else: self.assertFalse(user in org.users.all()) + def test_ldap_team_mapping(self): + for name in ('USER_SEARCH', 'ALWAYS_UPDATE_USER', 'USER_ATTR_MAP', + 'GROUP_SEARCH', 'GROUP_TYPE', 'USER_FLAGS_BY_GROUP'): + self.use_test_setting(name) + self.assertEqual(User.objects.filter(username=self.ldap_username).count(), 0) + self.use_test_setting('TEAM_MAP', {}) + self.use_test_setting('TEAM_MAP_RESULT', {}) + for team_name, team_opts in settings.AUTH_LDAP_TEAM_MAP.items(): + self.assertEqual(Team.objects.filter(name=team_name).count(), 0) + self.assertEqual(Organization.objects.filter(name=team_opts['organization']).count(), 0) + user = self.check_login() + for team_name, team_opts in settings.AUTH_LDAP_TEAM_MAP.items(): + self.assertEqual(Team.objects.filter(name=team_name, organization__name=team_opts['organization']).count(), 1) + for team_name, team_result in settings.AUTH_LDAP_TEAM_MAP_RESULT.items(): + team = Team.objects.get(name=team_name) + if team_result.get('users', False): + self.assertTrue(user in team.users.all()) + else: + self.assertFalse(user in team.users.all()) + # Try again with different test mapping. + self.use_test_setting('TEAM_MAP', {}, from_name='TEAM_MAP_2') + self.use_test_setting('TEAM_MAP_RESULT', {}, + from_name='TEAM_MAP_2_RESULT') + user = self.check_login() + for team_name, team_opts in settings.AUTH_LDAP_TEAM_MAP.items(): + self.assertEqual(Team.objects.filter(name=team_name, organization__name=team_opts['organization']).count(), 1) + for team_name, team_result in settings.AUTH_LDAP_TEAM_MAP_RESULT.items(): + team = Team.objects.get(name=team_name) + if team_result.get('users', False): + self.assertTrue(user in team.users.all()) + else: + self.assertFalse(user in team.users.all()) + def test_prevent_changing_ldap_user_fields(self): for name in ('USER_SEARCH', 'ALWAYS_UPDATE_USER', 'USER_ATTR_MAP', 'GROUP_SEARCH', 'GROUP_TYPE', 'USER_FLAGS_BY_GROUP'): diff --git a/awx/settings/local_settings.py.example b/awx/settings/local_settings.py.example index 24182ab30f..e46dd61055 100644 --- a/awx/settings/local_settings.py.example +++ b/awx/settings/local_settings.py.example @@ -234,7 +234,7 @@ AUTH_LDAP_USER_FLAGS_BY_GROUP = { # If True/False, all LDAP users will be added/removed as admins. # If a string or list of strings, specifies the group DN(s). User will be # added as an org admin if the user is a member of ANY of these groups. -# - remove_admins: True/False. Defaults to False. If True, a user who is not an +# - remove_admins: True/False. Defaults to False. If True, a user who is not a # member of the given groups will be removed from the organization's admins. # - users: None, True/False, string or list/tuple of strings. Same rules apply # as for admins. @@ -251,6 +251,33 @@ AUTH_LDAP_ORGANIZATION_MAP = { #}, } +# Mapping between team members (users) and LDAP groups. Keys are team names +# (will be created if not present). Values are dictionaries of options for +# each team's membership, where each can contain the following parameters: +# - organization: string. The name of the organization to which the team +# belongs. The team will be created if the combination of organization and +# team name does not exist. The organization will first be created if it +# does not exist. +# - users: None, True/False, string or list/tuple of strings. +# If None, team members will not be updated. +# If True/False, all LDAP users will be added/removed as team members. +# If a string or list of strings, specifies the group DN(s). User will be +# added as a team member if the user is a member of ANY of these groups. +# - remove: True/False. Defaults to False. If True, a user who is not a member +# of the given groups will be removed from the team. +AUTH_LDAP_TEAM_MAP = { + 'My Team': { + 'organization': 'Test Org', + 'users': ['CN=Domain Users,CN=Users,DC=example,DC=com'], + 'remove': True, + }, + 'Other Team': { + 'organization': 'Test Org 2', + 'users': 'CN=Other Users,CN=Users,DC=example,DC=com', + 'remove': False, + }, +} + ############################################################################### # SCM TEST SETTINGS ############################################################################### @@ -387,6 +414,57 @@ TEST_AUTH_LDAP_ORGANIZATION_MAP_2_RESULT = { 'Test Org 2': {'admins': True, 'users': False}, } +# Test mapping between team users and LDAP groups. +TEST_AUTH_LDAP_TEAM_MAP = { + 'Domain Users Team': { + 'organization': 'Test Org', + 'users': ['CN=Domain Users,CN=Users,DC=example,DC=com'], + 'remove': False, + }, + 'Admins Team': { + 'organization': 'Admins Org', + 'users': 'CN=Domain Admins,CN=Users,DC=example,DC=com', + 'remove': True, + }, + 'Everyone Team': { + 'organization': 'Test Org 2', + 'users': True, + }, +} +# Expected results from team mapping. After login, should user be a member of +# the given team? +TEST_AUTH_LDAP_TEAM_MAP_RESULT = { + 'Domain Users Team': {'users': False}, + 'Admins Team': {'users': True}, + 'Everyone Team': {'users': True}, +} + +# Second test mapping for teams to remove user. +TEST_AUTH_LDAP_TEAM_MAP_2 = { + 'Domain Users Team': { + 'organization': 'Test Org', + 'users': ['CN=Domain Users,CN=Users,DC=example,DC=com'], + 'remove': False, + }, + 'Admins Team': { + 'organization': 'Admins Org', + 'users': 'CN=Administrators,CN=Builtin,DC=example,DC=com', + 'remove': True, + }, + 'Everyone Team': { + 'organization': 'Test Org 2', + 'users': False, + 'remove': False, + }, +} +# Expected results from second team mapping. After login, should user be a +# member of the given team? +TEST_AUTH_LDAP_TEAM_MAP_2_RESULT = { + 'Domain Users Team': {'users': False}, + 'Admins Team': {'users': False}, + 'Everyone Team': {'users': True}, +} + ############################################################################### # INVENTORY IMPORT TEST SETTINGS ###############################################################################