aboutsummaryrefslogtreecommitdiffstats
path: root/keystone-moon/keystone/assignment/backends
diff options
context:
space:
mode:
authorWuKong <rebirthmonkey@gmail.com>2015-06-30 18:47:29 +0200
committerWuKong <rebirthmonkey@gmail.com>2015-06-30 18:47:29 +0200
commitb8c756ecdd7cced1db4300935484e8c83701c82e (patch)
tree87e51107d82b217ede145de9d9d59e2100725bd7 /keystone-moon/keystone/assignment/backends
parentc304c773bae68fb854ed9eab8fb35c4ef17cf136 (diff)
migrate moon code from github to opnfv
Change-Id: Ice53e368fd1114d56a75271aa9f2e598e3eba604 Signed-off-by: WuKong <rebirthmonkey@gmail.com>
Diffstat (limited to 'keystone-moon/keystone/assignment/backends')
-rw-r--r--keystone-moon/keystone/assignment/backends/__init__.py0
-rw-r--r--keystone-moon/keystone/assignment/backends/ldap.py531
-rw-r--r--keystone-moon/keystone/assignment/backends/sql.py415
3 files changed, 946 insertions, 0 deletions
diff --git a/keystone-moon/keystone/assignment/backends/__init__.py b/keystone-moon/keystone/assignment/backends/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/keystone-moon/keystone/assignment/backends/__init__.py
diff --git a/keystone-moon/keystone/assignment/backends/ldap.py b/keystone-moon/keystone/assignment/backends/ldap.py
new file mode 100644
index 00000000..f93e989f
--- /dev/null
+++ b/keystone-moon/keystone/assignment/backends/ldap.py
@@ -0,0 +1,531 @@
+# Copyright 2012-2013 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+from __future__ import absolute_import
+
+import ldap as ldap
+import ldap.filter
+from oslo_config import cfg
+from oslo_log import log
+
+from keystone import assignment
+from keystone.assignment.role_backends import ldap as ldap_role
+from keystone.common import ldap as common_ldap
+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
+LOG = log.getLogger(__name__)
+
+
+class Assignment(assignment.Driver):
+ @versionutils.deprecated(
+ versionutils.deprecated.KILO,
+ remove_in=+2,
+ what='keystone.assignment.backends.ldap.Assignment')
+ def __init__(self):
+ super(Assignment, self).__init__()
+ self.LDAP_URL = CONF.ldap.url
+ self.LDAP_USER = CONF.ldap.user
+ self.LDAP_PASSWORD = CONF.ldap.password
+ self.suffix = CONF.ldap.suffix
+
+ # This is the only deep dependency from assignment back to identity.
+ # This is safe to do since if you are using LDAP for assignment, it is
+ # required that you are using it for identity as well.
+ self.user = ldap_identity.UserApi(CONF)
+ self.group = ldap_identity.GroupApi(CONF)
+
+ self.project = ProjectApi(CONF)
+ self.role = RoleApi(CONF, self.user)
+
+ def default_role_driver(self):
+ return 'keystone.assignment.role_backends.ldap.Role'
+
+ def default_resource_driver(self):
+ return 'keystone.resource.backends.ldap.Resource'
+
+ def list_role_ids_for_groups_on_project(
+ self, groups, project_id, project_domain_id, project_parents):
+ group_dns = [self.group._id_to_dn(group_id) for group_id in groups]
+ role_list = [self.role._dn_to_id(role_assignment.role_dn)
+ for role_assignment in self.role.get_role_assignments
+ (self.project._id_to_dn(project_id))
+ if role_assignment.user_dn.upper() in group_dns]
+ # NOTE(morganfainberg): Does not support OS-INHERIT as domain
+ # metadata/roles are not supported by LDAP backend. Skip OS-INHERIT
+ # logic.
+ return role_list
+
+ def _get_metadata(self, user_id=None, tenant_id=None,
+ domain_id=None, group_id=None):
+
+ def _get_roles_for_just_user_and_project(user_id, tenant_id):
+ user_dn = self.user._id_to_dn(user_id)
+ return [self.role._dn_to_id(a.role_dn)
+ for a in self.role.get_role_assignments
+ (self.project._id_to_dn(tenant_id))
+ if common_ldap.is_dn_equal(a.user_dn, user_dn)]
+
+ def _get_roles_for_group_and_project(group_id, project_id):
+ group_dn = self.group._id_to_dn(group_id)
+ return [self.role._dn_to_id(a.role_dn)
+ for a in self.role.get_role_assignments
+ (self.project._id_to_dn(project_id))
+ if common_ldap.is_dn_equal(a.user_dn, group_dn)]
+
+ if domain_id is not None:
+ msg = _('Domain metadata not supported by LDAP')
+ raise exception.NotImplemented(message=msg)
+ if group_id is None and user_id is None:
+ return {}
+
+ if tenant_id is None:
+ return {}
+ if user_id is None:
+ metadata_ref = _get_roles_for_group_and_project(group_id,
+ tenant_id)
+ else:
+ metadata_ref = _get_roles_for_just_user_and_project(user_id,
+ tenant_id)
+ if not metadata_ref:
+ return {}
+ return {'roles': [self._role_to_dict(r, False) for r in metadata_ref]}
+
+ def list_project_ids_for_user(self, user_id, group_ids, hints,
+ inherited=False):
+ # TODO(henry-nash): The ldap driver does not support inherited
+ # assignments, so the inherited parameter is unused.
+ # See bug #1404273.
+ user_dn = self.user._id_to_dn(user_id)
+ associations = (self.role.list_project_roles_for_user
+ (user_dn, self.project.tree_dn))
+
+ for group_id in group_ids:
+ group_dn = self.group._id_to_dn(group_id)
+ for group_role in self.role.list_project_roles_for_group(
+ group_dn, self.project.tree_dn):
+ associations.append(group_role)
+
+ return list(set(
+ [self.project._dn_to_id(x.project_dn) for x in associations]))
+
+ def list_role_ids_for_groups_on_domain(self, group_ids, domain_id):
+ raise exception.NotImplemented()
+
+ def list_project_ids_for_groups(self, group_ids, hints,
+ inherited=False):
+ raise exception.NotImplemented()
+
+ def list_domain_ids_for_user(self, user_id, group_ids, hints):
+ raise exception.NotImplemented()
+
+ def list_domain_ids_for_groups(self, group_ids, inherited=False):
+ raise exception.NotImplemented()
+
+ def list_user_ids_for_project(self, tenant_id):
+ tenant_dn = self.project._id_to_dn(tenant_id)
+ rolegrants = self.role.get_role_assignments(tenant_dn)
+ return [self.user._dn_to_id(user_dn) for user_dn in
+ self.project.get_user_dns(tenant_id, rolegrants)]
+
+ def _subrole_id_to_dn(self, role_id, tenant_id):
+ if tenant_id is None:
+ return self.role._id_to_dn(role_id)
+ else:
+ return '%s=%s,%s' % (self.role.id_attr,
+ ldap.dn.escape_dn_chars(role_id),
+ self.project._id_to_dn(tenant_id))
+
+ def add_role_to_user_and_project(self, user_id, tenant_id, role_id):
+ user_dn = self.user._id_to_dn(user_id)
+ role_dn = self._subrole_id_to_dn(role_id, tenant_id)
+ self.role.add_user(role_id, role_dn, user_dn, user_id, tenant_id)
+ tenant_dn = self.project._id_to_dn(tenant_id)
+ return UserRoleAssociation(role_dn=role_dn,
+ user_dn=user_dn,
+ tenant_dn=tenant_dn)
+
+ def _add_role_to_group_and_project(self, group_id, tenant_id, role_id):
+ group_dn = self.group._id_to_dn(group_id)
+ role_dn = self._subrole_id_to_dn(role_id, tenant_id)
+ self.role.add_user(role_id, role_dn, group_dn, group_id, tenant_id)
+ tenant_dn = self.project._id_to_dn(tenant_id)
+ return GroupRoleAssociation(group_dn=group_dn,
+ role_dn=role_dn,
+ tenant_dn=tenant_dn)
+
+ def remove_role_from_user_and_project(self, user_id, tenant_id, role_id):
+ role_dn = self._subrole_id_to_dn(role_id, tenant_id)
+ return self.role.delete_user(role_dn,
+ self.user._id_to_dn(user_id), role_id)
+
+ def _remove_role_from_group_and_project(self, group_id, tenant_id,
+ role_id):
+ role_dn = self._subrole_id_to_dn(role_id, tenant_id)
+ return self.role.delete_user(role_dn,
+ self.group._id_to_dn(group_id), role_id)
+
+# Bulk actions on User From identity
+ def delete_user(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,
+ self.role._dn_to_id(ref.role_dn))
+ for ref in self.role.list_project_roles_for_user(user_dn,
+ self.project.tree_dn):
+ self.role.delete_user(ref.role_dn, ref.user_dn,
+ self.role._dn_to_id(ref.role_dn))
+
+ def delete_group(self, group_id):
+ """Called when the group was deleted.
+
+ Any role assignments for the group should be cleaned up.
+
+ """
+ group_dn = self.group._id_to_dn(group_id)
+ group_role_assignments = self.role.list_project_roles_for_group(
+ group_dn, self.project.tree_dn)
+ for ref in group_role_assignments:
+ self.role.delete_user(ref.role_dn, ref.group_dn,
+ self.role._dn_to_id(ref.role_dn))
+
+ def create_grant(self, role_id, user_id=None, group_id=None,
+ domain_id=None, project_id=None,
+ inherited_to_projects=False):
+
+ try:
+ metadata_ref = self._get_metadata(user_id, project_id,
+ domain_id, group_id)
+ except exception.MetadataNotFound:
+ metadata_ref = {}
+
+ if user_id is None:
+ metadata_ref['roles'] = self._add_role_to_group_and_project(
+ group_id, project_id, role_id)
+ else:
+ metadata_ref['roles'] = self.add_role_to_user_and_project(
+ user_id, project_id, role_id)
+
+ def check_grant_role_id(self, role_id, user_id=None, group_id=None,
+ domain_id=None, project_id=None,
+ inherited_to_projects=False):
+
+ try:
+ metadata_ref = self._get_metadata(user_id, project_id,
+ domain_id, group_id)
+ except exception.MetadataNotFound:
+ metadata_ref = {}
+ role_ids = set(self._roles_from_role_dicts(
+ metadata_ref.get('roles', []), inherited_to_projects))
+ if role_id not in role_ids:
+ actor_id = user_id or group_id
+ target_id = domain_id or project_id
+ raise exception.RoleAssignmentNotFound(role_id=role_id,
+ actor_id=actor_id,
+ target_id=target_id)
+
+ def delete_grant(self, role_id, user_id=None, group_id=None,
+ domain_id=None, project_id=None,
+ inherited_to_projects=False):
+
+ try:
+ metadata_ref = self._get_metadata(user_id, project_id,
+ domain_id, group_id)
+ except exception.MetadataNotFound:
+ metadata_ref = {}
+
+ try:
+ if user_id is None:
+ metadata_ref['roles'] = (
+ self._remove_role_from_group_and_project(
+ group_id, project_id, role_id))
+ else:
+ metadata_ref['roles'] = self.remove_role_from_user_and_project(
+ user_id, project_id, role_id)
+ except (exception.RoleNotFound, KeyError):
+ actor_id = user_id or group_id
+ target_id = domain_id or project_id
+ raise exception.RoleAssignmentNotFound(role_id=role_id,
+ actor_id=actor_id,
+ target_id=target_id)
+
+ def list_grant_role_ids(self, user_id=None, group_id=None,
+ domain_id=None, project_id=None,
+ inherited_to_projects=False):
+
+ try:
+ metadata_ref = self._get_metadata(user_id, project_id,
+ domain_id, group_id)
+ except exception.MetadataNotFound:
+ metadata_ref = {}
+
+ return self._roles_from_role_dicts(metadata_ref.get('roles', []),
+ inherited_to_projects)
+
+ def list_role_assignments(self):
+ 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)
+ return role_assignments
+
+ def delete_project_assignments(self, project_id):
+ tenant_dn = self.project._id_to_dn(project_id)
+ self.role.roles_delete_subtree_by_project(tenant_dn)
+
+ def delete_role_assignments(self, role_id):
+ self.role.roles_delete_subtree_by_role(role_id, self.project.tree_dn)
+
+
+# TODO(termie): turn this into a data object and move logic to driver
+class ProjectApi(common_ldap.ProjectLdapStructureMixin,
+ common_ldap.EnabledEmuMixIn, common_ldap.BaseLdap):
+
+ model = models.Project
+
+ def __init__(self, conf):
+ super(ProjectApi, self).__init__(conf)
+ self.member_attribute = (conf.ldap.project_member_attribute
+ or self.DEFAULT_MEMBER_ATTRIBUTE)
+
+ def get_user_projects(self, user_dn, associations):
+ """Returns list of tenants a user has access to
+ """
+
+ project_ids = set()
+ for assoc in associations:
+ project_ids.add(self._dn_to_id(assoc.project_dn))
+ projects = []
+ for project_id in project_ids:
+ # slower to get them one at a time, but a huge list could blow out
+ # the connection. This is the safer way
+ projects.append(self.get(project_id))
+ return projects
+
+ def get_user_dns(self, tenant_id, rolegrants, role_dn=None):
+ tenant = self._ldap_get(tenant_id)
+ res = set()
+ if not role_dn:
+ # Get users who have default tenant mapping
+ for user_dn in tenant[1].get(self.member_attribute, []):
+ if self._is_dumb_member(user_dn):
+ continue
+ res.add(user_dn)
+
+ # Get users who are explicitly mapped via a tenant
+ for rolegrant in rolegrants:
+ if role_dn is None or rolegrant.role_dn == role_dn:
+ res.add(rolegrant.user_dn)
+ return list(res)
+
+
+class UserRoleAssociation(object):
+ """Role Grant model."""
+
+ def __init__(self, user_dn=None, role_dn=None, tenant_dn=None,
+ *args, **kw):
+ self.user_dn = user_dn
+ self.role_dn = role_dn
+ self.project_dn = tenant_dn
+
+
+class GroupRoleAssociation(object):
+ """Role Grant model."""
+
+ def __init__(self, group_dn=None, role_dn=None, tenant_dn=None,
+ *args, **kw):
+ self.group_dn = group_dn
+ self.role_dn = role_dn
+ self.project_dn = tenant_dn
+
+
+# TODO(termie): turn this into a data object and move logic to driver
+# NOTE(heny-nash): The RoleLdapStructureMixin class enables the sharing of the
+# LDAP structure between here and the role backend LDAP, no methods are shared.
+class RoleApi(ldap_role.RoleLdapStructureMixin, common_ldap.BaseLdap):
+
+ def __init__(self, conf, user_api):
+ super(RoleApi, self).__init__(conf)
+ self.member_attribute = (conf.ldap.role_member_attribute
+ or self.DEFAULT_MEMBER_ATTRIBUTE)
+ self._user_api = user_api
+
+ def add_user(self, role_id, role_dn, user_dn, user_id, tenant_id=None):
+ try:
+ super(RoleApi, self).add_member(user_dn, role_dn)
+ except exception.Conflict:
+ msg = (_('User %(user_id)s already has role %(role_id)s in '
+ 'tenant %(tenant_id)s') %
+ dict(user_id=user_id, role_id=role_id, tenant_id=tenant_id))
+ raise exception.Conflict(type='role grant', details=msg)
+ except self.NotFound:
+ if tenant_id is None or self.get(role_id) is None:
+ raise Exception(_("Role %s not found") % (role_id,))
+
+ attrs = [('objectClass', [self.object_class]),
+ (self.member_attribute, [user_dn]),
+ (self.id_attr, [role_id])]
+
+ if self.use_dumb_member:
+ attrs[1][1].append(self.dumb_member)
+ with self.get_connection() as conn:
+ conn.add_s(role_dn, attrs)
+
+ def delete_user(self, role_dn, user_dn, role_id):
+ try:
+ super(RoleApi, self).remove_member(user_dn, role_dn)
+ except (self.NotFound, ldap.NO_SUCH_ATTRIBUTE):
+ raise exception.RoleNotFound(message=_(
+ 'Cannot remove role that has not been granted, %s') %
+ role_id)
+
+ def get_role_assignments(self, tenant_dn):
+ try:
+ roles = self._ldap_get_list(tenant_dn, ldap.SCOPE_ONELEVEL,
+ attrlist=[self.member_attribute])
+ except ldap.NO_SUCH_OBJECT:
+ roles = []
+ res = []
+ for role_dn, attrs in roles:
+ try:
+ user_dns = attrs[self.member_attribute]
+ except KeyError:
+ continue
+ for user_dn in user_dns:
+ if self._is_dumb_member(user_dn):
+ continue
+ res.append(UserRoleAssociation(
+ user_dn=user_dn,
+ role_dn=role_dn,
+ tenant_dn=tenant_dn))
+
+ return res
+
+ def list_global_roles_for_user(self, user_dn):
+ user_dn_esc = ldap.filter.escape_filter_chars(user_dn)
+ roles = self.get_all('(%s=%s)' % (self.member_attribute, user_dn_esc))
+ return [UserRoleAssociation(
+ role_dn=role.dn,
+ user_dn=user_dn) for role in roles]
+
+ def list_project_roles_for_user(self, user_dn, project_subtree):
+ try:
+ roles = self._ldap_get_list(project_subtree, ldap.SCOPE_SUBTREE,
+ query_params={
+ self.member_attribute: user_dn},
+ attrlist=common_ldap.DN_ONLY)
+ except ldap.NO_SUCH_OBJECT:
+ roles = []
+ res = []
+ for role_dn, _role_attrs in roles:
+ # ldap.dn.dn2str returns an array, where the first
+ # element is the first segment.
+ # For a role assignment, this contains the role ID,
+ # The remainder is the DN of the tenant.
+ # role_dn is already utf8 encoded since it came from LDAP.
+ tenant = ldap.dn.str2dn(role_dn)
+ tenant.pop(0)
+ tenant_dn = ldap.dn.dn2str(tenant)
+ res.append(UserRoleAssociation(
+ user_dn=user_dn,
+ role_dn=role_dn,
+ tenant_dn=tenant_dn))
+ return res
+
+ def list_project_roles_for_group(self, group_dn, project_subtree):
+ group_dn_esc = ldap.filter.escape_filter_chars(group_dn)
+ query = '(&(objectClass=%s)(%s=%s))' % (self.object_class,
+ self.member_attribute,
+ group_dn_esc)
+ with self.get_connection() as conn:
+ try:
+ roles = conn.search_s(project_subtree,
+ ldap.SCOPE_SUBTREE,
+ query,
+ attrlist=common_ldap.DN_ONLY)
+ except ldap.NO_SUCH_OBJECT:
+ # Return no roles rather than raise an exception if the project
+ # subtree entry doesn't exist because an empty subtree is not
+ # an error.
+ return []
+
+ res = []
+ for role_dn, _role_attrs in roles:
+ # ldap.dn.str2dn returns a list, where the first
+ # element is the first RDN.
+ # For a role assignment, this contains the role ID,
+ # the remainder is the DN of the project.
+ # role_dn is already utf8 encoded since it came from LDAP.
+ project = ldap.dn.str2dn(role_dn)
+ project.pop(0)
+ project_dn = ldap.dn.dn2str(project)
+ res.append(GroupRoleAssociation(
+ group_dn=group_dn,
+ role_dn=role_dn,
+ tenant_dn=project_dn))
+ return res
+
+ def roles_delete_subtree_by_project(self, tenant_dn):
+ self._delete_tree_nodes(tenant_dn, ldap.SCOPE_ONELEVEL)
+
+ def roles_delete_subtree_by_role(self, role_id, tree_dn):
+ self._delete_tree_nodes(tree_dn, ldap.SCOPE_SUBTREE, query_params={
+ 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.
+ """
+ try:
+ roles = self._ldap_get_list(project_tree_dn, ldap.SCOPE_SUBTREE,
+ attrlist=[self.member_attribute])
+ except ldap.NO_SUCH_OBJECT:
+ roles = []
+ res = []
+ for role_dn, role in roles:
+ # role_dn is already utf8 encoded since it came from LDAP.
+ tenant = ldap.dn.str2dn(role_dn)
+ tenant.pop(0)
+ # It obtains the tenant DN to construct the UserRoleAssociation
+ # object.
+ tenant_dn = ldap.dn.dn2str(tenant)
+ for occupant_dn in role[self.member_attribute]:
+ if self._is_dumb_member(occupant_dn):
+ continue
+ if self._user_api.is_user(occupant_dn):
+ association = UserRoleAssociation(
+ user_dn=occupant_dn,
+ role_dn=role_dn,
+ tenant_dn=tenant_dn)
+ else:
+ # occupant_dn is a group.
+ association = GroupRoleAssociation(
+ group_dn=occupant_dn,
+ role_dn=role_dn,
+ tenant_dn=tenant_dn)
+ res.append(association)
+ return res
diff --git a/keystone-moon/keystone/assignment/backends/sql.py b/keystone-moon/keystone/assignment/backends/sql.py
new file mode 100644
index 00000000..2de6ca60
--- /dev/null
+++ b/keystone-moon/keystone/assignment/backends/sql.py
@@ -0,0 +1,415 @@
+# Copyright 2012-13 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_config import cfg
+from oslo_log import log
+import six
+import sqlalchemy
+from sqlalchemy.sql.expression import false
+
+from keystone import assignment as keystone_assignment
+from keystone.common import sql
+from keystone import exception
+from keystone.i18n import _
+
+
+CONF = cfg.CONF
+LOG = log.getLogger(__name__)
+
+
+class AssignmentType(object):
+ USER_PROJECT = 'UserProject'
+ GROUP_PROJECT = 'GroupProject'
+ USER_DOMAIN = 'UserDomain'
+ GROUP_DOMAIN = 'GroupDomain'
+
+ @classmethod
+ def calculate_type(cls, user_id, group_id, project_id, domain_id):
+ if user_id:
+ if project_id:
+ return cls.USER_PROJECT
+ if domain_id:
+ return cls.USER_DOMAIN
+ if group_id:
+ if project_id:
+ return cls.GROUP_PROJECT
+ if domain_id:
+ return cls.GROUP_DOMAIN
+ # Invalid parameters combination
+ raise exception.AssignmentTypeCalculationError(**locals())
+
+
+class Assignment(keystone_assignment.Driver):
+
+ def default_role_driver(self):
+ return "keystone.assignment.role_backends.sql.Role"
+
+ def default_resource_driver(self):
+ return 'keystone.resource.backends.sql.Resource'
+
+ def list_user_ids_for_project(self, tenant_id):
+ with sql.transaction() as session:
+ query = session.query(RoleAssignment.actor_id)
+ query = query.filter_by(type=AssignmentType.USER_PROJECT)
+ query = query.filter_by(target_id=tenant_id)
+ query = query.distinct('actor_id')
+ assignments = query.all()
+ return [assignment.actor_id for assignment in assignments]
+
+ def _get_metadata(self, user_id=None, tenant_id=None,
+ domain_id=None, group_id=None, session=None):
+ # TODO(henry-nash): This method represents the last vestiges of the old
+ # metadata concept in this driver. Although we no longer need it here,
+ # since the Manager layer uses the metadata concept across all
+ # assignment drivers, we need to remove it from all of them in order to
+ # finally remove this method.
+
+ # We aren't given a session when called by the manager directly.
+ if session is None:
+ session = sql.get_session()
+
+ q = session.query(RoleAssignment)
+
+ def _calc_assignment_type():
+ # Figure out the assignment type we're checking for from the args.
+ if user_id:
+ if tenant_id:
+ return AssignmentType.USER_PROJECT
+ else:
+ return AssignmentType.USER_DOMAIN
+ else:
+ if tenant_id:
+ return AssignmentType.GROUP_PROJECT
+ else:
+ return AssignmentType.GROUP_DOMAIN
+
+ q = q.filter_by(type=_calc_assignment_type())
+ q = q.filter_by(actor_id=user_id or group_id)
+ q = q.filter_by(target_id=tenant_id or domain_id)
+ refs = q.all()
+ if not refs:
+ raise exception.MetadataNotFound()
+
+ metadata_ref = {}
+ metadata_ref['roles'] = []
+ for assignment in refs:
+ role_ref = {}
+ role_ref['id'] = assignment.role_id
+ if assignment.inherited:
+ role_ref['inherited_to'] = 'projects'
+ metadata_ref['roles'].append(role_ref)
+
+ return metadata_ref
+
+ def create_grant(self, role_id, user_id=None, group_id=None,
+ domain_id=None, project_id=None,
+ inherited_to_projects=False):
+
+ assignment_type = AssignmentType.calculate_type(
+ user_id, group_id, project_id, domain_id)
+ try:
+ with sql.transaction() as session:
+ session.add(RoleAssignment(
+ type=assignment_type,
+ actor_id=user_id or group_id,
+ target_id=project_id or domain_id,
+ role_id=role_id,
+ inherited=inherited_to_projects))
+ except sql.DBDuplicateEntry:
+ # The v3 grant APIs are silent if the assignment already exists
+ pass
+
+ def list_grant_role_ids(self, user_id=None, group_id=None,
+ domain_id=None, project_id=None,
+ inherited_to_projects=False):
+ with sql.transaction() as session:
+ q = session.query(RoleAssignment.role_id)
+ q = q.filter(RoleAssignment.actor_id == (user_id or group_id))
+ q = q.filter(RoleAssignment.target_id == (project_id or domain_id))
+ q = q.filter(RoleAssignment.inherited == inherited_to_projects)
+ return [x.role_id for x in q.all()]
+
+ def _build_grant_filter(self, session, role_id, user_id, group_id,
+ domain_id, project_id, inherited_to_projects):
+ q = session.query(RoleAssignment)
+ q = q.filter_by(actor_id=user_id or group_id)
+ q = q.filter_by(target_id=project_id or domain_id)
+ q = q.filter_by(role_id=role_id)
+ q = q.filter_by(inherited=inherited_to_projects)
+ return q
+
+ def check_grant_role_id(self, role_id, user_id=None, group_id=None,
+ domain_id=None, project_id=None,
+ inherited_to_projects=False):
+ with sql.transaction() as session:
+ try:
+ q = self._build_grant_filter(
+ session, role_id, user_id, group_id, domain_id, project_id,
+ inherited_to_projects)
+ q.one()
+ except sql.NotFound:
+ actor_id = user_id or group_id
+ target_id = domain_id or project_id
+ raise exception.RoleAssignmentNotFound(role_id=role_id,
+ actor_id=actor_id,
+ target_id=target_id)
+
+ def delete_grant(self, role_id, user_id=None, group_id=None,
+ domain_id=None, project_id=None,
+ inherited_to_projects=False):
+ with sql.transaction() as session:
+ q = self._build_grant_filter(
+ session, role_id, user_id, group_id, domain_id, project_id,
+ inherited_to_projects)
+ if not q.delete(False):
+ actor_id = user_id or group_id
+ target_id = domain_id or project_id
+ raise exception.RoleAssignmentNotFound(role_id=role_id,
+ actor_id=actor_id,
+ target_id=target_id)
+
+ def _list_project_ids_for_actor(self, actors, hints, inherited,
+ group_only=False):
+ # TODO(henry-nash): Now that we have a single assignment table, we
+ # should be able to honor the hints list that is provided.
+
+ assignment_type = [AssignmentType.GROUP_PROJECT]
+ if not group_only:
+ assignment_type.append(AssignmentType.USER_PROJECT)
+
+ sql_constraints = sqlalchemy.and_(
+ RoleAssignment.type.in_(assignment_type),
+ RoleAssignment.inherited == inherited,
+ RoleAssignment.actor_id.in_(actors))
+
+ with sql.transaction() as session:
+ query = session.query(RoleAssignment.target_id).filter(
+ sql_constraints).distinct()
+
+ return [x.target_id for x in query.all()]
+
+ def list_project_ids_for_user(self, user_id, group_ids, hints,
+ inherited=False):
+ actor_list = [user_id]
+ if group_ids:
+ actor_list = actor_list + group_ids
+
+ return self._list_project_ids_for_actor(actor_list, hints, inherited)
+
+ def list_domain_ids_for_user(self, user_id, group_ids, hints,
+ inherited=False):
+ with sql.transaction() as session:
+ query = session.query(RoleAssignment.target_id)
+ filters = []
+
+ if user_id:
+ sql_constraints = sqlalchemy.and_(
+ RoleAssignment.actor_id == user_id,
+ RoleAssignment.inherited == inherited,
+ RoleAssignment.type == AssignmentType.USER_DOMAIN)
+ filters.append(sql_constraints)
+
+ if group_ids:
+ sql_constraints = sqlalchemy.and_(
+ RoleAssignment.actor_id.in_(group_ids),
+ RoleAssignment.inherited == inherited,
+ RoleAssignment.type == AssignmentType.GROUP_DOMAIN)
+ filters.append(sql_constraints)
+
+ if not filters:
+ return []
+
+ query = query.filter(sqlalchemy.or_(*filters)).distinct()
+
+ return [assignment.target_id for assignment in query.all()]
+
+ def list_role_ids_for_groups_on_domain(self, group_ids, domain_id):
+ if not group_ids:
+ # If there's no groups then there will be no domain roles.
+ return []
+
+ sql_constraints = sqlalchemy.and_(
+ RoleAssignment.type == AssignmentType.GROUP_DOMAIN,
+ RoleAssignment.target_id == domain_id,
+ RoleAssignment.inherited == false(),
+ RoleAssignment.actor_id.in_(group_ids))
+
+ with sql.transaction() as session:
+ query = session.query(RoleAssignment.role_id).filter(
+ sql_constraints).distinct()
+ return [role.role_id for role in query.all()]
+
+ def list_role_ids_for_groups_on_project(
+ self, group_ids, project_id, project_domain_id, project_parents):
+
+ if not group_ids:
+ # If there's no groups then there will be no project roles.
+ return []
+
+ # NOTE(rodrigods): First, we always include projects with
+ # non-inherited assignments
+ sql_constraints = sqlalchemy.and_(
+ RoleAssignment.type == AssignmentType.GROUP_PROJECT,
+ RoleAssignment.inherited == false(),
+ RoleAssignment.target_id == project_id)
+
+ if CONF.os_inherit.enabled:
+ # Inherited roles from domains
+ sql_constraints = sqlalchemy.or_(
+ sql_constraints,
+ sqlalchemy.and_(
+ RoleAssignment.type == AssignmentType.GROUP_DOMAIN,
+ RoleAssignment.inherited,
+ RoleAssignment.target_id == project_domain_id))
+
+ # Inherited roles from projects
+ if project_parents:
+ sql_constraints = sqlalchemy.or_(
+ sql_constraints,
+ sqlalchemy.and_(
+ RoleAssignment.type == AssignmentType.GROUP_PROJECT,
+ RoleAssignment.inherited,
+ RoleAssignment.target_id.in_(project_parents)))
+
+ sql_constraints = sqlalchemy.and_(
+ sql_constraints, RoleAssignment.actor_id.in_(group_ids))
+
+ with sql.transaction() as session:
+ # NOTE(morganfainberg): Only select the columns we actually care
+ # about here, in this case role_id.
+ query = session.query(RoleAssignment.role_id).filter(
+ sql_constraints).distinct()
+
+ return [result.role_id for result in query.all()]
+
+ def list_project_ids_for_groups(self, group_ids, hints,
+ inherited=False):
+ return self._list_project_ids_for_actor(
+ group_ids, hints, inherited, group_only=True)
+
+ def list_domain_ids_for_groups(self, group_ids, inherited=False):
+ if not group_ids:
+ # If there's no groups then there will be no domains.
+ return []
+
+ group_sql_conditions = sqlalchemy.and_(
+ RoleAssignment.type == AssignmentType.GROUP_DOMAIN,
+ RoleAssignment.inherited == inherited,
+ RoleAssignment.actor_id.in_(group_ids))
+
+ with sql.transaction() as session:
+ query = session.query(RoleAssignment.target_id).filter(
+ group_sql_conditions).distinct()
+ return [x.target_id for x in query.all()]
+
+ def add_role_to_user_and_project(self, user_id, tenant_id, role_id):
+ try:
+ with sql.transaction() as session:
+ session.add(RoleAssignment(
+ type=AssignmentType.USER_PROJECT,
+ actor_id=user_id, target_id=tenant_id,
+ role_id=role_id, inherited=False))
+ except sql.DBDuplicateEntry:
+ msg = ('User %s already has role %s in tenant %s'
+ % (user_id, role_id, tenant_id))
+ raise exception.Conflict(type='role grant', details=msg)
+
+ def remove_role_from_user_and_project(self, user_id, tenant_id, role_id):
+ with sql.transaction() as session:
+ q = session.query(RoleAssignment)
+ q = q.filter_by(actor_id=user_id)
+ q = q.filter_by(target_id=tenant_id)
+ q = q.filter_by(role_id=role_id)
+ if q.delete() == 0:
+ raise exception.RoleNotFound(message=_(
+ 'Cannot remove role that has not been granted, %s') %
+ role_id)
+
+ def list_role_assignments(self):
+
+ def denormalize_role(ref):
+ assignment = {}
+ if ref.type == AssignmentType.USER_PROJECT:
+ assignment['user_id'] = ref.actor_id
+ assignment['project_id'] = ref.target_id
+ elif ref.type == AssignmentType.USER_DOMAIN:
+ assignment['user_id'] = ref.actor_id
+ assignment['domain_id'] = ref.target_id
+ elif ref.type == AssignmentType.GROUP_PROJECT:
+ assignment['group_id'] = ref.actor_id
+ assignment['project_id'] = ref.target_id
+ elif ref.type == AssignmentType.GROUP_DOMAIN:
+ assignment['group_id'] = ref.actor_id
+ assignment['domain_id'] = ref.target_id
+ else:
+ raise exception.Error(message=_(
+ 'Unexpected assignment type encountered, %s') %
+ ref.type)
+ assignment['role_id'] = ref.role_id
+ if ref.inherited:
+ assignment['inherited_to_projects'] = 'projects'
+ return assignment
+
+ with sql.transaction() as session:
+ refs = session.query(RoleAssignment).all()
+ return [denormalize_role(ref) for ref in refs]
+
+ def delete_project_assignments(self, project_id):
+ with sql.transaction() as session:
+ q = session.query(RoleAssignment)
+ q = q.filter_by(target_id=project_id)
+ q.delete(False)
+
+ def delete_role_assignments(self, role_id):
+ with sql.transaction() as session:
+ q = session.query(RoleAssignment)
+ q = q.filter_by(role_id=role_id)
+ q.delete(False)
+
+ def delete_user(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):
+ with sql.transaction() as session:
+ q = session.query(RoleAssignment)
+ q = q.filter_by(actor_id=group_id)
+ q.delete(False)
+
+
+class RoleAssignment(sql.ModelBase, sql.DictBase):
+ __tablename__ = 'assignment'
+ attributes = ['type', 'actor_id', 'target_id', 'role_id', 'inherited']
+ # NOTE(henry-nash); Postgres requires a name to be defined for an Enum
+ type = sql.Column(
+ sql.Enum(AssignmentType.USER_PROJECT, AssignmentType.GROUP_PROJECT,
+ AssignmentType.USER_DOMAIN, AssignmentType.GROUP_DOMAIN,
+ name='type'),
+ nullable=False)
+ actor_id = sql.Column(sql.String(64), nullable=False, index=True)
+ 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'), {})
+
+ def to_dict(self):
+ """Override parent to_dict() method with a simpler implementation.
+
+ RoleAssignment doesn't have non-indexed 'extra' attributes, so the
+ parent implementation is not applicable.
+ """
+ return dict(six.iteritems(self))