aboutsummaryrefslogtreecommitdiffstats
path: root/keystone-moon/keystone/assignment
diff options
context:
space:
mode:
Diffstat (limited to 'keystone-moon/keystone/assignment')
-rw-r--r--keystone-moon/keystone/assignment/backends/ldap.py66
-rw-r--r--keystone-moon/keystone/assignment/backends/sql.py108
-rw-r--r--keystone-moon/keystone/assignment/controllers.py385
-rw-r--r--keystone-moon/keystone/assignment/core.py660
4 files changed, 714 insertions, 505 deletions
diff --git a/keystone-moon/keystone/assignment/backends/ldap.py b/keystone-moon/keystone/assignment/backends/ldap.py
index f93e989f..4ca66c4d 100644
--- a/keystone-moon/keystone/assignment/backends/ldap.py
+++ b/keystone-moon/keystone/assignment/backends/ldap.py
@@ -13,10 +13,10 @@
# under the License.
from __future__ import absolute_import
-import ldap as ldap
import ldap.filter
from oslo_config import cfg
from oslo_log import log
+from oslo_log import versionutils
from keystone import assignment
from keystone.assignment.role_backends import ldap as ldap_role
@@ -25,7 +25,6 @@ from keystone.common import models
from keystone import exception
from keystone.i18n import _
from keystone.identity.backends import ldap as ldap_identity
-from keystone.openstack.common import versionutils
CONF = cfg.CONF
@@ -36,7 +35,7 @@ class Assignment(assignment.Driver):
@versionutils.deprecated(
versionutils.deprecated.KILO,
remove_in=+2,
- what='keystone.assignment.backends.ldap.Assignment')
+ what='ldap')
def __init__(self):
super(Assignment, self).__init__()
self.LDAP_URL = CONF.ldap.url
@@ -54,10 +53,10 @@ class Assignment(assignment.Driver):
self.role = RoleApi(CONF, self.user)
def default_role_driver(self):
- return 'keystone.assignment.role_backends.ldap.Role'
+ return 'ldap'
def default_resource_driver(self):
- return 'keystone.resource.backends.ldap.Resource'
+ return 'ldap'
def list_role_ids_for_groups_on_project(
self, groups, project_id, project_domain_id, project_parents):
@@ -181,7 +180,7 @@ class Assignment(assignment.Driver):
self.group._id_to_dn(group_id), role_id)
# Bulk actions on User From identity
- def delete_user(self, user_id):
+ def delete_user_assignments(self, user_id):
user_dn = self.user._id_to_dn(user_id)
for ref in self.role.list_global_roles_for_user(user_dn):
self.role.delete_user(ref.role_dn, ref.user_dn,
@@ -191,7 +190,7 @@ class Assignment(assignment.Driver):
self.role.delete_user(ref.role_dn, ref.user_dn,
self.role._dn_to_id(ref.role_dn))
- def delete_group(self, group_id):
+ def delete_group_assignments(self, group_id):
"""Called when the group was deleted.
Any role assignments for the group should be cleaned up.
@@ -277,20 +276,39 @@ class Assignment(assignment.Driver):
return self._roles_from_role_dicts(metadata_ref.get('roles', []),
inherited_to_projects)
- def list_role_assignments(self):
+ def list_role_assignments(self, role_id=None,
+ user_id=None, group_ids=None,
+ domain_id=None, project_ids=None,
+ inherited_to_projects=None):
role_assignments = []
- for a in self.role.list_role_assignments(self.project.tree_dn):
- if isinstance(a, UserRoleAssociation):
- assignment = {
- 'role_id': self.role._dn_to_id(a.role_dn),
- 'user_id': self.user._dn_to_id(a.user_dn),
- 'project_id': self.project._dn_to_id(a.project_dn)}
- else:
- assignment = {
- 'role_id': self.role._dn_to_id(a.role_dn),
- 'group_id': self.group._dn_to_id(a.group_dn),
- 'project_id': self.project._dn_to_id(a.project_dn)}
- role_assignments.append(assignment)
+
+ # Since the LDAP backend does not support assignments to domains, if
+ # the request is to filter by domain, then the answer is guaranteed
+ # to be an empty list.
+ if not domain_id:
+ for a in self.role.list_role_assignments(self.project.tree_dn):
+ if isinstance(a, UserRoleAssociation):
+ assignment = {
+ 'role_id': self.role._dn_to_id(a.role_dn),
+ 'user_id': self.user._dn_to_id(a.user_dn),
+ 'project_id': self.project._dn_to_id(a.project_dn)}
+ else:
+ assignment = {
+ 'role_id': self.role._dn_to_id(a.role_dn),
+ 'group_id': self.group._dn_to_id(a.group_dn),
+ 'project_id': self.project._dn_to_id(a.project_dn)}
+
+ if role_id and assignment['role_id'] != role_id:
+ continue
+ if user_id and assignment.get('user_id') != user_id:
+ continue
+ if group_ids and assignment.get('group_id') not in group_ids:
+ continue
+ if project_ids and assignment['project_id'] not in project_ids:
+ continue
+
+ role_assignments.append(assignment)
+
return role_assignments
def delete_project_assignments(self, project_id):
@@ -313,9 +331,7 @@ class ProjectApi(common_ldap.ProjectLdapStructureMixin,
or self.DEFAULT_MEMBER_ATTRIBUTE)
def get_user_projects(self, user_dn, associations):
- """Returns list of tenants a user has access to
- """
-
+ """Returns the list of tenants to which a user has access."""
project_ids = set()
for assoc in associations:
project_ids.add(self._dn_to_id(assoc.project_dn))
@@ -497,9 +513,7 @@ class RoleApi(ldap_role.RoleLdapStructureMixin, common_ldap.BaseLdap):
self.id_attr: role_id})
def list_role_assignments(self, project_tree_dn):
- """Returns a list of all the role assignments linked to project_tree_dn
- attribute.
- """
+ """List the role assignments linked to project_tree_dn attribute."""
try:
roles = self._ldap_get_list(project_tree_dn, ldap.SCOPE_SUBTREE,
attrlist=[self.member_attribute])
diff --git a/keystone-moon/keystone/assignment/backends/sql.py b/keystone-moon/keystone/assignment/backends/sql.py
index 2de6ca60..89ff64b5 100644
--- a/keystone-moon/keystone/assignment/backends/sql.py
+++ b/keystone-moon/keystone/assignment/backends/sql.py
@@ -14,7 +14,6 @@
from oslo_config import cfg
from oslo_log import log
-import six
import sqlalchemy
from sqlalchemy.sql.expression import false
@@ -53,10 +52,10 @@ class AssignmentType(object):
class Assignment(keystone_assignment.Driver):
def default_role_driver(self):
- return "keystone.assignment.role_backends.sql.Role"
+ return 'sql'
def default_resource_driver(self):
- return 'keystone.resource.backends.sql.Resource'
+ return 'sql'
def list_user_ids_for_project(self, tenant_id):
with sql.transaction() as session:
@@ -336,7 +335,62 @@ class Assignment(keystone_assignment.Driver):
'Cannot remove role that has not been granted, %s') %
role_id)
- def list_role_assignments(self):
+ def _get_user_assignment_types(self):
+ return [AssignmentType.USER_PROJECT, AssignmentType.USER_DOMAIN]
+
+ def _get_group_assignment_types(self):
+ return [AssignmentType.GROUP_PROJECT, AssignmentType.GROUP_DOMAIN]
+
+ def _get_project_assignment_types(self):
+ return [AssignmentType.USER_PROJECT, AssignmentType.GROUP_PROJECT]
+
+ def _get_domain_assignment_types(self):
+ return [AssignmentType.USER_DOMAIN, AssignmentType.GROUP_DOMAIN]
+
+ def _get_assignment_types(self, user, group, project, domain):
+ """Returns a list of role assignment types based on provided entities
+
+ If one of user or group (the "actor") as well as one of project or
+ domain (the "target") are provided, the list will contain the role
+ assignment type for that specific pair of actor and target.
+
+ If only an actor or target is provided, the list will contain the
+ role assignment types that satisfy the specified entity.
+
+ For example, if user and project are provided, the return will be:
+
+ [AssignmentType.USER_PROJECT]
+
+ However, if only user was provided, the return would be:
+
+ [AssignmentType.USER_PROJECT, AssignmentType.USER_DOMAIN]
+
+ It is not expected that user and group (or project and domain) are
+ specified - but if they are, the most fine-grained value will be
+ chosen (i.e. user over group, project over domain).
+
+ """
+ actor_types = []
+ if user:
+ actor_types = self._get_user_assignment_types()
+ elif group:
+ actor_types = self._get_group_assignment_types()
+
+ target_types = []
+ if project:
+ target_types = self._get_project_assignment_types()
+ elif domain:
+ target_types = self._get_domain_assignment_types()
+
+ if actor_types and target_types:
+ return list(set(actor_types).intersection(target_types))
+
+ return actor_types or target_types
+
+ def list_role_assignments(self, role_id=None,
+ user_id=None, group_ids=None,
+ domain_id=None, project_ids=None,
+ inherited_to_projects=None):
def denormalize_role(ref):
assignment = {}
@@ -362,8 +416,35 @@ class Assignment(keystone_assignment.Driver):
return assignment
with sql.transaction() as session:
- refs = session.query(RoleAssignment).all()
- return [denormalize_role(ref) for ref in refs]
+ assignment_types = self._get_assignment_types(
+ user_id, group_ids, project_ids, domain_id)
+
+ targets = None
+ if project_ids:
+ targets = project_ids
+ elif domain_id:
+ targets = [domain_id]
+
+ actors = None
+ if group_ids:
+ actors = group_ids
+ elif user_id:
+ actors = [user_id]
+
+ query = session.query(RoleAssignment)
+
+ if role_id:
+ query = query.filter_by(role_id=role_id)
+ if actors:
+ query = query.filter(RoleAssignment.actor_id.in_(actors))
+ if targets:
+ query = query.filter(RoleAssignment.target_id.in_(targets))
+ if assignment_types:
+ query = query.filter(RoleAssignment.type.in_(assignment_types))
+ if inherited_to_projects is not None:
+ query = query.filter_by(inherited=inherited_to_projects)
+
+ return [denormalize_role(ref) for ref in query.all()]
def delete_project_assignments(self, project_id):
with sql.transaction() as session:
@@ -377,13 +458,13 @@ class Assignment(keystone_assignment.Driver):
q = q.filter_by(role_id=role_id)
q.delete(False)
- def delete_user(self, user_id):
+ def delete_user_assignments(self, user_id):
with sql.transaction() as session:
q = session.query(RoleAssignment)
q = q.filter_by(actor_id=user_id)
q.delete(False)
- def delete_group(self, group_id):
+ def delete_group_assignments(self, group_id):
with sql.transaction() as session:
q = session.query(RoleAssignment)
q = q.filter_by(actor_id=group_id)
@@ -399,12 +480,15 @@ class RoleAssignment(sql.ModelBase, sql.DictBase):
AssignmentType.USER_DOMAIN, AssignmentType.GROUP_DOMAIN,
name='type'),
nullable=False)
- actor_id = sql.Column(sql.String(64), nullable=False, index=True)
+ actor_id = sql.Column(sql.String(64), nullable=False)
target_id = sql.Column(sql.String(64), nullable=False)
role_id = sql.Column(sql.String(64), nullable=False)
inherited = sql.Column(sql.Boolean, default=False, nullable=False)
- __table_args__ = (sql.PrimaryKeyConstraint('type', 'actor_id', 'target_id',
- 'role_id'), {})
+ __table_args__ = (
+ sql.PrimaryKeyConstraint('type', 'actor_id', 'target_id', 'role_id',
+ 'inherited'),
+ sql.Index('ix_actor_id', 'actor_id'),
+ )
def to_dict(self):
"""Override parent to_dict() method with a simpler implementation.
@@ -412,4 +496,4 @@ class RoleAssignment(sql.ModelBase, sql.DictBase):
RoleAssignment doesn't have non-indexed 'extra' attributes, so the
parent implementation is not applicable.
"""
- return dict(six.iteritems(self))
+ return dict(self.items())
diff --git a/keystone-moon/keystone/assignment/controllers.py b/keystone-moon/keystone/assignment/controllers.py
index ff27fd36..d33dce70 100644
--- a/keystone-moon/keystone/assignment/controllers.py
+++ b/keystone-moon/keystone/assignment/controllers.py
@@ -15,7 +15,6 @@
"""Workflow Logic the Assignment service."""
-import copy
import functools
import uuid
@@ -26,10 +25,10 @@ from six.moves import urllib
from keystone.assignment import schema
from keystone.common import controller
from keystone.common import dependency
+from keystone.common import utils
from keystone.common import validation
from keystone import exception
-from keystone.i18n import _, _LW
-from keystone.models import token_model
+from keystone.i18n import _
from keystone import notifications
@@ -51,18 +50,11 @@ class TenantAssignment(controller.V2Controller):
Doesn't care about token scopedness.
"""
- try:
- token_data = self.token_provider_api.validate_token(
- context['token_id'])
- token_ref = token_model.KeystoneToken(token_id=context['token_id'],
- token_data=token_data)
- except exception.NotFound as e:
- LOG.warning(_LW('Authentication failed: %s'), e)
- raise exception.Unauthorized(e)
+ token_ref = utils.get_token_ref(context)
tenant_refs = (
self.assignment_api.list_projects_for_user(token_ref.user_id))
- tenant_refs = [self.filter_domain_id(ref) for ref in tenant_refs
+ tenant_refs = [self.v3_to_v2_project(ref) for ref in tenant_refs
if ref['domain_id'] == CONF.identity.default_domain_id]
params = {
'limit': context['query_string'].get('limit'),
@@ -107,7 +99,14 @@ class Role(controller.V2Controller):
msg = _('Name field is required and cannot be empty')
raise exception.ValidationError(message=msg)
- role_id = uuid.uuid4().hex
+ if role['name'] == CONF.member_role_name:
+ # Use the configured member role ID when creating the configured
+ # member role name. This avoids the potential of creating a
+ # "member" role with an unexpected ID.
+ role_id = CONF.member_role_id
+ else:
+ role_id = uuid.uuid4().hex
+
role['id'] = role_id
role_ref = self.role_api.create_role(role_id, role)
return {'role': role_ref}
@@ -152,8 +151,8 @@ class RoleAssignmentV2(controller.V2Controller):
"""
self.assert_admin(context)
if tenant_id is None:
- raise exception.NotImplemented(message='User roles not supported: '
- 'tenant_id required')
+ raise exception.NotImplemented(
+ message=_('User roles not supported: tenant_id required'))
self.assignment_api.add_role_to_user_and_project(
user_id, tenant_id, role_id)
@@ -171,8 +170,8 @@ class RoleAssignmentV2(controller.V2Controller):
"""
self.assert_admin(context)
if tenant_id is None:
- raise exception.NotImplemented(message='User roles not supported: '
- 'tenant_id required')
+ raise exception.NotImplemented(
+ message=_('User roles not supported: tenant_id required'))
# This still has the weird legacy semantics that adding a role to
# a user also adds them to a tenant, so we must follow up on that
@@ -282,7 +281,16 @@ class RoleV3(controller.V3Controller):
@controller.protected()
@validation.validated(schema.role_create, 'role')
def create_role(self, context, role):
- ref = self._assign_unique_id(self._normalize_dict(role))
+ if role['name'] == CONF.member_role_name:
+ # Use the configured member role ID when creating the configured
+ # member role name. This avoids the potential of creating a
+ # "member" role with an unexpected ID.
+ role['id'] = CONF.member_role_id
+ else:
+ role = self._assign_unique_id(role)
+
+ ref = self._normalize_dict(role)
+
initiator = notifications._get_request_audit_info(context)
ref = self.role_api.create_role(ref['id'], ref, initiator)
return RoleV3.wrap_member(context, ref)
@@ -452,16 +460,25 @@ class RoleAssignmentV3(controller.V3Controller):
actor (e.g. user or group), target (e.g. domain or project) and role.
If it is an inherited role, then this is also indicated. Examples:
+ For a non-inherited expanded assignment from group membership:
{'user_id': user_id,
- 'project_id': domain_id,
- 'role_id': role_id}
+ 'project_id': project_id,
+ 'role_id': role_id,
+ 'indirect': {'group_id': group_id}}
- or, for an inherited role:
+ or, for a project inherited role:
{'user_id': user_id,
- 'domain_id': domain_id,
+ 'project_id': project_id,
'role_id': role_id,
- 'inherited_to_projects': true}
+ 'indirect': {'project_id': parent_id}}
+
+ It is possible to deduce if a role assignment came from group
+ membership if it has both 'user_id' in the main body of the dict and
+ 'group_id' in the 'indirect' subdict, as well as it is possible to
+ deduce if it has come from inheritance if it contains both a
+ 'project_id' in the main body of the dict and 'parent_id' in the
+ 'indirect' subdict.
This function maps this into the format to be returned via the API,
e.g. for the second example above:
@@ -471,262 +488,71 @@ class RoleAssignmentV3(controller.V3Controller):
{'id': user_id}
},
'scope': {
- 'domain': {
- {'id': domain_id}
+ 'project': {
+ {'id': project_id}
},
- 'OS-INHERIT:inherited_to': 'projects
+ 'OS-INHERIT:inherited_to': 'projects'
},
'role': {
{'id': role_id}
},
'links': {
- 'assignment': '/domains/domain_id/users/user_id/roles/'
- 'role_id/inherited_to_projects'
+ 'assignment': '/OS-INHERIT/projects/parent_id/users/user_id/'
+ 'roles/role_id/inherited_to_projects'
}
}
"""
- formatted_entity = {}
- suffix = ""
- if 'user_id' in entity:
- formatted_entity['user'] = {'id': entity['user_id']}
- actor_link = 'users/%s' % entity['user_id']
- if 'group_id' in entity:
- formatted_entity['group'] = {'id': entity['group_id']}
- actor_link = 'groups/%s' % entity['group_id']
- if 'role_id' in entity:
- formatted_entity['role'] = {'id': entity['role_id']}
+ formatted_entity = {'links': {}}
+ inherited_assignment = entity.get('inherited_to_projects')
+
if 'project_id' in entity:
formatted_entity['scope'] = (
{'project': {'id': entity['project_id']}})
- if 'inherited_to_projects' in entity:
- formatted_entity['scope']['OS-INHERIT:inherited_to'] = (
- 'projects')
- target_link = '/OS-INHERIT/projects/%s' % entity['project_id']
- suffix = '/inherited_to_projects'
- else:
- target_link = '/projects/%s' % entity['project_id']
- if 'domain_id' in entity:
- formatted_entity['scope'] = (
- {'domain': {'id': entity['domain_id']}})
- if 'inherited_to_projects' in entity:
- formatted_entity['scope']['OS-INHERIT:inherited_to'] = (
- 'projects')
- target_link = '/OS-INHERIT/domains/%s' % entity['domain_id']
- suffix = '/inherited_to_projects'
- else:
- target_link = '/domains/%s' % entity['domain_id']
- formatted_entity.setdefault('links', {})
-
- path = '%(target)s/%(actor)s/roles/%(role)s%(suffix)s' % {
- 'target': target_link,
- 'actor': actor_link,
- 'role': entity['role_id'],
- 'suffix': suffix}
- formatted_entity['links']['assignment'] = self.base_url(context, path)
-
- return formatted_entity
- def _expand_indirect_assignments(self, context, refs):
- """Processes entity list into all-direct assignments.
-
- For any group role assignments in the list, create a role assignment
- entity for each member of that group, and then remove the group
- assignment entity itself from the list.
+ if 'domain_id' in entity.get('indirect', {}):
+ inherited_assignment = True
+ formatted_link = ('/domains/%s' %
+ entity['indirect']['domain_id'])
+ elif 'project_id' in entity.get('indirect', {}):
+ inherited_assignment = True
+ formatted_link = ('/projects/%s' %
+ entity['indirect']['project_id'])
+ else:
+ formatted_link = '/projects/%s' % entity['project_id']
+ elif 'domain_id' in entity:
+ formatted_entity['scope'] = {'domain': {'id': entity['domain_id']}}
+ formatted_link = '/domains/%s' % entity['domain_id']
- If the OS-INHERIT extension is enabled, then honor any inherited
- roles on the domain by creating the equivalent on all projects
- owned by the domain.
+ if 'user_id' in entity:
+ formatted_entity['user'] = {'id': entity['user_id']}
- For any new entity created by virtue of group membership, add in an
- additional link to that membership.
+ if 'group_id' in entity.get('indirect', {}):
+ membership_url = (
+ self.base_url(context, '/groups/%s/users/%s' % (
+ entity['indirect']['group_id'], entity['user_id'])))
+ formatted_entity['links']['membership'] = membership_url
+ formatted_link += '/groups/%s' % entity['indirect']['group_id']
+ else:
+ formatted_link += '/users/%s' % entity['user_id']
+ elif 'group_id' in entity:
+ formatted_entity['group'] = {'id': entity['group_id']}
+ formatted_link += '/groups/%s' % entity['group_id']
- """
- def _get_group_members(ref):
- """Get a list of group members.
+ formatted_entity['role'] = {'id': entity['role_id']}
+ formatted_link += '/roles/%s' % entity['role_id']
- Get the list of group members. If this fails with
- GroupNotFound, then log this as a warning, but allow
- overall processing to continue.
+ if inherited_assignment:
+ formatted_entity['scope']['OS-INHERIT:inherited_to'] = (
+ 'projects')
+ formatted_link = ('/OS-INHERIT%s/inherited_to_projects' %
+ formatted_link)
- """
- try:
- members = self.identity_api.list_users_in_group(
- ref['group']['id'])
- except exception.GroupNotFound:
- members = []
- # The group is missing, which should not happen since
- # group deletion should remove any related assignments, so
- # log a warning
- target = 'Unknown'
- # Should always be a domain or project, but since to get
- # here things have gone astray, let's be cautious.
- if 'scope' in ref:
- if 'domain' in ref['scope']:
- dom_id = ref['scope']['domain'].get('id', 'Unknown')
- target = 'Domain: %s' % dom_id
- elif 'project' in ref['scope']:
- proj_id = ref['scope']['project'].get('id', 'Unknown')
- target = 'Project: %s' % proj_id
- role_id = 'Unknown'
- if 'role' in ref and 'id' in ref['role']:
- role_id = ref['role']['id']
- LOG.warning(
- _LW('Group %(group)s not found for role-assignment - '
- '%(target)s with Role: %(role)s'), {
- 'group': ref['group']['id'], 'target': target,
- 'role': role_id})
- return members
-
- def _build_user_assignment_equivalent_of_group(
- user, group_id, template):
- """Create a user assignment equivalent to the group one.
-
- The template has had the 'group' entity removed, so
- substitute a 'user' one. The 'assignment' link stays as it is,
- referring to the group assignment that led to this role.
- A 'membership' link is added that refers to this particular
- user's membership of this group.
-
- """
- user_entry = copy.deepcopy(template)
- user_entry['user'] = {'id': user['id']}
- user_entry['links']['membership'] = (
- self.base_url(context, '/groups/%s/users/%s' %
- (group_id, user['id'])))
- return user_entry
-
- def _build_project_equivalent_of_user_target_role(
- project_id, target_id, target_type, template):
- """Create a user project assignment equivalent to the domain one.
-
- The template has had the 'domain' entity removed, so
- substitute a 'project' one, modifying the 'assignment' link
- to match.
-
- """
- project_entry = copy.deepcopy(template)
- project_entry['scope']['project'] = {'id': project_id}
- project_entry['links']['assignment'] = (
- self.base_url(
- context,
- '/OS-INHERIT/%s/%s/users/%s/roles/%s'
- '/inherited_to_projects' % (
- target_type, target_id, project_entry['user']['id'],
- project_entry['role']['id'])))
- return project_entry
-
- def _build_project_equivalent_of_group_target_role(
- user_id, group_id, project_id,
- target_id, target_type, template):
- """Create a user project equivalent to the domain group one.
-
- The template has had the 'domain' and 'group' entities removed, so
- substitute a 'user-project' one, modifying the 'assignment' link
- to match.
-
- """
- project_entry = copy.deepcopy(template)
- project_entry['user'] = {'id': user_id}
- project_entry['scope']['project'] = {'id': project_id}
- project_entry['links']['assignment'] = (
- self.base_url(context,
- '/OS-INHERIT/%s/%s/groups/%s/roles/%s'
- '/inherited_to_projects' % (
- target_type, target_id, group_id,
- project_entry['role']['id'])))
- project_entry['links']['membership'] = (
- self.base_url(context, '/groups/%s/users/%s' %
- (group_id, user_id)))
- return project_entry
-
- # Scan the list of entities for any assignments that need to be
- # expanded.
- #
- # If the OS-INERIT extension is enabled, the refs lists may
- # contain roles to be inherited from domain to project, so expand
- # these as well into project equivalents
- #
- # For any regular group entries, expand these into user entries based
- # on membership of that group.
- #
- # Due to the potentially large expansions, rather than modify the
- # list we are enumerating, we build a new one as we go.
- #
-
- new_refs = []
- for r in refs:
- if 'OS-INHERIT:inherited_to' in r['scope']:
- if 'domain' in r['scope']:
- # It's an inherited domain role - so get the list of
- # projects owned by this domain.
- project_ids = (
- [x['id'] for x in
- self.resource_api.list_projects_in_domain(
- r['scope']['domain']['id'])])
- base_entry = copy.deepcopy(r)
- target_type = 'domains'
- target_id = base_entry['scope']['domain']['id']
- base_entry['scope'].pop('domain')
- else:
- # It's an inherited project role - so get the list of
- # projects in this project subtree.
- project_id = r['scope']['project']['id']
- project_ids = (
- [x['id'] for x in
- self.resource_api.list_projects_in_subtree(
- project_id)])
- base_entry = copy.deepcopy(r)
- target_type = 'projects'
- target_id = base_entry['scope']['project']['id']
- base_entry['scope'].pop('project')
-
- # For each project, create an equivalent role assignment
- for p in project_ids:
- # If it's a group assignment, then create equivalent user
- # roles based on membership of the group
- if 'group' in base_entry:
- members = _get_group_members(base_entry)
- sub_entry = copy.deepcopy(base_entry)
- group_id = sub_entry['group']['id']
- sub_entry.pop('group')
- for m in members:
- new_entry = (
- _build_project_equivalent_of_group_target_role(
- m['id'], group_id, p,
- target_id, target_type, sub_entry))
- new_refs.append(new_entry)
- else:
- new_entry = (
- _build_project_equivalent_of_user_target_role(
- p, target_id, target_type, base_entry))
- new_refs.append(new_entry)
- elif 'group' in r:
- # It's a non-inherited group role assignment, so get the list
- # of members.
- members = _get_group_members(r)
-
- # Now replace that group role assignment entry with an
- # equivalent user role assignment for each of the group members
- base_entry = copy.deepcopy(r)
- group_id = base_entry['group']['id']
- base_entry.pop('group')
- for m in members:
- user_entry = _build_user_assignment_equivalent_of_group(
- m, group_id, base_entry)
- new_refs.append(user_entry)
- else:
- new_refs.append(r)
+ formatted_entity['links']['assignment'] = self.base_url(context,
+ formatted_link)
- return new_refs
-
- def _filter_inherited(self, entry):
- if ('inherited_to_projects' in entry and
- not CONF.os_inherit.enabled):
- return False
- else:
- return True
+ return formatted_entity
def _assert_effective_filters(self, inherited, group, domain):
"""Assert that useless filter combinations are avoided.
@@ -762,13 +588,28 @@ class RoleAssignmentV3(controller.V3Controller):
'scope.domain.id', 'scope.project.id',
'scope.OS-INHERIT:inherited_to', 'user.id')
def list_role_assignments(self, context, filters):
+ """List role assignments to user and groups on domains and projects.
+
+ Return a list of all existing role assignments in the system, filtered
+ by assignments attributes, if provided.
- # TODO(henry-nash): This implementation uses the standard filtering
- # in the V3.wrap_collection. Given the large number of individual
- # assignments, this is pretty inefficient. An alternative would be
- # to pass the filters into the driver call, so that the list size is
- # kept a minimum.
+ If effective option is used and OS-INHERIT extension is enabled, the
+ following functions will be applied:
+ 1) For any group role assignment on a target, replace it by a set of
+ role assignments containing one for each user of that group on that
+ target;
+ 2) For any inherited role assignment for an actor on a target, replace
+ it by a set of role assignments for that actor on every project under
+ that target.
+ It means that, if effective mode is used, no group or domain inherited
+ assignments will be present in the resultant list. Thus, combining
+ effective with them is invalid.
+
+ As a role assignment contains only one actor and one target, providing
+ both user and group ids or domain and project ids is invalid as well.
+
+ """
params = context['query_string']
effective = 'effective' in params and (
self.query_filter_is_true(params['effective']))
@@ -791,17 +632,17 @@ class RoleAssignmentV3(controller.V3Controller):
domain=params.get(
'scope.domain.id'))
- hints = self.build_driver_hints(context, filters)
- refs = self.assignment_api.list_role_assignments()
- formatted_refs = (
- [self._format_entity(context, x) for x in refs
- if self._filter_inherited(x)])
+ refs = self.assignment_api.list_role_assignments(
+ role_id=params.get('role.id'),
+ user_id=params.get('user.id'),
+ group_id=params.get('group.id'),
+ domain_id=params.get('scope.domain.id'),
+ project_id=params.get('scope.project.id'),
+ inherited=inherited, effective=effective)
- if effective:
- formatted_refs = self._expand_indirect_assignments(context,
- formatted_refs)
+ formatted_refs = [self._format_entity(context, ref) for ref in refs]
- return self.wrap_collection(context, formatted_refs, hints=hints)
+ return self.wrap_collection(context, formatted_refs)
@controller.protected()
def get_role_assignment(self, context):
diff --git a/keystone-moon/keystone/assignment/core.py b/keystone-moon/keystone/assignment/core.py
index 0f9c03e9..a001e6b1 100644
--- a/keystone-moon/keystone/assignment/core.py
+++ b/keystone-moon/keystone/assignment/core.py
@@ -12,9 +12,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""Main entry point into the assignment service."""
+"""Main entry point into the Assignment service."""
import abc
+import copy
from oslo_config import cfg
from oslo_log import log
@@ -28,7 +29,6 @@ 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
@@ -36,40 +36,6 @@ 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')
@@ -80,6 +46,9 @@ class Manager(manager.Manager):
dynamically calls the backend.
"""
+
+ driver_namespace = 'keystone.assignment'
+
_PROJECT = 'project'
_ROLE_REMOVED_FROM_USER = 'role_removed_from_user'
_INVALIDATION_USER_PROJECT_TOKENS = 'invalidate_user_project_tokens'
@@ -129,7 +98,7 @@ class Manager(manager.Manager):
"""
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(
+ return self.list_role_ids_for_groups_on_project(
group_ids,
project_ref['id'],
project_ref['domain_id'],
@@ -155,7 +124,8 @@ class Manager(manager.Manager):
except (exception.MetadataNotFound, exception.NotImplemented):
pass
# As well inherited roles from parent projects
- for p in self.list_project_parents(project_ref['id']):
+ for p in self.resource_api.list_project_parents(
+ project_ref['id']):
p_roles = self.list_grants(
user_id=user_id, project_id=p['id'],
inherited_to_projects=True)
@@ -207,7 +177,7 @@ class Manager(manager.Manager):
return self._roles_from_role_dicts(
metadata_ref.get('roles', {}), False)
- self.get_domain(domain_id)
+ self.resource_api.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
@@ -218,11 +188,11 @@ class Manager(manager.Manager):
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(
+ role_ids = self.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(
+ role_ids = self.list_role_ids_for_groups_on_domain(
group_ids, domain_id)
else:
raise AttributeError(_("Must specify either domain or project"))
@@ -261,10 +231,24 @@ class Manager(manager.Manager):
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)
+ @notifications.role_assignment('created')
+ def _add_role_to_user_and_project_adapter(self, role_id, user_id=None,
+ group_id=None, domain_id=None,
+ project_id=None,
+ inherited_to_projects=False,
+ context=None):
+
+ # The parameters for this method must match the parameters for
+ # create_grant so that the notifications.role_assignment decorator
+ # will work.
+
+ self.resource_api.get_project(project_id)
self.role_api.get_role(role_id)
- self.driver.add_role_to_user_and_project(user_id, tenant_id, role_id)
+ self.driver.add_role_to_user_and_project(user_id, project_id, role_id)
+
+ def add_role_to_user_and_project(self, user_id, tenant_id, role_id):
+ self._add_role_to_user_and_project_adapter(
+ role_id, user_id=user_id, project_id=tenant_id)
def remove_user_from_project(self, tenant_id, user_id):
"""Remove user from a tenant
@@ -299,7 +283,7 @@ class Manager(manager.Manager):
# 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(
+ project_ids = self.list_project_ids_for_user(
user_id, group_ids, hints or driver_hints.Hints())
if not CONF.os_inherit.enabled:
@@ -309,7 +293,7 @@ class Manager(manager.Manager):
# 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(
+ project_ids_inherited = self.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(
@@ -317,7 +301,7 @@ class Manager(manager.Manager):
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(
+ domain_ids = self.list_domain_ids_for_user(
user_id, group_ids, hints or driver_hints.Hints(),
inherited=True)
project_ids.update(
@@ -335,33 +319,42 @@ class Manager(manager.Manager):
# 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(
+ domain_ids = self.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)
+ domain_ids = self.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()))
+ self.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.
+ # os_inherit extension is enabled, so check to see if these groups have
+ # any inherited role assignment on: i) any domain, in which case we
+ # must add in all the projects in that domain; ii) any project, in
+ # which case we must add in all the subprojects under that project in
+ # the hierarchy.
- domain_ids = self.driver.list_domain_ids_for_groups(
- group_ids, inherited=True)
+ domain_ids = self.list_domain_ids_for_groups(group_ids, inherited=True)
project_ids_from_domains = (
self.resource_api.list_project_ids_from_domain_ids(domain_ids))
+ parents_ids = self.list_project_ids_for_groups(group_ids,
+ driver_hints.Hints(),
+ inherited=True)
+
+ subproject_ids = []
+ for parent_id in parents_ids:
+ subtree = self.resource_api.list_projects_in_subtree(parent_id)
+ subproject_ids += [subproject['id'] for subproject in subtree]
+
return self.resource_api.list_projects_from_ids(
- list(set(project_ids + project_ids_from_domains)))
+ list(set(project_ids + project_ids_from_domains + subproject_ids)))
def list_role_assignments_for_role(self, role_id=None):
# NOTE(henry-nash): Currently the efficiency of the key driver
@@ -374,17 +367,37 @@ class Manager(manager.Manager):
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,
+ @notifications.role_assignment('deleted')
+ def _remove_role_from_user_and_project_adapter(self, role_id, user_id=None,
+ group_id=None,
+ domain_id=None,
+ project_id=None,
+ inherited_to_projects=False,
+ context=None):
+
+ # The parameters for this method must match the parameters for
+ # delete_grant so that the notifications.role_assignment decorator
+ # will work.
+
+ self.driver.remove_role_from_user_and_project(user_id, project_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)
+ project_id=project_id)
+
+ def remove_role_from_user_and_project(self, user_id, tenant_id, role_id):
+ self._remove_role_from_user_and_project_adapter(
+ 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)
+ def _emit_invalidate_grant_token_persistence(self, user_id, project_id):
+ self.identity_api.emit_invalidate_grant_token_persistence(
+ {'user_id': user_id, 'project_id': project_id}
+ )
+
@notifications.role_assignment('created')
def create_grant(self, role_id, user_id=None, group_id=None,
domain_id=None, project_id=None,
@@ -405,7 +418,7 @@ class Manager(manager.Manager):
self.resource_api.get_domain(domain_id)
if project_id:
self.resource_api.get_project(project_id)
- self.driver.check_grant_role_id(
+ self.check_grant_role_id(
role_id, user_id, group_id, domain_id, project_id,
inherited_to_projects)
return role_ref
@@ -417,11 +430,15 @@ class Manager(manager.Manager):
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(
+ grant_ids = self.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 _emit_revoke_user_grant(self, role_id, user_id, domain_id, project_id,
+ inherited_to_projects, context):
+ self._emit_invalidate_grant_token_persistence(user_id, project_id)
+
def delete_grant(self, role_id, user_id=None, group_id=None,
domain_id=None, project_id=None,
inherited_to_projects=False, context=None):
@@ -430,17 +447,29 @@ class Manager(manager.Manager):
role_id=role_id,
domain_id=domain_id,
project_id=project_id)
+ self._emit_revoke_user_grant(
+ role_id, user_id, domain_id, project_id,
+ inherited_to_projects, context)
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)
+ # Group may contain a lot of users so revocation will be
+ # by role & domain/project
+ if domain_id is None:
+ self.revoke_api.revoke_by_project_role_assignment(
+ project_id, role_id
+ )
+ else:
+ self.revoke_api.revoke_by_domain_role_assignment(
+ domain_id, role_id
+ )
+ if CONF.token.revoke_by_id:
+ # 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):
+ self._emit_revoke_user_grant(
+ role_id, user['id'], domain_id, project_id,
+ inherited_to_projects, context)
except exception.GroupNotFound:
LOG.debug('Group %s not found, no tokens to invalidate.',
group_id)
@@ -457,8 +486,356 @@ class Manager(manager.Manager):
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)
+
+ # The methods _expand_indirect_assignment, _list_direct_role_assignments
+ # and _list_effective_role_assignments below are only used on
+ # list_role_assignments, but they are not in its scope as nested functions
+ # since it would significantly increase McCabe complexity, that should be
+ # kept as it is in order to detect unnecessarily complex code, which is not
+ # this case.
+
+ def _expand_indirect_assignment(self, ref, user_id=None,
+ project_id=None):
+ """Returns a list of expanded role assignments.
+
+ This methods is called for each discovered assignment that either needs
+ a group assignment expanded into individual user assignments, or needs
+ an inherited assignment to be applied to its children.
+
+ In all cases, if either user_id and/or project_id is specified, then we
+ filter the result on those values.
+
+ """
+
+ def create_group_assignment(base_ref, user_id):
+ """Creates a group assignment from the provided ref."""
+
+ ref = copy.deepcopy(base_ref)
+
+ ref['user_id'] = user_id
+
+ indirect = ref.setdefault('indirect', {})
+ indirect['group_id'] = ref.pop('group_id')
+
+ return ref
+
+ def expand_group_assignment(ref, user_id):
+ """Expands group role assignment.
+
+ For any group role assignment on a target, it is replaced by a list
+ of role assignments containing one for each user of that group on
+ that target.
+
+ An example of accepted ref is:
+
+ {
+ 'group_id': group_id,
+ 'project_id': project_id,
+ 'role_id': role_id
+ }
+
+ Once expanded, it should be returned as a list of entities like the
+ one below, one for each each user_id in the provided group_id.
+
+ {
+ 'user_id': user_id,
+ 'project_id': project_id,
+ 'role_id': role_id,
+ 'indirect' : {
+ 'group_id': group_id
+ }
+ }
+
+ Returned list will be formatted by the Controller, which will
+ deduce a role assignment came from group membership if it has both
+ 'user_id' in the main body of the dict and 'group_id' in indirect
+ subdict.
+
+ """
+ if user_id:
+ return [create_group_assignment(ref, user_id=user_id)]
+
+ return [create_group_assignment(ref, user_id=m['id'])
+ for m in self.identity_api.list_users_in_group(
+ ref['group_id'])]
+
+ def expand_inherited_assignment(ref, user_id, project_id=None):
+ """Expands inherited role assignments.
+
+ If this is a group role assignment on a target, replace it by a
+ list of role assignments containing one for each user of that
+ group, on every project under that target.
+
+ If this is a user role assignment on a target, replace it by a
+ list of role assignments for that user on every project under
+ that target.
+
+ An example of accepted ref is:
+
+ {
+ 'group_id': group_id,
+ 'project_id': parent_id,
+ 'role_id': role_id,
+ 'inherited_to_projects': 'projects'
+ }
+
+ Once expanded, it should be returned as a list of entities like the
+ one below, one for each each user_id in the provided group_id and
+ for each subproject_id in the project_id subtree.
+
+ {
+ 'user_id': user_id,
+ 'project_id': subproject_id,
+ 'role_id': role_id,
+ 'indirect' : {
+ 'group_id': group_id,
+ 'project_id': parent_id
+ }
+ }
+
+ Returned list will be formatted by the Controller, which will
+ deduce a role assignment came from group membership if it has both
+ 'user_id' in the main body of the dict and 'group_id' in the
+ 'indirect' subdict, as well as it is possible to deduce if it has
+ come from inheritance if it contains both a 'project_id' in the
+ main body of the dict and 'parent_id' in the 'indirect' subdict.
+
+ """
+ def create_inherited_assignment(base_ref, project_id):
+ """Creates a project assignment from the provided ref.
+
+ base_ref can either be a project or domain inherited
+ assignment ref.
+
+ """
+ ref = copy.deepcopy(base_ref)
+
+ indirect = ref.setdefault('indirect', {})
+ if ref.get('project_id'):
+ indirect['project_id'] = ref.pop('project_id')
+ else:
+ indirect['domain_id'] = ref.pop('domain_id')
+
+ ref['project_id'] = project_id
+ ref.pop('inherited_to_projects')
+
+ return ref
+
+ # Define expanded project list to which to apply this assignment
+ if project_id:
+ # Since ref is an inherited assignment, it must have come from
+ # the domain or a parent. We only need apply it to the project
+ # requested.
+ project_ids = [project_id]
+ elif ref.get('domain_id'):
+ # A domain inherited assignment, so apply it to all projects
+ # in this domain
+ project_ids = (
+ [x['id'] for x in
+ self.resource_api.list_projects_in_domain(
+ ref['domain_id'])])
+ else:
+ # It must be a project assignment, so apply it to the subtree
+ project_ids = (
+ [x['id'] for x in
+ self.resource_api.list_projects_in_subtree(
+ ref['project_id'])])
+
+ new_refs = []
+ if 'group_id' in ref:
+ # Expand role assignment for all members and for all projects
+ for ref in expand_group_assignment(ref, user_id):
+ new_refs += [create_inherited_assignment(ref, proj_id)
+ for proj_id in project_ids]
+ else:
+ # Expand role assignment for all projects
+ new_refs += [create_inherited_assignment(ref, proj_id)
+ for proj_id in project_ids]
+
+ return new_refs
+
+ if ref.get('inherited_to_projects') == 'projects':
+ return expand_inherited_assignment(ref, user_id, project_id)
+ elif 'group_id' in ref:
+ return expand_group_assignment(ref, user_id)
+ return [ref]
+
+ def _list_effective_role_assignments(self, role_id, user_id, group_id,
+ domain_id, project_id, inherited):
+ """List role assignments in effective mode.
+
+ When using effective mode, besides the direct assignments, the indirect
+ ones that come from grouping or inheritance are retrieved and will then
+ be expanded.
+
+ The resulting list of assignments will be filtered by the provided
+ parameters, although since we are in effective mode, group can never
+ act as a filter (since group assignments are expanded into user roles)
+ and domain can only be filter if we want non-inherited assignments,
+ since domains can't inherit assignments.
+
+ The goal of this method is to only ask the driver for those
+ assignments as could effect the result based on the parameter filters
+ specified, hence avoiding retrieving a huge list.
+
+ """
+
+ def list_role_assignments_for_actor(
+ role_id, inherited, user_id=None,
+ group_ids=None, project_id=None, domain_id=None):
+ """List role assignments for actor on target.
+
+ List direct and indirect assignments for an actor, optionally
+ for a given target (i.e. project or domain).
+
+ :param role_id: List for a specific role, can be None meaning all
+ roles
+ :param inherited: Indicates whether inherited assignments or only
+ direct assignments are required. If None, then
+ both are required.
+ :param user_id: If not None, list only assignments that affect this
+ user.
+ :param group_ids: A list of groups required. Only one of user_id
+ and group_ids can be specified
+ :param project_id: If specified, only include those assignments
+ that affect this project
+ :param domain_id: If specified, only include those assignments
+ that affect this domain - by definition this will
+ not include any inherited assignments
+
+ :returns: List of assignments matching the criteria. Any inherited
+ or group assignments that could affect the resulting
+ response are included.
+
+ """
+
+ # List direct project role assignments
+ project_ids = [project_id] if project_id else None
+
+ non_inherited_refs = []
+ if inherited is False or inherited is None:
+ # Get non inherited assignments
+ non_inherited_refs = self.driver.list_role_assignments(
+ role_id=role_id, domain_id=domain_id,
+ project_ids=project_ids, user_id=user_id,
+ group_ids=group_ids, inherited_to_projects=False)
+
+ inherited_refs = []
+ if inherited is True or inherited is None:
+ # Get inherited assignments
+ if project_id:
+ # If we are filtering by a specific project, then we can
+ # only get inherited assignments from its domain or from
+ # any of its parents.
+
+ # List inherited assignments from the project's domain
+ proj_domain_id = self.resource_api.get_project(
+ project_id)['domain_id']
+ inherited_refs += self.driver.list_role_assignments(
+ role_id=role_id, domain_id=proj_domain_id,
+ user_id=user_id, group_ids=group_ids,
+ inherited_to_projects=True)
+
+ # And those assignments that could be inherited from the
+ # project's parents.
+ parent_ids = [project['id'] for project in
+ self.resource_api.list_project_parents(
+ project_id)]
+ if parent_ids:
+ inherited_refs += self.driver.list_role_assignments(
+ role_id=role_id, project_ids=parent_ids,
+ user_id=user_id, group_ids=group_ids,
+ inherited_to_projects=True)
+ else:
+ # List inherited assignments without filtering by target
+ inherited_refs = self.driver.list_role_assignments(
+ role_id=role_id, user_id=user_id, group_ids=group_ids,
+ inherited_to_projects=True)
+
+ return non_inherited_refs + inherited_refs
+
+ # If filtering by group or inherited domain assignment the list is
+ # guranteed to be empty
+ if group_id or (domain_id and inherited):
+ return []
+
+ # If filtering by domain, then only non-inherited assignments are
+ # relevant, since domains don't inherit assignments
+ inherited = False if domain_id else inherited
+
+ # List user assignments
+ direct_refs = list_role_assignments_for_actor(
+ role_id=role_id, user_id=user_id, project_id=project_id,
+ domain_id=domain_id, inherited=inherited)
+
+ # And those from the user's groups
+ group_refs = []
+ if user_id:
+ group_ids = self._get_group_ids_for_user_id(user_id)
+ if group_ids:
+ group_refs = list_role_assignments_for_actor(
+ role_id=role_id, project_id=project_id,
+ group_ids=group_ids, domain_id=domain_id,
+ inherited=inherited)
+
+ # Expand grouping and inheritance on retrieved role assignments
+ refs = []
+ for ref in (direct_refs + group_refs):
+ refs += self._expand_indirect_assignment(ref=ref, user_id=user_id,
+ project_id=project_id)
+
+ return refs
+
+ def _list_direct_role_assignments(self, role_id, user_id, group_id,
+ domain_id, project_id, inherited):
+ """List role assignments without applying expansion.
+
+ Returns a list of direct role assignments, where their attributes match
+ the provided filters.
+
+ """
+ group_ids = [group_id] if group_id else None
+ project_ids = [project_id] if project_id else None
+
+ return self.driver.list_role_assignments(
+ role_id=role_id, user_id=user_id, group_ids=group_ids,
+ domain_id=domain_id, project_ids=project_ids,
+ inherited_to_projects=inherited)
+
+ def list_role_assignments(self, role_id=None, user_id=None, group_id=None,
+ domain_id=None, project_id=None, inherited=None,
+ effective=None):
+ """List role assignments, honoring effective mode and provided filters.
+
+ Returns a list of role assignments, where their attributes match the
+ provided filters (role_id, user_id, group_id, domain_id, project_id and
+ inherited). The inherited filter defaults to None, meaning to get both
+ non-inherited and inherited role assignments.
+
+ If effective mode is specified, this means that rather than simply
+ return the assignments that match the filters, any group or
+ inheritance assignments will be expanded. Group assignments will
+ become assignments for all the users in that group, and inherited
+ assignments will be shown on the projects below the assignment point.
+ Think of effective mode as being the list of assignments that actually
+ affect a user, for example the roles that would be placed in a token.
+
+ If OS-INHERIT extension is disabled or the used driver does not support
+ inherited roles retrieval, inherited role assignments will be ignored.
+
+ """
+
+ if not CONF.os_inherit.enabled:
+ if inherited:
+ return []
+ inherited = False
+
+ if effective:
+ return self._list_effective_role_assignments(
+ role_id, user_id, group_id, domain_id, project_id, inherited)
+ else:
+ return self._list_direct_role_assignments(
+ role_id, user_id, group_id, domain_id, project_id, inherited)
def delete_tokens_for_role_assignments(self, role_id):
assignments = self.list_role_assignments_for_role(role_id=role_id)
@@ -532,98 +909,6 @@ class Manager(manager.Manager):
# 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):
@@ -642,26 +927,6 @@ class Driver(object):
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
@@ -740,8 +1005,16 @@ class Driver(object):
raise exception.NotImplemented() # pragma: no cover
@abc.abstractmethod
- def list_role_assignments(self):
+ def list_role_assignments(self, role_id=None,
+ user_id=None, group_ids=None,
+ domain_id=None, project_ids=None,
+ inherited_to_projects=None):
+ """Returns a list of role assignments for actors on targets.
+
+ Available parameters represent values in which the returned role
+ assignments attributes need to be filtered on.
+ """
raise exception.NotImplemented() # pragma: no cover
@abc.abstractmethod
@@ -866,12 +1139,8 @@ class Driver(object):
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):
+ def delete_user_assignments(self, user_id):
"""Deletes all assignments for a user.
:raises: keystone.exception.RoleNotFound
@@ -880,7 +1149,7 @@ class Driver(object):
raise exception.NotImplemented() # pragma: no cover
@abc.abstractmethod
- def delete_group(self, group_id):
+ def delete_group_assignments(self, group_id):
"""Deletes all assignments for a group.
:raises: keystone.exception.RoleNotFound
@@ -894,6 +1163,8 @@ class Driver(object):
class RoleManager(manager.Manager):
"""Default pivot point for the Role backend."""
+ driver_namespace = 'keystone.role'
+
_ROLE = 'role'
def __init__(self):
@@ -902,9 +1173,8 @@ class RoleManager(manager.Manager):
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()
+ assignment_manager = dependency.get_provider('assignment_api')
+ role_driver = assignment_manager.default_role_driver()
super(RoleManager, self).__init__(role_driver)