keystone  18.0.0
About: OpenStack Keystone (Core Service: Identity) provides an authentication and authorization service for other OpenStack services. Provides a catalog of endpoints for all OpenStack services.
The "Victoria" series (maintained release).
  Fossies Dox: keystone-18.0.0.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

core.py
Go to the documentation of this file.
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)
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:
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))
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)
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):
312  if domain_id in self:
313  return self[domain_id]['driver']
314 
315  def get_domain_conf(self, 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 
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 
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)
487  notifications.register_event_callback(
488  notifications.ACTIONS.internal, notifications.DOMAIN_DELETED,
489  self._domain_deleted
490  )
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'])
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):
601  ref, domain_id, driver, entity_type, conf)
602  elif isinstance(ref, 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 
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 
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 
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")
921  message=(msg % default_project_id))
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'])
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'])
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 
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 
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:
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.
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 
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:
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:
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 
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 
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)
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 
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
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.
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
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 
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)
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)
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)
keystone.identity.core.Manager.check_user_in_group
def check_user_in_group(self, user_id, group_id)
Definition: core.py:1435
keystone.identity.core.Manager._require_matching_domain_id
def _require_matching_domain_id(self, new_ref, orig_ref)
Definition: core.py:1112
keystone.identity.core.Manager._persist_revocation_event_for_user
def _persist_revocation_event_for_user(self, user_id)
Definition: core.py:1368
keystone.identity.core.Manager.list_users_in_group
def list_users_in_group(self, group_id, hints=None)
Definition: core.py:1419
keystone.identity.mapping_backends
Definition: __init__.py:1
keystone.identity.core.MappingManager.get_id_mapping
def get_id_mapping(self, public_id)
Definition: core.py:1559
keystone.exception.UserNotFound
Definition: exception.py:469
keystone.exception.ConfigRegistrationNotFound
Definition: exception.py:543
keystone.identity.core.Manager.list_users
def list_users(self, domain_scope=None, hints=None)
Definition: core.py:1095
keystone.identity.core.DomainConfigs._setup_domain_drivers_from_files
def _setup_domain_drivers_from_files(self, standard_driver, resource_api)
Definition: core.py:132
keystone.exception.FederatedProtocolNotFound
Definition: exception.py:509
keystone.identity.core.DomainConfigs.driver
driver
Definition: core.py:85
keystone.identity.core.Manager._translate_expired_password_hints
def _translate_expired_password_hints(self, hints)
Definition: core.py:1039
keystone.identity.core.Manager._validate_federated_objects
def _validate_federated_objects(self, fed_obj_list)
Definition: core.py:927
keystone.identity.core.DomainConfigs.setup_domain_drivers
def setup_domain_drivers(self, standard_driver, resource_api)
Definition: core.py:298
keystone.identity.core.Manager._insert_new_public_id
def _insert_new_public_id(self, local_entity, ref, driver)
Definition: core.py:613
keystone.exception.PasswordValidationError
Definition: exception.py:112
keystone.identity.core.DomainConfigs._load_config_from_file
def _load_config_from_file(self, resource_api, file_list, domain_name)
Definition: core.py:94
keystone.identity.core.MappingManager
Definition: core.py:1538
keystone.identity.core.Manager._assert_user_and_group_in_same_backend
def _assert_user_and_group_in_same_backend(self, user_entity_id, user_driver, group_entity_id, group_driver)
Definition: core.py:826
keystone.identity.core.Manager._is_mapping_needed
def _is_mapping_needed(self, driver)
Definition: core.py:697
keystone.identity.core.Manager.change_password
def change_password(self, user_id, original_password, new_password, initiator=None)
Definition: core.py:1455
keystone.identity.core.Manager._set_domain_id_and_mapping_for_list
def _set_domain_id_and_mapping_for_list(self, ref_list, domain_id, driver, entity_type, conf)
Definition: core.py:647
keystone.identity.core.Manager._create_federated_objects
def _create_federated_objects(self, user_ref, fed_obj_list)
Definition: core.py:947
keystone.identity.core.Manager.shadow_federated_user
def shadow_federated_user(self, idp_id, protocol_id, unique_id, display_name, email=None, group_ids=None)
Definition: core.py:1515
keystone.identity.core.DomainConfigs.configured
bool configured
Definition: core.py:84
keystone.exception.MultipleSQLDriversInConfig
Definition: exception.py:645
keystone.exception.InvalidOperatorError
Definition: exception.py:159
keystone.identity.core.Manager._shadow_nonlocal_user
def _shadow_nonlocal_user(self, user)
Definition: core.py:1479
keystone.identity.core.Manager.delete_group
def delete_group(self, group_id, initiator=None)
Definition: core.py:1292
keystone.identity.core.Manager._get_domain_driver_and_entity_id
def _get_domain_driver_and_entity_id(self, public_id)
Definition: core.py:755
keystone.identity.core.Manager.remove_user_from_group
def remove_user_from_group(self, user_id, group_id, initiator=None)
Definition: core.py:1344
keystone.identity.core.DomainConfigs.get_domain_driver
def get_domain_driver(self, domain_id)
Definition: core.py:310
keystone.identity.core.Manager._set_domain_id_and_mapping_for_single_ref
def _set_domain_id_and_mapping_for_single_ref(self, ref, domain_id, driver, entity_type, conf)
Definition: core.py:624
keystone.exception.ValidationError
Definition: exception.py:98
keystone.identity.core.DomainConfigs.reload_domain_driver
def reload_domain_driver(self, domain_id)
Definition: core.py:322
keystone.identity.core.Manager.list_groups
def list_groups(self, domain_scope=None, hints=None)
Definition: core.py:1401
keystone.identity.core.Manager._set_list_limit_in_hints
def _set_list_limit_in_hints(self, hints, driver)
Definition: core.py:856
keystone.identity.core.Manager._clear_domain_id_if_domain_unaware
def _clear_domain_id_if_domain_unaware(self, driver, ref)
Definition: core.py:712
keystone.exception.UnexpectedError
Definition: exception.py:566
keystone.identity.core.DomainConfigs._any_sql
bool _any_sql
Definition: core.py:86
keystone.identity.core.Manager._assert_default_project_id_is_not_domain
def _assert_default_project_id_is_not_domain(self, default_project_id)
Definition: core.py:910
keystone.identity.core.Manager.get_group_by_name
def get_group_by_name(self, group_name, domain_id)
Definition: core.py:1268
keystone.exception.CrossBackendNotAllowed
Definition: exception.py:364
keystone.identity.core.Manager.create_group
def create_group(self, group_ref, initiator=None)
Definition: core.py:1234
keystone.identity.core.MappingManager._get_public_id
def _get_public_id(self, domain_id, local_id, entity_type)
Definition: core.py:1548
keystone.identity.core.Manager.delete_user
def delete_user(self, user_id, initiator=None)
Definition: core.py:1203
keystone.identity.core.Manager._USER
string _USER
Definition: core.py:481
keystone.identity.core.DomainConfigs.check_config_and_reload_domain_driver_if_required
def check_config_and_reload_domain_driver_if_required(self, domain_id)
Definition: core.py:336
keystone.identity.core.MappingManager.create_id_mapping
def create_id_mapping(self, local_entity, public_id=None)
Definition: core.py:1562
keystone.identity.core.MappingManager.purge_mappings
def purge_mappings(self, purge_filter)
Definition: core.py:1582
keystone.identity.core.Manager._mark_domain_id_filter_satisfied
def _mark_domain_id_filter_satisfied(self, hints)
Definition: core.py:844
keystone.identity.core.DomainConfigs.get_domain_conf
def get_domain_conf(self, domain_id)
Definition: core.py:315
keystone.identity.core.Manager.assert_user_enabled
def assert_user_enabled(self, user_id, user=None)
Definition: core.py:1019
keystone.identity.core.DomainConfigs._load_config_from_database
def _load_config_from_database(self, domain_id, specific_config)
Definition: core.py:168
keystone.exception.ValidationTimeStampError
Definition: exception.py:150
keystone.identity.core.ShadowUsersManager.__init__
def __init__(self)
Definition: core.py:1596
keystone.identity.core.Manager.__init__
def __init__(self)
Definition: core.py:484
keystone.exception.DomainNotFound
Definition: exception.py:453
keystone.identity.core.Manager.get_user
def get_user(self, user_id)
Definition: core.py:1008
keystone.identity.core.domains_configured
def domains_configured(f)
Definition: core.py:392
keystone.identity.core.DomainConfigs._load_driver
def _load_driver(self, domain_config)
Definition: core.py:89
keystone.identity.core.Manager.event_callbacks
event_callbacks
Definition: core.py:491
keystone.identity.core.Manager._update_user_with_federated_objects
def _update_user_with_federated_objects(self, user, driver, entity_id)
Definition: core.py:1128
keystone.exception.GroupNotFound
Definition: exception.py:473
keystone.identity.core.Manager.domain_configs
domain_configs
Definition: core.py:486
keystone.identity.core.Manager.update_user
def update_user(self, user_id, user_ref, initiator=None)
Definition: core.py:1156
keystone.identity.core.Manager.update_group
def update_group(self, group_id, group, initiator=None)
Definition: core.py:1276
keystone.identity.core.DomainConfigs._setup_domain_drivers_from_database
def _setup_domain_drivers_from_database(self, standard_driver, resource_api)
Definition: core.py:278
keystone.identity.core.exception_translated
def exception_translated(exception_type)
Definition: core.py:418
keystone.identity.core.Manager._unset_default_project
def _unset_default_project(self, service, resource_type, operation, payload)
Definition: core.py:531
keystone.identity.core.MappingManager.delete_id_mapping
def delete_id_mapping(self, public_id)
Definition: core.py:1572
keystone.identity.core.Manager._domain_deleted
def _domain_deleted(self, service, resource_type, operation, payload)
Definition: core.py:498
keystone.identity.core.Manager.get_group
def get_group(self, group_id)
Definition: core.py:1259
keystone.identity.core.Manager._set_domain_id_and_mapping
def _set_domain_id_and_mapping(self, ref, domain_id, driver, entity_type)
Definition: core.py:556
keystone.identity.core.ShadowUsersManager
Definition: core.py:1590
keystone.conf.configure
def configure(conf=None)
Definition: __init__.py:128
keystone.identity.core.DomainConfigs
Definition: core.py:67
keystone.exception.Forbidden
Definition: exception.py:352
keystone.identity.core.Manager._create_user_with_federated_objects
def _create_user_with_federated_objects(self, user, driver)
Definition: core.py:960
keystone.conf
Definition: __init__.py:1
keystone.identity.core.Manager
Definition: core.py:439
keystone.exception.PasswordExpired
Definition: exception.py:310
keystone.identity.core.Manager._ensure_domain_id_in_hints
def _ensure_domain_id_in_hints(self, hints, domain_id)
Definition: core.py:851
keystone.i18n._
_
Definition: i18n.py:29
keystone.identity.core.Manager._shadow_federated_user
def _shadow_federated_user(self, idp_id, protocol_id, unique_id, display_name, email=None)
Definition: core.py:1487
keystone.common
Definition: __init__.py:1
keystone.exception.ProjectNotFound
Definition: exception.py:457
keystone.exception.IdentityProviderNotFound
Definition: exception.py:501
keystone.i18n
Definition: i18n.py:1
keystone.identity.core.Manager.get_user_by_name
def get_user_by_name(self, user_name, domain_id)
Definition: core.py:1033
keystone.identity.core.Manager.list_groups_for_user
def list_groups_for_user(self, user_id, hints=None)
Definition: core.py:1383
keystone.identity.core.Manager._select_identity_driver
def _select_identity_driver(self, domain_id)
Definition: core.py:719
keystone.identity.core.Manager.add_user_to_group
def add_user_to_group(self, user_id, group_id, initiator=None)
Definition: core.py:1319
keystone.identity.core.Manager.create_user
def create_user(self, user_ref, initiator=None)
Definition: core.py:980
keystone.identity.core.MappingManager.get_public_id
def get_public_id(self, local_entity)
Definition: core.py:1553
keystone.identity.core.Manager._GROUP
string _GROUP
Definition: core.py:482
keystone.identity.core.MappingManager.__init__
def __init__(self)
Definition: core.py:1544
keystone.identity.core.Manager._needs_post_processing
def _needs_post_processing(self, driver)
Definition: core.py:608
keystone.common.validation
Definition: __init__.py:1
keystone.exception.PublicIDNotFound
Definition: exception.py:514
keystone.identity.core.Manager.authenticate
def authenticate(self, user_id, password)
Definition: core.py:900
keystone.identity.core.Manager._handle_shadow_and_local_users
def _handle_shadow_and_local_users(self, driver, hints)
Definition: core.py:1078