diff options
Diffstat (limited to 'keystone/assignment/core.py')
-rw-r--r-- | keystone/assignment/core.py | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/keystone/assignment/core.py b/keystone/assignment/core.py new file mode 100644 index 00000000..64edb3fa --- /dev/null +++ b/keystone/assignment/core.py @@ -0,0 +1,548 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC +# +# 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.""" + +from keystone.common import dependency +from keystone.common import logging +from keystone.common import manager +from keystone import config +from keystone import exception + + +CONF = config.CONF +LOG = logging.getLogger(__name__) + +DEFAULT_DOMAIN = {'description': + (u'Owns users and tenants (i.e. projects)' + ' available on Identity API v2.'), + 'enabled': True, + 'id': CONF.identity.default_domain_id, + 'name': u'Default'} + + +@dependency.provider('assignment_api') +@dependency.requires('identity_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. + assignment.Manager() and identity.Manager() have a circular dependency. + The late import works around this. THe if block prevents creation of the + api object by both managers. + """ + + def __init__(self): + assignment_driver = CONF.assignment.driver + + if assignment_driver is None: + identity_driver = dependency.REGISTRY['identity_api'].driver + assignment_driver = identity_driver.default_assignment_driver() + + super(Manager, self).__init__(assignment_driver) + + 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): + role_list = [] + group_refs = (self.identity_api.list_groups_for_user + (user_id=user_id)) + for x in group_refs: + try: + metadata_ref = self._get_metadata( + group_id=x['id'], tenant_id=project_ref['id']) + role_list += self._roles_from_role_dicts( + metadata_ref.get('roles', {}), False) + except exception.MetadataNotFound: + # no group grant, skip + pass + + if CONF.os_inherit.enabled: + # Now get any inherited group roles for the owning domain + try: + metadata_ref = self._get_metadata( + group_id=x['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 + + return role_list + + 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 + + return role_list + + self.identity_api.get_user(user_id) + project_ref = self.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_refs = (self.identity_api. + list_groups_for_user(user_id=user_id)) + for x in group_refs: + try: + metadata_ref = self._get_metadata(group_id=x['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.identity_api.get_user(user_id) + 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 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.driver.add_role_to_user_and_project(user_id, + tenant_id, + config.CONF.member_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: + self.remove_role_from_user_and_project(user_id, tenant_id, role_id) + + +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_project_by_name(self, tenant_name, domain_id): + """Get a tenant by name. + + :returns: tenant_ref + :raises: keystone.exception.ProjectNotFound + + """ + raise exception.NotImplemented() + + def get_project_users(self, tenant_id): + """Lists all users with a relationship to the specified project. + + :returns: a list of user_refs or an empty set. + :raises: keystone.exception.ProjectNotFound + + """ + raise exception.NotImplemented() + + def get_projects_for_user(self, user_id): + """Get the tenants associated with a given user. + + :returns: a list of tenant_id's. + :raises: keystone.exception.UserNotFound + + """ + raise exception.NotImplemented() + + 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.UserNotFound, + keystone.exception.ProjectNotFound, + keystone.exception.RoleNotFound + """ + raise exception.NotImplemented() + + 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.UserNotFound, + keystone.exception.ProjectNotFound, + keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() + + # assignment/grant crud + + 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). + + :raises: keystone.exception.UserNotFound, + keystone.exception.GroupNotFound, + keystone.exception.ProjectNotFound, + keystone.exception.DomainNotFound, + keystone.exception.ProjectNotFound, + keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() + + def list_grants(self, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + """Lists assignments/grants. + + :raises: keystone.exception.UserNotFound, + keystone.exception.GroupNotFound, + keystone.exception.ProjectNotFound, + keystone.exception.DomainNotFound, + keystone.exception.ProjectNotFound, + keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() + + def get_grant(self, role_id, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + """Lists assignments/grants. + + :raises: keystone.exception.UserNotFound, + keystone.exception.GroupNotFound, + keystone.exception.ProjectNotFound, + keystone.exception.DomainNotFound, + keystone.exception.ProjectNotFound, + keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() + + def delete_grant(self, role_id, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + """Lists assignments/grants. + + :raises: keystone.exception.UserNotFound, + keystone.exception.GroupNotFound, + keystone.exception.ProjectNotFound, + keystone.exception.DomainNotFound, + keystone.exception.ProjectNotFound, + keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() + + def list_role_assignments(self): + + raise exception.NotImplemented() + + # domain crud + def create_domain(self, domain_id, domain): + """Creates a new domain. + + :raises: keystone.exception.Conflict + + """ + raise exception.NotImplemented() + + def list_domains(self): + """List all domains in the system. + + :returns: a list of domain_refs or an empty list. + + """ + raise exception.NotImplemented() + + def get_domain(self, domain_id): + """Get a domain by ID. + + :returns: domain_ref + :raises: keystone.exception.DomainNotFound + + """ + raise exception.NotImplemented() + + def get_domain_by_name(self, domain_name): + """Get a domain by name. + + :returns: domain_ref + :raises: keystone.exception.DomainNotFound + + """ + raise exception.NotImplemented() + + def update_domain(self, domain_id, domain): + """Updates an existing domain. + + :raises: keystone.exception.DomainNotFound, + keystone.exception.Conflict + + """ + raise exception.NotImplemented() + + def delete_domain(self, domain_id): + """Deletes an existing domain. + + :raises: keystone.exception.DomainNotFound + + """ + raise exception.NotImplemented() + + # project crud + def create_project(self, project_id, project): + """Creates a new project. + + :raises: keystone.exception.Conflict + + """ + raise exception.NotImplemented() + + def list_projects(self, domain_id=None): + """List all projects in the system. + + :returns: a list of project_refs or an empty list. + + """ + raise exception.NotImplemented() + + def list_user_projects(self, user_id): + """List all projects associated with a given user. + + :returns: a list of project_refs or an empty list. + + """ + raise exception.NotImplemented() + + def get_project(self, project_id): + """Get a project by ID. + + :returns: project_ref + :raises: keystone.exception.ProjectNotFound + + """ + raise exception.NotImplemented() + + def update_project(self, project_id, project): + """Updates an existing project. + + :raises: keystone.exception.ProjectNotFound, + keystone.exception.Conflict + + """ + raise exception.NotImplemented() + + def delete_project(self, project_id): + """Deletes an existing project. + + :raises: keystone.exception.ProjectNotFound + + """ + raise exception.NotImplemented() + + """Interface description for an assignment driver.""" + # role crud + + def create_role(self, role_id, role): + """Creates a new role. + + :raises: keystone.exception.Conflict + + """ + raise exception.NotImplemented() + + def list_roles(self): + """List all roles in the system. + + :returns: a list of role_refs or an empty list. + + """ + raise exception.NotImplemented() + + def get_role(self, role_id): + """Get a role by ID. + + :returns: role_ref + :raises: keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() + + def update_role(self, role_id, role): + """Updates an existing role. + + :raises: keystone.exception.RoleNotFound, + keystone.exception.Conflict + + """ + raise exception.NotImplemented() + + def delete_role(self, role_id): + """Deletes an existing role. + + :raises: keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() + +#TODO(ayoung): determine what else these two functions raise + def delete_user(self, user_id): + """Deletes all assignments for a user. + + :raises: keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() + + def delete_group(self, group_id): + """Deletes all assignments for a group. + + :raises: keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() + + #domain management functions for backends that only allow a single domain. + #currently, this is only LDAP, but might be used by PAM or other backends + #as well. This is used by both identity and assignment drivers. + def _set_default_domain(self, ref): + """If the domain ID has not been set, set it to the default.""" + if isinstance(ref, dict): + if 'domain_id' not in ref: + ref = ref.copy() + ref['domain_id'] = CONF.identity.default_domain_id + return ref + elif isinstance(ref, list): + return [self._set_default_domain(x) for x in ref] + else: + raise ValueError(_('Expected dict or list: %s') % type(ref)) + + def _validate_default_domain(self, ref): + """Validate that either the default domain or nothing is specified. + + Also removes the domain from the ref so that LDAP doesn't have to + persist the attribute. + + """ + ref = ref.copy() + domain_id = ref.pop('domain_id', CONF.identity.default_domain_id) + self._validate_default_domain_id(domain_id) + return ref + + def _validate_default_domain_id(self, domain_id): + """Validate that the domain ID specified belongs to the default domain. + + """ + if domain_id != CONF.identity.default_domain_id: + raise exception.DomainNotFound(domain_id=domain_id) |