diff options
Diffstat (limited to 'keystone-moon/keystone/assignment/controllers.py')
-rw-r--r-- | keystone-moon/keystone/assignment/controllers.py | 385 |
1 files changed, 113 insertions, 272 deletions
diff --git a/keystone-moon/keystone/assignment/controllers.py b/keystone-moon/keystone/assignment/controllers.py index ff27fd36..d33dce70 100644 --- a/keystone-moon/keystone/assignment/controllers.py +++ b/keystone-moon/keystone/assignment/controllers.py @@ -15,7 +15,6 @@ """Workflow Logic the Assignment service.""" -import copy import functools import uuid @@ -26,10 +25,10 @@ from six.moves import urllib from keystone.assignment import schema from keystone.common import controller from keystone.common import dependency +from keystone.common import utils from keystone.common import validation from keystone import exception -from keystone.i18n import _, _LW -from keystone.models import token_model +from keystone.i18n import _ from keystone import notifications @@ -51,18 +50,11 @@ class TenantAssignment(controller.V2Controller): Doesn't care about token scopedness. """ - try: - token_data = self.token_provider_api.validate_token( - context['token_id']) - token_ref = token_model.KeystoneToken(token_id=context['token_id'], - token_data=token_data) - except exception.NotFound as e: - LOG.warning(_LW('Authentication failed: %s'), e) - raise exception.Unauthorized(e) + token_ref = utils.get_token_ref(context) tenant_refs = ( self.assignment_api.list_projects_for_user(token_ref.user_id)) - tenant_refs = [self.filter_domain_id(ref) for ref in tenant_refs + tenant_refs = [self.v3_to_v2_project(ref) for ref in tenant_refs if ref['domain_id'] == CONF.identity.default_domain_id] params = { 'limit': context['query_string'].get('limit'), @@ -107,7 +99,14 @@ class Role(controller.V2Controller): msg = _('Name field is required and cannot be empty') raise exception.ValidationError(message=msg) - role_id = uuid.uuid4().hex + if role['name'] == CONF.member_role_name: + # Use the configured member role ID when creating the configured + # member role name. This avoids the potential of creating a + # "member" role with an unexpected ID. + role_id = CONF.member_role_id + else: + role_id = uuid.uuid4().hex + role['id'] = role_id role_ref = self.role_api.create_role(role_id, role) return {'role': role_ref} @@ -152,8 +151,8 @@ class RoleAssignmentV2(controller.V2Controller): """ self.assert_admin(context) if tenant_id is None: - raise exception.NotImplemented(message='User roles not supported: ' - 'tenant_id required') + raise exception.NotImplemented( + message=_('User roles not supported: tenant_id required')) self.assignment_api.add_role_to_user_and_project( user_id, tenant_id, role_id) @@ -171,8 +170,8 @@ class RoleAssignmentV2(controller.V2Controller): """ self.assert_admin(context) if tenant_id is None: - raise exception.NotImplemented(message='User roles not supported: ' - 'tenant_id required') + raise exception.NotImplemented( + message=_('User roles not supported: tenant_id required')) # This still has the weird legacy semantics that adding a role to # a user also adds them to a tenant, so we must follow up on that @@ -282,7 +281,16 @@ class RoleV3(controller.V3Controller): @controller.protected() @validation.validated(schema.role_create, 'role') def create_role(self, context, role): - ref = self._assign_unique_id(self._normalize_dict(role)) + if role['name'] == CONF.member_role_name: + # Use the configured member role ID when creating the configured + # member role name. This avoids the potential of creating a + # "member" role with an unexpected ID. + role['id'] = CONF.member_role_id + else: + role = self._assign_unique_id(role) + + ref = self._normalize_dict(role) + initiator = notifications._get_request_audit_info(context) ref = self.role_api.create_role(ref['id'], ref, initiator) return RoleV3.wrap_member(context, ref) @@ -452,16 +460,25 @@ class RoleAssignmentV3(controller.V3Controller): actor (e.g. user or group), target (e.g. domain or project) and role. If it is an inherited role, then this is also indicated. Examples: + For a non-inherited expanded assignment from group membership: {'user_id': user_id, - 'project_id': domain_id, - 'role_id': role_id} + 'project_id': project_id, + 'role_id': role_id, + 'indirect': {'group_id': group_id}} - or, for an inherited role: + or, for a project inherited role: {'user_id': user_id, - 'domain_id': domain_id, + 'project_id': project_id, 'role_id': role_id, - 'inherited_to_projects': true} + 'indirect': {'project_id': parent_id}} + + It is possible to deduce if a role assignment came from group + membership if it has both 'user_id' in the main body of the dict and + 'group_id' in the 'indirect' subdict, as well as it is possible to + deduce if it has come from inheritance if it contains both a + 'project_id' in the main body of the dict and 'parent_id' in the + 'indirect' subdict. This function maps this into the format to be returned via the API, e.g. for the second example above: @@ -471,262 +488,71 @@ class RoleAssignmentV3(controller.V3Controller): {'id': user_id} }, 'scope': { - 'domain': { - {'id': domain_id} + 'project': { + {'id': project_id} }, - 'OS-INHERIT:inherited_to': 'projects + 'OS-INHERIT:inherited_to': 'projects' }, 'role': { {'id': role_id} }, 'links': { - 'assignment': '/domains/domain_id/users/user_id/roles/' - 'role_id/inherited_to_projects' + 'assignment': '/OS-INHERIT/projects/parent_id/users/user_id/' + 'roles/role_id/inherited_to_projects' } } """ - formatted_entity = {} - suffix = "" - if 'user_id' in entity: - formatted_entity['user'] = {'id': entity['user_id']} - actor_link = 'users/%s' % entity['user_id'] - if 'group_id' in entity: - formatted_entity['group'] = {'id': entity['group_id']} - actor_link = 'groups/%s' % entity['group_id'] - if 'role_id' in entity: - formatted_entity['role'] = {'id': entity['role_id']} + formatted_entity = {'links': {}} + inherited_assignment = entity.get('inherited_to_projects') + if 'project_id' in entity: formatted_entity['scope'] = ( {'project': {'id': entity['project_id']}}) - if 'inherited_to_projects' in entity: - formatted_entity['scope']['OS-INHERIT:inherited_to'] = ( - 'projects') - target_link = '/OS-INHERIT/projects/%s' % entity['project_id'] - suffix = '/inherited_to_projects' - else: - target_link = '/projects/%s' % entity['project_id'] - if 'domain_id' in entity: - formatted_entity['scope'] = ( - {'domain': {'id': entity['domain_id']}}) - if 'inherited_to_projects' in entity: - formatted_entity['scope']['OS-INHERIT:inherited_to'] = ( - 'projects') - target_link = '/OS-INHERIT/domains/%s' % entity['domain_id'] - suffix = '/inherited_to_projects' - else: - target_link = '/domains/%s' % entity['domain_id'] - formatted_entity.setdefault('links', {}) - - path = '%(target)s/%(actor)s/roles/%(role)s%(suffix)s' % { - 'target': target_link, - 'actor': actor_link, - 'role': entity['role_id'], - 'suffix': suffix} - formatted_entity['links']['assignment'] = self.base_url(context, path) - - return formatted_entity - def _expand_indirect_assignments(self, context, refs): - """Processes entity list into all-direct assignments. - - For any group role assignments in the list, create a role assignment - entity for each member of that group, and then remove the group - assignment entity itself from the list. + if 'domain_id' in entity.get('indirect', {}): + inherited_assignment = True + formatted_link = ('/domains/%s' % + entity['indirect']['domain_id']) + elif 'project_id' in entity.get('indirect', {}): + inherited_assignment = True + formatted_link = ('/projects/%s' % + entity['indirect']['project_id']) + else: + formatted_link = '/projects/%s' % entity['project_id'] + elif 'domain_id' in entity: + formatted_entity['scope'] = {'domain': {'id': entity['domain_id']}} + formatted_link = '/domains/%s' % entity['domain_id'] - If the OS-INHERIT extension is enabled, then honor any inherited - roles on the domain by creating the equivalent on all projects - owned by the domain. + if 'user_id' in entity: + formatted_entity['user'] = {'id': entity['user_id']} - For any new entity created by virtue of group membership, add in an - additional link to that membership. + if 'group_id' in entity.get('indirect', {}): + membership_url = ( + self.base_url(context, '/groups/%s/users/%s' % ( + entity['indirect']['group_id'], entity['user_id']))) + formatted_entity['links']['membership'] = membership_url + formatted_link += '/groups/%s' % entity['indirect']['group_id'] + else: + formatted_link += '/users/%s' % entity['user_id'] + elif 'group_id' in entity: + formatted_entity['group'] = {'id': entity['group_id']} + formatted_link += '/groups/%s' % entity['group_id'] - """ - def _get_group_members(ref): - """Get a list of group members. + formatted_entity['role'] = {'id': entity['role_id']} + formatted_link += '/roles/%s' % entity['role_id'] - Get the list of group members. If this fails with - GroupNotFound, then log this as a warning, but allow - overall processing to continue. + if inherited_assignment: + formatted_entity['scope']['OS-INHERIT:inherited_to'] = ( + 'projects') + formatted_link = ('/OS-INHERIT%s/inherited_to_projects' % + formatted_link) - """ - try: - members = self.identity_api.list_users_in_group( - ref['group']['id']) - except exception.GroupNotFound: - members = [] - # The group is missing, which should not happen since - # group deletion should remove any related assignments, so - # log a warning - target = 'Unknown' - # Should always be a domain or project, but since to get - # here things have gone astray, let's be cautious. - if 'scope' in ref: - if 'domain' in ref['scope']: - dom_id = ref['scope']['domain'].get('id', 'Unknown') - target = 'Domain: %s' % dom_id - elif 'project' in ref['scope']: - proj_id = ref['scope']['project'].get('id', 'Unknown') - target = 'Project: %s' % proj_id - role_id = 'Unknown' - if 'role' in ref and 'id' in ref['role']: - role_id = ref['role']['id'] - LOG.warning( - _LW('Group %(group)s not found for role-assignment - ' - '%(target)s with Role: %(role)s'), { - 'group': ref['group']['id'], 'target': target, - 'role': role_id}) - return members - - def _build_user_assignment_equivalent_of_group( - user, group_id, template): - """Create a user assignment equivalent to the group one. - - The template has had the 'group' entity removed, so - substitute a 'user' one. The 'assignment' link stays as it is, - referring to the group assignment that led to this role. - A 'membership' link is added that refers to this particular - user's membership of this group. - - """ - user_entry = copy.deepcopy(template) - user_entry['user'] = {'id': user['id']} - user_entry['links']['membership'] = ( - self.base_url(context, '/groups/%s/users/%s' % - (group_id, user['id']))) - return user_entry - - def _build_project_equivalent_of_user_target_role( - project_id, target_id, target_type, template): - """Create a user project assignment equivalent to the domain one. - - The template has had the 'domain' entity removed, so - substitute a 'project' one, modifying the 'assignment' link - to match. - - """ - project_entry = copy.deepcopy(template) - project_entry['scope']['project'] = {'id': project_id} - project_entry['links']['assignment'] = ( - self.base_url( - context, - '/OS-INHERIT/%s/%s/users/%s/roles/%s' - '/inherited_to_projects' % ( - target_type, target_id, project_entry['user']['id'], - project_entry['role']['id']))) - return project_entry - - def _build_project_equivalent_of_group_target_role( - user_id, group_id, project_id, - target_id, target_type, template): - """Create a user project equivalent to the domain group one. - - The template has had the 'domain' and 'group' entities removed, so - substitute a 'user-project' one, modifying the 'assignment' link - to match. - - """ - project_entry = copy.deepcopy(template) - project_entry['user'] = {'id': user_id} - project_entry['scope']['project'] = {'id': project_id} - project_entry['links']['assignment'] = ( - self.base_url(context, - '/OS-INHERIT/%s/%s/groups/%s/roles/%s' - '/inherited_to_projects' % ( - target_type, target_id, group_id, - project_entry['role']['id']))) - project_entry['links']['membership'] = ( - self.base_url(context, '/groups/%s/users/%s' % - (group_id, user_id))) - return project_entry - - # Scan the list of entities for any assignments that need to be - # expanded. - # - # If the OS-INERIT extension is enabled, the refs lists may - # contain roles to be inherited from domain to project, so expand - # these as well into project equivalents - # - # For any regular group entries, expand these into user entries based - # on membership of that group. - # - # Due to the potentially large expansions, rather than modify the - # list we are enumerating, we build a new one as we go. - # - - new_refs = [] - for r in refs: - if 'OS-INHERIT:inherited_to' in r['scope']: - if 'domain' in r['scope']: - # It's an inherited domain role - so get the list of - # projects owned by this domain. - project_ids = ( - [x['id'] for x in - self.resource_api.list_projects_in_domain( - r['scope']['domain']['id'])]) - base_entry = copy.deepcopy(r) - target_type = 'domains' - target_id = base_entry['scope']['domain']['id'] - base_entry['scope'].pop('domain') - else: - # It's an inherited project role - so get the list of - # projects in this project subtree. - project_id = r['scope']['project']['id'] - project_ids = ( - [x['id'] for x in - self.resource_api.list_projects_in_subtree( - project_id)]) - base_entry = copy.deepcopy(r) - target_type = 'projects' - target_id = base_entry['scope']['project']['id'] - base_entry['scope'].pop('project') - - # For each project, create an equivalent role assignment - for p in project_ids: - # If it's a group assignment, then create equivalent user - # roles based on membership of the group - if 'group' in base_entry: - members = _get_group_members(base_entry) - sub_entry = copy.deepcopy(base_entry) - group_id = sub_entry['group']['id'] - sub_entry.pop('group') - for m in members: - new_entry = ( - _build_project_equivalent_of_group_target_role( - m['id'], group_id, p, - target_id, target_type, sub_entry)) - new_refs.append(new_entry) - else: - new_entry = ( - _build_project_equivalent_of_user_target_role( - p, target_id, target_type, base_entry)) - new_refs.append(new_entry) - elif 'group' in r: - # It's a non-inherited group role assignment, so get the list - # of members. - members = _get_group_members(r) - - # Now replace that group role assignment entry with an - # equivalent user role assignment for each of the group members - base_entry = copy.deepcopy(r) - group_id = base_entry['group']['id'] - base_entry.pop('group') - for m in members: - user_entry = _build_user_assignment_equivalent_of_group( - m, group_id, base_entry) - new_refs.append(user_entry) - else: - new_refs.append(r) + formatted_entity['links']['assignment'] = self.base_url(context, + formatted_link) - return new_refs - - def _filter_inherited(self, entry): - if ('inherited_to_projects' in entry and - not CONF.os_inherit.enabled): - return False - else: - return True + return formatted_entity def _assert_effective_filters(self, inherited, group, domain): """Assert that useless filter combinations are avoided. @@ -762,13 +588,28 @@ class RoleAssignmentV3(controller.V3Controller): 'scope.domain.id', 'scope.project.id', 'scope.OS-INHERIT:inherited_to', 'user.id') def list_role_assignments(self, context, filters): + """List role assignments to user and groups on domains and projects. + + Return a list of all existing role assignments in the system, filtered + by assignments attributes, if provided. - # TODO(henry-nash): This implementation uses the standard filtering - # in the V3.wrap_collection. Given the large number of individual - # assignments, this is pretty inefficient. An alternative would be - # to pass the filters into the driver call, so that the list size is - # kept a minimum. + If effective option is used and OS-INHERIT extension is enabled, the + following functions will be applied: + 1) For any group role assignment on a target, replace it by a set of + role assignments containing one for each user of that group on that + target; + 2) For any inherited role assignment for an actor on a target, replace + it by a set of role assignments for that actor on every project under + that target. + It means that, if effective mode is used, no group or domain inherited + assignments will be present in the resultant list. Thus, combining + effective with them is invalid. + + As a role assignment contains only one actor and one target, providing + both user and group ids or domain and project ids is invalid as well. + + """ params = context['query_string'] effective = 'effective' in params and ( self.query_filter_is_true(params['effective'])) @@ -791,17 +632,17 @@ class RoleAssignmentV3(controller.V3Controller): domain=params.get( 'scope.domain.id')) - hints = self.build_driver_hints(context, filters) - refs = self.assignment_api.list_role_assignments() - formatted_refs = ( - [self._format_entity(context, x) for x in refs - if self._filter_inherited(x)]) + refs = self.assignment_api.list_role_assignments( + role_id=params.get('role.id'), + user_id=params.get('user.id'), + group_id=params.get('group.id'), + domain_id=params.get('scope.domain.id'), + project_id=params.get('scope.project.id'), + inherited=inherited, effective=effective) - if effective: - formatted_refs = self._expand_indirect_assignments(context, - formatted_refs) + formatted_refs = [self._format_entity(context, ref) for ref in refs] - return self.wrap_collection(context, formatted_refs, hints=hints) + return self.wrap_collection(context, formatted_refs) @controller.protected() def get_role_assignment(self, context): |