diff options
Diffstat (limited to 'keystone-moon/keystone/identity')
16 files changed, 0 insertions, 3224 deletions
diff --git a/keystone-moon/keystone/identity/__init__.py b/keystone-moon/keystone/identity/__init__.py deleted file mode 100644 index 96b3ee77..00000000 --- a/keystone-moon/keystone/identity/__init__.py +++ /dev/null @@ -1,17 +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. - -from keystone.identity import controllers # noqa -from keystone.identity.core import * # noqa -from keystone.identity import generator # noqa diff --git a/keystone-moon/keystone/identity/backends/__init__.py b/keystone-moon/keystone/identity/backends/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/keystone-moon/keystone/identity/backends/__init__.py +++ /dev/null diff --git a/keystone-moon/keystone/identity/backends/ldap.py b/keystone-moon/keystone/identity/backends/ldap.py deleted file mode 100644 index fe8e8477..00000000 --- a/keystone-moon/keystone/identity/backends/ldap.py +++ /dev/null @@ -1,425 +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. -from __future__ import absolute_import -import uuid - -import ldap.filter -from oslo_config import cfg -from oslo_log import log -from oslo_log import versionutils -import six - -from keystone.common import clean -from keystone.common import driver_hints -from keystone.common import ldap as common_ldap -from keystone.common import models -from keystone import exception -from keystone.i18n import _ -from keystone import identity - - -CONF = cfg.CONF -LOG = log.getLogger(__name__) - -_DEPRECATION_MSG = _('%s for the LDAP identity backend has been deprecated in ' - 'the Mitaka release in favor of read-only identity LDAP ' - 'access. It will be removed in the "O" release.') - - -class Identity(identity.IdentityDriverV8): - def __init__(self, conf=None): - super(Identity, self).__init__() - if conf is None: - self.conf = CONF - else: - self.conf = conf - self.user = UserApi(self.conf) - self.group = GroupApi(self.conf) - - def is_domain_aware(self): - return False - - def generates_uuids(self): - return False - - # Identity interface - - def authenticate(self, user_id, password): - try: - user_ref = self._get_user(user_id) - except exception.UserNotFound: - raise AssertionError(_('Invalid user / password')) - if not user_id or not password: - raise AssertionError(_('Invalid user / password')) - conn = None - try: - conn = self.user.get_connection(user_ref['dn'], - password, end_user_auth=True) - if not conn: - raise AssertionError(_('Invalid user / password')) - except Exception: - raise AssertionError(_('Invalid user / password')) - finally: - if conn: - conn.unbind_s() - return self.user.filter_attributes(user_ref) - - def _get_user(self, user_id): - return self.user.get(user_id) - - def get_user(self, user_id): - return self.user.get_filtered(user_id) - - def list_users(self, hints): - return self.user.get_all_filtered(hints) - - def get_user_by_name(self, user_name, domain_id): - # domain_id will already have been handled in the Manager layer, - # parameter left in so this matches the Driver specification - return self.user.filter_attributes(self.user.get_by_name(user_name)) - - # CRUD - def create_user(self, user_id, user): - msg = _DEPRECATION_MSG % "create_user" - versionutils.report_deprecated_feature(LOG, msg) - self.user.check_allow_create() - user_ref = self.user.create(user) - return self.user.filter_attributes(user_ref) - - def update_user(self, user_id, user): - msg = _DEPRECATION_MSG % "update_user" - versionutils.report_deprecated_feature(LOG, msg) - self.user.check_allow_update() - old_obj = self.user.get(user_id) - if 'name' in user and old_obj.get('name') != user['name']: - raise exception.Conflict(_('Cannot change user name')) - - if self.user.enabled_mask: - self.user.mask_enabled_attribute(user) - elif self.user.enabled_invert and not self.user.enabled_emulation: - # We need to invert the enabled value for the old model object - # to prevent the LDAP update code from thinking that the enabled - # values are already equal. - user['enabled'] = not user['enabled'] - old_obj['enabled'] = not old_obj['enabled'] - - self.user.update(user_id, user, old_obj) - return self.user.get_filtered(user_id) - - def delete_user(self, user_id): - msg = _DEPRECATION_MSG % "delete_user" - versionutils.report_deprecated_feature(LOG, msg) - self.user.check_allow_delete() - user = self.user.get(user_id) - user_dn = user['dn'] - groups = self.group.list_user_groups(user_dn) - for group in groups: - self.group.remove_user(user_dn, group['id'], user_id) - - if hasattr(user, 'tenant_id'): - self.project.remove_user(user.tenant_id, user_dn) - self.user.delete(user_id) - - def create_group(self, group_id, group): - msg = _DEPRECATION_MSG % "create_group" - versionutils.report_deprecated_feature(LOG, msg) - self.group.check_allow_create() - group['name'] = clean.group_name(group['name']) - return common_ldap.filter_entity(self.group.create(group)) - - def get_group(self, group_id): - return self.group.get_filtered(group_id) - - def get_group_by_name(self, group_name, domain_id): - # domain_id will already have been handled in the Manager layer, - # parameter left in so this matches the Driver specification - return self.group.get_filtered_by_name(group_name) - - def update_group(self, group_id, group): - msg = _DEPRECATION_MSG % "update_group" - versionutils.report_deprecated_feature(LOG, msg) - self.group.check_allow_update() - if 'name' in group: - group['name'] = clean.group_name(group['name']) - return common_ldap.filter_entity(self.group.update(group_id, group)) - - def delete_group(self, group_id): - msg = _DEPRECATION_MSG % "delete_group" - versionutils.report_deprecated_feature(LOG, msg) - self.group.check_allow_delete() - return self.group.delete(group_id) - - def add_user_to_group(self, user_id, group_id): - msg = _DEPRECATION_MSG % "add_user_to_group" - versionutils.report_deprecated_feature(LOG, msg) - user_ref = self._get_user(user_id) - user_dn = user_ref['dn'] - self.group.add_user(user_dn, group_id, user_id) - - def remove_user_from_group(self, user_id, group_id): - msg = _DEPRECATION_MSG % "remove_user_from_group" - versionutils.report_deprecated_feature(LOG, msg) - user_ref = self._get_user(user_id) - user_dn = user_ref['dn'] - self.group.remove_user(user_dn, group_id, user_id) - - def list_groups_for_user(self, user_id, hints): - user_ref = self._get_user(user_id) - if self.conf.ldap.group_members_are_ids: - user_dn = user_ref['id'] - else: - user_dn = user_ref['dn'] - return self.group.list_user_groups_filtered(user_dn, hints) - - def list_groups(self, hints): - return self.group.get_all_filtered(hints) - - def list_users_in_group(self, group_id, hints): - users = [] - for user_key in self.group.list_group_users(group_id): - if self.conf.ldap.group_members_are_ids: - user_id = user_key - else: - user_id = self.user._dn_to_id(user_key) - - try: - users.append(self.user.get_filtered(user_id)) - except exception.UserNotFound: - LOG.debug(("Group member '%(user_key)s' not found in" - " '%(group_id)s'. The user should be removed" - " from the group. The user will be ignored."), - dict(user_key=user_key, group_id=group_id)) - return users - - def check_user_in_group(self, user_id, group_id): - user_refs = self.list_users_in_group(group_id, driver_hints.Hints()) - for x in user_refs: - if x['id'] == user_id: - break - else: - # Try to fetch the user to see if it even exists. This - # will raise a more accurate exception. - self.get_user(user_id) - raise exception.NotFound(_("User '%(user_id)s' not found in" - " group '%(group_id)s'") % - {'user_id': user_id, - 'group_id': group_id}) - - -# TODO(termie): turn this into a data object and move logic to driver -class UserApi(common_ldap.EnabledEmuMixIn, common_ldap.BaseLdap): - DEFAULT_OU = 'ou=Users' - DEFAULT_STRUCTURAL_CLASSES = ['person'] - DEFAULT_ID_ATTR = 'cn' - DEFAULT_OBJECTCLASS = 'inetOrgPerson' - NotFound = exception.UserNotFound - options_name = 'user' - attribute_options_names = {'password': 'pass', - 'email': 'mail', - 'name': 'name', - 'description': 'description', - 'enabled': 'enabled', - 'default_project_id': 'default_project_id'} - immutable_attrs = ['id'] - - model = models.User - - def __init__(self, conf): - super(UserApi, self).__init__(conf) - self.enabled_mask = conf.ldap.user_enabled_mask - self.enabled_default = conf.ldap.user_enabled_default - self.enabled_invert = conf.ldap.user_enabled_invert - self.enabled_emulation = conf.ldap.user_enabled_emulation - - def _ldap_res_to_model(self, res): - obj = super(UserApi, self)._ldap_res_to_model(res) - if self.enabled_mask != 0: - enabled = int(obj.get('enabled', self.enabled_default)) - obj['enabled'] = ((enabled & self.enabled_mask) != - self.enabled_mask) - elif self.enabled_invert and not self.enabled_emulation: - # This could be a bool or a string. If it's a string, - # we need to convert it so we can invert it properly. - enabled = obj.get('enabled', self.enabled_default) - if isinstance(enabled, six.string_types): - if enabled.lower() == 'true': - enabled = True - else: - enabled = False - obj['enabled'] = not enabled - obj['dn'] = res[0] - - return obj - - def mask_enabled_attribute(self, values): - value = values['enabled'] - values.setdefault('enabled_nomask', int(self.enabled_default)) - if value != ((values['enabled_nomask'] & self.enabled_mask) != - self.enabled_mask): - values['enabled_nomask'] ^= self.enabled_mask - values['enabled'] = values['enabled_nomask'] - del values['enabled_nomask'] - - def create(self, values): - if self.enabled_mask: - orig_enabled = values['enabled'] - self.mask_enabled_attribute(values) - elif self.enabled_invert and not self.enabled_emulation: - orig_enabled = values['enabled'] - if orig_enabled is not None: - values['enabled'] = not orig_enabled - else: - values['enabled'] = self.enabled_default - values = super(UserApi, self).create(values) - if self.enabled_mask or (self.enabled_invert and - not self.enabled_emulation): - values['enabled'] = orig_enabled - return values - - def get_filtered(self, user_id): - user = self.get(user_id) - return self.filter_attributes(user) - - def get_all_filtered(self, hints): - query = self.filter_query(hints, self.ldap_filter) - return [self.filter_attributes(user) - for user in self.get_all(query, hints)] - - def filter_attributes(self, user): - return identity.filter_user(common_ldap.filter_entity(user)) - - def is_user(self, dn): - """Returns True if the entry is a user.""" - # NOTE(blk-u): It's easy to check if the DN is under the User tree, - # but may not be accurate. A more accurate test would be to fetch the - # entry to see if it's got the user objectclass, but this could be - # really expensive considering how this is used. - - return common_ldap.dn_startswith(dn, self.tree_dn) - - -class GroupApi(common_ldap.BaseLdap): - DEFAULT_OU = 'ou=UserGroups' - DEFAULT_STRUCTURAL_CLASSES = [] - DEFAULT_OBJECTCLASS = 'groupOfNames' - DEFAULT_ID_ATTR = 'cn' - DEFAULT_MEMBER_ATTRIBUTE = 'member' - NotFound = exception.GroupNotFound - options_name = 'group' - attribute_options_names = {'description': 'desc', - 'name': 'name'} - immutable_attrs = ['name'] - model = models.Group - - def _ldap_res_to_model(self, res): - model = super(GroupApi, self)._ldap_res_to_model(res) - model['dn'] = res[0] - return model - - def __init__(self, conf): - super(GroupApi, self).__init__(conf) - self.member_attribute = (conf.ldap.group_member_attribute - or self.DEFAULT_MEMBER_ATTRIBUTE) - - def create(self, values): - data = values.copy() - if data.get('id') is None: - data['id'] = uuid.uuid4().hex - if 'description' in data and data['description'] in ['', None]: - data.pop('description') - return super(GroupApi, self).create(data) - - def delete(self, group_id): - if self.subtree_delete_enabled: - super(GroupApi, self).delete_tree(group_id) - else: - # TODO(spzala): this is only placeholder for group and domain - # role support which will be added under bug 1101287 - - group_ref = self.get(group_id) - group_dn = group_ref['dn'] - if group_dn: - self._delete_tree_nodes(group_dn, ldap.SCOPE_ONELEVEL) - super(GroupApi, self).delete(group_id) - - def update(self, group_id, values): - old_obj = self.get(group_id) - return super(GroupApi, self).update(group_id, values, old_obj) - - def add_user(self, user_dn, group_id, user_id): - group_ref = self.get(group_id) - group_dn = group_ref['dn'] - try: - super(GroupApi, self).add_member(user_dn, group_dn) - except exception.Conflict: - raise exception.Conflict(_( - 'User %(user_id)s is already a member of group %(group_id)s') % - {'user_id': user_id, 'group_id': group_id}) - - def remove_user(self, user_dn, group_id, user_id): - group_ref = self.get(group_id) - group_dn = group_ref['dn'] - try: - super(GroupApi, self).remove_member(user_dn, group_dn) - except ldap.NO_SUCH_ATTRIBUTE: - raise exception.UserNotFound(user_id=user_id) - - def list_user_groups(self, user_dn): - """Return a list of groups for which the user is a member.""" - user_dn_esc = ldap.filter.escape_filter_chars(user_dn) - query = '(%s=%s)%s' % (self.member_attribute, - user_dn_esc, - self.ldap_filter or '') - return self.get_all(query) - - def list_user_groups_filtered(self, user_dn, hints): - """Return a filtered list of groups for which the user is a member.""" - user_dn_esc = ldap.filter.escape_filter_chars(user_dn) - query = '(%s=%s)%s' % (self.member_attribute, - user_dn_esc, - self.ldap_filter or '') - return self.get_all_filtered(hints, query) - - def list_group_users(self, group_id): - """Return a list of user dns which are members of a group.""" - group_ref = self.get(group_id) - group_dn = group_ref['dn'] - - try: - attrs = self._ldap_get_list(group_dn, ldap.SCOPE_BASE, - attrlist=[self.member_attribute]) - except ldap.NO_SUCH_OBJECT: - raise self.NotFound(group_id=group_id) - - users = [] - for dn, member in attrs: - user_dns = member.get(self.member_attribute, []) - for user_dn in user_dns: - if self._is_dumb_member(user_dn): - continue - users.append(user_dn) - return users - - def get_filtered(self, group_id): - group = self.get(group_id) - return common_ldap.filter_entity(group) - - def get_filtered_by_name(self, group_name): - group = self.get_by_name(group_name) - return common_ldap.filter_entity(group) - - def get_all_filtered(self, hints, query=None): - query = self.filter_query(hints, query) - return [common_ldap.filter_entity(group) - for group in self.get_all(query, hints)] diff --git a/keystone-moon/keystone/identity/backends/sql.py b/keystone-moon/keystone/identity/backends/sql.py deleted file mode 100644 index 5680a8a2..00000000 --- a/keystone-moon/keystone/identity/backends/sql.py +++ /dev/null @@ -1,402 +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. - -import sqlalchemy -from sqlalchemy.ext.hybrid import hybrid_property -from sqlalchemy import orm - -from keystone.common import driver_hints -from keystone.common import sql -from keystone.common import utils -from keystone import exception -from keystone.i18n import _ -from keystone import identity - - -class User(sql.ModelBase, sql.DictBase): - __tablename__ = 'user' - attributes = ['id', 'name', 'domain_id', 'password', 'enabled', - 'default_project_id'] - id = sql.Column(sql.String(64), primary_key=True) - enabled = sql.Column(sql.Boolean) - extra = sql.Column(sql.JsonBlob()) - default_project_id = sql.Column(sql.String(64)) - local_user = orm.relationship('LocalUser', uselist=False, - single_parent=True, lazy='subquery', - cascade='all,delete-orphan', backref='user') - federated_users = orm.relationship('FederatedUser', - single_parent=True, - lazy='subquery', - cascade='all,delete-orphan', - backref='user') - - # name property - @hybrid_property - def name(self): - if self.local_user: - return self.local_user.name - elif self.federated_users: - return self.federated_users[0].display_name - else: - return None - - @name.setter - def name(self, value): - if not self.local_user: - self.local_user = LocalUser() - self.local_user.name = value - - @name.expression - def name(cls): - return LocalUser.name - - # password property - @hybrid_property - def password(self): - if self.local_user and self.local_user.passwords: - return self.local_user.passwords[0].password - else: - return None - - @password.setter - def password(self, value): - if not value: - if self.local_user and self.local_user.passwords: - self.local_user.passwords = [] - else: - if not self.local_user: - self.local_user = LocalUser() - if not self.local_user.passwords: - self.local_user.passwords.append(Password()) - self.local_user.passwords[0].password = value - - @password.expression - def password(cls): - return Password.password - - # domain_id property - @hybrid_property - def domain_id(self): - if self.local_user: - return self.local_user.domain_id - else: - return None - - @domain_id.setter - def domain_id(self, value): - if not self.local_user: - self.local_user = LocalUser() - self.local_user.domain_id = value - - @domain_id.expression - def domain_id(cls): - return LocalUser.domain_id - - def to_dict(self, include_extra_dict=False): - d = super(User, self).to_dict(include_extra_dict=include_extra_dict) - if 'default_project_id' in d and d['default_project_id'] is None: - del d['default_project_id'] - return d - - -class LocalUser(sql.ModelBase, sql.DictBase): - __tablename__ = 'local_user' - attributes = ['id', 'user_id', 'domain_id', 'name'] - id = sql.Column(sql.Integer, primary_key=True) - user_id = sql.Column(sql.String(64), sql.ForeignKey('user.id', - ondelete='CASCADE'), unique=True) - domain_id = sql.Column(sql.String(64), nullable=False) - name = sql.Column(sql.String(255), nullable=False) - passwords = orm.relationship('Password', single_parent=True, - cascade='all,delete-orphan', - backref='local_user') - __table_args__ = (sql.UniqueConstraint('domain_id', 'name'), {}) - - -class Password(sql.ModelBase, sql.DictBase): - __tablename__ = 'password' - attributes = ['id', 'local_user_id', 'password'] - id = sql.Column(sql.Integer, primary_key=True) - local_user_id = sql.Column(sql.Integer, sql.ForeignKey('local_user.id', - ondelete='CASCADE')) - password = sql.Column(sql.String(128)) - - -class FederatedUser(sql.ModelBase, sql.ModelDictMixin): - __tablename__ = 'federated_user' - attributes = ['id', 'user_id', 'idp_id', 'protocol_id', 'unique_id', - 'display_name'] - id = sql.Column(sql.Integer, primary_key=True) - user_id = sql.Column(sql.String(64), sql.ForeignKey('user.id', - ondelete='CASCADE')) - idp_id = sql.Column(sql.String(64), sql.ForeignKey('identity_provider.id', - ondelete='CASCADE')) - protocol_id = sql.Column(sql.String(64), nullable=False) - unique_id = sql.Column(sql.String(255), nullable=False) - display_name = sql.Column(sql.String(255), nullable=True) - __table_args__ = ( - sql.UniqueConstraint('idp_id', 'protocol_id', 'unique_id'), - sqlalchemy.ForeignKeyConstraint(['protocol_id', 'idp_id'], - ['federation_protocol.id', - 'federation_protocol.idp_id']) - ) - - -class Group(sql.ModelBase, sql.DictBase): - __tablename__ = 'group' - attributes = ['id', 'name', 'domain_id', 'description'] - id = sql.Column(sql.String(64), primary_key=True) - name = sql.Column(sql.String(64), nullable=False) - domain_id = sql.Column(sql.String(64), nullable=False) - description = sql.Column(sql.Text()) - extra = sql.Column(sql.JsonBlob()) - # Unique constraint across two columns to create the separation - # rather than just only 'name' being unique - __table_args__ = (sql.UniqueConstraint('domain_id', 'name'),) - - -class UserGroupMembership(sql.ModelBase, sql.DictBase): - """Group membership join table.""" - - __tablename__ = 'user_group_membership' - user_id = sql.Column(sql.String(64), - sql.ForeignKey('user.id'), - primary_key=True) - group_id = sql.Column(sql.String(64), - sql.ForeignKey('group.id'), - primary_key=True) - - -class Identity(identity.IdentityDriverV8): - # NOTE(henry-nash): Override the __init__() method so as to take a - # config parameter to enable sql to be used as a domain-specific driver. - def __init__(self, conf=None): - self.conf = conf - super(Identity, self).__init__() - - @property - def is_sql(self): - return True - - def _check_password(self, password, user_ref): - """Check the specified password against the data store. - - Note that we'll pass in the entire user_ref in case the subclass - needs things like user_ref.get('name') - For further justification, please see the follow up suggestion at - https://blueprints.launchpad.net/keystone/+spec/sql-identiy-pam - - """ - return utils.check_password(password, user_ref.password) - - # Identity interface - def authenticate(self, user_id, password): - with sql.session_for_read() as session: - user_ref = None - try: - user_ref = self._get_user(session, user_id) - except exception.UserNotFound: - raise AssertionError(_('Invalid user / password')) - if not self._check_password(password, user_ref): - raise AssertionError(_('Invalid user / password')) - return identity.filter_user(user_ref.to_dict()) - - # user crud - - @sql.handle_conflicts(conflict_type='user') - def create_user(self, user_id, user): - user = utils.hash_user_password(user) - with sql.session_for_write() as session: - user_ref = User.from_dict(user) - session.add(user_ref) - return identity.filter_user(user_ref.to_dict()) - - @driver_hints.truncated - def list_users(self, hints): - with sql.session_for_read() as session: - query = session.query(User).outerjoin(LocalUser) - user_refs = sql.filter_limit_query(User, query, hints) - return [identity.filter_user(x.to_dict()) for x in user_refs] - - def _get_user(self, session, user_id): - user_ref = session.query(User).get(user_id) - if not user_ref: - raise exception.UserNotFound(user_id=user_id) - return user_ref - - def get_user(self, user_id): - with sql.session_for_read() as session: - return identity.filter_user( - self._get_user(session, user_id).to_dict()) - - def get_user_by_name(self, user_name, domain_id): - with sql.session_for_read() as session: - query = session.query(User).join(LocalUser) - query = query.filter(sqlalchemy.and_(LocalUser.name == user_name, - LocalUser.domain_id == domain_id)) - try: - user_ref = query.one() - except sql.NotFound: - raise exception.UserNotFound(user_id=user_name) - return identity.filter_user(user_ref.to_dict()) - - @sql.handle_conflicts(conflict_type='user') - def update_user(self, user_id, user): - with sql.session_for_write() as session: - user_ref = self._get_user(session, user_id) - old_user_dict = user_ref.to_dict() - user = utils.hash_user_password(user) - for k in user: - old_user_dict[k] = user[k] - new_user = User.from_dict(old_user_dict) - for attr in User.attributes: - if attr != 'id': - setattr(user_ref, attr, getattr(new_user, attr)) - user_ref.extra = new_user.extra - return identity.filter_user( - user_ref.to_dict(include_extra_dict=True)) - - def add_user_to_group(self, user_id, group_id): - with sql.session_for_write() as session: - self.get_group(group_id) - self.get_user(user_id) - query = session.query(UserGroupMembership) - query = query.filter_by(user_id=user_id) - query = query.filter_by(group_id=group_id) - rv = query.first() - if rv: - return - - session.add(UserGroupMembership(user_id=user_id, - group_id=group_id)) - - def check_user_in_group(self, user_id, group_id): - with sql.session_for_read() as session: - self.get_group(group_id) - self.get_user(user_id) - query = session.query(UserGroupMembership) - query = query.filter_by(user_id=user_id) - query = query.filter_by(group_id=group_id) - if not query.first(): - raise exception.NotFound(_("User '%(user_id)s' not found in" - " group '%(group_id)s'") % - {'user_id': user_id, - 'group_id': group_id}) - - def remove_user_from_group(self, user_id, group_id): - # We don't check if user or group are still valid and let the remove - # be tried anyway - in case this is some kind of clean-up operation - with sql.session_for_write() as session: - query = session.query(UserGroupMembership) - query = query.filter_by(user_id=user_id) - query = query.filter_by(group_id=group_id) - membership_ref = query.first() - if membership_ref is None: - # Check if the group and user exist to return descriptive - # exceptions. - self.get_group(group_id) - self.get_user(user_id) - raise exception.NotFound(_("User '%(user_id)s' not found in" - " group '%(group_id)s'") % - {'user_id': user_id, - 'group_id': group_id}) - session.delete(membership_ref) - - def list_groups_for_user(self, user_id, hints): - with sql.session_for_read() as session: - self.get_user(user_id) - query = session.query(Group).join(UserGroupMembership) - query = query.filter(UserGroupMembership.user_id == user_id) - query = sql.filter_limit_query(Group, query, hints) - return [g.to_dict() for g in query] - - def list_users_in_group(self, group_id, hints): - with sql.session_for_read() as session: - self.get_group(group_id) - query = session.query(User).outerjoin(LocalUser) - query = query.join(UserGroupMembership) - query = query.filter(UserGroupMembership.group_id == group_id) - query = sql.filter_limit_query(User, query, hints) - return [identity.filter_user(u.to_dict()) for u in query] - - def delete_user(self, user_id): - with sql.session_for_write() as session: - ref = self._get_user(session, user_id) - - q = session.query(UserGroupMembership) - q = q.filter_by(user_id=user_id) - q.delete(False) - - session.delete(ref) - - # group crud - - @sql.handle_conflicts(conflict_type='group') - def create_group(self, group_id, group): - with sql.session_for_write() as session: - ref = Group.from_dict(group) - session.add(ref) - return ref.to_dict() - - @driver_hints.truncated - def list_groups(self, hints): - with sql.session_for_read() as session: - query = session.query(Group) - refs = sql.filter_limit_query(Group, query, hints) - return [ref.to_dict() for ref in refs] - - def _get_group(self, session, group_id): - ref = session.query(Group).get(group_id) - if not ref: - raise exception.GroupNotFound(group_id=group_id) - return ref - - def get_group(self, group_id): - with sql.session_for_read() as session: - return self._get_group(session, group_id).to_dict() - - def get_group_by_name(self, group_name, domain_id): - with sql.session_for_read() as session: - query = session.query(Group) - query = query.filter_by(name=group_name) - query = query.filter_by(domain_id=domain_id) - try: - group_ref = query.one() - except sql.NotFound: - raise exception.GroupNotFound(group_id=group_name) - return group_ref.to_dict() - - @sql.handle_conflicts(conflict_type='group') - def update_group(self, group_id, group): - with sql.session_for_write() as session: - ref = self._get_group(session, group_id) - old_dict = ref.to_dict() - for k in group: - old_dict[k] = group[k] - new_group = Group.from_dict(old_dict) - for attr in Group.attributes: - if attr != 'id': - setattr(ref, attr, getattr(new_group, attr)) - ref.extra = new_group.extra - return ref.to_dict() - - def delete_group(self, group_id): - with sql.session_for_write() as session: - ref = self._get_group(session, group_id) - - q = session.query(UserGroupMembership) - q = q.filter_by(group_id=group_id) - q.delete(False) - - session.delete(ref) diff --git a/keystone-moon/keystone/identity/controllers.py b/keystone-moon/keystone/identity/controllers.py deleted file mode 100644 index 9e8ba6fc..00000000 --- a/keystone-moon/keystone/identity/controllers.py +++ /dev/null @@ -1,344 +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. - -"""Workflow Logic the Identity service.""" - -from oslo_config import cfg -from oslo_log import log - -from keystone.common import controller -from keystone.common import dependency -from keystone.common import validation -from keystone import exception -from keystone.i18n import _, _LW -from keystone.identity import schema -from keystone import notifications - - -CONF = cfg.CONF -LOG = log.getLogger(__name__) - - -@dependency.requires('assignment_api', 'identity_api', 'resource_api') -class User(controller.V2Controller): - - @controller.v2_deprecated - def get_user(self, context, user_id): - self.assert_admin(context) - ref = self.identity_api.get_user(user_id) - return {'user': self.v3_to_v2_user(ref)} - - @controller.v2_deprecated - def get_users(self, context): - # NOTE(termie): i can't imagine that this really wants all the data - # about every single user in the system... - if 'name' in context['query_string']: - return self.get_user_by_name( - context, context['query_string'].get('name')) - - self.assert_admin(context) - user_list = self.identity_api.list_users( - CONF.identity.default_domain_id) - return {'users': self.v3_to_v2_user(user_list)} - - @controller.v2_deprecated - def get_user_by_name(self, context, user_name): - self.assert_admin(context) - ref = self.identity_api.get_user_by_name( - user_name, CONF.identity.default_domain_id) - return {'user': self.v3_to_v2_user(ref)} - - # CRUD extension - @controller.v2_deprecated - def create_user(self, context, user): - user = self._normalize_OSKSADM_password_on_request(user) - user = self.normalize_username_in_request(user) - user = self._normalize_dict(user) - self.assert_admin(context) - - if 'name' not in user or not user['name']: - msg = _('Name field is required and cannot be empty') - raise exception.ValidationError(message=msg) - if 'enabled' in user and not isinstance(user['enabled'], bool): - msg = _('Enabled field must be a boolean') - raise exception.ValidationError(message=msg) - - default_project_id = user.pop('tenantId', None) - if default_project_id is not None: - # Check to see if the project is valid before moving on. - self.resource_api.get_project(default_project_id) - user['default_project_id'] = default_project_id - - self.resource_api.ensure_default_domain_exists() - - # The manager layer will generate the unique ID for users - user_ref = self._normalize_domain_id(context, user.copy()) - initiator = notifications._get_request_audit_info(context) - new_user_ref = self.v3_to_v2_user( - self.identity_api.create_user(user_ref, initiator)) - - if default_project_id is not None: - self.assignment_api.add_user_to_project(default_project_id, - new_user_ref['id']) - return {'user': new_user_ref} - - @controller.v2_deprecated - def update_user(self, context, user_id, user): - # NOTE(termie): this is really more of a patch than a put - user = self.normalize_username_in_request(user) - self.assert_admin(context) - - if 'enabled' in user and not isinstance(user['enabled'], bool): - msg = _('Enabled field should be a boolean') - raise exception.ValidationError(message=msg) - - default_project_id = user.pop('tenantId', None) - if default_project_id is not None: - user['default_project_id'] = default_project_id - - old_user_ref = self.v3_to_v2_user( - self.identity_api.get_user(user_id)) - - # Check whether a tenant is being added or changed for the user. - # Catch the case where the tenant is being changed for a user and also - # where a user previously had no tenant but a tenant is now being - # added for the user. - if (('tenantId' in old_user_ref and - old_user_ref['tenantId'] != default_project_id and - default_project_id is not None) or - ('tenantId' not in old_user_ref and - default_project_id is not None)): - # Make sure the new project actually exists before we perform the - # user update. - self.resource_api.get_project(default_project_id) - - initiator = notifications._get_request_audit_info(context) - user_ref = self.v3_to_v2_user( - self.identity_api.update_user(user_id, user, initiator)) - - # If 'tenantId' is in either ref, we might need to add or remove the - # user from a project. - if 'tenantId' in user_ref or 'tenantId' in old_user_ref: - if user_ref['tenantId'] != old_user_ref.get('tenantId'): - if old_user_ref.get('tenantId'): - try: - member_role_id = CONF.member_role_id - self.assignment_api.remove_role_from_user_and_project( - user_id, old_user_ref['tenantId'], member_role_id) - except exception.NotFound: - # NOTE(morganfainberg): This is not a critical error it - # just means that the user cannot be removed from the - # old tenant. This could occur if roles aren't found - # or if the project is invalid or if there are no roles - # for the user on that project. - msg = _LW('Unable to remove user %(user)s from ' - '%(tenant)s.') - LOG.warning(msg, {'user': user_id, - 'tenant': old_user_ref['tenantId']}) - - if user_ref['tenantId']: - try: - self.assignment_api.add_user_to_project( - user_ref['tenantId'], user_id) - except exception.Conflict: # nosec - # We are already a member of that tenant - pass - except exception.NotFound: - # NOTE(morganfainberg): Log this and move on. This is - # not the end of the world if we can't add the user to - # the appropriate tenant. Most of the time this means - # that the project is invalid or roles are some how - # incorrect. This shouldn't prevent the return of the - # new ref. - msg = _LW('Unable to add user %(user)s to %(tenant)s.') - LOG.warning(msg, {'user': user_id, - 'tenant': user_ref['tenantId']}) - - return {'user': user_ref} - - @controller.v2_deprecated - def delete_user(self, context, user_id): - self.assert_admin(context) - initiator = notifications._get_request_audit_info(context) - self.identity_api.delete_user(user_id, initiator) - - @controller.v2_deprecated - def set_user_enabled(self, context, user_id, user): - return self.update_user(context, user_id, user) - - @controller.v2_deprecated - def set_user_password(self, context, user_id, user): - user = self._normalize_OSKSADM_password_on_request(user) - return self.update_user(context, user_id, user) - - @staticmethod - def _normalize_OSKSADM_password_on_request(ref): - """Sets the password from the OS-KSADM Admin Extension. - - The OS-KSADM Admin Extension documentation says that - `OS-KSADM:password` can be used in place of `password`. - - """ - if 'OS-KSADM:password' in ref: - ref['password'] = ref.pop('OS-KSADM:password') - return ref - - -@dependency.requires('identity_api') -class UserV3(controller.V3Controller): - collection_name = 'users' - member_name = 'user' - - def __init__(self): - super(UserV3, self).__init__() - self.get_member_from_driver = self.identity_api.get_user - - def _check_user_and_group_protection(self, context, prep_info, - user_id, group_id): - ref = {} - ref['user'] = self.identity_api.get_user(user_id) - ref['group'] = self.identity_api.get_group(group_id) - self.check_protection(context, prep_info, ref) - - @controller.protected() - @validation.validated(schema.user_create, 'user') - def create_user(self, context, user): - # The manager layer will generate the unique ID for users - ref = self._normalize_dict(user) - ref = self._normalize_domain_id(context, ref) - initiator = notifications._get_request_audit_info(context) - ref = self.identity_api.create_user(ref, initiator) - return UserV3.wrap_member(context, ref) - - @controller.filterprotected('domain_id', 'enabled', 'name') - def list_users(self, context, filters): - hints = UserV3.build_driver_hints(context, filters) - refs = self.identity_api.list_users( - domain_scope=self._get_domain_id_for_list_request(context), - hints=hints) - return UserV3.wrap_collection(context, refs, hints=hints) - - @controller.filterprotected('domain_id', 'enabled', 'name') - def list_users_in_group(self, context, filters, group_id): - hints = UserV3.build_driver_hints(context, filters) - refs = self.identity_api.list_users_in_group(group_id, hints=hints) - return UserV3.wrap_collection(context, refs, hints=hints) - - @controller.protected() - def get_user(self, context, user_id): - ref = self.identity_api.get_user(user_id) - return UserV3.wrap_member(context, ref) - - def _update_user(self, context, user_id, user): - self._require_matching_id(user_id, user) - self._require_matching_domain_id( - user_id, user, self.identity_api.get_user) - initiator = notifications._get_request_audit_info(context) - ref = self.identity_api.update_user(user_id, user, initiator) - return UserV3.wrap_member(context, ref) - - @controller.protected() - @validation.validated(schema.user_update, 'user') - def update_user(self, context, user_id, user): - return self._update_user(context, user_id, user) - - @controller.protected(callback=_check_user_and_group_protection) - def add_user_to_group(self, context, user_id, group_id): - initiator = notifications._get_request_audit_info(context) - self.identity_api.add_user_to_group(user_id, group_id, initiator) - - @controller.protected(callback=_check_user_and_group_protection) - def check_user_in_group(self, context, user_id, group_id): - return self.identity_api.check_user_in_group(user_id, group_id) - - @controller.protected(callback=_check_user_and_group_protection) - def remove_user_from_group(self, context, user_id, group_id): - initiator = notifications._get_request_audit_info(context) - self.identity_api.remove_user_from_group(user_id, group_id, initiator) - - @controller.protected() - def delete_user(self, context, user_id): - initiator = notifications._get_request_audit_info(context) - return self.identity_api.delete_user(user_id, initiator) - - @controller.protected() - def change_password(self, context, user_id, user): - original_password = user.get('original_password') - if original_password is None: - raise exception.ValidationError(target='user', - attribute='original_password') - - password = user.get('password') - if password is None: - raise exception.ValidationError(target='user', - attribute='password') - try: - self.identity_api.change_password( - context, user_id, original_password, password) - except AssertionError: - raise exception.Unauthorized() - - -@dependency.requires('identity_api') -class GroupV3(controller.V3Controller): - collection_name = 'groups' - member_name = 'group' - - def __init__(self): - super(GroupV3, self).__init__() - self.get_member_from_driver = self.identity_api.get_group - - @controller.protected() - @validation.validated(schema.group_create, 'group') - def create_group(self, context, group): - # The manager layer will generate the unique ID for groups - ref = self._normalize_dict(group) - ref = self._normalize_domain_id(context, ref) - initiator = notifications._get_request_audit_info(context) - ref = self.identity_api.create_group(ref, initiator) - return GroupV3.wrap_member(context, ref) - - @controller.filterprotected('domain_id', 'name') - def list_groups(self, context, filters): - hints = GroupV3.build_driver_hints(context, filters) - refs = self.identity_api.list_groups( - domain_scope=self._get_domain_id_for_list_request(context), - hints=hints) - return GroupV3.wrap_collection(context, refs, hints=hints) - - @controller.filterprotected('name') - def list_groups_for_user(self, context, filters, user_id): - hints = GroupV3.build_driver_hints(context, filters) - refs = self.identity_api.list_groups_for_user(user_id, hints=hints) - return GroupV3.wrap_collection(context, refs, hints=hints) - - @controller.protected() - def get_group(self, context, group_id): - ref = self.identity_api.get_group(group_id) - return GroupV3.wrap_member(context, ref) - - @controller.protected() - @validation.validated(schema.group_update, 'group') - def update_group(self, context, group_id, group): - self._require_matching_id(group_id, group) - self._require_matching_domain_id( - group_id, group, self.identity_api.get_group) - initiator = notifications._get_request_audit_info(context) - ref = self.identity_api.update_group(group_id, group, initiator) - return GroupV3.wrap_member(context, ref) - - @controller.protected() - def delete_group(self, context, group_id): - initiator = notifications._get_request_audit_info(context) - self.identity_api.delete_group(group_id, initiator) diff --git a/keystone-moon/keystone/identity/core.py b/keystone-moon/keystone/identity/core.py deleted file mode 100644 index 2f52a358..00000000 --- a/keystone-moon/keystone/identity/core.py +++ /dev/null @@ -1,1613 +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 Identity service.""" - -import abc -import functools -import os -import threading -import uuid - -from oslo_config import cfg -from oslo_log import log -from oslo_log import versionutils -import six - -from keystone import assignment # TODO(lbragstad): Decouple this dependency -from keystone.common import cache -from keystone.common import clean -from keystone.common import config -from keystone.common import dependency -from keystone.common import driver_hints -from keystone.common import manager -from keystone import exception -from keystone.i18n import _, _LW -from keystone.identity.mapping_backends import mapping -from keystone import notifications - - -CONF = cfg.CONF - -LOG = log.getLogger(__name__) - -MEMOIZE = cache.get_memoization_decorator(group='identity') - -DOMAIN_CONF_FHEAD = 'keystone.' -DOMAIN_CONF_FTAIL = '.conf' - -# The number of times we will attempt to register a domain to use the SQL -# driver, if we find that another process is in the middle of registering or -# releasing at the same time as us. -REGISTRATION_ATTEMPTS = 10 - -# Config Registration Types -SQL_DRIVER = 'SQL' - - -def filter_user(user_ref): - """Filter out private items in a user dict. - - 'password', 'tenants' and 'groups' are never returned. - - :returns: user_ref - - """ - if user_ref: - user_ref = user_ref.copy() - user_ref.pop('password', None) - user_ref.pop('tenants', None) - user_ref.pop('groups', None) - user_ref.pop('domains', None) - try: - user_ref['extra'].pop('password', None) - user_ref['extra'].pop('tenants', None) - except KeyError: # nosec - # ok to not have extra in the user_ref. - pass - return user_ref - - -@dependency.requires('domain_config_api', 'resource_api') -class DomainConfigs(dict): - """Discover, store and provide access to domain specific configs. - - The setup_domain_drivers() call will be made via the wrapper from - the first call to any driver function handled by this manager. - - Domain specific configurations are only supported for the identity backend - and the individual configurations are either specified in the resource - database or in individual domain configuration files, depending on the - setting of the 'domain_configurations_from_database' config option. - - The result will be that for each domain with a specific configuration, - this class will hold a reference to a ConfigOpts and driver object that - the identity manager and driver can use. - - """ - - configured = False - driver = None - _any_sql = False - lock = threading.Lock() - - def _load_driver(self, domain_config): - return manager.load_driver(Manager.driver_namespace, - domain_config['cfg'].identity.driver, - domain_config['cfg']) - - def _load_config_from_file(self, resource_api, file_list, domain_name): - - def _assert_no_more_than_one_sql_driver(domain_id, new_config, - config_file): - """Ensure there is no more than one sql driver. - - Check to see if the addition of the driver in this new config - would cause there to be more than one sql driver. - - """ - if (new_config['driver'].is_sql and - (self.driver.is_sql or self._any_sql)): - # The addition of this driver would cause us to have more than - # one sql driver, so raise an exception. - raise exception.MultipleSQLDriversInConfig(source=config_file) - self._any_sql = self._any_sql or new_config['driver'].is_sql - - try: - domain_ref = resource_api.get_domain_by_name(domain_name) - except exception.DomainNotFound: - LOG.warning( - _LW('Invalid domain name (%s) found in config file name'), - domain_name) - return - - # Create a new entry in the domain config dict, which contains - # a new instance of both the conf environment and driver using - # options defined in this set of config files. Later, when we - # service calls via this Manager, we'll index via this domain - # config dict to make sure we call the right driver - domain_config = {} - domain_config['cfg'] = cfg.ConfigOpts() - config.configure(conf=domain_config['cfg']) - domain_config['cfg'](args=[], project='keystone', - default_config_files=file_list) - domain_config['driver'] = self._load_driver(domain_config) - _assert_no_more_than_one_sql_driver(domain_ref['id'], - domain_config, - file_list) - self[domain_ref['id']] = domain_config - - def _setup_domain_drivers_from_files(self, standard_driver, resource_api): - """Read the domain specific configuration files and load the drivers. - - Domain configuration files are stored in the domain config directory, - and must be named of the form: - - keystone.<domain_name>.conf - - For each file, call the load config method where the domain_name - will be turned into a domain_id and then: - - - Create a new config structure, adding in the specific additional - options defined in this config file - - Initialise a new instance of the required driver with this new config - - """ - conf_dir = CONF.identity.domain_config_dir - if not os.path.exists(conf_dir): - LOG.warning(_LW('Unable to locate domain config directory: %s'), - conf_dir) - return - - for r, d, f in os.walk(conf_dir): - for fname in f: - if (fname.startswith(DOMAIN_CONF_FHEAD) and - fname.endswith(DOMAIN_CONF_FTAIL)): - if fname.count('.') >= 2: - self._load_config_from_file( - resource_api, [os.path.join(r, fname)], - fname[len(DOMAIN_CONF_FHEAD): - -len(DOMAIN_CONF_FTAIL)]) - else: - LOG.debug(('Ignoring file (%s) while scanning domain ' - 'config directory'), - fname) - - def _load_config_from_database(self, domain_id, specific_config): - - def _assert_no_more_than_one_sql_driver(domain_id, new_config): - """Ensure adding driver doesn't push us over the limit of 1 - - The checks we make in this method need to take into account that - we may be in a multiple process configuration and ensure that - any race conditions are avoided. - - """ - if not new_config['driver'].is_sql: - self.domain_config_api.release_registration(domain_id) - return - - # To ensure the current domain is the only SQL driver, we attempt - # to register our use of SQL. If we get it we know we are good, - # if we fail to register it then we should: - # - # - First check if another process has registered for SQL for our - # domain, in which case we are fine - # - If a different domain has it, we should check that this domain - # is still valid, in case, for example, domain deletion somehow - # failed to remove its registration (i.e. we self heal for these - # kinds of issues). - - domain_registered = 'Unknown' - for attempt in range(REGISTRATION_ATTEMPTS): - if self.domain_config_api.obtain_registration( - domain_id, SQL_DRIVER): - LOG.debug('Domain %s successfully registered to use the ' - 'SQL driver.', domain_id) - return - - # We failed to register our use, let's find out who is using it - try: - domain_registered = ( - self.domain_config_api.read_registration( - SQL_DRIVER)) - except exception.ConfigRegistrationNotFound: - msg = ('While attempting to register domain %(domain)s to ' - 'use the SQL driver, another process released it, ' - 'retrying (attempt %(attempt)s).') - LOG.debug(msg, {'domain': domain_id, - 'attempt': attempt + 1}) - continue - - if domain_registered == domain_id: - # Another process already registered it for us, so we are - # fine. In the race condition when another process is - # in the middle of deleting this domain, we know the domain - # is already disabled and hence telling the caller that we - # are registered is benign. - LOG.debug('While attempting to register domain %s to use ' - 'the SQL driver, found that another process had ' - 'already registered this domain. This is normal ' - 'in multi-process configurations.', domain_id) - return - - # So we don't have it, but someone else does...let's check that - # this domain is still valid - try: - self.resource_api.get_domain(domain_registered) - except exception.DomainNotFound: - msg = ('While attempting to register domain %(domain)s to ' - 'use the SQL driver, found that it was already ' - 'registered to a domain that no longer exists ' - '(%(old_domain)s). Removing this stale ' - 'registration and retrying (attempt %(attempt)s).') - LOG.debug(msg, {'domain': domain_id, - 'old_domain': domain_registered, - 'attempt': attempt + 1}) - self.domain_config_api.release_registration( - domain_registered, type=SQL_DRIVER) - continue - - # The domain is valid, so we really do have an attempt at more - # than one SQL driver. - details = ( - _('Config API entity at /domains/%s/config') % domain_id) - raise exception.MultipleSQLDriversInConfig(source=details) - - # We fell out of the loop without either registering our domain or - # being able to find who has it...either we were very very very - # unlucky or something is awry. - msg = _('Exceeded attempts to register domain %(domain)s to use ' - 'the SQL driver, the last domain that appears to have ' - 'had it is %(last_domain)s, giving up') % { - 'domain': domain_id, 'last_domain': domain_registered} - raise exception.UnexpectedError(msg) - - domain_config = {} - domain_config['cfg'] = cfg.ConfigOpts() - config.configure(conf=domain_config['cfg']) - domain_config['cfg'](args=[], project='keystone', - default_config_files=[]) - - # Override any options that have been passed in as specified in the - # database. - for group in specific_config: - for option in specific_config[group]: - domain_config['cfg'].set_override( - option, specific_config[group][option], - group, enforce_type=True) - - domain_config['cfg_overrides'] = specific_config - domain_config['driver'] = self._load_driver(domain_config) - _assert_no_more_than_one_sql_driver(domain_id, domain_config) - self[domain_id] = domain_config - - def _setup_domain_drivers_from_database(self, standard_driver, - resource_api): - """Read domain specific configuration from database and load drivers. - - Domain configurations are stored in the domain-config backend, - so we go through each domain to find those that have a specific config - defined, and for those that do we: - - - Create a new config structure, overriding any specific options - defined in the resource backend - - Initialise a new instance of the required driver with this new config - - """ - for domain in resource_api.list_domains(): - domain_config_options = ( - self.domain_config_api. - get_config_with_sensitive_info(domain['id'])) - if domain_config_options: - self._load_config_from_database(domain['id'], - domain_config_options) - - def setup_domain_drivers(self, standard_driver, resource_api): - # This is called by the api call wrapper - self.driver = standard_driver - - if CONF.identity.domain_configurations_from_database: - self._setup_domain_drivers_from_database(standard_driver, - resource_api) - else: - self._setup_domain_drivers_from_files(standard_driver, - resource_api) - self.configured = True - - def get_domain_driver(self, domain_id): - self.check_config_and_reload_domain_driver_if_required(domain_id) - if domain_id in self: - return self[domain_id]['driver'] - - def get_domain_conf(self, domain_id): - self.check_config_and_reload_domain_driver_if_required(domain_id) - if domain_id in self: - return self[domain_id]['cfg'] - else: - return CONF - - def reload_domain_driver(self, domain_id): - # Only used to support unit tests that want to set - # new config values. This should only be called once - # the domains have been configured, since it relies on - # the fact that the configuration files/database have already been - # read. - if self.configured: - if domain_id in self: - self[domain_id]['driver'] = ( - self._load_driver(self[domain_id])) - else: - # The standard driver - self.driver = self.driver() - - def check_config_and_reload_domain_driver_if_required(self, domain_id): - """Check for, and load, any new domain specific config for this domain. - - This is only supported for the database-stored domain specific - configuration. - - When the domain specific drivers were set up, we stored away the - specific config for this domain that was available at that time. So we - now read the current version and compare. While this might seem - somewhat inefficient, the sensitive config call is cached, so should be - light weight. More importantly, when the cache timeout is reached, we - will get any config that has been updated from any other keystone - process. - - This cache-timeout approach works for both multi-process and - multi-threaded keystone configurations. In multi-threaded - configurations, even though we might remove a driver object (that - could be in use by another thread), this won't actually be thrown away - until all references to it have been broken. When that other - thread is released back and is restarted with another command to - process, next time it accesses the driver it will pickup the new one. - - """ - if (not CONF.identity.domain_specific_drivers_enabled or - not CONF.identity.domain_configurations_from_database): - # If specific drivers are not enabled, then there is nothing to do. - # If we are not storing the configurations in the database, then - # we'll only re-read the domain specific config files on startup - # of keystone. - return - - latest_domain_config = ( - self.domain_config_api. - get_config_with_sensitive_info(domain_id)) - domain_config_in_use = domain_id in self - - if latest_domain_config: - if (not domain_config_in_use or - latest_domain_config != self[domain_id]['cfg_overrides']): - self._load_config_from_database(domain_id, - latest_domain_config) - elif domain_config_in_use: - # The domain specific config has been deleted, so should remove the - # specific driver for this domain. - try: - del self[domain_id] - except KeyError: # nosec - # Allow this error in case we are unlucky and in a - # multi-threaded situation, two threads happen to be running - # in lock step. - pass - # If we fall into the else condition, this means there is no domain - # config set, and there is none in use either, so we have nothing - # to do. - - -def domains_configured(f): - """Wraps API calls to lazy load domain configs after init. - - This is required since the assignment manager needs to be initialized - before this manager, and yet this manager's init wants to be - able to make assignment calls (to build the domain configs). So - instead, we check if the domains have been initialized on entry - to each call, and if requires load them, - - """ - @functools.wraps(f) - def wrapper(self, *args, **kwargs): - if (not self.domain_configs.configured and - CONF.identity.domain_specific_drivers_enabled): - # If domain specific driver has not been configured, acquire the - # lock and proceed with loading the driver. - with self.domain_configs.lock: - # Check again just in case some other thread has already - # completed domain config. - if not self.domain_configs.configured: - self.domain_configs.setup_domain_drivers( - self.driver, self.resource_api) - return f(self, *args, **kwargs) - return wrapper - - -def exception_translated(exception_type): - """Wraps API calls to map to correct exception.""" - def _exception_translated(f): - @functools.wraps(f) - def wrapper(self, *args, **kwargs): - try: - return f(self, *args, **kwargs) - except exception.PublicIDNotFound as e: - if exception_type == 'user': - raise exception.UserNotFound(user_id=str(e)) - elif exception_type == 'group': - raise exception.GroupNotFound(group_id=str(e)) - elif exception_type == 'assertion': - raise AssertionError(_('Invalid user / password')) - else: - raise - return wrapper - return _exception_translated - - -@notifications.listener -@dependency.provider('identity_api') -@dependency.requires('assignment_api', 'credential_api', 'id_mapping_api', - 'resource_api', 'revoke_api', 'shadow_users_api') -class Manager(manager.Manager): - """Default pivot point for the Identity backend. - - See :mod:`keystone.common.manager.Manager` for more details on how this - dynamically calls the backend. - - This class also handles the support of domain specific backends, by using - the DomainConfigs class. The setup call for DomainConfigs is called - from with the @domains_configured wrapper in a lazy loading fashion - to get around the fact that we can't satisfy the assignment api it needs - from within our __init__() function since the assignment driver is not - itself yet initialized. - - Each of the identity calls are pre-processed here to choose, based on - domain, which of the drivers should be called. The non-domain-specific - driver is still in place, and is used if there is no specific driver for - the domain in question (or we are not using multiple domain drivers). - - Starting with Juno, in order to be able to obtain the domain from - just an ID being presented as part of an API call, a public ID to domain - and local ID mapping is maintained. This mapping also allows for the local - ID of drivers that do not provide simple UUIDs (such as LDAP) to be - referenced via a public facing ID. The mapping itself is automatically - generated as entities are accessed via the driver. - - This mapping is only used when: - - the entity is being handled by anything other than the default driver, or - - the entity is being handled by the default LDAP driver and backward - compatible IDs are not required. - - This means that in the standard case of a single SQL backend or the default - settings of a single LDAP backend (since backward compatible IDs is set to - True by default), no mapping is used. An alternative approach would be to - always use the mapping table, but in the cases where we don't need it to - make the public and local IDs the same. It is felt that not using the - mapping by default is a more prudent way to introduce this functionality. - - """ - - driver_namespace = 'keystone.identity' - - _USER = 'user' - _GROUP = 'group' - - def __init__(self): - super(Manager, self).__init__(CONF.identity.driver) - self.domain_configs = DomainConfigs() - - self.event_callbacks = { - notifications.ACTIONS.deleted: { - 'domain': [self._domain_deleted], - }, - } - - def _domain_deleted(self, service, resource_type, operation, - payload): - domain_id = payload['resource_info'] - - user_refs = self.list_users(domain_scope=domain_id) - group_refs = self.list_groups(domain_scope=domain_id) - - for group in group_refs: - # Cleanup any existing groups. - try: - self.delete_group(group['id']) - except exception.GroupNotFound: - LOG.debug(('Group %(groupid)s not found when deleting domain ' - 'contents for %(domainid)s, continuing with ' - 'cleanup.'), - {'groupid': group['id'], 'domainid': domain_id}) - - # And finally, delete the users themselves - for user in user_refs: - try: - self.delete_user(user['id']) - except exception.UserNotFound: - LOG.debug(('User %(userid)s not found when deleting domain ' - 'contents for %(domainid)s, continuing with ' - 'cleanup.'), - {'userid': user['id'], 'domainid': domain_id}) - - # Domain ID normalization methods - - def _set_domain_id_and_mapping(self, ref, domain_id, driver, - entity_type): - """Patch the domain_id/public_id into the resulting entity(ies). - - :param ref: the entity or list of entities to post process - :param domain_id: the domain scope used for the call - :param driver: the driver used to execute the call - :param entity_type: whether this is a user or group - - :returns: post processed entity or list or entities - - Called to post-process the entity being returned, using a mapping - to substitute a public facing ID as necessary. This method must - take into account: - - - If the driver is not domain aware, then we must set the domain - attribute of all entities irrespective of mapping. - - If the driver does not support UUIDs, then we always want to provide - a mapping, except for the special case of this being the default - driver and backward_compatible_ids is set to True. This is to ensure - that entity IDs do not change for an existing LDAP installation (only - single domain/driver LDAP configurations were previously supported). - - If the driver does support UUIDs, then we always create a mapping - entry, but use the local UUID as the public ID. The exception to - - this is that if we just have single driver (i.e. not using specific - multi-domain configs), then we don't both with the mapping at all. - - """ - conf = CONF.identity - - if not self._needs_post_processing(driver): - # a classic case would be when running with a single SQL driver - return ref - - LOG.debug('ID Mapping - Domain ID: %(domain)s, ' - 'Default Driver: %(driver)s, ' - 'Domains: %(aware)s, UUIDs: %(generate)s, ' - 'Compatible IDs: %(compat)s', - {'domain': domain_id, - 'driver': (driver == self.driver), - 'aware': driver.is_domain_aware(), - 'generate': driver.generates_uuids(), - 'compat': CONF.identity_mapping.backward_compatible_ids}) - - if isinstance(ref, dict): - return self._set_domain_id_and_mapping_for_single_ref( - ref, domain_id, driver, entity_type, conf) - elif isinstance(ref, list): - return [self._set_domain_id_and_mapping( - x, domain_id, driver, entity_type) for x in ref] - else: - raise ValueError(_('Expected dict or list: %s') % type(ref)) - - def _needs_post_processing(self, driver): - """Returns whether entity from driver needs domain added or mapping.""" - return (driver is not self.driver or not driver.generates_uuids() or - not driver.is_domain_aware()) - - def _set_domain_id_and_mapping_for_single_ref(self, ref, domain_id, - driver, entity_type, conf): - LOG.debug('Local ID: %s', ref['id']) - ref = ref.copy() - - self._insert_domain_id_if_needed(ref, driver, domain_id, conf) - - if self._is_mapping_needed(driver): - local_entity = {'domain_id': ref['domain_id'], - 'local_id': ref['id'], - 'entity_type': entity_type} - public_id = self.id_mapping_api.get_public_id(local_entity) - if public_id: - ref['id'] = public_id - LOG.debug('Found existing mapping to public ID: %s', - ref['id']) - else: - # Need to create a mapping. If the driver generates UUIDs - # then pass the local UUID in as the public ID to use. - if driver.generates_uuids(): - public_id = ref['id'] - ref['id'] = self.id_mapping_api.create_id_mapping( - local_entity, public_id) - LOG.debug('Created new mapping to public ID: %s', - ref['id']) - return ref - - def _insert_domain_id_if_needed(self, ref, driver, domain_id, conf): - """Inserts the domain ID into the ref, if required. - - If the driver can't handle domains, then we need to insert the - domain_id into the entity being returned. If the domain_id is - None that means we are running in a single backend mode, so to - remain backwardly compatible, we put in the default domain ID. - """ - if not driver.is_domain_aware(): - if domain_id is None: - domain_id = conf.default_domain_id - ref['domain_id'] = domain_id - - def _is_mapping_needed(self, driver): - """Returns whether mapping is needed. - - There are two situations where we must use the mapping: - - this isn't the default driver (i.e. multiple backends), or - - we have a single backend that doesn't use UUIDs - The exception to the above is that we must honor backward - compatibility if this is the default driver (e.g. to support - current LDAP) - """ - is_not_default_driver = driver is not self.driver - return (is_not_default_driver or ( - not driver.generates_uuids() and - not CONF.identity_mapping.backward_compatible_ids)) - - def _clear_domain_id_if_domain_unaware(self, driver, ref): - """Clear domain_id details if driver is not domain aware.""" - if not driver.is_domain_aware() and 'domain_id' in ref: - ref = ref.copy() - ref.pop('domain_id') - return ref - - def _select_identity_driver(self, domain_id): - """Choose a backend driver for the given domain_id. - - :param domain_id: The domain_id for which we want to find a driver. If - the domain_id is specified as None, then this means - we need a driver that handles multiple domains. - - :returns: chosen backend driver - - If there is a specific driver defined for this domain then choose it. - If the domain is None, or there no specific backend for the given - domain is found, then we chose the default driver. - - """ - if domain_id is None: - driver = self.driver - else: - driver = (self.domain_configs.get_domain_driver(domain_id) or - self.driver) - - # If the driver is not domain aware (e.g. LDAP) then check to - # ensure we are not mapping multiple domains onto it - the only way - # that would happen is that the default driver is LDAP and the - # domain is anything other than None or the default domain. - if (not driver.is_domain_aware() and driver == self.driver and - domain_id != CONF.identity.default_domain_id and - domain_id is not None): - LOG.warning(_LW('Found multiple domains being mapped to a ' - 'driver that does not support that (e.g. ' - 'LDAP) - Domain ID: %(domain)s, ' - 'Default Driver: %(driver)s'), - {'domain': domain_id, - 'driver': (driver == self.driver)}) - raise exception.DomainNotFound(domain_id=domain_id) - return driver - - def _get_domain_driver_and_entity_id(self, public_id): - """Look up details using the public ID. - - :param public_id: the ID provided in the call - - :returns: domain_id, which can be None to indicate that the driver - in question supports multiple domains - driver selected based on this domain - entity_id which will is understood by the driver. - - Use the mapping table to look up the domain, driver and local entity - that is represented by the provided public ID. Handle the situations - where we do not use the mapping (e.g. single driver that understands - UUIDs etc.) - - """ - conf = CONF.identity - # First, since we don't know anything about the entity yet, we must - # assume it needs mapping, so long as we are using domain specific - # drivers. - if conf.domain_specific_drivers_enabled: - local_id_ref = self.id_mapping_api.get_id_mapping(public_id) - if local_id_ref: - return ( - local_id_ref['domain_id'], - self._select_identity_driver(local_id_ref['domain_id']), - local_id_ref['local_id']) - - # So either we are using multiple drivers but the public ID is invalid - # (and hence was not found in the mapping table), or the public ID is - # being handled by the default driver. Either way, the only place left - # to look is in that standard driver. However, we don't yet know if - # this driver also needs mapping (e.g. LDAP in non backward - # compatibility mode). - driver = self.driver - if driver.generates_uuids(): - if driver.is_domain_aware: - # No mapping required, and the driver can handle the domain - # information itself. The classic case of this is the - # current SQL driver. - return (None, driver, public_id) - else: - # Although we don't have any drivers of this type, i.e. that - # understand UUIDs but not domains, conceptually you could. - return (conf.default_domain_id, driver, public_id) - - # So the only place left to find the ID is in the default driver which - # we now know doesn't generate UUIDs - if not CONF.identity_mapping.backward_compatible_ids: - # We are not running in backward compatibility mode, so we - # must use a mapping. - local_id_ref = self.id_mapping_api.get_id_mapping(public_id) - if local_id_ref: - return ( - local_id_ref['domain_id'], - driver, - local_id_ref['local_id']) - else: - raise exception.PublicIDNotFound(id=public_id) - - # If we reach here, this means that the default driver - # requires no mapping - but also doesn't understand domains - # (e.g. the classic single LDAP driver situation). Hence we pass - # back the public_ID unmodified and use the default domain (to - # keep backwards compatibility with existing installations). - # - # It is still possible that the public ID is just invalid in - # which case we leave this to the caller to check. - return (conf.default_domain_id, driver, public_id) - - def _assert_user_and_group_in_same_backend( - self, user_entity_id, user_driver, group_entity_id, group_driver): - """Ensures that user and group IDs are backed by the same backend. - - Raise a CrossBackendNotAllowed exception if they are not from the same - backend, otherwise return None. - - """ - if user_driver is not group_driver: - # Determine first if either IDs don't exist by calling - # the driver.get methods (which will raise a NotFound - # exception). - user_driver.get_user(user_entity_id) - group_driver.get_group(group_entity_id) - # If we get here, then someone is attempting to create a cross - # backend membership, which is not allowed. - raise exception.CrossBackendNotAllowed(group_id=group_entity_id, - user_id=user_entity_id) - - def _mark_domain_id_filter_satisfied(self, hints): - if hints: - for filter in hints.filters: - if (filter['name'] == 'domain_id' and - filter['comparator'] == 'equals'): - hints.filters.remove(filter) - - def _ensure_domain_id_in_hints(self, hints, domain_id): - if (domain_id is not None and - not hints.get_exact_filter_by_name('domain_id')): - hints.add_filter('domain_id', domain_id) - - def _set_list_limit_in_hints(self, hints, driver): - """Set list limit in hints from driver - - If a hints list is provided, the wrapper will insert the relevant - limit into the hints so that the underlying driver call can try and - honor it. If the driver does truncate the response, it will update the - 'truncated' attribute in the 'limit' entry in the hints list, which - enables the caller of this function to know if truncation has taken - place. If, however, the driver layer is unable to perform truncation, - the 'limit' entry is simply left in the hints list for the caller to - handle. - - A _get_list_limit() method is required to be present in the object - class hierarchy, which returns the limit for this backend to which - we will truncate. - - If a hints list is not provided in the arguments of the wrapped call - then any limits set in the config file are ignored. This allows - internal use of such wrapped methods where the entire data set is - needed as input for the calculations of some other API (e.g. get role - assignments for a given project). - - This method, specific to identity manager, is used instead of more - general response_truncated, because the limit for identity entities - can be overriden in domain-specific config files. The driver to use - is determined during processing of the passed parameters and - response_truncated is designed to set the limit before any processing. - """ - if hints is None: - return - - list_limit = driver._get_list_limit() - if list_limit: - hints.set_limit(list_limit) - - # The actual driver calls - these are pre/post processed here as - # part of the Manager layer to make sure we: - # - # - select the right driver for this domain - # - clear/set domain_ids for drivers that do not support domains - # - create any ID mapping that might be required - - @notifications.emit_event('authenticate') - @domains_configured - @exception_translated('assertion') - def authenticate(self, context, user_id, password): - domain_id, driver, entity_id = ( - self._get_domain_driver_and_entity_id(user_id)) - ref = driver.authenticate(entity_id, password) - return self._set_domain_id_and_mapping( - ref, domain_id, driver, mapping.EntityType.USER) - - @domains_configured - @exception_translated('user') - def create_user(self, user_ref, initiator=None): - user = user_ref.copy() - user['name'] = clean.user_name(user['name']) - user.setdefault('enabled', True) - user['enabled'] = clean.user_enabled(user['enabled']) - domain_id = user['domain_id'] - self.resource_api.get_domain(domain_id) - - # For creating a user, the domain is in the object itself - domain_id = user_ref['domain_id'] - driver = self._select_identity_driver(domain_id) - user = self._clear_domain_id_if_domain_unaware(driver, user) - # Generate a local ID - in the future this might become a function of - # the underlying driver so that it could conform to rules set down by - # that particular driver type. - user['id'] = uuid.uuid4().hex - ref = driver.create_user(user['id'], user) - notifications.Audit.created(self._USER, user['id'], initiator) - return self._set_domain_id_and_mapping( - ref, domain_id, driver, mapping.EntityType.USER) - - @domains_configured - @exception_translated('user') - @MEMOIZE - def get_user(self, user_id): - domain_id, driver, entity_id = ( - self._get_domain_driver_and_entity_id(user_id)) - ref = driver.get_user(entity_id) - return self._set_domain_id_and_mapping( - ref, domain_id, driver, mapping.EntityType.USER) - - def assert_user_enabled(self, user_id, user=None): - """Assert the user and the user's domain are enabled. - - :raise AssertionError if the user or the user's domain is disabled. - """ - if user is None: - user = self.get_user(user_id) - self.resource_api.assert_domain_enabled(user['domain_id']) - if not user.get('enabled', True): - raise AssertionError(_('User is disabled: %s') % user_id) - - @domains_configured - @exception_translated('user') - @MEMOIZE - def get_user_by_name(self, user_name, domain_id): - driver = self._select_identity_driver(domain_id) - ref = driver.get_user_by_name(user_name, domain_id) - return self._set_domain_id_and_mapping( - ref, domain_id, driver, mapping.EntityType.USER) - - @domains_configured - @exception_translated('user') - def list_users(self, domain_scope=None, hints=None): - driver = self._select_identity_driver(domain_scope) - self._set_list_limit_in_hints(hints, driver) - hints = hints or driver_hints.Hints() - if driver.is_domain_aware(): - # Force the domain_scope into the hint to ensure that we only get - # back domains for that scope. - self._ensure_domain_id_in_hints(hints, domain_scope) - else: - # We are effectively satisfying any domain_id filter by the above - # driver selection, so remove any such filter. - self._mark_domain_id_filter_satisfied(hints) - ref_list = driver.list_users(hints) - return self._set_domain_id_and_mapping( - ref_list, domain_scope, driver, mapping.EntityType.USER) - - def _check_update_of_domain_id(self, new_domain, old_domain): - if new_domain != old_domain: - versionutils.report_deprecated_feature( - LOG, - _('update of domain_id is deprecated as of Mitaka ' - 'and will be removed in O.') - ) - - @domains_configured - @exception_translated('user') - def update_user(self, user_id, user_ref, initiator=None): - old_user_ref = self.get_user(user_id) - user = user_ref.copy() - if 'name' in user: - user['name'] = clean.user_name(user['name']) - if 'enabled' in user: - user['enabled'] = clean.user_enabled(user['enabled']) - if 'domain_id' in user: - self._check_update_of_domain_id(user['domain_id'], - old_user_ref['domain_id']) - self.resource_api.get_domain(user['domain_id']) - if 'id' in user: - if user_id != user['id']: - raise exception.ValidationError(_('Cannot change user ID')) - # Since any ID in the user dict is now irrelevant, remove its so as - # the driver layer won't be confused by the fact the this is the - # public ID not the local ID - user.pop('id') - - domain_id, driver, entity_id = ( - self._get_domain_driver_and_entity_id(user_id)) - user = self._clear_domain_id_if_domain_unaware(driver, user) - self.get_user.invalidate(self, old_user_ref['id']) - self.get_user_by_name.invalidate(self, old_user_ref['name'], - old_user_ref['domain_id']) - - ref = driver.update_user(entity_id, user) - - notifications.Audit.updated(self._USER, user_id, initiator) - - enabled_change = ((user.get('enabled') is False) and - user['enabled'] != old_user_ref.get('enabled')) - if enabled_change or user.get('password') is not None: - self.emit_invalidate_user_token_persistence(user_id) - - return self._set_domain_id_and_mapping( - ref, domain_id, driver, mapping.EntityType.USER) - - @domains_configured - @exception_translated('user') - def delete_user(self, user_id, initiator=None): - domain_id, driver, entity_id = ( - self._get_domain_driver_and_entity_id(user_id)) - # Get user details to invalidate the cache. - user_old = self.get_user(user_id) - driver.delete_user(entity_id) - self.assignment_api.delete_user_assignments(user_id) - self.get_user.invalidate(self, user_id) - self.get_user_by_name.invalidate(self, user_old['name'], - user_old['domain_id']) - self.credential_api.delete_credentials_for_user(user_id) - self.id_mapping_api.delete_id_mapping(user_id) - notifications.Audit.deleted(self._USER, user_id, initiator) - - # Invalidate user role assignments cache region, as it may be caching - # role assignments where the actor is the specified user - assignment.COMPUTED_ASSIGNMENTS_REGION.invalidate() - - @domains_configured - @exception_translated('group') - def create_group(self, group_ref, initiator=None): - group = group_ref.copy() - group.setdefault('description', '') - domain_id = group['domain_id'] - self.resource_api.get_domain(domain_id) - - # For creating a group, the domain is in the object itself - domain_id = group_ref['domain_id'] - driver = self._select_identity_driver(domain_id) - group = self._clear_domain_id_if_domain_unaware(driver, group) - # Generate a local ID - in the future this might become a function of - # the underlying driver so that it could conform to rules set down by - # that particular driver type. - group['id'] = uuid.uuid4().hex - ref = driver.create_group(group['id'], group) - - notifications.Audit.created(self._GROUP, group['id'], initiator) - - return self._set_domain_id_and_mapping( - ref, domain_id, driver, mapping.EntityType.GROUP) - - @domains_configured - @exception_translated('group') - @MEMOIZE - def get_group(self, group_id): - domain_id, driver, entity_id = ( - self._get_domain_driver_and_entity_id(group_id)) - ref = driver.get_group(entity_id) - return self._set_domain_id_and_mapping( - ref, domain_id, driver, mapping.EntityType.GROUP) - - @domains_configured - @exception_translated('group') - def get_group_by_name(self, group_name, domain_id): - driver = self._select_identity_driver(domain_id) - ref = driver.get_group_by_name(group_name, domain_id) - return self._set_domain_id_and_mapping( - ref, domain_id, driver, mapping.EntityType.GROUP) - - @domains_configured - @exception_translated('group') - def update_group(self, group_id, group, initiator=None): - if 'domain_id' in group: - old_group_ref = self.get_group(group_id) - self._check_update_of_domain_id(group['domain_id'], - old_group_ref['domain_id']) - self.resource_api.get_domain(group['domain_id']) - domain_id, driver, entity_id = ( - self._get_domain_driver_and_entity_id(group_id)) - group = self._clear_domain_id_if_domain_unaware(driver, group) - ref = driver.update_group(entity_id, group) - self.get_group.invalidate(self, group_id) - notifications.Audit.updated(self._GROUP, group_id, initiator) - return self._set_domain_id_and_mapping( - ref, domain_id, driver, mapping.EntityType.GROUP) - - @domains_configured - @exception_translated('group') - def delete_group(self, group_id, initiator=None): - domain_id, driver, entity_id = ( - self._get_domain_driver_and_entity_id(group_id)) - user_ids = (u['id'] for u in self.list_users_in_group(group_id)) - driver.delete_group(entity_id) - self.get_group.invalidate(self, group_id) - self.id_mapping_api.delete_id_mapping(group_id) - self.assignment_api.delete_group_assignments(group_id) - - notifications.Audit.deleted(self._GROUP, group_id, initiator) - - for uid in user_ids: - self.emit_invalidate_user_token_persistence(uid) - - # Invalidate user role assignments cache region, as it may be caching - # role assignments expanded from the specified group to its users - assignment.COMPUTED_ASSIGNMENTS_REGION.invalidate() - - @domains_configured - @exception_translated('group') - def add_user_to_group(self, user_id, group_id, initiator=None): - @exception_translated('user') - def get_entity_info_for_user(public_id): - return self._get_domain_driver_and_entity_id(public_id) - - _domain_id, group_driver, group_entity_id = ( - self._get_domain_driver_and_entity_id(group_id)) - # Get the same info for the user_id, taking care to map any - # exceptions correctly - _domain_id, user_driver, user_entity_id = ( - get_entity_info_for_user(user_id)) - - self._assert_user_and_group_in_same_backend( - user_entity_id, user_driver, group_entity_id, group_driver) - - group_driver.add_user_to_group(user_entity_id, group_entity_id) - - # Invalidate user role assignments cache region, as it may now need to - # include role assignments from the specified group to its users - assignment.COMPUTED_ASSIGNMENTS_REGION.invalidate() - notifications.Audit.added_to(self._GROUP, group_id, self._USER, - user_id, initiator) - - @domains_configured - @exception_translated('group') - def remove_user_from_group(self, user_id, group_id, initiator=None): - @exception_translated('user') - def get_entity_info_for_user(public_id): - return self._get_domain_driver_and_entity_id(public_id) - - _domain_id, group_driver, group_entity_id = ( - self._get_domain_driver_and_entity_id(group_id)) - # Get the same info for the user_id, taking care to map any - # exceptions correctly - _domain_id, user_driver, user_entity_id = ( - get_entity_info_for_user(user_id)) - - self._assert_user_and_group_in_same_backend( - user_entity_id, user_driver, group_entity_id, group_driver) - - group_driver.remove_user_from_group(user_entity_id, group_entity_id) - self.emit_invalidate_user_token_persistence(user_id) - - # Invalidate user role assignments cache region, as it may be caching - # role assignments expanded from this group to this user - assignment.COMPUTED_ASSIGNMENTS_REGION.invalidate() - notifications.Audit.removed_from(self._GROUP, group_id, self._USER, - user_id, initiator) - - def emit_invalidate_user_token_persistence(self, user_id): - """Emit a notification to the callback system to revoke user tokens. - - This method and associated callback listener removes the need for - making a direct call to another manager to delete and revoke tokens. - - :param user_id: user identifier - :type user_id: string - """ - notifications.Audit.internal( - notifications.INVALIDATE_USER_TOKEN_PERSISTENCE, user_id - ) - - def emit_invalidate_grant_token_persistence(self, user_project): - """Emit a notification to the callback system to revoke grant tokens. - - This method and associated callback listener removes the need for - making a direct call to another manager to delete and revoke tokens. - - :param user_project: {'user_id': user_id, 'project_id': project_id} - :type user_project: dict - """ - notifications.Audit.internal( - notifications.INVALIDATE_USER_PROJECT_TOKEN_PERSISTENCE, - user_project - ) - - @domains_configured - @exception_translated('user') - def list_groups_for_user(self, user_id, hints=None): - domain_id, driver, entity_id = ( - self._get_domain_driver_and_entity_id(user_id)) - self._set_list_limit_in_hints(hints, driver) - hints = hints or driver_hints.Hints() - if not driver.is_domain_aware(): - # We are effectively satisfying any domain_id filter by the above - # driver selection, so remove any such filter - self._mark_domain_id_filter_satisfied(hints) - ref_list = driver.list_groups_for_user(entity_id, hints) - return self._set_domain_id_and_mapping( - ref_list, domain_id, driver, mapping.EntityType.GROUP) - - @domains_configured - @exception_translated('group') - def list_groups(self, domain_scope=None, hints=None): - driver = self._select_identity_driver(domain_scope) - self._set_list_limit_in_hints(hints, driver) - hints = hints or driver_hints.Hints() - if driver.is_domain_aware(): - # Force the domain_scope into the hint to ensure that we only get - # back domains for that scope. - self._ensure_domain_id_in_hints(hints, domain_scope) - else: - # We are effectively satisfying any domain_id filter by the above - # driver selection, so remove any such filter. - self._mark_domain_id_filter_satisfied(hints) - ref_list = driver.list_groups(hints) - return self._set_domain_id_and_mapping( - ref_list, domain_scope, driver, mapping.EntityType.GROUP) - - @domains_configured - @exception_translated('group') - def list_users_in_group(self, group_id, hints=None): - domain_id, driver, entity_id = ( - self._get_domain_driver_and_entity_id(group_id)) - self._set_list_limit_in_hints(hints, driver) - hints = hints or driver_hints.Hints() - if not driver.is_domain_aware(): - # We are effectively satisfying any domain_id filter by the above - # driver selection, so remove any such filter - self._mark_domain_id_filter_satisfied(hints) - ref_list = driver.list_users_in_group(entity_id, hints) - return self._set_domain_id_and_mapping( - ref_list, domain_id, driver, mapping.EntityType.USER) - - @domains_configured - @exception_translated('group') - def check_user_in_group(self, user_id, group_id): - @exception_translated('user') - def get_entity_info_for_user(public_id): - return self._get_domain_driver_and_entity_id(public_id) - - _domain_id, group_driver, group_entity_id = ( - self._get_domain_driver_and_entity_id(group_id)) - # Get the same info for the user_id, taking care to map any - # exceptions correctly - _domain_id, user_driver, user_entity_id = ( - get_entity_info_for_user(user_id)) - - self._assert_user_and_group_in_same_backend( - user_entity_id, user_driver, group_entity_id, group_driver) - - return group_driver.check_user_in_group(user_entity_id, - group_entity_id) - - @domains_configured - def change_password(self, context, user_id, original_password, - new_password): - - # authenticate() will raise an AssertionError if authentication fails - self.authenticate(context, user_id, original_password) - - update_dict = {'password': new_password} - self.update_user(user_id, update_dict) - - @MEMOIZE - def shadow_federated_user(self, idp_id, protocol_id, unique_id, - display_name): - """Shadows a federated user by mapping to a user. - - :param idp_id: identity provider id - :param protocol_id: protocol id - :param unique_id: unique id for the user within the IdP - :param display_name: user's display name - - :returns: dictionary of the mapped User entity - """ - user_dict = {} - try: - self.shadow_users_api.update_federated_user_display_name( - idp_id, protocol_id, unique_id, display_name) - user_dict = self.shadow_users_api.get_federated_user( - idp_id, protocol_id, unique_id) - except exception.UserNotFound: - federated_dict = { - 'idp_id': idp_id, - 'protocol_id': protocol_id, - 'unique_id': unique_id, - 'display_name': display_name - } - user_dict = self.shadow_users_api.create_federated_user( - federated_dict) - return user_dict - - -@six.add_metaclass(abc.ABCMeta) -class IdentityDriverV8(object): - """Interface description for an Identity driver.""" - - def _get_conf(self): - try: - return self.conf or CONF - except AttributeError: - return CONF - - def _get_list_limit(self): - conf = self._get_conf() - # use list_limit from domain-specific config. If list_limit in - # domain-specific config is not set, look it up in the default config - return (conf.identity.list_limit or conf.list_limit or - CONF.identity.list_limit or CONF.list_limit) - - def is_domain_aware(self): - """Indicates if Driver supports domains.""" - return True - - def default_assignment_driver(self): - # TODO(morganfainberg): To be removed when assignment driver based - # upon [identity]/driver option is removed in the "O" release. - return 'sql' - - @property - def is_sql(self): - """Indicates if this Driver uses SQL.""" - return False - - @property - def multiple_domains_supported(self): - return (self.is_domain_aware() or - CONF.identity.domain_specific_drivers_enabled) - - def generates_uuids(self): - """Indicates if Driver generates UUIDs as the local entity ID.""" - return True - - @abc.abstractmethod - def authenticate(self, user_id, password): - """Authenticate a given user and password. - - :returns: user_ref - :raises AssertionError: If user or password is invalid. - """ - raise exception.NotImplemented() # pragma: no cover - - # user crud - - @abc.abstractmethod - def create_user(self, user_id, user): - """Creates a new user. - - :raises keystone.exception.Conflict: If a duplicate user exists. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def list_users(self, hints): - """List users in the system. - - :param hints: filter hints which the driver should - implement if at all possible. - - :returns: a list of user_refs or an empty list. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def list_users_in_group(self, group_id, hints): - """List users in a group. - - :param group_id: the group in question - :param hints: filter hints which the driver should - implement if at all possible. - - :returns: a list of user_refs or an empty list. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def get_user(self, user_id): - """Get a user by ID. - - :returns: user_ref - :raises keystone.exception.UserNotFound: If the user doesn't exist. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def update_user(self, user_id, user): - """Updates an existing user. - - :raises keystone.exception.UserNotFound: If the user doesn't exist. - :raises keystone.exception.Conflict: If a duplicate user exists. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def add_user_to_group(self, user_id, group_id): - """Adds a user to a group. - - :raises keystone.exception.UserNotFound: If the user doesn't exist. - :raises keystone.exception.GroupNotFound: If the group doesn't exist. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def check_user_in_group(self, user_id, group_id): - """Checks if a user is a member of a group. - - :raises keystone.exception.UserNotFound: If the user doesn't exist. - :raises keystone.exception.GroupNotFound: If the group doesn't exist. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def remove_user_from_group(self, user_id, group_id): - """Removes a user from a group. - - :raises keystone.exception.NotFound: If the entity not found. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def delete_user(self, user_id): - """Deletes an existing user. - - :raises keystone.exception.UserNotFound: If the user doesn't exist. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def get_user_by_name(self, user_name, domain_id): - """Get a user by name. - - :returns: user_ref - :raises keystone.exception.UserNotFound: If the user doesn't exist. - - """ - raise exception.NotImplemented() # pragma: no cover - - # group crud - - @abc.abstractmethod - def create_group(self, group_id, group): - """Creates a new group. - - :raises keystone.exception.Conflict: If a duplicate group exists. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def list_groups(self, hints): - """List groups in the system. - - :param hints: filter hints which the driver should - implement if at all possible. - - :returns: a list of group_refs or an empty list. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def list_groups_for_user(self, user_id, hints): - """List groups a user is in - - :param user_id: the user in question - :param hints: filter hints which the driver should - implement if at all possible. - - :returns: a list of group_refs or an empty list. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def get_group(self, group_id): - """Get a group by ID. - - :returns: group_ref - :raises keystone.exception.GroupNotFound: If the group doesn't exist. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def get_group_by_name(self, group_name, domain_id): - """Get a group by name. - - :returns: group_ref - :raises keystone.exception.GroupNotFound: If the group doesn't exist. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def update_group(self, group_id, group): - """Updates an existing group. - - :raises keystone.exception.GroupNotFound: If the group doesn't exist. - :raises keystone.exception.Conflict: If a duplicate group exists. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def delete_group(self, group_id): - """Deletes an existing group. - - :raises keystone.exception.GroupNotFound: If the group doesn't exist. - - """ - raise exception.NotImplemented() # pragma: no cover - - # end of identity - - -Driver = manager.create_legacy_driver(IdentityDriverV8) - - -@dependency.provider('id_mapping_api') -class MappingManager(manager.Manager): - """Default pivot point for the ID Mapping backend.""" - - driver_namespace = 'keystone.identity.id_mapping' - - def __init__(self): - super(MappingManager, self).__init__(CONF.identity_mapping.driver) - - -@six.add_metaclass(abc.ABCMeta) -class MappingDriverV8(object): - """Interface description for an ID Mapping driver.""" - - @abc.abstractmethod - def get_public_id(self, local_entity): - """Returns the public ID for the given local entity. - - :param dict local_entity: Containing the entity domain, local ID and - type ('user' or 'group'). - :returns: public ID, or None if no mapping is found. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def get_id_mapping(self, public_id): - """Returns the local mapping. - - :param public_id: The public ID for the mapping required. - :returns dict: Containing the entity domain, local ID and type. If no - mapping is found, it returns None. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def create_id_mapping(self, local_entity, public_id=None): - """Create and store a mapping to a public_id. - - :param dict local_entity: Containing the entity domain, local ID and - type ('user' or 'group'). - :param public_id: If specified, this will be the public ID. If this - is not specified, a public ID will be generated. - :returns: public ID - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def delete_id_mapping(self, public_id): - """Deletes an entry for the given public_id. - - :param public_id: The public ID for the mapping to be deleted. - - The method is silent if no mapping is found. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def purge_mappings(self, purge_filter): - """Purge selected identity mappings. - - :param dict purge_filter: Containing the attributes of the filter that - defines which entries to purge. An empty - filter means purge all mappings. - - """ - raise exception.NotImplemented() # pragma: no cover - - -MappingDriver = manager.create_legacy_driver(MappingDriverV8) - - -@dependency.provider('shadow_users_api') -class ShadowUsersManager(manager.Manager): - """Default pivot point for the Shadow Users backend.""" - - driver_namespace = 'keystone.identity.shadow_users' - - def __init__(self): - super(ShadowUsersManager, self).__init__(CONF.shadow_users.driver) - - -@six.add_metaclass(abc.ABCMeta) -class ShadowUsersDriverV9(object): - """Interface description for an Shadow Users driver.""" - - @abc.abstractmethod - def create_federated_user(self, federated_dict): - """Create a new user with the federated identity - - :param dict federated_dict: Reference to the federated user - :param user_id: user ID for linking to the federated identity - :returns dict: Containing the user reference - - """ - raise exception.NotImplemented() - - @abc.abstractmethod - def get_federated_user(self, idp_id, protocol_id, unique_id): - """Returns the found user for the federated identity - - :param idp_id: The identity provider ID - :param protocol_id: The federation protocol ID - :param unique_id: The unique ID for the user - :returns dict: Containing the user reference - - """ - raise exception.NotImplemented() - - @abc.abstractmethod - def update_federated_user_display_name(self, idp_id, protocol_id, - unique_id, display_name): - """Updates federated user's display name if changed - - :param idp_id: The identity provider ID - :param protocol_id: The federation protocol ID - :param unique_id: The unique ID for the user - :param display_name: The user's display name - - """ - raise exception.NotImplemented() diff --git a/keystone-moon/keystone/identity/generator.py b/keystone-moon/keystone/identity/generator.py deleted file mode 100644 index 05ad2df5..00000000 --- a/keystone-moon/keystone/identity/generator.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2014 IBM Corp. -# -# 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. - -"""ID Generator provider interface.""" - -import abc - -from oslo_config import cfg -import six - -from keystone.common import dependency -from keystone.common import manager -from keystone import exception - - -CONF = cfg.CONF - - -@dependency.provider('id_generator_api') -class Manager(manager.Manager): - """Default pivot point for the identifier generator backend.""" - - driver_namespace = 'keystone.identity.id_generator' - - def __init__(self): - super(Manager, self).__init__(CONF.identity_mapping.generator) - - -@six.add_metaclass(abc.ABCMeta) -class IDGenerator(object): - """Interface description for an ID Generator provider.""" - - @abc.abstractmethod - def generate_public_ID(self, mapping): - """Return a Public ID for the given mapping dict. - - :param dict mapping: The items to be hashed. - - The ID must be reproducible and no more than 64 chars in length. - The ID generated should be independent of the order of the items - in the mapping dict. - - """ - raise exception.NotImplemented() # pragma: no cover diff --git a/keystone-moon/keystone/identity/id_generators/__init__.py b/keystone-moon/keystone/identity/id_generators/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/keystone-moon/keystone/identity/id_generators/__init__.py +++ /dev/null diff --git a/keystone-moon/keystone/identity/id_generators/sha256.py b/keystone-moon/keystone/identity/id_generators/sha256.py deleted file mode 100644 index e3a8b416..00000000 --- a/keystone-moon/keystone/identity/id_generators/sha256.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 2014 IBM Corp. -# -# 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. - -import hashlib - -import six - -from keystone.identity import generator - - -class Generator(generator.IDGenerator): - - def generate_public_ID(self, mapping): - m = hashlib.sha256() - for key in sorted(six.iterkeys(mapping)): - m.update(mapping[key].encode('utf-8')) - return m.hexdigest() diff --git a/keystone-moon/keystone/identity/mapping_backends/__init__.py b/keystone-moon/keystone/identity/mapping_backends/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/keystone-moon/keystone/identity/mapping_backends/__init__.py +++ /dev/null diff --git a/keystone-moon/keystone/identity/mapping_backends/mapping.py b/keystone-moon/keystone/identity/mapping_backends/mapping.py deleted file mode 100644 index dddf36c1..00000000 --- a/keystone-moon/keystone/identity/mapping_backends/mapping.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2014 IBM Corp. -# -# 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. - - -class EntityType(object): - USER = 'user' - GROUP = 'group' diff --git a/keystone-moon/keystone/identity/mapping_backends/sql.py b/keystone-moon/keystone/identity/mapping_backends/sql.py deleted file mode 100644 index 91b33dd7..00000000 --- a/keystone-moon/keystone/identity/mapping_backends/sql.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright 2014 IBM Corp. -# -# 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 import dependency -from keystone.common import sql -from keystone import identity -from keystone.identity.mapping_backends import mapping as identity_mapping - - -class IDMapping(sql.ModelBase, sql.ModelDictMixin): - __tablename__ = 'id_mapping' - public_id = sql.Column(sql.String(64), primary_key=True) - domain_id = sql.Column(sql.String(64), nullable=False) - local_id = sql.Column(sql.String(64), nullable=False) - # NOTE(henry-nash): Postgres requires a name to be defined for an Enum - entity_type = sql.Column( - sql.Enum(identity_mapping.EntityType.USER, - identity_mapping.EntityType.GROUP, - name='entity_type'), - nullable=False) - # Unique constraint to ensure you can't store more than one mapping to the - # same underlying values - __table_args__ = ( - sql.UniqueConstraint('domain_id', 'local_id', 'entity_type'),) - - -@dependency.requires('id_generator_api') -class Mapping(identity.MappingDriverV8): - - def get_public_id(self, local_entity): - # NOTE(henry-nash): Since the Public ID is regeneratable, rather - # than search for the entry using the local entity values, we - # could create the hash and do a PK lookup. However this would only - # work if we hashed all the entries, even those that already generate - # UUIDs, like SQL. Further, this would only work if the generation - # algorithm was immutable (e.g. it had always been sha256). - with sql.session_for_read() as session: - query = session.query(IDMapping.public_id) - query = query.filter_by(domain_id=local_entity['domain_id']) - query = query.filter_by(local_id=local_entity['local_id']) - query = query.filter_by(entity_type=local_entity['entity_type']) - try: - public_ref = query.one() - public_id = public_ref.public_id - return public_id - except sql.NotFound: - return None - - def get_id_mapping(self, public_id): - with sql.session_for_read() as session: - mapping_ref = session.query(IDMapping).get(public_id) - if mapping_ref: - return mapping_ref.to_dict() - - def create_id_mapping(self, local_entity, public_id=None): - entity = local_entity.copy() - with sql.session_for_write() as session: - if public_id is None: - public_id = self.id_generator_api.generate_public_ID(entity) - entity['public_id'] = public_id - mapping_ref = IDMapping.from_dict(entity) - session.add(mapping_ref) - return public_id - - def delete_id_mapping(self, public_id): - with sql.session_for_write() as session: - try: - session.query(IDMapping).filter( - IDMapping.public_id == public_id).delete() - except sql.NotFound: # nosec - # NOTE(morganfainberg): There is nothing to delete and nothing - # to do. - pass - - def purge_mappings(self, purge_filter): - with sql.session_for_write() as session: - query = session.query(IDMapping) - if 'domain_id' in purge_filter: - query = query.filter_by(domain_id=purge_filter['domain_id']) - if 'public_id' in purge_filter: - query = query.filter_by(public_id=purge_filter['public_id']) - if 'local_id' in purge_filter: - query = query.filter_by(local_id=purge_filter['local_id']) - if 'entity_type' in purge_filter: - query = query.filter_by( - entity_type=purge_filter['entity_type']) - query.delete() diff --git a/keystone-moon/keystone/identity/routers.py b/keystone-moon/keystone/identity/routers.py deleted file mode 100644 index e274d6f4..00000000 --- a/keystone-moon/keystone/identity/routers.py +++ /dev/null @@ -1,84 +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. -"""WSGI Routers for the Identity service.""" - -from keystone.common import json_home -from keystone.common import router -from keystone.common import wsgi -from keystone.identity import controllers - - -class Admin(wsgi.ComposableRouter): - def add_routes(self, mapper): - # User Operations - user_controller = controllers.User() - mapper.connect('/users/{user_id}', - controller=user_controller, - action='get_user', - conditions=dict(method=['GET'])) - - -class Routers(wsgi.RoutersBase): - - def append_v3_routers(self, mapper, routers): - user_controller = controllers.UserV3() - routers.append( - router.Router(user_controller, - 'users', 'user', - resource_descriptions=self.v3_resources)) - - self._add_resource( - mapper, user_controller, - path='/users/{user_id}/password', - post_action='change_password', - rel=json_home.build_v3_resource_relation('user_change_password'), - path_vars={ - 'user_id': json_home.Parameters.USER_ID, - }) - - self._add_resource( - mapper, user_controller, - path='/groups/{group_id}/users', - get_action='list_users_in_group', - rel=json_home.build_v3_resource_relation('group_users'), - path_vars={ - 'group_id': json_home.Parameters.GROUP_ID, - }) - - self._add_resource( - mapper, user_controller, - path='/groups/{group_id}/users/{user_id}', - put_action='add_user_to_group', - get_head_action='check_user_in_group', - delete_action='remove_user_from_group', - rel=json_home.build_v3_resource_relation('group_user'), - path_vars={ - 'group_id': json_home.Parameters.GROUP_ID, - 'user_id': json_home.Parameters.USER_ID, - }) - - group_controller = controllers.GroupV3() - routers.append( - router.Router(group_controller, - 'groups', 'group', - resource_descriptions=self.v3_resources)) - - self._add_resource( - mapper, group_controller, - path='/users/{user_id}/groups', - get_action='list_groups_for_user', - rel=json_home.build_v3_resource_relation('user_groups'), - path_vars={ - 'user_id': json_home.Parameters.USER_ID, - }) diff --git a/keystone-moon/keystone/identity/schema.py b/keystone-moon/keystone/identity/schema.py deleted file mode 100644 index 047fcf02..00000000 --- a/keystone-moon/keystone/identity/schema.py +++ /dev/null @@ -1,67 +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 import validation -from keystone.common.validation import parameter_types - - -# NOTE(lhcheng): the max length is not applicable since it is specific -# to the SQL backend, LDAP does not have length limitation. -_identity_name = { - 'type': 'string', - 'minLength': 1 -} - -_user_properties = { - 'default_project_id': validation.nullable(parameter_types.id_string), - 'description': validation.nullable(parameter_types.description), - 'domain_id': parameter_types.id_string, - 'enabled': parameter_types.boolean, - 'name': _identity_name, - 'password': { - 'type': ['string', 'null'] - } -} - -user_create = { - 'type': 'object', - 'properties': _user_properties, - 'required': ['name'], - 'additionalProperties': True -} - -user_update = { - 'type': 'object', - 'properties': _user_properties, - 'minProperties': 1, - 'additionalProperties': True -} - -_group_properties = { - 'description': validation.nullable(parameter_types.description), - 'domain_id': parameter_types.id_string, - 'name': _identity_name -} - -group_create = { - 'type': 'object', - 'properties': _group_properties, - 'required': ['name'], - 'additionalProperties': True -} - -group_update = { - 'type': 'object', - 'properties': _group_properties, - 'minProperties': 1, - 'additionalProperties': True -} diff --git a/keystone-moon/keystone/identity/shadow_backends/__init__.py b/keystone-moon/keystone/identity/shadow_backends/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/keystone-moon/keystone/identity/shadow_backends/__init__.py +++ /dev/null diff --git a/keystone-moon/keystone/identity/shadow_backends/sql.py b/keystone-moon/keystone/identity/shadow_backends/sql.py deleted file mode 100644 index af5a995b..00000000 --- a/keystone-moon/keystone/identity/shadow_backends/sql.py +++ /dev/null @@ -1,73 +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. - -import uuid - -from keystone.common import sql -from keystone import exception -from keystone import identity -from keystone.identity.backends import sql as model - - -class ShadowUsers(identity.ShadowUsersDriverV9): - @sql.handle_conflicts(conflict_type='federated_user') - def create_federated_user(self, federated_dict): - user = { - 'id': uuid.uuid4().hex, - 'enabled': True - } - with sql.session_for_write() as session: - federated_ref = model.FederatedUser.from_dict(federated_dict) - user_ref = model.User.from_dict(user) - user_ref.federated_users.append(federated_ref) - session.add(user_ref) - return identity.filter_user(user_ref.to_dict()) - - def get_federated_user(self, idp_id, protocol_id, unique_id): - user_ref = self._get_federated_user(idp_id, protocol_id, unique_id) - return identity.filter_user(user_ref.to_dict()) - - def _get_federated_user(self, idp_id, protocol_id, unique_id): - """Returns the found user for the federated identity - - :param idp_id: The identity provider ID - :param protocol_id: The federation protocol ID - :param unique_id: The user's unique ID (unique within the IdP) - :returns User: Returns a reference to the User - - """ - with sql.session_for_read() as session: - query = session.query(model.User).outerjoin(model.LocalUser) - query = query.join(model.FederatedUser) - query = query.filter(model.FederatedUser.idp_id == idp_id) - query = query.filter(model.FederatedUser.protocol_id == - protocol_id) - query = query.filter(model.FederatedUser.unique_id == unique_id) - try: - user_ref = query.one() - except sql.NotFound: - raise exception.UserNotFound(user_id=unique_id) - return user_ref - - @sql.handle_conflicts(conflict_type='federated_user') - def update_federated_user_display_name(self, idp_id, protocol_id, - unique_id, display_name): - with sql.session_for_write() as session: - query = session.query(model.FederatedUser) - query = query.filter(model.FederatedUser.idp_id == idp_id) - query = query.filter(model.FederatedUser.protocol_id == - protocol_id) - query = query.filter(model.FederatedUser.unique_id == unique_id) - query = query.filter(model.FederatedUser.display_name != - display_name) - query.update({'display_name': display_name}) - return |