Merge "Emit CADF notifications on authentication for invalid users"

This commit is contained in:
Zuul 2018-11-09 23:18:14 +00:00 committed by Gerrit Code Review
commit 6c18a2c3a9
2 changed files with 73 additions and 0 deletions

View File

@ -15,17 +15,24 @@
import sys import sys
from oslo_log import log from oslo_log import log
from pycadf import cadftaxonomy as taxonomy
from pycadf import reason
from pycadf import resource
import six import six
from keystone.common import driver_hints from keystone.common import driver_hints
from keystone.common import provider_api from keystone.common import provider_api
import keystone.conf import keystone.conf
from keystone import exception from keystone import exception
from keystone import notifications
CONF = keystone.conf.CONF CONF = keystone.conf.CONF
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
PROVIDERS = provider_api.ProviderAPIs PROVIDERS = provider_api.ProviderAPIs
_NOTIFY_OP = 'authenticate'
_NOTIFY_EVENT = '{service}.{event}'.format(service=notifications.SERVICE,
event=_NOTIFY_OP)
def construct_method_map_from_config(): def construct_method_map_from_config():
@ -153,6 +160,7 @@ class BaseUserInfo(provider_api.ProviderAPIMixin, object):
user_info = auth_payload['user'] user_info = auth_payload['user']
user_id = user_info.get('id') user_id = user_info.get('id')
user_name = user_info.get('name') user_name = user_info.get('name')
domain_ref = {}
if not user_id and not user_name: if not user_id and not user_name:
raise exception.ValidationError(attribute='id or name', raise exception.ValidationError(attribute='id or name',
target='user') target='user')
@ -171,6 +179,29 @@ class BaseUserInfo(provider_api.ProviderAPIMixin, object):
self._assert_domain_is_enabled(domain_ref) self._assert_domain_is_enabled(domain_ref)
except exception.UserNotFound as e: except exception.UserNotFound as e:
LOG.warning(six.text_type(e)) LOG.warning(six.text_type(e))
# We need to special case USER NOT FOUND here for CADF
# notifications as the normal path for notification(s) come from
# `identity_api.authenticate` and we are a bit before dropping into
# that method.
audit_reason = reason.Reason(str(e), str(e.code))
audit_initiator = notifications.build_audit_initiator()
# build an appropriate audit initiator with relevant information
# for the failed request. This will catch invalid user_name and
# invalid user_id.
if user_name:
audit_initiator.user_name = user_name
else:
audit_initiator.user_id = user_id
audit_initiator.domain_id = domain_ref.get('id')
audit_initiator.domain_name = domain_ref.get('name')
notifications._send_audit_notification(
action=_NOTIFY_OP,
initiator=audit_initiator,
outcome=taxonomy.OUTCOME_FAILURE,
target=resource.Resource(typeURI=taxonomy.ACCOUNT_USER),
event_type=_NOTIFY_EVENT,
reason=audit_reason)
raise exception.Unauthorized(e) raise exception.Unauthorized(e)
self._assert_user_is_enabled(user_ref) self._assert_user_is_enabled(user_ref)
self.user_ref = user_ref self.user_ref = user_ref

View File

@ -25,6 +25,7 @@ from pycadf import cadftaxonomy
from pycadf import cadftype from pycadf import cadftype
from pycadf import eventfactory from pycadf import eventfactory
from pycadf import resource as cadfresource from pycadf import resource as cadfresource
from six.moves import http_client
from keystone.common import provider_api from keystone.common import provider_api
import keystone.conf import keystone.conf
@ -1068,6 +1069,10 @@ class CadfNotificationsWrapperTestCase(test_v3.RestfulTestCase):
self.useFixture(fixtures.MockPatchObject( self.useFixture(fixtures.MockPatchObject(
notifications, '_send_audit_notification', fake_notify)) notifications, '_send_audit_notification', fake_notify))
def _get_last_note(self):
self.assertTrue(self._notifications)
return self._notifications[-1]
def _assert_last_note(self, action, user_id, event_type=None): def _assert_last_note(self, action, user_id, event_type=None):
self.assertTrue(self._notifications) self.assertTrue(self._notifications)
note = self._notifications[-1] note = self._notifications[-1]
@ -1158,6 +1163,43 @@ class CadfNotificationsWrapperTestCase(test_v3.RestfulTestCase):
self.post('/auth/tokens', body=data) self.post('/auth/tokens', body=data)
self._assert_last_note(self.ACTION, user_id) self._assert_last_note(self.ACTION, user_id)
def test_v3_authenticate_with_invalid_user_id_sends_notification(self):
user_id = uuid.uuid4().hex
password = self.user['password']
data = self.build_authentication_request(user_id=user_id,
password=password)
self.post('/auth/tokens', body=data,
expected_status=http_client.UNAUTHORIZED)
note = self._get_last_note()
initiator = note['initiator']
# Confirm user-name specific event was emitted.
self.assertEqual(self.ACTION, note['action'])
self.assertEqual(user_id, initiator.user_id)
self.assertTrue(note['send_notification_called'])
self.assertEqual(cadftaxonomy.OUTCOME_FAILURE, note['event'].outcome)
self.assertEqual(self.LOCAL_HOST, initiator.host.address)
def test_v3_authenticate_with_invalid_user_name_sends_notification(self):
user_name = uuid.uuid4().hex
password = self.user['password']
domain_id = self.domain_id
data = self.build_authentication_request(username=user_name,
user_domain_id=domain_id,
password=password)
self.post('/auth/tokens', body=data,
expected_status=http_client.UNAUTHORIZED)
note = self._get_last_note()
initiator = note['initiator']
# Confirm user-name specific event was emitted.
self.assertEqual(self.ACTION, note['action'])
self.assertEqual(user_name, initiator.user_name)
self.assertEqual(domain_id, initiator.domain_id)
self.assertTrue(note['send_notification_called'])
self.assertEqual(cadftaxonomy.OUTCOME_FAILURE, note['event'].outcome)
self.assertEqual(self.LOCAL_HOST, initiator.host.address)
def test_v3_authenticate_user_name_and_domain_name(self): def test_v3_authenticate_user_name_and_domain_name(self):
user_id = self.user_id user_id = self.user_id
user_name = self.user['name'] user_name = self.user['name']