blob: 575e2c318c4a391ab9a0b538eadbf5d3775c86df [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
Mykola Yakovlieve0f35502018-09-26 18:26:57 -050020from 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
Felipe Monteiro51299a12018-06-28 20:03:27 -040025from tempest.lib import exceptions as lib_exc
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
Chi Lo8c04bd82018-06-01 16:21:50 -050041def action(service,
Chi Lo8c04bd82018-06-01 16:21:50 -050042 rules=None,
Chi Lo8c04bd82018-06-01 16:21:50 -050043 expected_error_codes=None,
Felipe Monteiro44d77842018-03-21 02:42:59 +000044 extra_target_data=None):
Felipe Monteirof2b58d72017-08-31 22:40:36 +010045 """A decorator for verifying OpenStack policy enforcement.
Felipe Monteirod5d76b82017-03-20 23:18:50 +000046
Felipe Monteiro01d633b2017-08-16 20:17:26 +010047 A decorator which allows for positive and negative RBAC testing. Given:
Rick Bartraed950052017-06-29 17:20:33 -040048
Masayuki Igawa80b9aab2018-01-09 17:00:45 +090049 * an OpenStack service,
50 * a policy action (``rule``) enforced by that service, and
Mykola Yakovlieve0f35502018-09-26 18:26:57 -050051 * the test roles defined by ``[patrole] rbac_test_roles``
Felipe Monteirod5d76b82017-03-20 23:18:50 +000052
Felipe Monteiro01d633b2017-08-16 20:17:26 +010053 determines whether the test role has sufficient permissions to perform an
54 API call that enforces the ``rule``.
Felipe Monteirod5d76b82017-03-20 23:18:50 +000055
Felipe Monteiro01d633b2017-08-16 20:17:26 +010056 This decorator should only be applied to an instance or subclass of
Masayuki Igawa80b9aab2018-01-09 17:00:45 +090057 ``tempest.test.BaseTestCase``.
Felipe Monteiro01d633b2017-08-16 20:17:26 +010058
59 The result from ``_is_authorized`` is used to determine the *expected*
60 test result. The *actual* test result is determined by running the
61 Tempest test this decorator applies to.
62
63 Below are the following possibilities from comparing the *expected* and
64 *actual* results:
65
66 1) If *expected* is True and the test passes (*actual*), this is a success.
67 2) If *expected* is True and the test fails (*actual*), this results in a
Felipe Monteirof16b6b32018-06-28 19:32:59 -040068 ``RbacUnderPermissionException`` exception failure.
Felipe Monteiro01d633b2017-08-16 20:17:26 +010069 3) If *expected* is False and the test passes (*actual*), this results in
Felipe Monteirof16b6b32018-06-28 19:32:59 -040070 an ``RbacOverPermissionException`` exception failure.
Felipe Monteiro01d633b2017-08-16 20:17:26 +010071 4) If *expected* is False and the test fails (*actual*), this is a success.
72
73 As such, negative and positive testing can be applied using this decorator.
74
Felipe Monteiro44d77842018-03-21 02:42:59 +000075 :param str service: An OpenStack service. Examples: "nova" or "neutron".
Felipe Monteiro59f538f2018-08-22 23:34:40 -040076 :param list rules: A list of policy actions defined in a policy file or in
77 code. The rules are logical-ANDed together to derive the expected
Chi Lo8c04bd82018-06-01 16:21:50 -050078 result. Also accepts list of callables that return a policy action.
Felipe Monteiro01d633b2017-08-16 20:17:26 +010079
80 .. note::
81
82 Patrole currently only supports custom JSON policy files.
83
Chi Lo8c04bd82018-06-01 16:21:50 -050084 :type rules: list[str] or list[callable]
Cliff Parsons35a77112018-05-07 14:03:40 -050085 :param list expected_error_codes: When the ``rules`` list parameter is
86 used, then this list indicates the expected error code to use if one
87 of the rules does not allow the role being tested. This list must
88 coincide with and its elements remain in the same order as the rules
89 in the rules list.
90
91 Example::
Felipe Monteiro318fa3b2018-06-19 16:53:33 -040092
Cliff Parsons35a77112018-05-07 14:03:40 -050093 rules=["api_action1", "api_action2"]
94 expected_error_codes=[404, 403]
95
96 a) If api_action1 fails and api_action2 passes, then the expected
97 error code is 404.
98 b) if api_action2 fails and api_action1 passes, then the expected
99 error code is 403.
100 c) if both api_action1 and api_action2 fail, then the expected error
101 code is the first error seen (404).
102
ghanshyam98437d42018-08-17 08:51:43 +0000103 If it is not passed, then it is defaulted to 403.
Cliff Parsons35a77112018-05-07 14:03:40 -0500104
Felipe Monteiro59f538f2018-08-22 23:34:40 -0400105 .. warning::
106
107 A 404 should not be provided *unless* the endpoint masks a
108 ``Forbidden`` exception as a ``NotFound`` exception.
109
110 :type expected_error_codes: list[int]
Felipe Monteiro44d77842018-03-21 02:42:59 +0000111 :param dict extra_target_data: Dictionary, keyed with ``oslo.policy``
112 generic check names, whose values are string literals that reference
113 nested ``tempest.test.BaseTestCase`` attributes. Used by
114 ``oslo.policy`` for performing matching against attributes that are
115 sent along with the API calls. Example::
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100116
117 extra_target_data={
118 "target.token.user_id":
119 "os_alt.auth_provider.credentials.user_id"
120 })
121
Felipe Monteiro51299a12018-06-28 20:03:27 -0400122 :raises RbacInvalidServiceException: If ``service`` is invalid.
Felipe Monteirof16b6b32018-06-28 19:32:59 -0400123 :raises RbacUnderPermissionException: For item (2) above.
124 :raises RbacOverPermissionException: For item (3) above.
125 :raises RbacExpectedWrongException: When a 403 is expected but a 404
126 is raised instead or vice versa.
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100127
128 Examples::
129
130 @rbac_rule_validation.action(
Felipe Monteiro59f538f2018-08-22 23:34:40 -0400131 service="nova",
132 rules=["os_compute_api:os-agents"])
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100133 def test_list_agents_rbac(self):
Felipe Monteiro1c8620a2018-02-25 18:52:22 +0000134 # The call to `override_role` is mandatory.
135 with self.rbac_utils.override_role(self):
136 self.agents_client.list_agents()
Felipe Monteirod5d76b82017-03-20 23:18:50 +0000137 """
Felipe Monteiro0854ded2017-05-05 16:30:55 +0100138
139 if extra_target_data is None:
140 extra_target_data = {}
141
Felipe Monteiro59f538f2018-08-22 23:34:40 -0400142 rules, expected_error_codes = _prepare_multi_policy(rules,
Cliff Parsons35a77112018-05-07 14:03:40 -0500143 expected_error_codes)
Felipe Monteiro44d77842018-03-21 02:42:59 +0000144
Sean Pryor7f8993f2017-08-14 12:53:17 -0400145 def decorator(test_func):
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500146 roles = CONF.patrole.rbac_test_roles
147 # TODO(vegasq) drop once CONF.patrole.rbac_test_role is removed
148 if CONF.patrole.rbac_test_role:
149 msg = ('CONF.patrole.rbac_test_role is deprecated in favor of '
150 'CONF.patrole.rbac_test_roles and will be removed in '
151 'future.')
152 versionutils.report_deprecated_feature(LOG, msg)
153 if not roles:
154 roles.append(CONF.patrole.rbac_test_role)
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100155
Felipe Monteiro2fe986d2018-03-20 21:53:51 +0000156 @functools.wraps(test_func)
DavidPurcell029d8c32017-01-06 15:27:41 -0500157 def wrapper(*args, **kwargs):
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100158 if args and isinstance(args[0], test.BaseTestCase):
159 test_obj = args[0]
160 else:
161 raise rbac_exceptions.RbacResourceSetupFailed(
162 '`rbac_rule_validation` decorator can only be applied to '
163 'an instance of `tempest.test.BaseTestCase`.')
raiesmh088590c0c2017-03-14 18:06:52 +0530164
Felipe Monteiro44d77842018-03-21 02:42:59 +0000165 allowed = True
166 disallowed_rules = []
167 for rule in rules:
168 _allowed = _is_authorized(
169 test_obj, service, rule, extra_target_data)
170 if not _allowed:
171 disallowed_rules.append(rule)
172 allowed = allowed and _allowed
Felipe Monteirod5d76b82017-03-20 23:18:50 +0000173
Cliff Parsons35a77112018-05-07 14:03:40 -0500174 if disallowed_rules:
175 # Choose the first disallowed rule and expect the error
176 # code corresponding to it.
177 first_error_index = rules.index(disallowed_rules[0])
178 exp_error_code = expected_error_codes[first_error_index]
179 LOG.debug("%s: Expecting %d to be raised for policy name: %s",
180 test_func.__name__, exp_error_code,
181 disallowed_rules[0])
Felipe Monteiro59f538f2018-08-22 23:34:40 -0400182 else:
183 exp_error_code = expected_error_codes[0]
Cliff Parsons35a77112018-05-07 14:03:40 -0500184
Rick Bartra12998942017-03-17 17:35:45 -0400185 expected_exception, irregular_msg = _get_exception_type(
Cliff Parsons35a77112018-05-07 14:03:40 -0500186 exp_error_code)
DavidPurcell029d8c32017-01-06 15:27:41 -0500187
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500188 caught_exception = None
Sean Pryor7f8993f2017-08-14 12:53:17 -0400189 test_status = 'Allowed'
190
DavidPurcell029d8c32017-01-06 15:27:41 -0500191 try:
Sean Pryor7f8993f2017-08-14 12:53:17 -0400192 test_func(*args, **kwargs)
Felipe Monteiro51299a12018-06-28 20:03:27 -0400193 except rbac_exceptions.RbacInvalidServiceException:
194 with excutils.save_and_reraise_exception():
195 msg = ("%s is not a valid service." % service)
196 # FIXME(felipemonteiro): This test_status is logged too
197 # late. Need a function to log it before re-raising.
198 test_status = ('Error, %s' % (msg))
199 LOG.error(msg)
Samantha Blanco36bea052017-07-19 12:01:59 -0400200 except (expected_exception,
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500201 rbac_exceptions.RbacMalformedResponse) as actual_exception:
202 caught_exception = actual_exception
Sean Pryor7f8993f2017-08-14 12:53:17 -0400203 test_status = 'Denied'
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500204
Felipe Monteiro8eda8cc2017-03-22 14:15:14 +0000205 if irregular_msg:
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400206 LOG.warning(irregular_msg,
207 test_func.__name__,
208 ', '.join(rules),
209 service)
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500210
DavidPurcell029d8c32017-01-06 15:27:41 -0500211 if allowed:
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500212 msg = ("User with roles %s was not allowed to perform the "
213 "following actions: %s. Expected allowed actions: "
214 "%s. Expected disallowed actions: %s." % (
215 roles, sorted(rules),
Felipe Monteiro44d77842018-03-21 02:42:59 +0000216 sorted(set(rules) - set(disallowed_rules)),
217 sorted(disallowed_rules)))
DavidPurcell029d8c32017-01-06 15:27:41 -0500218 LOG.error(msg)
Felipe Monteirof16b6b32018-06-28 19:32:59 -0400219 raise rbac_exceptions.RbacUnderPermissionException(
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500220 "%s Exception was: %s" % (msg, actual_exception))
Felipe Monteirof16b6b32018-06-28 19:32:59 -0400221 except Exception as actual_exception:
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500222 caught_exception = actual_exception
223
Felipe Monteirof16b6b32018-06-28 19:32:59 -0400224 if _check_for_expected_mismatch_exception(expected_exception,
225 actual_exception):
226 LOG.error('Expected and actual exceptions do not match. '
227 'Expected: %s. Actual: %s.',
228 expected_exception,
229 actual_exception.__class__)
230 raise rbac_exceptions.RbacExpectedWrongException(
231 expected=expected_exception,
232 actual=actual_exception.__class__,
233 exception=actual_exception)
234 else:
235 with excutils.save_and_reraise_exception():
236 exc_info = sys.exc_info()
237 error_details = six.text_type(exc_info[1])
238 msg = ("An unexpected exception has occurred during "
239 "test: %s. Exception was: %s" % (
240 test_func.__name__, error_details))
241 test_status = 'Error, %s' % (error_details)
242 LOG.error(msg)
DavidPurcell029d8c32017-01-06 15:27:41 -0500243 else:
244 if not allowed:
Felipe Monteiro44d77842018-03-21 02:42:59 +0000245 msg = (
246 "OverPermission: Role %s was allowed to perform the "
247 "following disallowed actions: %s" % (
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500248 roles, sorted(disallowed_rules)
Felipe Monteiro44d77842018-03-21 02:42:59 +0000249 )
250 )
251 LOG.error(msg)
Felipe Monteirof16b6b32018-06-28 19:32:59 -0400252 raise rbac_exceptions.RbacOverPermissionException(msg)
raiesmh088590c0c2017-03-14 18:06:52 +0530253 finally:
Sean Pryor7f8993f2017-08-14 12:53:17 -0400254 if CONF.patrole_log.enable_reporting:
255 RBACLOG.info(
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400256 "[Service]: %s, [Test]: %s, [Rules]: %s, "
Sean Pryor7f8993f2017-08-14 12:53:17 -0400257 "[Expected]: %s, [Actual]: %s",
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400258 service, test_func.__name__, ', '.join(rules),
Sean Pryor7f8993f2017-08-14 12:53:17 -0400259 "Allowed" if allowed else "Denied",
260 test_status)
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100261
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500262 # Sanity-check that ``override_role`` was called to eliminate
263 # false-positives and bad test flows resulting from exceptions
264 # getting raised too early, too late or not at all, within
265 # the scope of an RBAC test.
266 _validate_override_role_called(
267 test_obj,
268 actual_exception=caught_exception)
269
Felipe Monteiro2fe986d2018-03-20 21:53:51 +0000270 return wrapper
DavidPurcell029d8c32017-01-06 15:27:41 -0500271 return decorator
Rick Bartra12998942017-03-17 17:35:45 -0400272
273
Felipe Monteiro59f538f2018-08-22 23:34:40 -0400274def _prepare_multi_policy(rules, exp_error_codes):
Cliff Parsons35a77112018-05-07 14:03:40 -0500275 if exp_error_codes:
276 if not rules:
277 msg = ("The `rules` list must be provided if using the "
278 "`expected_error_codes` list.")
279 raise ValueError(msg)
280 if len(rules) != len(exp_error_codes):
281 msg = ("The `expected_error_codes` list is not the same length "
282 "as the `rules` list.")
283 raise ValueError(msg)
Cliff Parsons35a77112018-05-07 14:03:40 -0500284 if not isinstance(exp_error_codes, (tuple, list)):
285 exp_error_codes = [exp_error_codes]
286 else:
287 exp_error_codes = []
Cliff Parsons35a77112018-05-07 14:03:40 -0500288
Felipe Monteiro44d77842018-03-21 02:42:59 +0000289 if rules is None:
290 rules = []
291 elif not isinstance(rules, (tuple, list)):
292 rules = [rules]
Cliff Parsons35a77112018-05-07 14:03:40 -0500293
294 # Fill in the exp_error_codes if needed. This is needed for the scenarios
295 # where no exp_error_codes array is provided, so the error codes must be
296 # set to the default error code value and there must be the same number
297 # of error codes as rules.
298 num_ecs = len(exp_error_codes)
299 num_rules = len(rules)
300 if (num_ecs < num_rules):
301 for i in range(num_rules - num_ecs):
302 exp_error_codes.append(_DEFAULT_ERROR_CODE)
303
Chi Lo8c04bd82018-06-01 16:21:50 -0500304 evaluated_rules = [
305 r() if callable(r) else r for r in rules
306 ]
307
308 return evaluated_rules, exp_error_codes
Felipe Monteiro44d77842018-03-21 02:42:59 +0000309
310
Felipe Monteiro318a0bf2018-02-27 06:57:10 -0500311def _is_authorized(test_obj, service, rule, extra_target_data):
Felipe Monteirodea13842017-07-05 04:11:18 +0100312 """Validates whether current RBAC role has permission to do policy action.
313
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100314 :param test_obj: An instance or subclass of ``tempest.test.BaseTestCase``.
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100315 :param service: The OpenStack service that enforces ``rule``.
316 :param rule: The name of the policy action. Examples include
317 "identity:create_user" or "os_compute_api:os-agents".
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100318 :param extra_target_data: Dictionary, keyed with ``oslo.policy`` generic
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100319 check names, whose values are string literals that reference nested
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100320 ``tempest.test.BaseTestCase`` attributes. Used by ``oslo.policy`` for
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100321 performing matching against attributes that are sent along with the API
322 calls.
Sean Pryor7f8993f2017-08-14 12:53:17 -0400323
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100324 :returns: True if the current RBAC role can perform the policy action,
325 else False.
Sean Pryor7f8993f2017-08-14 12:53:17 -0400326
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100327 :raises RbacResourceSetupFailed: If `project_id` or `user_id` are missing
328 from the `auth_provider` attribute in `test_obj`.
Felipe Monteirodea13842017-07-05 04:11:18 +0100329 """
Sean Pryor7f8993f2017-08-14 12:53:17 -0400330
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100331 try:
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100332 project_id = test_obj.os_primary.credentials.project_id
333 user_id = test_obj.os_primary.credentials.user_id
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100334 except AttributeError as e:
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100335 msg = ("{0}: project_id or user_id not found in os_primary.credentials"
336 .format(e))
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100337 LOG.error(msg)
338 raise rbac_exceptions.RbacResourceSetupFailed(msg)
339
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500340 roles = CONF.patrole.rbac_test_roles
341 # TODO(vegasq) drop once CONF.patrole.rbac_test_role is removed
342 if CONF.patrole.rbac_test_role:
343 if not roles:
344 roles.append(CONF.patrole.rbac_test_role)
345
Felipe Monteiro4ef7e532018-03-11 07:17:11 -0400346 # Test RBAC against custom requirements. Otherwise use oslo.policy.
347 if CONF.patrole.test_custom_requirements:
348 authority = requirements_authority.RequirementsAuthority(
349 CONF.patrole.custom_requirements_file, service)
350 else:
351 formatted_target_data = _format_extra_target_data(
352 test_obj, extra_target_data)
353 authority = policy_authority.PolicyAuthority(
354 project_id, user_id, service,
355 extra_target_data=formatted_target_data)
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500356 is_allowed = authority.allowed(rule, roles)
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100357
Felipe Monteiro4ef7e532018-03-11 07:17:11 -0400358 if is_allowed:
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400359 LOG.debug("[Policy action]: %s, [Role]: %s is allowed!", rule,
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500360 roles)
Felipe Monteiro4ef7e532018-03-11 07:17:11 -0400361 else:
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400362 LOG.debug("[Policy action]: %s, [Role]: %s is NOT allowed!",
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500363 rule, roles)
Felipe Monteiro4ef7e532018-03-11 07:17:11 -0400364
365 return is_allowed
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100366
367
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400368def _get_exception_type(expected_error_code=_DEFAULT_ERROR_CODE):
Felipe Monteiro973a1bc2017-06-14 21:23:54 +0100369 """Dynamically calculate the expected exception to be caught.
370
371 Dynamically calculate the expected exception to be caught by the test case.
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100372 Only ``Forbidden`` and ``NotFound`` exceptions are permitted. ``NotFound``
373 is supported because Neutron, for security reasons, masks ``Forbidden``
374 exceptions as ``NotFound`` exceptions.
Felipe Monteiro973a1bc2017-06-14 21:23:54 +0100375
376 :param expected_error_code: the integer representation of the expected
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100377 exception to be caught. Must be contained in
378 ``_SUPPORTED_ERROR_CODES``.
Felipe Monteiro973a1bc2017-06-14 21:23:54 +0100379 :returns: tuple of the exception type corresponding to
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100380 ``expected_error_code`` and a message explaining that a non-Forbidden
Felipe Monteiro973a1bc2017-06-14 21:23:54 +0100381 exception was expected, if applicable.
382 """
Rick Bartra12998942017-03-17 17:35:45 -0400383 expected_exception = None
384 irregular_msg = None
Felipe Monteiro973a1bc2017-06-14 21:23:54 +0100385
386 if not isinstance(expected_error_code, six.integer_types) \
Sean Pryor7f8993f2017-08-14 12:53:17 -0400387 or expected_error_code not in _SUPPORTED_ERROR_CODES:
Felipe Monteiro973a1bc2017-06-14 21:23:54 +0100388 msg = ("Please pass an expected error code. Currently "
389 "supported codes: {0}".format(_SUPPORTED_ERROR_CODES))
390 LOG.error(msg)
391 raise rbac_exceptions.RbacInvalidErrorCode(msg)
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100392
Rick Bartra12998942017-03-17 17:35:45 -0400393 if expected_error_code == 403:
Felipe Monteiro51299a12018-06-28 20:03:27 -0400394 expected_exception = lib_exc.Forbidden
Rick Bartra12998942017-03-17 17:35:45 -0400395 elif expected_error_code == 404:
Felipe Monteiro51299a12018-06-28 20:03:27 -0400396 expected_exception = lib_exc.NotFound
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400397 irregular_msg = ("NotFound exception was caught for test %s. Expected "
398 "policies which may have caused the error: %s. The "
399 "service %s throws a 404 instead of a 403, which is "
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500400 "irregular")
Rick Bartra12998942017-03-17 17:35:45 -0400401 return expected_exception, irregular_msg
Felipe Monteirofd1db982017-04-13 21:19:41 +0100402
403
404def _format_extra_target_data(test_obj, extra_target_data):
405 """Formats the "extra_target_data" dictionary with correct test data.
406
407 Before being formatted, "extra_target_data" is a dictionary that maps a
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100408 policy string like "trust.trustor_user_id" to a nested list of
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100409 ``tempest.test.BaseTestCase`` attributes. For example, the attribute list
Masayuki Igawa80b9aab2018-01-09 17:00:45 +0900410 in::
Felipe Monteirofd1db982017-04-13 21:19:41 +0100411
Masayuki Igawa80b9aab2018-01-09 17:00:45 +0900412 "trust.trustor_user_id": "os.auth_provider.credentials.user_id"
Felipe Monteirofd1db982017-04-13 21:19:41 +0100413
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100414 is parsed by iteratively calling ``getattr`` until the value of "user_id"
Masayuki Igawa80b9aab2018-01-09 17:00:45 +0900415 is resolved. The resulting dictionary returns::
Felipe Monteirofd1db982017-04-13 21:19:41 +0100416
Masayuki Igawa80b9aab2018-01-09 17:00:45 +0900417 "trust.trustor_user_id": "the user_id of the `os_primary` credential"
Felipe Monteirofd1db982017-04-13 21:19:41 +0100418
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100419 :param test_obj: An instance or subclass of ``tempest.test.BaseTestCase``.
420 :param extra_target_data: Dictionary, keyed with ``oslo.policy`` generic
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100421 check names, whose values are string literals that reference nested
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100422 ``tempest.test.BaseTestCase`` attributes. Used by ``oslo.policy`` for
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100423 performing matching against attributes that are sent along with the API
424 calls.
425 :returns: Dictionary containing additional object data needed by
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100426 ``oslo.policy`` to validate generic checks.
Felipe Monteirofd1db982017-04-13 21:19:41 +0100427 """
428 attr_value = test_obj
429 formatted_target_data = {}
430
431 for user_attribute, attr_string in extra_target_data.items():
432 attrs = attr_string.split('.')
433 for attr in attrs:
434 attr_value = getattr(attr_value, attr)
435 formatted_target_data[user_attribute] = attr_value
436
437 return formatted_target_data
Felipe Monteirof16b6b32018-06-28 19:32:59 -0400438
439
440def _check_for_expected_mismatch_exception(expected_exception,
441 actual_exception):
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500442 """Checks that ``expected_exception`` matches ``actual_exception``.
443
444 Since Patrole must handle 403/404 it is important that the expected and
445 actual error codes match.
446
447 :param excepted_exception: Expected exception for test.
448 :param actual_exception: Actual exception raised by test.
449 :returns: True if match, else False.
450 :rtype: boolean
451 """
Felipe Monteiro51299a12018-06-28 20:03:27 -0400452 permission_exceptions = (lib_exc.Forbidden, lib_exc.NotFound)
Felipe Monteirof16b6b32018-06-28 19:32:59 -0400453 if isinstance(actual_exception, permission_exceptions):
454 if not isinstance(actual_exception, expected_exception.__class__):
455 return True
456 return False
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500457
458
459def _validate_override_role_called(test_obj, actual_exception):
460 """Validates that :func:`rbac_utils.RbacUtils.override_role` is called
461 during each Patrole test.
462
463 Useful for validating that the expected exception isn't raised too early
464 (before ``override_role`` call) or too late (after ``override_call``) or
465 at all (which is a bad test).
466
467 :param test_obj: An instance or subclass of ``tempest.test.BaseTestCase``.
468 :param actual_exception: Actual exception raised by test.
469 :raises RbacOverrideRoleException: If ``override_role`` isn't called, is
470 called too early, or is called too late.
471 """
472 called = test_obj._validate_override_role_called()
473 base_msg = ('This error is unrelated to RBAC and is due to either '
474 'an API or override role failure. Exception: %s' %
475 actual_exception)
476
477 if not called:
478 if actual_exception is not None:
479 msg = ('Caught exception (%s) but it was raised before the '
480 '`override_role` context. ' % actual_exception.__class__)
481 else:
482 msg = 'Test missing required `override_role` call. '
483 msg += base_msg
484 LOG.error(msg)
485 raise rbac_exceptions.RbacOverrideRoleException(msg)
486 else:
487 exc_caught_in_ctx = test_obj._validate_override_role_caught_exc()
488 # This block is only executed if ``override_role`` is called. If
489 # an exception is raised and the exception wasn't raised in the
490 # ``override_role`` context and if the exception isn't a valid
491 # exception type (instance of ``BasePatroleException``), then this is
492 # a legitimate error.
493 if (not exc_caught_in_ctx and
494 actual_exception is not None and
495 not isinstance(actual_exception,
496 rbac_exceptions.BasePatroleException)):
497 msg = ('Caught exception (%s) but it was raised after the '
498 '`override_role` context. ' % actual_exception.__class__)
499 msg += base_msg
500 LOG.error(msg)
501 raise rbac_exceptions.RbacOverrideRoleException(msg)