diff options
Diffstat (limited to 'keystone/assignment')
| -rw-r--r-- | keystone/assignment/backends/kvs.py | 86 | ||||
| -rw-r--r-- | keystone/assignment/backends/ldap.py | 6 | ||||
| -rw-r--r-- | keystone/assignment/backends/sql.py | 162 | ||||
| -rw-r--r-- | keystone/assignment/core.py | 121 |
4 files changed, 283 insertions, 92 deletions
diff --git a/keystone/assignment/backends/kvs.py b/keystone/assignment/backends/kvs.py index 4ed3937b..4dfd908f 100644 --- a/keystone/assignment/backends/kvs.py +++ b/keystone/assignment/backends/kvs.py @@ -33,10 +33,16 @@ class Assignment(kvs.Base, assignment.Driver): except exception.NotFound: raise exception.ProjectNotFound(project_id=tenant_id) - def list_projects(self): - tenant_keys = filter(lambda x: x.startswith("tenant-"), - self.db.keys()) - return [self.db.get(key) for key in tenant_keys] + def list_projects(self, domain_id=None): + project_keys = filter(lambda x: x.startswith("tenant-"), + self.db.keys()) + project_refs = [self.db.get(key) for key in project_keys] + + if domain_id: + self.get_domain(domain_id) + project_refs = filter(lambda x: domain_id in x['domain_id'], + project_refs) + return project_refs def get_project_by_name(self, tenant_name, domain_id): try: @@ -105,13 +111,16 @@ class Assignment(kvs.Base, assignment.Driver): metadata_ref = self._get_metadata(user_id, tenant_id) except exception.MetadataNotFound: metadata_ref = {} - roles = set(metadata_ref.get('roles', [])) - if role_id in roles: + + try: + metadata_ref['roles'] = self._add_role_to_role_dicts( + role_id, False, metadata_ref.get('roles', []), + allow_existing=False) + except KeyError: msg = ('User %s already has role %s in tenant %s' % (user_id, role_id, tenant_id)) raise exception.Conflict(type='role grant', details=msg) - roles.add(role_id) - metadata_ref['roles'] = list(roles) + self._update_metadata(user_id, tenant_id, metadata_ref) def remove_role_from_user_and_project(self, user_id, tenant_id, role_id): @@ -119,23 +128,25 @@ class Assignment(kvs.Base, assignment.Driver): metadata_ref = self._get_metadata(user_id, tenant_id) except exception.MetadataNotFound: metadata_ref = {} - roles = set(metadata_ref.get('roles', [])) - if role_id not in roles: - msg = 'Cannot remove role that has not been granted, %s' % role_id - raise exception.RoleNotFound(message=msg) - roles.remove(role_id) - metadata_ref['roles'] = list(roles) + try: + metadata_ref['roles'] = self._remove_role_from_role_dicts( + role_id, False, metadata_ref.get('roles', [])) + except KeyError: + raise exception.RoleNotFound(message=_( + 'Cannot remove role that has not been granted, %s') % + role_id) + + if len(metadata_ref['roles']): + self._update_metadata(user_id, tenant_id, metadata_ref) + else: - if not len(roles): self.db.delete('metadata-%s-%s' % (tenant_id, user_id)) user_ref = self._get_user(user_id) tenants = set(user_ref.get('tenants', [])) tenants.remove(tenant_id) user_ref['tenants'] = list(tenants) self.identity_api.update_user(user_id, user_ref) - else: - self._update_metadata(user_id, tenant_id, metadata_ref) def list_role_assignments(self): """List the role assignments. @@ -144,7 +155,7 @@ class Assignment(kvs.Base, assignment.Driver): "metadata-{target}-{actor}", with the value being a role list - i.e. "metadata-MyProjectID-MyUserID" [role1, role2] + i.e. "metadata-MyProjectID-MyUserID" [{'id': role1}, {'id': role2}] ...so we enumerate the list and extract the targets, actors and roles. @@ -169,7 +180,8 @@ class Assignment(kvs.Base, assignment.Driver): template['group_id'] = meta_id2 entry = self.db.get(key) - for r in entry.get('roles', []): + for r in self._roles_from_role_dicts(entry.get('roles', {}), + False): role_assignment = template.copy() role_assignment['role_id'] = r assignment_list.append(role_assignment) @@ -324,7 +336,8 @@ class Assignment(kvs.Base, assignment.Driver): self.db.set('role_list', list(role_list)) def create_grant(self, role_id, user_id=None, group_id=None, - domain_id=None, project_id=None): + domain_id=None, project_id=None, + inherited_to_projects=False): self.get_role(role_id) if user_id: @@ -341,14 +354,16 @@ class Assignment(kvs.Base, assignment.Driver): domain_id, group_id) except exception.MetadataNotFound: metadata_ref = {} - roles = set(metadata_ref.get('roles', [])) - roles.add(role_id) - metadata_ref['roles'] = list(roles) + + metadata_ref['roles'] = self._add_role_to_role_dicts( + role_id, inherited_to_projects, metadata_ref.get('roles', [])) + self._update_metadata(user_id, project_id, metadata_ref, domain_id, group_id) def list_grants(self, user_id=None, group_id=None, - domain_id=None, project_id=None): + domain_id=None, project_id=None, + inherited_to_projects=False): if user_id: self.identity_api.get_user(user_id) if group_id: @@ -363,10 +378,14 @@ class Assignment(kvs.Base, assignment.Driver): domain_id, group_id) except exception.MetadataNotFound: metadata_ref = {} - return [self.get_role(x) for x in metadata_ref.get('roles', [])] + + return [self.get_role(x) for x in + self._roles_from_role_dicts(metadata_ref.get('roles', []), + inherited_to_projects)] def get_grant(self, role_id, user_id=None, group_id=None, - domain_id=None, project_id=None): + domain_id=None, project_id=None, + inherited_to_projects=False): self.get_role(role_id) if user_id: self.identity_api.get_user(user_id) @@ -382,13 +401,17 @@ class Assignment(kvs.Base, assignment.Driver): domain_id, group_id) except exception.MetadataNotFound: metadata_ref = {} - role_ids = set(metadata_ref.get('roles', [])) + + role_ids = set(self._roles_from_role_dicts( + metadata_ref.get('roles', []), inherited_to_projects)) + if role_id not in role_ids: raise exception.RoleNotFound(role_id=role_id) return self.get_role(role_id) def delete_grant(self, role_id, user_id=None, group_id=None, - domain_id=None, project_id=None): + domain_id=None, project_id=None, + inherited_to_projects=False): self.get_role(role_id) if user_id: self.identity_api.get_user(user_id) @@ -404,12 +427,13 @@ class Assignment(kvs.Base, assignment.Driver): domain_id, group_id) except exception.MetadataNotFound: metadata_ref = {} - roles = set(metadata_ref.get('roles', [])) + try: - roles.remove(role_id) + metadata_ref['roles'] = self._remove_role_from_role_dicts( + role_id, inherited_to_projects, metadata_ref.get('roles', [])) except KeyError: raise exception.RoleNotFound(role_id=role_id) - metadata_ref['roles'] = list(roles) + self._update_metadata(user_id, project_id, metadata_ref, domain_id, group_id) diff --git a/keystone/assignment/backends/ldap.py b/keystone/assignment/backends/ldap.py index 09539c9f..b1b3f99f 100644 --- a/keystone/assignment/backends/ldap.py +++ b/keystone/assignment/backends/ldap.py @@ -72,7 +72,9 @@ class Assignment(assignment.Driver): def get_project(self, tenant_id): return self._set_default_domain(self.project.get(tenant_id)) - def list_projects(self): + def list_projects(self, domain_id=None): + # We don't support multiple domains within this driver, so ignore + # any domain passed. return self._set_default_domain(self.project.get_all()) def get_project_by_name(self, tenant_name, domain_id): @@ -117,7 +119,7 @@ class Assignment(assignment.Driver): metadata_ref = _get_roles_for_just_user_and_project(user_id, tenant_id) if not metadata_ref: return {} - return {'roles': metadata_ref} + return {'roles': [self._role_to_dict(r, False) for r in metadata_ref]} def get_role(self, role_id): return self.role.get(role_id) diff --git a/keystone/assignment/backends/sql.py b/keystone/assignment/backends/sql.py index 237330ce..5ec435ff 100644 --- a/keystone/assignment/backends/sql.py +++ b/keystone/assignment/backends/sql.py @@ -96,7 +96,8 @@ class Assignment(sql.Base, assignment.Driver): raise exception.MetadataNotFound() def create_grant(self, role_id, user_id=None, group_id=None, - domain_id=None, project_id=None): + domain_id=None, project_id=None, + inherited_to_projects=False): if user_id: self.identity_api.get_user(user_id) if group_id: @@ -110,6 +111,10 @@ class Assignment(sql.Base, assignment.Driver): if project_id: self._get_project(session, project_id) + if project_id and inherited_to_projects: + msg = _('Inherited roles can only be assigned to domains') + raise exception.Conflict(type='role grant', details=msg) + try: metadata_ref = self._get_metadata(user_id, project_id, domain_id, group_id) @@ -117,9 +122,10 @@ class Assignment(sql.Base, assignment.Driver): except exception.MetadataNotFound: metadata_ref = {} is_new = True - roles = set(metadata_ref.get('roles', [])) - roles.add(role_id) - metadata_ref['roles'] = list(roles) + + metadata_ref['roles'] = self._add_role_to_role_dicts( + role_id, inherited_to_projects, metadata_ref.get('roles', [])) + if is_new: self._create_metadata(user_id, project_id, metadata_ref, domain_id, group_id) @@ -128,7 +134,8 @@ class Assignment(sql.Base, assignment.Driver): domain_id, group_id) def list_grants(self, user_id=None, group_id=None, - domain_id=None, project_id=None): + domain_id=None, project_id=None, + inherited_to_projects=False): if user_id: self.identity_api.get_user(user_id) if group_id: @@ -144,10 +151,14 @@ class Assignment(sql.Base, assignment.Driver): domain_id, group_id) except exception.MetadataNotFound: metadata_ref = {} - return [self.get_role(x) for x in metadata_ref.get('roles', [])] + + return [self.get_role(x) for x in + self._roles_from_role_dicts(metadata_ref.get('roles', []), + inherited_to_projects)] def get_grant(self, role_id, user_id=None, group_id=None, - domain_id=None, project_id=None): + domain_id=None, project_id=None, + inherited_to_projects=False): if user_id: self.identity_api.get_user(user_id) if group_id: @@ -166,13 +177,15 @@ class Assignment(sql.Base, assignment.Driver): domain_id, group_id) except exception.MetadataNotFound: metadata_ref = {} - role_ids = set(metadata_ref.get('roles', [])) + role_ids = set(self._roles_from_role_dicts( + metadata_ref.get('roles', []), inherited_to_projects)) if role_id not in role_ids: raise exception.RoleNotFound(role_id=role_id) return role_ref.to_dict() def delete_grant(self, role_id, user_id=None, group_id=None, - domain_id=None, project_id=None): + domain_id=None, project_id=None, + inherited_to_projects=False): if user_id: self.identity_api.get_user(user_id) if group_id: @@ -193,25 +206,43 @@ class Assignment(sql.Base, assignment.Driver): except exception.MetadataNotFound: metadata_ref = {} is_new = True - roles = set(metadata_ref.get('roles', [])) + try: - roles.remove(role_id) + metadata_ref['roles'] = self._remove_role_from_role_dicts( + role_id, inherited_to_projects, metadata_ref.get('roles', [])) except KeyError: raise exception.RoleNotFound(role_id=role_id) - metadata_ref['roles'] = list(roles) + if is_new: + # TODO(henry-nash) It seems odd that you would create a new + # entry in response to trying to delete a role that was not + # assigned. Although benign, this should probably be removed. self._create_metadata(user_id, project_id, metadata_ref, domain_id, group_id) else: self._update_metadata(user_id, project_id, metadata_ref, domain_id, group_id) - def list_projects(self): + def list_projects(self, domain_id=None): session = self.get_session() - tenant_refs = session.query(Project).all() - return [tenant_ref.to_dict() for tenant_ref in tenant_refs] + if domain_id: + self._get_domain(session, domain_id) + + query = session.query(Project) + if domain_id: + query = query.filter_by(domain_id=domain_id) + project_refs = query.all() + return [project_ref.to_dict() for project_ref in project_refs] def get_projects_for_user(self, user_id): + + # FIXME(henry-nash) The following should take into account + # both group and inherited roles. In fact, I don't see why this + # call can't be handled at the controller level like we do + # with 'get_roles_for_user_and_project()'. Further, this + # call seems essentially the same as 'list_user_projects()' + # later in this driver. Both should be removed. + self.identity_api.get_user(user_id) session = self.get_session() query = session.query(UserProjectGrant) @@ -230,13 +261,16 @@ class Assignment(sql.Base, assignment.Driver): except exception.MetadataNotFound: metadata_ref = {} is_new = True - roles = set(metadata_ref.get('roles', [])) - if role_id in roles: + + try: + metadata_ref['roles'] = self._add_role_to_role_dicts( + role_id, False, metadata_ref.get('roles', []), + allow_existing=False) + except KeyError: msg = ('User %s already has role %s in tenant %s' % (user_id, role_id, tenant_id)) raise exception.Conflict(type='role grant', details=msg) - roles.add(role_id) - metadata_ref['roles'] = list(roles) + if is_new: self._create_metadata(user_id, tenant_id, metadata_ref) else: @@ -245,14 +279,15 @@ class Assignment(sql.Base, assignment.Driver): def remove_role_from_user_and_project(self, user_id, tenant_id, role_id): try: metadata_ref = self._get_metadata(user_id, tenant_id) - roles = set(metadata_ref.get('roles', [])) - if role_id not in roles: + try: + metadata_ref['roles'] = self._remove_role_from_role_dicts( + role_id, False, metadata_ref.get('roles', [])) + except KeyError: raise exception.RoleNotFound(message=_( 'Cannot remove role that has not been granted, %s') % role_id) - roles.remove(role_id) - metadata_ref['roles'] = list(roles) - if len(roles): + + if len(metadata_ref['roles']): self._update_metadata(user_id, tenant_id, metadata_ref) else: session = self.get_session() @@ -277,28 +312,44 @@ class Assignment(sql.Base, assignment.Driver): assignment_list = [] refs = session.query(UserDomainGrant).all() for x in refs: - for r in x.data.get('roles', []): - assignment_list.append({'user_id': x.user_id, - 'domain_id': x.domain_id, - 'role_id': r}) + for r in self._roles_from_role_dicts( + x.data.get('roles', {}), False): + assignment_list.append({'user_id': x.user_id, + 'domain_id': x.domain_id, + 'role_id': r}) + for r in self._roles_from_role_dicts( + x.data.get('roles', {}), True): + assignment_list.append({'user_id': x.user_id, + 'domain_id': x.domain_id, + 'role_id': r, + 'inherited_to_projects': True}) refs = session.query(UserProjectGrant).all() for x in refs: - for r in x.data.get('roles', []): - assignment_list.append({'user_id': x.user_id, - 'project_id': x.project_id, - 'role_id': r}) + for r in self._roles_from_role_dicts( + x.data.get('roles', {}), False): + assignment_list.append({'user_id': x.user_id, + 'project_id': x.project_id, + 'role_id': r}) refs = session.query(GroupDomainGrant).all() for x in refs: - for r in x.data.get('roles', []): - assignment_list.append({'group_id': x.group_id, - 'domain_id': x.domain_id, - 'role_id': r}) + for r in self._roles_from_role_dicts( + x.data.get('roles', {}), False): + assignment_list.append({'group_id': x.group_id, + 'domain_id': x.domain_id, + 'role_id': r}) + for r in self._roles_from_role_dicts( + x.data.get('roles', {}), True): + assignment_list.append({'group_id': x.group_id, + 'domain_id': x.domain_id, + 'role_id': r, + 'inherited_to_projects': True}) refs = session.query(GroupProjectGrant).all() for x in refs: - for r in x.data.get('roles', []): - assignment_list.append({'group_id': x.group_id, - 'project_id': x.project_id, - 'role_id': r}) + for r in self._roles_from_role_dicts( + x.data.get('roles', {}), False): + assignment_list.append({'group_id': x.group_id, + 'project_id': x.project_id, + 'role_id': r}) return assignment_list # CRUD @@ -473,6 +524,14 @@ class Assignment(sql.Base, assignment.Driver): session.flush() def list_user_projects(self, user_id): + + # FIXME(henry-nash) The following should take into account + # both group and inherited roles. In fact, I don't see why this + # call can't be handled at the controller level like we do + # with 'get_roles_for_user_and_project()'. Further, this + # call seems essentially the same as 'get_projects_for_user()' + # earlier in this driver. Both should be removed. + session = self.get_session() user = self.identity_api.get_user(user_id) metadata_refs = session\ @@ -627,6 +686,29 @@ class Role(sql.ModelBase, sql.DictBase): class BaseGrant(sql.DictBase): + """Base Grant class. + + There are four grant tables in the current implementation, one for + each type of grant: + + - User for Project + - User for Domain + - Group for Project + - Group for Domain + + Each is a table with the two attributes above as a combined primary key, + with the data field holding all roles for that combination. The data + field is a list of dicts. For regular role assignments each dict in + the list of of the form: + + {'id': role_id} + + If the OS-INHERIT extension is enabled and the role on a domain is an + inherited role, the dict will be of the form: + + {'id': role_id, 'inherited_to': 'projects'} + + """ def to_dict(self): """Override parent to_dict() method with a simpler implementation. diff --git a/keystone/assignment/core.py b/keystone/assignment/core.py index 531da02e..b71e2a18 100644 --- a/keystone/assignment/core.py +++ b/keystone/assignment/core.py @@ -59,33 +59,72 @@ class Manager(manager.Manager): self.identity_api.assignment_api = self def get_roles_for_user_and_project(self, user_id, tenant_id): - def _get_group_project_roles(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=tenant_id) - role_list += metadata_ref.get('roles', []) + 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, tenant_id): - metadata_ref = {} + def _get_user_project_roles(user_id, project_ref): + role_list = [] try: metadata_ref = self._get_metadata(user_id=user_id, - tenant_id=tenant_id) + tenant_id=project_ref['id']) + role_list = self._roles_from_role_dicts( + metadata_ref.get('roles', {}), False) except exception.MetadataNotFound: pass - return metadata_ref.get('roles', []) + + 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) - self.get_project(tenant_id) - user_role_list = _get_user_project_roles(user_id, tenant_id) - group_role_list = _get_group_project_roles(user_id, tenant_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)) @@ -106,11 +145,12 @@ class Manager(manager.Manager): try: metadata_ref = self._get_metadata(group_id=x['id'], domain_id=domain_id) - role_list += metadata_ref.get('roles', []) + 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 + # domains. pass return role_list @@ -124,7 +164,8 @@ class Manager(manager.Manager): # Ignore NotImplemented since not all backends support # domains pass - return metadata_ref.get('roles', []) + return self._roles_from_role_dicts( + metadata_ref.get('roles', {}), False) self.identity_api.get_user(user_id) self.get_domain(domain_id) @@ -160,6 +201,40 @@ class Manager(manager.Manager): 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. @@ -209,9 +284,14 @@ class Driver(object): # assignment/grant crud def create_grant(self, role_id, user_id=None, group_id=None, - domain_id=None, project_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, @@ -223,7 +303,8 @@ class Driver(object): raise exception.NotImplemented() def list_grants(self, user_id=None, group_id=None, - domain_id=None, project_id=None): + domain_id=None, project_id=None, + inherited_to_projects=False): """Lists assignments/grants. :raises: keystone.exception.UserNotFound, @@ -237,7 +318,8 @@ class Driver(object): raise exception.NotImplemented() def get_grant(self, role_id, user_id=None, group_id=None, - domain_id=None, project_id=None): + domain_id=None, project_id=None, + inherited_to_projects=False): """Lists assignments/grants. :raises: keystone.exception.UserNotFound, @@ -251,7 +333,8 @@ class Driver(object): raise exception.NotImplemented() def delete_grant(self, role_id, user_id=None, group_id=None, - domain_id=None, project_id=None): + domain_id=None, project_id=None, + inherited_to_projects=False): """Lists assignments/grants. :raises: keystone.exception.UserNotFound, @@ -329,7 +412,7 @@ class Driver(object): """ raise exception.NotImplemented() - def list_projects(self): + def list_projects(self, domain_id=None): """List all projects in the system. :returns: a list of project_refs or an empty list. |
