summaryrefslogtreecommitdiffstats
path: root/keystone/identity/controllers.py
diff options
context:
space:
mode:
Diffstat (limited to 'keystone/identity/controllers.py')
-rw-r--r--keystone/identity/controllers.py355
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()