Merge "Emit CADF notifications on authentication for invalid users"
This commit is contained in:
commit
6c18a2c3a9
|
@ -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
|
||||||
|
|
|
@ -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']
|
||||||
|
|
Loading…
Reference in New Issue