summaryrefslogtreecommitdiffstats
path: root/keystone/assignment
diff options
context:
space:
mode:
authorHenry Nash <henryn@linux.vnet.ibm.com>2013-07-05 06:04:25 +0100
committerHenry Nash <henryn@linux.vnet.ibm.com>2013-07-17 00:37:32 +0100
commit7f4891ddc3da7457df09c0cc8bbfe8a888063feb (patch)
tree89a26f688965566ec35eb35192d34a71ab513da4 /keystone/assignment
parent3a56c8a68d0f033266f98963261a6d724e506966 (diff)
downloadkeystone-7f4891ddc3da7457df09c0cc8bbfe8a888063feb.tar.gz
keystone-7f4891ddc3da7457df09c0cc8bbfe8a888063feb.tar.xz
keystone-7f4891ddc3da7457df09c0cc8bbfe8a888063feb.zip
Implement role assignment inheritance (OS-INHERIT extension)
This extension allows for project roles to be optionally inherited from the owning domain. The v3 grant APIs are extended to take an inherited_to_projects flag. The GET role_assignments API will also include these roles in its response, either showing them as inherited roles assigned to the domain or, if the 'effective' query parameter is set, will interpret the inheritance and reflect those role assignments on the projects. The inherited_to_projects flag is encoded in the role list in the metadata of the relevant entries in the grant tables. The 'roles' key in the metadata is now a list of dicts, as opposed to a simple list, where each dict is either {'id': role_id} for a regular role, or {'id': role_id, 'inherited_to': 'projects'} for an inherited role Remember that a previous patch had rationalized the way metadata is handled so that its structure is entirely hidden within the driver layer. The extension can be enabled/disabled via a config setting. Limitations: - The extension is not yet discoverable via url, this will be added as a separate patch when the v3/extensions work is complete. A separate issue has been discovered with the fact that the v2 calls of 'get_projects_for_user()' and 'list_user_projects()' should be rationalized and also honor both group (and inherited) role assignments. This is being raised as a separate bug. DocImpact Implements bp inherited-domain-roles Change-Id: I35b57ce0df668f12462e96b3467cef0239594e97
Diffstat (limited to 'keystone/assignment')
-rw-r--r--keystone/assignment/backends/kvs.py86
-rw-r--r--keystone/assignment/backends/ldap.py6
-rw-r--r--keystone/assignment/backends/sql.py162
-rw-r--r--keystone/assignment/core.py121
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.