diff --git a/keystone/assignment/backends/sql.py b/keystone/assignment/backends/sql.py index ee59e8328f..bb492a365c 100644 --- a/keystone/assignment/backends/sql.py +++ b/keystone/assignment/backends/sql.py @@ -13,8 +13,6 @@ # under the License. from oslo_config import cfg -import sqlalchemy -from sqlalchemy.sql.expression import false from keystone import assignment as keystone_assignment from keystone.common import sql @@ -122,105 +120,6 @@ class Assignment(keystone_assignment.AssignmentDriverV9): actor_id=actor_id, target_id=target_id) - def _list_project_ids_for_actor(self, actors, hints, inherited, - group_only=False): - # TODO(henry-nash): Now that we have a single assignment table, we - # should be able to honor the hints list that is provided. - - assignment_type = [AssignmentType.GROUP_PROJECT] - if not group_only: - assignment_type.append(AssignmentType.USER_PROJECT) - - sql_constraints = sqlalchemy.and_( - RoleAssignment.type.in_(assignment_type), - RoleAssignment.inherited == inherited, - RoleAssignment.actor_id.in_(actors)) - - with sql.transaction() as session: - query = session.query(RoleAssignment.target_id).filter( - sql_constraints).distinct() - - return [x.target_id for x in query.all()] - - def list_role_ids_for_groups_on_domain(self, group_ids, domain_id): - if not group_ids: - # If there's no groups then there will be no domain roles. - return [] - - sql_constraints = sqlalchemy.and_( - RoleAssignment.type == AssignmentType.GROUP_DOMAIN, - RoleAssignment.target_id == domain_id, - RoleAssignment.inherited == false(), - RoleAssignment.actor_id.in_(group_ids)) - - with sql.transaction() as session: - query = session.query(RoleAssignment.role_id).filter( - sql_constraints).distinct() - return [role.role_id for role in query.all()] - - def list_role_ids_for_groups_on_project( - self, group_ids, project_id, project_domain_id, project_parents): - - if not group_ids: - # If there's no groups then there will be no project roles. - return [] - - # NOTE(rodrigods): First, we always include projects with - # non-inherited assignments - sql_constraints = sqlalchemy.and_( - RoleAssignment.type == AssignmentType.GROUP_PROJECT, - RoleAssignment.inherited == false(), - RoleAssignment.target_id == project_id) - - if CONF.os_inherit.enabled: - # Inherited roles from domains - sql_constraints = sqlalchemy.or_( - sql_constraints, - sqlalchemy.and_( - RoleAssignment.type == AssignmentType.GROUP_DOMAIN, - RoleAssignment.inherited, - RoleAssignment.target_id == project_domain_id)) - - # Inherited roles from projects - if project_parents: - sql_constraints = sqlalchemy.or_( - sql_constraints, - sqlalchemy.and_( - RoleAssignment.type == AssignmentType.GROUP_PROJECT, - RoleAssignment.inherited, - RoleAssignment.target_id.in_(project_parents))) - - sql_constraints = sqlalchemy.and_( - sql_constraints, RoleAssignment.actor_id.in_(group_ids)) - - with sql.transaction() as session: - # NOTE(morganfainberg): Only select the columns we actually care - # about here, in this case role_id. - query = session.query(RoleAssignment.role_id).filter( - sql_constraints).distinct() - - return [result.role_id for result in query.all()] - - def list_project_ids_for_groups(self, group_ids, hints, - inherited=False): - return self._list_project_ids_for_actor( - group_ids, hints, inherited, group_only=True) - - def list_domain_ids_for_groups(self, group_ids, inherited=False): - if not group_ids: - # If there's no groups then there will be no domains. - return [] - - group_sql_conditions = sqlalchemy.and_( - RoleAssignment.type == AssignmentType.GROUP_DOMAIN, - RoleAssignment.inherited == inherited, - RoleAssignment.actor_id.in_(group_ids)) - - with sql.transaction() as session: - query = session.query(RoleAssignment.target_id).filter( - group_sql_conditions).distinct() - return [x.target_id for x in query.all()] - def add_role_to_user_and_project(self, user_id, tenant_id, role_id): try: with sql.transaction() as session: diff --git a/keystone/assignment/core.py b/keystone/assignment/core.py index a72d3b3c69..0e92b86216 100644 --- a/keystone/assignment/core.py +++ b/keystone/assignment/core.py @@ -133,16 +133,18 @@ class Manager(manager.Manager): def get_roles_for_groups(self, group_ids, project_id=None, domain_id=None): """Get a list of roles for this group on domain and/or project.""" if project_id is not None: - project = self.resource_api.get_project(project_id) - role_ids = self.list_role_ids_for_groups_on_project( - group_ids, project_id, project['domain_id'], - self._list_parent_ids_of_project(project_id)) + self.resource_api.get_project(project_id) + assignment_list = self.list_role_assignments( + source_from_group_ids=group_ids, project_id=project_id, + effective=True) elif domain_id is not None: - role_ids = self.list_role_ids_for_groups_on_domain( - group_ids, domain_id) + assignment_list = self.list_role_assignments( + source_from_group_ids=group_ids, domain_id=domain_id, + effective=True) else: raise AttributeError(_("Must specify either domain or project")) + role_ids = list(set([x['role_id'] for x in assignment_list])) return self.role_api.list_roles_from_ids(role_ids) def add_user_to_project(self, tenant_id, user_id): @@ -244,37 +246,18 @@ class Manager(manager.Manager): return self.resource_api.list_domains_from_ids(domain_ids) def list_domains_for_groups(self, group_ids): - domain_ids = self.list_domain_ids_for_groups(group_ids) + assignment_list = self.list_role_assignments( + source_from_group_ids=group_ids, effective=True) + domain_ids = list(set([x['domain_id'] for x in assignment_list + if x.get('domain_id')])) return self.resource_api.list_domains_from_ids(domain_ids) def list_projects_for_groups(self, group_ids): - project_ids = ( - self.list_project_ids_for_groups(group_ids, driver_hints.Hints())) - if not CONF.os_inherit.enabled: - return self.resource_api.list_projects_from_ids(project_ids) - - # os_inherit extension is enabled, so check to see if these groups have - # any inherited role assignment on: i) any domain, in which case we - # must add in all the projects in that domain; ii) any project, in - # which case we must add in all the subprojects under that project in - # the hierarchy. - - domain_ids = self.list_domain_ids_for_groups(group_ids, inherited=True) - - project_ids_from_domains = ( - self.resource_api.list_project_ids_from_domain_ids(domain_ids)) - - parents_ids = self.list_project_ids_for_groups(group_ids, - driver_hints.Hints(), - inherited=True) - - subproject_ids = [] - for parent_id in parents_ids: - subtree = self.resource_api.list_projects_in_subtree(parent_id) - subproject_ids += [subproject['id'] for subproject in subtree] - - return self.resource_api.list_projects_from_ids( - list(set(project_ids + project_ids_from_domains + subproject_ids))) + assignment_list = self.list_role_assignments( + source_from_group_ids=group_ids, effective=True) + project_ids = list(set([x['project_id'] for x in assignment_list + if x.get('project_id')])) + return self.resource_api.list_projects_from_ids(project_ids) @notifications.role_assignment('deleted') def _remove_role_from_user_and_project_adapter(self, role_id, user_id=None, @@ -409,8 +392,8 @@ class Manager(manager.Manager): # kept as it is in order to detect unnecessarily complex code, which is not # this case. - def _expand_indirect_assignment(self, ref, user_id=None, - project_id=None, subtree_ids=None): + def _expand_indirect_assignment(self, ref, user_id=None, project_id=None, + subtree_ids=None, expand_groups=True): """Returns a list of expanded role assignments. This methods is called for each discovered assignment that either needs @@ -428,6 +411,9 @@ class Manager(manager.Manager): already ensured only those assignments that could affect them were passed to this method. + If expand_groups is True then we expand groups out to a list of + assignments, one for each member of that group. + """ def create_group_assignment(base_ref, user_id): """Creates a group assignment from the provided ref.""" @@ -482,12 +468,15 @@ class Manager(manager.Manager): for m in self.identity_api.list_users_in_group( ref['group_id'])] - def expand_inherited_assignment(ref, user_id, project_id, subtree_ids): + def expand_inherited_assignment(ref, user_id, project_id, subtree_ids, + expand_groups): """Expands inherited role assignments. - If this is a group role assignment on a target, replace it by a - list of role assignments containing one for each user of that - group, on every project under that target. + If expand_groups is True and this is a group role assignment on a + target, replace it by a list of role assignments containing one for + each user of that group, on every project under that target. If + expand_groups is False, then return a group assignment on an + inherited target. If this is a user role assignment on a specific target (i.e. project_id is specified, but subtree_ids is None) then simply @@ -587,8 +576,15 @@ class Manager(manager.Manager): new_refs = [] if 'group_id' in ref: - # Expand role assignment for all members and for all projects - for ref in expand_group_assignment(ref, user_id): + if expand_groups: + # Expand role assignment to all group members on any + # inherited target of any of the projects + for ref in expand_group_assignment(ref, user_id): + new_refs += [create_inherited_assignment(ref, proj_id) + for proj_id in project_ids] + else: + # Just place the group assignment on any inherited target + # of any of the projects new_refs += [create_inherited_assignment(ref, proj_id) for proj_id in project_ids] else: @@ -599,9 +595,9 @@ class Manager(manager.Manager): return new_refs if ref.get('inherited_to_projects') == 'projects': - return expand_inherited_assignment(ref, user_id, project_id, - subtree_ids) - elif 'group_id' in ref: + return expand_inherited_assignment( + ref, user_id, project_id, subtree_ids, expand_groups) + elif 'group_id' in ref and expand_groups: return expand_group_assignment(ref, user_id) return [ref] @@ -669,7 +665,7 @@ class Manager(manager.Manager): def _list_effective_role_assignments(self, role_id, user_id, group_id, domain_id, project_id, subtree_ids, - inherited): + inherited, source_from_group_ids): """List role assignments in effective mode. When using effective mode, besides the direct assignments, the indirect @@ -786,24 +782,31 @@ class Manager(manager.Manager): if group_id or (domain_id and inherited): return [] + if user_id and source_from_group_ids: + # You can't do both - and since source_from_group_ids is only used + # internally, this must be a coding error by the caller. + msg = _('Cannot list assignments sourced from groups and filtered ' + 'by user ID.') + raise exception.UnexpectedError(msg) + # If filtering by domain, then only non-inherited assignments are # relevant, since domains don't inherit assignments inherited = False if domain_id else inherited - # List user assignments. + # List user or explicit group assignments. # Due to the need to expand implied roles, this call will skip # filtering by role_id and instead return the whole set of roles. # Matching on the specified role is performed at the end. - direct_refs = list_role_assignments_for_actor( - role_id=None, user_id=user_id, project_id=project_id, - subtree_ids=subtree_ids, domain_id=domain_id, - inherited=inherited) + role_id=None, user_id=user_id, group_ids=source_from_group_ids, + project_id=project_id, subtree_ids=subtree_ids, + domain_id=domain_id, inherited=inherited) - # And those from the user's groups. Again, role_id is not - # used to filter here + # And those from the user's groups, so long as we are not restricting + # to a set of source groups (in which case we already got those + # assignments in the direct listing above). group_refs = [] - if user_id: + if not source_from_group_ids and user_id: group_ids = self._get_group_ids_for_user_id(user_id) if group_ids: group_refs = list_role_assignments_for_actor( @@ -813,10 +816,10 @@ class Manager(manager.Manager): # Expand grouping and inheritance on retrieved role assignments refs = [] + expand_groups = (source_from_group_ids is None) for ref in (direct_refs + group_refs): refs += self._expand_indirect_assignment( - ref=ref, user_id=user_id, project_id=project_id, - subtree_ids=subtree_ids) + ref, user_id, project_id, subtree_ids, expand_groups) refs = self._add_implied_roles(refs) if role_id: @@ -850,7 +853,8 @@ class Manager(manager.Manager): def list_role_assignments(self, role_id=None, user_id=None, group_id=None, domain_id=None, project_id=None, include_subtree=False, inherited=None, - effective=None, include_names=False): + effective=None, include_names=False, + source_from_group_ids=None): """List role assignments, honoring effective mode and provided filters. Returns a list of role assignments, where their attributes match the @@ -869,7 +873,18 @@ class Manager(manager.Manager): affect a user, for example the roles that would be placed in a token. If include_names is set to true the entities' names are returned - in addition to their id's + in addition to their id's. + + source_from_group_ids is a list of group IDs and, if specified, then + only those assignments that are derived from membership of these groups + are considered, and any such assignments will not be expanded into + their user membership assignments. This is different to a group filter + of the resulting list, instead being a restriction on which assignments + should be considered before expansion of inheritance. This option is + only used internally (i.e. it is not exposed at the API level) and is + only supported in effective mode (since in regular mode there is no + difference between this and a group filter, other than it is a list of + groups). If OS-INHERIT extension is disabled or the used driver does not support inherited roles retrieval, inherited role assignments will be ignored. @@ -889,7 +904,7 @@ class Manager(manager.Manager): if effective: role_assignments = self._list_effective_role_assignments( role_id, user_id, group_id, domain_id, project_id, - subtree_ids, inherited) + subtree_ids, inherited, source_from_group_ids) else: role_assignments = self._list_direct_role_assignments( role_id, user_id, group_id, domain_id, project_id, @@ -1105,71 +1120,6 @@ class AssignmentDriverBase(object): """ raise exception.NotImplemented() # pragma: no cover - @abc.abstractmethod - def list_project_ids_for_groups(self, group_ids, hints, - inherited=False): - """List project ids accessible to specified groups. - - :param group_ids: List of group ids. - :param hints: filter hints which the driver should - implement if at all possible. - :param inherited: whether assignments marked as inherited should - be included. - :returns: List of project ids accessible to specified groups. - - This method should not try and expand any inherited assignments, - just report the projects that have the role for this group. The manager - method is responsible for expanding out inherited assignments. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def list_domain_ids_for_groups(self, group_ids, inherited=False): - """List domain ids accessible to specified groups. - - :param group_ids: List of group ids. - :param inherited: whether to return domain_ids that have inherited - assignments or not. - :returns: List of domain ids accessible to specified groups. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def list_role_ids_for_groups_on_project( - self, group_ids, project_id, project_domain_id, project_parents): - """List the group role ids for a specific project. - - Supports the ``OS-INHERIT`` role inheritance from the project's domain - if supported by the assignment driver. - - :param group_ids: list of group ids - :type group_ids: list - :param project_id: project identifier - :type project_id: str - :param project_domain_id: project's domain identifier - :type project_domain_id: str - :param project_parents: list of parent ids of this project - :type project_parents: list - :returns: list of role ids for the project - :rtype: list - """ - raise exception.NotImplemented() - - @abc.abstractmethod - def list_role_ids_for_groups_on_domain(self, group_ids, domain_id): - """List the group role ids for a specific domain. - - :param group_ids: list of group ids - :type group_ids: list - :param domain_id: domain identifier - :type domain_id: str - :returns: list of role ids for the project - :rtype: list - """ - raise exception.NotImplemented() - @abc.abstractmethod def delete_project_assignments(self, project_id): """Deletes all assignments for a project. @@ -1265,6 +1215,71 @@ class AssignmentDriverV8(AssignmentDriverBase): """ raise exception.NotImplemented() # pragma: no cover + @abc.abstractmethod + def list_project_ids_for_groups(self, group_ids, hints, + inherited=False): + """List project ids accessible to specified groups. + + :param group_ids: List of group ids. + :param hints: filter hints which the driver should + implement if at all possible. + :param inherited: whether assignments marked as inherited should + be included. + :returns: List of project ids accessible to specified groups. + + This method should not try and expand any inherited assignments, + just report the projects that have the role for this group. The manager + method is responsible for expanding out inherited assignments. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def list_domain_ids_for_groups(self, group_ids, inherited=False): + """List domain ids accessible to specified groups. + + :param group_ids: List of group ids. + :param inherited: whether to return domain_ids that have inherited + assignments or not. + :returns: List of domain ids accessible to specified groups. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def list_role_ids_for_groups_on_project( + self, group_ids, project_id, project_domain_id, project_parents): + """List the group role ids for a specific project. + + Supports the ``OS-INHERIT`` role inheritance from the project's domain + if supported by the assignment driver. + + :param group_ids: list of group ids + :type group_ids: list + :param project_id: project identifier + :type project_id: str + :param project_domain_id: project's domain identifier + :type project_domain_id: str + :param project_parents: list of parent ids of this project + :type project_parents: list + :returns: list of role ids for the project + :rtype: list + """ + raise exception.NotImplemented() + + @abc.abstractmethod + def list_role_ids_for_groups_on_domain(self, group_ids, domain_id): + """List the group role ids for a specific domain. + + :param group_ids: list of group ids + :type group_ids: list + :param domain_id: domain identifier + :type domain_id: str + :returns: list of role ids for the project + :rtype: list + """ + raise exception.NotImplemented() + class AssignmentDriverV9(AssignmentDriverBase): """New or redefined methods from V8. @@ -1362,24 +1377,6 @@ class V9AssignmentWrapperForV8Driver(AssignmentDriverV9): domain_id=domain_id, project_ids=project_ids, inherited_to_projects=inherited_to_projects) - def list_project_ids_for_groups(self, group_ids, hints, - inherited=False): - return self.driver.list_project_ids_for_groups( - group_ids, hints, inherited=inherited) - - def list_domain_ids_for_groups(self, group_ids, inherited=False): - return self.driver.list_domain_ids_for_groups( - group_ids, inherited=inherited) - - def list_role_ids_for_groups_on_project( - self, group_ids, project_id, project_domain_id, project_parents): - return self.driver.list_role_ids_for_groups_on_project( - group_ids, project_id, project_domain_id, project_parents) - - def list_role_ids_for_groups_on_domain(self, group_ids, domain_id): - return self.driver.list_role_ids_for_groups_on_domain( - group_ids, domain_id) - def delete_project_assignments(self, project_id): self.driver.delete_project_assignments(project_id) diff --git a/keystone/tests/unit/test_backend.py b/keystone/tests/unit/test_backend.py index fcf985fb33..900c2df90f 100644 --- a/keystone/tests/unit/test_backend.py +++ b/keystone/tests/unit/test_backend.py @@ -138,8 +138,9 @@ class AssignmentTestHelperMixin(object): 'results': [{'group': 0, 'role': 2, 'domain': 0}, {'user': 0, 'role': 2, 'project': 0}]}] - # The 'params' key also supports the 'effective' and - # 'inherited_to_projects' options to list_role_assignments.} + # The 'params' key also supports the 'effective', + # 'inherited_to_projects' and 'source_from_group_ids' options to + # list_role_assignments.} """ @@ -394,6 +395,13 @@ class AssignmentTestHelperMixin(object): expected_assignment[key] = value self.assertIn(expected_assignment, actual) + def convert_group_ids_sourced_from_list(index_list, reference_data): + value_list = [] + for group_index in index_list: + value_list.append( + reference_data['groups'][group_index]['id']) + return value_list + # Go through each test in the array, processing the input params, which # we build into an args dict, and then call list_role_assignments. Then # check the results against those specified in the test plan. @@ -403,6 +411,10 @@ class AssignmentTestHelperMixin(object): if param in ['effective', 'inherited', 'include_subtree']: # Just pass the value into the args args[param] = test['params'][param] + elif param == 'source_from_group_ids': + # Convert the list of indexes into a list of IDs + args[param] = convert_group_ids_sourced_from_list( + test['params']['source_from_group_ids'], test_data) else: # Turn 'entity : 0' into 'entity_id = ac6736ba873d' # where entity in user, group, project or domain @@ -1970,6 +1982,91 @@ class IdentityTests(AssignmentTestHelperMixin): } self.execute_assignment_plan(test_plan) + def test_list_role_assignment_using_sourced_groups(self): + """Test listing assignments when restricted by source groups.""" + test_plan = { + # The default domain with 3 users, 3 groups, 3 projects, + # plus 3 roles. + 'entities': {'domains': {'id': DEFAULT_DOMAIN_ID, + 'users': 3, 'groups': 3, 'projects': 3}, + 'roles': 3}, + # Users 0 & 1 are in the group 0, User 0 also in group 1 + 'group_memberships': [{'group': 0, 'users': [0, 1]}, + {'group': 1, 'users': [0]}], + # Spread the assignments around - we want to be able to show that + # if sourced by group, assignments from other sources are excluded + 'assignments': [{'user': 0, 'role': 0, 'project': 0}, + {'group': 0, 'role': 1, 'project': 1}, + {'group': 1, 'role': 2, 'project': 0}, + {'group': 1, 'role': 2, 'project': 1}, + {'user': 2, 'role': 1, 'project': 1}, + {'group': 2, 'role': 2, 'project': 2} + ], + 'tests': [ + # List all effective assignments sourced from groups 0 and 1 + {'params': {'source_from_group_ids': [0, 1], + 'effective': True}, + 'results': [{'group': 0, 'role': 1, 'project': 1}, + {'group': 1, 'role': 2, 'project': 0}, + {'group': 1, 'role': 2, 'project': 1} + ]}, + # Adding a role a filter should further restrict the entries + {'params': {'source_from_group_ids': [0, 1], 'role': 2, + 'effective': True}, + 'results': [{'group': 1, 'role': 2, 'project': 0}, + {'group': 1, 'role': 2, 'project': 1} + ]}, + ] + } + self.execute_assignment_plan(test_plan) + + def test_list_role_assignment_using_sourced_groups_with_domains(self): + """Test listing domain assignments when restricted by source groups.""" + test_plan = { + # A domain with 3 users, 3 groups, 3 projects, a second domain, + # plus 3 roles. + 'entities': {'domains': [{'users': 3, 'groups': 3, 'projects': 3}, + 1], + 'roles': 3}, + # Users 0 & 1 are in the group 0, User 0 also in group 1 + 'group_memberships': [{'group': 0, 'users': [0, 1]}, + {'group': 1, 'users': [0]}], + # Spread the assignments around - we want to be able to show that + # if sourced by group, assignments from other sources are excluded + 'assignments': [{'user': 0, 'role': 0, 'domain': 0}, + {'group': 0, 'role': 1, 'domain': 1}, + {'group': 1, 'role': 2, 'project': 0}, + {'group': 1, 'role': 2, 'project': 1}, + {'user': 2, 'role': 1, 'project': 1}, + {'group': 2, 'role': 2, 'project': 2} + ], + 'tests': [ + # List all effective assignments sourced from groups 0 and 1 + {'params': {'source_from_group_ids': [0, 1], + 'effective': True}, + 'results': [{'group': 0, 'role': 1, 'domain': 1}, + {'group': 1, 'role': 2, 'project': 0}, + {'group': 1, 'role': 2, 'project': 1} + ]}, + # Adding a role a filter should further restrict the entries + {'params': {'source_from_group_ids': [0, 1], 'role': 1, + 'effective': True}, + 'results': [{'group': 0, 'role': 1, 'domain': 1}, + ]}, + ] + } + self.execute_assignment_plan(test_plan) + + def test_list_role_assignment_fails_with_userid_and_source_groups(self): + """Show we trap this unsupported internal combination of params.""" + group = unit.new_group_ref(domain_id=DEFAULT_DOMAIN_ID) + group = self.identity_api.create_group(group) + self.assertRaises(exception.UnexpectedError, + self.assignment_api.list_role_assignments, + effective=True, + user_id=self.user_foo['id'], + source_from_group_ids=[group['id']]) + def test_delete_domain_with_user_group_project_links(self): # TODO(chungg):add test case once expected behaviour defined pass @@ -6546,6 +6643,47 @@ class InheritanceTests(AssignmentTestHelperMixin): for x in range(0, 4): self.assertIn(test_data['users'][x]['id'], user_ids) + def test_list_role_assignment_using_inherited_sourced_groups(self): + """Test listing inherited assignments when restricted by groups.""" + test_plan = { + # A domain with 3 users, 3 groups, 3 projects, a second domain, + # plus 3 roles. + 'entities': {'domains': [{'users': 3, 'groups': 3, 'projects': 3}, + 1], + 'roles': 3}, + # Users 0 & 1 are in the group 0, User 0 also in group 1 + 'group_memberships': [{'group': 0, 'users': [0, 1]}, + {'group': 1, 'users': [0]}], + # Spread the assignments around - we want to be able to show that + # if sourced by group, assignments from other sources are excluded + 'assignments': [{'user': 0, 'role': 0, 'domain': 0}, + {'group': 0, 'role': 1, 'domain': 1}, + {'group': 1, 'role': 2, 'domain': 0, + 'inherited_to_projects': True}, + {'group': 1, 'role': 2, 'project': 1}, + {'user': 2, 'role': 1, 'project': 1, + 'inherited_to_projects': True}, + {'group': 2, 'role': 2, 'project': 2} + ], + 'tests': [ + # List all effective assignments sourced from groups 0 and 1. + # We should see the inherited group assigned on the 3 projects + # from domain 0, as well as the direct assignments. + {'params': {'source_from_group_ids': [0, 1], + 'effective': True}, + 'results': [{'group': 0, 'role': 1, 'domain': 1}, + {'group': 1, 'role': 2, 'project': 0, + 'indirect': {'domain': 0}}, + {'group': 1, 'role': 2, 'project': 1, + 'indirect': {'domain': 0}}, + {'group': 1, 'role': 2, 'project': 2, + 'indirect': {'domain': 0}}, + {'group': 1, 'role': 2, 'project': 1} + ]}, + ] + } + self.execute_assignment_plan(test_plan) + class ImpliedRoleTests(AssignmentTestHelperMixin): diff --git a/keystone/tests/unit/test_backend_ldap.py b/keystone/tests/unit/test_backend_ldap.py index f8328cc3d1..36ccecf40b 100644 --- a/keystone/tests/unit/test_backend_ldap.py +++ b/keystone/tests/unit/test_backend_ldap.py @@ -981,6 +981,13 @@ class BaseLDAPIdentity(test_backend.IdentityTests): def test_domain_crud(self): self.skipTest('Resource LDAP has been removed') + def test_list_role_assignment_using_sourced_groups_with_domains(self): + """Multiple domain assignments are not supported.""" + self.assertRaises( + (exception.Forbidden, exception.DomainNotFound), + super(BaseLDAPIdentity, self). + test_list_role_assignment_using_sourced_groups_with_domains) + class LDAPIdentity(BaseLDAPIdentity, unit.TestCase): @@ -2553,6 +2560,12 @@ class MultiLDAPandSQLIdentity(BaseLDAPIdentity, unit.SQLDriverOverrides, super(BaseLDAPIdentity, self).\ test_list_role_assignment_by_user_with_domain_group_roles + def test_list_role_assignment_using_sourced_groups_with_domains(self): + # With SQL Assignment this method should work, so override the override + # from BaseLDAPIdentity + base = super(BaseLDAPIdentity, self) + base.test_list_role_assignment_using_sourced_groups_with_domains() + class MultiLDAPandSQLIdentityDomainConfigsInSQL(MultiLDAPandSQLIdentity): """Class to test the use of domain configs stored in the database. diff --git a/releasenotes/notes/Assignment_V9_driver-c22be069f7baccb0.yaml b/releasenotes/notes/Assignment_V9_driver-c22be069f7baccb0.yaml index cca53f31f1..e6f09af47b 100644 --- a/releasenotes/notes/Assignment_V9_driver-c22be069f7baccb0.yaml +++ b/releasenotes/notes/Assignment_V9_driver-c22be069f7baccb0.yaml @@ -1,8 +1,10 @@ --- features: - - The list_project_ids_for_user(), list_domain_ids_for_user() and - list_user_ids_for_project() methods have been removed from the V9 version - of the Assignment driver. + - The list_project_ids_for_user(), list_domain_ids_for_user(), + list_user_ids_for_project(), list_project_ids_for_groups(), + list_domain_ids_for_groups(), list_role_ids_for_groups_on_project() and + list_role_ids_for_groups_on_domain() methods have been removed from the + V9 version of the Assignment driver. upgrade: - The V8 Assignment driver interface is deprecated, but still supported in this release, so any custom drivers based on the V8 interface should still