blob: fa87158a01ffd25a9339154f3db92284990c88b8 [file] [log] [blame]
DavidPurcellb25f93d2017-01-27 12:46:27 -05001# Copyright 2017 AT&T Corporation.
DavidPurcell029d8c32017-01-06 15:27:41 -05002# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
Felipe Monteiro2fe986d2018-03-20 21:53:51 +000016import functools
Felipe Monteirob0595652017-01-23 16:51:58 -050017import logging
Felipe Monteiro8eda8cc2017-03-22 14:15:14 +000018import sys
19
Felipe Monteiro44d77842018-03-21 02:42:59 +000020from oslo_log import versionutils
Felipe Monteiro38f344b2017-11-03 12:59:15 +000021from oslo_utils import excutils
Felipe Monteiro8eda8cc2017-03-22 14:15:14 +000022import six
Felipe Monteirob0595652017-01-23 16:51:58 -050023
DavidPurcell029d8c32017-01-06 15:27:41 -050024from tempest import config
25from tempest.lib import exceptions
raiesmh088590c0c2017-03-14 18:06:52 +053026from tempest import test
DavidPurcell029d8c32017-01-06 15:27:41 -050027
Felipe Monteiro88a5bab2017-08-31 04:00:32 +010028from patrole_tempest_plugin import policy_authority
DavidPurcell029d8c32017-01-06 15:27:41 -050029from patrole_tempest_plugin import rbac_exceptions
Rick Bartraed950052017-06-29 17:20:33 -040030from patrole_tempest_plugin import requirements_authority
DavidPurcell029d8c32017-01-06 15:27:41 -050031
32CONF = config.CONF
33LOG = logging.getLogger(__name__)
34
Felipe Monteiro973a1bc2017-06-14 21:23:54 +010035_SUPPORTED_ERROR_CODES = [403, 404]
Cliff Parsons35a77112018-05-07 14:03:40 -050036_DEFAULT_ERROR_CODE = 403
Felipe Monteiro973a1bc2017-06-14 21:23:54 +010037
Sean Pryor7f8993f2017-08-14 12:53:17 -040038RBACLOG = logging.getLogger('rbac_reporting')
39
DavidPurcell029d8c32017-01-06 15:27:41 -050040
Cliff Parsons35a77112018-05-07 14:03:40 -050041def action(service, rule='', rules=None,
42 expected_error_code=_DEFAULT_ERROR_CODE, expected_error_codes=None,
Felipe Monteiro44d77842018-03-21 02:42:59 +000043 extra_target_data=None):
Felipe Monteirof2b58d72017-08-31 22:40:36 +010044 """A decorator for verifying OpenStack policy enforcement.
Felipe Monteirod5d76b82017-03-20 23:18:50 +000045
Felipe Monteiro01d633b2017-08-16 20:17:26 +010046 A decorator which allows for positive and negative RBAC testing. Given:
Rick Bartraed950052017-06-29 17:20:33 -040047
Masayuki Igawa80b9aab2018-01-09 17:00:45 +090048 * an OpenStack service,
49 * a policy action (``rule``) enforced by that service, and
50 * the test role defined by ``[patrole] rbac_test_role``
Felipe Monteirod5d76b82017-03-20 23:18:50 +000051
Felipe Monteiro01d633b2017-08-16 20:17:26 +010052 determines whether the test role has sufficient permissions to perform an
53 API call that enforces the ``rule``.
Felipe Monteirod5d76b82017-03-20 23:18:50 +000054
Felipe Monteiro01d633b2017-08-16 20:17:26 +010055 This decorator should only be applied to an instance or subclass of
Masayuki Igawa80b9aab2018-01-09 17:00:45 +090056 ``tempest.test.BaseTestCase``.
Felipe Monteiro01d633b2017-08-16 20:17:26 +010057
58 The result from ``_is_authorized`` is used to determine the *expected*
59 test result. The *actual* test result is determined by running the
60 Tempest test this decorator applies to.
61
62 Below are the following possibilities from comparing the *expected* and
63 *actual* results:
64
65 1) If *expected* is True and the test passes (*actual*), this is a success.
66 2) If *expected* is True and the test fails (*actual*), this results in a
67 `Forbidden` exception failure.
68 3) If *expected* is False and the test passes (*actual*), this results in
69 an `OverPermission` exception failure.
70 4) If *expected* is False and the test fails (*actual*), this is a success.
71
72 As such, negative and positive testing can be applied using this decorator.
73
Felipe Monteiro44d77842018-03-21 02:42:59 +000074 :param str service: An OpenStack service. Examples: "nova" or "neutron".
75 :param str rule: (DEPRECATED) A policy action defined in a policy.json file
76 or in code.
77 :param list rules: A list of policy actions defined in a policy.json file
78 or in code. The rules are logical-ANDed together to derive the expected
79 result.
Felipe Monteiro01d633b2017-08-16 20:17:26 +010080
81 .. note::
82
83 Patrole currently only supports custom JSON policy files.
84
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -040085 :param int expected_error_code: (DEPRECATED) Overrides default value of 403
86 (Forbidden) with endpoint-specific error code. Currently only supports
87 403 and 404. Support for 404 is needed because some services, like
88 Neutron, intentionally throw a 404 for security reasons.
Felipe Monteirod5d76b82017-03-20 23:18:50 +000089
Felipe Monteiro01d633b2017-08-16 20:17:26 +010090 .. warning::
91
92 A 404 should not be provided *unless* the endpoint masks a
Felipe Monteirof2b58d72017-08-31 22:40:36 +010093 ``Forbidden`` exception as a ``NotFound`` exception.
Felipe Monteiro01d633b2017-08-16 20:17:26 +010094
Cliff Parsons35a77112018-05-07 14:03:40 -050095 :param list expected_error_codes: When the ``rules`` list parameter is
96 used, then this list indicates the expected error code to use if one
97 of the rules does not allow the role being tested. This list must
98 coincide with and its elements remain in the same order as the rules
99 in the rules list.
100
101 Example::
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400102
Cliff Parsons35a77112018-05-07 14:03:40 -0500103 rules=["api_action1", "api_action2"]
104 expected_error_codes=[404, 403]
105
106 a) If api_action1 fails and api_action2 passes, then the expected
107 error code is 404.
108 b) if api_action2 fails and api_action1 passes, then the expected
109 error code is 403.
110 c) if both api_action1 and api_action2 fail, then the expected error
111 code is the first error seen (404).
112
113 If an error code is missing from the list, it is defaulted to 403.
114
Felipe Monteiro44d77842018-03-21 02:42:59 +0000115 :param dict extra_target_data: Dictionary, keyed with ``oslo.policy``
116 generic check names, whose values are string literals that reference
117 nested ``tempest.test.BaseTestCase`` attributes. Used by
118 ``oslo.policy`` for performing matching against attributes that are
119 sent along with the API calls. Example::
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100120
121 extra_target_data={
122 "target.token.user_id":
123 "os_alt.auth_provider.credentials.user_id"
124 })
125
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100126 :raises NotFound: If ``service`` is invalid.
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100127 :raises Forbidden: For item (2) above.
128 :raises RbacOverPermission: For item (3) above.
129
130 Examples::
131
132 @rbac_rule_validation.action(
133 service="nova", rule="os_compute_api:os-agents")
134 def test_list_agents_rbac(self):
Felipe Monteiro1c8620a2018-02-25 18:52:22 +0000135 # The call to `override_role` is mandatory.
136 with self.rbac_utils.override_role(self):
137 self.agents_client.list_agents()
Felipe Monteirod5d76b82017-03-20 23:18:50 +0000138 """
Felipe Monteiro0854ded2017-05-05 16:30:55 +0100139
140 if extra_target_data is None:
141 extra_target_data = {}
142
Cliff Parsons35a77112018-05-07 14:03:40 -0500143 rules, expected_error_codes = _prepare_multi_policy(rule, rules,
144 expected_error_code,
145 expected_error_codes)
Felipe Monteiro44d77842018-03-21 02:42:59 +0000146
Sean Pryor7f8993f2017-08-14 12:53:17 -0400147 def decorator(test_func):
Felipe Monteirof6eb8622017-08-06 06:08:02 +0100148 role = CONF.patrole.rbac_test_role
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100149
Felipe Monteiro2fe986d2018-03-20 21:53:51 +0000150 @functools.wraps(test_func)
DavidPurcell029d8c32017-01-06 15:27:41 -0500151 def wrapper(*args, **kwargs):
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100152 if args and isinstance(args[0], test.BaseTestCase):
153 test_obj = args[0]
154 else:
155 raise rbac_exceptions.RbacResourceSetupFailed(
156 '`rbac_rule_validation` decorator can only be applied to '
157 'an instance of `tempest.test.BaseTestCase`.')
raiesmh088590c0c2017-03-14 18:06:52 +0530158
Felipe Monteiro44d77842018-03-21 02:42:59 +0000159 allowed = True
160 disallowed_rules = []
161 for rule in rules:
162 _allowed = _is_authorized(
163 test_obj, service, rule, extra_target_data)
164 if not _allowed:
165 disallowed_rules.append(rule)
166 allowed = allowed and _allowed
Felipe Monteirod5d76b82017-03-20 23:18:50 +0000167
Cliff Parsons35a77112018-05-07 14:03:40 -0500168 exp_error_code = expected_error_code
169 if disallowed_rules:
170 # Choose the first disallowed rule and expect the error
171 # code corresponding to it.
172 first_error_index = rules.index(disallowed_rules[0])
173 exp_error_code = expected_error_codes[first_error_index]
174 LOG.debug("%s: Expecting %d to be raised for policy name: %s",
175 test_func.__name__, exp_error_code,
176 disallowed_rules[0])
177
Rick Bartra12998942017-03-17 17:35:45 -0400178 expected_exception, irregular_msg = _get_exception_type(
Cliff Parsons35a77112018-05-07 14:03:40 -0500179 exp_error_code)
DavidPurcell029d8c32017-01-06 15:27:41 -0500180
Sean Pryor7f8993f2017-08-14 12:53:17 -0400181 test_status = 'Allowed'
182
DavidPurcell029d8c32017-01-06 15:27:41 -0500183 try:
Sean Pryor7f8993f2017-08-14 12:53:17 -0400184 test_func(*args, **kwargs)
Rick Bartra503c5572017-03-09 13:49:58 -0500185 except rbac_exceptions.RbacInvalidService as e:
Felipe Monteiro48c913d2017-03-15 12:07:48 -0400186 msg = ("%s is not a valid service." % service)
Sean Pryor7f8993f2017-08-14 12:53:17 -0400187 test_status = ('Error, %s' % (msg))
Felipe Monteiro48c913d2017-03-15 12:07:48 -0400188 LOG.error(msg)
189 raise exceptions.NotFound(
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100190 "%s RbacInvalidService was: %s" % (msg, e))
Samantha Blanco36bea052017-07-19 12:01:59 -0400191 except (expected_exception,
192 rbac_exceptions.RbacConflictingPolicies,
193 rbac_exceptions.RbacMalformedResponse) as e:
Sean Pryor7f8993f2017-08-14 12:53:17 -0400194 test_status = 'Denied'
Felipe Monteiro8eda8cc2017-03-22 14:15:14 +0000195 if irregular_msg:
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400196 LOG.warning(irregular_msg,
197 test_func.__name__,
198 ', '.join(rules),
199 service)
DavidPurcell029d8c32017-01-06 15:27:41 -0500200 if allowed:
Felipe Monteiro44d77842018-03-21 02:42:59 +0000201 msg = ("Role %s was not allowed to perform the following "
202 "actions: %s. Expected allowed actions: %s. "
203 "Expected disallowed actions: %s." % (
204 role, sorted(rules),
205 sorted(set(rules) - set(disallowed_rules)),
206 sorted(disallowed_rules)))
DavidPurcell029d8c32017-01-06 15:27:41 -0500207 LOG.error(msg)
208 raise exceptions.Forbidden(
Felipe Monteiro4bf66a22017-05-07 14:44:21 +0100209 "%s Exception was: %s" % (msg, e))
Felipe Monteiro8eda8cc2017-03-22 14:15:14 +0000210 except Exception as e:
Felipe Monteiro38f344b2017-11-03 12:59:15 +0000211 with excutils.save_and_reraise_exception():
212 exc_info = sys.exc_info()
213 error_details = six.text_type(exc_info[1])
214 msg = ("An unexpected exception has occurred during test: "
215 "%s. Exception was: %s" % (test_func.__name__,
216 error_details))
217 test_status = 'Error, %s' % (error_details)
218 LOG.error(msg)
DavidPurcell029d8c32017-01-06 15:27:41 -0500219 else:
220 if not allowed:
Felipe Monteiro44d77842018-03-21 02:42:59 +0000221 msg = (
222 "OverPermission: Role %s was allowed to perform the "
223 "following disallowed actions: %s" % (
224 role, sorted(disallowed_rules)
225 )
226 )
227 LOG.error(msg)
228 raise rbac_exceptions.RbacOverPermission(msg)
raiesmh088590c0c2017-03-14 18:06:52 +0530229 finally:
Sean Pryor7f8993f2017-08-14 12:53:17 -0400230 if CONF.patrole_log.enable_reporting:
231 RBACLOG.info(
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400232 "[Service]: %s, [Test]: %s, [Rules]: %s, "
Sean Pryor7f8993f2017-08-14 12:53:17 -0400233 "[Expected]: %s, [Actual]: %s",
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400234 service, test_func.__name__, ', '.join(rules),
Sean Pryor7f8993f2017-08-14 12:53:17 -0400235 "Allowed" if allowed else "Denied",
236 test_status)
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100237
Felipe Monteiro2fe986d2018-03-20 21:53:51 +0000238 return wrapper
DavidPurcell029d8c32017-01-06 15:27:41 -0500239 return decorator
Rick Bartra12998942017-03-17 17:35:45 -0400240
241
Cliff Parsons35a77112018-05-07 14:03:40 -0500242def _prepare_multi_policy(rule, rules, exp_error_code, exp_error_codes):
243
244 if exp_error_codes:
245 if not rules:
246 msg = ("The `rules` list must be provided if using the "
247 "`expected_error_codes` list.")
248 raise ValueError(msg)
249 if len(rules) != len(exp_error_codes):
250 msg = ("The `expected_error_codes` list is not the same length "
251 "as the `rules` list.")
252 raise ValueError(msg)
253 if exp_error_code:
254 deprecation_msg = (
255 "The `exp_error_code` argument has been deprecated in favor "
256 "of `exp_error_codes` and will be removed in a future "
257 "version.")
258 versionutils.report_deprecated_feature(LOG, deprecation_msg)
259 LOG.debug("The `exp_error_codes` argument will be used instead of "
260 "`exp_error_code`.")
261 if not isinstance(exp_error_codes, (tuple, list)):
262 exp_error_codes = [exp_error_codes]
263 else:
264 exp_error_codes = []
265 if exp_error_code:
266 exp_error_codes.append(exp_error_code)
267
Felipe Monteiro44d77842018-03-21 02:42:59 +0000268 if rules is None:
269 rules = []
270 elif not isinstance(rules, (tuple, list)):
271 rules = [rules]
272 if rule:
273 deprecation_msg = (
274 "The `rule` argument has been deprecated in favor of `rules` "
275 "and will be removed in a future version.")
276 versionutils.report_deprecated_feature(LOG, deprecation_msg)
277 if rules:
278 LOG.debug("The `rules` argument will be used instead of `rule`.")
279 else:
280 rules.append(rule)
Cliff Parsons35a77112018-05-07 14:03:40 -0500281
282 # Fill in the exp_error_codes if needed. This is needed for the scenarios
283 # where no exp_error_codes array is provided, so the error codes must be
284 # set to the default error code value and there must be the same number
285 # of error codes as rules.
286 num_ecs = len(exp_error_codes)
287 num_rules = len(rules)
288 if (num_ecs < num_rules):
289 for i in range(num_rules - num_ecs):
290 exp_error_codes.append(_DEFAULT_ERROR_CODE)
291
292 return rules, exp_error_codes
Felipe Monteiro44d77842018-03-21 02:42:59 +0000293
294
Felipe Monteiro318a0bf2018-02-27 06:57:10 -0500295def _is_authorized(test_obj, service, rule, extra_target_data):
Felipe Monteirodea13842017-07-05 04:11:18 +0100296 """Validates whether current RBAC role has permission to do policy action.
297
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100298 :param test_obj: An instance or subclass of ``tempest.test.BaseTestCase``.
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100299 :param service: The OpenStack service that enforces ``rule``.
300 :param rule: The name of the policy action. Examples include
301 "identity:create_user" or "os_compute_api:os-agents".
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100302 :param extra_target_data: Dictionary, keyed with ``oslo.policy`` generic
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100303 check names, whose values are string literals that reference nested
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100304 ``tempest.test.BaseTestCase`` attributes. Used by ``oslo.policy`` for
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100305 performing matching against attributes that are sent along with the API
306 calls.
Sean Pryor7f8993f2017-08-14 12:53:17 -0400307
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100308 :returns: True if the current RBAC role can perform the policy action,
309 else False.
Sean Pryor7f8993f2017-08-14 12:53:17 -0400310
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100311 :raises RbacResourceSetupFailed: If `project_id` or `user_id` are missing
312 from the `auth_provider` attribute in `test_obj`.
Felipe Monteirodea13842017-07-05 04:11:18 +0100313 """
Sean Pryor7f8993f2017-08-14 12:53:17 -0400314
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100315 try:
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100316 project_id = test_obj.os_primary.credentials.project_id
317 user_id = test_obj.os_primary.credentials.user_id
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100318 except AttributeError as e:
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100319 msg = ("{0}: project_id or user_id not found in os_primary.credentials"
320 .format(e))
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100321 LOG.error(msg)
322 raise rbac_exceptions.RbacResourceSetupFailed(msg)
323
Felipe Monteiro4ef7e532018-03-11 07:17:11 -0400324 role = CONF.patrole.rbac_test_role
325 # Test RBAC against custom requirements. Otherwise use oslo.policy.
326 if CONF.patrole.test_custom_requirements:
327 authority = requirements_authority.RequirementsAuthority(
328 CONF.patrole.custom_requirements_file, service)
329 else:
330 formatted_target_data = _format_extra_target_data(
331 test_obj, extra_target_data)
332 authority = policy_authority.PolicyAuthority(
333 project_id, user_id, service,
334 extra_target_data=formatted_target_data)
335 is_allowed = authority.allowed(rule, role)
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100336
Felipe Monteiro4ef7e532018-03-11 07:17:11 -0400337 if is_allowed:
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400338 LOG.debug("[Policy action]: %s, [Role]: %s is allowed!", rule,
Felipe Monteiro4ef7e532018-03-11 07:17:11 -0400339 role)
340 else:
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400341 LOG.debug("[Policy action]: %s, [Role]: %s is NOT allowed!",
Felipe Monteiro4ef7e532018-03-11 07:17:11 -0400342 rule, role)
343
344 return is_allowed
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100345
346
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400347def _get_exception_type(expected_error_code=_DEFAULT_ERROR_CODE):
Felipe Monteiro973a1bc2017-06-14 21:23:54 +0100348 """Dynamically calculate the expected exception to be caught.
349
350 Dynamically calculate the expected exception to be caught by the test case.
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100351 Only ``Forbidden`` and ``NotFound`` exceptions are permitted. ``NotFound``
352 is supported because Neutron, for security reasons, masks ``Forbidden``
353 exceptions as ``NotFound`` exceptions.
Felipe Monteiro973a1bc2017-06-14 21:23:54 +0100354
355 :param expected_error_code: the integer representation of the expected
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100356 exception to be caught. Must be contained in
357 ``_SUPPORTED_ERROR_CODES``.
Felipe Monteiro973a1bc2017-06-14 21:23:54 +0100358 :returns: tuple of the exception type corresponding to
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100359 ``expected_error_code`` and a message explaining that a non-Forbidden
Felipe Monteiro973a1bc2017-06-14 21:23:54 +0100360 exception was expected, if applicable.
361 """
Rick Bartra12998942017-03-17 17:35:45 -0400362 expected_exception = None
363 irregular_msg = None
Felipe Monteiro973a1bc2017-06-14 21:23:54 +0100364
365 if not isinstance(expected_error_code, six.integer_types) \
Sean Pryor7f8993f2017-08-14 12:53:17 -0400366 or expected_error_code not in _SUPPORTED_ERROR_CODES:
Felipe Monteiro973a1bc2017-06-14 21:23:54 +0100367 msg = ("Please pass an expected error code. Currently "
368 "supported codes: {0}".format(_SUPPORTED_ERROR_CODES))
369 LOG.error(msg)
370 raise rbac_exceptions.RbacInvalidErrorCode(msg)
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100371
Rick Bartra12998942017-03-17 17:35:45 -0400372 if expected_error_code == 403:
373 expected_exception = exceptions.Forbidden
374 elif expected_error_code == 404:
375 expected_exception = exceptions.NotFound
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400376 irregular_msg = ("NotFound exception was caught for test %s. Expected "
377 "policies which may have caused the error: %s. The "
378 "service %s throws a 404 instead of a 403, which is "
379 "irregular.")
Rick Bartra12998942017-03-17 17:35:45 -0400380
381 return expected_exception, irregular_msg
Felipe Monteirofd1db982017-04-13 21:19:41 +0100382
383
384def _format_extra_target_data(test_obj, extra_target_data):
385 """Formats the "extra_target_data" dictionary with correct test data.
386
387 Before being formatted, "extra_target_data" is a dictionary that maps a
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100388 policy string like "trust.trustor_user_id" to a nested list of
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100389 ``tempest.test.BaseTestCase`` attributes. For example, the attribute list
Masayuki Igawa80b9aab2018-01-09 17:00:45 +0900390 in::
Felipe Monteirofd1db982017-04-13 21:19:41 +0100391
Masayuki Igawa80b9aab2018-01-09 17:00:45 +0900392 "trust.trustor_user_id": "os.auth_provider.credentials.user_id"
Felipe Monteirofd1db982017-04-13 21:19:41 +0100393
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100394 is parsed by iteratively calling ``getattr`` until the value of "user_id"
Masayuki Igawa80b9aab2018-01-09 17:00:45 +0900395 is resolved. The resulting dictionary returns::
Felipe Monteirofd1db982017-04-13 21:19:41 +0100396
Masayuki Igawa80b9aab2018-01-09 17:00:45 +0900397 "trust.trustor_user_id": "the user_id of the `os_primary` credential"
Felipe Monteirofd1db982017-04-13 21:19:41 +0100398
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100399 :param test_obj: An instance or subclass of ``tempest.test.BaseTestCase``.
400 :param extra_target_data: Dictionary, keyed with ``oslo.policy`` generic
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100401 check names, whose values are string literals that reference nested
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100402 ``tempest.test.BaseTestCase`` attributes. Used by ``oslo.policy`` for
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100403 performing matching against attributes that are sent along with the API
404 calls.
405 :returns: Dictionary containing additional object data needed by
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100406 ``oslo.policy`` to validate generic checks.
Felipe Monteirofd1db982017-04-13 21:19:41 +0100407 """
408 attr_value = test_obj
409 formatted_target_data = {}
410
411 for user_attribute, attr_string in extra_target_data.items():
412 attrs = attr_string.split('.')
413 for attr in attrs:
414 attr_value = getattr(attr_value, attr)
415 formatted_target_data[user_attribute] = attr_value
416
417 return formatted_target_data