diff options
Diffstat (limited to 'keystone-moon/keystone/assignment/core.py')
-rw-r--r-- | keystone-moon/keystone/assignment/core.py | 1019 |
1 files changed, 1019 insertions, 0 deletions
diff --git a/keystone-moon/keystone/assignment/core.py b/keystone-moon/keystone/assignment/core.py new file mode 100644 index 00000000..0f9c03e9 --- /dev/null +++ b/keystone-moon/keystone/assignment/core.py @@ -0,0 +1,1019 @@ +# Copyright 2012 OpenStack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Main entry point into the assignment service.""" + +import abc + +from oslo_config import cfg +from oslo_log import log +import six + +from keystone.common import cache +from keystone.common import dependency +from keystone.common import driver_hints +from keystone.common import manager +from keystone import exception +from keystone.i18n import _ +from keystone.i18n import _LI +from keystone import notifications +from keystone.openstack.common import versionutils + + +CONF = cfg.CONF +LOG = log.getLogger(__name__) +MEMOIZE = cache.get_memoization_decorator(section='role') + + +def deprecated_to_role_api(f): + """Specialized deprecation wrapper for assignment to role api. + + This wraps the standard deprecation wrapper and fills in the method + names automatically. + + """ + @six.wraps(f) + def wrapper(*args, **kwargs): + x = versionutils.deprecated( + what='assignment.' + f.__name__ + '()', + as_of=versionutils.deprecated.KILO, + in_favor_of='role.' + f.__name__ + '()') + return x(f) + return wrapper() + + +def deprecated_to_resource_api(f): + """Specialized deprecation wrapper for assignment to resource api. + + This wraps the standard deprecation wrapper and fills in the method + names automatically. + + """ + @six.wraps(f) + def wrapper(*args, **kwargs): + x = versionutils.deprecated( + what='assignment.' + f.__name__ + '()', + as_of=versionutils.deprecated.KILO, + in_favor_of='resource.' + f.__name__ + '()') + return x(f) + return wrapper() + + +@dependency.provider('assignment_api') +@dependency.requires('credential_api', 'identity_api', 'resource_api', + 'revoke_api', 'role_api') +class Manager(manager.Manager): + """Default pivot point for the Assignment backend. + + See :mod:`keystone.common.manager.Manager` for more details on how this + dynamically calls the backend. + + """ + _PROJECT = 'project' + _ROLE_REMOVED_FROM_USER = 'role_removed_from_user' + _INVALIDATION_USER_PROJECT_TOKENS = 'invalidate_user_project_tokens' + + def __init__(self): + assignment_driver = CONF.assignment.driver + + # If there is no explicit assignment driver specified, we let the + # identity driver tell us what to use. This is for backward + # compatibility reasons from the time when identity, resource and + # assignment were all part of identity. + if assignment_driver is None: + identity_driver = dependency.get_provider('identity_api').driver + assignment_driver = identity_driver.default_assignment_driver() + + super(Manager, self).__init__(assignment_driver) + + def _get_group_ids_for_user_id(self, user_id): + # TODO(morganfainberg): Implement a way to get only group_ids + # instead of the more expensive to_dict() call for each record. + return [x['id'] for + x in self.identity_api.list_groups_for_user(user_id)] + + def list_user_ids_for_project(self, tenant_id): + self.resource_api.get_project(tenant_id) + return self.driver.list_user_ids_for_project(tenant_id) + + def _list_parent_ids_of_project(self, project_id): + if CONF.os_inherit.enabled: + return [x['id'] for x in ( + self.resource_api.list_project_parents(project_id))] + else: + return [] + + def get_roles_for_user_and_project(self, user_id, tenant_id): + """Get the roles associated with a user within given project. + + This includes roles directly assigned to the user on the + project, as well as those by virtue of group membership. If + the OS-INHERIT extension is enabled, then this will also + include roles inherited from the domain. + + :returns: a list of role ids. + :raises: keystone.exception.UserNotFound, + keystone.exception.ProjectNotFound + + """ + def _get_group_project_roles(user_id, project_ref): + group_ids = self._get_group_ids_for_user_id(user_id) + return self.driver.list_role_ids_for_groups_on_project( + group_ids, + project_ref['id'], + project_ref['domain_id'], + self._list_parent_ids_of_project(project_ref['id'])) + + def _get_user_project_roles(user_id, project_ref): + role_list = [] + try: + metadata_ref = self._get_metadata(user_id=user_id, + tenant_id=project_ref['id']) + role_list = self._roles_from_role_dicts( + metadata_ref.get('roles', {}), False) + except exception.MetadataNotFound: + pass + + if CONF.os_inherit.enabled: + # Now get any inherited roles for the owning domain + try: + metadata_ref = self._get_metadata( + user_id=user_id, domain_id=project_ref['domain_id']) + role_list += self._roles_from_role_dicts( + metadata_ref.get('roles', {}), True) + except (exception.MetadataNotFound, exception.NotImplemented): + pass + # As well inherited roles from parent projects + for p in self.list_project_parents(project_ref['id']): + p_roles = self.list_grants( + user_id=user_id, project_id=p['id'], + inherited_to_projects=True) + role_list += [x['id'] for x in p_roles] + + return role_list + + project_ref = self.resource_api.get_project(tenant_id) + user_role_list = _get_user_project_roles(user_id, project_ref) + group_role_list = _get_group_project_roles(user_id, project_ref) + # Use set() to process the list to remove any duplicates + return list(set(user_role_list + group_role_list)) + + def get_roles_for_user_and_domain(self, user_id, domain_id): + """Get the roles associated with a user within given domain. + + :returns: a list of role ids. + :raises: keystone.exception.UserNotFound, + keystone.exception.DomainNotFound + + """ + + def _get_group_domain_roles(user_id, domain_id): + role_list = [] + group_ids = self._get_group_ids_for_user_id(user_id) + for group_id in group_ids: + try: + metadata_ref = self._get_metadata(group_id=group_id, + domain_id=domain_id) + role_list += self._roles_from_role_dicts( + metadata_ref.get('roles', {}), False) + except (exception.MetadataNotFound, exception.NotImplemented): + # MetadataNotFound implies no group grant, so skip. + # Ignore NotImplemented since not all backends support + # domains. + pass + return role_list + + def _get_user_domain_roles(user_id, domain_id): + metadata_ref = {} + try: + metadata_ref = self._get_metadata(user_id=user_id, + domain_id=domain_id) + except (exception.MetadataNotFound, exception.NotImplemented): + # MetadataNotFound implies no user grants. + # Ignore NotImplemented since not all backends support + # domains + pass + return self._roles_from_role_dicts( + metadata_ref.get('roles', {}), False) + + self.get_domain(domain_id) + user_role_list = _get_user_domain_roles(user_id, domain_id) + group_role_list = _get_group_domain_roles(user_id, domain_id) + # Use set() to process the list to remove any duplicates + return list(set(user_role_list + group_role_list)) + + 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.driver.list_role_ids_for_groups_on_project( + group_ids, project_id, project['domain_id'], + self._list_parent_ids_of_project(project_id)) + elif domain_id is not None: + role_ids = self.driver.list_role_ids_for_groups_on_domain( + group_ids, domain_id) + else: + raise AttributeError(_("Must specify either domain or project")) + + return self.role_api.list_roles_from_ids(role_ids) + + def add_user_to_project(self, tenant_id, user_id): + """Add user to a tenant by creating a default role relationship. + + :raises: keystone.exception.ProjectNotFound, + keystone.exception.UserNotFound + + """ + self.resource_api.get_project(tenant_id) + try: + self.role_api.get_role(CONF.member_role_id) + self.driver.add_role_to_user_and_project( + user_id, + tenant_id, + CONF.member_role_id) + except exception.RoleNotFound: + LOG.info(_LI("Creating the default role %s " + "because it does not exist."), + CONF.member_role_id) + role = {'id': CONF.member_role_id, + 'name': CONF.member_role_name} + try: + self.role_api.create_role(CONF.member_role_id, role) + except exception.Conflict: + LOG.info(_LI("Creating the default role %s failed because it " + "was already created"), + CONF.member_role_id) + # now that default role exists, the add should succeed + self.driver.add_role_to_user_and_project( + user_id, + tenant_id, + CONF.member_role_id) + + def add_role_to_user_and_project(self, user_id, tenant_id, role_id): + self.resource_api.get_project(tenant_id) + self.role_api.get_role(role_id) + self.driver.add_role_to_user_and_project(user_id, tenant_id, role_id) + + def remove_user_from_project(self, tenant_id, user_id): + """Remove user from a tenant + + :raises: keystone.exception.ProjectNotFound, + keystone.exception.UserNotFound + + """ + roles = self.get_roles_for_user_and_project(user_id, tenant_id) + if not roles: + raise exception.NotFound(tenant_id) + for role_id in roles: + try: + self.driver.remove_role_from_user_and_project(user_id, + tenant_id, + role_id) + self.revoke_api.revoke_by_grant(role_id, user_id=user_id, + project_id=tenant_id) + + except exception.RoleNotFound: + LOG.debug("Removing role %s failed because it does not exist.", + role_id) + + # TODO(henry-nash): We might want to consider list limiting this at some + # point in the future. + def list_projects_for_user(self, user_id, hints=None): + # NOTE(henry-nash): In order to get a complete list of user projects, + # the driver will need to look at group assignments. To avoid cross + # calling between the assignment and identity driver we get the group + # list here and pass it in. The rest of the detailed logic of listing + # projects for a user is pushed down into the driver to enable + # optimization with the various backend technologies (SQL, LDAP etc.). + + group_ids = self._get_group_ids_for_user_id(user_id) + project_ids = self.driver.list_project_ids_for_user( + user_id, group_ids, hints or driver_hints.Hints()) + + if not CONF.os_inherit.enabled: + return self.resource_api.list_projects_from_ids(project_ids) + + # Inherited roles are enabled, so check to see if this user has any + # inherited role (direct or group) on any parent project, in which + # case we must add in all the projects in that parent's subtree. + project_ids = set(project_ids) + project_ids_inherited = self.driver.list_project_ids_for_user( + user_id, group_ids, hints or driver_hints.Hints(), inherited=True) + for proj_id in project_ids_inherited: + project_ids.update( + (x['id'] for x in + self.resource_api.list_projects_in_subtree(proj_id))) + + # Now do the same for any domain inherited roles + domain_ids = self.driver.list_domain_ids_for_user( + user_id, group_ids, hints or driver_hints.Hints(), + inherited=True) + project_ids.update( + self.resource_api.list_project_ids_from_domain_ids(domain_ids)) + + return self.resource_api.list_projects_from_ids(list(project_ids)) + + # TODO(henry-nash): We might want to consider list limiting this at some + # point in the future. + def list_domains_for_user(self, user_id, hints=None): + # NOTE(henry-nash): In order to get a complete list of user domains, + # the driver will need to look at group assignments. To avoid cross + # calling between the assignment and identity driver we get the group + # list here and pass it in. The rest of the detailed logic of listing + # projects for a user is pushed down into the driver to enable + # optimization with the various backend technologies (SQL, LDAP etc.). + group_ids = self._get_group_ids_for_user_id(user_id) + domain_ids = self.driver.list_domain_ids_for_user( + user_id, group_ids, hints or driver_hints.Hints()) + return self.resource_api.list_domains_from_ids(domain_ids) + + def list_domains_for_groups(self, group_ids): + domain_ids = self.driver.list_domain_ids_for_groups(group_ids) + return self.resource_api.list_domains_from_ids(domain_ids) + + def list_projects_for_groups(self, group_ids): + project_ids = ( + self.driver.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) + + # Inherited roles are enabled, so check to see if these groups have any + # roles on any domain, in which case we must add in all the projects + # in that domain. + + domain_ids = self.driver.list_domain_ids_for_groups( + group_ids, inherited=True) + + project_ids_from_domains = ( + self.resource_api.list_project_ids_from_domain_ids(domain_ids)) + + return self.resource_api.list_projects_from_ids( + list(set(project_ids + project_ids_from_domains))) + + def list_role_assignments_for_role(self, role_id=None): + # NOTE(henry-nash): Currently the efficiency of the key driver + # implementation (SQL) of list_role_assignments is severely hampered by + # the existence of the multiple grant tables - hence there is little + # advantage in pushing the logic of this method down into the driver. + # Once the single assignment table is implemented, then this situation + # will be different, and this method should have its own driver + # implementation. + return [r for r in self.driver.list_role_assignments() + if r['role_id'] == role_id] + + def remove_role_from_user_and_project(self, user_id, tenant_id, role_id): + self.driver.remove_role_from_user_and_project(user_id, tenant_id, + role_id) + self.identity_api.emit_invalidate_user_token_persistence(user_id) + self.revoke_api.revoke_by_grant(role_id, user_id=user_id, + project_id=tenant_id) + + @notifications.internal(notifications.INVALIDATE_USER_TOKEN_PERSISTENCE) + def _emit_invalidate_user_token_persistence(self, user_id): + self.identity_api.emit_invalidate_user_token_persistence(user_id) + + @notifications.role_assignment('created') + def create_grant(self, role_id, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False, context=None): + self.role_api.get_role(role_id) + if domain_id: + self.resource_api.get_domain(domain_id) + if project_id: + self.resource_api.get_project(project_id) + self.driver.create_grant(role_id, user_id, group_id, domain_id, + project_id, inherited_to_projects) + + def get_grant(self, role_id, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + role_ref = self.role_api.get_role(role_id) + if domain_id: + self.resource_api.get_domain(domain_id) + if project_id: + self.resource_api.get_project(project_id) + self.driver.check_grant_role_id( + role_id, user_id, group_id, domain_id, project_id, + inherited_to_projects) + return role_ref + + def list_grants(self, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + if domain_id: + self.resource_api.get_domain(domain_id) + if project_id: + self.resource_api.get_project(project_id) + grant_ids = self.driver.list_grant_role_ids( + user_id, group_id, domain_id, project_id, inherited_to_projects) + return self.role_api.list_roles_from_ids(grant_ids) + + @notifications.role_assignment('deleted') + def delete_grant(self, role_id, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False, context=None): + if group_id is None: + self.revoke_api.revoke_by_grant(user_id=user_id, + role_id=role_id, + domain_id=domain_id, + project_id=project_id) + else: + try: + # NOTE(morganfainberg): The user ids are the important part + # for invalidating tokens below, so extract them here. + for user in self.identity_api.list_users_in_group(group_id): + if user['id'] != user_id: + self._emit_invalidate_user_token_persistence( + user['id']) + self.revoke_api.revoke_by_grant( + user_id=user['id'], role_id=role_id, + domain_id=domain_id, project_id=project_id) + except exception.GroupNotFound: + LOG.debug('Group %s not found, no tokens to invalidate.', + group_id) + + # TODO(henry-nash): While having the call to get_role here mimics the + # previous behavior (when it was buried inside the driver delete call), + # this seems an odd place to have this check, given what we have + # already done so far in this method. See Bug #1406776. + self.role_api.get_role(role_id) + + if domain_id: + self.resource_api.get_domain(domain_id) + if project_id: + self.resource_api.get_project(project_id) + self.driver.delete_grant(role_id, user_id, group_id, domain_id, + project_id, inherited_to_projects) + if user_id is not None: + self._emit_invalidate_user_token_persistence(user_id) + + def delete_tokens_for_role_assignments(self, role_id): + assignments = self.list_role_assignments_for_role(role_id=role_id) + + # Iterate over the assignments for this role and build the list of + # user or user+project IDs for the tokens we need to delete + user_ids = set() + user_and_project_ids = list() + for assignment in assignments: + # If we have a project assignment, then record both the user and + # project IDs so we can target the right token to delete. If it is + # a domain assignment, we might as well kill all the tokens for + # the user, since in the vast majority of cases all the tokens + # for a user will be within one domain anyway, so not worth + # trying to delete tokens for each project in the domain. + if 'user_id' in assignment: + if 'project_id' in assignment: + user_and_project_ids.append( + (assignment['user_id'], assignment['project_id'])) + elif 'domain_id' in assignment: + self._emit_invalidate_user_token_persistence( + assignment['user_id']) + elif 'group_id' in assignment: + # Add in any users for this group, being tolerant of any + # cross-driver database integrity errors. + try: + users = self.identity_api.list_users_in_group( + assignment['group_id']) + except exception.GroupNotFound: + # Ignore it, but log a debug message + if 'project_id' in assignment: + target = _('Project (%s)') % assignment['project_id'] + elif 'domain_id' in assignment: + target = _('Domain (%s)') % assignment['domain_id'] + else: + target = _('Unknown Target') + msg = ('Group (%(group)s), referenced in assignment ' + 'for %(target)s, not found - ignoring.') + LOG.debug(msg, {'group': assignment['group_id'], + 'target': target}) + continue + + if 'project_id' in assignment: + for user in users: + user_and_project_ids.append( + (user['id'], assignment['project_id'])) + elif 'domain_id' in assignment: + for user in users: + self._emit_invalidate_user_token_persistence( + user['id']) + + # Now process the built up lists. Before issuing calls to delete any + # tokens, let's try and minimize the number of calls by pruning out + # any user+project deletions where a general token deletion for that + # same user is also planned. + user_and_project_ids_to_action = [] + for user_and_project_id in user_and_project_ids: + if user_and_project_id[0] not in user_ids: + user_and_project_ids_to_action.append(user_and_project_id) + + for user_id, project_id in user_and_project_ids_to_action: + self._emit_invalidate_user_project_tokens_notification( + {'user_id': user_id, + 'project_id': project_id}) + + @notifications.internal( + notifications.INVALIDATE_USER_PROJECT_TOKEN_PERSISTENCE) + def _emit_invalidate_user_project_tokens_notification(self, payload): + # This notification's payload is a dict of user_id and + # project_id so the token provider can invalidate the tokens + # from persistence if persistence is enabled. + pass + + @deprecated_to_role_api + def create_role(self, role_id, role): + return self.role_api.create_role(role_id, role) + + @deprecated_to_role_api + def get_role(self, role_id): + return self.role_api.get_role(role_id) + + @deprecated_to_role_api + def update_role(self, role_id, role): + return self.role_api.update_role(role_id, role) + + @deprecated_to_role_api + def delete_role(self, role_id): + return self.role_api.delete_role(role_id) + + @deprecated_to_role_api + def list_roles(self, hints=None): + return self.role_api.list_roles(hints=hints) + + @deprecated_to_resource_api + def create_project(self, project_id, project): + return self.resource_api.create_project(project_id, project) + + @deprecated_to_resource_api + def get_project_by_name(self, tenant_name, domain_id): + return self.resource_api.get_project_by_name(tenant_name, domain_id) + + @deprecated_to_resource_api + def get_project(self, project_id): + return self.resource_api.get_project(project_id) + + @deprecated_to_resource_api + def update_project(self, project_id, project): + return self.resource_api.update_project(project_id, project) + + @deprecated_to_resource_api + def delete_project(self, project_id): + return self.resource_api.delete_project(project_id) + + @deprecated_to_resource_api + def list_projects(self, hints=None): + return self.resource_api.list_projects(hints=hints) + + @deprecated_to_resource_api + def list_projects_in_domain(self, domain_id): + return self.resource_api.list_projects_in_domain(domain_id) + + @deprecated_to_resource_api + def create_domain(self, domain_id, domain): + return self.resource_api.create_domain(domain_id, domain) + + @deprecated_to_resource_api + def get_domain_by_name(self, domain_name): + return self.resource_api.get_domain_by_name(domain_name) + + @deprecated_to_resource_api + def get_domain(self, domain_id): + return self.resource_api.get_domain(domain_id) + + @deprecated_to_resource_api + def update_domain(self, domain_id, domain): + return self.resource_api.update_domain(domain_id, domain) + + @deprecated_to_resource_api + def delete_domain(self, domain_id): + return self.resource_api.delete_domain(domain_id) + + @deprecated_to_resource_api + def list_domains(self, hints=None): + return self.resource_api.list_domains(hints=hints) + + @deprecated_to_resource_api + def assert_domain_enabled(self, domain_id, domain=None): + return self.resource_api.assert_domain_enabled(domain_id, domain) + + @deprecated_to_resource_api + def assert_project_enabled(self, project_id, project=None): + return self.resource_api.assert_project_enabled(project_id, project) + + @deprecated_to_resource_api + def is_leaf_project(self, project_id): + return self.resource_api.is_leaf_project(project_id) + + @deprecated_to_resource_api + def list_project_parents(self, project_id, user_id=None): + return self.resource_api.list_project_parents(project_id, user_id) + + @deprecated_to_resource_api + def list_projects_in_subtree(self, project_id, user_id=None): + return self.resource_api.list_projects_in_subtree(project_id, user_id) + + +@six.add_metaclass(abc.ABCMeta) +class Driver(object): + + def _role_to_dict(self, role_id, inherited): + role_dict = {'id': role_id} + if inherited: + role_dict['inherited_to'] = 'projects' + return role_dict + + def _roles_from_role_dicts(self, dict_list, inherited): + role_list = [] + for d in dict_list: + if ((not d.get('inherited_to') and not inherited) or + (d.get('inherited_to') == 'projects' and inherited)): + role_list.append(d['id']) + return role_list + + def _add_role_to_role_dicts(self, role_id, inherited, dict_list, + allow_existing=True): + # There is a difference in error semantics when trying to + # assign a role that already exists between the coded v2 and v3 + # API calls. v2 will error if the assignment already exists, + # while v3 is silent. Setting the 'allow_existing' parameter + # appropriately lets this call be used for both. + role_set = set([frozenset(r.items()) for r in dict_list]) + key = frozenset(self._role_to_dict(role_id, inherited).items()) + if not allow_existing and key in role_set: + raise KeyError + role_set.add(key) + return [dict(r) for r in role_set] + + def _remove_role_from_role_dicts(self, role_id, inherited, dict_list): + role_set = set([frozenset(r.items()) for r in dict_list]) + role_set.remove(frozenset(self._role_to_dict(role_id, + inherited).items())) + return [dict(r) for r in role_set] + + def _get_list_limit(self): + return CONF.assignment.list_limit or CONF.list_limit + + @abc.abstractmethod + def list_user_ids_for_project(self, tenant_id): + """Lists all user IDs with a role assignment in the specified project. + + :returns: a list of user_ids or an empty set. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def add_role_to_user_and_project(self, user_id, tenant_id, role_id): + """Add a role to a user within given tenant. + + :raises: keystone.exception.Conflict + + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def remove_role_from_user_and_project(self, user_id, tenant_id, role_id): + """Remove a role from a user within given tenant. + + :raises: keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() # pragma: no cover + + # assignment/grant crud + + @abc.abstractmethod + def create_grant(self, role_id, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + """Creates a new assignment/grant. + + If the assignment is to a domain, then optionally it may be + specified as inherited to owned projects (this requires + the OS-INHERIT extension to be enabled). + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def list_grant_role_ids(self, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + """Lists role ids for assignments/grants.""" + + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def check_grant_role_id(self, role_id, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + """Checks an assignment/grant role id. + + :raises: keystone.exception.RoleAssignmentNotFound + :returns: None or raises an exception if grant not found + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def delete_grant(self, role_id, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + """Deletes assignments/grants. + + :raises: keystone.exception.RoleAssignmentNotFound + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def list_role_assignments(self): + + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def list_project_ids_for_user(self, user_id, group_ids, hints, + inherited=False): + """List all project ids associated with a given user. + + :param user_id: the user in question + :param group_ids: the groups this user is a member of. This list is + built in the Manager, so that the driver itself + does not have to call across to identity. + :param hints: filter hints which the driver should + implement if at all possible. + :param inherited: whether assignments marked as inherited should + be included. + + :returns: a list of project ids or an empty list. + + This method should not try and expand any inherited assignments, + just report the projects that have the role for this user. The manager + method is responsible for expanding out inherited assignments. + + """ + 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_user(self, user_id, group_ids, hints, + inherited=False): + """List all domain ids associated with a given user. + + :param user_id: the user in question + :param group_ids: the groups this user is a member of. This list is + built in the Manager, so that the driver itself + does not have to call across to identity. + :param hints: filter hints which the driver should + implement if at all possible. + :param inherited: whether to return domain_ids that have inherited + assignments or not. + + :returns: a list of domain ids or an empty list. + + """ + 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. + + :raises: keystone.exception.ProjectNotFound + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def delete_role_assignments(self, role_id): + """Deletes all assignments for a role.""" + + raise exception.NotImplemented() # pragma: no cover + + # TODO(henry-nash): Rename the following two methods to match the more + # meaningfully named ones above. + +# TODO(ayoung): determine what else these two functions raise + @abc.abstractmethod + def delete_user(self, user_id): + """Deletes all assignments for a user. + + :raises: keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def delete_group(self, group_id): + """Deletes all assignments for a group. + + :raises: keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() # pragma: no cover + + +@dependency.provider('role_api') +@dependency.requires('assignment_api') +class RoleManager(manager.Manager): + """Default pivot point for the Role backend.""" + + _ROLE = 'role' + + def __init__(self): + # If there is a specific driver specified for role, then use it. + # Otherwise retrieve the driver type from the assignment driver. + role_driver = CONF.role.driver + + if role_driver is None: + assignment_driver = ( + dependency.get_provider('assignment_api').driver) + role_driver = assignment_driver.default_role_driver() + + super(RoleManager, self).__init__(role_driver) + + @MEMOIZE + def get_role(self, role_id): + return self.driver.get_role(role_id) + + def create_role(self, role_id, role, initiator=None): + ret = self.driver.create_role(role_id, role) + notifications.Audit.created(self._ROLE, role_id, initiator) + if MEMOIZE.should_cache(ret): + self.get_role.set(ret, self, role_id) + return ret + + @manager.response_truncated + def list_roles(self, hints=None): + return self.driver.list_roles(hints or driver_hints.Hints()) + + def update_role(self, role_id, role, initiator=None): + ret = self.driver.update_role(role_id, role) + notifications.Audit.updated(self._ROLE, role_id, initiator) + self.get_role.invalidate(self, role_id) + return ret + + def delete_role(self, role_id, initiator=None): + try: + self.assignment_api.delete_tokens_for_role_assignments(role_id) + except exception.NotImplemented: + # FIXME(morganfainberg): Not all backends (ldap) implement + # `list_role_assignments_for_role` which would have previously + # caused a NotImplmented error to be raised when called through + # the controller. Now error or proper action will always come from + # the `delete_role` method logic. Work needs to be done to make + # the behavior between drivers consistent (capable of revoking + # tokens for the same circumstances). This is related to the bug + # https://bugs.launchpad.net/keystone/+bug/1221805 + pass + self.assignment_api.delete_role_assignments(role_id) + self.driver.delete_role(role_id) + notifications.Audit.deleted(self._ROLE, role_id, initiator) + self.get_role.invalidate(self, role_id) + + +@six.add_metaclass(abc.ABCMeta) +class RoleDriver(object): + + def _get_list_limit(self): + return CONF.role.list_limit or CONF.list_limit + + @abc.abstractmethod + def create_role(self, role_id, role): + """Creates a new role. + + :raises: keystone.exception.Conflict + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def list_roles(self, hints): + """List roles in the system. + + :param hints: filter hints which the driver should + implement if at all possible. + + :returns: a list of role_refs or an empty list. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def list_roles_from_ids(self, role_ids): + """List roles for the provided list of ids. + + :param role_ids: list of ids + + :returns: a list of role_refs. + + This method is used internally by the assignment manager to bulk read + a set of roles given their ids. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def get_role(self, role_id): + """Get a role by ID. + + :returns: role_ref + :raises: keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def update_role(self, role_id, role): + """Updates an existing role. + + :raises: keystone.exception.RoleNotFound, + keystone.exception.Conflict + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def delete_role(self, role_id): + """Deletes an existing role. + + :raises: keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() # pragma: no cover |