diff options
Diffstat (limited to 'keystone/identity/controllers.py')
-rw-r--r-- | keystone/identity/controllers.py | 355 |
1 files changed, 351 insertions, 4 deletions
diff --git a/keystone/identity/controllers.py b/keystone/identity/controllers.py index f798e3dc..7ca1f8bf 100644 --- a/keystone/identity/controllers.py +++ b/keystone/identity/controllers.py @@ -16,6 +16,7 @@ """Workflow Logic the Identity service.""" +import copy import urllib import urlparse import uuid @@ -402,6 +403,8 @@ class DomainV3(controller.V3Controller): @controller.protected def create_domain(self, context, domain): + self._require_attribute(domain, 'name') + ref = self._assign_unique_id(self._normalize_dict(domain)) ref = self.identity_api.create_domain(ref['id'], ref) return DomainV3.wrap_member(context, ref) @@ -543,6 +546,8 @@ class ProjectV3(controller.V3Controller): @controller.protected def create_project(self, context, project): + self._require_attribute(project, 'name') + ref = self._assign_unique_id(self._normalize_dict(project)) ref = self._normalize_domain_id(context, ref) ref = self.identity_api.create_project(ref['id'], ref) @@ -591,6 +596,8 @@ class UserV3(controller.V3Controller): @controller.protected def create_user(self, context, user): + self._require_attribute(user, 'name') + ref = self._assign_unique_id(self._normalize_dict(user)) ref = self._normalize_domain_id(context, ref) ref = self.identity_api.create_user(ref['id'], ref) @@ -662,6 +669,8 @@ class GroupV3(controller.V3Controller): @controller.protected def create_group(self, context, group): + self._require_attribute(group, 'name') + ref = self._assign_unique_id(self._normalize_dict(group)) ref = self._normalize_domain_id(context, ref) ref = self.identity_api.create_group(ref['id'], ref) @@ -712,6 +721,8 @@ class RoleV3(controller.V3Controller): @controller.protected def create_role(self, context, role): + self._require_attribute(role, 'name') + ref = self._assign_unique_id(self._normalize_dict(role)) ref = self.identity_api.create_role(ref['id'], ref) return RoleV3.wrap_member(context, ref) @@ -747,6 +758,11 @@ class RoleV3(controller.V3Controller): msg = 'Specify a user or group, not both' raise exception.ValidationError(msg) + def _check_if_inherited(self, context): + return (CONF.os_inherit.enabled and + context['path'].startswith('/OS-INHERIT') and + context['path'].endswith('/inherited_to_projects')) + @controller.protected def create_grant(self, context, role_id, user_id=None, group_id=None, domain_id=None, project_id=None): @@ -755,7 +771,8 @@ class RoleV3(controller.V3Controller): self._require_user_xor_group(user_id, group_id) self.identity_api.create_grant( - role_id, user_id, group_id, domain_id, project_id) + role_id, user_id, group_id, domain_id, project_id, + self._check_if_inherited(context)) @controller.protected def list_grants(self, context, user_id=None, group_id=None, @@ -765,7 +782,8 @@ class RoleV3(controller.V3Controller): self._require_user_xor_group(user_id, group_id) refs = self.identity_api.list_grants( - user_id, group_id, domain_id, project_id) + user_id, group_id, domain_id, project_id, + self._check_if_inherited(context)) return RoleV3.wrap_collection(context, refs) @controller.protected @@ -776,7 +794,8 @@ class RoleV3(controller.V3Controller): self._require_user_xor_group(user_id, group_id) self.identity_api.get_grant( - role_id, user_id, group_id, domain_id, project_id) + role_id, user_id, group_id, domain_id, project_id, + self._check_if_inherited(context)) @controller.protected def revoke_grant(self, context, role_id, user_id=None, group_id=None, @@ -786,7 +805,8 @@ class RoleV3(controller.V3Controller): self._require_user_xor_group(user_id, group_id) self.identity_api.delete_grant( - role_id, user_id, group_id, domain_id, project_id) + role_id, user_id, group_id, domain_id, project_id, + self._check_if_inherited(context)) # Now delete any tokens for this user or, in the case of a group, # tokens from all the uses who are members of this group. @@ -794,3 +814,330 @@ class RoleV3(controller.V3Controller): self._delete_tokens_for_user(user_id) else: self._delete_tokens_for_group(group_id) + + +class RoleAssignmentV3(controller.V3Controller): + + # TODO(henry-nash): The current implementation does not provide a full + # first class entity for role-assignment. There is no role_assignment_id + # and only the list_role_assignment call is supported. Further, since it + # is not a first class entity, the links for the individual entities + # reference the individual role grant APIs. + + collection_name = 'role_assignments' + member_name = 'role_assignment' + + @classmethod + def wrap_member(cls, context, ref): + # NOTE(henry-nash): Since we are not yet a true collection, we override + # the wrapper as have already included the links in the entities + pass + + def _format_entity(self, entity): + """Format an assignment entity for API response. + + The driver layer returns entities as dicts containing the ids of the + 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: + + {'user_id': user_id, + 'project_id': domain_id, + 'role_id': role_id} + + or, for an inherited role: + + {'user_id': user_id, + 'domain_id': domain_id, + 'role_id': role_id, + 'inherited_to_projects': true} + + This function maps this into the format to be returned via the API, + e.g. for the second example above: + + { + 'user': { + {'id': user_id} + }, + 'scope': { + 'domain': { + {'id': domain_id} + }, + 'OS-INHERIT:inherited_to': 'projects + }, + 'role': { + {'id': role_id} + }, + 'links': { + 'assignment': '/domains/domain_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']} + if 'project_id' in entity: + formatted_entity['scope'] = ( + {'project': {'id': entity['project_id']}}) + 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', {}) + formatted_entity['links']['assignment'] = ( + self.base_url('%(target)s/%(actor)s/roles/%(role)s%(suffix)s' % { + 'target': target_link, + 'actor': actor_link, + 'role': entity['role_id'], + 'suffix': suffix})) + + return formatted_entity + + def _expand_indirect_assignments(self, 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 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. + + For any new entity created by virtue of group membership, add in an + additional link to that membership. + + """ + def _get_group_members(ref): + """Get a list of group members. + + Get the list of group members. If this fails with + GroupNotFound, then log this as a warning, but allow + overall processing to continue. + + """ + 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 + if 'domain' in ref: + target = 'Domain: %s' % ref['domain'].get('domain_id') + elif 'project' in ref: + target = 'Project: %s' % ref['project'].get('project_id') + else: + # Should always be a domain or project, but since to get + # here things have gone astray, let's be cautious. + target = 'Unknown' + LOG.warning( + _('Group %(group)s not found for role-assignment - ' + '%(target)s with Role: %(role)s') % { + 'group': ref['group_id'], 'target': target, + 'role': ref.get('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('/groups/%s/users/%s' % + (group_id, user['id']))) + return user_entry + + def _build_project_equivalent_of_user_domain_role( + project_id, domain_id, 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( + '/OS-INHERIT/domains/%s/users/%s/roles/%s' + '/inherited_to_projects' % ( + domain_id, project_entry['user']['id'], + project_entry['role']['id']))) + return project_entry + + def _build_project_equivalent_of_group_domain_role( + user_id, group_id, project_id, domain_id, 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('/OS-INHERIT/domains/%s/groups/%s/roles/%s' + '/inherited_to_projects' % ( + domain_id, group_id, + project_entry['role']['id']))) + project_entry['links']['membership'] = ( + self.base_url('/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']: + # It's an inherited domain role - so get the list of projects + # owned by this domain. A domain scope is guaranteed since we + # checked this when we built the refs list + project_ids = ( + [x['id'] for x in self.assignment_api.list_projects( + r['scope']['domain']['id'])]) + base_entry = copy.deepcopy(r) + domain_id = base_entry['scope']['domain']['id'] + base_entry['scope'].pop('domain') + # 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_domain_role( + m['id'], group_id, p, + domain_id, sub_entry)) + new_refs.append(new_entry) + else: + new_entry = ( + _build_project_equivalent_of_user_domain_role( + p, domain_id, 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) + + return new_refs + + def _query_filter_is_true(self, filter_value): + """Determine if bool query param is 'True'. + + We treat this the same way as we do for policy + enforcement: + + {bool_param}=0 is treated as False + + Any other value is considered to be equivalent to + True, including the absence of a value + + """ + + if (isinstance(filter_value, basestring) and + filter_value == '0'): + val = False + else: + val = True + return val + + def _filter_inherited(self, entry): + if ('inherited_to_projects' in entry and + not CONF.os_inherit.enabled): + return False + else: + return True + + @controller.filterprotected('group.id', 'role.id', + 'scope.domain.id', 'scope.project.id', + 'scope.OS-INHERIT:inherited_to', 'user.id') + def list_role_assignments(self, context, filters): + + # 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. + + refs = self.identity_api.list_role_assignments() + formatted_refs = ( + [self._format_entity(x) for x in refs + if self._filter_inherited(x)]) + + if ('effective' in context['query_string'] and + self._query_filter_is_true( + context['query_string']['effective'])): + + formatted_refs = self._expand_indirect_assignments(formatted_refs) + + return self.wrap_collection(context, formatted_refs, filters) + + @controller.protected + def get_role_assignment(self, context): + raise exception.NotImplemented() + + @controller.protected + def update_role_assignment(self, context): + raise exception.NotImplemented() + + @controller.protected + def delete_role_assignment(self, context): + raise exception.NotImplemented() |