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)  

notifications.py
Go to the documentation of this file.
1 # Copyright 2013 IBM Corp.
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 """Notifications module for OpenStack Identity Service resources."""
16 
17 import collections
18 import functools
19 import inspect
20 import socket
21 
22 import flask
23 from oslo_log import log
24 import oslo_messaging
25 from oslo_utils import reflection
26 import pycadf
27 from pycadf import cadftaxonomy as taxonomy
28 from pycadf import cadftype
29 from pycadf import credential
30 from pycadf import eventfactory
31 from pycadf import host
32 from pycadf import reason
33 from pycadf import resource
34 
35 from keystone.common import context
36 from keystone.common import provider_api
37 from keystone.common import utils
38 import keystone.conf
39 from keystone import exception
40 from keystone.i18n import _
41 
42 
43 _CATALOG_HELPER_OBJ = None
44 
45 LOG = log.getLogger(__name__)
46 # NOTE(gyee): actions that can be notified. One must update this list whenever
47 # a new action is supported.
48 _ACTIONS = collections.namedtuple(
49  'NotificationActions',
50  'created, deleted, disabled, updated, internal')
51 ACTIONS = _ACTIONS(created='created', deleted='deleted', disabled='disabled',
52  updated='updated', internal='internal')
53 """The actions on resources."""
54 
55 CADF_TYPE_MAP = {
56  'group': taxonomy.SECURITY_GROUP,
57  'project': taxonomy.SECURITY_PROJECT,
58  'role': taxonomy.SECURITY_ROLE,
59  'user': taxonomy.SECURITY_ACCOUNT_USER,
60  'domain': taxonomy.SECURITY_DOMAIN,
61  'region': taxonomy.SECURITY_REGION,
62  'endpoint': taxonomy.SECURITY_ENDPOINT,
63  'service': taxonomy.SECURITY_SERVICE,
64  'policy': taxonomy.SECURITY_POLICY,
65  'OS-TRUST:trust': taxonomy.SECURITY_TRUST,
66  'OS-OAUTH1:access_token': taxonomy.SECURITY_CREDENTIAL,
67  'OS-OAUTH1:request_token': taxonomy.SECURITY_CREDENTIAL,
68  'OS-OAUTH1:consumer': taxonomy.SECURITY_ACCOUNT,
69  'application_credential': taxonomy.SECURITY_CREDENTIAL,
70 }
71 
72 SAML_AUDIT_TYPE = 'http://docs.oasis-open.org/security/saml/v2.0'
73 # resource types that can be notified
74 _SUBSCRIBERS = {}
75 _notifier = None
76 SERVICE = 'identity'
77 PROVIDERS = provider_api.ProviderAPIs
78 
79 ROOT_DOMAIN = '<<keystone.domain.root>>'
80 
81 CONF = keystone.conf.CONF
82 
83 # NOTE(morganfainberg): Special case notifications that are only used
84 # internally for handling token persistence token deletions
85 INVALIDATE_TOKEN_CACHE = 'invalidate_token_cache' # nosec
86 PERSIST_REVOCATION_EVENT_FOR_USER = 'persist_revocation_event_for_user'
87 REMOVE_APP_CREDS_FOR_USER = 'remove_application_credentials_for_user'
88 DOMAIN_DELETED = 'domain_deleted'
89 
90 
92  """A pyCADF initiator describing the current authenticated context."""
93  pycadf_host = host.Host(address=flask.request.remote_addr,
94  agent=str(flask.request.user_agent))
95  initiator = resource.Resource(typeURI=taxonomy.ACCOUNT_USER,
96  host=pycadf_host)
97  oslo_context = flask.request.environ.get(context.REQUEST_CONTEXT_ENV)
98  if oslo_context.user_id:
99  initiator.id = utils.resource_uuid(oslo_context.user_id)
100  initiator.user_id = oslo_context.user_id
101 
102  if oslo_context.project_id:
103  initiator.project_id = oslo_context.project_id
104 
105  if oslo_context.domain_id:
106  initiator.domain_id = oslo_context.domain_id
107 
108  initiator.request_id = oslo_context.request_id
109 
110  if oslo_context.global_request_id:
111  initiator.global_request_id = oslo_context.global_request_id
112 
113  return initiator
114 
115 
116 class Audit(object):
117  """Namespace for audit notification functions.
118 
119  This is a namespace object to contain all of the direct notification
120  functions utilized for ``Manager`` methods.
121  """
122 
123  @classmethod
124  def _emit(cls, operation, resource_type, resource_id, initiator, public,
125  actor_dict=None, reason=None):
126  """Directly send an event notification.
127 
128  :param operation: one of the values from ACTIONS
129  :param resource_type: type of resource being affected
130  :param resource_id: ID of the resource affected
131  :param initiator: CADF representation of the user that created the
132  request
133  :param public: If True (default), the event will be sent to the
134  notifier API. If False, the event will only be sent via
135  notify_event_callbacks to in process listeners
136  :param actor_dict: dictionary of actor information in the event of
137  assignment notification
138  :param reason: pycadf object containing the response code and
139  message description
140  """
141  # NOTE(stevemar): the _send_notification function is
142  # overloaded, it's used to register callbacks and to actually
143  # send the notification externally. Thus, we should check
144  # the desired notification format in the function instead
145  # of before it.
147  operation,
148  resource_type,
149  resource_id,
150  initiator=initiator,
151  actor_dict=actor_dict,
152  public=public)
153 
154  if CONF.notification_format == 'cadf' and public:
155  outcome = taxonomy.OUTCOME_SUCCESS
156  _create_cadf_payload(operation, resource_type, resource_id,
157  outcome, initiator, reason)
158 
159  @classmethod
160  def created(cls, resource_type, resource_id, initiator=None,
161  public=True, reason=None):
162  cls._emit(ACTIONS.created, resource_type, resource_id, initiator,
163  public, reason=reason)
164 
165  @classmethod
166  def updated(cls, resource_type, resource_id, initiator=None,
167  public=True, reason=None):
168  cls._emit(ACTIONS.updated, resource_type, resource_id, initiator,
169  public, reason=reason)
170 
171  @classmethod
172  def disabled(cls, resource_type, resource_id, initiator=None,
173  public=True, reason=None):
174  cls._emit(ACTIONS.disabled, resource_type, resource_id, initiator,
175  public, reason=reason)
176 
177  @classmethod
178  def deleted(cls, resource_type, resource_id, initiator=None,
179  public=True, reason=None):
180  cls._emit(ACTIONS.deleted, resource_type, resource_id, initiator,
181  public, reason=reason)
182 
183  @classmethod
184  def added_to(cls, target_type, target_id, actor_type, actor_id,
185  initiator=None, public=True, reason=None):
186  actor_dict = {'id': actor_id,
187  'type': actor_type,
188  'actor_operation': 'added'}
189  cls._emit(ACTIONS.updated, target_type, target_id, initiator, public,
190  actor_dict=actor_dict, reason=reason)
191 
192  @classmethod
193  def removed_from(cls, target_type, target_id, actor_type, actor_id,
194  initiator=None, public=True, reason=None):
195  actor_dict = {'id': actor_id,
196  'type': actor_type,
197  'actor_operation': 'removed'}
198  cls._emit(ACTIONS.updated, target_type, target_id, initiator, public,
199  actor_dict=actor_dict, reason=reason)
200 
201  @classmethod
202  def internal(cls, resource_type, resource_id, reason=None):
203  # NOTE(lbragstad): Internal notifications are never public and have
204  # never used the initiator variable, but the _emit() method expects
205  # them. Let's set them here but not expose them through the method
206  # signature - that way someone can not do something like send an
207  # internal notification publicly.
208  initiator = None
209  public = False
210  cls._emit(ACTIONS.internal, resource_type, resource_id, initiator,
211  public, reason)
212 
213 
215  """A specific notification for invalidating the token cache.
216 
217  :param reason: The specific reason why the token cache is being
218  invalidated.
219  :type reason: string
220 
221  """
222  # Since keystone does a lot of work in the authentication and validation
223  # process to make sure the authorization context for the user is
224  # update-to-date, invalidating the token cache is a somewhat common
225  # operation. It's done across various subsystems when role assignments
226  # change, users are disabled, identity providers deleted or disabled, etc..
227  # This notification is meant to make the process of invalidating the token
228  # cache DRY, instead of have each subsystem implement their own token cache
229  # invalidation strategy or callbacks.
230  LOG.debug(reason)
231  resource_id = None
232  initiator = None
233  public = False
234  Audit._emit(
235  ACTIONS.internal, INVALIDATE_TOKEN_CACHE, resource_id, initiator,
236  public, reason=reason
237  )
238 
239 
240 def _get_callback_info(callback):
241  """Return list containing callback's module and name.
242 
243  If the callback is a bound instance method also return the class name.
244 
245  :param callback: Function to call
246  :type callback: function
247  :returns: List containing parent module, (optional class,) function name
248  :rtype: list
249  """
250  module_name = getattr(callback, '__module__', None)
251  func_name = callback.__name__
252  if inspect.ismethod(callback):
253  class_name = reflection.get_class_name(callback.__self__,
254  fully_qualified=False)
255  return [module_name, class_name, func_name]
256  else:
257  return [module_name, func_name]
258 
259 
260 def register_event_callback(event, resource_type, callbacks):
261  """Register each callback with the event.
262 
263  :param event: Action being registered
264  :type event: keystone.notifications.ACTIONS
265  :param resource_type: Type of resource being operated on
266  :type resource_type: str
267  :param callbacks: Callback items to be registered with event
268  :type callbacks: list
269  :raises ValueError: If event is not a valid ACTION
270  :raises TypeError: If callback is not callable
271  """
272  if event not in ACTIONS:
273  raise ValueError(_('%(event)s is not a valid notification event, must '
274  'be one of: %(actions)s') %
275  {'event': event, 'actions': ', '.join(ACTIONS)})
276 
277  if not hasattr(callbacks, '__iter__'):
278  callbacks = [callbacks]
279 
280  for callback in callbacks:
281  if not callable(callback):
282  msg = 'Method not callable: %s' % callback
283  tr_msg = _('Method not callable: %s') % callback
284  LOG.error(msg)
285  raise TypeError(tr_msg)
286  _SUBSCRIBERS.setdefault(event, {}).setdefault(resource_type, set())
287  _SUBSCRIBERS[event][resource_type].add(callback)
288 
289  if LOG.logger.getEffectiveLevel() <= log.DEBUG:
290  # Do this only if its going to appear in the logs.
291  msg = 'Callback: `%(callback)s` subscribed to event `%(event)s`.'
292  callback_info = _get_callback_info(callback)
293  callback_str = '.'.join(i for i in callback_info if i is not None)
294  event_str = '.'.join(['identity', resource_type, event])
295  LOG.debug(msg, {'callback': callback_str, 'event': event_str})
296 
297 
298 def listener(cls):
299  """A class decorator to declare a class to be a notification listener.
300 
301  A notification listener must specify the event(s) it is interested in by
302  defining a ``event_callbacks`` attribute or property. ``event_callbacks``
303  is a dictionary where the key is the type of event and the value is a
304  dictionary containing a mapping of resource types to callback(s).
305 
306  :data:`.ACTIONS` contains constants for the currently
307  supported events. There is currently no single place to find constants for
308  the resource types.
309 
310  Example::
311 
312  @listener
313  class Something(object):
314 
315  def __init__(self):
316  self.event_callbacks = {
317  notifications.ACTIONS.created: {
318  'user': self._user_created_callback,
319  },
320  notifications.ACTIONS.deleted: {
321  'project': [
322  self._project_deleted_callback,
323  self._do_cleanup,
324  ]
325  },
326  }
327 
328  """
329  def init_wrapper(init):
330  @functools.wraps(init)
331  def __new_init__(self, *args, **kwargs):
332  init(self, *args, **kwargs)
333  _register_event_callbacks(self)
334  return __new_init__
335 
336  def _register_event_callbacks(self):
337  for event, resource_types in self.event_callbacks.items():
338  for resource_type, callbacks in resource_types.items():
339  register_event_callback(event, resource_type, callbacks)
340 
341  cls.__init__ = init_wrapper(cls.__init__)
342  return cls
343 
344 
345 def notify_event_callbacks(service, resource_type, operation, payload):
346  """Send a notification to registered extensions."""
347  if operation in _SUBSCRIBERS:
348  if resource_type in _SUBSCRIBERS[operation]:
349  for cb in _SUBSCRIBERS[operation][resource_type]:
350  subst_dict = {'cb_name': cb.__name__,
351  'service': service,
352  'resource_type': resource_type,
353  'operation': operation,
354  'payload': payload}
355  LOG.debug('Invoking callback %(cb_name)s for event '
356  '%(service)s %(resource_type)s %(operation)s for '
357  '%(payload)s', subst_dict)
358  cb(service, resource_type, operation, payload)
359 
360 
362  """Return a notifier object.
363 
364  If _notifier is None it means that a notifier object has not been set.
365  If _notifier is False it means that a notifier has previously failed to
366  construct.
367  Otherwise it is a constructed Notifier object.
368  """
369  global _notifier
370 
371  if _notifier is None:
372  host = CONF.default_publisher_id or socket.gethostname()
373  try:
374  transport = oslo_messaging.get_notification_transport(CONF)
375  _notifier = oslo_messaging.Notifier(transport,
376  "identity.%s" % host)
377  except Exception:
378  LOG.exception("Failed to construct notifier")
379  _notifier = False
380 
381  return _notifier
382 
383 
385  """Empty subscribers dictionary.
386 
387  This effectively stops notifications since there will be no subscribers
388  to publish to.
389  """
390  _SUBSCRIBERS.clear()
391 
392 
394  """Reset the notifications internal state.
395 
396  This is used only for testing purposes.
397 
398  """
399  global _notifier
400  _notifier = None
401 
402 
403 def _create_cadf_payload(operation, resource_type, resource_id,
404  outcome, initiator, reason=None):
405  """Prepare data for CADF audit notifier.
406 
407  Transform the arguments into content to be consumed by the function that
408  emits CADF events (_send_audit_notification). Specifically the
409  ``resource_type`` (role, user, etc) must be transformed into a CADF
410  keyword, such as: ``data/security/role``. The ``resource_id`` is added as a
411  top level value for the ``resource_info`` key. Lastly, the ``operation`` is
412  used to create the CADF ``action``, and the ``event_type`` name.
413 
414  As per the CADF specification, the ``action`` must start with create,
415  update, delete, etc... i.e.: created.user or deleted.role
416 
417  However the ``event_type`` is an OpenStack-ism that is typically of the
418  form project.resource.operation. i.e.: identity.project.updated
419 
420  :param operation: operation being performed (created, updated, or deleted)
421  :param resource_type: type of resource being operated on (role, user, etc)
422  :param resource_id: ID of resource being operated on
423  :param outcome: outcomes of the operation (SUCCESS, FAILURE, etc)
424  :param initiator: CADF representation of the user that created the request
425  :param reason: pycadf object containing the response code and
426  message description
427  """
428  if resource_type not in CADF_TYPE_MAP:
429  target_uri = taxonomy.UNKNOWN
430  else:
431  target_uri = CADF_TYPE_MAP.get(resource_type)
432 
433  # TODO(gagehugo): The root domain ID is typically hidden, there isn't a
434  # reason to emit a notification for it. Once we expose the root domain
435  # (and handle the CADF UUID), remove this.
436  if resource_id == ROOT_DOMAIN:
437  return
438 
439  target = resource.Resource(typeURI=target_uri,
440  id=resource_id)
441 
442  audit_kwargs = {'resource_info': resource_id}
443  cadf_action = '%s.%s' % (operation, resource_type)
444  event_type = '%s.%s.%s' % (SERVICE, resource_type, operation)
445 
446  _send_audit_notification(cadf_action, initiator, outcome,
447  target, event_type, reason=reason, **audit_kwargs)
448 
449 
450 def _send_notification(operation, resource_type, resource_id, initiator=None,
451  actor_dict=None, public=True):
452  """Send notification to inform observers about the affected resource.
453 
454  This method doesn't raise an exception when sending the notification fails.
455 
456  :param operation: operation being performed (created, updated, or deleted)
457  :param resource_type: type of resource being operated on
458  :param resource_id: ID of resource being operated on
459  :param initiator: representation of the user that created the request
460  :param actor_dict: a dictionary containing the actor's ID and type
461  :param public: if True (default), the event will be sent
462  to the notifier API.
463  if False, the event will only be sent via
464  notify_event_callbacks to in process listeners.
465  """
466  payload = {'resource_info': resource_id}
467 
468  if actor_dict:
469  payload['actor_id'] = actor_dict['id']
470  payload['actor_type'] = actor_dict['type']
471  payload['actor_operation'] = actor_dict['actor_operation']
472 
473  if initiator:
474  payload['request_id'] = initiator.request_id
475  global_request_id = getattr(initiator, 'global_request_id', None)
476  if global_request_id:
477  payload['global_request_id'] = global_request_id
478 
479  notify_event_callbacks(SERVICE, resource_type, operation, payload)
480 
481  # Only send this notification if the 'basic' format is used, otherwise
482  # let the CADF functions handle sending the notification. But we check
483  # here so as to not disrupt the notify_event_callbacks function.
484  if public and CONF.notification_format == 'basic':
485  notifier = _get_notifier()
486  if notifier:
487  context = {}
488  event_type = '%(service)s.%(resource_type)s.%(operation)s' % {
489  'service': SERVICE,
490  'resource_type': resource_type,
491  'operation': operation}
492  if _check_notification_opt_out(event_type, outcome=None):
493  return
494  try:
495  notifier.info(context, event_type, payload)
496  except Exception:
497  LOG.exception(
498  'Failed to send %(res_id)s %(event_type)s notification',
499  {'res_id': resource_id, 'event_type': event_type})
500 
501 
502 def _get_request_audit_info(context, user_id=None):
503  """Collect audit information about the request used for CADF.
504 
505  :param context: Request context
506  :param user_id: Optional user ID, alternatively collected from context
507  :returns: Auditing data about the request
508  :rtype: :class:`pycadf.Resource`
509  """
510  remote_addr = None
511  http_user_agent = None
512  project_id = None
513  domain_id = None
514 
515  if context and 'environment' in context and context['environment']:
516  environment = context['environment']
517  remote_addr = environment.get('REMOTE_ADDR')
518  http_user_agent = environment.get('HTTP_USER_AGENT')
519  if not user_id:
520  user_id = environment.get('KEYSTONE_AUTH_CONTEXT',
521  {}).get('user_id')
522  project_id = environment.get('KEYSTONE_AUTH_CONTEXT',
523  {}).get('project_id')
524  domain_id = environment.get('KEYSTONE_AUTH_CONTEXT',
525  {}).get('domain_id')
526 
527  host = pycadf.host.Host(address=remote_addr, agent=http_user_agent)
528  initiator = resource.Resource(typeURI=taxonomy.ACCOUNT_USER, host=host)
529 
530  if user_id:
531  initiator.user_id = user_id
532  initiator.id = utils.resource_uuid(user_id)
533  initiator = _add_username_to_initiator(initiator)
534 
535  if project_id:
536  initiator.project_id = project_id
537  if domain_id:
538  initiator.domain_id = domain_id
539 
540  return initiator
541 
542 
544  """Send CADF event notifications for various methods.
545 
546  This function is only used for Authentication events. Its ``action`` and
547  ``event_type`` are dictated below.
548 
549  - action: ``authenticate``
550  - event_type: ``identity.authenticate``
551 
552  Sends CADF notifications for events such as whether an authentication was
553  successful or not.
554 
555  :param operation: The authentication related action being performed
556 
557  """
558 
559  def __init__(self, operation):
560  self.action = operation
561  self.event_type = '%s.%s' % (SERVICE, operation)
562 
563  def __call__(self, f):
564  @functools.wraps(f)
565  def wrapper(wrapped_self, user_id, *args, **kwargs):
566  """Will always send a notification."""
567  target = resource.Resource(typeURI=taxonomy.ACCOUNT_USER)
568  initiator = build_audit_initiator()
569  initiator.user_id = user_id
570  initiator = _add_username_to_initiator(initiator)
571  initiator.id = utils.resource_uuid(user_id)
572  try:
573  result = f(wrapped_self, user_id, *args, **kwargs)
574  except (exception.AccountLocked,
576  # Send a CADF event with a reason for PCI-DSS related
577  # authentication failures
578  audit_reason = reason.Reason(str(ex), str(ex.code))
579  _send_audit_notification(self.action, initiator,
580  taxonomy.OUTCOME_FAILURE,
581  target, self.event_type,
582  reason=audit_reason)
583  raise
584  except Exception:
585  # For authentication failure send a CADF event as well
586  _send_audit_notification(self.action, initiator,
587  taxonomy.OUTCOME_FAILURE,
588  target, self.event_type)
589  raise
590  else:
591  _send_audit_notification(self.action, initiator,
592  taxonomy.OUTCOME_SUCCESS,
593  target, self.event_type)
594  return result
595 
596  return wrapper
597 
598 
600  """Send CADF notifications for ``role_assignment`` methods.
601 
602  This function is only used for role assignment events. Its ``action`` and
603  ``event_type`` are dictated below.
604 
605  - action: ``created.role_assignment`` or ``deleted.role_assignment``
606  - event_type: ``identity.role_assignment.created`` or
607  ``identity.role_assignment.deleted``
608 
609  Sends a CADF notification if the wrapped method does not raise an
610  :class:`Exception` (such as :class:`keystone.exception.NotFound`).
611 
612  :param operation: one of the values from ACTIONS (created or deleted)
613  """
614 
615  ROLE_ASSIGNMENT = 'role_assignment'
616 
617  def __init__(self, operation):
618  self.action = '%s.%s' % (operation, self.ROLE_ASSIGNMENT)
619  self.event_type = '%s.%s.%s' % (SERVICE, self.ROLE_ASSIGNMENT,
620  operation)
621 
622  def __call__(self, f):
623  @functools.wraps(f)
624  def wrapper(wrapped_self, role_id, *args, **kwargs):
625  """Send a notification if the wrapped callable is successful.
626 
627  NOTE(stevemar): The reason we go through checking kwargs
628  and args for possible target and actor values is because the
629  create_grant() (and delete_grant()) method are called
630  differently in various tests.
631  Using named arguments, i.e.::
632 
633  create_grant(user_id=user['id'], domain_id=domain['id'],
634  role_id=role['id'])
635 
636  Or, using positional arguments, i.e.::
637 
638  create_grant(role_id['id'], user['id'], None,
639  domain_id=domain['id'], None)
640 
641  Or, both, i.e.::
642 
643  create_grant(role_id['id'], user_id=user['id'],
644  domain_id=domain['id'])
645 
646  Checking the values for kwargs is easy enough, since it comes
647  in as a dictionary
648 
649  The actual method signature is
650 
651  ::
652 
653  create_grant(role_id, user_id=None, group_id=None,
654  domain_id=None, project_id=None,
655  inherited_to_projects=False)
656 
657  So, if the values of actor or target are still None after
658  checking kwargs, we can check the positional arguments,
659  based on the method signature.
660  """
661  call_args = inspect.getcallargs(
662  f, wrapped_self, role_id, *args, **kwargs)
663  inherited = call_args['inherited_to_projects']
664  initiator = call_args.get('initiator', None)
665  target = resource.Resource(typeURI=taxonomy.ACCOUNT_USER)
666 
667  audit_kwargs = {}
668  if call_args['project_id']:
669  audit_kwargs['project'] = call_args['project_id']
670  elif call_args['domain_id']:
671  audit_kwargs['domain'] = call_args['domain_id']
672 
673  if call_args['user_id']:
674  audit_kwargs['user'] = call_args['user_id']
675  elif call_args['group_id']:
676  audit_kwargs['group'] = call_args['group_id']
677 
678  audit_kwargs['inherited_to_projects'] = inherited
679  audit_kwargs['role'] = role_id
680 
681  try:
682  result = f(wrapped_self, role_id, *args, **kwargs)
683  except Exception:
684  _send_audit_notification(self.action, initiator,
685  taxonomy.OUTCOME_FAILURE,
686  target, self.event_type,
687  **audit_kwargs)
688  raise
689  else:
690  _send_audit_notification(self.action, initiator,
691  taxonomy.OUTCOME_SUCCESS,
692  target, self.event_type,
693  **audit_kwargs)
694  return result
695 
696  return wrapper
697 
698 
699 def send_saml_audit_notification(action, user_id, group_ids,
700  identity_provider, protocol, token_id,
701  outcome):
702  """Send notification to inform observers about SAML events.
703 
704  :param action: Action being audited
705  :type action: str
706  :param user_id: User ID from Keystone token
707  :type user_id: str
708  :param group_ids: List of Group IDs from Keystone token
709  :type group_ids: list
710  :param identity_provider: ID of the IdP from the Keystone token
711  :type identity_provider: str or None
712  :param protocol: Protocol ID for IdP from the Keystone token
713  :type protocol: str
714  :param token_id: audit_id from Keystone token
715  :type token_id: str or None
716  :param outcome: One of :class:`pycadf.cadftaxonomy`
717  :type outcome: str
718  """
719  initiator = build_audit_initiator()
720  target = resource.Resource(typeURI=taxonomy.ACCOUNT_USER)
721  audit_type = SAML_AUDIT_TYPE
722  user_id = user_id or taxonomy.UNKNOWN
723  token_id = token_id or taxonomy.UNKNOWN
724  group_ids = group_ids or []
725  cred = credential.FederatedCredential(token=token_id, type=audit_type,
726  identity_provider=identity_provider,
727  user=user_id, groups=group_ids)
728  initiator.credential = cred
729  event_type = '%s.%s' % (SERVICE, action)
730  _send_audit_notification(action, initiator, outcome, target, event_type)
731 
732 
733 class _CatalogHelperObj(provider_api.ProviderAPIMixin, object):
734  """A helper object to allow lookups of identity service id."""
735 
736 
737 def _send_audit_notification(action, initiator, outcome, target,
738  event_type, reason=None, **kwargs):
739  """Send CADF notification to inform observers about the affected resource.
740 
741  This method logs an exception when sending the notification fails.
742 
743  :param action: CADF action being audited (e.g., 'authenticate')
744  :param initiator: CADF resource representing the initiator
745  :param outcome: The CADF outcome (taxonomy.OUTCOME_PENDING,
746  taxonomy.OUTCOME_SUCCESS, taxonomy.OUTCOME_FAILURE)
747  :param target: CADF resource representing the target
748  :param event_type: An OpenStack-ism, typically this is the meter name that
749  Ceilometer uses to poll events.
750  :param kwargs: Any additional arguments passed in will be added as
751  key-value pairs to the CADF event.
752  :param reason: Reason for the notification which contains the response
753  code and message description
754  """
755  if _check_notification_opt_out(event_type, outcome):
756  return
757 
758  global _CATALOG_HELPER_OBJ
759  if _CATALOG_HELPER_OBJ is None:
760  _CATALOG_HELPER_OBJ = _CatalogHelperObj()
761  service_list = _CATALOG_HELPER_OBJ.catalog_api.list_services()
762  service_id = None
763 
764  for i in service_list:
765  if i['type'] == SERVICE:
766  service_id = i['id']
767  break
768 
769  initiator = _add_username_to_initiator(initiator)
770 
771  event = eventfactory.EventFactory().new_event(
772  eventType=cadftype.EVENTTYPE_ACTIVITY,
773  outcome=outcome,
774  action=action,
775  initiator=initiator,
776  target=target,
777  reason=reason,
778  observer=resource.Resource(typeURI=taxonomy.SERVICE_SECURITY))
779 
780  if service_id is not None:
781  event.observer.id = service_id
782 
783  for key, value in kwargs.items():
784  setattr(event, key, value)
785 
786  context = {}
787  payload = event.as_dict()
788  notifier = _get_notifier()
789 
790  if notifier:
791  try:
792  notifier.info(context, event_type, payload)
793  except Exception:
794  # diaper defense: any exception that occurs while emitting the
795  # notification should not interfere with the API request
796  LOG.exception(
797  'Failed to send %(action)s %(event_type)s notification',
798  {'action': action, 'event_type': event_type})
799 
800 
801 def _check_notification_opt_out(event_type, outcome):
802  """Check if a particular event_type has been opted-out of.
803 
804  This method checks to see if an event should be sent to the messaging
805  service. Any event specified in the opt-out list will not be transmitted.
806 
807  :param event_type: This is the meter name that Ceilometer uses to poll
808  events. For example: identity.user.created, or
809  identity.authenticate.success, or identity.role_assignment.created
810  :param outcome: The CADF outcome (taxonomy.OUTCOME_PENDING,
811  taxonomy.OUTCOME_SUCCESS, taxonomy.OUTCOME_FAILURE)
812 
813  """
814  # NOTE(stevemar): Special handling for authenticate, we look at the outcome
815  # as well when evaluating. For authN events, event_type is just
816  # identity.authenticate, which isn't fine enough to provide any opt-out
817  # value, so we attach the outcome to re-create the meter name used in
818  # ceilometer.
819  if 'authenticate' in event_type:
820  event_type = event_type + "." + outcome
821 
822  if event_type in CONF.notification_opt_out:
823  return True
824 
825  return False
826 
827 
829  """Add the username to the initiator if missing."""
830  if hasattr(initiator, 'username'):
831  return initiator
832  try:
833  user_ref = PROVIDERS.identity_api.get_user(initiator.user_id)
834  initiator.username = user_ref['name']
835  except (exception.UserNotFound, AttributeError):
836  # Either user not found or no user_id, move along
837  pass
838 
839  return initiator
840 
841 
842 emit_event = CadfNotificationWrapper
843 
844 
845 role_assignment = CadfRoleAssignmentNotificationWrapper
keystone.notifications.CadfRoleAssignmentNotificationWrapper.event_type
event_type
Definition: notifications.py:619
keystone.exception.UserNotFound
Definition: exception.py:469
keystone.exception.AccountLocked
Definition: exception.py:327
keystone.notifications.CadfRoleAssignmentNotificationWrapper.__init__
def __init__(self, operation)
Definition: notifications.py:617
keystone.notifications.register_event_callback
def register_event_callback(event, resource_type, callbacks)
Definition: notifications.py:260
keystone.notifications.build_audit_initiator
def build_audit_initiator()
Definition: notifications.py:91
keystone.notifications._check_notification_opt_out
def _check_notification_opt_out(event_type, outcome)
Definition: notifications.py:801
keystone.notifications.Audit.created
def created(cls, resource_type, resource_id, initiator=None, public=True, reason=None)
Definition: notifications.py:161
keystone.notifications._get_callback_info
def _get_callback_info(callback)
Definition: notifications.py:240
keystone.notifications.CadfRoleAssignmentNotificationWrapper
Definition: notifications.py:599
keystone.notifications._get_request_audit_info
def _get_request_audit_info(context, user_id=None)
Definition: notifications.py:502
keystone.notifications.CadfNotificationWrapper.event_type
event_type
Definition: notifications.py:561
keystone.notifications._create_cadf_payload
def _create_cadf_payload(operation, resource_type, resource_id, outcome, initiator, reason=None)
Definition: notifications.py:404
keystone.notifications._add_username_to_initiator
def _add_username_to_initiator(initiator)
Definition: notifications.py:828
keystone.notifications._CatalogHelperObj
Definition: notifications.py:733
keystone.notifications.Audit.deleted
def deleted(cls, resource_type, resource_id, initiator=None, public=True, reason=None)
Definition: notifications.py:179
keystone.notifications.invalidate_token_cache_notification
def invalidate_token_cache_notification(reason)
Definition: notifications.py:214
keystone.notifications.CadfNotificationWrapper.__call__
def __call__(self, f)
Definition: notifications.py:563
keystone.notifications._send_audit_notification
def _send_audit_notification(action, initiator, outcome, target, event_type, reason=None, **kwargs)
Definition: notifications.py:738
keystone.notifications.CadfNotificationWrapper.action
action
Definition: notifications.py:560
keystone.notifications.send_saml_audit_notification
def send_saml_audit_notification(action, user_id, group_ids, identity_provider, protocol, token_id, outcome)
Definition: notifications.py:701
keystone.notifications.CadfRoleAssignmentNotificationWrapper.__call__
def __call__(self, f)
Definition: notifications.py:622
keystone.notifications.Audit.disabled
def disabled(cls, resource_type, resource_id, initiator=None, public=True, reason=None)
Definition: notifications.py:173
keystone.notifications.CadfRoleAssignmentNotificationWrapper.action
action
Definition: notifications.py:618
keystone.notifications.Audit.added_to
def added_to(cls, target_type, target_id, actor_type, actor_id, initiator=None, public=True, reason=None)
Definition: notifications.py:185
keystone.notifications.clear_subscribers
def clear_subscribers()
Definition: notifications.py:384
keystone.notifications.notify_event_callbacks
def notify_event_callbacks(service, resource_type, operation, payload)
Definition: notifications.py:345
keystone.notifications.CadfNotificationWrapper
Definition: notifications.py:543
keystone.notifications.Audit
Definition: notifications.py:116
keystone.notifications.Audit.internal
def internal(cls, resource_type, resource_id, reason=None)
Definition: notifications.py:202
keystone.notifications.CadfNotificationWrapper.__init__
def __init__(self, operation)
Definition: notifications.py:559
keystone.notifications._send_notification
def _send_notification(operation, resource_type, resource_id, initiator=None, actor_dict=None, public=True)
Definition: notifications.py:451
keystone.notifications.reset_notifier
def reset_notifier()
Definition: notifications.py:393
keystone.conf
Definition: __init__.py:1
keystone.exception.PasswordExpired
Definition: exception.py:310
keystone.i18n._
_
Definition: i18n.py:29
keystone.common
Definition: __init__.py:1
keystone.i18n
Definition: i18n.py:1
keystone.notifications._ACTIONS
_ACTIONS
Definition: notifications.py:48
keystone.notifications.Audit.updated
def updated(cls, resource_type, resource_id, initiator=None, public=True, reason=None)
Definition: notifications.py:167
keystone.notifications.CadfRoleAssignmentNotificationWrapper.ROLE_ASSIGNMENT
string ROLE_ASSIGNMENT
Definition: notifications.py:615
keystone.notifications.Audit._emit
def _emit(cls, operation, resource_type, resource_id, initiator, public, actor_dict=None, reason=None)
Definition: notifications.py:125
keystone.notifications._get_notifier
def _get_notifier()
Definition: notifications.py:361
keystone.notifications.listener
def listener(cls)
Definition: notifications.py:298
keystone.notifications.Audit.removed_from
def removed_from(cls, target_type, target_id, actor_type, actor_id, initiator=None, public=True, reason=None)
Definition: notifications.py:194