OpenStack Identity (Keystone)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1498 lines
66 KiB

  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. import datetime
  15. import uuid
  16. import fixtures
  17. import freezegun
  18. import mock
  19. from oslo_config import fixture as config_fixture
  20. from oslo_log import log
  21. import oslo_messaging
  22. from pycadf import cadftaxonomy
  23. from pycadf import cadftype
  24. from pycadf import eventfactory
  25. from pycadf import resource as cadfresource
  26. from six.moves import http_client
  27. from keystone.common import provider_api
  28. import keystone.conf
  29. from keystone import exception
  30. from keystone import notifications
  31. from keystone.tests import unit
  32. from keystone.tests.unit import test_v3
  33. CONF = keystone.conf.CONF
  34. PROVIDERS = provider_api.ProviderAPIs
  35. EXP_RESOURCE_TYPE = uuid.uuid4().hex
  36. CREATED_OPERATION = notifications.ACTIONS.created
  37. UPDATED_OPERATION = notifications.ACTIONS.updated
  38. DELETED_OPERATION = notifications.ACTIONS.deleted
  39. DISABLED_OPERATION = notifications.ACTIONS.disabled
  40. class ArbitraryException(Exception):
  41. pass
  42. def register_callback(operation, resource_type=EXP_RESOURCE_TYPE):
  43. """Helper for creating and registering a mock callback."""
  44. callback = mock.Mock(__name__='callback',
  45. im_class=mock.Mock(__name__='class'))
  46. notifications.register_event_callback(operation, resource_type, callback)
  47. return callback
  48. class AuditNotificationsTestCase(unit.BaseTestCase):
  49. def setUp(self):
  50. super(AuditNotificationsTestCase, self).setUp()
  51. self.config_fixture = self.useFixture(config_fixture.Config(CONF))
  52. self.addCleanup(notifications.clear_subscribers)
  53. def _test_notification_operation_with_basic_format(self,
  54. notify_function,
  55. operation):
  56. self.config_fixture.config(notification_format='basic')
  57. exp_resource_id = uuid.uuid4().hex
  58. callback = register_callback(operation)
  59. notify_function(EXP_RESOURCE_TYPE, exp_resource_id)
  60. callback.assert_called_once_with('identity', EXP_RESOURCE_TYPE,
  61. operation,
  62. {'resource_info': exp_resource_id})
  63. def _test_notification_operation_with_cadf_format(self,
  64. notify_function,
  65. operation):
  66. self.config_fixture.config(notification_format='cadf')
  67. exp_resource_id = uuid.uuid4().hex
  68. with mock.patch(
  69. 'keystone.notifications._create_cadf_payload') as cadf_notify:
  70. notify_function(EXP_RESOURCE_TYPE, exp_resource_id)
  71. initiator = None
  72. reason = None
  73. cadf_notify.assert_called_once_with(
  74. operation, EXP_RESOURCE_TYPE, exp_resource_id,
  75. notifications.taxonomy.OUTCOME_SUCCESS, initiator, reason)
  76. notify_function(EXP_RESOURCE_TYPE, exp_resource_id, public=False)
  77. cadf_notify.assert_called_once_with(
  78. operation, EXP_RESOURCE_TYPE, exp_resource_id,
  79. notifications.taxonomy.OUTCOME_SUCCESS, initiator, reason)
  80. def test_resource_created_notification(self):
  81. self._test_notification_operation_with_basic_format(
  82. notifications.Audit.created, CREATED_OPERATION)
  83. self._test_notification_operation_with_cadf_format(
  84. notifications.Audit.created, CREATED_OPERATION)
  85. def test_resource_updated_notification(self):
  86. self._test_notification_operation_with_basic_format(
  87. notifications.Audit.updated, UPDATED_OPERATION)
  88. self._test_notification_operation_with_cadf_format(
  89. notifications.Audit.updated, UPDATED_OPERATION)
  90. def test_resource_deleted_notification(self):
  91. self._test_notification_operation_with_basic_format(
  92. notifications.Audit.deleted, DELETED_OPERATION)
  93. self._test_notification_operation_with_cadf_format(
  94. notifications.Audit.deleted, DELETED_OPERATION)
  95. def test_resource_disabled_notification(self):
  96. self._test_notification_operation_with_basic_format(
  97. notifications.Audit.disabled, DISABLED_OPERATION)
  98. self._test_notification_operation_with_cadf_format(
  99. notifications.Audit.disabled, DISABLED_OPERATION)
  100. class NotificationsTestCase(unit.BaseTestCase):
  101. def setUp(self):
  102. super(NotificationsTestCase, self).setUp()
  103. self.config_fixture = self.useFixture(config_fixture.Config(CONF))
  104. self.config_fixture.config(
  105. group='oslo_messaging_notifications', transport_url='rabbit://'
  106. )
  107. def test_send_notification(self):
  108. """Test _send_notification.
  109. Test the private method _send_notification to ensure event_type,
  110. payload, and context are built and passed properly.
  111. """
  112. resource = uuid.uuid4().hex
  113. resource_type = EXP_RESOURCE_TYPE
  114. operation = CREATED_OPERATION
  115. conf = self.useFixture(config_fixture.Config(CONF))
  116. conf.config(notification_format='basic')
  117. # NOTE(ldbragst): Even though notifications._send_notification doesn't
  118. # contain logic that creates cases, this is supposed to test that
  119. # context is always empty and that we ensure the resource ID of the
  120. # resource in the notification is contained in the payload. It was
  121. # agreed that context should be empty in Keystone's case, which is
  122. # also noted in the /keystone/notifications.py module. This test
  123. # ensures and maintains these conditions.
  124. expected_args = [
  125. {}, # empty context
  126. 'identity.%s.created' % resource_type, # event_type
  127. {'resource_info': resource} # payload
  128. ]
  129. with mock.patch.object(notifications._get_notifier(),
  130. 'info') as mocked:
  131. notifications._send_notification(operation, resource_type,
  132. resource)
  133. mocked.assert_called_once_with(*expected_args)
  134. def test_send_notification_with_opt_out(self):
  135. """Test the private method _send_notification with opt-out.
  136. Test that _send_notification does not notify when a valid
  137. notification_opt_out configuration is provided.
  138. """
  139. resource = uuid.uuid4().hex
  140. resource_type = EXP_RESOURCE_TYPE
  141. operation = CREATED_OPERATION
  142. event_type = 'identity.%s.created' % resource_type
  143. # NOTE(diazjf): Here we add notification_opt_out to the
  144. # configuration so that we should return before _get_notifer is
  145. # called. This is because we are opting out notifications for the
  146. # passed resource_type and operation.
  147. conf = self.useFixture(config_fixture.Config(CONF))
  148. conf.config(notification_opt_out=[event_type])
  149. with mock.patch.object(notifications._get_notifier(),
  150. 'info') as mocked:
  151. notifications._send_notification(operation, resource_type,
  152. resource)
  153. mocked.assert_not_called()
  154. def test_send_audit_notification_with_opt_out(self):
  155. """Test the private method _send_audit_notification with opt-out.
  156. Test that _send_audit_notification does not notify when a valid
  157. notification_opt_out configuration is provided.
  158. """
  159. resource_type = EXP_RESOURCE_TYPE
  160. action = CREATED_OPERATION + '.' + resource_type
  161. initiator = mock
  162. target = mock
  163. outcome = 'success'
  164. event_type = 'identity.%s.created' % resource_type
  165. conf = self.useFixture(config_fixture.Config(CONF))
  166. conf.config(notification_opt_out=[event_type])
  167. with mock.patch.object(notifications._get_notifier(),
  168. 'info') as mocked:
  169. notifications._send_audit_notification(action,
  170. initiator,
  171. outcome,
  172. target,
  173. event_type)
  174. mocked.assert_not_called()
  175. def test_opt_out_authenticate_event(self):
  176. """Test that authenticate events are successfully opted out."""
  177. resource_type = EXP_RESOURCE_TYPE
  178. action = CREATED_OPERATION + '.' + resource_type
  179. initiator = mock
  180. target = mock
  181. outcome = 'success'
  182. event_type = 'identity.authenticate'
  183. meter_name = '%s.%s' % (event_type, outcome)
  184. conf = self.useFixture(config_fixture.Config(CONF))
  185. conf.config(notification_opt_out=[meter_name])
  186. with mock.patch.object(notifications._get_notifier(),
  187. 'info') as mocked:
  188. notifications._send_audit_notification(action,
  189. initiator,
  190. outcome,
  191. target,
  192. event_type)
  193. mocked.assert_not_called()
  194. class BaseNotificationTest(test_v3.RestfulTestCase):
  195. def setUp(self):
  196. super(BaseNotificationTest, self).setUp()
  197. self._notifications = []
  198. self._audits = []
  199. def fake_notify(operation, resource_type, resource_id, initiator=None,
  200. actor_dict=None, public=True):
  201. note = {
  202. 'resource_id': resource_id,
  203. 'operation': operation,
  204. 'resource_type': resource_type,
  205. 'initiator': initiator,
  206. 'send_notification_called': True,
  207. 'public': public}
  208. if actor_dict:
  209. note['actor_id'] = actor_dict.get('id')
  210. note['actor_type'] = actor_dict.get('type')
  211. note['actor_operation'] = actor_dict.get('actor_operation')
  212. self._notifications.append(note)
  213. self.useFixture(fixtures.MockPatchObject(
  214. notifications, '_send_notification', fake_notify))
  215. def fake_audit(action, initiator, outcome, target,
  216. event_type, reason=None, **kwargs):
  217. service_security = cadftaxonomy.SERVICE_SECURITY
  218. event = eventfactory.EventFactory().new_event(
  219. eventType=cadftype.EVENTTYPE_ACTIVITY,
  220. outcome=outcome,
  221. action=action,
  222. initiator=initiator,
  223. target=target,
  224. reason=reason,
  225. observer=cadfresource.Resource(typeURI=service_security))
  226. for key, value in kwargs.items():
  227. setattr(event, key, value)
  228. payload = event.as_dict()
  229. audit = {
  230. 'payload': payload,
  231. 'event_type': event_type,
  232. 'send_notification_called': True}
  233. self._audits.append(audit)
  234. self.useFixture(fixtures.MockPatchObject(
  235. notifications, '_send_audit_notification', fake_audit))
  236. def _assert_last_note(self, resource_id, operation, resource_type,
  237. actor_id=None, actor_type=None,
  238. actor_operation=None):
  239. # NOTE(stevemar): If 'basic' format is not used, then simply
  240. # return since this assertion is not valid.
  241. if CONF.notification_format != 'basic':
  242. return
  243. self.assertGreater(len(self._notifications), 0)
  244. note = self._notifications[-1]
  245. self.assertEqual(operation, note['operation'])
  246. self.assertEqual(resource_id, note['resource_id'])
  247. self.assertEqual(resource_type, note['resource_type'])
  248. self.assertTrue(note['send_notification_called'])
  249. if actor_id:
  250. self.assertEqual(actor_id, note['actor_id'])
  251. self.assertEqual(actor_type, note['actor_type'])
  252. self.assertEqual(actor_operation, note['actor_operation'])
  253. def _assert_last_audit(self, resource_id, operation, resource_type,
  254. target_uri, reason=None):
  255. # NOTE(stevemar): If 'cadf' format is not used, then simply
  256. # return since this assertion is not valid.
  257. if CONF.notification_format != 'cadf':
  258. return
  259. self.assertGreater(len(self._audits), 0)
  260. audit = self._audits[-1]
  261. payload = audit['payload']
  262. if 'resource_info' in payload:
  263. self.assertEqual(resource_id, payload['resource_info'])
  264. action = '.'.join(filter(None, [operation, resource_type]))
  265. self.assertEqual(action, payload['action'])
  266. self.assertEqual(target_uri, payload['target']['typeURI'])
  267. if resource_id:
  268. self.assertEqual(resource_id, payload['target']['id'])
  269. event_type = '.'.join(filter(None, ['identity',
  270. resource_type,
  271. operation]))
  272. self.assertEqual(event_type, audit['event_type'])
  273. if reason:
  274. self.assertEqual(reason['reasonCode'],
  275. payload['reason']['reasonCode'])
  276. self.assertEqual(reason['reasonType'],
  277. payload['reason']['reasonType'])
  278. self.assertTrue(audit['send_notification_called'])
  279. def _assert_initiator_data_is_set(self, operation, resource_type, typeURI):
  280. self.assertGreater(len(self._audits), 0)
  281. audit = self._audits[-1]
  282. payload = audit['payload']
  283. self.assertEqual(self.user_id, payload['initiator']['id'])
  284. self.assertEqual(self.project_id, payload['initiator']['project_id'])
  285. self.assertEqual(typeURI, payload['target']['typeURI'])
  286. self.assertIn('request_id', payload['initiator'])
  287. action = '%s.%s' % (operation, resource_type)
  288. self.assertEqual(action, payload['action'])
  289. def _assert_notify_not_sent(self, resource_id, operation, resource_type,
  290. public=True):
  291. unexpected = {
  292. 'resource_id': resource_id,
  293. 'operation': operation,
  294. 'resource_type': resource_type,
  295. 'send_notification_called': True,
  296. 'public': public}
  297. for note in self._notifications:
  298. self.assertNotEqual(unexpected, note)
  299. def _assert_notify_sent(self, resource_id, operation, resource_type,
  300. public=True):
  301. expected = {
  302. 'resource_id': resource_id,
  303. 'operation': operation,
  304. 'resource_type': resource_type,
  305. 'send_notification_called': True,
  306. 'public': public}
  307. for note in self._notifications:
  308. # compare only expected fields
  309. if all(note.get(k) == v for k, v in expected.items()):
  310. break
  311. else:
  312. self.fail("Notification not sent.")
  313. class NotificationsForEntities(BaseNotificationTest):
  314. def test_create_group(self):
  315. group_ref = unit.new_group_ref(domain_id=self.domain_id)
  316. group_ref = PROVIDERS.identity_api.create_group(group_ref)
  317. self._assert_last_note(group_ref['id'], CREATED_OPERATION, 'group')
  318. self._assert_last_audit(group_ref['id'], CREATED_OPERATION, 'group',
  319. cadftaxonomy.SECURITY_GROUP)
  320. def test_create_project(self):
  321. project_ref = unit.new_project_ref(domain_id=self.domain_id)
  322. PROVIDERS.resource_api.create_project(project_ref['id'], project_ref)
  323. self._assert_last_note(
  324. project_ref['id'], CREATED_OPERATION, 'project')
  325. self._assert_last_audit(project_ref['id'], CREATED_OPERATION,
  326. 'project', cadftaxonomy.SECURITY_PROJECT)
  327. def test_create_role(self):
  328. role_ref = unit.new_role_ref()
  329. PROVIDERS.role_api.create_role(role_ref['id'], role_ref)
  330. self._assert_last_note(role_ref['id'], CREATED_OPERATION, 'role')
  331. self._assert_last_audit(role_ref['id'], CREATED_OPERATION, 'role',
  332. cadftaxonomy.SECURITY_ROLE)
  333. def test_create_user(self):
  334. user_ref = unit.new_user_ref(domain_id=self.domain_id)
  335. user_ref = PROVIDERS.identity_api.create_user(user_ref)
  336. self._assert_last_note(user_ref['id'], CREATED_OPERATION, 'user')
  337. self._assert_last_audit(user_ref['id'], CREATED_OPERATION, 'user',
  338. cadftaxonomy.SECURITY_ACCOUNT_USER)
  339. def test_create_trust(self):
  340. trustor = unit.new_user_ref(domain_id=self.domain_id)
  341. trustor = PROVIDERS.identity_api.create_user(trustor)
  342. trustee = unit.new_user_ref(domain_id=self.domain_id)
  343. trustee = PROVIDERS.identity_api.create_user(trustee)
  344. role_ref = unit.new_role_ref()
  345. PROVIDERS.role_api.create_role(role_ref['id'], role_ref)
  346. trust_ref = unit.new_trust_ref(trustor['id'],
  347. trustee['id'])
  348. PROVIDERS.trust_api.create_trust(
  349. trust_ref['id'], trust_ref, [role_ref]
  350. )
  351. self._assert_last_note(
  352. trust_ref['id'], CREATED_OPERATION, 'OS-TRUST:trust')
  353. self._assert_last_audit(trust_ref['id'], CREATED_OPERATION,
  354. 'OS-TRUST:trust', cadftaxonomy.SECURITY_TRUST)
  355. def test_delete_group(self):
  356. group_ref = unit.new_group_ref(domain_id=self.domain_id)
  357. group_ref = PROVIDERS.identity_api.create_group(group_ref)
  358. PROVIDERS.identity_api.delete_group(group_ref['id'])
  359. self._assert_last_note(group_ref['id'], DELETED_OPERATION, 'group')
  360. self._assert_last_audit(group_ref['id'], DELETED_OPERATION, 'group',
  361. cadftaxonomy.SECURITY_GROUP)
  362. def test_delete_project(self):
  363. project_ref = unit.new_project_ref(domain_id=self.domain_id)
  364. PROVIDERS.resource_api.create_project(project_ref['id'], project_ref)
  365. PROVIDERS.resource_api.delete_project(project_ref['id'])
  366. self._assert_last_note(
  367. project_ref['id'], DELETED_OPERATION, 'project')
  368. self._assert_last_audit(project_ref['id'], DELETED_OPERATION,
  369. 'project', cadftaxonomy.SECURITY_PROJECT)
  370. def test_delete_role(self):
  371. role_ref = unit.new_role_ref()
  372. PROVIDERS.role_api.create_role(role_ref['id'], role_ref)
  373. PROVIDERS.role_api.delete_role(role_ref['id'])
  374. self._assert_last_note(role_ref['id'], DELETED_OPERATION, 'role')
  375. self._assert_last_audit(role_ref['id'], DELETED_OPERATION, 'role',
  376. cadftaxonomy.SECURITY_ROLE)
  377. def test_delete_user(self):
  378. user_ref = unit.new_user_ref(domain_id=self.domain_id)
  379. user_ref = PROVIDERS.identity_api.create_user(user_ref)
  380. PROVIDERS.identity_api.delete_user(user_ref['id'])
  381. self._assert_last_note(user_ref['id'], DELETED_OPERATION, 'user')
  382. self._assert_last_audit(user_ref['id'], DELETED_OPERATION, 'user',
  383. cadftaxonomy.SECURITY_ACCOUNT_USER)
  384. def test_create_domain(self):
  385. domain_ref = unit.new_domain_ref()
  386. PROVIDERS.resource_api.create_domain(domain_ref['id'], domain_ref)
  387. self._assert_last_note(domain_ref['id'], CREATED_OPERATION, 'domain')
  388. self._assert_last_audit(domain_ref['id'], CREATED_OPERATION, 'domain',
  389. cadftaxonomy.SECURITY_DOMAIN)
  390. def test_update_domain(self):
  391. domain_ref = unit.new_domain_ref()
  392. PROVIDERS.resource_api.create_domain(domain_ref['id'], domain_ref)
  393. domain_ref['description'] = uuid.uuid4().hex
  394. PROVIDERS.resource_api.update_domain(domain_ref['id'], domain_ref)
  395. self._assert_last_note(domain_ref['id'], UPDATED_OPERATION, 'domain')
  396. self._assert_last_audit(domain_ref['id'], UPDATED_OPERATION, 'domain',
  397. cadftaxonomy.SECURITY_DOMAIN)
  398. def test_delete_domain(self):
  399. domain_ref = unit.new_domain_ref()
  400. PROVIDERS.resource_api.create_domain(domain_ref['id'], domain_ref)
  401. domain_ref['enabled'] = False
  402. PROVIDERS.resource_api.update_domain(domain_ref['id'], domain_ref)
  403. PROVIDERS.resource_api.delete_domain(domain_ref['id'])
  404. self._assert_last_note(domain_ref['id'], DELETED_OPERATION, 'domain')
  405. self._assert_last_audit(domain_ref['id'], DELETED_OPERATION, 'domain',
  406. cadftaxonomy.SECURITY_DOMAIN)
  407. def test_delete_trust(self):
  408. trustor = unit.new_user_ref(domain_id=self.domain_id)
  409. trustor = PROVIDERS.identity_api.create_user(trustor)
  410. trustee = unit.new_user_ref(domain_id=self.domain_id)
  411. trustee = PROVIDERS.identity_api.create_user(trustee)
  412. role_ref = unit.new_role_ref()
  413. trust_ref = unit.new_trust_ref(trustor['id'], trustee['id'])
  414. PROVIDERS.trust_api.create_trust(
  415. trust_ref['id'], trust_ref, [role_ref]
  416. )
  417. PROVIDERS.trust_api.delete_trust(trust_ref['id'])
  418. self._assert_last_note(
  419. trust_ref['id'], DELETED_OPERATION, 'OS-TRUST:trust')
  420. self._assert_last_audit(trust_ref['id'], DELETED_OPERATION,
  421. 'OS-TRUST:trust', cadftaxonomy.SECURITY_TRUST)
  422. def test_create_endpoint(self):
  423. endpoint_ref = unit.new_endpoint_ref(service_id=self.service_id,
  424. interface='public',
  425. region_id=self.region_id)
  426. PROVIDERS.catalog_api.create_endpoint(endpoint_ref['id'], endpoint_ref)
  427. self._assert_notify_sent(endpoint_ref['id'], CREATED_OPERATION,
  428. 'endpoint')
  429. self._assert_last_audit(endpoint_ref['id'], CREATED_OPERATION,
  430. 'endpoint', cadftaxonomy.SECURITY_ENDPOINT)
  431. def test_update_endpoint(self):
  432. endpoint_ref = unit.new_endpoint_ref(service_id=self.service_id,
  433. interface='public',
  434. region_id=self.region_id)
  435. PROVIDERS.catalog_api.create_endpoint(endpoint_ref['id'], endpoint_ref)
  436. PROVIDERS.catalog_api.update_endpoint(endpoint_ref['id'], endpoint_ref)
  437. self._assert_notify_sent(endpoint_ref['id'], UPDATED_OPERATION,
  438. 'endpoint')
  439. self._assert_last_audit(endpoint_ref['id'], UPDATED_OPERATION,
  440. 'endpoint', cadftaxonomy.SECURITY_ENDPOINT)
  441. def test_delete_endpoint(self):
  442. endpoint_ref = unit.new_endpoint_ref(service_id=self.service_id,
  443. interface='public',
  444. region_id=self.region_id)
  445. PROVIDERS.catalog_api.create_endpoint(endpoint_ref['id'], endpoint_ref)
  446. PROVIDERS.catalog_api.delete_endpoint(endpoint_ref['id'])
  447. self._assert_notify_sent(endpoint_ref['id'], DELETED_OPERATION,
  448. 'endpoint')
  449. self._assert_last_audit(endpoint_ref['id'], DELETED_OPERATION,
  450. 'endpoint', cadftaxonomy.SECURITY_ENDPOINT)
  451. def test_create_service(self):
  452. service_ref = unit.new_service_ref()
  453. PROVIDERS.catalog_api.create_service(service_ref['id'], service_ref)
  454. self._assert_notify_sent(service_ref['id'], CREATED_OPERATION,
  455. 'service')
  456. self._assert_last_audit(service_ref['id'], CREATED_OPERATION,
  457. 'service', cadftaxonomy.SECURITY_SERVICE)
  458. def test_update_service(self):
  459. service_ref = unit.new_service_ref()
  460. PROVIDERS.catalog_api.create_service(service_ref['id'], service_ref)
  461. PROVIDERS.catalog_api.update_service(service_ref['id'], service_ref)
  462. self._assert_notify_sent(service_ref['id'], UPDATED_OPERATION,
  463. 'service')
  464. self._assert_last_audit(service_ref['id'], UPDATED_OPERATION,
  465. 'service', cadftaxonomy.SECURITY_SERVICE)
  466. def test_delete_service(self):
  467. service_ref = unit.new_service_ref()
  468. PROVIDERS.catalog_api.create_service(service_ref['id'], service_ref)
  469. PROVIDERS.catalog_api.delete_service(service_ref['id'])
  470. self._assert_notify_sent(service_ref['id'], DELETED_OPERATION,
  471. 'service')
  472. self._assert_last_audit(service_ref['id'], DELETED_OPERATION,
  473. 'service', cadftaxonomy.SECURITY_SERVICE)
  474. def test_create_region(self):
  475. region_ref = unit.new_region_ref()
  476. PROVIDERS.catalog_api.create_region(region_ref)
  477. self._assert_notify_sent(region_ref['id'], CREATED_OPERATION,
  478. 'region')
  479. self._assert_last_audit(region_ref['id'], CREATED_OPERATION,
  480. 'region', cadftaxonomy.SECURITY_REGION)
  481. def test_update_region(self):
  482. region_ref = unit.new_region_ref()
  483. PROVIDERS.catalog_api.create_region(region_ref)
  484. PROVIDERS.catalog_api.update_region(region_ref['id'], region_ref)
  485. self._assert_notify_sent(region_ref['id'], UPDATED_OPERATION,
  486. 'region')
  487. self._assert_last_audit(region_ref['id'], UPDATED_OPERATION,
  488. 'region', cadftaxonomy.SECURITY_REGION)
  489. def test_delete_region(self):
  490. region_ref = unit.new_region_ref()
  491. PROVIDERS.catalog_api.create_region(region_ref)
  492. PROVIDERS.catalog_api.delete_region(region_ref['id'])
  493. self._assert_notify_sent(region_ref['id'], DELETED_OPERATION,
  494. 'region')
  495. self._assert_last_audit(region_ref['id'], DELETED_OPERATION,
  496. 'region', cadftaxonomy.SECURITY_REGION)
  497. def test_create_policy(self):
  498. policy_ref = unit.new_policy_ref()
  499. PROVIDERS.policy_api.create_policy(policy_ref['id'], policy_ref)
  500. self._assert_notify_sent(policy_ref['id'], CREATED_OPERATION,
  501. 'policy')
  502. self._assert_last_audit(policy_ref['id'], CREATED_OPERATION,
  503. 'policy', cadftaxonomy.SECURITY_POLICY)
  504. def test_update_policy(self):
  505. policy_ref = unit.new_policy_ref()
  506. PROVIDERS.policy_api.create_policy(policy_ref['id'], policy_ref)
  507. PROVIDERS.policy_api.update_policy(policy_ref['id'], policy_ref)
  508. self._assert_notify_sent(policy_ref['id'], UPDATED_OPERATION,
  509. 'policy')
  510. self._assert_last_audit(policy_ref['id'], UPDATED_OPERATION,
  511. 'policy', cadftaxonomy.SECURITY_POLICY)
  512. def test_delete_policy(self):
  513. policy_ref = unit.new_policy_ref()
  514. PROVIDERS.policy_api.create_policy(policy_ref['id'], policy_ref)
  515. PROVIDERS.policy_api.delete_policy(policy_ref['id'])
  516. self._assert_notify_sent(policy_ref['id'], DELETED_OPERATION,
  517. 'policy')
  518. self._assert_last_audit(policy_ref['id'], DELETED_OPERATION,
  519. 'policy', cadftaxonomy.SECURITY_POLICY)
  520. def test_disable_domain(self):
  521. domain_ref = unit.new_domain_ref()
  522. PROVIDERS.resource_api.create_domain(domain_ref['id'], domain_ref)
  523. domain_ref['enabled'] = False
  524. PROVIDERS.resource_api.update_domain(domain_ref['id'], domain_ref)
  525. self._assert_notify_sent(domain_ref['id'], 'disabled', 'domain',
  526. public=False)
  527. def test_disable_of_disabled_domain_does_not_notify(self):
  528. domain_ref = unit.new_domain_ref(enabled=False)
  529. PROVIDERS.resource_api.create_domain(domain_ref['id'], domain_ref)
  530. # The domain_ref above is not changed during the create process. We
  531. # can use the same ref to perform the update.
  532. PROVIDERS.resource_api.update_domain(domain_ref['id'], domain_ref)
  533. self._assert_notify_not_sent(domain_ref['id'], 'disabled', 'domain',
  534. public=False)
  535. def test_update_group(self):
  536. group_ref = unit.new_group_ref(domain_id=self.domain_id)
  537. group_ref = PROVIDERS.identity_api.create_group(group_ref)
  538. PROVIDERS.identity_api.update_group(group_ref['id'], group_ref)
  539. self._assert_last_note(group_ref['id'], UPDATED_OPERATION, 'group')
  540. self._assert_last_audit(group_ref['id'], UPDATED_OPERATION, 'group',
  541. cadftaxonomy.SECURITY_GROUP)
  542. def test_update_project(self):
  543. project_ref = unit.new_project_ref(domain_id=self.domain_id)
  544. PROVIDERS.resource_api.create_project(project_ref['id'], project_ref)
  545. PROVIDERS.resource_api.update_project(project_ref['id'], project_ref)
  546. self._assert_notify_sent(
  547. project_ref['id'], UPDATED_OPERATION, 'project', public=True)
  548. self._assert_last_audit(project_ref['id'], UPDATED_OPERATION,
  549. 'project', cadftaxonomy.SECURITY_PROJECT)
  550. def test_disable_project(self):
  551. project_ref = unit.new_project_ref(domain_id=self.domain_id)
  552. PROVIDERS.resource_api.create_project(project_ref['id'], project_ref)
  553. project_ref['enabled'] = False
  554. PROVIDERS.resource_api.update_project(project_ref['id'], project_ref)
  555. self._assert_notify_sent(project_ref['id'], 'disabled', 'project',
  556. public=False)
  557. def test_disable_of_disabled_project_does_not_notify(self):
  558. project_ref = unit.new_project_ref(domain_id=self.domain_id,
  559. enabled=False)
  560. PROVIDERS.resource_api.create_project(project_ref['id'], project_ref)
  561. # The project_ref above is not changed during the create process. We
  562. # can use the same ref to perform the update.
  563. PROVIDERS.resource_api.update_project(project_ref['id'], project_ref)
  564. self._assert_notify_not_sent(project_ref['id'], 'disabled', 'project',
  565. public=False)
  566. def test_update_project_does_not_send_disable(self):
  567. project_ref = unit.new_project_ref(domain_id=self.domain_id)
  568. PROVIDERS.resource_api.create_project(project_ref['id'], project_ref)
  569. project_ref['enabled'] = True
  570. PROVIDERS.resource_api.update_project(project_ref['id'], project_ref)
  571. self._assert_last_note(
  572. project_ref['id'], UPDATED_OPERATION, 'project')
  573. self._assert_notify_not_sent(project_ref['id'], 'disabled', 'project')
  574. def test_update_role(self):
  575. role_ref = unit.new_role_ref()
  576. PROVIDERS.role_api.create_role(role_ref['id'], role_ref)
  577. PROVIDERS.role_api.update_role(role_ref['id'], role_ref)
  578. self._assert_last_note(role_ref['id'], UPDATED_OPERATION, 'role')
  579. self._assert_last_audit(role_ref['id'], UPDATED_OPERATION, 'role',
  580. cadftaxonomy.SECURITY_ROLE)
  581. def test_update_user(self):
  582. user_ref = unit.new_user_ref(domain_id=self.domain_id)
  583. user_ref = PROVIDERS.identity_api.create_user(user_ref)
  584. PROVIDERS.identity_api.update_user(user_ref['id'], user_ref)
  585. self._assert_last_note(user_ref['id'], UPDATED_OPERATION, 'user')
  586. self._assert_last_audit(user_ref['id'], UPDATED_OPERATION, 'user',
  587. cadftaxonomy.SECURITY_ACCOUNT_USER)
  588. def test_config_option_no_events(self):
  589. self.config_fixture.config(notification_format='basic')
  590. role_ref = unit.new_role_ref()
  591. PROVIDERS.role_api.create_role(role_ref['id'], role_ref)
  592. # The regular notifications will still be emitted, since they are
  593. # used for callback handling.
  594. self._assert_last_note(role_ref['id'], CREATED_OPERATION, 'role')
  595. # No audit event should have occurred
  596. self.assertEqual(0, len(self._audits))
  597. def test_add_user_to_group(self):
  598. user_ref = unit.new_user_ref(domain_id=self.domain_id)
  599. user_ref = PROVIDERS.identity_api.create_user(user_ref)
  600. group_ref = unit.new_group_ref(domain_id=self.domain_id)
  601. group_ref = PROVIDERS.identity_api.create_group(group_ref)
  602. PROVIDERS.identity_api.add_user_to_group(
  603. user_ref['id'], group_ref['id']
  604. )
  605. self._assert_last_note(group_ref['id'], UPDATED_OPERATION, 'group',
  606. actor_id=user_ref['id'], actor_type='user',
  607. actor_operation='added')
  608. def test_remove_user_from_group(self):
  609. user_ref = unit.new_user_ref(domain_id=self.domain_id)
  610. user_ref = PROVIDERS.identity_api.create_user(user_ref)
  611. group_ref = unit.new_group_ref(domain_id=self.domain_id)
  612. group_ref = PROVIDERS.identity_api.create_group(group_ref)
  613. PROVIDERS.identity_api.add_user_to_group(
  614. user_ref['id'], group_ref['id']
  615. )
  616. PROVIDERS.identity_api.remove_user_from_group(
  617. user_ref['id'], group_ref['id']
  618. )
  619. self._assert_last_note(group_ref['id'], UPDATED_OPERATION, 'group',
  620. actor_id=user_ref['id'], actor_type='user',
  621. actor_operation='removed')
  622. def test_initiator_request_id(self):
  623. ref = unit.new_domain_ref()
  624. self.post('/domains', body={'domain': ref})
  625. note = self._notifications[-1]
  626. initiator = note['initiator']
  627. self.assertIsNotNone(initiator.request_id)
  628. def test_initiator_global_request_id(self):
  629. global_request_id = 'req-%s' % uuid.uuid4()
  630. ref = unit.new_domain_ref()
  631. self.post('/domains', body={'domain': ref},
  632. headers={'X-OpenStack-Request-Id': global_request_id})
  633. note = self._notifications[-1]
  634. initiator = note['initiator']
  635. self.assertEqual(
  636. initiator.global_request_id, global_request_id)
  637. def test_initiator_global_request_id_not_set(self):
  638. ref = unit.new_domain_ref()
  639. self.post('/domains', body={'domain': ref})
  640. note = self._notifications[-1]
  641. initiator = note['initiator']
  642. self.assertFalse(hasattr(initiator, 'global_request_id'))
  643. class CADFNotificationsForPCIDSSEvents(BaseNotificationTest):
  644. def setUp(self):
  645. super(CADFNotificationsForPCIDSSEvents, self).setUp()
  646. conf = self.useFixture(config_fixture.Config(CONF))
  647. conf.config(notification_format='cadf')
  648. conf.config(group='security_compliance',
  649. password_expires_days=2)
  650. conf.config(group='security_compliance',
  651. lockout_failure_attempts=3)
  652. conf.config(group='security_compliance',
  653. unique_last_password_count=2)
  654. conf.config(group='security_compliance',
  655. minimum_password_age=2)
  656. conf.config(group='security_compliance',
  657. password_regex='^(?=.*\d)(?=.*[a-zA-Z]).{7,}$')
  658. conf.config(group='security_compliance',
  659. password_regex_description='1 letter, 1 digit, 7 chars')
  660. def test_password_expired_sends_notification(self):
  661. password = uuid.uuid4().hex
  662. password_creation_time = (
  663. datetime.datetime.utcnow() -
  664. datetime.timedelta(
  665. days=CONF.security_compliance.password_expires_days + 1)
  666. )
  667. freezer = freezegun.freeze_time(password_creation_time)
  668. # NOTE(gagehugo): This part below uses freezegun to spoof
  669. # the time as being three days in the past from right now. We will
  670. # create a user and have that user successfully authenticate,
  671. # then stop the time machine and return to the present time,
  672. # where the user's password is now expired.
  673. freezer.start()
  674. user_ref = unit.new_user_ref(domain_id=self.domain_id,
  675. password=password)
  676. user_ref = PROVIDERS.identity_api.create_user(user_ref)
  677. with self.make_request():
  678. PROVIDERS.identity_api.authenticate(user_ref['id'], password)
  679. freezer.stop()
  680. reason_type = (exception.PasswordExpired.message_format %
  681. {'user_id': user_ref['id']})
  682. expected_reason = {'reasonCode': '401',
  683. 'reasonType': reason_type}
  684. with self.make_request():
  685. self.assertRaises(exception.PasswordExpired,
  686. PROVIDERS.identity_api.authenticate,
  687. user_id=user_ref['id'],
  688. password=password)
  689. self._assert_last_audit(None, 'authenticate', None,
  690. cadftaxonomy.ACCOUNT_USER,
  691. reason=expected_reason)
  692. def test_locked_out_user_sends_notification(self):
  693. password = uuid.uuid4().hex
  694. new_password = uuid.uuid4().hex
  695. expected_responses = [AssertionError, AssertionError, AssertionError,
  696. exception.AccountLocked]
  697. user_ref = unit.new_user_ref(domain_id=self.domain_id,
  698. password=password)
  699. user_ref = PROVIDERS.identity_api.create_user(user_ref)
  700. reason_type = (exception.AccountLocked.message_format %
  701. {'user_id': user_ref['id']})
  702. expected_reason = {'reasonCode': '401',
  703. 'reasonType': reason_type}
  704. for ex in expected_responses:
  705. with self.make_request():
  706. self.assertRaises(ex,
  707. PROVIDERS.identity_api.change_password,
  708. user_id=user_ref['id'],
  709. original_password=new_password,
  710. new_password=new_password)
  711. self._assert_last_audit(None, 'authenticate', None,
  712. cadftaxonomy.ACCOUNT_USER,
  713. reason=expected_reason)
  714. def test_repeated_password_sends_notification(self):
  715. conf = self.useFixture(config_fixture.Config(CONF))
  716. conf.config(group='security_compliance',
  717. minimum_password_age=0)
  718. password = uuid.uuid4().hex
  719. new_password = uuid.uuid4().hex
  720. count = CONF.security_compliance.unique_last_password_count
  721. reason_type = (exception.PasswordHistoryValidationError.message_format
  722. % {'unique_count': count})
  723. expected_reason = {'reasonCode': '400',
  724. 'reasonType': reason_type}
  725. user_ref = unit.new_user_ref(domain_id=self.domain_id,
  726. password=password)
  727. user_ref = PROVIDERS.identity_api.create_user(user_ref)
  728. with self.make_request():
  729. PROVIDERS.identity_api.change_password(
  730. user_id=user_ref['id'],
  731. original_password=password, new_password=new_password
  732. )
  733. with self.make_request():
  734. self.assertRaises(exception.PasswordValidationError,
  735. PROVIDERS.identity_api.change_password,
  736. user_id=user_ref['id'],
  737. original_password=new_password,
  738. new_password=password)
  739. self._assert_last_audit(user_ref['id'], UPDATED_OPERATION, 'user',
  740. cadftaxonomy.SECURITY_ACCOUNT_USER,
  741. reason=expected_reason)
  742. def test_invalid_password_sends_notification(self):
  743. password = uuid.uuid4().hex
  744. invalid_password = '1'
  745. regex = CONF.security_compliance.password_regex_description
  746. reason_type = (exception.PasswordRequirementsValidationError
  747. .message_format %
  748. {'detail': regex})
  749. expected_reason = {'reasonCode': '400',
  750. 'reasonType': reason_type}
  751. user_ref = unit.new_user_ref(domain_id=self.domain_id,
  752. password=password)
  753. user_ref = PROVIDERS.identity_api.create_user(user_ref)
  754. with self.make_request():
  755. self.assertRaises(exception.PasswordValidationError,
  756. PROVIDERS.identity_api.change_password,
  757. user_id=user_ref['id'],
  758. original_password=password,
  759. new_password=invalid_password)
  760. self._assert_last_audit(user_ref['id'], UPDATED_OPERATION, 'user',
  761. cadftaxonomy.SECURITY_ACCOUNT_USER,
  762. reason=expected_reason)
  763. def test_changing_password_too_early_sends_notification(self):
  764. password = uuid.uuid4().hex
  765. new_password = uuid.uuid4().hex
  766. next_password = uuid.uuid4().hex
  767. user_ref = unit.new_user_ref(domain_id=self.domain_id,
  768. password=password,
  769. password_created_at=(
  770. datetime.datetime.utcnow()))
  771. user_ref = PROVIDERS.identity_api.create_user(user_ref)
  772. min_days = CONF.security_compliance.minimum_password_age
  773. min_age = (user_ref['password_created_at'] +
  774. datetime.timedelta(days=min_days))
  775. days_left = (min_age - datetime.datetime.utcnow()).days
  776. reason_type = (exception.PasswordAgeValidationError.message_format %
  777. {'min_age_days': min_days, 'days_left': days_left})
  778. expected_reason = {'reasonCode': '400',
  779. 'reasonType': reason_type}
  780. with self.make_request():
  781. PROVIDERS.identity_api.change_password(
  782. user_id=user_ref['id'],
  783. original_password=password, new_password=new_password
  784. )
  785. with self.make_request():
  786. self.assertRaises(exception.PasswordValidationError,
  787. PROVIDERS.identity_api.change_password,
  788. user_id=user_ref['id'],
  789. original_password=new_password,
  790. new_password=next_password)
  791. self._assert_last_audit(user_ref['id'], UPDATED_OPERATION, 'user',
  792. cadftaxonomy.SECURITY_ACCOUNT_USER,
  793. reason=expected_reason)
  794. class CADFNotificationsForEntities(NotificationsForEntities):
  795. def setUp(self):
  796. super(CADFNotificationsForEntities, self).setUp()
  797. self.config_fixture.config(notification_format='cadf')
  798. def test_initiator_data_is_set(self):
  799. ref = unit.new_domain_ref()
  800. resp = self.post('/domains', body={'domain': ref})
  801. resource_id = resp.result.get('domain').get('id')
  802. self._assert_last_audit(resource_id, CREATED_OPERATION, 'domain',
  803. cadftaxonomy.SECURITY_DOMAIN)
  804. self._assert_initiator_data_is_set(CREATED_OPERATION,
  805. 'domain',
  806. cadftaxonomy.SECURITY_DOMAIN)
  807. def test_initiator_request_id(self):
  808. data = self.build_authentication_request(
  809. user_id=self.user_id,
  810. password=self.user['password'])
  811. self.post('/auth/tokens', body=data)
  812. audit = self._audits[-1]
  813. initiator = audit['payload']['initiator']
  814. self.assertIn('request_id', initiator)
  815. def test_initiator_global_request_id(self):
  816. global_request_id = 'req-%s' % uuid.uuid4()
  817. data = self.build_authentication_request(
  818. user_id=self.user_id,
  819. password=self.user['password'])
  820. self.post(
  821. '/auth/tokens', body=data,
  822. headers={'X-OpenStack-Request-Id': global_request_id})
  823. audit = self._audits[-1]
  824. initiator = audit['payload']['initiator']
  825. self.assertEqual(
  826. initiator['global_request_id'], global_request_id)
  827. def test_initiator_global_request_id_not_set(self):
  828. data = self.build_authentication_request(
  829. user_id=self.user_id,
  830. password=self.user['password'])
  831. self.post('/auth/tokens', body=data)
  832. audit = self._audits[-1]
  833. initiator = audit['payload']['initiator']
  834. self.assertNotIn('global_request_id', initiator)
  835. class TestEventCallbacks(test_v3.RestfulTestCase):
  836. class FakeManager(object):
  837. def _project_deleted_callback(self, service, resource_type, operation,
  838. payload):
  839. """Used just for the callback interface."""
  840. def test_notification_received(self):
  841. callback = register_callback(CREATED_OPERATION, 'project')
  842. project_ref = unit.new_project_ref(domain_id=self.domain_id)
  843. PROVIDERS.resource_api.create_project(project_ref['id'], project_ref)
  844. self.assertTrue(callback.called)
  845. def test_notification_method_not_callable(self):
  846. fake_method = None
  847. self.assertRaises(TypeError,
  848. notifications.register_event_callback,
  849. UPDATED_OPERATION,
  850. 'project',
  851. [fake_method])
  852. def test_notification_event_not_valid(self):
  853. manager = self.FakeManager()
  854. self.assertRaises(ValueError,
  855. notifications.register_event_callback,
  856. uuid.uuid4().hex,
  857. 'project',
  858. manager._project_deleted_callback)
  859. def test_event_registration_for_unknown_resource_type(self):
  860. # Registration for unknown resource types should succeed. If no event
  861. # is issued for that resource type, the callback wont be triggered.
  862. manager = self.FakeManager()
  863. notifications.register_event_callback(
  864. DELETED_OPERATION,
  865. uuid.uuid4().hex,
  866. manager._project_deleted_callback)
  867. resource_type = uuid.uuid4().hex
  868. notifications.register_event_callback(
  869. DELETED_OPERATION,
  870. resource_type,
  871. manager._project_deleted_callback)
  872. def test_provider_event_callback_subscription(self):
  873. callback_called = []
  874. @notifications.listener
  875. class Foo(object):
  876. def __init__(self):
  877. self.event_callbacks = {
  878. CREATED_OPERATION: {'project': self.foo_callback}}
  879. def foo_callback(self, service, resource_type, operation,
  880. payload):
  881. # uses callback_called from the closure
  882. callback_called.append(True)
  883. Foo()
  884. project_ref = unit.new_project_ref(domain_id=self.domain_id)
  885. PROVIDERS.resource_api.create_project(project_ref['id'], project_ref)
  886. self.assertEqual([True], callback_called)
  887. def test_provider_event_callbacks_subscription(self):
  888. callback_called = []
  889. @notifications.listener
  890. class Foo(object):
  891. def __init__(self):
  892. self.event_callbacks = {
  893. CREATED_OPERATION: {
  894. 'project': [self.callback_0, self.callback_1]}}
  895. def callback_0(self, service, resource_type, operation, payload):
  896. # uses callback_called from the closure
  897. callback_called.append('cb0')
  898. def callback_1(self, service, resource_type, operation, payload):
  899. # uses callback_called from the closure
  900. callback_called.append('cb1')
  901. Foo()
  902. project_ref = unit.new_project_ref(domain_id=self.domain_id)
  903. PROVIDERS.resource_api.create_project(project_ref['id'], project_ref)
  904. self.assertItemsEqual(['cb1', 'cb0'], callback_called)
  905. def test_invalid_event_callbacks(self):
  906. @notifications.listener
  907. class Foo(object):
  908. def __init__(self):
  909. self.event_callbacks = 'bogus'
  910. self.assertRaises(AttributeError, Foo)
  911. def test_invalid_event_callbacks_event(self):
  912. @notifications.listener
  913. class Foo(object):
  914. def __init__(self):
  915. self.event_callbacks = {CREATED_OPERATION: 'bogus'}
  916. self.assertRaises(AttributeError, Foo)
  917. def test_using_an_unbound_method_as_a_callback_fails(self):
  918. # NOTE(dstanek): An unbound method is when you reference a method
  919. # from a class object. You'll get a method that isn't bound to a
  920. # particular instance so there is no magic 'self'. You can call it,
  921. # but you have to pass in the instance manually like: C.m(C()).
  922. # If you reference the method from an instance then you get a method
  923. # that effectively curries the self argument for you
  924. # (think functools.partial). Obviously is we don't have an
  925. # instance then we can't call the method.
  926. @notifications.listener
  927. class Foo(object):
  928. def __init__(self):
  929. self.event_callbacks = {CREATED_OPERATION:
  930. {'project': Foo.callback}}
  931. def callback(self, service, resource_type, operation, payload):
  932. pass
  933. # TODO(dstanek): it would probably be nice to fail early using
  934. # something like:
  935. # self.assertRaises(TypeError, Foo)
  936. Foo()
  937. project_ref = unit.new_project_ref(domain_id=self.domain_id)
  938. self.assertRaises(TypeError, PROVIDERS.resource_api.create_project,
  939. project_ref['id'], project_ref)
  940. class CadfNotificationsWrapperTestCase(test_v3.RestfulTestCase):
  941. LOCAL_HOST = 'localhost'
  942. ACTION = 'authenticate'
  943. ROLE_ASSIGNMENT = 'role_assignment'
  944. def setUp(self):
  945. super(CadfNotificationsWrapperTestCase, self).setUp()
  946. self._notifications = []
  947. def fake_notify(action, initiator, outcome, target,
  948. event_type, reason=None, **kwargs):
  949. service_security = cadftaxonomy.SERVICE_SECURITY
  950. event = eventfactory.EventFactory().new_event(
  951. eventType=cadftype.EVENTTYPE_ACTIVITY,
  952. outcome=outcome,
  953. action=action,
  954. initiator=initiator,
  955. target=target,
  956. reason=reason,
  957. observer=cadfresource.Resource(typeURI=service_security))
  958. for key, value in kwargs.items():
  959. setattr(event, key, value)
  960. note = {
  961. 'action': action,
  962. 'initiator': initiator,
  963. 'event': event,
  964. 'event_type': event_type,
  965. 'send_notification_called': True}
  966. self._notifications.append(note)
  967. self.useFixture(fixtures.MockPatchObject(
  968. notifications, '_send_audit_notification', fake_notify))
  969. def _get_last_note(self):
  970. self.assertTrue(self._notifications)
  971. return self._notifications[-1]
  972. def _assert_last_note(self, action, user_id, event_type=None):
  973. self.assertTrue(self._notifications)
  974. note = self._notifications[-1]
  975. self.assertEqual(action, note['action'])
  976. initiator = note['initiator']
  977. self.assertEqual(user_id, initiator.id)
  978. self.assertEqual(self.LOCAL_HOST, initiator.host.address)
  979. self.assertTrue(note['send_notification_called'])
  980. if event_type:
  981. self.assertEqual(event_type, note['event_type'])
  982. def _assert_event(self, role_id, project=None, domain=None,
  983. user=None, group=None, inherit=False):
  984. """Assert that the CADF event is valid.
  985. In the case of role assignments, the event will have extra data,
  986. specifically, the role, target, actor, and if the role is inherited.
  987. An example event, as a dictionary is seen below:
  988. {
  989. 'typeURI': 'http://schemas.dmtf.org/cloud/audit/1.0/event',
  990. 'initiator': {
  991. 'typeURI': 'service/security/account/user',
  992. 'host': {'address': 'localhost'},
  993. 'id': 'openstack:0a90d95d-582c-4efb-9cbc-e2ca7ca9c341',
  994. 'username': u'admin'
  995. },
  996. 'target': {
  997. 'typeURI': 'service/security/account/user',
  998. 'id': 'openstack:d48ea485-ef70-4f65-8d2b-01aa9d7ec12d'
  999. },
  1000. 'observer': {
  1001. 'typeURI': 'service/security',
  1002. 'id': 'openstack:d51dd870-d929-4aba-8d75-dcd7555a0c95'
  1003. },
  1004. 'eventType': 'activity',
  1005. 'eventTime': '2014-08-21T21:04:56.204536+0000',
  1006. 'role': u'0e6b990380154a2599ce6b6e91548a68',
  1007. 'domain': u'24bdcff1aab8474895dbaac509793de1',
  1008. 'inherited_to_projects': False,
  1009. 'group': u'c1e22dc67cbd469ea0e33bf428fe597a',
  1010. 'action': 'created.role_assignment',
  1011. 'outcome': 'success',
  1012. 'id': 'openstack:782689dd-f428-4f13-99c7-5c70f94a5ac1'
  1013. }
  1014. """
  1015. note = self._notifications[-1]
  1016. event = note['event']
  1017. if project:
  1018. self.assertEqual(project, event.project)
  1019. if domain:
  1020. self.assertEqual(domain, event.domain)
  1021. if group:
  1022. self.assertEqual(group, event.group)
  1023. elif user:
  1024. self.assertEqual(user, event.user)
  1025. self.assertEqual(role_id, event.role)
  1026. self.assertEqual(inherit, event.inherited_to_projects)
  1027. def test_initiator_id_always_matches_user_id(self):
  1028. # Clear notifications
  1029. while self._notifications:
  1030. self._notifications.pop()
  1031. self.get_scoped_token()
  1032. self.assertEqual(len(self._notifications), 1)
  1033. note = self._notifications.pop()
  1034. initiator = note['initiator']
  1035. self.assertEqual(self.user_id, initiator.id)
  1036. self.assertEqual(self.user_id, initiator.user_id)
  1037. def test_initiator_always_contains_username(self):
  1038. # Clear notifications
  1039. while self._notifications:
  1040. self._notifications.pop()
  1041. self.get_scoped_token()
  1042. self.assertEqual(len(self._notifications), 1)
  1043. note = self._notifications.pop()
  1044. initiator = note['initiator']
  1045. self.assertEqual(self.user['name'], initiator.username)
  1046. def test_v3_authenticate_user_name_and_domain_id(self):
  1047. user_id = self.user_id
  1048. user_name = self.user['name']
  1049. password = self.user['password']
  1050. domain_id = self.domain_id
  1051. data = self.build_authentication_request(username=user_name,
  1052. user_domain_id=domain_id,
  1053. password=password)
  1054. self.post('/auth/tokens', body=data)
  1055. self._assert_last_note(self.ACTION, user_id)
  1056. def test_v3_authenticate_user_id(self):
  1057. user_id = self.user_id
  1058. password = self.user['password']
  1059. data = self.build_authentication_request(user_id=user_id,
  1060. password=password)
  1061. self.post('/auth/tokens', body=data)
  1062. self._assert_last_note(self.ACTION, user_id)
  1063. def test_v3_authenticate_with_invalid_user_id_sends_notification(self):
  1064. user_id = uuid.uuid4().hex
  1065. password = self.user['password']
  1066. data = self.build_authentication_request(user_id=user_id,
  1067. password=password)
  1068. self.post('/auth/tokens', body=data,
  1069. expected_status=http_client.UNAUTHORIZED)
  1070. note = self._get_last_note()
  1071. initiator = note['initiator']
  1072. # Confirm user-name specific event was emitted.
  1073. self.assertEqual(self.ACTION, note['action'])
  1074. self.assertEqual(user_id, initiator.user_id)
  1075. self.assertTrue(note['send_notification_called'])
  1076. self.assertEqual(cadftaxonomy.OUTCOME_FAILURE, note['event'].outcome)
  1077. self.assertEqual(self.LOCAL_HOST, initiator.host.address)
  1078. def test_v3_authenticate_with_invalid_user_name_sends_notification(self):
  1079. user_name = uuid.uuid4().hex
  1080. password = self.user['password']
  1081. domain_id = self.domain_id
  1082. data = self.build_authentication_request(username=user_name,
  1083. user_domain_id=domain_id,
  1084. password=password)
  1085. self.post('/auth/tokens', body=data,
  1086. expected_status=http_client.UNAUTHORIZED)
  1087. note = self._get_last_note()
  1088. initiator = note['initiator']
  1089. # Confirm user-name specific event was emitted.
  1090. self.assertEqual(self.ACTION, note['action'])
  1091. self.assertEqual(user_name, initiator.user_name)
  1092. self.assertEqual(domain_id, initiator.domain_id)
  1093. self.assertTrue(note['send_notification_called'])
  1094. self.assertEqual(cadftaxonomy.OUTCOME_FAILURE, note['event'].outcome)
  1095. self.assertEqual(self.LOCAL_HOST, initiator.host.address)
  1096. def test_v3_authenticate_user_name_and_domain_name(self):
  1097. user_id = self.user_id
  1098. user_name = self.user['name']
  1099. password = self.user['password']
  1100. domain_name = self.domain['name']
  1101. data = self.build_authentication_request(username=user_name,
  1102. user_domain_name=domain_name,
  1103. password=password)
  1104. self.post('/auth/tokens', body=data)
  1105. self._assert_last_note(self.ACTION, user_id)
  1106. def _test_role_assignment(self, url, role, project=None, domain=None,
  1107. user=None, group=None):
  1108. self.put(url)
  1109. action = "%s.%s" % (CREATED_OPERATION, self.ROLE_ASSIGNMENT)
  1110. event_type = '%s.%s.%s' % (notifications.SERVICE,
  1111. self.ROLE_ASSIGNMENT, CREATED_OPERATION)
  1112. self._assert_last_note(action, self.user_id, event_type)
  1113. self._assert_event(role, project, domain, user, group)
  1114. self.delete(url)
  1115. action = "%s.%s" % (DELETED_OPERATION, self.ROLE_ASSIGNMENT)
  1116. event_type = '%s.%s.%s' % (notifications.SERVICE,
  1117. self.ROLE_ASSIGNMENT, DELETED_OPERATION)
  1118. self._assert_last_note(action, self.user_id, event_type)
  1119. self._assert_event(role, project, domain, user, None)
  1120. def test_user_project_grant(self):
  1121. url = ('/projects/%s/users/%s/roles/%s' %
  1122. (self.project_id, self.user_id, self.role_id))
  1123. self._test_role_assignment(url, self.role_id,
  1124. project=self.project_id,
  1125. user=self.user_id)
  1126. def test_group_domain_grant(self):
  1127. group_ref = unit.new_group_ref(domain_id=self.domain_id)
  1128. group = PROVIDERS.identity_api.create_group(group_ref)
  1129. PROVIDERS.identity_api.add_user_to_group(self.user_id, group['id'])
  1130. url = ('/domains/%s/groups/%s/roles/%s' %
  1131. (self.domain_id, group['id'], self.role_id))
  1132. self._test_role_assignment(url, self.role_id,
  1133. domain=self.domain_id,
  1134. group=group['id'])
  1135. def test_add_role_to_user_and_project(self):
  1136. # A notification is sent when add_role_to_user_and_project is called on
  1137. # the assignment manager.
  1138. project_ref = unit.new_project_ref(self.domain_id)
  1139. project = PROVIDERS.resource_api.create_project(
  1140. project_ref['id'], project_ref)
  1141. project_id = project['id']
  1142. PROVIDERS.assignment_api.add_role_to_user_and_project(
  1143. self.user_id, project_id, self.role_id)
  1144. self.assertTrue(self._notifications)
  1145. note = self._notifications[-1]
  1146. self.assertEqual('created.role_assignment', note['action'])
  1147. self.assertTrue(note['send_notification_called'])
  1148. self._assert_event(self.role_id, project=project_id, user=self.user_id)
  1149. def test_remove_role_from_user_and_project(self):
  1150. # A notification is sent when remove_role_from_user_and_project is
  1151. # called on the assignment manager.
  1152. PROVIDERS.assignment_api.remove_role_from_user_and_project(
  1153. self.user_id, self.project_id, self.role_id)
  1154. self.assertTrue(self._notifications)
  1155. note = self._notifications[-1]
  1156. self.assertEqual('deleted.role_assignment', note['action'])
  1157. self.assertTrue(note['send_notification_called'])
  1158. self._assert_event(self.role_id, project=self.project_id,
  1159. user=self.user_id)
  1160. class TestCallbackRegistration(unit.BaseTestCase):
  1161. def setUp(self):
  1162. super(TestCallbackRegistration, self).setUp()
  1163. self.mock_log = mock.Mock()
  1164. # Force the callback logging to occur
  1165. self.mock_log.logger.getEffectiveLevel.return_value = log.DEBUG
  1166. def verify_log_message(self, data):
  1167. """Verify log message.
  1168. Tests that use this are a little brittle because adding more
  1169. logging can break them.
  1170. TODO(dstanek): remove the need for this in a future refactoring
  1171. """
  1172. log_fn = self.mock_log.debug
  1173. self.assertEqual(len(data), log_fn.call_count)
  1174. for datum in data:
  1175. log_fn.assert_any_call(mock.ANY, datum)
  1176. def test_a_function_callback(self):
  1177. def callback(*args, **kwargs):
  1178. pass
  1179. resource_type = 'thing'
  1180. with mock.patch('keystone.notifications.LOG', self.mock_log):
  1181. notifications.register_event_callback(
  1182. CREATED_OPERATION, resource_type, callback)
  1183. callback = 'keystone.tests.unit.common.test_notifications.callback'
  1184. expected_log_data = {
  1185. 'callback': callback,
  1186. 'event': 'identity.%s.created' % resource_type
  1187. }
  1188. self.verify_log_message([expected_log_data])
  1189. def test_a_method_callback(self):
  1190. class C(object):
  1191. def callback(self, *args, **kwargs):
  1192. pass
  1193. with mock.patch('keystone.notifications.LOG', self.mock_log):
  1194. notifications.register_event_callback(
  1195. CREATED_OPERATION, 'thing', C().callback)
  1196. callback = 'keystone.tests.unit.common.test_notifications.C.callback'
  1197. expected_log_data = {
  1198. 'callback': callback,
  1199. 'event': 'identity.thing.created'
  1200. }
  1201. self.verify_log_message([expected_log_data])
  1202. def test_a_list_of_callbacks(self):
  1203. def callback(*args, **kwargs):
  1204. pass
  1205. class C(object):
  1206. def callback(self, *args, **kwargs):
  1207. pass
  1208. with mock.patch('keystone.notifications.LOG', self.mock_log):
  1209. notifications.register_event_callback(
  1210. CREATED_OPERATION, 'thing', [callback, C().callback])
  1211. callback_1 = 'keystone.tests.unit.common.test_notifications.callback'
  1212. callback_2 = 'keystone.tests.unit.common.test_notifications.C.callback'
  1213. expected_log_data = [
  1214. {
  1215. 'callback': callback_1,
  1216. 'event': 'identity.thing.created'
  1217. },
  1218. {
  1219. 'callback': callback_2,
  1220. 'event': 'identity.thing.created'
  1221. },
  1222. ]
  1223. self.verify_log_message(expected_log_data)
  1224. def test_an_invalid_callback(self):
  1225. self.assertRaises(TypeError,
  1226. notifications.register_event_callback,
  1227. (CREATED_OPERATION, 'thing', object()))
  1228. def test_an_invalid_event(self):
  1229. def callback(*args, **kwargs):
  1230. pass
  1231. self.assertRaises(ValueError,
  1232. notifications.register_event_callback,
  1233. uuid.uuid4().hex,
  1234. 'thing',
  1235. callback)
  1236. class CADFNotificationsDataTestCase(test_v3.RestfulTestCase):
  1237. def config_overrides(self):
  1238. super(CADFNotificationsDataTestCase, self).config_overrides()
  1239. # NOTE(lbragstad): This is a workaround since oslo.messaging version
  1240. # 9.0.0 had a broken default for transport_url. This makes it so that
  1241. # we are able to use version 9.0.0 in tests because we are supplying
  1242. # an override to use a sane default (rabbit://). The problem is that
  1243. # we can't update the config fixture until we call
  1244. # get_notification_transport since that method registers the
  1245. # configuration options for oslo.messaging, which fails since there
  1246. # isn't a default value for transport_url with version 9.0.0. All the
  1247. # next line is doing is bypassing the broken default logic by supplying
  1248. # a dummy url, which allows the options to be registered. After that,
  1249. # we can actually update the configuration option to override the
  1250. # transport_url option that was just registered before proceeding with
  1251. # the test.
  1252. oslo_messaging.get_notification_transport(CONF, url='rabbit://')
  1253. self.config_fixture.config(
  1254. group='oslo_messaging_notifications', transport_url='rabbit://'
  1255. )
  1256. def test_receive_identityId_from_audit_notification(self):
  1257. observer = None
  1258. resource_type = EXP_RESOURCE_TYPE
  1259. ref = unit.new_service_ref()
  1260. ref['type'] = 'identity'
  1261. PROVIDERS.catalog_api.create_service(ref['id'], ref.copy())
  1262. action = CREATED_OPERATION + '.' + resource_type
  1263. initiator = notifications._get_request_audit_info(self.user_id)
  1264. target = cadfresource.Resource(typeURI=cadftaxonomy.ACCOUNT_USER)
  1265. outcome = 'success'
  1266. event_type = 'identity.authenticate.created'
  1267. with mock.patch.object(notifications._get_notifier(),
  1268. 'info') as mocked:
  1269. notifications._send_audit_notification(action,
  1270. initiator,
  1271. outcome,
  1272. target,
  1273. event_type)
  1274. for mock_args_list in mocked.call_args:
  1275. if len(mock_args_list) != 0:
  1276. for mock_args in mock_args_list:
  1277. if 'observer' in mock_args:
  1278. observer = mock_args['observer']
  1279. break
  1280. self.assertEqual(ref['id'], observer['id'])