def test_assign_role_to_user_ignore_already_exists_error(self): user_db = UserDB(name='test-user-10') user_db = User.add_or_update(user_db) role_assignment_db_1 = rbac_service.assign_role_to_user( role_db=self.roles['custom_role_1'], user_db=user_db, source='assignments/%s_10.yaml' % user_db.name) # 1. Without ignore errors self.assertRaises(StackStormDBObjectConflictError, rbac_service.assign_role_to_user, role_db=self.roles['custom_role_1'], user_db=user_db, source='assignments/%s_10.yaml' % user_db.name) # 2. With ignore errors role_assignment_db_2 = rbac_service.assign_role_to_user( role_db=self.roles['custom_role_1'], user_db=user_db, source='assignments/%s_10.yaml' % user_db.name, ignore_already_exists_error=True) self.assertEqual(role_assignment_db_1, role_assignment_db_2) self.assertEqual(role_assignment_db_1.id, role_assignment_db_2.id) self.assertEqual(role_assignment_db_1.user, role_assignment_db_2.user) self.assertEqual(role_assignment_db_1.role, role_assignment_db_2.role)
def setUp(self): super(RBACRemoteGroupToRoleSyncerTestCase, self).setUp() self.roles = {} self.role_assignments = {} # Insert mock local role assignments role_db = rbac_service.create_role(name='mock_local_role_1') user_db = self.users['user_1'] source = 'assignments/%s.yaml' % user_db.name role_assignment_db_1 = rbac_service.assign_role_to_user( role_db=role_db, user_db=user_db, source=source, is_remote=False) self.roles['mock_local_role_1'] = role_db self.role_assignments['assignment_1'] = role_assignment_db_1 role_db = rbac_service.create_role(name='mock_local_role_2') user_db = self.users['user_1'] source = 'assignments/%s.yaml' % user_db.name role_assignment_db_2 = rbac_service.assign_role_to_user( role_db=role_db, user_db=user_db, source=source, is_remote=False) self.roles['mock_local_role_2'] = role_db self.role_assignments['assignment_2'] = role_assignment_db_2 role_db = rbac_service.create_role(name='mock_remote_role_3') self.roles['mock_remote_role_3'] = role_db role_db = rbac_service.create_role(name='mock_remote_role_4') self.roles['mock_remote_role_4'] = role_db role_db = rbac_service.create_role(name='mock_role_5') self.roles['mock_role_5'] = role_db
def test_grant_and_revoke_role(self): user_db = UserDB(name='test-user-1') user_db = User.add_or_update(user_db) # Initial state, no roles role_dbs = rbac_service.get_roles_for_user(user_db=user_db) self.assertItemsEqual(role_dbs, []) role_dbs = user_db.get_roles() self.assertItemsEqual(role_dbs, []) # Assign a role, should have one role assigned rbac_service.assign_role_to_user(role_db=self.roles['custom_role_1'], user_db=user_db, source='assignments/%s.yaml' % user_db.name) role_dbs = rbac_service.get_roles_for_user(user_db=user_db) self.assertItemsEqual(role_dbs, [self.roles['custom_role_1']]) role_dbs = user_db.get_roles() self.assertItemsEqual(role_dbs, [self.roles['custom_role_1']]) # Revoke previously assigned role, should have no roles again rbac_service.revoke_role_from_user(role_db=self.roles['custom_role_1'], user_db=user_db) role_dbs = rbac_service.get_roles_for_user(user_db=user_db) self.assertItemsEqual(role_dbs, []) role_dbs = user_db.get_roles() self.assertItemsEqual(role_dbs, [])
def test_sync_success_one_existing_remote_assignment(self): syncer = RBACRemoteGroupToRoleSyncer() user_db = self.users['user_1'] # Create mock mapping which maps CN=stormers,OU=groups,DC=stackstorm,DC=net # to "mock_remote_role_3" and "mock_remote_role_4" rbac_service.create_group_to_role_map( group='CN=stormers,OU=groups,DC=stackstorm,DC=net', roles=['mock_remote_role_3', 'mock_remote_role_4'], source='mappings/stormers.yaml') # Assign existing remote mock_role_5 to the user role_db = self.roles['mock_role_5'] source = 'mappings/stormers.yaml' rbac_service.assign_role_to_user(role_db=role_db, user_db=user_db, source=source, is_remote=True) # Verify initial state role_dbs = rbac_service.get_roles_for_user(user_db=user_db, include_remote=True) self.assertEqual(len(role_dbs), 3) self.assertEqual(role_dbs[0], self.roles['mock_local_role_1']) self.assertEqual(role_dbs[1], self.roles['mock_local_role_2']) self.assertEqual(role_dbs[2], self.roles['mock_role_5']) groups = [ 'CN=stormers,OU=groups,DC=stackstorm,DC=net', 'CN=testers,OU=groups,DC=stackstorm,DC=net' ] result = syncer.sync(user_db=self.users['user_1'], groups=groups) created_role_assignment_dbs = result[0] removed_role_assignment_dbs = result[1] self.assertEqual(len(created_role_assignment_dbs), 2) self.assertEqual(created_role_assignment_dbs[0].role, 'mock_remote_role_3') self.assertEqual(created_role_assignment_dbs[1].role, 'mock_remote_role_4') self.assertEqual(len(removed_role_assignment_dbs), 1) self.assertEqual(removed_role_assignment_dbs[0].role, 'mock_role_5') # User should have two new roles assigned now, but the existing "mock_role_5" remote role # removed since it wasn't specified in any mapping role_dbs = rbac_service.get_roles_for_user(user_db=user_db, include_remote=True) self.assertEqual(len(role_dbs), 4) self.assertEqual(role_dbs[0], self.roles['mock_local_role_1']) self.assertEqual(role_dbs[1], self.roles['mock_local_role_2']) self.assertEqual(role_dbs[2], self.roles['mock_remote_role_3']) self.assertEqual(role_dbs[3], self.roles['mock_remote_role_4'])
def setUp(self): super(AuthHandlerRBACRoleSyncTestCase, self).setUp() cfg.CONF.set_override(group='auth', name='backend', override='mock') cfg.CONF.set_override(group='rbac', name='backend', override='enterprise') self.users = {} self.roles = {} self.role_assignments = {} # Insert some mock users user_1_db = UserDB(name='auser') user_1_db = User.add_or_update(user_1_db) self.users['user_1'] = user_1_db user_2_db = UserDB(name='buser') user_2_db = User.add_or_update(user_2_db) self.users['user_2'] = user_2_db # Insert mock local role assignments role_db = rbac_service.create_role(name='mock_local_role_1') user_db = self.users['user_1'] source = 'assignments/%s.yaml' % user_db.name role_assignment_db_1 = rbac_service.assign_role_to_user( role_db=role_db, user_db=user_db, source=source, is_remote=False) self.roles['mock_local_role_1'] = role_db self.role_assignments['assignment_1'] = role_assignment_db_1 role_db = rbac_service.create_role(name='mock_local_role_2') user_db = self.users['user_1'] source = 'assignments/%s.yaml' % user_db.name role_assignment_db_2 = rbac_service.assign_role_to_user( role_db=role_db, user_db=user_db, source=source, is_remote=False) self.roles['mock_local_role_2'] = role_db self.role_assignments['assignment_2'] = role_assignment_db_2 role_db = rbac_service.create_role(name='mock_role_3') self.roles['mock_role_3'] = role_db role_db = rbac_service.create_role(name='mock_role_4') self.roles['mock_role_4'] = role_db role_db = rbac_service.create_role(name='mock_role_5') self.roles['mock_role_5'] = role_db
def test_sync_remote_assignments_are_not_manipulated(self): # Verify remote assignments are not manipulated. syncer = RBACDefinitionsDBSyncer() self._insert_mock_roles() # Initial state, no roles user_db = UserDB(name='doesntexistwhaha') role_dbs = rbac_service.get_roles_for_user(user_db=user_db) self.assertItemsEqual(role_dbs, []) # Create mock remote role assignment role_db = self.roles['role_3'] source = 'assignments/%s.yaml' % user_db.name role_assignment_db = rbac_service.assign_role_to_user(role_db=role_db, user_db=user_db, source=source, is_remote=True) self.assertTrue(role_assignment_db.is_remote) # Verify assignment has been created role_dbs = rbac_service.get_roles_for_user(user_db=user_db) self.assertItemsEqual(role_dbs, [self.roles['role_3']]) # Do the sync with two roles defined - verify remote role assignment hasn't been # manipulated with. api = UserRoleAssignmentFileFormatAPI(username=user_db.name, roles=['role_1', 'role_2'], file_path='assignments/%s.yaml' % user_db.name) syncer.sync_users_role_assignments(role_assignment_apis=[api]) role_dbs = rbac_service.get_roles_for_user(user_db=user_db) self.assertEqual(len(role_dbs), 3) self.assertEqual(role_dbs[0], self.roles['role_1']) self.assertEqual(role_dbs[1], self.roles['role_2']) self.assertEqual(role_dbs[2], self.roles['role_3']) # Do sync with no roles - verify all roles except remote one are removed. api = UserRoleAssignmentFileFormatAPI(username=user_db.name, roles=[], file_path='assignments/%s.yaml' % user_db.name) syncer.sync_users_role_assignments(role_assignment_apis=[api]) role_dbs = rbac_service.get_roles_for_user(user_db=user_db) self.assertEqual(len(role_dbs), 1) self.assertEqual(role_dbs[0], self.roles['role_3'])
def sync(self, user_db, groups): """ :param user_db: User to sync the assignments for. :type user: :class:`UserDB` :param groups: A list of remote groups user is a member of. :type groups: ``list`` of ``str`` :return: A list of mappings which have been created. :rtype: ``list`` of :class:`UserRoleAssignmentDB` """ groups = list(set(groups)) extra = {'user_db': user_db, 'groups': groups} LOG.info('Synchronizing remote role assignments for user "%s"' % (str(user_db)), extra=extra) # 1. Retrieve group to role mappings for the provided groups all_mapping_dbs = GroupToRoleMapping.query(group__in=groups) enabled_mapping_dbs = [mapping_db for mapping_db in all_mapping_dbs if mapping_db.enabled] disabled_mapping_dbs = [mapping_db for mapping_db in all_mapping_dbs if not mapping_db.enabled] if not all_mapping_dbs: LOG.debug('No group to role mappings found for user "%s"' % (str(user_db)), extra=extra) # 2. Remove all the existing remote role assignments remote_assignment_dbs = UserRoleAssignment.query(user=user_db.name, is_remote=True) existing_role_names = [assignment_db.role for assignment_db in remote_assignment_dbs] existing_role_names = set(existing_role_names) current_role_names = set([]) for mapping_db in all_mapping_dbs: for role in mapping_db.roles: current_role_names.add(role) # A list of new role assignments which should be added to the database new_role_names = current_role_names.difference(existing_role_names) # A list of role assignments which need to be updated in the database updated_role_names = existing_role_names.intersection(current_role_names) # A list of role assignments which should be removed from the database removed_role_names = (existing_role_names - new_role_names) # Also remove any assignments for mappings which are disabled in the database for mapping_db in disabled_mapping_dbs: for role in mapping_db.roles: removed_role_names.add(role) LOG.debug('New role assignments: %r' % (new_role_names)) LOG.debug('Updated role assignments: %r' % (updated_role_names)) LOG.debug('Removed role assignments: %r' % (removed_role_names)) # Build a list of role assignments to delete role_names_to_delete = updated_role_names.union(removed_role_names) role_assignment_dbs_to_delete = [role_assignment_db for role_assignment_db in remote_assignment_dbs if role_assignment_db.role in role_names_to_delete] UserRoleAssignment.query(user=user_db.name, role__in=role_names_to_delete, is_remote=True).delete() # 3. Create role assignments for all the current groups created_assignments_dbs = [] for mapping_db in enabled_mapping_dbs: extra['mapping_db'] = mapping_db for role_name in mapping_db.roles: role_db = rbac_service.get_role_by_name(name=role_name) if not role_db: # Gracefully skip assignment for role which doesn't exist in the db LOG.info('Role with name "%s" for mapping "%s" not found, skipping assignment.' % (role_name, str(mapping_db)), extra=extra) continue description = ('Automatic role assignment based on the remote user membership in ' 'group "%s"' % (mapping_db.group)) assignment_db = rbac_service.assign_role_to_user(role_db=role_db, user_db=user_db, description=description, is_remote=True, source=mapping_db.source, ignore_already_exists_error=True) assert assignment_db.is_remote is True created_assignments_dbs.append(assignment_db) LOG.debug('Created %s new remote role assignments for user "%s"' % (len(created_assignments_dbs), str(user_db)), extra=extra) return (created_assignments_dbs, role_assignment_dbs_to_delete)
def _sync_user_role_assignments(self, user_db, role_assignment_dbs, role_assignment_apis): """ Synchronize role assignments for a particular user. :param user_db: User to synchronize the assignments for. :type user_db: :class:`UserDB` :param role_assignment_dbs: Existing user role assignments. :type role_assignment_dbs: ``list`` of :class:`UserRoleAssignmentDB` :param role_assignment_apis: List of user role assignments to apply. :param role_assignment_apis: ``list`` of :class:`UserRoleAssignmentFileFormatAPI` :rtype: ``tuple`` """ db_roles = set([(entry.role, entry.source) for entry in role_assignment_dbs]) api_roles = [ list(izip_longest(entry.roles, [], fillvalue=entry.file_path)) for entry in role_assignment_apis ] api_roles = set(list(chain.from_iterable(api_roles))) # A list of new assignments which should be added to the database new_roles = api_roles.difference(db_roles) # A list of assignments which need to be updated in the database updated_roles = db_roles.intersection(api_roles) # A list of assignments which should be removed from the database removed_roles = (db_roles - api_roles) LOG.debug('New assignments for user "%s": %r' % (user_db.name, new_roles)) LOG.debug('Updated assignments for user "%s": %r' % (user_db.name, updated_roles)) LOG.debug('Removed assignments for user "%s": %r' % (user_db.name, removed_roles)) # Build a list of role assignments to delete roles_to_delete = updated_roles.union(removed_roles) role_assignment_dbs_to_delete = [ role_assignment_db for role_assignment_db in role_assignment_dbs if (role_assignment_db.role, role_assignment_db.source) in roles_to_delete ] for role_name, assignment_source in roles_to_delete: queryset_filter = ( Q(user=user_db.name) & Q(role=role_name) & Q(source=assignment_source) & (Q(is_remote=False) | Q(is_remote__exists=False)) ) UserRoleAssignmentDB.objects(queryset_filter).delete() LOG.debug('Removed role "%s" from "%s" for user "%s".' % (role_name, assignment_source, user_db.name)) # Build a list of roles assignments to create roles_to_create = new_roles.union(updated_roles) created_role_assignment_dbs = [] for role_name, assignment_source in roles_to_create: role_db = Role.get(name=role_name) if not role_db: msg = 'Role "%s" referenced in assignment file "%s" doesn\'t exist' raise ValueError(msg % (role_name, assignment_source)) role_assignment_api = [r for r in role_assignment_apis if r.file_path == assignment_source][0] description = getattr(role_assignment_api, 'description', None) assignment_db = rbac_service.assign_role_to_user( role_db=role_db, user_db=user_db, source=assignment_source, description=description) created_role_assignment_dbs.append(assignment_db) LOG.debug('Assigned role "%s" from "%s" for user "%s".' % (role_name, assignment_source, user_db.name)) return (created_role_assignment_dbs, role_assignment_dbs_to_delete)
def test_sync_user_same_role_granted_locally_and_remote_via_mapping(self): syncer = RBACRemoteGroupToRoleSyncer() user_db = self.users['user_6'] # Insert 2 local assignments for mock_role_7 role_db = rbac_service.create_role(name='mock_role_7') source = 'assignments/user_6_one.yaml' rbac_service.assign_role_to_user(role_db=role_db, user_db=user_db, source=source, is_remote=False) source = 'assignments/user_6_two.yaml' rbac_service.assign_role_to_user(role_db=role_db, user_db=user_db, source=source, is_remote=False) # Create mock mapping which maps CN=stormers,OU=groups,DC=stackstorm,DC=net # to "mock_role_7" rbac_service.create_group_to_role_map( group='CN=stormers,OU=groups,DC=stackstorm,DC=net', roles=['mock_role_7'], source='mappings/stormers.yaml') # Create mock mapping which maps CN=testers,OU=groups,DC=stackstorm,DC=net # to "mock_role_7" rbac_service.create_group_to_role_map( group='CN=testers,OU=groups,DC=stackstorm,DC=net', roles=['mock_role_7'], source='mappings/testers.yaml') groups = [ 'CN=stormers,OU=groups,DC=stackstorm,DC=net', 'CN=testers,OU=groups,DC=stackstorm,DC=net' ] result = syncer.sync(user_db=self.users['user_6'], groups=groups) created_role_assignment_dbs = result[0] removed_role_assignment_dbs = result[1] self.assertEqual(len(created_role_assignment_dbs), 2) self.assertEqual(created_role_assignment_dbs[0].role, 'mock_role_7') self.assertEqual(created_role_assignment_dbs[1].role, 'mock_role_7') self.assertEqual(removed_role_assignment_dbs, []) # There should be one role and 4 assignments for the same role role_dbs = rbac_service.get_roles_for_user(user_db=user_db, include_remote=True) self.assertEqual(len(role_dbs), 1) self.assertEqual(role_dbs[0].name, 'mock_role_7') role_assignment_dbs = rbac_service.get_role_assignments_for_user( user_db=self.users['user_6']) self.assertEqual(len(role_assignment_dbs), 4) self.assertEqual(role_assignment_dbs[0].source, 'assignments/user_6_one.yaml') self.assertEqual(role_assignment_dbs[1].source, 'assignments/user_6_two.yaml') self.assertEqual(role_assignment_dbs[2].source, 'mappings/stormers.yaml') self.assertEqual(role_assignment_dbs[3].source, 'mappings/testers.yaml') # Remove one remote group - should be 3 left groups = ['CN=stormers,OU=groups,DC=stackstorm,DC=net'] result = syncer.sync(user_db=self.users['user_6'], groups=groups) role_dbs = rbac_service.get_roles_for_user(user_db=user_db, include_remote=True) self.assertEqual(len(role_dbs), 1) self.assertEqual(role_dbs[0].name, 'mock_role_7') role_assignment_dbs = rbac_service.get_role_assignments_for_user( user_db=self.users['user_6']) self.assertEqual(len(role_assignment_dbs), 3)