diff options
Diffstat (limited to 'keystone-moon/keystone/assignment')
-rw-r--r-- | keystone-moon/keystone/assignment/V8_backends/__init__.py | 0 | ||||
-rw-r--r-- | keystone-moon/keystone/assignment/V8_backends/sql.py | 452 | ||||
-rw-r--r-- | keystone-moon/keystone/assignment/V8_role_backends/__init__.py | 0 | ||||
-rw-r--r-- | keystone-moon/keystone/assignment/V8_role_backends/sql.py | 80 | ||||
-rw-r--r-- | keystone-moon/keystone/assignment/__init__.py | 16 | ||||
-rw-r--r-- | keystone-moon/keystone/assignment/backends/__init__.py | 0 | ||||
-rw-r--r-- | keystone-moon/keystone/assignment/backends/ldap.py | 545 | ||||
-rw-r--r-- | keystone-moon/keystone/assignment/backends/sql.py | 319 | ||||
-rw-r--r-- | keystone-moon/keystone/assignment/controllers.py | 972 | ||||
-rw-r--r-- | keystone-moon/keystone/assignment/core.py | 1790 | ||||
-rw-r--r-- | keystone-moon/keystone/assignment/role_backends/__init__.py | 0 | ||||
-rw-r--r-- | keystone-moon/keystone/assignment/role_backends/ldap.py | 125 | ||||
-rw-r--r-- | keystone-moon/keystone/assignment/role_backends/sql.py | 202 | ||||
-rw-r--r-- | keystone-moon/keystone/assignment/routers.py | 282 | ||||
-rw-r--r-- | keystone-moon/keystone/assignment/schema.py | 32 |
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 -} |