def test_delete_single_acl_and_count(self): session = self.acl_repo.get_session() container = self._create_base_container() acl1 = self.acl_repo.create_from( models.ContainerACL(container.id, 'read'), session) self.acl_repo.create_or_replace_from(container, acl1, user_ids=['u1', 'u2']) acl2 = self.acl_repo.create_from( models.ContainerACL(container.id, 'write'), session) self.acl_repo.create_or_replace_from(container, acl2, user_ids=['u1', 'u2', 'u3']) acl3 = self.acl_repo.create_from( models.ContainerACL(container.id, 'list'), session) self.acl_repo.create_or_replace_from(container, acl3, user_ids=['u1', 'u3']) count = self.acl_repo.get_count(container.id) self.assertEqual(3, count) self.acl_repo.delete_entity_by_id(acl2.id, None) session.commit() # commit the changes made so far self.assertEqual(2, len(container.container_acls)) deleted_acl = self.acl_repo.get(acl2.id, suppress_exception=True) self.assertIsNone(deleted_acl) acls = self.acl_repo.get_by_container_id(container.id) self.assertEqual(2, len(acls)) count = self.acl_repo.get_count(container.id) self.assertEqual(2, count)
def test_create_or_replace_from_for_new_acls(self): """Check create_or_replace_from and get count call. It creates new acls with users and make sure that same users are returned when acls are queries by secret id. It uses get count to assert expected number of acls for that secret. """ session = self.acl_repo.get_session() container = self._create_base_container() acl1 = self.acl_repo.create_from( models.ContainerACL(container.id, 'read'), session) self.acl_repo.create_or_replace_from(container, acl1, user_ids=['u1', 'u2'], session=session) acl2 = self.acl_repo.create_from( models.ContainerACL(container.id, 'write', False), session) self.acl_repo.create_or_replace_from(container, acl2, user_ids=['u1', 'u2', 'u3'], session=session) acl3 = self.acl_repo.create_from( models.ContainerACL(container.id, 'list'), session) self.acl_repo.create_or_replace_from(container, acl3, user_ids=[], session=session) acls = self.acl_repo.get_by_container_id(container.id, session) self.assertEqual(3, len(acls)) id_map = self._map_id_to_acl(acls) self.assertTrue(id_map[acl1.id].project_access) self.assertFalse(id_map[acl2.id].project_access) self.assertEqual('read', id_map[acl1.id].operation) self.assertEqual('write', id_map[acl2.id].operation) self.assertEqual('list', id_map[acl3.id].operation) # order of input users should not matter self._assert_acl_users(['u1', 'u2'], acls, acl1.id) self._assert_acl_users(['u2', 'u1'], acls, acl1.id) self._assert_acl_users(['u2', 'u1', 'u3'], acls, acl2.id) count = self.acl_repo.get_count(container.id, session) self.assertEqual(3, count) self.assertEqual(count, len(acls))
def setUp(self): super(WhenTestingContainerResource, self).setUp() self.external_project_id = '12345project' self.container_id = '12345secret' self.user_id = '123456user' self.creator_user_id = '123456CreatorUser' # Force an error on GET and DELETE calls that pass RBAC, # as we are not testing such flows in this test module. self.container_repo = mock.MagicMock() fail_method = mock.MagicMock(return_value=None, side_effect=self._generate_get_error()) self.container_repo.get = fail_method self.container_repo.delete_entity_by_id = fail_method acl_read = models.ContainerACL(container_id=self.container_id, operation='read', project_access=True, user_ids=[self.user_id, 'anyRandomId']) self.acl_list = [acl_read] container = mock.MagicMock() container.to_dict_fields = mock.MagicMock(side_effect=IOError) container.id = self.container_id container.container_acls.__iter__.return_value = self.acl_list container.project.external_id = self.external_project_id container.creator_id = self.creator_user_id self.container_repo.get_container_by_id.return_value = container self.setup_container_repository_mock(self.container_repo) self.resource = ContainerResource(container)
def test_new_containeracl_for_bare_minimum_input(self): acl = models.ContainerACL(self.container_id, self.operation, None, None) self.assertEqual(self.container_id, acl.container_id) self.assertEqual(0, len(acl.acl_users)) self.assertEqual(self.operation, acl.operation) self.assertIsNone(acl.project_access)
def test_new_containeracl_for_given_all_input(self): acl = models.ContainerACL(self.container_id, self.operation, self.project_access, self.user_ids) self.assertEqual(self.container_id, acl.container_id) self.assertEqual(self.operation, acl.operation) self.assertEqual(self.project_access, acl.project_access) self.assertTrue( all(acl_user.user_id in self.user_ids for acl_user in acl.acl_users))
def test_delete_acls_for_secret(self): session = self.acl_repo.get_session() container = self._create_base_container() acl1 = self.acl_repo.create_from( models.ContainerACL(container.id, 'read'), session) self.acl_repo.create_or_replace_from(container, acl1, user_ids=['u1', 'u2'], session=session) acl2 = self.acl_repo.create_from( models.ContainerACL(container.id, 'write'), session) self.acl_repo.create_or_replace_from(container, acl2, user_ids=['u1', 'u2', 'u3'], session=session) self.acl_repo.delete_acls_for_container(container) acls = self.acl_repo.get_by_container_id(container.id) self.assertEqual(0, len(acls))
def test_new_containeracl_with_duplicate_userids_input(self): user_ids = list(self.user_ids) user_ids *= 2 # duplicate ids acl = models.ContainerACL(self.container_id, self.operation, True, user_ids=user_ids) self.assertEqual(self.container_id, acl.container_id) self.assertEqual(self.operation, acl.operation) self.assertTrue(acl.project_access) self.assertEqual(2, len(acl.acl_users))
def test_get_count(self): session = self.acl_repo.get_session() container1 = self._create_base_container() acl1 = self.acl_repo.create_from( models.ContainerACL(container1.id, 'read', None, ['u1', 'u2']), session) self.acl_repo.create_or_replace_from(container1, acl1) container2 = self._create_base_container(container1.project_id) acl21 = self.acl_repo.create_from( models.ContainerACL(container2.id, 'read', None, ['u3', 'u4']), session) self.acl_repo.create_or_replace_from(container2, acl21) acl22 = self.acl_repo.create_from( models.ContainerACL(container2.id, 'write', None, ['u5', 'u6']), session) self.acl_repo.create_or_replace_from(container2, acl22) self.assertEqual(1, self.acl_repo.get_count(container1.id)) self.assertEqual(2, self.acl_repo.get_count(container2.id))
def test_create_or_replace_from_with_none_or_blank_users(self): session = self.acl_repo.get_session() container = self._create_base_container() acl1 = self.acl_repo.create_from( models.ContainerACL(container.id, 'read'), session) self.acl_repo.create_or_replace_from(container, acl1, user_ids=None, session=session) acl2 = self.acl_repo.create_from( models.ContainerACL(container.id, 'write'), session) self.acl_repo.create_or_replace_from(container, acl1, user_ids=[], session=session) acls = self.acl_repo.get_by_container_id(container.id, session) id_map = self._map_id_to_acl(acls) self.assertIsNone(id_map[acl1.id].to_dict_fields().get('users')) self.assertIsNone(id_map[acl2.id].to_dict_fields().get('users'))
def test_new_containeracl_check_to_dict_fields(self): acl = models.ContainerACL(self.container_id, self.operation, self.project_access, self.user_ids) self.assertEqual(self.container_id, acl.to_dict_fields()['container_id']) self.assertEqual(self.operation, acl.to_dict_fields()['operation']) self.assertEqual(self.project_access, acl.to_dict_fields()['project_access']) self.assertTrue( all(user_id in self.user_ids for user_id in acl.to_dict_fields()['users'])) self.assertIsNone(acl.to_dict_fields()['acl_id'])
def on_patch(self, external_project_id, **kwargs): """Handles update of existing container acl requests. At least one container ACL needs to exist for update to proceed. In update, multiple operation ACL payload can be specified as mentioned in sample below. A specific ACL can be updated by its own id via ContainerACLController patch request. { "read":{ "users":[ "5ecb18f341894e94baca9e8c7b6a824a", "20b63d71f90848cf827ee48074f213b7", "c7753f8da8dc4fbea75730ab0b6e0ef4" ] }, "write":{ "users":[ "5ecb18f341894e94baca9e8c7b6a824a" ], "project-access":false } } """ data = api.load_body(pecan.request, validator=self.validator) LOG.debug('Start ContainerACLsController on_patch...%s', data) existing_acls_map = { acl.operation: acl for acl in self.container.container_acls } for operation in itertools.ifilter(lambda x: data.get(x), validators.ACL_OPERATIONS): project_access = data[operation].get('project-access') user_ids = data[operation].get('users') if operation in existing_acls_map: # update if matching acl exists c_acl = existing_acls_map[operation] if project_access is not None: c_acl.project_access = project_access else: c_acl = models.ContainerACL(self.container.id, operation=operation, project_access=project_access) self.acl_repo.create_or_replace_from(self.container, container_acl=c_acl, user_ids=user_ids) acl_ref = '{0}/acl'.format( hrefs.convert_container_to_href(self.container.id)) return {'acl_ref': acl_ref}
def test_get_by_container_id(self): session = self.acl_repo.get_session() container = self._create_base_container() acls = self.acl_repo.get_by_container_id(container.id, session) self.assertEqual(0, len(acls)) acl1 = self.acl_repo.create_from( models.ContainerACL(container.id, 'read', True, ['u1', 'u2']), session) acls = self.acl_repo.get_by_container_id(container.id, session) self.assertEqual(1, len(acls)) self.assertEqual(acl1.id, acls[0].id) self.assertEqual('read', acls[0].operation) self._assert_acl_users(['u1', 'u2'], acls, acl1.id)
def test_pass_get_container_for_admin_user_project_access_disabled(self): """Should pass authz for admin user when container is marked private. For private container, admin user should still be able to access the secret. """ self.acl_list.pop() # remove read acl from default setup acl_read = models.ContainerACL(container_id=self.container_id, operation='read', project_access=False, user_ids=['anyRandomUserX', 'aclUser1']) self.acl_list.append(acl_read) self._assert_pass_rbac(['admin'], self._invoke_on_get, user_id=self.user_id, project_id=self.external_project_id)
def test_pass_get_container_for_creator_user_project_access_disabled(self): """Should pass authz for creator user when container is marked private. As container is private so user who created the container can still access it as long as user has 'creator' role in container project. """ self.acl_list.pop() # remove read acl from default setup acl_read = models.ContainerACL(container_id=self.container_id, operation='read', project_access=False, user_ids=['anyRandomUserX', 'aclUser1']) self.acl_list.append(acl_read) self._assert_pass_rbac(['creator'], self._invoke_on_get, user_id=self.creator_user_id, project_id=self.external_project_id)
def test_should_raise_get_container_for_with_project_access_disabled(self): """Should raise authz error as container is marked private. As container is private so project users should not be able to access the secret (other than admin user). """ self.acl_list.pop() # remove read acl from default setup acl_read = models.ContainerACL(container_id=self.container_id, operation='read', project_access=False, user_ids=['anyRandomUserX', 'aclUser1']) self.acl_list.append(acl_read) self._assert_fail_rbac(['observer', 'creator', 'audit'], self._invoke_on_get, user_id=self.user_id, project_id=self.external_project_id)
def test_fail_get_container_for_creator_user_different_project(self): """Check for creator user rule for container get call. If token's user is creator of container but its scoped to different project, then he/she is not allowed access to container when project is marked private. """ self.acl_list.pop() # remove read acl from default setup acl_read = models.ContainerACL(container_id=self.container_id, operation='read', project_access=False, user_ids=['anyRandomUserX', 'aclUser1']) self.acl_list.append(acl_read) self._assert_fail_rbac(['creator'], self._invoke_on_get, user_id=self.creator_user_id, project_id='differet_project_id')
def test_should_pass_get_container_for_private_enabled_with_read_acl(self): """Should pass authz as user has read acl for private container. Even though container is private, user with read acl should be able to access the container. """ self.acl_list.pop() # remove read acl from default setup acl_read = models.ContainerACL(container_id=self.container_id, operation='read', project_access=False, user_ids=['anyRandomUserX', 'aclUser1']) self.acl_list.append(acl_read) self._assert_pass_rbac( ['admin', 'observer', 'creator', 'audit', 'bogusRole'], self._invoke_on_get, user_id='aclUser1', project_id=self.external_project_id)
def test_get_by_entity_id(self): session = self.acl_repo.get_session() container = self._create_base_container() acl1 = self.acl_repo.create_from( models.ContainerACL(container.id, 'read', True, ['u1', 'u2']), session) acl = self.acl_repo.get(acl1.id, session) self.assertIsNotNone(acl) self.assertEqual(acl1.id, acl.id) self.assertEqual('read', acl.operation) self._assert_acl_users(['u1', 'u2'], [acl], acl1.id) self.acl_repo.delete_entity_by_id(acl1.id, session) acl = self.acl_repo.get(acl1.id, session, suppress_exception=True) self.assertIsNone(acl)
def test_should_raise_get_container_for_different_user_with_no_read_acl( self): """Get secret fails when no read acl is defined. With different container and token's project, read is not allowed without a read ACL. """ self.acl_list.pop() # remove read acl from default setup acl_read = models.ContainerACL(container_id=self.container_id, operation='write', project_access=True, user_ids=['anyRandomUserX', 'aclUser1']) self.acl_list.append(acl_read) # token project_id is different from secret's project id but another # user (from different project) has read acl for secret so should pass self._assert_fail_rbac(['admin', 'observer', 'creator', 'audit'], self._invoke_on_get, user_id='aclUser1', project_id='different_project_id')
def test_should_pass_get_container_different_user_with_valid_read_acl( self): """Should allow when read ACL is defined for a user. Container's own project and token's project is different but read is allowed because of valid read ACL. User can read regardless of what is token's project as it has necessary ACL. """ self.acl_list.pop() # remove read acl from default setup acl_read = models.ContainerACL(container_id=self.container_id, operation='read', project_access=True, user_ids=['anyRandomUserX', 'aclUser1']) self.acl_list.append(acl_read) self._assert_pass_rbac( ['admin', 'observer', 'creator', 'audit', 'bogusRole'], self._invoke_on_get, user_id='aclUser1', project_id='different_project_id')
def test_create_or_replace_from_for_existing_acls(self): """Check create_or_replace_from and get count call. It modifies existing acls with users and make sure that updated users and project_access flag changes are returned when acls are queries by secret id. It uses get count to assert expected number of acls for that secret. """ session = self.acl_repo.get_session() container = self._create_base_container() acl1 = self.acl_repo.create_from( models.ContainerACL(container.id, 'read'), session) self.acl_repo.create_or_replace_from(container, acl1, user_ids=['u1', 'u2'], session=session) acl2 = self.acl_repo.create_from( models.ContainerACL(container.id, 'write'), session) self.acl_repo.create_or_replace_from(container, acl2, user_ids=['u1', 'u2', 'u3'], session=session) acl3 = self.acl_repo.create_from( models.ContainerACL(container.id, 'list'), session) self.acl_repo.create_or_replace_from(container, acl3, user_ids=[], session=session) acls = self.acl_repo.get_by_container_id(container.id, session) self.assertEqual(3, len(acls)) id_map = self._map_id_to_acl(acls) # replace users in existing acls id_map[acl1.id].project_access = False self.acl_repo.create_or_replace_from(container, id_map[acl1.id], user_ids=['u5'], session=session) self.acl_repo.create_or_replace_from(container, id_map[acl2.id], user_ids=['u1', 'u2', 'u3', 'u4'], session=session) self.acl_repo.create_or_replace_from(container, id_map[acl3.id], user_ids=['u1', 'u2', 'u4'], session=session) session.commit() acls = self.acl_repo.get_by_container_id(container.id, session) id_map = self._map_id_to_acl(acls) self.assertEqual(3, len(acls)) self.assertFalse(id_map[acl1.id].project_access) self.assertTrue(id_map[acl2.id].project_access) self.assertTrue(id_map[acl3.id].project_access) self._assert_acl_users(['u5'], acls, acl1.id) self._assert_acl_users(['u1', 'u2', 'u3', 'u4'], acls, acl2.id) self._assert_acl_users(['u1', 'u2', 'u4'], acls, acl3.id)
def test_new_containeracl_expect_user_ids_as_list(self): acl = models.ContainerACL(self.container_id, self.operation, None, {'aUser': '******'}) self.assertEqual(0, len(acl.acl_users))
def on_put(self, external_project_id, **kwargs): """Handles update of existing container acl requests. Replaces existing container ACL(s) with input ACL(s) data. Existing ACL operation not specified in input are removed as part of update. For missing project-access in ACL, true is used as default. In update, multiple operation ACL payload can be specified as mentioned in sample below. A specific ACL can be updated by its own id via ContainerACLController patch request. { "read":{ "users":[ "5ecb18f341894e94baca9e8c7b6a824a", "20b63d71f90848cf827ee48074f213b7", "c7753f8da8dc4fbea75730ab0b6e0ef4" ] }, "write":{ "users":[ "5ecb18f341894e94baca9e8c7b6a824a" ], "project-access":false } } Every container, by default, has an implicit ACL in case client has not defined an explicit ACL. That default ACL definition, DEFAULT_ACL, signifies that a container by default has project based access i.e. client with necessary roles on container project can access the container. That's why when ACL is added to a container, it always returns 200 (and not 201) indicating existence of implicit ACL on a container. """ data = api.load_body(pecan.request, validator=self.validator) LOG.debug('Start ContainerACLsController on_put...%s', data) existing_acls_map = { acl.operation: acl for acl in self.container.container_acls } for operation in itertools.ifilter(lambda x: data.get(x), validators.ACL_OPERATIONS): project_access = data[operation].get('project-access', True) user_ids = data[operation].get('users', []) if operation in existing_acls_map: # update if matching acl exists c_acl = existing_acls_map.pop(operation) c_acl.project_access = project_access else: c_acl = models.ContainerACL(self.container.id, operation=operation, project_access=project_access) self.acl_repo.create_or_replace_from(self.container, container_acl=c_acl, user_ids=user_ids) # delete remaining existing acls as they are not present in input. for acl in six.itervalues(existing_acls_map): self.acl_repo.delete_entity_by_id(entity_id=acl.id, external_project_id=None) acl_ref = '{0}/acl'.format( hrefs.convert_container_to_href(self.container.id)) return {'acl_ref': acl_ref}