aboutsummaryrefslogtreecommitdiffstats
path: root/keystone-moon/keystone/identity/core.py
diff options
context:
space:
mode:
Diffstat (limited to 'keystone-moon/keystone/identity/core.py')
-rw-r--r--keystone-moon/keystone/identity/core.py1613
1 files changed, 0 insertions, 1613 deletions
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()