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/V8_backends/__init__.py0
-rw-r--r--keystone-moon/keystone/assignment/V8_backends/sql.py452
-rw-r--r--keystone-moon/keystone/assignment/V8_role_backends/__init__.py0
-rw-r--r--keystone-moon/keystone/assignment/V8_role_backends/sql.py80
-rw-r--r--keystone-moon/keystone/assignment/__init__.py16
-rw-r--r--keystone-moon/keystone/assignment/backends/__init__.py0
-rw-r--r--keystone-moon/keystone/assignment/backends/ldap.py545
-rw-r--r--keystone-moon/keystone/assignment/backends/sql.py319
-rw-r--r--keystone-moon/keystone/assignment/controllers.py972
-rw-r--r--keystone-moon/keystone/assignment/core.py1790
-rw-r--r--keystone-moon/keystone/assignment/role_backends/__init__.py0
-rw-r--r--keystone-moon/keystone/assignment/role_backends/ldap.py125
-rw-r--r--keystone-moon/keystone/assignment/role_backends/sql.py202
-rw-r--r--keystone-moon/keystone/assignment/routers.py282
-rw-r--r--keystone-moon/keystone/assignment/schema.py32
15 files changed, 0 insertions, 4815 deletions
diff --git a/keystone-moon/keystone/assignment/V8_backends/__init__.py b/keystone-moon/keystone/assignment/V8_backends/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/keystone-moon/keystone/assignment/V8_backends/__init__.py
+++ /dev/null
diff --git a/keystone-moon/keystone/assignment/V8_backends/sql.py b/keystone-moon/keystone/assignment/V8_backends/sql.py
deleted file mode 100644
index 88c10a6a..00000000
--- a/keystone-moon/keystone/assignment/V8_backends/sql.py
+++ /dev/null
@@ -1,452 +0,0 @@
-# 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
-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
-
-
-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.AssignmentDriverV8):
-
- def default_role_driver(self):
- return 'sql'
-
- def default_resource_driver(self):
- return 'sql'
-
- def list_user_ids_for_project(self, tenant_id):
- with sql.session_for_read() 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 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.session_for_write() 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: # nosec : 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.session_for_read() 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.session_for_read() 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.session_for_write() 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.session_for_read() 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.session_for_read() 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.session_for_read() 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.session_for_read() 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.session_for_read() 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.session_for_write() 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.session_for_write() 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 _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 = {}
- 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.session_for_read() as session:
- 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.session_for_write() 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.session_for_write() as session:
- q = session.query(RoleAssignment)
- q = q.filter_by(role_id=role_id)
- q.delete(False)
-
- def delete_user_assignments(self, user_id):
- with sql.session_for_write() as session:
- q = session.query(RoleAssignment)
- q = q.filter_by(actor_id=user_id)
- q.delete(False)
-
- def delete_group_assignments(self, group_id):
- with sql.session_for_write() 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)
- 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',
- 'inherited'),
- sql.Index('ix_actor_id', 'actor_id'),
- )
-
- def to_dict(self):
- """Override parent method with a simpler implementation.
-
- RoleAssignment doesn't have non-indexed 'extra' attributes, so the
- parent implementation is not applicable.
- """
- return dict(self.items())
diff --git a/keystone-moon/keystone/assignment/V8_role_backends/__init__.py b/keystone-moon/keystone/assignment/V8_role_backends/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/keystone-moon/keystone/assignment/V8_role_backends/__init__.py
+++ /dev/null
diff --git a/keystone-moon/keystone/assignment/V8_role_backends/sql.py b/keystone-moon/keystone/assignment/V8_role_backends/sql.py
deleted file mode 100644
index 2e2e119a..00000000
--- a/keystone-moon/keystone/assignment/V8_role_backends/sql.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# 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 keystone import assignment
-from keystone.common import sql
-from keystone import exception
-
-
-class Role(assignment.RoleDriverV8):
-
- @sql.handle_conflicts(conflict_type='role')
- def create_role(self, role_id, role):
- with sql.session_for_write() as session:
- ref = RoleTable.from_dict(role)
- session.add(ref)
- return ref.to_dict()
-
- @sql.truncated
- def list_roles(self, hints):
- with sql.session_for_read() as session:
- query = session.query(RoleTable)
- refs = sql.filter_limit_query(RoleTable, query, hints)
- return [ref.to_dict() for ref in refs]
-
- def list_roles_from_ids(self, ids):
- if not ids:
- return []
- else:
- with sql.session_for_read() as session:
- query = session.query(RoleTable)
- query = query.filter(RoleTable.id.in_(ids))
- role_refs = query.all()
- return [role_ref.to_dict() for role_ref in role_refs]
-
- def _get_role(self, session, role_id):
- ref = session.query(RoleTable).get(role_id)
- if ref is None:
- raise exception.RoleNotFound(role_id=role_id)
- return ref
-
- def get_role(self, role_id):
- with sql.session_for_read() as session:
- return self._get_role(session, role_id).to_dict()
-
- @sql.handle_conflicts(conflict_type='role')
- def update_role(self, role_id, role):
- with sql.session_for_write() as session:
- ref = self._get_role(session, role_id)
- old_dict = ref.to_dict()
- for k in role:
- old_dict[k] = role[k]
- new_role = RoleTable.from_dict(old_dict)
- for attr in RoleTable.attributes:
- if attr != 'id':
- setattr(ref, attr, getattr(new_role, attr))
- ref.extra = new_role.extra
- return ref.to_dict()
-
- def delete_role(self, role_id):
- with sql.session_for_write() as session:
- ref = self._get_role(session, role_id)
- session.delete(ref)
-
-
-class RoleTable(sql.ModelBase, sql.DictBase):
- __tablename__ = 'role'
- attributes = ['id', 'name']
- id = sql.Column(sql.String(64), primary_key=True)
- name = sql.Column(sql.String(255), unique=True, nullable=False)
- extra = sql.Column(sql.JsonBlob())
- __table_args__ = (sql.UniqueConstraint('name'),)
diff --git a/keystone-moon/keystone/assignment/__init__.py b/keystone-moon/keystone/assignment/__init__.py
deleted file mode 100644
index 4aa04ee6..00000000
--- a/keystone-moon/keystone/assignment/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 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 keystone.assignment import controllers # noqa
-from keystone.assignment.core import * # noqa
diff --git a/keystone-moon/keystone/assignment/backends/__init__.py b/keystone-moon/keystone/assignment/backends/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/keystone-moon/keystone/assignment/backends/__init__.py
+++ /dev/null
diff --git a/keystone-moon/keystone/assignment/backends/ldap.py b/keystone-moon/keystone/assignment/backends/ldap.py
deleted file mode 100644
index b52dc46e..00000000
--- a/keystone-moon/keystone/assignment/backends/ldap.py
+++ /dev/null
@@ -1,545 +0,0 @@
-# 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.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
-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
-
-
-CONF = cfg.CONF
-LOG = log.getLogger(__name__)
-
-
-class Assignment(assignment.AssignmentDriverV8):
- @versionutils.deprecated(
- versionutils.deprecated.KILO,
- remove_in=+2,
- what='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 'ldap'
-
- def default_resource_driver(self):
- return 'ldap'
-
- 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_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,
- 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_assignments(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_id=None,
- user_id=None, group_ids=None,
- domain_id=None, project_ids=None,
- inherited_to_projects=None):
- role_assignments = []
-
- # 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):
- 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 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))
- 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):
- """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])
- 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
deleted file mode 100644
index e089726a..00000000
--- a/keystone-moon/keystone/assignment/backends/sql.py
+++ /dev/null
@@ -1,319 +0,0 @@
-# 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 keystone import assignment as keystone_assignment
-from keystone.common import sql
-from keystone import exception
-from keystone.i18n import _
-
-
-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.AssignmentDriverV9):
-
- def default_role_driver(self):
- return 'sql'
-
- def default_resource_driver(self):
- return 'sql'
-
- 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.session_for_write() 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: # nosec : 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.session_for_read() 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.session_for_read() 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.session_for_write() 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 add_role_to_user_and_project(self, user_id, tenant_id, role_id):
- try:
- with sql.session_for_write() 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.session_for_write() 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 _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 = {}
- 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.session_for_read() as session:
- 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.session_for_write() as session:
- q = session.query(RoleAssignment)
- q = q.filter_by(target_id=project_id).filter(
- RoleAssignment.type.in_((AssignmentType.USER_PROJECT,
- AssignmentType.GROUP_PROJECT))
- )
- q.delete(False)
-
- def delete_role_assignments(self, role_id):
- with sql.session_for_write() as session:
- q = session.query(RoleAssignment)
- q = q.filter_by(role_id=role_id)
- q.delete(False)
-
- def delete_domain_assignments(self, domain_id):
- with sql.session_for_write() as session:
- q = session.query(RoleAssignment)
- q = q.filter(RoleAssignment.target_id == domain_id).filter(
- (RoleAssignment.type == AssignmentType.USER_DOMAIN) |
- (RoleAssignment.type == AssignmentType.GROUP_DOMAIN))
- q.delete(False)
-
- def delete_user_assignments(self, user_id):
- with sql.session_for_write() as session:
- q = session.query(RoleAssignment)
- q = q.filter_by(actor_id=user_id).filter(
- RoleAssignment.type.in_((AssignmentType.USER_PROJECT,
- AssignmentType.USER_DOMAIN))
- )
- q.delete(False)
-
- def delete_group_assignments(self, group_id):
- with sql.session_for_write() as session:
- q = session.query(RoleAssignment)
- q = q.filter_by(actor_id=group_id).filter(
- RoleAssignment.type.in_((AssignmentType.GROUP_PROJECT,
- AssignmentType.GROUP_DOMAIN))
- )
- 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)
- 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',
- 'inherited'),
- sql.Index('ix_actor_id', 'actor_id'),
- )
-
- def to_dict(self):
- """Override parent method with a simpler implementation.
-
- RoleAssignment doesn't have non-indexed 'extra' attributes, so the
- parent implementation is not applicable.
- """
- return dict(self.items())
diff --git a/keystone-moon/keystone/assignment/controllers.py b/keystone-moon/keystone/assignment/controllers.py
deleted file mode 100644
index 1b163013..00000000
--- a/keystone-moon/keystone/assignment/controllers.py
+++ /dev/null
@@ -1,972 +0,0 @@
-# Copyright 2013 Metacloud, Inc.
-# Copyright 2012 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.
-
-"""Workflow Logic the Assignment service."""
-
-import functools
-import uuid
-
-from oslo_config import cfg
-from oslo_log import log
-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.common import wsgi
-from keystone import exception
-from keystone.i18n import _
-from keystone import notifications
-
-
-CONF = cfg.CONF
-LOG = log.getLogger(__name__)
-
-
-@dependency.requires('assignment_api', 'identity_api', 'token_provider_api')
-class TenantAssignment(controller.V2Controller):
- """The V2 Project APIs that are processing assignments."""
-
- @controller.v2_auth_deprecated
- def get_projects_for_token(self, context, **kw):
- """Get valid tenants for token based on token used to authenticate.
-
- Pulls the token from the context, validates it and gets the valid
- tenants for the user in the token.
-
- Doesn't care about token scopedness.
-
- """
- token_ref = utils.get_token_ref(context)
-
- tenant_refs = (
- self.assignment_api.list_projects_for_user(token_ref.user_id))
- 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'),
- 'marker': context['query_string'].get('marker'),
- }
- return self.format_project_list(tenant_refs, **params)
-
- @controller.v2_deprecated
- def get_project_users(self, context, tenant_id, **kw):
- self.assert_admin(context)
- user_refs = []
- user_ids = self.assignment_api.list_user_ids_for_project(tenant_id)
- for user_id in user_ids:
- try:
- user_ref = self.identity_api.get_user(user_id)
- except exception.UserNotFound:
- # Log that user is missing and continue on.
- message = ("User %(user_id)s in project %(project_id)s "
- "doesn't exist.")
- LOG.debug(message,
- {'user_id': user_id, 'project_id': tenant_id})
- else:
- user_refs.append(self.v3_to_v2_user(user_ref))
- return {'users': user_refs}
-
-
-@dependency.requires('assignment_api', 'role_api')
-class Role(controller.V2Controller):
- """The Role management APIs."""
-
- @controller.v2_deprecated
- def get_role(self, context, role_id):
- self.assert_admin(context)
- return {'role': self.role_api.get_role(role_id)}
-
- @controller.v2_deprecated
- def create_role(self, context, role):
- role = self._normalize_dict(role)
- self.assert_admin(context)
-
- if 'name' not in role or not role['name']:
- msg = _('Name field is required and cannot be empty')
- raise exception.ValidationError(message=msg)
-
- 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
- initiator = notifications._get_request_audit_info(context)
- role_ref = self.role_api.create_role(role_id, role, initiator)
- return {'role': role_ref}
-
- @controller.v2_deprecated
- def delete_role(self, context, role_id):
- self.assert_admin(context)
- initiator = notifications._get_request_audit_info(context)
- self.role_api.delete_role(role_id, initiator)
-
- @controller.v2_deprecated
- def get_roles(self, context):
- self.assert_admin(context)
- return {'roles': self.role_api.list_roles()}
-
-
-@dependency.requires('assignment_api', 'resource_api', 'role_api')
-class RoleAssignmentV2(controller.V2Controller):
- """The V2 Role APIs that are processing assignments."""
-
- # COMPAT(essex-3)
- @controller.v2_deprecated
- def get_user_roles(self, context, user_id, tenant_id=None):
- """Get the roles for a user and tenant pair.
-
- Since we're trying to ignore the idea of user-only roles we're
- not implementing them in hopes that the idea will die off.
-
- """
- self.assert_admin(context)
- # NOTE(davechen): Router without project id is defined,
- # but we don't plan on implementing this.
- if tenant_id is None:
- raise exception.NotImplemented(
- message=_('User roles not supported: tenant_id required'))
- roles = self.assignment_api.get_roles_for_user_and_project(
- user_id, tenant_id)
- return {'roles': [self.role_api.get_role(x)
- for x in roles]}
-
- @controller.v2_deprecated
- def add_role_to_user(self, context, user_id, role_id, tenant_id=None):
- """Add a role to a user and tenant pair.
-
- Since we're trying to ignore the idea of user-only roles we're
- not implementing them in hopes that the idea will die off.
-
- """
- self.assert_admin(context)
- if tenant_id is None:
- 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)
-
- role_ref = self.role_api.get_role(role_id)
- return {'role': role_ref}
-
- @controller.v2_deprecated
- def remove_role_from_user(self, context, user_id, role_id, tenant_id=None):
- """Remove a role from a user and tenant pair.
-
- Since we're trying to ignore the idea of user-only roles we're
- not implementing them in hopes that the idea will die off.
-
- """
- self.assert_admin(context)
- if tenant_id is None:
- 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
- self.assignment_api.remove_role_from_user_and_project(
- user_id, tenant_id, role_id)
-
- # COMPAT(diablo): CRUD extension
- @controller.v2_deprecated
- def get_role_refs(self, context, user_id):
- """Ultimate hack to get around having to make role_refs first-class.
-
- This will basically iterate over the various roles the user has in
- all tenants the user is a member of and create fake role_refs where
- the id encodes the user-tenant-role information so we can look
- up the appropriate data when we need to delete them.
-
- """
- self.assert_admin(context)
- tenants = self.assignment_api.list_projects_for_user(user_id)
- o = []
- for tenant in tenants:
- # As a v2 call, we should limit the response to those projects in
- # the default domain.
- if tenant['domain_id'] != CONF.identity.default_domain_id:
- continue
- role_ids = self.assignment_api.get_roles_for_user_and_project(
- user_id, tenant['id'])
- for role_id in role_ids:
- ref = {'roleId': role_id,
- 'tenantId': tenant['id'],
- 'userId': user_id}
- ref['id'] = urllib.parse.urlencode(ref)
- o.append(ref)
- return {'roles': o}
-
- # COMPAT(diablo): CRUD extension
- @controller.v2_deprecated
- def create_role_ref(self, context, user_id, role):
- """This is actually used for adding a user to a tenant.
-
- In the legacy data model adding a user to a tenant required setting
- a role.
-
- """
- self.assert_admin(context)
- # TODO(termie): for now we're ignoring the actual role
- tenant_id = role.get('tenantId')
- role_id = role.get('roleId')
- self.assignment_api.add_role_to_user_and_project(
- user_id, tenant_id, role_id)
-
- role_ref = self.role_api.get_role(role_id)
- return {'role': role_ref}
-
- # COMPAT(diablo): CRUD extension
- @controller.v2_deprecated
- def delete_role_ref(self, context, user_id, role_ref_id):
- """This is actually used for deleting a user from a tenant.
-
- In the legacy data model removing a user from a tenant required
- deleting a role.
-
- To emulate this, we encode the tenant and role in the role_ref_id,
- and if this happens to be the last role for the user-tenant pair,
- we remove the user from the tenant.
-
- """
- self.assert_admin(context)
- # TODO(termie): for now we're ignoring the actual role
- role_ref_ref = urllib.parse.parse_qs(role_ref_id)
- tenant_id = role_ref_ref.get('tenantId')[0]
- role_id = role_ref_ref.get('roleId')[0]
- self.assignment_api.remove_role_from_user_and_project(
- user_id, tenant_id, role_id)
-
-
-@dependency.requires('assignment_api', 'resource_api')
-class ProjectAssignmentV3(controller.V3Controller):
- """The V3 Project APIs that are processing assignments."""
-
- collection_name = 'projects'
- member_name = 'project'
-
- def __init__(self):
- super(ProjectAssignmentV3, self).__init__()
- self.get_member_from_driver = self.resource_api.get_project
-
- @controller.filterprotected('domain_id', 'enabled', 'name')
- def list_user_projects(self, context, filters, user_id):
- hints = ProjectAssignmentV3.build_driver_hints(context, filters)
- refs = self.assignment_api.list_projects_for_user(user_id,
- hints=hints)
- return ProjectAssignmentV3.wrap_collection(context, refs, hints=hints)
-
-
-@dependency.requires('role_api')
-class RoleV3(controller.V3Controller):
- """The V3 Role CRUD APIs.
-
- To ease complexity (and hence risk) in writing the policy rules for the
- role APIs, we create separate policy actions for roles that are domain
- specific, as opposed to those that are global. In order to achieve this
- each of the role API methods has a wrapper method that checks to see if the
- role is global or domain specific.
-
- NOTE (henry-nash): If this separate global vs scoped policy action pattern
- becomes repeated for other entities, we should consider encapsulating this
- into a specialized router class.
-
- """
-
- collection_name = 'roles'
- member_name = 'role'
-
- def __init__(self):
- super(RoleV3, self).__init__()
- self.get_member_from_driver = self.role_api.get_role
-
- def _is_domain_role(self, role):
- return role.get('domain_id') is not None
-
- def _is_domain_role_target(self, role_id):
- try:
- role = self.role_api.get_role(role_id)
- except exception.RoleNotFound:
- # We hide this error since we have not yet carried out a policy
- # check - and it maybe that the caller isn't authorized to make
- # this call. If so, we want that error to be raised instead.
- return False
- return self._is_domain_role(role)
-
- def create_role_wrapper(self, context, role):
- if self._is_domain_role(role):
- return self.create_domain_role(context, role=role)
- else:
- return self.create_role(context, role=role)
-
- @controller.protected()
- @validation.validated(schema.role_create, 'role')
- def create_role(self, context, role):
- return self._create_role(context, role)
-
- @controller.protected()
- @validation.validated(schema.role_create, 'role')
- def create_domain_role(self, context, role):
- return self._create_role(context, role)
-
- def list_roles_wrapper(self, context):
- # If there is no domain_id filter defined, then we only want to return
- # global roles, so we set the domain_id filter to None.
- params = context['query_string']
- if 'domain_id' not in params:
- context['query_string']['domain_id'] = None
-
- if context['query_string']['domain_id'] is not None:
- return self.list_domain_roles(context)
- else:
- return self.list_roles(context)
-
- @controller.filterprotected('name', 'domain_id')
- def list_roles(self, context, filters):
- return self._list_roles(context, filters)
-
- @controller.filterprotected('name', 'domain_id')
- def list_domain_roles(self, context, filters):
- return self._list_roles(context, filters)
-
- def get_role_wrapper(self, context, role_id):
- if self._is_domain_role_target(role_id):
- return self.get_domain_role(context, role_id=role_id)
- else:
- return self.get_role(context, role_id=role_id)
-
- @controller.protected()
- def get_role(self, context, role_id):
- return self._get_role(context, role_id)
-
- @controller.protected()
- def get_domain_role(self, context, role_id):
- return self._get_role(context, role_id)
-
- def update_role_wrapper(self, context, role_id, role):
- # Since we don't allow you change whether a role is global or domain
- # specific, we can ignore the new update attributes and just look at
- # the existing role.
- if self._is_domain_role_target(role_id):
- return self.update_domain_role(
- context, role_id=role_id, role=role)
- else:
- return self.update_role(context, role_id=role_id, role=role)
-
- @controller.protected()
- @validation.validated(schema.role_update, 'role')
- def update_role(self, context, role_id, role):
- return self._update_role(context, role_id, role)
-
- @controller.protected()
- @validation.validated(schema.role_update, 'role')
- def update_domain_role(self, context, role_id, role):
- return self._update_role(context, role_id, role)
-
- def delete_role_wrapper(self, context, role_id):
- if self._is_domain_role_target(role_id):
- return self.delete_domain_role(context, role_id=role_id)
- else:
- return self.delete_role(context, role_id=role_id)
-
- @controller.protected()
- def delete_role(self, context, role_id):
- return self._delete_role(context, role_id)
-
- @controller.protected()
- def delete_domain_role(self, context, role_id):
- return self._delete_role(context, role_id)
-
- def _create_role(self, context, 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)
-
- def _list_roles(self, context, filters):
- hints = RoleV3.build_driver_hints(context, filters)
- refs = self.role_api.list_roles(
- hints=hints)
- return RoleV3.wrap_collection(context, refs, hints=hints)
-
- def _get_role(self, context, role_id):
- ref = self.role_api.get_role(role_id)
- return RoleV3.wrap_member(context, ref)
-
- def _update_role(self, context, role_id, role):
- self._require_matching_id(role_id, role)
- initiator = notifications._get_request_audit_info(context)
- ref = self.role_api.update_role(role_id, role, initiator)
- return RoleV3.wrap_member(context, ref)
-
- def _delete_role(self, context, role_id):
- initiator = notifications._get_request_audit_info(context)
- self.role_api.delete_role(role_id, initiator)
-
-
-@dependency.requires('role_api')
-class ImpliedRolesV3(controller.V3Controller):
- """The V3 ImpliedRoles CRD APIs. There is no Update."""
-
- def _prior_role_stanza(self, endpoint, prior_role_id, prior_role_name):
- return {
- "id": prior_role_id,
- "links": {
- "self": endpoint + "/v3/roles/" + prior_role_id
- },
- "name": prior_role_name
- }
-
- def _implied_role_stanza(self, endpoint, implied_role):
- implied_id = implied_role['id']
- implied_response = {
- "id": implied_id,
- "links": {
- "self": endpoint + "/v3/roles/" + implied_id
- },
- "name": implied_role['name']
- }
- return implied_response
-
- def _populate_prior_role_response(self, endpoint, prior_id):
- prior_role = self.role_api.get_role(prior_id)
- response = {
- "role_inference": {
- "prior_role": self._prior_role_stanza(
- endpoint, prior_id, prior_role['name'])
- }
- }
- return response
-
- def _populate_implied_roles_response(self, endpoint,
- prior_id, implied_ids):
- response = self._populate_prior_role_response(endpoint, prior_id)
- response["role_inference"]['implies'] = []
- for implied_id in implied_ids:
- implied_role = self.role_api.get_role(implied_id)
- implied_response = self._implied_role_stanza(
- endpoint, implied_role)
- response["role_inference"]['implies'].append(implied_response)
- return response
-
- def _populate_implied_role_response(self, endpoint, prior_id, implied_id):
- response = self._populate_prior_role_response(endpoint, prior_id)
- implied_role = self.role_api.get_role(implied_id)
- stanza = self._implied_role_stanza(endpoint, implied_role)
- response["role_inference"]['implies'] = stanza
- return response
-
- @controller.protected()
- def get_implied_role(self, context, prior_role_id, implied_role_id):
- ref = self.role_api.get_implied_role(prior_role_id, implied_role_id)
-
- prior_id = ref['prior_role_id']
- implied_id = ref['implied_role_id']
- endpoint = super(controller.V3Controller, ImpliedRolesV3).base_url(
- context, 'public')
- response = self._populate_implied_role_response(
- endpoint, prior_id, implied_id)
- return response
-
- @controller.protected()
- def check_implied_role(self, context, prior_role_id, implied_role_id):
- self.role_api.get_implied_role(prior_role_id, implied_role_id)
-
- @controller.protected()
- def create_implied_role(self, context, prior_role_id, implied_role_id):
- self.role_api.create_implied_role(prior_role_id, implied_role_id)
- return wsgi.render_response(
- self.get_implied_role(context, prior_role_id, implied_role_id),
- status=(201, 'Created'))
-
- @controller.protected()
- def delete_implied_role(self, context, prior_role_id, implied_role_id):
- self.role_api.delete_implied_role(prior_role_id, implied_role_id)
-
- @controller.protected()
- def list_implied_roles(self, context, prior_role_id):
- ref = self.role_api.list_implied_roles(prior_role_id)
- implied_ids = [r['implied_role_id'] for r in ref]
- endpoint = super(controller.V3Controller, ImpliedRolesV3).base_url(
- context, 'public')
-
- results = self._populate_implied_roles_response(
- endpoint, prior_role_id, implied_ids)
-
- return results
-
- @controller.protected()
- def list_role_inference_rules(self, context):
- refs = self.role_api.list_role_inference_rules()
- role_dict = {role_ref['id']: role_ref
- for role_ref in self.role_api.list_roles()}
-
- rules = dict()
- endpoint = super(controller.V3Controller, ImpliedRolesV3).base_url(
- context, 'public')
-
- for ref in refs:
- implied_role_id = ref['implied_role_id']
- prior_role_id = ref['prior_role_id']
- implied = rules.get(prior_role_id, [])
- implied.append(self._implied_role_stanza(
- endpoint, role_dict[implied_role_id]))
- rules[prior_role_id] = implied
-
- inferences = []
- for prior_id, implied in rules.items():
- prior_response = self._prior_role_stanza(
- endpoint, prior_id, role_dict[prior_id]['name'])
- inferences.append({'prior_role': prior_response,
- 'implies': implied})
- results = {'role_inferences': inferences}
- return results
-
-
-@dependency.requires('assignment_api', 'identity_api', 'resource_api',
- 'role_api')
-class GrantAssignmentV3(controller.V3Controller):
- """The V3 Grant Assignment APIs."""
-
- collection_name = 'roles'
- member_name = 'role'
-
- def __init__(self):
- super(GrantAssignmentV3, self).__init__()
- self.get_member_from_driver = self.role_api.get_role
-
- def _require_domain_xor_project(self, domain_id, project_id):
- if domain_id and project_id:
- msg = _('Specify a domain or project, not both')
- raise exception.ValidationError(msg)
- if not domain_id and not project_id:
- msg = _('Specify one of domain or project')
- raise exception.ValidationError(msg)
-
- def _require_user_xor_group(self, user_id, group_id):
- if user_id and group_id:
- msg = _('Specify a user or group, not both')
- raise exception.ValidationError(msg)
- if not user_id and not group_id:
- msg = _('Specify one of user or group')
- raise exception.ValidationError(msg)
-
- def _check_if_inherited(self, context):
- return (CONF.os_inherit.enabled and
- context['path'].startswith('/OS-INHERIT') and
- context['path'].endswith('/inherited_to_projects'))
-
- def _check_grant_protection(self, context, protection, role_id=None,
- user_id=None, group_id=None,
- domain_id=None, project_id=None,
- allow_no_user=False):
- """Check protection for role grant APIs.
-
- The policy rule might want to inspect attributes of any of the entities
- involved in the grant. So we get these and pass them to the
- check_protection() handler in the controller.
-
- """
- ref = {}
- if role_id:
- ref['role'] = self.role_api.get_role(role_id)
- if user_id:
- try:
- ref['user'] = self.identity_api.get_user(user_id)
- except exception.UserNotFound:
- if not allow_no_user:
- raise
- else:
- ref['group'] = self.identity_api.get_group(group_id)
-
- if domain_id:
- ref['domain'] = self.resource_api.get_domain(domain_id)
- else:
- ref['project'] = self.resource_api.get_project(project_id)
-
- self.check_protection(context, protection, ref)
-
- @controller.protected(callback=_check_grant_protection)
- def create_grant(self, context, role_id, user_id=None,
- group_id=None, domain_id=None, project_id=None):
- """Grants a role to a user or group on either a domain or project."""
- self._require_domain_xor_project(domain_id, project_id)
- self._require_user_xor_group(user_id, group_id)
-
- self.assignment_api.create_grant(
- role_id, user_id, group_id, domain_id, project_id,
- self._check_if_inherited(context), context)
-
- @controller.protected(callback=_check_grant_protection)
- def list_grants(self, context, user_id=None,
- group_id=None, domain_id=None, project_id=None):
- """Lists roles granted to user/group on either a domain or project."""
- self._require_domain_xor_project(domain_id, project_id)
- self._require_user_xor_group(user_id, group_id)
-
- refs = self.assignment_api.list_grants(
- user_id, group_id, domain_id, project_id,
- self._check_if_inherited(context))
- return GrantAssignmentV3.wrap_collection(context, refs)
-
- @controller.protected(callback=_check_grant_protection)
- def check_grant(self, context, role_id, user_id=None,
- group_id=None, domain_id=None, project_id=None):
- """Checks if a role has been granted on either a domain or project."""
- self._require_domain_xor_project(domain_id, project_id)
- self._require_user_xor_group(user_id, group_id)
-
- self.assignment_api.get_grant(
- role_id, user_id, group_id, domain_id, project_id,
- self._check_if_inherited(context))
-
- # NOTE(lbragstad): This will allow users to clean up role assignments
- # from the backend in the event the user was removed prior to the role
- # assignment being removed.
- @controller.protected(callback=functools.partial(
- _check_grant_protection, allow_no_user=True))
- def revoke_grant(self, context, role_id, user_id=None,
- group_id=None, domain_id=None, project_id=None):
- """Revokes a role from user/group on either a domain or project."""
- self._require_domain_xor_project(domain_id, project_id)
- self._require_user_xor_group(user_id, group_id)
-
- self.assignment_api.delete_grant(
- role_id, user_id, group_id, domain_id, project_id,
- self._check_if_inherited(context), context)
-
-
-@dependency.requires('assignment_api', 'identity_api', 'resource_api')
-class RoleAssignmentV3(controller.V3Controller):
- """The V3 Role Assignment APIs, really just list_role_assignment()."""
-
- # TODO(henry-nash): The current implementation does not provide a full
- # first class entity for role-assignment. There is no role_assignment_id
- # and only the list_role_assignment call is supported. Further, since it
- # is not a first class entity, the links for the individual entities
- # reference the individual role grant APIs.
-
- collection_name = 'role_assignments'
- member_name = 'role_assignment'
-
- @classmethod
- def wrap_member(cls, context, ref):
- # NOTE(henry-nash): Since we are not yet a true collection, we override
- # the wrapper as have already included the links in the entities
- pass
-
- def _format_entity(self, context, entity):
- """Format an assignment entity for API response.
-
- The driver layer returns entities as dicts containing the ids of the
- 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': project_id,
- 'role_id': role_id,
- 'indirect': {'group_id': group_id}}
-
- or, for a project inherited role:
-
- {'user_id': user_id,
- 'project_id': project_id,
- 'role_id': role_id,
- 'indirect': {'project_id': parent_id}}
-
- or, for a role that was implied by a prior role:
-
- {'user_id': user_id,
- 'project_id': project_id,
- 'role_id': role_id,
- 'indirect': {'role_id': prior role_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:
-
- {
- 'user': {
- {'id': user_id}
- },
- 'scope': {
- 'project': {
- {'id': project_id}
- },
- 'OS-INHERIT:inherited_to': 'projects'
- },
- 'role': {
- {'id': role_id}
- },
- 'links': {
- 'assignment': '/OS-INHERIT/projects/parent_id/users/user_id/'
- 'roles/role_id/inherited_to_projects'
- }
- }
-
- """
- formatted_entity = {'links': {}}
- inherited_assignment = entity.get('inherited_to_projects')
-
- if 'project_id' in entity:
- if 'project_name' in entity:
- formatted_entity['scope'] = {'project': {
- 'id': entity['project_id'],
- 'name': entity['project_name'],
- 'domain': {'id': entity['project_domain_id'],
- 'name': entity['project_domain_name']}}}
- else:
- formatted_entity['scope'] = {
- 'project': {'id': entity['project_id']}}
-
- 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:
- if 'domain_name' in entity:
- formatted_entity['scope'] = {
- 'domain': {'id': entity['domain_id'],
- 'name': entity['domain_name']}}
- else:
- formatted_entity['scope'] = {
- 'domain': {'id': entity['domain_id']}}
- formatted_link = '/domains/%s' % entity['domain_id']
-
- if 'user_id' in entity:
- if 'user_name' in entity:
- formatted_entity['user'] = {
- 'id': entity['user_id'],
- 'name': entity['user_name'],
- 'domain': {'id': entity['user_domain_id'],
- 'name': entity['user_domain_name']}}
- else:
- formatted_entity['user'] = {'id': entity['user_id']}
- 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:
- if 'group_name' in entity:
- formatted_entity['group'] = {
- 'id': entity['group_id'],
- 'name': entity['group_name'],
- 'domain': {'id': entity['group_domain_id'],
- 'name': entity['group_domain_name']}}
- else:
- formatted_entity['group'] = {'id': entity['group_id']}
- formatted_link += '/groups/%s' % entity['group_id']
-
- if 'role_name' in entity:
- formatted_entity['role'] = {'id': entity['role_id'],
- 'name': entity['role_name']}
- else:
- formatted_entity['role'] = {'id': entity['role_id']}
- prior_role_link = ''
- if 'role_id' in entity.get('indirect', {}):
- formatted_link += '/roles/%s' % entity['indirect']['role_id']
- prior_role_link = (
- '/prior_role/%(prior)s/implies/%(implied)s' % {
- 'prior': entity['role_id'],
- 'implied': entity['indirect']['role_id']
- })
- else:
- formatted_link += '/roles/%s' % entity['role_id']
-
- if inherited_assignment:
- formatted_entity['scope']['OS-INHERIT:inherited_to'] = (
- 'projects')
- formatted_link = ('/OS-INHERIT%s/inherited_to_projects' %
- formatted_link)
-
- formatted_entity['links']['assignment'] = self.base_url(context,
- formatted_link)
- if prior_role_link:
- formatted_entity['links']['prior_role'] = (
- self.base_url(context, prior_role_link))
-
- return formatted_entity
-
- def _assert_effective_filters(self, inherited, group, domain):
- """Assert that useless filter combinations are avoided.
-
- In effective mode, the following filter combinations are useless, since
- they would always return an empty list of role assignments:
- - group id, since no group assignment is returned in effective mode;
- - domain id and inherited, since no domain inherited assignment is
- returned in effective mode.
-
- """
- if group:
- msg = _('Combining effective and group filter will always '
- 'result in an empty list.')
- raise exception.ValidationError(msg)
-
- if inherited and domain:
- msg = _('Combining effective, domain and inherited filters will '
- 'always result in an empty list.')
- raise exception.ValidationError(msg)
-
- def _assert_domain_nand_project(self, domain_id, project_id):
- if domain_id and project_id:
- msg = _('Specify a domain or project, not both')
- raise exception.ValidationError(msg)
-
- def _assert_user_nand_group(self, user_id, group_id):
- if user_id and group_id:
- msg = _('Specify a user or group, not both')
- raise exception.ValidationError(msg)
-
- def _list_role_assignments(self, context, filters, include_subtree=False):
- """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.
-
- 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']))
- include_names = ('include_names' in params and
- self.query_filter_is_true(params['include_names']))
-
- if 'scope.OS-INHERIT:inherited_to' in params:
- inherited = (
- params['scope.OS-INHERIT:inherited_to'] == 'projects')
- else:
- # None means querying both inherited and direct assignments
- inherited = None
-
- self._assert_domain_nand_project(params.get('scope.domain.id'),
- params.get('scope.project.id'))
- self._assert_user_nand_group(params.get('user.id'),
- params.get('group.id'))
-
- if effective:
- self._assert_effective_filters(inherited=inherited,
- group=params.get('group.id'),
- domain=params.get(
- 'scope.domain.id'))
-
- 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'),
- include_subtree=include_subtree,
- inherited=inherited, effective=effective,
- include_names=include_names)
-
- formatted_refs = [self._format_entity(context, ref) for ref in refs]
-
- return self.wrap_collection(context, formatted_refs)
-
- @controller.filterprotected('group.id', 'role.id',
- 'scope.domain.id', 'scope.project.id',
- 'scope.OS-INHERIT:inherited_to', 'user.id')
- def list_role_assignments(self, context, filters):
- return self._list_role_assignments(context, filters)
-
- def _check_list_tree_protection(self, context, protection_info):
- """Check protection for list assignment for tree API.
-
- The policy rule might want to inspect the domain of any project filter
- so if one is defined, then load the project ref and pass it to the
- check protection method.
-
- """
- ref = {}
- for filter, value in protection_info['filter_attr'].items():
- if filter == 'scope.project.id' and value:
- ref['project'] = self.resource_api.get_project(value)
-
- self.check_protection(context, protection_info, ref)
-
- @controller.filterprotected('group.id', 'role.id',
- 'scope.domain.id', 'scope.project.id',
- 'scope.OS-INHERIT:inherited_to', 'user.id',
- callback=_check_list_tree_protection)
- def list_role_assignments_for_tree(self, context, filters):
- if not context['query_string'].get('scope.project.id'):
- msg = _('scope.project.id must be specified if include_subtree '
- 'is also specified')
- raise exception.ValidationError(message=msg)
- return self._list_role_assignments(context, filters,
- include_subtree=True)
-
- def list_role_assignments_wrapper(self, context):
- """Main entry point from router for list role assignments.
-
- Since we want different policy file rules to be applicable based on
- whether there the include_subtree query parameter is part of the API
- call, this method checks for this and then calls the appropriate
- protected entry point.
-
- """
- params = context['query_string']
- if 'include_subtree' in params and (
- self.query_filter_is_true(params['include_subtree'])):
- return self.list_role_assignments_for_tree(context)
- else:
- return self.list_role_assignments(context)
diff --git a/keystone-moon/keystone/assignment/core.py b/keystone-moon/keystone/assignment/core.py
deleted file mode 100644
index 05368fbf..00000000
--- a/keystone-moon/keystone/assignment/core.py
+++ /dev/null
@@ -1,1790 +0,0 @@
-# Copyright 2012 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.
-
-"""Main entry point into the Assignment service."""
-
-import abc
-import copy
-
-from oslo_cache import core as oslo_cache
-from oslo_config import cfg
-from oslo_log import log
-from oslo_log import versionutils
-import six
-
-from keystone.common import cache
-from keystone.common import dependency
-from keystone.common import driver_hints
-from keystone.common import manager
-from keystone import exception
-from keystone.i18n import _
-from keystone.i18n import _LI, _LE, _LW
-from keystone import notifications
-
-
-CONF = cfg.CONF
-LOG = log.getLogger(__name__)
-
-# This is a general cache region for assignment administration (CRUD
-# operations).
-MEMOIZE = cache.get_memoization_decorator(group='role')
-
-# This builds a discrete cache region dedicated to role assignments computed
-# for a given user + project/domain pair. Any write operation to add or remove
-# any role assignment should invalidate this entire cache region.
-COMPUTED_ASSIGNMENTS_REGION = oslo_cache.create_region()
-MEMOIZE_COMPUTED_ASSIGNMENTS = cache.get_memoization_decorator(
- group='role',
- region=COMPUTED_ASSIGNMENTS_REGION)
-
-
-@notifications.listener
-@dependency.provider('assignment_api')
-@dependency.requires('credential_api', 'identity_api', 'resource_api',
- 'revoke_api', 'role_api')
-class Manager(manager.Manager):
- """Default pivot point for the Assignment backend.
-
- See :class:`keystone.common.manager.Manager` for more details on how this
- 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'
-
- def __init__(self):
- assignment_driver = CONF.assignment.driver
- # If there is no explicit assignment driver specified, we let the
- # identity driver tell us what to use. This is for backward
- # compatibility reasons from the time when identity, resource and
- # assignment were all part of identity.
- if assignment_driver is None:
- msg = _('Use of the identity driver config to automatically '
- 'configure the same assignment driver has been '
- 'deprecated, in the "O" release, the assignment driver '
- 'will need to be expicitly configured if different '
- 'than the default (SQL).')
- versionutils.report_deprecated_feature(LOG, msg)
- try:
- identity_driver = dependency.get_provider(
- 'identity_api').driver
- assignment_driver = identity_driver.default_assignment_driver()
- except ValueError:
- msg = _('Attempted automatic driver selection for assignment '
- 'based upon [identity]\driver option failed since '
- 'driver %s is not found. Set [assignment]/driver to '
- 'a valid driver in keystone config.')
- LOG.critical(msg)
- raise exception.KeystoneConfigurationError(msg)
- super(Manager, self).__init__(assignment_driver)
-
- # Make sure it is a driver version we support, and if it is a legacy
- # driver, then wrap it.
- if isinstance(self.driver, AssignmentDriverV8):
- self.driver = V9AssignmentWrapperForV8Driver(self.driver)
- elif not isinstance(self.driver, AssignmentDriverV9):
- raise exception.UnsupportedDriverVersion(driver=assignment_driver)
-
- self.event_callbacks = {
- notifications.ACTIONS.deleted: {
- 'domain': [self._delete_domain_assignments],
- },
- }
-
- def _delete_domain_assignments(self, service, resource_type, operations,
- payload):
- domain_id = payload['resource_info']
- self.driver.delete_domain_assignments(domain_id)
-
- def _get_group_ids_for_user_id(self, user_id):
- # TODO(morganfainberg): Implement a way to get only group_ids
- # instead of the more expensive to_dict() call for each record.
- return [x['id'] for
- x in self.identity_api.list_groups_for_user(user_id)]
-
- def list_user_ids_for_project(self, tenant_id):
- self.resource_api.get_project(tenant_id)
- assignment_list = self.list_role_assignments(
- project_id=tenant_id, effective=True)
- # Use set() to process the list to remove any duplicates
- return list(set([x['user_id'] for x in assignment_list]))
-
- def _list_parent_ids_of_project(self, project_id):
- if CONF.os_inherit.enabled:
- return [x['id'] for x in (
- self.resource_api.list_project_parents(project_id))]
- else:
- return []
-
- @MEMOIZE_COMPUTED_ASSIGNMENTS
- def get_roles_for_user_and_project(self, user_id, tenant_id):
- """Get the roles associated with a user within given project.
-
- This includes roles directly assigned to the user on the
- project, as well as those by virtue of group membership or
- inheritance.
-
- :returns: a list of role ids.
- :raises keystone.exception.ProjectNotFound: If the project doesn't
- exist.
-
- """
- self.resource_api.get_project(tenant_id)
- assignment_list = self.list_role_assignments(
- user_id=user_id, project_id=tenant_id, effective=True)
- # Use set() to process the list to remove any duplicates
- return list(set([x['role_id'] for x in assignment_list]))
-
- @MEMOIZE_COMPUTED_ASSIGNMENTS
- def get_roles_for_user_and_domain(self, user_id, domain_id):
- """Get the roles associated with a user within given domain.
-
- :returns: a list of role ids.
- :raises keystone.exception.DomainNotFound: If the domain doesn't exist.
-
- """
- self.resource_api.get_domain(domain_id)
- assignment_list = self.list_role_assignments(
- user_id=user_id, domain_id=domain_id, effective=True)
- # Use set() to process the list to remove any duplicates
- return list(set([x['role_id'] for x in assignment_list]))
-
- def get_roles_for_groups(self, group_ids, project_id=None, domain_id=None):
- """Get a list of roles for this group on domain and/or project."""
- if project_id is not None:
- self.resource_api.get_project(project_id)
- assignment_list = self.list_role_assignments(
- source_from_group_ids=group_ids, project_id=project_id,
- effective=True)
- elif domain_id is not None:
- assignment_list = self.list_role_assignments(
- source_from_group_ids=group_ids, domain_id=domain_id,
- effective=True)
- else:
- raise AttributeError(_("Must specify either domain or project"))
-
- role_ids = list(set([x['role_id'] for x in assignment_list]))
- return self.role_api.list_roles_from_ids(role_ids)
-
- def add_user_to_project(self, tenant_id, user_id):
- """Add user to a tenant by creating a default role relationship.
-
- :raises keystone.exception.ProjectNotFound: If the project doesn't
- exist.
- :raises keystone.exception.UserNotFound: If the user doesn't exist.
-
- """
- self.resource_api.get_project(tenant_id)
- try:
- self.role_api.get_role(CONF.member_role_id)
- self.driver.add_role_to_user_and_project(
- user_id,
- tenant_id,
- CONF.member_role_id)
- except exception.RoleNotFound:
- LOG.info(_LI("Creating the default role %s "
- "because it does not exist."),
- CONF.member_role_id)
- role = {'id': CONF.member_role_id,
- 'name': CONF.member_role_name}
- try:
- self.role_api.create_role(CONF.member_role_id, role)
- except exception.Conflict:
- LOG.info(_LI("Creating the default role %s failed because it "
- "was already created"),
- CONF.member_role_id)
- # now that default role exists, the add should succeed
- self.driver.add_role_to_user_and_project(
- user_id,
- tenant_id,
- CONF.member_role_id)
- COMPUTED_ASSIGNMENTS_REGION.invalidate()
-
- @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, 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)
- COMPUTED_ASSIGNMENTS_REGION.invalidate()
-
- def remove_user_from_project(self, tenant_id, user_id):
- """Remove user from a tenant
-
- :raises keystone.exception.ProjectNotFound: If the project doesn't
- exist.
- :raises keystone.exception.UserNotFound: If the user doesn't exist.
-
- """
- roles = self.get_roles_for_user_and_project(user_id, tenant_id)
- if not roles:
- raise exception.NotFound(tenant_id)
- for role_id in roles:
- try:
- self.driver.remove_role_from_user_and_project(user_id,
- tenant_id,
- role_id)
- self.revoke_api.revoke_by_grant(role_id, user_id=user_id,
- project_id=tenant_id)
-
- except exception.RoleNotFound:
- LOG.debug("Removing role %s failed because it does not exist.",
- role_id)
- COMPUTED_ASSIGNMENTS_REGION.invalidate()
-
- # TODO(henry-nash): We might want to consider list limiting this at some
- # point in the future.
- def list_projects_for_user(self, user_id, hints=None):
- assignment_list = self.list_role_assignments(
- user_id=user_id, effective=True)
- # Use set() to process the list to remove any duplicates
- project_ids = list(set([x['project_id'] for x in assignment_list
- if x.get('project_id')]))
- return self.resource_api.list_projects_from_ids(list(project_ids))
-
- # TODO(henry-nash): We might want to consider list limiting this at some
- # point in the future.
- def list_domains_for_user(self, user_id, hints=None):
- assignment_list = self.list_role_assignments(
- user_id=user_id, effective=True)
- # Use set() to process the list to remove any duplicates
- domain_ids = list(set([x['domain_id'] for x in assignment_list
- if x.get('domain_id')]))
- return self.resource_api.list_domains_from_ids(domain_ids)
-
- def list_domains_for_groups(self, group_ids):
- assignment_list = self.list_role_assignments(
- source_from_group_ids=group_ids, effective=True)
- domain_ids = list(set([x['domain_id'] for x in assignment_list
- if x.get('domain_id')]))
- return self.resource_api.list_domains_from_ids(domain_ids)
-
- def list_projects_for_groups(self, group_ids):
- assignment_list = self.list_role_assignments(
- source_from_group_ids=group_ids, effective=True)
- project_ids = list(set([x['project_id'] for x in assignment_list
- if x.get('project_id')]))
- return self.resource_api.list_projects_from_ids(project_ids)
-
- @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)
- if project_id:
- self._emit_invalidate_grant_token_persistence(user_id, project_id)
- else:
- self.identity_api.emit_invalidate_user_token_persistence(user_id)
- self.revoke_api.revoke_by_grant(role_id, user_id=user_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)
- COMPUTED_ASSIGNMENTS_REGION.invalidate()
-
- def _emit_invalidate_user_token_persistence(self, user_id):
- self.identity_api.emit_invalidate_user_token_persistence(user_id)
-
- # NOTE(lbragstad): The previous notification decorator behavior didn't
- # send the notification unless the operation was successful. We
- # maintain that behavior here by calling to the notification module
- # after the call to emit invalid user tokens.
- notifications.Audit.internal(
- notifications.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,
- inherited_to_projects=False, context=None):
- self.role_api.get_role(role_id)
- if domain_id:
- self.resource_api.get_domain(domain_id)
- if project_id:
- self.resource_api.get_project(project_id)
- self.driver.create_grant(role_id, user_id, group_id, domain_id,
- project_id, inherited_to_projects)
- COMPUTED_ASSIGNMENTS_REGION.invalidate()
-
- def get_grant(self, role_id, user_id=None, group_id=None,
- domain_id=None, project_id=None,
- inherited_to_projects=False):
- role_ref = self.role_api.get_role(role_id)
- if domain_id:
- self.resource_api.get_domain(domain_id)
- if project_id:
- self.resource_api.get_project(project_id)
- self.check_grant_role_id(
- role_id, user_id, group_id, domain_id, project_id,
- inherited_to_projects)
- return role_ref
-
- def list_grants(self, user_id=None, group_id=None,
- domain_id=None, project_id=None,
- inherited_to_projects=False):
- if domain_id:
- self.resource_api.get_domain(domain_id)
- if project_id:
- self.resource_api.get_project(project_id)
- 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):
- if group_id is None:
- self.revoke_api.revoke_by_grant(user_id=user_id,
- 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:
- # 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)
-
- # TODO(henry-nash): While having the call to get_role here mimics the
- # previous behavior (when it was buried inside the driver delete call),
- # this seems an odd place to have this check, given what we have
- # already done so far in this method. See Bug #1406776.
- self.role_api.get_role(role_id)
-
- if domain_id:
- self.resource_api.get_domain(domain_id)
- if project_id:
- self.resource_api.get_project(project_id)
- self.driver.delete_grant(role_id, user_id, group_id, domain_id,
- project_id, inherited_to_projects)
- COMPUTED_ASSIGNMENTS_REGION.invalidate()
-
- # 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,
- subtree_ids=None, expand_groups=True):
- """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.
-
- If project_id is specified and subtree_ids is None, then this
- indicates that we are only interested in that one project. If
- subtree_ids is not None, then this is an indicator that any
- inherited assignments need to be expanded down the tree. The
- actual subtree_ids don't need to be used as a filter here, since we
- already ensured only those assignments that could affect them
- were passed to this method.
-
- If expand_groups is True then we expand groups out to a list of
- assignments, one for each member of that group.
-
- """
- 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, subtree_ids,
- expand_groups):
- """Expands inherited role assignments.
-
- If expand_groups is True and 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
- expand_groups is False, then return a group assignment on an
- inherited target.
-
- If this is a user role assignment on a specific target (i.e.
- project_id is specified, but subtree_ids is None) then simply
- format this as a single assignment (since we are effectively
- filtering on project_id). If however, project_id is None or
- subtree_ids is not None, then replace this one assignment with 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 and we are filtering by
- # project(s), we are only going to apply the assignment to the
- # relevant project(s)
- project_ids = [project_id]
- if subtree_ids:
- project_ids += subtree_ids
- # If this is a domain inherited assignment, then we know
- # that all the project_ids will get this assignment. If
- # it's a project inherited assignment, and the assignment
- # point is an ancestor of project_id, then we know that
- # again all the project_ids will get the assignment. If,
- # however, the assignment point is within the subtree,
- # then only a partial tree will get the assignment.
- if ref.get('project_id'):
- if ref['project_id'] in project_ids:
- project_ids = (
- [x['id'] for x in
- self.resource_api.list_projects_in_subtree(
- ref['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 its 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:
- if expand_groups:
- # Expand role assignment to all group members on any
- # inherited target of any of the 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:
- # Just place the group assignment on any inherited target
- # of any of the projects
- 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, subtree_ids, expand_groups)
- elif 'group_id' in ref and expand_groups:
- return expand_group_assignment(ref, user_id)
- return [ref]
-
- def add_implied_roles(self, role_refs):
- """Expand out implied roles.
-
- The role_refs passed in have had all inheritance and group assignments
- expanded out. We now need to look at the role_id in each ref and see
- if it is a prior role for some implied roles. If it is, then we need to
- duplicate that ref, one for each implied role. We store the prior role
- in the indirect dict that is part of such a duplicated ref, so that a
- caller can determine where the assignment came from.
-
- """
- def _make_implied_ref_copy(prior_ref, implied_role_id):
- # Create a ref for an implied role from the ref of a prior role,
- # setting the new role_id to be the implied role and the indirect
- # role_id to be the prior role
- implied_ref = copy.deepcopy(prior_ref)
- implied_ref['role_id'] = implied_role_id
- indirect = implied_ref.setdefault('indirect', {})
- indirect['role_id'] = prior_ref['role_id']
- return implied_ref
-
- if not CONF.token.infer_roles:
- return role_refs
- try:
- implied_roles_cache = {}
- role_refs_to_check = list(role_refs)
- ref_results = list(role_refs)
- checked_role_refs = list()
- while(role_refs_to_check):
- next_ref = role_refs_to_check.pop()
- checked_role_refs.append(next_ref)
- next_role_id = next_ref['role_id']
- if next_role_id in implied_roles_cache:
- implied_roles = implied_roles_cache[next_role_id]
- else:
- implied_roles = (
- self.role_api.list_implied_roles(next_role_id))
- implied_roles_cache[next_role_id] = implied_roles
- for implied_role in implied_roles:
- implied_ref = (
- _make_implied_ref_copy(
- next_ref, implied_role['implied_role_id']))
- if implied_ref in checked_role_refs:
- msg = _LE('Circular reference found '
- 'role inference rules - %(prior_role_id)s.')
- LOG.error(msg, {'prior_role_id': next_ref['role_id']})
- else:
- ref_results.append(implied_ref)
- role_refs_to_check.append(implied_ref)
- except exception.NotImplemented:
- LOG.error('Role driver does not support implied roles.')
-
- return ref_results
-
- def _filter_by_role_id(self, role_id, ref_results):
- # if we arrive here, we need to filer by role_id.
- filter_results = []
- for ref in ref_results:
- if ref['role_id'] == role_id:
- filter_results.append(ref)
- return filter_results
-
- def _strip_domain_roles(self, role_refs):
- """Post process assignment list for domain roles.
-
- Domain roles are only designed to do the job of inferring other roles
- and since that has been done before this method is called, we need to
- remove any assignments that include a domain role.
-
- """
- def _role_is_global(role_id):
- ref = self.role_api.get_role(role_id)
- return (ref['domain_id'] is None)
-
- filter_results = []
- for ref in role_refs:
- if _role_is_global(ref['role_id']):
- filter_results.append(ref)
- return filter_results
-
- def _list_effective_role_assignments(self, role_id, user_id, group_id,
- domain_id, project_id, subtree_ids,
- inherited, source_from_group_ids,
- strip_domain_roles):
- """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. If subtree_ids is not None, then we also want to include
- all subtree_ids in the filter as well. 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, subtree_ids=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. projects 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 at least this project, with
- additionally any projects specified in
- subtree_ids
- :param subtree_ids: The list of projects in the subtree. If
- specified, also include those assignments that
- affect these projects. These projects are
- guaranteed to be in the same domain as the
- project specified in project_id. subtree_ids
- can only be specified if project_id has also
- been specified.
- :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.
-
- """
- project_ids_of_interest = None
- if project_id:
- if subtree_ids:
- project_ids_of_interest = subtree_ids + [project_id]
- else:
- project_ids_of_interest = [project_id]
-
- # List direct project role assignments
- 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_of_interest, 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:
- # The project and any subtree are guaranteed to be owned by
- # the same domain, so since we are filtering by these
- # specific projects, then we can only get inherited
- # assignments from their common domain or from any of
- # their parents projects.
-
- # 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)
-
- # For inherited assignments from projects, since we know
- # they are from the same tree the only places these can
- # come from are from parents of the main project or
- # inherited assignments on the project or subtree itself.
- source_ids = [project['id'] for project in
- self.resource_api.list_project_parents(
- project_id)]
- if subtree_ids:
- source_ids += project_ids_of_interest
- if source_ids:
- inherited_refs += self.driver.list_role_assignments(
- role_id=role_id, project_ids=source_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
- # guaranteed to be empty
- if group_id or (domain_id and inherited):
- return []
-
- if user_id and source_from_group_ids:
- # You can't do both - and since source_from_group_ids is only used
- # internally, this must be a coding error by the caller.
- msg = _('Cannot list assignments sourced from groups and filtered '
- 'by user ID.')
- raise exception.UnexpectedError(msg)
-
- # 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 or explicit group assignments.
- # Due to the need to expand implied roles, this call will skip
- # filtering by role_id and instead return the whole set of roles.
- # Matching on the specified role is performed at the end.
- direct_refs = list_role_assignments_for_actor(
- role_id=None, user_id=user_id, group_ids=source_from_group_ids,
- project_id=project_id, subtree_ids=subtree_ids,
- domain_id=domain_id, inherited=inherited)
-
- # And those from the user's groups, so long as we are not restricting
- # to a set of source groups (in which case we already got those
- # assignments in the direct listing above).
- group_refs = []
- if not source_from_group_ids and 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=None, project_id=project_id,
- subtree_ids=subtree_ids, group_ids=group_ids,
- domain_id=domain_id, inherited=inherited)
-
- # Expand grouping and inheritance on retrieved role assignments
- refs = []
- expand_groups = (source_from_group_ids is None)
- for ref in (direct_refs + group_refs):
- refs += self._expand_indirect_assignment(
- ref, user_id, project_id, subtree_ids, expand_groups)
-
- refs = self.add_implied_roles(refs)
- if strip_domain_roles:
- refs = self._strip_domain_roles(refs)
- if role_id:
- refs = self._filter_by_role_id(role_id, refs)
-
- return refs
-
- def _list_direct_role_assignments(self, role_id, user_id, group_id,
- domain_id, project_id, subtree_ids,
- inherited):
- """List role assignments without applying expansion.
-
- Returns a list of direct role assignments, where their attributes match
- the provided filters. If subtree_ids is not None, then we also want to
- include all subtree_ids in the filter as well.
-
- """
- group_ids = [group_id] if group_id else None
- project_ids_of_interest = None
- if project_id:
- if subtree_ids:
- project_ids_of_interest = subtree_ids + [project_id]
- else:
- project_ids_of_interest = [project_id]
-
- 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_of_interest,
- inherited_to_projects=inherited)
-
- def list_role_assignments(self, role_id=None, user_id=None, group_id=None,
- domain_id=None, project_id=None,
- include_subtree=False, inherited=None,
- effective=None, include_names=False,
- source_from_group_ids=None,
- strip_domain_roles=True):
- """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). If include_subtree is True, then assignments on all
- descendants of the project specified by project_id are also included.
- 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 include_names is set to true the entities' names are returned
- in addition to their id's.
-
- source_from_group_ids is a list of group IDs and, if specified, then
- only those assignments that are derived from membership of these groups
- are considered, and any such assignments will not be expanded into
- their user membership assignments. This is different to a group filter
- of the resulting list, instead being a restriction on which assignments
- should be considered before expansion of inheritance. This option is
- only used internally (i.e. it is not exposed at the API level) and is
- only supported in effective mode (since in regular mode there is no
- difference between this and a group filter, other than it is a list of
- groups).
-
- In effective mode, any domain specific roles are usually stripped from
- the returned assignments (since such roles are not placed in tokens).
- This stripping can be disabled by specifying strip_domain_roles=False,
- which is useful for internal calls like trusts which need to examine
- the full set of roles.
-
- 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
-
- subtree_ids = None
- if project_id and include_subtree:
- subtree_ids = (
- [x['id'] for x in
- self.resource_api.list_projects_in_subtree(project_id)])
-
- if effective:
- role_assignments = self._list_effective_role_assignments(
- role_id, user_id, group_id, domain_id, project_id,
- subtree_ids, inherited, source_from_group_ids,
- strip_domain_roles)
- else:
- role_assignments = self._list_direct_role_assignments(
- role_id, user_id, group_id, domain_id, project_id,
- subtree_ids, inherited)
-
- if include_names:
- return self._get_names_from_role_assignments(role_assignments)
- return role_assignments
-
- def _get_names_from_role_assignments(self, role_assignments):
- role_assign_list = []
-
- for role_asgmt in role_assignments:
- new_assign = {}
- for id_type, id_ in role_asgmt.items():
- if id_type == 'domain_id':
- _domain = self.resource_api.get_domain(id_)
- new_assign['domain_id'] = _domain['id']
- new_assign['domain_name'] = _domain['name']
- elif id_type == 'user_id':
- _user = self.identity_api.get_user(id_)
- new_assign['user_id'] = _user['id']
- new_assign['user_name'] = _user['name']
- new_assign['user_domain_id'] = _user['domain_id']
- new_assign['user_domain_name'] = (
- self.resource_api.get_domain(_user['domain_id'])
- ['name'])
- elif id_type == 'group_id':
- _group = self.identity_api.get_group(id_)
- new_assign['group_id'] = _group['id']
- new_assign['group_name'] = _group['name']
- new_assign['group_domain_id'] = _group['domain_id']
- new_assign['group_domain_name'] = (
- self.resource_api.get_domain(_group['domain_id'])
- ['name'])
- elif id_type == 'project_id':
- _project = self.resource_api.get_project(id_)
- new_assign['project_id'] = _project['id']
- new_assign['project_name'] = _project['name']
- new_assign['project_domain_id'] = _project['domain_id']
- new_assign['project_domain_name'] = (
- self.resource_api.get_domain(_project['domain_id'])
- ['name'])
- elif id_type == 'role_id':
- _role = self.role_api.get_role(id_)
- new_assign['role_id'] = _role['id']
- new_assign['role_name'] = _role['name']
- role_assign_list.append(new_assign)
- return role_assign_list
-
- def delete_tokens_for_role_assignments(self, role_id):
- assignments = self.list_role_assignments(role_id=role_id)
-
- # Iterate over the assignments for this role and build the list of
- # user or user+project IDs for the tokens we need to delete
- user_ids = set()
- user_and_project_ids = list()
- for assignment in assignments:
- # If we have a project assignment, then record both the user and
- # project IDs so we can target the right token to delete. If it is
- # a domain assignment, we might as well kill all the tokens for
- # the user, since in the vast majority of cases all the tokens
- # for a user will be within one domain anyway, so not worth
- # trying to delete tokens for each project in the domain.
- if 'user_id' in assignment:
- if 'project_id' in assignment:
- user_and_project_ids.append(
- (assignment['user_id'], assignment['project_id']))
- elif 'domain_id' in assignment:
- self._emit_invalidate_user_token_persistence(
- assignment['user_id'])
- elif 'group_id' in assignment:
- # Add in any users for this group, being tolerant of any
- # cross-driver database integrity errors.
- try:
- users = self.identity_api.list_users_in_group(
- assignment['group_id'])
- except exception.GroupNotFound:
- # Ignore it, but log a debug message
- if 'project_id' in assignment:
- target = _('Project (%s)') % assignment['project_id']
- elif 'domain_id' in assignment:
- target = _('Domain (%s)') % assignment['domain_id']
- else:
- target = _('Unknown Target')
- msg = ('Group (%(group)s), referenced in assignment '
- 'for %(target)s, not found - ignoring.')
- LOG.debug(msg, {'group': assignment['group_id'],
- 'target': target})
- continue
-
- if 'project_id' in assignment:
- for user in users:
- user_and_project_ids.append(
- (user['id'], assignment['project_id']))
- elif 'domain_id' in assignment:
- for user in users:
- self._emit_invalidate_user_token_persistence(
- user['id'])
-
- # Now process the built up lists. Before issuing calls to delete any
- # tokens, let's try and minimize the number of calls by pruning out
- # any user+project deletions where a general token deletion for that
- # same user is also planned.
- user_and_project_ids_to_action = []
- for user_and_project_id in user_and_project_ids:
- if user_and_project_id[0] not in user_ids:
- user_and_project_ids_to_action.append(user_and_project_id)
-
- for user_id, project_id in user_and_project_ids_to_action:
- payload = {'user_id': user_id, 'project_id': project_id}
- notifications.Audit.internal(
- notifications.INVALIDATE_USER_PROJECT_TOKEN_PERSISTENCE,
- payload
- )
-
-
-# The AssignmentDriverBase class is the set of driver methods from earlier
-# drivers that we still support, that have not been removed or modified. This
-# class is then used to created the augmented V8 and V9 version abstract driver
-# classes, without having to duplicate a lot of abstract method signatures.
-# If you remove a method from V9, then move the abstract methods from this Base
-# class to the V8 class. Do not modify any of the method signatures in the Base
-# class - changes should only be made in the V8 and subsequent classes.
-@six.add_metaclass(abc.ABCMeta)
-class AssignmentDriverBase(object):
-
- def _get_list_limit(self):
- return CONF.assignment.list_limit or CONF.list_limit
-
- @abc.abstractmethod
- def add_role_to_user_and_project(self, user_id, tenant_id, role_id):
- """Add a role to a user within given tenant.
-
- :raises keystone.exception.Conflict: If a duplicate role assignment
- exists.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def remove_role_from_user_and_project(self, user_id, tenant_id, role_id):
- """Remove a role from a user within given tenant.
-
- :raises keystone.exception.RoleNotFound: If the role doesn't exist.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- # assignment/grant crud
-
- @abc.abstractmethod
- def create_grant(self, role_id, user_id=None, group_id=None,
- domain_id=None, project_id=None,
- inherited_to_projects=False):
- """Creates a new assignment/grant.
-
- If the assignment is to a domain, then optionally it may be
- specified as inherited to owned projects (this requires
- the OS-INHERIT extension to be enabled).
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_grant_role_ids(self, user_id=None, group_id=None,
- domain_id=None, project_id=None,
- inherited_to_projects=False):
- """Lists role ids for assignments/grants."""
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def check_grant_role_id(self, role_id, user_id=None, group_id=None,
- domain_id=None, project_id=None,
- inherited_to_projects=False):
- """Checks an assignment/grant role id.
-
- :raises keystone.exception.RoleAssignmentNotFound: If the role
- assignment doesn't exist.
- :returns: None or raises an exception if grant not found
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def delete_grant(self, role_id, user_id=None, group_id=None,
- domain_id=None, project_id=None,
- inherited_to_projects=False):
- """Deletes assignments/grants.
-
- :raises keystone.exception.RoleAssignmentNotFound: If the role
- assignment doesn't exist.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- 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
- def delete_project_assignments(self, project_id):
- """Deletes all assignments for a project.
-
- :raises keystone.exception.ProjectNotFound: If the project doesn't
- exist.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def delete_role_assignments(self, role_id):
- """Deletes all assignments for a role."""
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def delete_user_assignments(self, user_id):
- """Deletes all assignments for a user.
-
- :raises keystone.exception.RoleNotFound: If the role doesn't exist.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def delete_group_assignments(self, group_id):
- """Deletes all assignments for a group.
-
- :raises keystone.exception.RoleNotFound: If the role doesn't exist.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
-
-class AssignmentDriverV8(AssignmentDriverBase):
- """Removed or redefined methods from V8.
-
- Move the abstract methods of any methods removed or modified in later
- versions of the driver from AssignmentDriverBase to here. We maintain this
- so that legacy drivers, which will be a subclass of AssignmentDriverV8, can
- still reference them.
-
- """
-
- @abc.abstractmethod
- def list_user_ids_for_project(self, tenant_id):
- """Lists all user IDs with a role assignment in the specified project.
-
- :returns: a list of user_ids or an empty set.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_project_ids_for_user(self, user_id, group_ids, hints,
- inherited=False):
- """List all project ids associated with a given user.
-
- :param user_id: the user in question
- :param group_ids: the groups this user is a member of. This list is
- built in the Manager, so that the driver itself
- does not have to call across to identity.
- :param hints: filter hints which the driver should
- implement if at all possible.
- :param inherited: whether assignments marked as inherited should
- be included.
-
- :returns: a list of project ids or an empty list.
-
- This method should not try and expand any inherited assignments,
- just report the projects that have the role for this user. The manager
- method is responsible for expanding out inherited assignments.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_domain_ids_for_user(self, user_id, group_ids, hints,
- inherited=False):
- """List all domain ids associated with a given user.
-
- :param user_id: the user in question
- :param group_ids: the groups this user is a member of. This list is
- built in the Manager, so that the driver itself
- does not have to call across to identity.
- :param hints: filter hints which the driver should
- implement if at all possible.
- :param inherited: whether to return domain_ids that have inherited
- assignments or not.
-
- :returns: a list of domain ids or an empty list.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_project_ids_for_groups(self, group_ids, hints,
- inherited=False):
- """List project ids accessible to specified groups.
-
- :param group_ids: List of group ids.
- :param hints: filter hints which the driver should
- implement if at all possible.
- :param inherited: whether assignments marked as inherited should
- be included.
- :returns: List of project ids accessible to specified groups.
-
- This method should not try and expand any inherited assignments,
- just report the projects that have the role for this group. The manager
- method is responsible for expanding out inherited assignments.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_domain_ids_for_groups(self, group_ids, inherited=False):
- """List domain ids accessible to specified groups.
-
- :param group_ids: List of group ids.
- :param inherited: whether to return domain_ids that have inherited
- assignments or not.
- :returns: List of domain ids accessible to specified groups.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_role_ids_for_groups_on_project(
- self, group_ids, project_id, project_domain_id, project_parents):
- """List the group role ids for a specific project.
-
- Supports the ``OS-INHERIT`` role inheritance from the project's domain
- if supported by the assignment driver.
-
- :param group_ids: list of group ids
- :type group_ids: list
- :param project_id: project identifier
- :type project_id: str
- :param project_domain_id: project's domain identifier
- :type project_domain_id: str
- :param project_parents: list of parent ids of this project
- :type project_parents: list
- :returns: list of role ids for the project
- :rtype: list
- """
- raise exception.NotImplemented()
-
- @abc.abstractmethod
- def list_role_ids_for_groups_on_domain(self, group_ids, domain_id):
- """List the group role ids for a specific domain.
-
- :param group_ids: list of group ids
- :type group_ids: list
- :param domain_id: domain identifier
- :type domain_id: str
- :returns: list of role ids for the project
- :rtype: list
- """
- raise exception.NotImplemented()
-
-
-class AssignmentDriverV9(AssignmentDriverBase):
- """New or redefined methods from V8.
-
- Add any new V9 abstract methods (or those with modified signatures) to
- this class.
-
- """
-
- @abc.abstractmethod
- def delete_domain_assignments(self, domain_id):
- """Deletes all assignments for a domain."""
- raise exception.NotImplemented()
-
-
-class V9AssignmentWrapperForV8Driver(AssignmentDriverV9):
- """Wrapper class to supported a V8 legacy driver.
-
- In order to support legacy drivers without having to make the manager code
- driver-version aware, we wrap legacy drivers so that they look like the
- latest version. For the various changes made in a new driver, here are the
- actions needed in this wrapper:
-
- Method removed from new driver - remove the call-through method from this
- class, since the manager will no longer be
- calling it.
- Method signature (or meaning) changed - wrap the old method in a new
- signature here, and munge the input
- and output parameters accordingly.
- New method added to new driver - add a method to implement the new
- functionality here if possible. If that is
- not possible, then return NotImplemented,
- since we do not guarantee to support new
- functionality with legacy drivers.
-
- """
-
- @versionutils.deprecated(
- as_of=versionutils.deprecated.MITAKA,
- what='keystone.assignment.AssignmentDriverV8',
- in_favor_of='keystone.assignment.AssignmentDriverV9',
- remove_in=+2)
- def __init__(self, wrapped_driver):
- self.driver = wrapped_driver
-
- def delete_domain_assignments(self, domain_id):
- """Deletes all assignments for a domain."""
- msg = _LW('delete_domain_assignments method not found in custom '
- 'assignment driver. Domain assignments for domain (%s) to '
- 'users from other domains will not be removed. This was '
- 'added in V9 of the assignment driver.')
- LOG.warning(msg, domain_id)
-
- def default_role_driver(self):
- return self.driver.default_role_driver()
-
- def default_resource_driver(self):
- return self.driver.default_resource_driver()
-
- def add_role_to_user_and_project(self, user_id, tenant_id, role_id):
- self.driver.add_role_to_user_and_project(user_id, tenant_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, role_id)
-
- def create_grant(self, role_id, user_id=None, group_id=None,
- domain_id=None, project_id=None,
- inherited_to_projects=False):
- self.driver.create_grant(
- role_id, user_id=user_id, group_id=group_id,
- domain_id=domain_id, project_id=project_id,
- inherited_to_projects=inherited_to_projects)
-
- def list_grant_role_ids(self, user_id=None, group_id=None,
- domain_id=None, project_id=None,
- inherited_to_projects=False):
- return self.driver.list_grant_role_ids(
- user_id=user_id, group_id=group_id,
- domain_id=domain_id, project_id=project_id,
- inherited_to_projects=inherited_to_projects)
-
- def check_grant_role_id(self, role_id, user_id=None, group_id=None,
- domain_id=None, project_id=None,
- inherited_to_projects=False):
- self.driver.check_grant_role_id(
- role_id, user_id=user_id, group_id=group_id,
- domain_id=domain_id, project_id=project_id,
- inherited_to_projects=inherited_to_projects)
-
- def delete_grant(self, role_id, user_id=None, group_id=None,
- domain_id=None, project_id=None,
- inherited_to_projects=False):
- self.driver.delete_grant(
- role_id, user_id=user_id, group_id=group_id,
- domain_id=domain_id, project_id=project_id,
- inherited_to_projects=inherited_to_projects)
-
- def list_role_assignments(self, role_id=None,
- user_id=None, group_ids=None,
- domain_id=None, project_ids=None,
- inherited_to_projects=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_to_projects)
-
- def delete_project_assignments(self, project_id):
- self.driver.delete_project_assignments(project_id)
-
- def delete_role_assignments(self, role_id):
- self.driver.delete_role_assignments(role_id)
-
- def delete_user_assignments(self, user_id):
- self.driver.delete_user_assignments(user_id)
-
- def delete_group_assignments(self, group_id):
- self.driver.delete_group_assignments(group_id)
-
-
-Driver = manager.create_legacy_driver(AssignmentDriverV8)
-
-
-@dependency.provider('role_api')
-@dependency.requires('assignment_api')
-class RoleManager(manager.Manager):
- """Default pivot point for the Role backend."""
-
- driver_namespace = 'keystone.role'
-
- _ROLE = 'role'
-
- def __init__(self):
- # If there is a specific driver specified for role, then use it.
- # Otherwise retrieve the driver type from the assignment driver.
- role_driver = CONF.role.driver
-
- if role_driver is None:
- assignment_manager = dependency.get_provider('assignment_api')
- role_driver = assignment_manager.default_role_driver()
-
- super(RoleManager, self).__init__(role_driver)
-
- # Make sure it is a driver version we support, and if it is a legacy
- # driver, then wrap it.
- if isinstance(self.driver, RoleDriverV8):
- self.driver = V9RoleWrapperForV8Driver(self.driver)
- elif not isinstance(self.driver, RoleDriverV9):
- raise exception.UnsupportedDriverVersion(driver=role_driver)
-
- @MEMOIZE
- def get_role(self, role_id):
- return self.driver.get_role(role_id)
-
- def create_role(self, role_id, role, initiator=None):
- ret = self.driver.create_role(role_id, role)
- notifications.Audit.created(self._ROLE, role_id, initiator)
- if MEMOIZE.should_cache(ret):
- self.get_role.set(ret, self, role_id)
- return ret
-
- @manager.response_truncated
- def list_roles(self, hints=None):
- return self.driver.list_roles(hints or driver_hints.Hints())
-
- def update_role(self, role_id, role, initiator=None):
- original_role = self.driver.get_role(role_id)
- if ('domain_id' in role and
- role['domain_id'] != original_role['domain_id']):
- raise exception.ValidationError(
- message=_('Update of `domain_id` is not allowed.'))
-
- ret = self.driver.update_role(role_id, role)
- notifications.Audit.updated(self._ROLE, role_id, initiator)
- self.get_role.invalidate(self, role_id)
- return ret
-
- def delete_role(self, role_id, initiator=None):
- self.assignment_api.delete_tokens_for_role_assignments(role_id)
- self.assignment_api.delete_role_assignments(role_id)
- self.driver.delete_role(role_id)
- notifications.Audit.deleted(self._ROLE, role_id, initiator)
- self.get_role.invalidate(self, role_id)
- COMPUTED_ASSIGNMENTS_REGION.invalidate()
-
- # TODO(ayoung): Add notification
- def create_implied_role(self, prior_role_id, implied_role_id):
- implied_role = self.driver.get_role(implied_role_id)
- self.driver.get_role(prior_role_id)
- if implied_role['name'] in CONF.assignment.prohibited_implied_role:
- raise exception.InvalidImpliedRole(role_id=implied_role_id)
- response = self.driver.create_implied_role(
- prior_role_id, implied_role_id)
- COMPUTED_ASSIGNMENTS_REGION.invalidate()
- return response
-
- def delete_implied_role(self, prior_role_id, implied_role_id):
- self.driver.delete_implied_role(prior_role_id, implied_role_id)
- COMPUTED_ASSIGNMENTS_REGION.invalidate()
-
-
-# The RoleDriverBase class is the set of driver methods from earlier
-# drivers that we still support, that have not been removed or modified. This
-# class is then used to created the augmented V8 and V9 version abstract driver
-# classes, without having to duplicate a lot of abstract method signatures.
-# If you remove a method from V9, then move the abstract methods from this Base
-# class to the V8 class. Do not modify any of the method signatures in the Base
-# class - changes should only be made in the V8 and subsequent classes.
-@six.add_metaclass(abc.ABCMeta)
-class RoleDriverBase(object):
-
- def _get_list_limit(self):
- return CONF.role.list_limit or CONF.list_limit
-
- @abc.abstractmethod
- def create_role(self, role_id, role):
- """Creates a new role.
-
- :raises keystone.exception.Conflict: If a duplicate role exists.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_roles(self, hints):
- """List roles in the system.
-
- :param hints: filter hints which the driver should
- implement if at all possible.
-
- :returns: a list of role_refs or an empty list.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_roles_from_ids(self, role_ids):
- """List roles for the provided list of ids.
-
- :param role_ids: list of ids
-
- :returns: a list of role_refs.
-
- This method is used internally by the assignment manager to bulk read
- a set of roles given their ids.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def get_role(self, role_id):
- """Get a role by ID.
-
- :returns: role_ref
- :raises keystone.exception.RoleNotFound: If the role doesn't exist.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def update_role(self, role_id, role):
- """Updates an existing role.
-
- :raises keystone.exception.RoleNotFound: If the role doesn't exist.
- :raises keystone.exception.Conflict: If a duplicate role exists.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def delete_role(self, role_id):
- """Deletes an existing role.
-
- :raises keystone.exception.RoleNotFound: If the role doesn't exist.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
-
-class RoleDriverV8(RoleDriverBase):
- """Removed or redefined methods from V8.
-
- Move the abstract methods of any methods removed or modified in later
- versions of the driver from RoleDriverBase to here. We maintain this
- so that legacy drivers, which will be a subclass of RoleDriverV8, can
- still reference them.
-
- """
-
- pass
-
-
-class RoleDriverV9(RoleDriverBase):
- """New or redefined methods from V8.
-
- Add any new V9 abstract methods (or those with modified signatures) to
- this class.
-
- """
-
- @abc.abstractmethod
- def get_implied_role(self, prior_role_id, implied_role_id):
- """Fetches a role inference rule
-
- :raises keystone.exception.ImpliedRoleNotFound: If the implied role
- doesn't exist.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def create_implied_role(self, prior_role_id, implied_role_id):
- """Creates a role inference rule
-
- :raises: keystone.exception.RoleNotFound: If the role doesn't exist.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def delete_implied_role(self, prior_role_id, implied_role_id):
- """Deletes a role inference rule
-
- :raises keystone.exception.ImpliedRoleNotFound: If the implied role
- doesn't exist.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_role_inference_rules(self):
- """Lists all the rules used to imply one role from another"""
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_implied_roles(self, prior_role_id):
- """Lists roles implied from the prior role ID"""
- raise exception.NotImplemented() # pragma: no cover
-
-
-class V9RoleWrapperForV8Driver(RoleDriverV9):
- """Wrapper class to supported a V8 legacy driver.
-
- In order to support legacy drivers without having to make the manager code
- driver-version aware, we wrap legacy drivers so that they look like the
- latest version. For the various changes made in a new driver, here are the
- actions needed in this wrapper:
-
- Method removed from new driver - remove the call-through method from this
- class, since the manager will no longer be
- calling it.
- Method signature (or meaning) changed - wrap the old method in a new
- signature here, and munge the input
- and output parameters accordingly.
- New method added to new driver - add a method to implement the new
- functionality here if possible. If that is
- not possible, then return NotImplemented,
- since we do not guarantee to support new
- functionality with legacy drivers.
-
- This V8 wrapper contains the following support for newer manager code:
-
- - The current manager code expects a role entity to have a domain_id
- attribute, with a non-None value indicating a domain specific role. V8
- drivers will only understand global roles, hence if a non-None domain_id
- is passed to this wrapper, it will raise a NotImplemented exception.
- If a None-valued domain_id is passed in, it will be trimmed off before
- the underlying driver is called (and a None-valued domain_id attribute
- is added in for any entities returned to the manager.
-
- """
-
- @versionutils.deprecated(
- as_of=versionutils.deprecated.MITAKA,
- what='keystone.assignment.RoleDriverV8',
- in_favor_of='keystone.assignment.RoleDriverV9',
- remove_in=+2)
- def __init__(self, wrapped_driver):
- self.driver = wrapped_driver
-
- def _append_null_domain_id(self, role_or_list):
- def _append_null_domain_id_to_dict(role):
- if 'domain_id' not in role:
- role['domain_id'] = None
- return role
-
- if isinstance(role_or_list, list):
- return [_append_null_domain_id_to_dict(x) for x in role_or_list]
- else:
- return _append_null_domain_id_to_dict(role_or_list)
-
- def _trim_and_assert_null_domain_id(self, role):
- if 'domain_id' in role:
- if role['domain_id'] is not None:
- raise exception.NotImplemented(
- _('Domain specific roles are not supported in the V8 '
- 'role driver'))
- else:
- new_role = role.copy()
- new_role.pop('domain_id')
- return new_role
- else:
- return role
-
- def create_role(self, role_id, role):
- new_role = self._trim_and_assert_null_domain_id(role)
- return self._append_null_domain_id(
- self.driver.create_role(role_id, new_role))
-
- def list_roles(self, hints):
- return self._append_null_domain_id(self.driver.list_roles(hints))
-
- def list_roles_from_ids(self, role_ids):
- return self._append_null_domain_id(
- self.driver.list_roles_from_ids(role_ids))
-
- def get_role(self, role_id):
- return self._append_null_domain_id(self.driver.get_role(role_id))
-
- def update_role(self, role_id, role):
- update_role = self._trim_and_assert_null_domain_id(role)
- return self._append_null_domain_id(
- self.driver.update_role(role_id, update_role))
-
- def delete_role(self, role_id):
- self.driver.delete_role(role_id)
-
- def get_implied_role(self, prior_role_id, implied_role_id):
- raise exception.NotImplemented() # pragma: no cover
-
- def create_implied_role(self, prior_role_id, implied_role_id):
- raise exception.NotImplemented() # pragma: no cover
-
- def delete_implied_role(self, prior_role_id, implied_role_id):
- raise exception.NotImplemented() # pragma: no cover
-
- def list_implied_roles(self, prior_role_id):
- raise exception.NotImplemented() # pragma: no cover
-
- def list_role_inference_rules(self):
- raise exception.NotImplemented() # pragma: no cover
-
-RoleDriver = manager.create_legacy_driver(RoleDriverV8)
diff --git a/keystone-moon/keystone/assignment/role_backends/__init__.py b/keystone-moon/keystone/assignment/role_backends/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/keystone-moon/keystone/assignment/role_backends/__init__.py
+++ /dev/null
diff --git a/keystone-moon/keystone/assignment/role_backends/ldap.py b/keystone-moon/keystone/assignment/role_backends/ldap.py
deleted file mode 100644
index 6e5e038e..00000000
--- a/keystone-moon/keystone/assignment/role_backends/ldap.py
+++ /dev/null
@@ -1,125 +0,0 @@
-# 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
-
-from oslo_config import cfg
-from oslo_log import log
-
-from keystone import assignment
-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
-
-
-CONF = cfg.CONF
-LOG = log.getLogger(__name__)
-
-
-class Role(assignment.RoleDriverV8):
-
- def __init__(self):
- super(Role, 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 resource back
- # to identity. The assumption is that if you are using
- # LDAP for resource, you are using it for identity as well.
- self.user = ldap_identity.UserApi(CONF)
- self.role = RoleApi(CONF, self.user)
-
- def get_role(self, role_id):
- return self.role.get(role_id)
-
- def list_roles(self, hints):
- return self.role.get_all()
-
- def list_roles_from_ids(self, ids):
- return [self.get_role(id) for id in ids]
-
- def create_role(self, role_id, role):
- self.role.check_allow_create()
- try:
- self.get_role(role_id)
- except exception.NotFound:
- pass
- else:
- msg = _('Duplicate ID, %s.') % role_id
- raise exception.Conflict(type='role', details=msg)
-
- try:
- self.role.get_by_name(role['name'])
- except exception.NotFound:
- pass
- else:
- msg = _('Duplicate name, %s.') % role['name']
- raise exception.Conflict(type='role', details=msg)
-
- return self.role.create(role)
-
- def delete_role(self, role_id):
- self.role.check_allow_delete()
- return self.role.delete(role_id)
-
- def update_role(self, role_id, role):
- self.role.check_allow_update()
- self.get_role(role_id)
- return self.role.update(role_id, role)
-
-
-# NOTE(heny-nash): A mixin class to enable the sharing of the LDAP structure
-# between here and the assignment LDAP.
-class RoleLdapStructureMixin(object):
- DEFAULT_OU = 'ou=Roles'
- DEFAULT_STRUCTURAL_CLASSES = []
- DEFAULT_OBJECTCLASS = 'organizationalRole'
- DEFAULT_MEMBER_ATTRIBUTE = 'roleOccupant'
- NotFound = exception.RoleNotFound
- options_name = 'role'
- attribute_options_names = {'name': 'name'}
- immutable_attrs = ['id']
- model = models.Role
-
-
-# TODO(termie): turn this into a data object and move logic to driver
-class RoleApi(RoleLdapStructureMixin, common_ldap.BaseLdap):
-
- def __init__(self, conf, user_api):
- super(RoleApi, self).__init__(conf)
- self._user_api = user_api
-
- def get(self, role_id, role_filter=None):
- model = super(RoleApi, self).get(role_id, role_filter)
- return model
-
- def create(self, values):
- return super(RoleApi, self).create(values)
-
- def update(self, role_id, role):
- new_name = role.get('name')
- if new_name is not None:
- try:
- old_role = self.get_by_name(new_name)
- if old_role['id'] != role_id:
- raise exception.Conflict(
- _('Cannot duplicate name %s') % old_role)
- except exception.NotFound:
- pass
- return super(RoleApi, self).update(role_id, role)
-
- def delete(self, role_id):
- super(RoleApi, self).delete(role_id)
diff --git a/keystone-moon/keystone/assignment/role_backends/sql.py b/keystone-moon/keystone/assignment/role_backends/sql.py
deleted file mode 100644
index 1045f23a..00000000
--- a/keystone-moon/keystone/assignment/role_backends/sql.py
+++ /dev/null
@@ -1,202 +0,0 @@
-# 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_db import exception as db_exception
-
-from keystone import assignment
-from keystone.common import driver_hints
-from keystone.common import sql
-from keystone import exception
-
-# NOTE(henry-nash): From the manager and above perspective, the domain_id
-# attribute of a role is nullable. However, to ensure uniqueness in
-# multi-process configurations, it is better to still use a sql uniqueness
-# constraint. Since the support for a nullable component of a uniqueness
-# constraint across different sql databases is mixed, we instead store a
-# special value to represent null, as defined in NULL_DOMAIN_ID below.
-NULL_DOMAIN_ID = '<<null>>'
-
-
-class Role(assignment.RoleDriverV9):
-
- @sql.handle_conflicts(conflict_type='role')
- def create_role(self, role_id, role):
- with sql.session_for_write() as session:
- ref = RoleTable.from_dict(role)
- session.add(ref)
- return ref.to_dict()
-
- @driver_hints.truncated
- def list_roles(self, hints):
- # If there is a filter on domain_id and the value is None, then to
- # ensure that the sql filtering works correctly, we need to patch
- # the value to be NULL_DOMAIN_ID. This is safe to do here since we
- # know we are able to satisfy any filter of this type in the call to
- # filter_limit_query() below, which will remove the filter from the
- # hints (hence ensuring our substitution is not exposed to the caller).
- for f in hints.filters:
- if (f['name'] == 'domain_id' and f['value'] is None):
- f['value'] = NULL_DOMAIN_ID
-
- with sql.session_for_read() as session:
- query = session.query(RoleTable)
- refs = sql.filter_limit_query(RoleTable, query, hints)
- return [ref.to_dict() for ref in refs]
-
- def list_roles_from_ids(self, ids):
- if not ids:
- return []
- else:
- with sql.session_for_read() as session:
- query = session.query(RoleTable)
- query = query.filter(RoleTable.id.in_(ids))
- role_refs = query.all()
- return [role_ref.to_dict() for role_ref in role_refs]
-
- def _get_role(self, session, role_id):
- ref = session.query(RoleTable).get(role_id)
- if ref is None:
- raise exception.RoleNotFound(role_id=role_id)
- return ref
-
- def get_role(self, role_id):
- with sql.session_for_read() as session:
- return self._get_role(session, role_id).to_dict()
-
- @sql.handle_conflicts(conflict_type='role')
- def update_role(self, role_id, role):
- with sql.session_for_write() as session:
- ref = self._get_role(session, role_id)
- old_dict = ref.to_dict()
- for k in role:
- old_dict[k] = role[k]
- new_role = RoleTable.from_dict(old_dict)
- for attr in RoleTable.attributes:
- if attr != 'id':
- setattr(ref, attr, getattr(new_role, attr))
- ref.extra = new_role.extra
- return ref.to_dict()
-
- def delete_role(self, role_id):
- with sql.session_for_write() as session:
- ref = self._get_role(session, role_id)
- session.delete(ref)
-
- def _get_implied_role(self, session, prior_role_id, implied_role_id):
- query = session.query(
- ImpliedRoleTable).filter(
- ImpliedRoleTable.prior_role_id == prior_role_id).filter(
- ImpliedRoleTable.implied_role_id == implied_role_id)
- try:
- ref = query.one()
- except sql.NotFound:
- raise exception.ImpliedRoleNotFound(
- prior_role_id=prior_role_id,
- implied_role_id=implied_role_id)
- return ref
-
- @sql.handle_conflicts(conflict_type='implied_role')
- def create_implied_role(self, prior_role_id, implied_role_id):
- with sql.session_for_write() as session:
- inference = {'prior_role_id': prior_role_id,
- 'implied_role_id': implied_role_id}
- ref = ImpliedRoleTable.from_dict(inference)
- try:
- session.add(ref)
- except db_exception.DBReferenceError:
- # We don't know which role threw this.
- # Query each to trigger the exception.
- self._get_role(session, prior_role_id)
- self._get_role(session, implied_role_id)
- return ref.to_dict()
-
- def delete_implied_role(self, prior_role_id, implied_role_id):
- with sql.session_for_write() as session:
- ref = self._get_implied_role(session, prior_role_id,
- implied_role_id)
- session.delete(ref)
-
- def list_implied_roles(self, prior_role_id):
- with sql.session_for_read() as session:
- query = session.query(
- ImpliedRoleTable).filter(
- ImpliedRoleTable.prior_role_id == prior_role_id)
- refs = query.all()
- return [ref.to_dict() for ref in refs]
-
- def list_role_inference_rules(self):
- with sql.session_for_read() as session:
- query = session.query(ImpliedRoleTable)
- refs = query.all()
- return [ref.to_dict() for ref in refs]
-
- def get_implied_role(self, prior_role_id, implied_role_id):
- with sql.session_for_read() as session:
- ref = self._get_implied_role(session, prior_role_id,
- implied_role_id)
- return ref.to_dict()
-
-
-class ImpliedRoleTable(sql.ModelBase, sql.DictBase):
- __tablename__ = 'implied_role'
- attributes = ['prior_role_id', 'implied_role_id']
- prior_role_id = sql.Column(
- sql.String(64),
- sql.ForeignKey('role.id', ondelete="CASCADE"),
- primary_key=True)
- implied_role_id = sql.Column(
- sql.String(64),
- sql.ForeignKey('role.id', ondelete="CASCADE"),
- primary_key=True)
-
- @classmethod
- def from_dict(cls, dictionary):
- new_dictionary = dictionary.copy()
- return cls(**new_dictionary)
-
- def to_dict(self):
- """Return a dictionary with model's attributes.
-
- overrides the `to_dict` function from the base class
- to avoid having an `extra` field.
- """
- d = dict()
- for attr in self.__class__.attributes:
- d[attr] = getattr(self, attr)
- return d
-
-
-class RoleTable(sql.ModelBase, sql.DictBase):
-
- def to_dict(self, include_extra_dict=False):
- d = super(RoleTable, self).to_dict(
- include_extra_dict=include_extra_dict)
- if d['domain_id'] == NULL_DOMAIN_ID:
- d['domain_id'] = None
- return d
-
- @classmethod
- def from_dict(cls, role_dict):
- if 'domain_id' in role_dict and role_dict['domain_id'] is None:
- new_dict = role_dict.copy()
- new_dict['domain_id'] = NULL_DOMAIN_ID
- else:
- new_dict = role_dict
- return super(RoleTable, cls).from_dict(new_dict)
-
- __tablename__ = 'role'
- attributes = ['id', 'name', 'domain_id']
- id = sql.Column(sql.String(64), primary_key=True)
- name = sql.Column(sql.String(255), nullable=False)
- domain_id = sql.Column(sql.String(64), nullable=False,
- server_default=NULL_DOMAIN_ID)
- extra = sql.Column(sql.JsonBlob())
- __table_args__ = (sql.UniqueConstraint('name', 'domain_id'),)
diff --git a/keystone-moon/keystone/assignment/routers.py b/keystone-moon/keystone/assignment/routers.py
deleted file mode 100644
index 9bef401e..00000000
--- a/keystone-moon/keystone/assignment/routers.py
+++ /dev/null
@@ -1,282 +0,0 @@
-# Copyright 2013 Metacloud, Inc.
-# Copyright 2012 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.
-
-"""WSGI Routers for the Assignment service."""
-
-import functools
-
-from oslo_config import cfg
-
-from keystone.assignment import controllers
-from keystone.common import json_home
-from keystone.common import router
-from keystone.common import wsgi
-
-
-CONF = cfg.CONF
-
-build_os_inherit_relation = functools.partial(
- json_home.build_v3_extension_resource_relation,
- extension_name='OS-INHERIT', extension_version='1.0')
-
-
-class Public(wsgi.ComposableRouter):
- def add_routes(self, mapper):
- tenant_controller = controllers.TenantAssignment()
- mapper.connect('/tenants',
- controller=tenant_controller,
- action='get_projects_for_token',
- conditions=dict(method=['GET']))
-
-
-class Admin(wsgi.ComposableRouter):
- def add_routes(self, mapper):
- # Role Operations
- roles_controller = controllers.RoleAssignmentV2()
- mapper.connect('/tenants/{tenant_id}/users/{user_id}/roles',
- controller=roles_controller,
- action='get_user_roles',
- conditions=dict(method=['GET']))
- mapper.connect('/users/{user_id}/roles',
- controller=roles_controller,
- action='get_user_roles',
- conditions=dict(method=['GET']))
-
-
-class Routers(wsgi.RoutersBase):
-
- def append_v3_routers(self, mapper, routers):
-
- project_controller = controllers.ProjectAssignmentV3()
- self._add_resource(
- mapper, project_controller,
- path='/users/{user_id}/projects',
- get_action='list_user_projects',
- rel=json_home.build_v3_resource_relation('user_projects'),
- path_vars={
- 'user_id': json_home.Parameters.USER_ID,
- })
-
- routers.append(
- router.Router(controllers.RoleV3(), 'roles', 'role',
- resource_descriptions=self.v3_resources,
- method_template='%s_wrapper'))
-
- implied_roles_controller = controllers.ImpliedRolesV3()
- self._add_resource(
- mapper, implied_roles_controller,
- path='/roles/{prior_role_id}/implies',
- rel=json_home.build_v3_resource_relation('implied_roles'),
- get_action='list_implied_roles',
- status=json_home.Status.EXPERIMENTAL,
- path_vars={
- 'prior_role_id': json_home.Parameters.ROLE_ID,
- }
- )
-
- self._add_resource(
- mapper, implied_roles_controller,
- path='/roles/{prior_role_id}/implies/{implied_role_id}',
- put_action='create_implied_role',
- delete_action='delete_implied_role',
- head_action='check_implied_role',
- get_action='get_implied_role',
- rel=json_home.build_v3_resource_relation('implied_role'),
- status=json_home.Status.EXPERIMENTAL,
- path_vars={
- 'prior_role_id': json_home.Parameters.ROLE_ID,
- 'implied_role_id': json_home.Parameters.ROLE_ID
- }
- )
- self._add_resource(
- mapper, implied_roles_controller,
- path='/role_inferences',
- get_action='list_role_inference_rules',
- rel=json_home.build_v3_resource_relation('role_inferences'),
- status=json_home.Status.EXPERIMENTAL,
- path_vars={}
- )
-
- grant_controller = controllers.GrantAssignmentV3()
- self._add_resource(
- mapper, grant_controller,
- path='/projects/{project_id}/users/{user_id}/roles/{role_id}',
- get_head_action='check_grant',
- put_action='create_grant',
- delete_action='revoke_grant',
- rel=json_home.build_v3_resource_relation('project_user_role'),
- path_vars={
- 'project_id': json_home.Parameters.PROJECT_ID,
- 'role_id': json_home.Parameters.ROLE_ID,
- 'user_id': json_home.Parameters.USER_ID,
- })
- self._add_resource(
- mapper, grant_controller,
- path='/projects/{project_id}/groups/{group_id}/roles/{role_id}',
- get_head_action='check_grant',
- put_action='create_grant',
- delete_action='revoke_grant',
- rel=json_home.build_v3_resource_relation('project_group_role'),
- path_vars={
- 'group_id': json_home.Parameters.GROUP_ID,
- 'project_id': json_home.Parameters.PROJECT_ID,
- 'role_id': json_home.Parameters.ROLE_ID,
- })
- self._add_resource(
- mapper, grant_controller,
- path='/projects/{project_id}/users/{user_id}/roles',
- get_action='list_grants',
- rel=json_home.build_v3_resource_relation('project_user_roles'),
- path_vars={
- 'project_id': json_home.Parameters.PROJECT_ID,
- 'user_id': json_home.Parameters.USER_ID,
- })
- self._add_resource(
- mapper, grant_controller,
- path='/projects/{project_id}/groups/{group_id}/roles',
- get_action='list_grants',
- rel=json_home.build_v3_resource_relation('project_group_roles'),
- path_vars={
- 'group_id': json_home.Parameters.GROUP_ID,
- 'project_id': json_home.Parameters.PROJECT_ID,
- })
- self._add_resource(
- mapper, grant_controller,
- path='/domains/{domain_id}/users/{user_id}/roles/{role_id}',
- get_head_action='check_grant',
- put_action='create_grant',
- delete_action='revoke_grant',
- rel=json_home.build_v3_resource_relation('domain_user_role'),
- path_vars={
- 'domain_id': json_home.Parameters.DOMAIN_ID,
- 'role_id': json_home.Parameters.ROLE_ID,
- 'user_id': json_home.Parameters.USER_ID,
- })
- self._add_resource(
- mapper, grant_controller,
- path='/domains/{domain_id}/groups/{group_id}/roles/{role_id}',
- get_head_action='check_grant',
- put_action='create_grant',
- delete_action='revoke_grant',
- rel=json_home.build_v3_resource_relation('domain_group_role'),
- path_vars={
- 'domain_id': json_home.Parameters.DOMAIN_ID,
- 'group_id': json_home.Parameters.GROUP_ID,
- 'role_id': json_home.Parameters.ROLE_ID,
- })
- self._add_resource(
- mapper, grant_controller,
- path='/domains/{domain_id}/users/{user_id}/roles',
- get_action='list_grants',
- rel=json_home.build_v3_resource_relation('domain_user_roles'),
- path_vars={
- 'domain_id': json_home.Parameters.DOMAIN_ID,
- 'user_id': json_home.Parameters.USER_ID,
- })
- self._add_resource(
- mapper, grant_controller,
- path='/domains/{domain_id}/groups/{group_id}/roles',
- get_action='list_grants',
- rel=json_home.build_v3_resource_relation('domain_group_roles'),
- path_vars={
- 'domain_id': json_home.Parameters.DOMAIN_ID,
- 'group_id': json_home.Parameters.GROUP_ID,
- })
-
- self._add_resource(
- mapper, controllers.RoleAssignmentV3(),
- path='/role_assignments',
- get_action='list_role_assignments_wrapper',
- rel=json_home.build_v3_resource_relation('role_assignments'))
-
- if CONF.os_inherit.enabled:
- self._add_resource(
- mapper, grant_controller,
- path='/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/'
- '{role_id}/inherited_to_projects',
- get_head_action='check_grant',
- put_action='create_grant',
- delete_action='revoke_grant',
- rel=build_os_inherit_relation(
- resource_name='domain_user_role_inherited_to_projects'),
- path_vars={
- 'domain_id': json_home.Parameters.DOMAIN_ID,
- 'role_id': json_home.Parameters.ROLE_ID,
- 'user_id': json_home.Parameters.USER_ID,
- })
- self._add_resource(
- mapper, grant_controller,
- path='/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/'
- '{role_id}/inherited_to_projects',
- get_head_action='check_grant',
- put_action='create_grant',
- delete_action='revoke_grant',
- rel=build_os_inherit_relation(
- resource_name='domain_group_role_inherited_to_projects'),
- path_vars={
- 'domain_id': json_home.Parameters.DOMAIN_ID,
- 'group_id': json_home.Parameters.GROUP_ID,
- 'role_id': json_home.Parameters.ROLE_ID,
- })
- self._add_resource(
- mapper, grant_controller,
- path='/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/'
- 'inherited_to_projects',
- get_action='list_grants',
- rel=build_os_inherit_relation(
- resource_name='domain_group_roles_inherited_to_projects'),
- path_vars={
- 'domain_id': json_home.Parameters.DOMAIN_ID,
- 'group_id': json_home.Parameters.GROUP_ID,
- })
- self._add_resource(
- mapper, grant_controller,
- path='/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/'
- 'inherited_to_projects',
- get_action='list_grants',
- rel=build_os_inherit_relation(
- resource_name='domain_user_roles_inherited_to_projects'),
- path_vars={
- 'domain_id': json_home.Parameters.DOMAIN_ID,
- 'user_id': json_home.Parameters.USER_ID,
- })
- self._add_resource(
- mapper, grant_controller,
- path='/OS-INHERIT/projects/{project_id}/users/{user_id}/roles/'
- '{role_id}/inherited_to_projects',
- get_head_action='check_grant',
- put_action='create_grant',
- delete_action='revoke_grant',
- rel=build_os_inherit_relation(
- resource_name='project_user_role_inherited_to_projects'),
- path_vars={
- 'project_id': json_home.Parameters.PROJECT_ID,
- 'user_id': json_home.Parameters.USER_ID,
- 'role_id': json_home.Parameters.ROLE_ID,
- })
- self._add_resource(
- mapper, grant_controller,
- path='/OS-INHERIT/projects/{project_id}/groups/{group_id}/'
- 'roles/{role_id}/inherited_to_projects',
- get_head_action='check_grant',
- put_action='create_grant',
- delete_action='revoke_grant',
- rel=build_os_inherit_relation(
- resource_name='project_group_role_inherited_to_projects'),
- path_vars={
- 'project_id': json_home.Parameters.PROJECT_ID,
- 'group_id': json_home.Parameters.GROUP_ID,
- 'role_id': json_home.Parameters.ROLE_ID,
- })
diff --git a/keystone-moon/keystone/assignment/schema.py b/keystone-moon/keystone/assignment/schema.py
deleted file mode 100644
index f4d1b08a..00000000
--- a/keystone-moon/keystone/assignment/schema.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# 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 keystone.common.validation import parameter_types
-
-
-_role_properties = {
- 'name': parameter_types.name
-}
-
-role_create = {
- 'type': 'object',
- 'properties': _role_properties,
- 'required': ['name'],
- 'additionalProperties': True
-}
-
-role_update = {
- 'type': 'object',
- 'properties': _role_properties,
- 'minProperties': 1,
- 'additionalProperties': True
-}