"Fossies" - the Fresh Open Source Software Archive

Member "keystone-17.0.0/keystone/identity/core.py" (13 May 2020, 71869 Bytes) of package /linux/misc/openstack/keystone-17.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "core.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 16.0.1_vs_17.0.0.

    1 # Copyright 2012 OpenStack Foundation
    2 #
    3 # Licensed under the Apache License, Version 2.0 (the "License"); you may
    4 # not use this file except in compliance with the License. You may obtain
    5 # a copy of the License at
    6 #
    7 #      http://www.apache.org/licenses/LICENSE-2.0
    8 #
    9 # Unless required by applicable law or agreed to in writing, software
   10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   12 # License for the specific language governing permissions and limitations
   13 # under the License.
   14 
   15 """Main entry point into the Identity service."""
   16 
   17 import copy
   18 import functools
   19 import itertools
   20 import operator
   21 import os
   22 import threading
   23 import uuid
   24 
   25 from oslo_config import cfg
   26 from oslo_log import log
   27 from pycadf import reason
   28 
   29 from keystone import assignment  # TODO(lbragstad): Decouple this dependency
   30 from keystone.common import cache
   31 from keystone.common import driver_hints
   32 from keystone.common import manager
   33 from keystone.common import provider_api
   34 from keystone.common.validation import validators
   35 import keystone.conf
   36 from keystone import exception
   37 from keystone.i18n import _
   38 from keystone.identity.mapping_backends import mapping
   39 from keystone import notifications
   40 from oslo_utils import timeutils
   41 
   42 
   43 CONF = keystone.conf.CONF
   44 
   45 LOG = log.getLogger(__name__)
   46 
   47 PROVIDERS = provider_api.ProviderAPIs
   48 
   49 MEMOIZE = cache.get_memoization_decorator(group='identity')
   50 
   51 ID_MAPPING_REGION = cache.create_region(name='id mapping')
   52 MEMOIZE_ID_MAPPING = cache.get_memoization_decorator(group='identity',
   53                                                      region=ID_MAPPING_REGION)
   54 
   55 DOMAIN_CONF_FHEAD = 'keystone.'
   56 DOMAIN_CONF_FTAIL = '.conf'
   57 
   58 # The number of times we will attempt to register a domain to use the SQL
   59 # driver, if we find that another process is in the middle of registering or
   60 # releasing at the same time as us.
   61 REGISTRATION_ATTEMPTS = 10
   62 
   63 # Config Registration Types
   64 SQL_DRIVER = 'SQL'
   65 
   66 
   67 class DomainConfigs(provider_api.ProviderAPIMixin, dict):
   68     """Discover, store and provide access to domain specific configs.
   69 
   70     The setup_domain_drivers() call will be made via the wrapper from
   71     the first call to any driver function handled by this manager.
   72 
   73     Domain specific configurations are only supported for the identity backend
   74     and the individual configurations are either specified in the resource
   75     database or in individual domain configuration files, depending on the
   76     setting of the 'domain_configurations_from_database' config option.
   77 
   78     The result will be that for each domain with a specific configuration,
   79     this class will hold a reference to a ConfigOpts and driver object that
   80     the identity manager and driver can use.
   81 
   82     """
   83 
   84     configured = False
   85     driver = None
   86     _any_sql = False
   87     lock = threading.Lock()
   88 
   89     def _load_driver(self, domain_config):
   90         return manager.load_driver(Manager.driver_namespace,
   91                                    domain_config['cfg'].identity.driver,
   92                                    domain_config['cfg'])
   93 
   94     def _load_config_from_file(self, resource_api, file_list, domain_name):
   95 
   96         def _assert_no_more_than_one_sql_driver(new_config, config_file):
   97             """Ensure there is no more than one sql driver.
   98 
   99             Check to see if the addition of the driver in this new config
  100             would cause there to be more than one sql driver.
  101 
  102             """
  103             if (new_config['driver'].is_sql and
  104                     (self.driver.is_sql or self._any_sql)):
  105                 # The addition of this driver would cause us to have more than
  106                 # one sql driver, so raise an exception.
  107                 raise exception.MultipleSQLDriversInConfig(source=config_file)
  108             self._any_sql = self._any_sql or new_config['driver'].is_sql
  109 
  110         try:
  111             domain_ref = resource_api.get_domain_by_name(domain_name)
  112         except exception.DomainNotFound:
  113             LOG.warning('Invalid domain name (%s) found in config file name',
  114                         domain_name)
  115             return
  116 
  117         # Create a new entry in the domain config dict, which contains
  118         # a new instance of both the conf environment and driver using
  119         # options defined in this set of config files.  Later, when we
  120         # service calls via this Manager, we'll index via this domain
  121         # config dict to make sure we call the right driver
  122         domain_config = {}
  123         domain_config['cfg'] = cfg.ConfigOpts()
  124         keystone.conf.configure(conf=domain_config['cfg'])
  125         domain_config['cfg'](args=[], project='keystone',
  126                              default_config_files=file_list,
  127                              default_config_dirs=[])
  128         domain_config['driver'] = self._load_driver(domain_config)
  129         _assert_no_more_than_one_sql_driver(domain_config, file_list)
  130         self[domain_ref['id']] = domain_config
  131 
  132     def _setup_domain_drivers_from_files(self, standard_driver, resource_api):
  133         """Read the domain specific configuration files and load the drivers.
  134 
  135         Domain configuration files are stored in the domain config directory,
  136         and must be named of the form:
  137 
  138         keystone.<domain_name>.conf
  139 
  140         For each file, call the load config method where the domain_name
  141         will be turned into a domain_id and then:
  142 
  143         - Create a new config structure, adding in the specific additional
  144           options defined in this config file
  145         - Initialise a new instance of the required driver with this new config
  146 
  147         """
  148         conf_dir = CONF.identity.domain_config_dir
  149         if not os.path.exists(conf_dir):
  150             LOG.warning('Unable to locate domain config directory: %s',
  151                         conf_dir)
  152             return
  153 
  154         for r, d, f in os.walk(conf_dir):
  155             for fname in f:
  156                 if (fname.startswith(DOMAIN_CONF_FHEAD) and
  157                         fname.endswith(DOMAIN_CONF_FTAIL)):
  158                     if fname.count('.') >= 2:
  159                         self._load_config_from_file(
  160                             resource_api, [os.path.join(r, fname)],
  161                             fname[len(DOMAIN_CONF_FHEAD):
  162                                   -len(DOMAIN_CONF_FTAIL)])
  163                     else:
  164                         LOG.debug(('Ignoring file (%s) while scanning domain '
  165                                    'config directory'),
  166                                   fname)
  167 
  168     def _load_config_from_database(self, domain_id, specific_config):
  169 
  170         def _assert_no_more_than_one_sql_driver(domain_id, new_config):
  171             """Ensure adding driver doesn't push us over the limit of 1.
  172 
  173             The checks we make in this method need to take into account that
  174             we may be in a multiple process configuration and ensure that
  175             any race conditions are avoided.
  176 
  177             """
  178             if not new_config['driver'].is_sql:
  179                 PROVIDERS.domain_config_api.release_registration(domain_id)
  180                 return
  181 
  182             # To ensure the current domain is the only SQL driver, we attempt
  183             # to register our use of SQL. If we get it we know we are good,
  184             # if we fail to register it then we should:
  185             #
  186             # - First check if another process has registered for SQL for our
  187             #   domain, in which case we are fine
  188             # - If a different domain has it, we should check that this domain
  189             #   is still valid, in case, for example, domain deletion somehow
  190             #   failed to remove its registration (i.e. we self heal for these
  191             #   kinds of issues).
  192 
  193             domain_registered = 'Unknown'
  194             for attempt in range(REGISTRATION_ATTEMPTS):
  195                 if PROVIDERS.domain_config_api.obtain_registration(
  196                         domain_id, SQL_DRIVER):
  197                     LOG.debug('Domain %s successfully registered to use the '
  198                               'SQL driver.', domain_id)
  199                     return
  200 
  201                 # We failed to register our use, let's find out who is using it
  202                 try:
  203                     domain_registered = (
  204                         PROVIDERS.domain_config_api.read_registration(
  205                             SQL_DRIVER))
  206                 except exception.ConfigRegistrationNotFound:
  207                     msg = ('While attempting to register domain %(domain)s to '
  208                            'use the SQL driver, another process released it, '
  209                            'retrying (attempt %(attempt)s).')
  210                     LOG.debug(msg, {'domain': domain_id,
  211                                     'attempt': attempt + 1})
  212                     continue
  213 
  214                 if domain_registered == domain_id:
  215                     # Another process already registered it for us, so we are
  216                     # fine. In the race condition when another process is
  217                     # in the middle of deleting this domain, we know the domain
  218                     # is already disabled and hence telling the caller that we
  219                     # are registered is benign.
  220                     LOG.debug('While attempting to register domain %s to use '
  221                               'the SQL driver, found that another process had '
  222                               'already registered this domain. This is normal '
  223                               'in multi-process configurations.', domain_id)
  224                     return
  225 
  226                 # So we don't have it, but someone else does...let's check that
  227                 # this domain is still valid
  228                 try:
  229                     PROVIDERS.resource_api.get_domain(domain_registered)
  230                 except exception.DomainNotFound:
  231                     msg = ('While attempting to register domain %(domain)s to '
  232                            'use the SQL driver, found that it was already '
  233                            'registered to a domain that no longer exists '
  234                            '(%(old_domain)s). Removing this stale '
  235                            'registration and retrying (attempt %(attempt)s).')
  236                     LOG.debug(msg, {'domain': domain_id,
  237                                     'old_domain': domain_registered,
  238                                     'attempt': attempt + 1})
  239                     PROVIDERS.domain_config_api.release_registration(
  240                         domain_registered, type=SQL_DRIVER)
  241                     continue
  242 
  243                 # The domain is valid, so we really do have an attempt at more
  244                 # than one SQL driver.
  245                 details = (
  246                     _('Config API entity at /domains/%s/config') % domain_id)
  247                 raise exception.MultipleSQLDriversInConfig(source=details)
  248 
  249             # We fell out of the loop without either registering our domain or
  250             # being able to find who has it...either we were very very very
  251             # unlucky or something is awry.
  252             msg = _('Exceeded attempts to register domain %(domain)s to use '
  253                     'the SQL driver, the last domain that appears to have '
  254                     'had it is %(last_domain)s, giving up') % {
  255                         'domain': domain_id, 'last_domain': domain_registered}
  256             raise exception.UnexpectedError(msg)
  257 
  258         domain_config = {}
  259         domain_config['cfg'] = cfg.ConfigOpts()
  260         keystone.conf.configure(conf=domain_config['cfg'])
  261         domain_config['cfg'](args=[], project='keystone',
  262                              default_config_files=[],
  263                              default_config_dirs=[])
  264 
  265         # Override any options that have been passed in as specified in the
  266         # database.
  267         for group in specific_config:
  268             for option in specific_config[group]:
  269                 domain_config['cfg'].set_override(
  270                     option, specific_config[group][option], group)
  271 
  272         domain_config['cfg_overrides'] = specific_config
  273         domain_config['driver'] = self._load_driver(domain_config)
  274         _assert_no_more_than_one_sql_driver(domain_id, domain_config)
  275         self[domain_id] = domain_config
  276 
  277     def _setup_domain_drivers_from_database(self, standard_driver,
  278                                             resource_api):
  279         """Read domain specific configuration from database and load drivers.
  280 
  281         Domain configurations are stored in the domain-config backend,
  282         so we go through each domain to find those that have a specific config
  283         defined, and for those that do we:
  284 
  285         - Create a new config structure, overriding any specific options
  286           defined in the resource backend
  287         - Initialise a new instance of the required driver with this new config
  288 
  289         """
  290         for domain in resource_api.list_domains():
  291             domain_config_options = (
  292                 PROVIDERS.domain_config_api.
  293                 get_config_with_sensitive_info(domain['id']))
  294             if domain_config_options:
  295                 self._load_config_from_database(domain['id'],
  296                                                 domain_config_options)
  297 
  298     def setup_domain_drivers(self, standard_driver, resource_api):
  299         # This is called by the api call wrapper
  300         self.driver = standard_driver
  301 
  302         if CONF.identity.domain_configurations_from_database:
  303             self._setup_domain_drivers_from_database(standard_driver,
  304                                                      resource_api)
  305         else:
  306             self._setup_domain_drivers_from_files(standard_driver,
  307                                                   resource_api)
  308         self.configured = True
  309 
  310     def get_domain_driver(self, domain_id):
  311         self.check_config_and_reload_domain_driver_if_required(domain_id)
  312         if domain_id in self:
  313             return self[domain_id]['driver']
  314 
  315     def get_domain_conf(self, domain_id):
  316         self.check_config_and_reload_domain_driver_if_required(domain_id)
  317         if domain_id in self:
  318             return self[domain_id]['cfg']
  319         else:
  320             return CONF
  321 
  322     def reload_domain_driver(self, domain_id):
  323         # Only used to support unit tests that want to set
  324         # new config values.  This should only be called once
  325         # the domains have been configured, since it relies on
  326         # the fact that the configuration files/database have already been
  327         # read.
  328         if self.configured:
  329             if domain_id in self:
  330                 self[domain_id]['driver'] = (
  331                     self._load_driver(self[domain_id]))
  332             else:
  333                 # The standard driver
  334                 self.driver = self.driver()
  335 
  336     def check_config_and_reload_domain_driver_if_required(self, domain_id):
  337         """Check for, and load, any new domain specific config for this domain.
  338 
  339         This is only supported for the database-stored domain specific
  340         configuration.
  341 
  342         When the domain specific drivers were set up, we stored away the
  343         specific config for this domain that was available at that time. So we
  344         now read the current version and compare. While this might seem
  345         somewhat inefficient, the sensitive config call is cached, so should be
  346         light weight. More importantly, when the cache timeout is reached, we
  347         will get any config that has been updated from any other keystone
  348         process.
  349 
  350         This cache-timeout approach works for both multi-process and
  351         multi-threaded keystone configurations. In multi-threaded
  352         configurations, even though we might remove a driver object (that
  353         could be in use by another thread), this won't actually be thrown away
  354         until all references to it have been broken. When that other
  355         thread is released back and is restarted with another command to
  356         process, next time it accesses the driver it will pickup the new one.
  357 
  358         """
  359         if (not CONF.identity.domain_specific_drivers_enabled or
  360                 not CONF.identity.domain_configurations_from_database):
  361             # If specific drivers are not enabled, then there is nothing to do.
  362             # If we are not storing the configurations in the database, then
  363             # we'll only re-read the domain specific config files on startup
  364             # of keystone.
  365             return
  366 
  367         latest_domain_config = (
  368             PROVIDERS.domain_config_api.
  369             get_config_with_sensitive_info(domain_id))
  370         domain_config_in_use = domain_id in self
  371 
  372         if latest_domain_config:
  373             if (not domain_config_in_use or
  374                     latest_domain_config != self[domain_id]['cfg_overrides']):
  375                 self._load_config_from_database(domain_id,
  376                                                 latest_domain_config)
  377         elif domain_config_in_use:
  378             # The domain specific config has been deleted, so should remove the
  379             # specific driver for this domain.
  380             try:
  381                 del self[domain_id]
  382             except KeyError:  # nosec
  383                 # Allow this error in case we are unlucky and in a
  384                 # multi-threaded situation, two threads happen to be running
  385                 # in lock step.
  386                 pass
  387         # If we fall into the else condition, this means there is no domain
  388         # config set, and there is none in use either, so we have nothing
  389         # to do.
  390 
  391 
  392 def domains_configured(f):
  393     """Wrap API calls to lazy load domain configs after init.
  394 
  395     This is required since the assignment manager needs to be initialized
  396     before this manager, and yet this manager's init wants to be
  397     able to make assignment calls (to build the domain configs).  So
  398     instead, we check if the domains have been initialized on entry
  399     to each call, and if requires load them,
  400 
  401     """
  402     @functools.wraps(f)
  403     def wrapper(self, *args, **kwargs):
  404         if (not self.domain_configs.configured and
  405                 CONF.identity.domain_specific_drivers_enabled):
  406             # If domain specific driver has not been configured, acquire the
  407             # lock and proceed with loading the driver.
  408             with self.domain_configs.lock:
  409                 # Check again just in case some other thread has already
  410                 # completed domain config.
  411                 if not self.domain_configs.configured:
  412                     self.domain_configs.setup_domain_drivers(
  413                         self.driver, PROVIDERS.resource_api)
  414         return f(self, *args, **kwargs)
  415     return wrapper
  416 
  417 
  418 def exception_translated(exception_type):
  419     """Wrap API calls to map to correct exception."""
  420     def _exception_translated(f):
  421         @functools.wraps(f)
  422         def wrapper(self, *args, **kwargs):
  423             try:
  424                 return f(self, *args, **kwargs)
  425             except exception.PublicIDNotFound as e:
  426                 if exception_type == 'user':
  427                     raise exception.UserNotFound(user_id=str(e))
  428                 elif exception_type == 'group':
  429                     raise exception.GroupNotFound(group_id=str(e))
  430                 elif exception_type == 'assertion':
  431                     raise AssertionError(_('Invalid user / password'))
  432                 else:
  433                     raise
  434         return wrapper
  435     return _exception_translated
  436 
  437 
  438 @notifications.listener
  439 class Manager(manager.Manager):
  440     """Default pivot point for the Identity backend.
  441 
  442     See :mod:`keystone.common.manager.Manager` for more details on how this
  443     dynamically calls the backend.
  444 
  445     This class also handles the support of domain specific backends, by using
  446     the DomainConfigs class. The setup call for DomainConfigs is called
  447     from with the @domains_configured wrapper in a lazy loading fashion
  448     to get around the fact that we can't satisfy the assignment api it needs
  449     from within our __init__() function since the assignment driver is not
  450     itself yet initialized.
  451 
  452     Each of the identity calls are pre-processed here to choose, based on
  453     domain, which of the drivers should be called. The non-domain-specific
  454     driver is still in place, and is used if there is no specific driver for
  455     the domain in question (or we are not using multiple domain drivers).
  456 
  457     Starting with Juno, in order to be able to obtain the domain from
  458     just an ID being presented as part of an API call, a public ID to domain
  459     and local ID mapping is maintained.  This mapping also allows for the local
  460     ID of drivers that do not provide simple UUIDs (such as LDAP) to be
  461     referenced via a public facing ID.  The mapping itself is automatically
  462     generated as entities are accessed via the driver.
  463 
  464     This mapping is only used when:
  465     - the entity is being handled by anything other than the default driver, or
  466     - the entity is being handled by the default LDAP driver and backward
  467     compatible IDs are not required.
  468 
  469     This means that in the standard case of a single SQL backend or the default
  470     settings of a single LDAP backend (since backward compatible IDs is set to
  471     True by default), no mapping is used. An alternative approach would be to
  472     always use the mapping table, but in the cases where we don't need it to
  473     make the public and local IDs the same. It is felt that not using the
  474     mapping by default is a more prudent way to introduce this functionality.
  475 
  476     """
  477 
  478     driver_namespace = 'keystone.identity'
  479     _provides_api = 'identity_api'
  480 
  481     _USER = 'user'
  482     _GROUP = 'group'
  483 
  484     def __init__(self):
  485         super(Manager, self).__init__(CONF.identity.driver)
  486         self.domain_configs = DomainConfigs()
  487         notifications.register_event_callback(
  488             notifications.ACTIONS.internal, notifications.DOMAIN_DELETED,
  489             self._domain_deleted
  490         )
  491         self.event_callbacks = {
  492             notifications.ACTIONS.deleted: {
  493                 'project': [self._unset_default_project],
  494             },
  495         }
  496 
  497     def _domain_deleted(self, service, resource_type, operation,
  498                         payload):
  499         domain_id = payload['resource_info']
  500 
  501         driver = self._select_identity_driver(domain_id)
  502 
  503         if driver.is_sql:
  504             group_refs = self.list_groups(domain_scope=domain_id)
  505             for group in group_refs:
  506                 # Cleanup any existing groups.
  507                 try:
  508                     self.delete_group(group['id'])
  509                 except exception.GroupNotFound:
  510                     LOG.debug(('Group %(groupid)s not found when deleting '
  511                                'domain contents for %(domainid)s, continuing '
  512                                'with cleanup.'),
  513                               {'groupid': group['id'], 'domainid': domain_id})
  514 
  515         # And finally, delete the users themselves
  516         user_refs = self.list_users(domain_scope=domain_id)
  517 
  518         for user in user_refs:
  519             try:
  520                 if not driver.is_sql:
  521                     PROVIDERS.shadow_users_api.delete_user(user['id'])
  522                 else:
  523                     self.delete_user(user['id'])
  524             except exception.UserNotFound:
  525                 LOG.debug(('User %(userid)s not found when deleting domain '
  526                            'contents for %(domainid)s, continuing with '
  527                            'cleanup.'),
  528                           {'userid': user['id'], 'domainid': domain_id})
  529 
  530     def _unset_default_project(self, service, resource_type, operation,
  531                                payload):
  532         """Callback, clears user default_project_id after project deletion.
  533 
  534         Notifications are used to unset a user's default project because
  535         there is no foreign key to the project. Projects can be in a non-SQL
  536         backend, making FKs impossible.
  537 
  538         """
  539         project_id = payload['resource_info']
  540         drivers = itertools.chain(
  541             self.domain_configs.values(), [{'driver': self.driver}]
  542         )
  543         for d in drivers:
  544             try:
  545                 d['driver'].unset_default_project_id(project_id)
  546             except exception.Forbidden:
  547                 # NOTE(lbragstad): If the driver throws a Forbidden, it's
  548                 # because the driver doesn't support writes. This is the case
  549                 # with the in-tree LDAP implementation since it is read-only.
  550                 # This also ensures consistency for out-of-tree backends that
  551                 # might be read-only.
  552                 pass
  553 
  554     # Domain ID normalization methods
  555     def _set_domain_id_and_mapping(self, ref, domain_id, driver,
  556                                    entity_type):
  557         """Patch the domain_id/public_id into the resulting entity(ies).
  558 
  559         :param ref: the entity or list of entities to post process
  560         :param domain_id: the domain scope used for the call
  561         :param driver: the driver used to execute the call
  562         :param entity_type: whether this is a user or group
  563 
  564         :returns: post processed entity or list or entities
  565 
  566         Called to post-process the entity being returned, using a mapping
  567         to substitute a public facing ID as necessary. This method must
  568         take into account:
  569 
  570         - If the driver is not domain aware, then we must set the domain
  571           attribute of all entities irrespective of mapping.
  572         - If the driver does not support UUIDs, then we always want to provide
  573           a mapping, except for the special case of this being the default
  574           driver and backward_compatible_ids is set to True. This is to ensure
  575           that entity IDs do not change for an existing LDAP installation (only
  576           single domain/driver LDAP configurations were previously supported).
  577         - If the driver does support UUIDs, then we always create a mapping
  578           entry, but use the local UUID as the public ID.  The exception to
  579           this is that if we just have single driver (i.e. not using specific
  580           multi-domain configs), then we don't bother with the mapping at all.
  581 
  582         """
  583         conf = CONF.identity
  584 
  585         if not self._needs_post_processing(driver):
  586             # a classic case would be when running with a single SQL driver
  587             return ref
  588 
  589         LOG.debug('ID Mapping - Domain ID: %(domain)s, '
  590                   'Default Driver: %(driver)s, '
  591                   'Domains: %(aware)s, UUIDs: %(generate)s, '
  592                   'Compatible IDs: %(compat)s',
  593                   {'domain': domain_id,
  594                    'driver': (driver == self.driver),
  595                    'aware': driver.is_domain_aware(),
  596                    'generate': driver.generates_uuids(),
  597                    'compat': CONF.identity_mapping.backward_compatible_ids})
  598 
  599         if isinstance(ref, dict):
  600             return self._set_domain_id_and_mapping_for_single_ref(
  601                 ref, domain_id, driver, entity_type, conf)
  602         elif isinstance(ref, list):
  603             return self._set_domain_id_and_mapping_for_list(
  604                 ref, domain_id, driver, entity_type, conf)
  605         else:
  606             raise ValueError(_('Expected dict or list: %s') % type(ref))
  607 
  608     def _needs_post_processing(self, driver):
  609         """Return whether entity from driver needs domain added or mapping."""
  610         return (driver is not self.driver or not driver.generates_uuids() or
  611                 not driver.is_domain_aware())
  612 
  613     def _insert_new_public_id(self, local_entity, ref, driver):
  614         # Need to create a mapping. If the driver generates UUIDs
  615         # then pass the local UUID in as the public ID to use.
  616         public_id = None
  617         if driver.generates_uuids():
  618             public_id = ref['id']
  619         ref['id'] = PROVIDERS.id_mapping_api.create_id_mapping(
  620             local_entity, public_id)
  621         LOG.debug('Created new mapping to public ID: %s', ref['id'])
  622 
  623     def _set_domain_id_and_mapping_for_single_ref(self, ref, domain_id,
  624                                                   driver, entity_type, conf):
  625         LOG.debug('Local ID: %s', ref['id'])
  626         ref = ref.copy()
  627 
  628         if not driver.is_domain_aware():
  629             if not domain_id:
  630                 domain_id = CONF.identity.default_domain_id
  631             ref['domain_id'] = domain_id
  632 
  633         if self._is_mapping_needed(driver):
  634             local_entity = {'domain_id': ref['domain_id'],
  635                             'local_id': ref['id'],
  636                             'entity_type': entity_type}
  637             public_id = PROVIDERS.id_mapping_api.get_public_id(local_entity)
  638             if public_id:
  639                 ref['id'] = public_id
  640                 LOG.debug('Found existing mapping to public ID: %s',
  641                           ref['id'])
  642             else:
  643                 self._insert_new_public_id(local_entity, ref, driver)
  644         return ref
  645 
  646     def _set_domain_id_and_mapping_for_list(self, ref_list, domain_id, driver,
  647                                             entity_type, conf):
  648         """Set domain id and mapping for a list of refs.
  649 
  650         The method modifies refs in-place.
  651         """
  652         if not ref_list:
  653             return []
  654 
  655         # If the domain_id is None that means we are running in a single
  656         # backend mode, so to remain backwards compatible we will use the
  657         # default domain ID.
  658         if not domain_id:
  659             domain_id = CONF.identity.default_domain_id
  660 
  661         if not driver.is_domain_aware():
  662             for ref in ref_list:
  663                 ref['domain_id'] = domain_id
  664 
  665         if not self._is_mapping_needed(driver):
  666             return ref_list
  667 
  668         # build a map of refs for fast look-up
  669         refs_map = {}
  670         for r in ref_list:
  671             refs_map[(r['id'], entity_type, r['domain_id'])] = r
  672 
  673         # fetch all mappings for the domain, lookup the user at the map built
  674         # at previous step and replace his id.
  675         domain_mappings = PROVIDERS.id_mapping_api.get_domain_mapping_list(
  676             domain_id, entity_type=entity_type)
  677         for _mapping in domain_mappings:
  678             idx = (_mapping.local_id, _mapping.entity_type, _mapping.domain_id)
  679             try:
  680                 ref = refs_map.pop(idx)
  681                 # due to python specifics, `ref` still points to an item in
  682                 # `ref_list`. That's why when we change it here, it gets
  683                 # changed in `ref_list`.
  684                 ref['id'] = _mapping.public_id
  685             except KeyError:
  686                 pass  # some old entry, skip it
  687 
  688         # at this point, all known refs were granted a public_id. For the refs
  689         # left, there are no mappings. They need to be created.
  690         for ref in refs_map.values():
  691             local_entity = {'domain_id': ref['domain_id'],
  692                             'local_id': ref['id'],
  693                             'entity_type': entity_type}
  694             self._insert_new_public_id(local_entity, ref, driver)
  695         return ref_list
  696 
  697     def _is_mapping_needed(self, driver):
  698         """Return whether mapping is needed.
  699 
  700         There are two situations where we must use the mapping:
  701         - this isn't the default driver (i.e. multiple backends), or
  702         - we have a single backend that doesn't use UUIDs
  703         The exception to the above is that we must honor backward
  704         compatibility if this is the default driver (e.g. to support
  705         current LDAP)
  706         """
  707         is_not_default_driver = driver is not self.driver
  708         return (is_not_default_driver or (
  709             not driver.generates_uuids() and
  710             not CONF.identity_mapping.backward_compatible_ids))
  711 
  712     def _clear_domain_id_if_domain_unaware(self, driver, ref):
  713         """Clear domain_id details if driver is not domain aware."""
  714         if not driver.is_domain_aware() and 'domain_id' in ref:
  715             ref = ref.copy()
  716             ref.pop('domain_id')
  717         return ref
  718 
  719     def _select_identity_driver(self, domain_id):
  720         """Choose a backend driver for the given domain_id.
  721 
  722         :param domain_id: The domain_id for which we want to find a driver.  If
  723                           the domain_id is specified as None, then this means
  724                           we need a driver that handles multiple domains.
  725 
  726         :returns: chosen backend driver
  727 
  728         If there is a specific driver defined for this domain then choose it.
  729         If the domain is None, or there no specific backend for the given
  730         domain is found, then we chose the default driver.
  731 
  732         """
  733         if domain_id is None:
  734             driver = self.driver
  735         else:
  736             driver = (self.domain_configs.get_domain_driver(domain_id) or
  737                       self.driver)
  738 
  739         # If the driver is not domain aware (e.g. LDAP) then check to
  740         # ensure we are not mapping multiple domains onto it - the only way
  741         # that would happen is that the default driver is LDAP and the
  742         # domain is anything other than None or the default domain.
  743         if (not driver.is_domain_aware() and driver == self.driver and
  744             domain_id != CONF.identity.default_domain_id and
  745                 domain_id is not None):
  746             LOG.warning('Found multiple domains being mapped to a '
  747                         'driver that does not support that (e.g. '
  748                         'LDAP) - Domain ID: %(domain)s, '
  749                         'Default Driver: %(driver)s',
  750                         {'domain': domain_id,
  751                          'driver': (driver == self.driver)})
  752             raise exception.DomainNotFound(domain_id=domain_id)
  753         return driver
  754 
  755     def _get_domain_driver_and_entity_id(self, public_id):
  756         """Look up details using the public ID.
  757 
  758         :param public_id: the ID provided in the call
  759 
  760         :returns: domain_id, which can be None to indicate that the driver
  761                   in question supports multiple domains
  762                   driver selected based on this domain
  763                   entity_id which will is understood by the driver.
  764 
  765         Use the mapping table to look up the domain, driver and local entity
  766         that is represented by the provided public ID.  Handle the situations
  767         where we do not use the mapping (e.g. single driver that understands
  768         UUIDs etc.)
  769 
  770         """
  771         conf = CONF.identity
  772         # First, since we don't know anything about the entity yet, we must
  773         # assume it needs mapping, so long as we are using domain specific
  774         # drivers.
  775         if conf.domain_specific_drivers_enabled:
  776             local_id_ref = PROVIDERS.id_mapping_api.get_id_mapping(public_id)
  777             if local_id_ref:
  778                 return (
  779                     local_id_ref['domain_id'],
  780                     self._select_identity_driver(local_id_ref['domain_id']),
  781                     local_id_ref['local_id'])
  782 
  783         # So either we are using multiple drivers but the public ID is invalid
  784         # (and hence was not found in the mapping table), or the public ID is
  785         # being handled by the default driver.  Either way, the only place left
  786         # to look is in that standard driver. However, we don't yet know if
  787         # this driver also needs mapping (e.g. LDAP in non backward
  788         # compatibility mode).
  789         driver = self.driver
  790         if driver.generates_uuids():
  791             if driver.is_domain_aware:
  792                 # No mapping required, and the driver can handle the domain
  793                 # information itself.  The classic case of this is the
  794                 # current SQL driver.
  795                 return (None, driver, public_id)
  796             else:
  797                 # Although we don't have any drivers of this type, i.e. that
  798                 # understand UUIDs but not domains, conceptually you could.
  799                 return (conf.default_domain_id, driver, public_id)
  800 
  801         # So the only place left to find the ID is in the default driver which
  802         # we now know doesn't generate UUIDs
  803         if not CONF.identity_mapping.backward_compatible_ids:
  804             # We are not running in backward compatibility mode, so we
  805             # must use a mapping.
  806             local_id_ref = PROVIDERS.id_mapping_api.get_id_mapping(public_id)
  807             if local_id_ref:
  808                 return (
  809                     local_id_ref['domain_id'],
  810                     driver,
  811                     local_id_ref['local_id'])
  812             else:
  813                 raise exception.PublicIDNotFound(id=public_id)
  814 
  815         # If we reach here, this means that the default driver
  816         # requires no mapping - but also doesn't understand domains
  817         # (e.g. the classic single LDAP driver situation). Hence we pass
  818         # back the public_ID unmodified and use the default domain (to
  819         # keep backwards compatibility with existing installations).
  820         #
  821         # It is still possible that the public ID is just invalid in
  822         # which case we leave this to the caller to check.
  823         return (conf.default_domain_id, driver, public_id)
  824 
  825     def _assert_user_and_group_in_same_backend(
  826             self, user_entity_id, user_driver, group_entity_id, group_driver):
  827         """Ensure that user and group IDs are backed by the same backend.
  828 
  829         Raise a CrossBackendNotAllowed exception if they are not from the same
  830         backend, otherwise return None.
  831 
  832         """
  833         if user_driver is not group_driver:
  834             # Determine first if either IDs don't exist by calling
  835             # the driver.get methods (which will raise a NotFound
  836             # exception).
  837             user_driver.get_user(user_entity_id)
  838             group_driver.get_group(group_entity_id)
  839             # If we get here, then someone is attempting to create a cross
  840             # backend membership, which is not allowed.
  841             raise exception.CrossBackendNotAllowed(group_id=group_entity_id,
  842                                                    user_id=user_entity_id)
  843 
  844     def _mark_domain_id_filter_satisfied(self, hints):
  845         if hints:
  846             for filter in hints.filters:
  847                 if (filter['name'] == 'domain_id' and
  848                         filter['comparator'] == 'equals'):
  849                     hints.filters.remove(filter)
  850 
  851     def _ensure_domain_id_in_hints(self, hints, domain_id):
  852         if (domain_id is not None and
  853                 not hints.get_exact_filter_by_name('domain_id')):
  854             hints.add_filter('domain_id', domain_id)
  855 
  856     def _set_list_limit_in_hints(self, hints, driver):
  857         """Set list limit in hints from driver.
  858 
  859         If a hints list is provided, the wrapper will insert the relevant
  860         limit into the hints so that the underlying driver call can try and
  861         honor it. If the driver does truncate the response, it will update the
  862         'truncated' attribute in the 'limit' entry in the hints list, which
  863         enables the caller of this function to know if truncation has taken
  864         place. If, however, the driver layer is unable to perform truncation,
  865         the 'limit' entry is simply left in the hints list for the caller to
  866         handle.
  867 
  868         A _get_list_limit() method is required to be present in the object
  869         class hierarchy, which returns the limit for this backend to which
  870         we will truncate.
  871 
  872         If a hints list is not provided in the arguments of the wrapped call
  873         then any limits set in the config file are ignored.  This allows
  874         internal use of such wrapped methods where the entire data set is
  875         needed as input for the calculations of some other API (e.g. get role
  876         assignments for a given project).
  877 
  878         This method, specific to identity manager, is used instead of more
  879         general response_truncated, because the limit for identity entities
  880         can be overridden in domain-specific config files. The driver to use
  881         is determined during processing of the passed parameters and
  882         response_truncated is designed to set the limit before any processing.
  883         """
  884         if hints is None:
  885             return
  886 
  887         list_limit = driver._get_list_limit()
  888         if list_limit:
  889             hints.set_limit(list_limit)
  890 
  891     # The actual driver calls - these are pre/post processed here as
  892     # part of the Manager layer to make sure we:
  893     #
  894     # - select the right driver for this domain
  895     # - clear/set domain_ids for drivers that do not support domains
  896     # - create any ID mapping that might be required
  897     @notifications.emit_event('authenticate')
  898     @domains_configured
  899     @exception_translated('assertion')
  900     def authenticate(self, user_id, password):
  901         domain_id, driver, entity_id = (
  902             self._get_domain_driver_and_entity_id(user_id))
  903         ref = driver.authenticate(entity_id, password)
  904         ref = self._set_domain_id_and_mapping(
  905             ref, domain_id, driver, mapping.EntityType.USER)
  906         ref = self._shadow_nonlocal_user(ref)
  907         PROVIDERS.shadow_users_api.set_last_active_at(ref['id'])
  908         return ref
  909 
  910     def _assert_default_project_id_is_not_domain(self, default_project_id):
  911         if default_project_id:
  912             # make sure project is not a domain
  913             try:
  914                 project_ref = PROVIDERS.resource_api.get_project(
  915                     default_project_id
  916                 )
  917                 if project_ref['is_domain'] is True:
  918                     msg = _("User's default project ID cannot be a "
  919                             "domain ID: %s")
  920                     raise exception.ValidationError(
  921                         message=(msg % default_project_id))
  922             except exception.ProjectNotFound:
  923                 # should be idempotent if project is not found so that it is
  924                 # backward compatible
  925                 pass
  926 
  927     def _validate_federated_objects(self, fed_obj_list):
  928         # Validate that the ipd and protocols exist
  929         for fed_obj in fed_obj_list:
  930             try:
  931                 self.federation_api.get_idp(fed_obj['idp_id'])
  932             except exception.IdentityProviderNotFound:
  933                 msg = (_("Could not find Identity Provider: %s")
  934                        % fed_obj['idp_id'])
  935                 raise exception.ValidationError(msg)
  936             for protocol in fed_obj['protocols']:
  937                 try:
  938                     self.federation_api.get_protocol(fed_obj['idp_id'],
  939                                                      protocol['protocol_id'])
  940                 except exception.FederatedProtocolNotFound:
  941                     msg = (_("Could not find federated protocol "
  942                              "%(protocol)s for Identity Provider: %(idp)s.")
  943                            % {'protocol': protocol['protocol_id'],
  944                               'idp': fed_obj['idp_id']})
  945                     raise exception.ValidationError(msg)
  946 
  947     def _create_federated_objects(self, user_ref, fed_obj_list):
  948         for fed_obj in fed_obj_list:
  949             for protocols in fed_obj['protocols']:
  950                 federated_dict = {
  951                     'user_id': user_ref['id'],
  952                     'idp_id': fed_obj['idp_id'],
  953                     'protocol_id': protocols['protocol_id'],
  954                     'unique_id': protocols['unique_id'],
  955                     'display_name': user_ref['name']
  956                 }
  957                 self.shadow_users_api.create_federated_object(
  958                     federated_dict)
  959 
  960     def _create_user_with_federated_objects(self, user, driver):
  961         # If the user did not pass a federated object along inside the user
  962         # object then we simply create the user as normal.
  963         if not user.get('federated'):
  964             if 'federated' in user:
  965                 del user['federated']
  966             user = driver.create_user(user['id'], user)
  967             return user
  968         # Otherwise, validate the federated object and create the user.
  969         else:
  970             user_ref = user.copy()
  971             del user['federated']
  972             self._validate_federated_objects(user_ref['federated'])
  973             user = driver.create_user(user['id'], user)
  974             self._create_federated_objects(user_ref, user_ref['federated'])
  975             user['federated'] = user_ref['federated']
  976             return user
  977 
  978     @domains_configured
  979     @exception_translated('user')
  980     def create_user(self, user_ref, initiator=None):
  981         user = user_ref.copy()
  982         if 'password' in user:
  983             validators.validate_password(user['password'])
  984         user['name'] = user['name'].strip()
  985         user.setdefault('enabled', True)
  986         domain_id = user['domain_id']
  987         PROVIDERS.resource_api.get_domain(domain_id)
  988 
  989         self._assert_default_project_id_is_not_domain(
  990             user_ref.get('default_project_id'))
  991 
  992         # For creating a user, the domain is in the object itself
  993         domain_id = user_ref['domain_id']
  994         driver = self._select_identity_driver(domain_id)
  995         user = self._clear_domain_id_if_domain_unaware(driver, user)
  996         # Generate a local ID - in the future this might become a function of
  997         # the underlying driver so that it could conform to rules set down by
  998         # that particular driver type.
  999         user['id'] = uuid.uuid4().hex
 1000         ref = self._create_user_with_federated_objects(user, driver)
 1001         notifications.Audit.created(self._USER, user['id'], initiator)
 1002         return self._set_domain_id_and_mapping(
 1003             ref, domain_id, driver, mapping.EntityType.USER)
 1004 
 1005     @domains_configured
 1006     @exception_translated('user')
 1007     @MEMOIZE
 1008     def get_user(self, user_id):
 1009         domain_id, driver, entity_id = (
 1010             self._get_domain_driver_and_entity_id(user_id))
 1011         ref = driver.get_user(entity_id)
 1012         # Add user's federated objects
 1013         fed_objects = self.shadow_users_api.get_federated_objects(user_id)
 1014         if fed_objects:
 1015             ref['federated'] = fed_objects
 1016         return self._set_domain_id_and_mapping(
 1017             ref, domain_id, driver, mapping.EntityType.USER)
 1018 
 1019     def assert_user_enabled(self, user_id, user=None):
 1020         """Assert the user and the user's domain are enabled.
 1021 
 1022         :raise AssertionError if the user or the user's domain is disabled.
 1023         """
 1024         if user is None:
 1025             user = self.get_user(user_id)
 1026         PROVIDERS.resource_api.assert_domain_enabled(user['domain_id'])
 1027         if not user.get('enabled', True):
 1028             raise AssertionError(_('User is disabled: %s') % user_id)
 1029 
 1030     @domains_configured
 1031     @exception_translated('user')
 1032     @MEMOIZE
 1033     def get_user_by_name(self, user_name, domain_id):
 1034         driver = self._select_identity_driver(domain_id)
 1035         ref = driver.get_user_by_name(user_name, domain_id)
 1036         return self._set_domain_id_and_mapping(
 1037             ref, domain_id, driver, mapping.EntityType.USER)
 1038 
 1039     def _translate_expired_password_hints(self, hints):
 1040         """Clean Up Expired Password Hints.
 1041 
 1042         Any `password_expires_at` filters on the `list_users` or
 1043         `list_users_in_group` queries are modified so the call will
 1044         return valid data.
 1045 
 1046         The filters `comparator` is changed to the operator specified in
 1047         the call, otherwise it is assumed to be `equals`. The filters
 1048         `value` becomes the timestamp specified. Both the operator and
 1049         timestamp are validated, and will raise a InvalidOperatorError
 1050         or ValidationTimeStampError exception respectively if invalid.
 1051 
 1052         """
 1053         operators = {'lt': operator.lt, 'gt': operator.gt,
 1054                      'eq': operator.eq, 'lte': operator.le,
 1055                      'gte': operator.ge, 'neq': operator.ne}
 1056         for filter_ in hints.filters:
 1057             if 'password_expires_at' == filter_['name']:
 1058                 # password_expires_at must be in the format
 1059                 # 'lt:2016-11-06T15:32:17Z'. So we can assume the position
 1060                 # of the ':' otherwise assign the operator to equals.
 1061                 if ':' in filter_['value'][2:4]:
 1062                     op, timestamp = filter_['value'].split(':', 1)
 1063                 else:
 1064                     op = 'eq'
 1065                     timestamp = filter_['value']
 1066 
 1067                 try:
 1068                     filter_['value'] = timeutils.parse_isotime(timestamp)
 1069                 except ValueError:
 1070                     raise exception.ValidationTimeStampError
 1071 
 1072                 try:
 1073                     filter_['comparator'] = operators[op]
 1074                 except KeyError:
 1075                     raise exception.InvalidOperatorError(_op=op)
 1076         return hints
 1077 
 1078     def _handle_shadow_and_local_users(self, driver, hints):
 1079         federated_attributes = {'idp_id', 'protocol_id', 'unique_id'}
 1080         fed_res = []
 1081         for filter_ in hints.filters:
 1082             if filter_['name'] in federated_attributes:
 1083                 return PROVIDERS.shadow_users_api.get_federated_users(hints)
 1084             # Note: If the filters contain 'name', we should get the user from
 1085             # both local user and shadow user backend.
 1086             if filter_['name'] == 'name':
 1087                 fed_hints = copy.deepcopy(hints)
 1088                 fed_res = PROVIDERS.shadow_users_api.get_federated_users(
 1089                     fed_hints)
 1090                 break
 1091         return driver.list_users(hints) + fed_res
 1092 
 1093     @domains_configured
 1094     @exception_translated('user')
 1095     def list_users(self, domain_scope=None, hints=None):
 1096         driver = self._select_identity_driver(domain_scope)
 1097         self._set_list_limit_in_hints(hints, driver)
 1098         hints = hints or driver_hints.Hints()
 1099         if driver.is_domain_aware():
 1100             # Force the domain_scope into the hint to ensure that we only get
 1101             # back domains for that scope.
 1102             self._ensure_domain_id_in_hints(hints, domain_scope)
 1103         else:
 1104             # We are effectively satisfying any domain_id filter by the above
 1105             # driver selection, so remove any such filter.
 1106             self._mark_domain_id_filter_satisfied(hints)
 1107         hints = self._translate_expired_password_hints(hints)
 1108         ref_list = self._handle_shadow_and_local_users(driver, hints)
 1109         return self._set_domain_id_and_mapping(
 1110             ref_list, domain_scope, driver, mapping.EntityType.USER)
 1111 
 1112     def _require_matching_domain_id(self, new_ref, orig_ref):
 1113         """Ensure the current domain ID matches the reference one, if any.
 1114 
 1115         Provided we want domain IDs to be immutable, check whether any
 1116         domain_id specified in the ref dictionary matches the existing
 1117         domain_id for this entity.
 1118 
 1119         :param new_ref: the dictionary of new values proposed for this entity
 1120         :param orig_ref: the dictionary of original values proposed for this
 1121                          entity
 1122         :raises: :class:`keystone.exception.ValidationError`
 1123         """
 1124         if 'domain_id' in new_ref:
 1125             if new_ref['domain_id'] != orig_ref['domain_id']:
 1126                 raise exception.ValidationError(_('Cannot change Domain ID'))
 1127 
 1128     def _update_user_with_federated_objects(self, user, driver, entity_id):
 1129         # If the user did not pass a federated object along inside the user
 1130         # object then we simply update the user as normal and add the
 1131         # currently associated federated objects to user to be added to the
 1132         # dictionary.
 1133         if not user.get('federated'):
 1134             if 'federated' in user:
 1135                 del user['federated']
 1136             user = driver.update_user(entity_id, user)
 1137             fed_objects = self.shadow_users_api.get_federated_objects(
 1138                 user['id'])
 1139             if fed_objects:
 1140                 user['federated'] = fed_objects
 1141             return user
 1142         # Otherwise, we validate, remove the previous user's federated objects,
 1143         # and update the user along with their updated federated objects.
 1144         else:
 1145             user_ref = user.copy()
 1146             self._validate_federated_objects(user_ref['federated'])
 1147             self.shadow_users_api.delete_federated_object(entity_id)
 1148             del user['federated']
 1149             user = driver.update_user(entity_id, user)
 1150             self._create_federated_objects(user, user_ref['federated'])
 1151             user['federated'] = user_ref['federated']
 1152             return user
 1153 
 1154     @domains_configured
 1155     @exception_translated('user')
 1156     def update_user(self, user_id, user_ref, initiator=None):
 1157         old_user_ref = self.get_user(user_id)
 1158         user = user_ref.copy()
 1159         self._require_matching_domain_id(user, old_user_ref)
 1160         if 'password' in user:
 1161             validators.validate_password(user['password'])
 1162         if 'name' in user:
 1163             user['name'] = user['name'].strip()
 1164         if 'id' in user:
 1165             if user_id != user['id']:
 1166                 raise exception.ValidationError(_('Cannot change user ID'))
 1167             # Since any ID in the user dict is now irrelevant, remove its so as
 1168             # the driver layer won't be confused by the fact the this is the
 1169             # public ID not the local ID
 1170             user.pop('id')
 1171 
 1172         self._assert_default_project_id_is_not_domain(
 1173             user_ref.get('default_project_id'))
 1174 
 1175         domain_id, driver, entity_id = (
 1176             self._get_domain_driver_and_entity_id(user_id))
 1177         user = self._clear_domain_id_if_domain_unaware(driver, user)
 1178         self.get_user.invalidate(self, old_user_ref['id'])
 1179         self.get_user_by_name.invalidate(self, old_user_ref['name'],
 1180                                          old_user_ref['domain_id'])
 1181 
 1182         ref = self._update_user_with_federated_objects(user, driver, entity_id)
 1183 
 1184         notifications.Audit.updated(self._USER, user_id, initiator)
 1185 
 1186         enabled_change = ((user.get('enabled') is False) and
 1187                           user['enabled'] != old_user_ref.get('enabled'))
 1188         if enabled_change or user.get('password') is not None:
 1189             self._persist_revocation_event_for_user(user_id)
 1190             reason = (
 1191                 'Invalidating the token cache because user %(user_id)s was '
 1192                 'enabled or disabled. Authorization will be calculated and '
 1193                 'enforced accordingly the next time they authenticate or '
 1194                 'validate a token.' % {'user_id': user_id}
 1195             )
 1196             notifications.invalidate_token_cache_notification(reason)
 1197 
 1198         return self._set_domain_id_and_mapping(
 1199             ref, domain_id, driver, mapping.EntityType.USER)
 1200 
 1201     @domains_configured
 1202     @exception_translated('user')
 1203     def delete_user(self, user_id, initiator=None):
 1204         domain_id, driver, entity_id = (
 1205             self._get_domain_driver_and_entity_id(user_id))
 1206         # Get user details to invalidate the cache.
 1207         user_old = self.get_user(user_id)
 1208 
 1209         hints = driver_hints.Hints()
 1210         hints.add_filter('user_id', user_id)
 1211         fed_users = PROVIDERS.shadow_users_api.list_federated_users_info(hints)
 1212 
 1213         driver.delete_user(entity_id)
 1214         PROVIDERS.assignment_api.delete_user_assignments(user_id)
 1215         self.get_user.invalidate(self, user_id)
 1216         self.get_user_by_name.invalidate(self, user_old['name'],
 1217                                          user_old['domain_id'])
 1218         for fed_user in fed_users:
 1219             self._shadow_federated_user.invalidate(
 1220                 self, fed_user['idp_id'], fed_user['protocol_id'],
 1221                 fed_user['unique_id'], fed_user['display_name'],
 1222                 user_old.get('extra', {}).get('email'))
 1223 
 1224         PROVIDERS.credential_api.delete_credentials_for_user(user_id)
 1225         PROVIDERS.id_mapping_api.delete_id_mapping(user_id)
 1226         notifications.Audit.deleted(self._USER, user_id, initiator)
 1227 
 1228         # Invalidate user role assignments cache region, as it may be caching
 1229         # role assignments where the actor is the specified user
 1230         assignment.COMPUTED_ASSIGNMENTS_REGION.invalidate()
 1231 
 1232     @domains_configured
 1233     @exception_translated('group')
 1234     def create_group(self, group_ref, initiator=None):
 1235         group = group_ref.copy()
 1236         group.setdefault('description', '')
 1237         domain_id = group['domain_id']
 1238         PROVIDERS.resource_api.get_domain(domain_id)
 1239 
 1240         # For creating a group, the domain is in the object itself
 1241         domain_id = group_ref['domain_id']
 1242         driver = self._select_identity_driver(domain_id)
 1243         group = self._clear_domain_id_if_domain_unaware(driver, group)
 1244         # Generate a local ID - in the future this might become a function of
 1245         # the underlying driver so that it could conform to rules set down by
 1246         # that particular driver type.
 1247         group['id'] = uuid.uuid4().hex
 1248         group['name'] = group['name'].strip()
 1249         ref = driver.create_group(group['id'], group)
 1250 
 1251         notifications.Audit.created(self._GROUP, group['id'], initiator)
 1252 
 1253         return self._set_domain_id_and_mapping(
 1254             ref, domain_id, driver, mapping.EntityType.GROUP)
 1255 
 1256     @domains_configured
 1257     @exception_translated('group')
 1258     @MEMOIZE
 1259     def get_group(self, group_id):
 1260         domain_id, driver, entity_id = (
 1261             self._get_domain_driver_and_entity_id(group_id))
 1262         ref = driver.get_group(entity_id)
 1263         return self._set_domain_id_and_mapping(
 1264             ref, domain_id, driver, mapping.EntityType.GROUP)
 1265 
 1266     @domains_configured
 1267     @exception_translated('group')
 1268     def get_group_by_name(self, group_name, domain_id):
 1269         driver = self._select_identity_driver(domain_id)
 1270         ref = driver.get_group_by_name(group_name, domain_id)
 1271         return self._set_domain_id_and_mapping(
 1272             ref, domain_id, driver, mapping.EntityType.GROUP)
 1273 
 1274     @domains_configured
 1275     @exception_translated('group')
 1276     def update_group(self, group_id, group, initiator=None):
 1277         old_group_ref = self.get_group(group_id)
 1278         self._require_matching_domain_id(group, old_group_ref)
 1279         domain_id, driver, entity_id = (
 1280             self._get_domain_driver_and_entity_id(group_id))
 1281         group = self._clear_domain_id_if_domain_unaware(driver, group)
 1282         if 'name' in group:
 1283             group['name'] = group['name'].strip()
 1284         ref = driver.update_group(entity_id, group)
 1285         self.get_group.invalidate(self, group_id)
 1286         notifications.Audit.updated(self._GROUP, group_id, initiator)
 1287         return self._set_domain_id_and_mapping(
 1288             ref, domain_id, driver, mapping.EntityType.GROUP)
 1289 
 1290     @domains_configured
 1291     @exception_translated('group')
 1292     def delete_group(self, group_id, initiator=None):
 1293         domain_id, driver, entity_id = (
 1294             self._get_domain_driver_and_entity_id(group_id))
 1295         roles = PROVIDERS.assignment_api.list_role_assignments(
 1296             group_id=group_id
 1297         )
 1298         user_ids = (u['id'] for u in self.list_users_in_group(group_id))
 1299         driver.delete_group(entity_id)
 1300         self.get_group.invalidate(self, group_id)
 1301         PROVIDERS.id_mapping_api.delete_id_mapping(group_id)
 1302         PROVIDERS.assignment_api.delete_group_assignments(group_id)
 1303 
 1304         notifications.Audit.deleted(self._GROUP, group_id, initiator)
 1305 
 1306         # If the group has been created and has users but has no role
 1307         # assignment for the group then we do not need to revoke all the users
 1308         # tokens and can just delete the group.
 1309         if roles:
 1310             for user_id in user_ids:
 1311                 self._persist_revocation_event_for_user(user_id)
 1312 
 1313         # Invalidate user role assignments cache region, as it may be caching
 1314         # role assignments expanded from the specified group to its users
 1315         assignment.COMPUTED_ASSIGNMENTS_REGION.invalidate()
 1316 
 1317     @domains_configured
 1318     @exception_translated('group')
 1319     def add_user_to_group(self, user_id, group_id, initiator=None):
 1320         @exception_translated('user')
 1321         def get_entity_info_for_user(public_id):
 1322             return self._get_domain_driver_and_entity_id(public_id)
 1323 
 1324         _domain_id, group_driver, group_entity_id = (
 1325             self._get_domain_driver_and_entity_id(group_id))
 1326         # Get the same info for the user_id, taking care to map any
 1327         # exceptions correctly
 1328         _domain_id, user_driver, user_entity_id = (
 1329             get_entity_info_for_user(user_id))
 1330 
 1331         self._assert_user_and_group_in_same_backend(
 1332             user_entity_id, user_driver, group_entity_id, group_driver)
 1333 
 1334         group_driver.add_user_to_group(user_entity_id, group_entity_id)
 1335 
 1336         # Invalidate user role assignments cache region, as it may now need to
 1337         # include role assignments from the specified group to its users
 1338         assignment.COMPUTED_ASSIGNMENTS_REGION.invalidate()
 1339         notifications.Audit.added_to(self._GROUP, group_id, self._USER,
 1340                                      user_id, initiator)
 1341 
 1342     @domains_configured
 1343     @exception_translated('group')
 1344     def remove_user_from_group(self, user_id, group_id, initiator=None):
 1345         @exception_translated('user')
 1346         def get_entity_info_for_user(public_id):
 1347             return self._get_domain_driver_and_entity_id(public_id)
 1348 
 1349         _domain_id, group_driver, group_entity_id = (
 1350             self._get_domain_driver_and_entity_id(group_id))
 1351         # Get the same info for the user_id, taking care to map any
 1352         # exceptions correctly
 1353         _domain_id, user_driver, user_entity_id = (
 1354             get_entity_info_for_user(user_id))
 1355 
 1356         self._assert_user_and_group_in_same_backend(
 1357             user_entity_id, user_driver, group_entity_id, group_driver)
 1358 
 1359         group_driver.remove_user_from_group(user_entity_id, group_entity_id)
 1360         self._persist_revocation_event_for_user(user_id)
 1361 
 1362         # Invalidate user role assignments cache region, as it may be caching
 1363         # role assignments expanded from this group to this user
 1364         assignment.COMPUTED_ASSIGNMENTS_REGION.invalidate()
 1365         notifications.Audit.removed_from(self._GROUP, group_id, self._USER,
 1366                                          user_id, initiator)
 1367 
 1368     def _persist_revocation_event_for_user(self, user_id):
 1369         """Emit a notification to invoke a revocation event callback.
 1370 
 1371         Fire off an internal notification that will be consumed by the
 1372         revocation API to store a revocation record for a specific user.
 1373 
 1374         :param user_id: user identifier
 1375         :type user_id: string
 1376         """
 1377         notifications.Audit.internal(
 1378             notifications.PERSIST_REVOCATION_EVENT_FOR_USER, user_id
 1379         )
 1380 
 1381     @domains_configured
 1382     @exception_translated('user')
 1383     def list_groups_for_user(self, user_id, hints=None):
 1384         domain_id, driver, entity_id = (
 1385             self._get_domain_driver_and_entity_id(user_id))
 1386         self._set_list_limit_in_hints(hints, driver)
 1387         hints = hints or driver_hints.Hints()
 1388         if not driver.is_domain_aware():
 1389             # We are effectively satisfying any domain_id filter by the above
 1390             # driver selection, so remove any such filter
 1391             self._mark_domain_id_filter_satisfied(hints)
 1392         ref_list = driver.list_groups_for_user(entity_id, hints)
 1393         for ref in ref_list:
 1394             if 'membership_expires_at' not in ref:
 1395                 ref['membership_expires_at'] = None
 1396         return self._set_domain_id_and_mapping(
 1397             ref_list, domain_id, driver, mapping.EntityType.GROUP)
 1398 
 1399     @domains_configured
 1400     @exception_translated('group')
 1401     def list_groups(self, domain_scope=None, hints=None):
 1402         driver = self._select_identity_driver(domain_scope)
 1403         self._set_list_limit_in_hints(hints, driver)
 1404         hints = hints or driver_hints.Hints()
 1405         if driver.is_domain_aware():
 1406             # Force the domain_scope into the hint to ensure that we only get
 1407             # back domains for that scope.
 1408             self._ensure_domain_id_in_hints(hints, domain_scope)
 1409         else:
 1410             # We are effectively satisfying any domain_id filter by the above
 1411             # driver selection, so remove any such filter.
 1412             self._mark_domain_id_filter_satisfied(hints)
 1413         ref_list = driver.list_groups(hints)
 1414         return self._set_domain_id_and_mapping(
 1415             ref_list, domain_scope, driver, mapping.EntityType.GROUP)
 1416 
 1417     @domains_configured
 1418     @exception_translated('group')
 1419     def list_users_in_group(self, group_id, hints=None):
 1420         domain_id, driver, entity_id = (
 1421             self._get_domain_driver_and_entity_id(group_id))
 1422         self._set_list_limit_in_hints(hints, driver)
 1423         hints = hints or driver_hints.Hints()
 1424         if not driver.is_domain_aware():
 1425             # We are effectively satisfying any domain_id filter by the above
 1426             # driver selection, so remove any such filter
 1427             self._mark_domain_id_filter_satisfied(hints)
 1428         hints = self._translate_expired_password_hints(hints)
 1429         ref_list = driver.list_users_in_group(entity_id, hints)
 1430         return self._set_domain_id_and_mapping(
 1431             ref_list, domain_id, driver, mapping.EntityType.USER)
 1432 
 1433     @domains_configured
 1434     @exception_translated('group')
 1435     def check_user_in_group(self, user_id, group_id):
 1436         @exception_translated('user')
 1437         def get_entity_info_for_user(public_id):
 1438             return self._get_domain_driver_and_entity_id(public_id)
 1439 
 1440         _domain_id, group_driver, group_entity_id = (
 1441             self._get_domain_driver_and_entity_id(group_id))
 1442         # Get the same info for the user_id, taking care to map any
 1443         # exceptions correctly
 1444         _domain_id, user_driver, user_entity_id = (
 1445             get_entity_info_for_user(user_id))
 1446 
 1447         self._assert_user_and_group_in_same_backend(
 1448             user_entity_id, user_driver, group_entity_id, group_driver)
 1449 
 1450         return group_driver.check_user_in_group(user_entity_id,
 1451                                                 group_entity_id)
 1452 
 1453     @domains_configured
 1454     def change_password(self, user_id, original_password,
 1455                         new_password, initiator=None):
 1456 
 1457         # authenticate() will raise an AssertionError if authentication fails
 1458         try:
 1459             self.authenticate(user_id, original_password)
 1460         except exception.PasswordExpired:
 1461             # If a password has expired, we want users to be able to change it
 1462             pass
 1463 
 1464         domain_id, driver, entity_id = (
 1465             self._get_domain_driver_and_entity_id(user_id))
 1466         try:
 1467             validators.validate_password(new_password)
 1468             driver.change_password(entity_id, new_password)
 1469         except exception.PasswordValidationError as ex:
 1470             audit_reason = reason.Reason(str(ex), str(ex.code))
 1471             notifications.Audit.updated(self._USER, user_id,
 1472                                         initiator, reason=audit_reason)
 1473             raise
 1474 
 1475         notifications.Audit.updated(self._USER, user_id, initiator)
 1476         self._persist_revocation_event_for_user(user_id)
 1477 
 1478     @MEMOIZE
 1479     def _shadow_nonlocal_user(self, user):
 1480         try:
 1481             return PROVIDERS.shadow_users_api.get_user(user['id'])
 1482         except exception.UserNotFound:
 1483             return PROVIDERS.shadow_users_api.create_nonlocal_user(user)
 1484 
 1485     @MEMOIZE
 1486     def _shadow_federated_user(self, idp_id, protocol_id, unique_id,
 1487                                display_name, email=None):
 1488         user_dict = {}
 1489         try:
 1490             PROVIDERS.shadow_users_api.update_federated_user_display_name(
 1491                 idp_id, protocol_id, unique_id, display_name)
 1492             user_dict = PROVIDERS.shadow_users_api.get_federated_user(
 1493                 idp_id, protocol_id, unique_id)
 1494             if email:
 1495                 user_ref = {"email": email}
 1496                 self.update_user(user_dict['id'], user_ref)
 1497                 user_dict.update({"email": email})
 1498         except exception.UserNotFound:
 1499             idp = PROVIDERS.federation_api.get_idp(idp_id)
 1500             federated_dict = {
 1501                 'idp_id': idp_id,
 1502                 'protocol_id': protocol_id,
 1503                 'unique_id': unique_id,
 1504                 'display_name': display_name
 1505             }
 1506             user_dict = (
 1507                 PROVIDERS.shadow_users_api.create_federated_user(
 1508                     idp['domain_id'], federated_dict, email=email
 1509                 )
 1510             )
 1511         PROVIDERS.shadow_users_api.set_last_active_at(user_dict['id'])
 1512         return user_dict
 1513 
 1514     def shadow_federated_user(self, idp_id, protocol_id, unique_id,
 1515                               display_name, email=None, group_ids=None):
 1516             """Map a federated user to a user.
 1517 
 1518             :param idp_id: identity provider id
 1519             :param protocol_id: protocol id
 1520             :param unique_id: unique id for the user within the IdP
 1521             :param display_name: user's display name
 1522             :param email: user's email
 1523             :param group_ids: list of group ids to add the user to
 1524 
 1525             :returns: dictionary of the mapped User entity
 1526             """
 1527             user_dict = self._shadow_federated_user(
 1528                 idp_id, protocol_id, unique_id, display_name, email)
 1529             # Note(knikolla): The shadowing operation can be cached,
 1530             # however we need to update the expiring group memberships.
 1531             if group_ids:
 1532                 for group_id in group_ids:
 1533                     PROVIDERS.shadow_users_api.add_user_to_group_expires(
 1534                         user_dict['id'], group_id)
 1535             return user_dict
 1536 
 1537 
 1538 class MappingManager(manager.Manager):
 1539     """Default pivot point for the ID Mapping backend."""
 1540 
 1541     driver_namespace = 'keystone.identity.id_mapping'
 1542     _provides_api = 'id_mapping_api'
 1543 
 1544     def __init__(self):
 1545         super(MappingManager, self).__init__(CONF.identity_mapping.driver)
 1546 
 1547     @MEMOIZE_ID_MAPPING
 1548     def _get_public_id(self, domain_id, local_id, entity_type):
 1549         return self.driver.get_public_id({'domain_id': domain_id,
 1550                                           'local_id': local_id,
 1551                                           'entity_type': entity_type})
 1552 
 1553     def get_public_id(self, local_entity):
 1554         return self._get_public_id(local_entity['domain_id'],
 1555                                    local_entity['local_id'],
 1556                                    local_entity['entity_type'])
 1557 
 1558     @MEMOIZE_ID_MAPPING
 1559     def get_id_mapping(self, public_id):
 1560         return self.driver.get_id_mapping(public_id)
 1561 
 1562     def create_id_mapping(self, local_entity, public_id=None):
 1563         public_id = self.driver.create_id_mapping(local_entity, public_id)
 1564         if MEMOIZE_ID_MAPPING.should_cache(public_id):
 1565             self._get_public_id.set(public_id, self,
 1566                                     local_entity['domain_id'],
 1567                                     local_entity['local_id'],
 1568                                     local_entity['entity_type'])
 1569             self.get_id_mapping.set(local_entity, self, public_id)
 1570         return public_id
 1571 
 1572     def delete_id_mapping(self, public_id):
 1573         local_entity = self.get_id_mapping.get(self, public_id)
 1574         self.driver.delete_id_mapping(public_id)
 1575         # Delete the key of entity from cache
 1576         if local_entity:
 1577             self._get_public_id.invalidate(self, local_entity['domain_id'],
 1578                                            local_entity['local_id'],
 1579                                            local_entity['entity_type'])
 1580         self.get_id_mapping.invalidate(self, public_id)
 1581 
 1582     def purge_mappings(self, purge_filter):
 1583         # Purge mapping is rarely used and only used by the command client,
 1584         # it's quite complex to invalidate part of the cache based on the purge
 1585         # filters, so here invalidate the whole cache when purging mappings.
 1586         self.driver.purge_mappings(purge_filter)
 1587         ID_MAPPING_REGION.invalidate()
 1588 
 1589 
 1590 class ShadowUsersManager(manager.Manager):
 1591     """Default pivot point for the Shadow Users backend."""
 1592 
 1593     driver_namespace = 'keystone.identity.shadow_users'
 1594     _provides_api = 'shadow_users_api'
 1595 
 1596     def __init__(self):
 1597         shadow_driver = CONF.shadow_users.driver
 1598 
 1599         super(ShadowUsersManager, self).__init__(shadow_driver)