blob: 0cb6f787be1733d29b0305a48706ab2e0c46aab1 [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 Monteiro38f344b2017-11-03 12:59:15 +000020from oslo_utils import excutils
Felipe Monteiro8eda8cc2017-03-22 14:15:14 +000021import six
Felipe Monteirob0595652017-01-23 16:51:58 -050022
DavidPurcell029d8c32017-01-06 15:27:41 -050023from tempest import config
Felipe Monteiro51299a12018-06-28 20:03:27 -040024from tempest.lib import exceptions as lib_exc
raiesmh088590c0c2017-03-14 18:06:52 +053025from tempest import test
DavidPurcell029d8c32017-01-06 15:27:41 -050026
Felipe Monteiro88a5bab2017-08-31 04:00:32 +010027from patrole_tempest_plugin import policy_authority
DavidPurcell029d8c32017-01-06 15:27:41 -050028from patrole_tempest_plugin import rbac_exceptions
Rick Bartraed950052017-06-29 17:20:33 -040029from patrole_tempest_plugin import requirements_authority
DavidPurcell029d8c32017-01-06 15:27:41 -050030
31CONF = config.CONF
32LOG = logging.getLogger(__name__)
33
Felipe Monteiro973a1bc2017-06-14 21:23:54 +010034_SUPPORTED_ERROR_CODES = [403, 404]
Cliff Parsons35a77112018-05-07 14:03:40 -050035_DEFAULT_ERROR_CODE = 403
Felipe Monteiro973a1bc2017-06-14 21:23:54 +010036
Sean Pryor7f8993f2017-08-14 12:53:17 -040037RBACLOG = logging.getLogger('rbac_reporting')
38
DavidPurcell029d8c32017-01-06 15:27:41 -050039
Chi Lo8c04bd82018-06-01 16:21:50 -050040def action(service,
Chi Lo8c04bd82018-06-01 16:21:50 -050041 rules=None,
Chi Lo8c04bd82018-06-01 16:21:50 -050042 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
Felipe Monteirof16b6b32018-06-28 19:32:59 -040067 ``RbacUnderPermissionException`` exception failure.
Felipe Monteiro01d633b2017-08-16 20:17:26 +010068 3) If *expected* is False and the test passes (*actual*), this results in
Felipe Monteirof16b6b32018-06-28 19:32:59 -040069 an ``RbacOverPermissionException`` exception failure.
Felipe Monteiro01d633b2017-08-16 20:17:26 +010070 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".
Felipe Monteiro59f538f2018-08-22 23:34:40 -040075 :param list rules: A list of policy actions defined in a policy file or in
76 code. The rules are logical-ANDed together to derive the expected
Chi Lo8c04bd82018-06-01 16:21:50 -050077 result. Also accepts list of callables that return a policy action.
Felipe Monteiro01d633b2017-08-16 20:17:26 +010078
79 .. note::
80
81 Patrole currently only supports custom JSON policy files.
82
Chi Lo8c04bd82018-06-01 16:21:50 -050083 :type rules: list[str] or list[callable]
Cliff Parsons35a77112018-05-07 14:03:40 -050084 :param list expected_error_codes: When the ``rules`` list parameter is
85 used, then this list indicates the expected error code to use if one
86 of the rules does not allow the role being tested. This list must
87 coincide with and its elements remain in the same order as the rules
88 in the rules list.
89
90 Example::
Felipe Monteiro318fa3b2018-06-19 16:53:33 -040091
Cliff Parsons35a77112018-05-07 14:03:40 -050092 rules=["api_action1", "api_action2"]
93 expected_error_codes=[404, 403]
94
95 a) If api_action1 fails and api_action2 passes, then the expected
96 error code is 404.
97 b) if api_action2 fails and api_action1 passes, then the expected
98 error code is 403.
99 c) if both api_action1 and api_action2 fail, then the expected error
100 code is the first error seen (404).
101
ghanshyam98437d42018-08-17 08:51:43 +0000102 If it is not passed, then it is defaulted to 403.
Cliff Parsons35a77112018-05-07 14:03:40 -0500103
Felipe Monteiro59f538f2018-08-22 23:34:40 -0400104 .. warning::
105
106 A 404 should not be provided *unless* the endpoint masks a
107 ``Forbidden`` exception as a ``NotFound`` exception.
108
109 :type expected_error_codes: list[int]
Felipe Monteiro44d77842018-03-21 02:42:59 +0000110 :param dict extra_target_data: Dictionary, keyed with ``oslo.policy``
111 generic check names, whose values are string literals that reference
112 nested ``tempest.test.BaseTestCase`` attributes. Used by
113 ``oslo.policy`` for performing matching against attributes that are
114 sent along with the API calls. Example::
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100115
116 extra_target_data={
117 "target.token.user_id":
118 "os_alt.auth_provider.credentials.user_id"
119 })
120
Felipe Monteiro51299a12018-06-28 20:03:27 -0400121 :raises RbacInvalidServiceException: If ``service`` is invalid.
Felipe Monteirof16b6b32018-06-28 19:32:59 -0400122 :raises RbacUnderPermissionException: For item (2) above.
123 :raises RbacOverPermissionException: For item (3) above.
124 :raises RbacExpectedWrongException: When a 403 is expected but a 404
125 is raised instead or vice versa.
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100126
127 Examples::
128
129 @rbac_rule_validation.action(
Felipe Monteiro59f538f2018-08-22 23:34:40 -0400130 service="nova",
131 rules=["os_compute_api:os-agents"])
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100132 def test_list_agents_rbac(self):
Felipe Monteiro1c8620a2018-02-25 18:52:22 +0000133 # The call to `override_role` is mandatory.
134 with self.rbac_utils.override_role(self):
135 self.agents_client.list_agents()
Felipe Monteirod5d76b82017-03-20 23:18:50 +0000136 """
Felipe Monteiro0854ded2017-05-05 16:30:55 +0100137
138 if extra_target_data is None:
139 extra_target_data = {}
140
Felipe Monteiro59f538f2018-08-22 23:34:40 -0400141 rules, expected_error_codes = _prepare_multi_policy(rules,
Cliff Parsons35a77112018-05-07 14:03:40 -0500142 expected_error_codes)
Felipe Monteiro44d77842018-03-21 02:42:59 +0000143
Sean Pryor7f8993f2017-08-14 12:53:17 -0400144 def decorator(test_func):
Felipe Monteirof6eb8622017-08-06 06:08:02 +0100145 role = CONF.patrole.rbac_test_role
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100146
Felipe Monteiro2fe986d2018-03-20 21:53:51 +0000147 @functools.wraps(test_func)
DavidPurcell029d8c32017-01-06 15:27:41 -0500148 def wrapper(*args, **kwargs):
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100149 if args and isinstance(args[0], test.BaseTestCase):
150 test_obj = args[0]
151 else:
152 raise rbac_exceptions.RbacResourceSetupFailed(
153 '`rbac_rule_validation` decorator can only be applied to '
154 'an instance of `tempest.test.BaseTestCase`.')
raiesmh088590c0c2017-03-14 18:06:52 +0530155
Felipe Monteiro44d77842018-03-21 02:42:59 +0000156 allowed = True
157 disallowed_rules = []
158 for rule in rules:
159 _allowed = _is_authorized(
160 test_obj, service, rule, extra_target_data)
161 if not _allowed:
162 disallowed_rules.append(rule)
163 allowed = allowed and _allowed
Felipe Monteirod5d76b82017-03-20 23:18:50 +0000164
Cliff Parsons35a77112018-05-07 14:03:40 -0500165 if disallowed_rules:
166 # Choose the first disallowed rule and expect the error
167 # code corresponding to it.
168 first_error_index = rules.index(disallowed_rules[0])
169 exp_error_code = expected_error_codes[first_error_index]
170 LOG.debug("%s: Expecting %d to be raised for policy name: %s",
171 test_func.__name__, exp_error_code,
172 disallowed_rules[0])
Felipe Monteiro59f538f2018-08-22 23:34:40 -0400173 else:
174 exp_error_code = expected_error_codes[0]
Cliff Parsons35a77112018-05-07 14:03:40 -0500175
Rick Bartra12998942017-03-17 17:35:45 -0400176 expected_exception, irregular_msg = _get_exception_type(
Cliff Parsons35a77112018-05-07 14:03:40 -0500177 exp_error_code)
DavidPurcell029d8c32017-01-06 15:27:41 -0500178
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500179 caught_exception = None
Sean Pryor7f8993f2017-08-14 12:53:17 -0400180 test_status = 'Allowed'
181
DavidPurcell029d8c32017-01-06 15:27:41 -0500182 try:
Sean Pryor7f8993f2017-08-14 12:53:17 -0400183 test_func(*args, **kwargs)
Felipe Monteiro51299a12018-06-28 20:03:27 -0400184 except rbac_exceptions.RbacInvalidServiceException:
185 with excutils.save_and_reraise_exception():
186 msg = ("%s is not a valid service." % service)
187 # FIXME(felipemonteiro): This test_status is logged too
188 # late. Need a function to log it before re-raising.
189 test_status = ('Error, %s' % (msg))
190 LOG.error(msg)
Samantha Blanco36bea052017-07-19 12:01:59 -0400191 except (expected_exception,
192 rbac_exceptions.RbacConflictingPolicies,
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500193 rbac_exceptions.RbacMalformedResponse) as actual_exception:
194 caught_exception = actual_exception
Sean Pryor7f8993f2017-08-14 12:53:17 -0400195 test_status = 'Denied'
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500196
Felipe Monteiro8eda8cc2017-03-22 14:15:14 +0000197 if irregular_msg:
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400198 LOG.warning(irregular_msg,
199 test_func.__name__,
200 ', '.join(rules),
201 service)
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500202
DavidPurcell029d8c32017-01-06 15:27:41 -0500203 if allowed:
Felipe Monteiro44d77842018-03-21 02:42:59 +0000204 msg = ("Role %s was not allowed to perform the following "
205 "actions: %s. Expected allowed actions: %s. "
206 "Expected disallowed actions: %s." % (
207 role, sorted(rules),
208 sorted(set(rules) - set(disallowed_rules)),
209 sorted(disallowed_rules)))
DavidPurcell029d8c32017-01-06 15:27:41 -0500210 LOG.error(msg)
Felipe Monteirof16b6b32018-06-28 19:32:59 -0400211 raise rbac_exceptions.RbacUnderPermissionException(
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500212 "%s Exception was: %s" % (msg, actual_exception))
Felipe Monteirof16b6b32018-06-28 19:32:59 -0400213 except Exception as actual_exception:
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500214 caught_exception = actual_exception
215
Felipe Monteirof16b6b32018-06-28 19:32:59 -0400216 if _check_for_expected_mismatch_exception(expected_exception,
217 actual_exception):
218 LOG.error('Expected and actual exceptions do not match. '
219 'Expected: %s. Actual: %s.',
220 expected_exception,
221 actual_exception.__class__)
222 raise rbac_exceptions.RbacExpectedWrongException(
223 expected=expected_exception,
224 actual=actual_exception.__class__,
225 exception=actual_exception)
226 else:
227 with excutils.save_and_reraise_exception():
228 exc_info = sys.exc_info()
229 error_details = six.text_type(exc_info[1])
230 msg = ("An unexpected exception has occurred during "
231 "test: %s. Exception was: %s" % (
232 test_func.__name__, error_details))
233 test_status = 'Error, %s' % (error_details)
234 LOG.error(msg)
DavidPurcell029d8c32017-01-06 15:27:41 -0500235 else:
236 if not allowed:
Felipe Monteiro44d77842018-03-21 02:42:59 +0000237 msg = (
238 "OverPermission: Role %s was allowed to perform the "
239 "following disallowed actions: %s" % (
240 role, sorted(disallowed_rules)
241 )
242 )
243 LOG.error(msg)
Felipe Monteirof16b6b32018-06-28 19:32:59 -0400244 raise rbac_exceptions.RbacOverPermissionException(msg)
raiesmh088590c0c2017-03-14 18:06:52 +0530245 finally:
Sean Pryor7f8993f2017-08-14 12:53:17 -0400246 if CONF.patrole_log.enable_reporting:
247 RBACLOG.info(
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400248 "[Service]: %s, [Test]: %s, [Rules]: %s, "
Sean Pryor7f8993f2017-08-14 12:53:17 -0400249 "[Expected]: %s, [Actual]: %s",
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400250 service, test_func.__name__, ', '.join(rules),
Sean Pryor7f8993f2017-08-14 12:53:17 -0400251 "Allowed" if allowed else "Denied",
252 test_status)
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100253
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500254 # Sanity-check that ``override_role`` was called to eliminate
255 # false-positives and bad test flows resulting from exceptions
256 # getting raised too early, too late or not at all, within
257 # the scope of an RBAC test.
258 _validate_override_role_called(
259 test_obj,
260 actual_exception=caught_exception)
261
Felipe Monteiro2fe986d2018-03-20 21:53:51 +0000262 return wrapper
DavidPurcell029d8c32017-01-06 15:27:41 -0500263 return decorator
Rick Bartra12998942017-03-17 17:35:45 -0400264
265
Felipe Monteiro59f538f2018-08-22 23:34:40 -0400266def _prepare_multi_policy(rules, exp_error_codes):
Cliff Parsons35a77112018-05-07 14:03:40 -0500267 if exp_error_codes:
268 if not rules:
269 msg = ("The `rules` list must be provided if using the "
270 "`expected_error_codes` list.")
271 raise ValueError(msg)
272 if len(rules) != len(exp_error_codes):
273 msg = ("The `expected_error_codes` list is not the same length "
274 "as the `rules` list.")
275 raise ValueError(msg)
Cliff Parsons35a77112018-05-07 14:03:40 -0500276 if not isinstance(exp_error_codes, (tuple, list)):
277 exp_error_codes = [exp_error_codes]
278 else:
279 exp_error_codes = []
Cliff Parsons35a77112018-05-07 14:03:40 -0500280
Felipe Monteiro44d77842018-03-21 02:42:59 +0000281 if rules is None:
282 rules = []
283 elif not isinstance(rules, (tuple, list)):
284 rules = [rules]
Cliff Parsons35a77112018-05-07 14:03:40 -0500285
286 # Fill in the exp_error_codes if needed. This is needed for the scenarios
287 # where no exp_error_codes array is provided, so the error codes must be
288 # set to the default error code value and there must be the same number
289 # of error codes as rules.
290 num_ecs = len(exp_error_codes)
291 num_rules = len(rules)
292 if (num_ecs < num_rules):
293 for i in range(num_rules - num_ecs):
294 exp_error_codes.append(_DEFAULT_ERROR_CODE)
295
Chi Lo8c04bd82018-06-01 16:21:50 -0500296 evaluated_rules = [
297 r() if callable(r) else r for r in rules
298 ]
299
300 return evaluated_rules, exp_error_codes
Felipe Monteiro44d77842018-03-21 02:42:59 +0000301
302
Felipe Monteiro318a0bf2018-02-27 06:57:10 -0500303def _is_authorized(test_obj, service, rule, extra_target_data):
Felipe Monteirodea13842017-07-05 04:11:18 +0100304 """Validates whether current RBAC role has permission to do policy action.
305
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100306 :param test_obj: An instance or subclass of ``tempest.test.BaseTestCase``.
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100307 :param service: The OpenStack service that enforces ``rule``.
308 :param rule: The name of the policy action. Examples include
309 "identity:create_user" or "os_compute_api:os-agents".
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100310 :param extra_target_data: Dictionary, keyed with ``oslo.policy`` generic
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100311 check names, whose values are string literals that reference nested
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100312 ``tempest.test.BaseTestCase`` attributes. Used by ``oslo.policy`` for
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100313 performing matching against attributes that are sent along with the API
314 calls.
Sean Pryor7f8993f2017-08-14 12:53:17 -0400315
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100316 :returns: True if the current RBAC role can perform the policy action,
317 else False.
Sean Pryor7f8993f2017-08-14 12:53:17 -0400318
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100319 :raises RbacResourceSetupFailed: If `project_id` or `user_id` are missing
320 from the `auth_provider` attribute in `test_obj`.
Felipe Monteirodea13842017-07-05 04:11:18 +0100321 """
Sean Pryor7f8993f2017-08-14 12:53:17 -0400322
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100323 try:
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100324 project_id = test_obj.os_primary.credentials.project_id
325 user_id = test_obj.os_primary.credentials.user_id
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100326 except AttributeError as e:
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100327 msg = ("{0}: project_id or user_id not found in os_primary.credentials"
328 .format(e))
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100329 LOG.error(msg)
330 raise rbac_exceptions.RbacResourceSetupFailed(msg)
331
Felipe Monteiro4ef7e532018-03-11 07:17:11 -0400332 role = CONF.patrole.rbac_test_role
333 # Test RBAC against custom requirements. Otherwise use oslo.policy.
334 if CONF.patrole.test_custom_requirements:
335 authority = requirements_authority.RequirementsAuthority(
336 CONF.patrole.custom_requirements_file, service)
337 else:
338 formatted_target_data = _format_extra_target_data(
339 test_obj, extra_target_data)
340 authority = policy_authority.PolicyAuthority(
341 project_id, user_id, service,
342 extra_target_data=formatted_target_data)
343 is_allowed = authority.allowed(rule, role)
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100344
Felipe Monteiro4ef7e532018-03-11 07:17:11 -0400345 if is_allowed:
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400346 LOG.debug("[Policy action]: %s, [Role]: %s is allowed!", rule,
Felipe Monteiro4ef7e532018-03-11 07:17:11 -0400347 role)
348 else:
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400349 LOG.debug("[Policy action]: %s, [Role]: %s is NOT allowed!",
Felipe Monteiro4ef7e532018-03-11 07:17:11 -0400350 rule, role)
351
352 return is_allowed
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100353
354
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400355def _get_exception_type(expected_error_code=_DEFAULT_ERROR_CODE):
Felipe Monteiro973a1bc2017-06-14 21:23:54 +0100356 """Dynamically calculate the expected exception to be caught.
357
358 Dynamically calculate the expected exception to be caught by the test case.
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100359 Only ``Forbidden`` and ``NotFound`` exceptions are permitted. ``NotFound``
360 is supported because Neutron, for security reasons, masks ``Forbidden``
361 exceptions as ``NotFound`` exceptions.
Felipe Monteiro973a1bc2017-06-14 21:23:54 +0100362
363 :param expected_error_code: the integer representation of the expected
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100364 exception to be caught. Must be contained in
365 ``_SUPPORTED_ERROR_CODES``.
Felipe Monteiro973a1bc2017-06-14 21:23:54 +0100366 :returns: tuple of the exception type corresponding to
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100367 ``expected_error_code`` and a message explaining that a non-Forbidden
Felipe Monteiro973a1bc2017-06-14 21:23:54 +0100368 exception was expected, if applicable.
369 """
Rick Bartra12998942017-03-17 17:35:45 -0400370 expected_exception = None
371 irregular_msg = None
Felipe Monteiro973a1bc2017-06-14 21:23:54 +0100372
373 if not isinstance(expected_error_code, six.integer_types) \
Sean Pryor7f8993f2017-08-14 12:53:17 -0400374 or expected_error_code not in _SUPPORTED_ERROR_CODES:
Felipe Monteiro973a1bc2017-06-14 21:23:54 +0100375 msg = ("Please pass an expected error code. Currently "
376 "supported codes: {0}".format(_SUPPORTED_ERROR_CODES))
377 LOG.error(msg)
378 raise rbac_exceptions.RbacInvalidErrorCode(msg)
Felipe Monteiro78fc4892017-04-12 21:33:39 +0100379
Rick Bartra12998942017-03-17 17:35:45 -0400380 if expected_error_code == 403:
Felipe Monteiro51299a12018-06-28 20:03:27 -0400381 expected_exception = lib_exc.Forbidden
Rick Bartra12998942017-03-17 17:35:45 -0400382 elif expected_error_code == 404:
Felipe Monteiro51299a12018-06-28 20:03:27 -0400383 expected_exception = lib_exc.NotFound
Felipe Monteiroc0cb7eb2018-06-19 19:50:36 -0400384 irregular_msg = ("NotFound exception was caught for test %s. Expected "
385 "policies which may have caused the error: %s. The "
386 "service %s throws a 404 instead of a 403, which is "
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500387 "irregular")
Rick Bartra12998942017-03-17 17:35:45 -0400388 return expected_exception, irregular_msg
Felipe Monteirofd1db982017-04-13 21:19:41 +0100389
390
391def _format_extra_target_data(test_obj, extra_target_data):
392 """Formats the "extra_target_data" dictionary with correct test data.
393
394 Before being formatted, "extra_target_data" is a dictionary that maps a
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100395 policy string like "trust.trustor_user_id" to a nested list of
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100396 ``tempest.test.BaseTestCase`` attributes. For example, the attribute list
Masayuki Igawa80b9aab2018-01-09 17:00:45 +0900397 in::
Felipe Monteirofd1db982017-04-13 21:19:41 +0100398
Masayuki Igawa80b9aab2018-01-09 17:00:45 +0900399 "trust.trustor_user_id": "os.auth_provider.credentials.user_id"
Felipe Monteirofd1db982017-04-13 21:19:41 +0100400
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100401 is parsed by iteratively calling ``getattr`` until the value of "user_id"
Masayuki Igawa80b9aab2018-01-09 17:00:45 +0900402 is resolved. The resulting dictionary returns::
Felipe Monteirofd1db982017-04-13 21:19:41 +0100403
Masayuki Igawa80b9aab2018-01-09 17:00:45 +0900404 "trust.trustor_user_id": "the user_id of the `os_primary` credential"
Felipe Monteirofd1db982017-04-13 21:19:41 +0100405
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100406 :param test_obj: An instance or subclass of ``tempest.test.BaseTestCase``.
407 :param extra_target_data: Dictionary, keyed with ``oslo.policy`` generic
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100408 check names, whose values are string literals that reference nested
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100409 ``tempest.test.BaseTestCase`` attributes. Used by ``oslo.policy`` for
Felipe Monteiro01d633b2017-08-16 20:17:26 +0100410 performing matching against attributes that are sent along with the API
411 calls.
412 :returns: Dictionary containing additional object data needed by
Felipe Monteirof2b58d72017-08-31 22:40:36 +0100413 ``oslo.policy`` to validate generic checks.
Felipe Monteirofd1db982017-04-13 21:19:41 +0100414 """
415 attr_value = test_obj
416 formatted_target_data = {}
417
418 for user_attribute, attr_string in extra_target_data.items():
419 attrs = attr_string.split('.')
420 for attr in attrs:
421 attr_value = getattr(attr_value, attr)
422 formatted_target_data[user_attribute] = attr_value
423
424 return formatted_target_data
Felipe Monteirof16b6b32018-06-28 19:32:59 -0400425
426
427def _check_for_expected_mismatch_exception(expected_exception,
428 actual_exception):
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500429 """Checks that ``expected_exception`` matches ``actual_exception``.
430
431 Since Patrole must handle 403/404 it is important that the expected and
432 actual error codes match.
433
434 :param excepted_exception: Expected exception for test.
435 :param actual_exception: Actual exception raised by test.
436 :returns: True if match, else False.
437 :rtype: boolean
438 """
Felipe Monteiro51299a12018-06-28 20:03:27 -0400439 permission_exceptions = (lib_exc.Forbidden, lib_exc.NotFound)
Felipe Monteirof16b6b32018-06-28 19:32:59 -0400440 if isinstance(actual_exception, permission_exceptions):
441 if not isinstance(actual_exception, expected_exception.__class__):
442 return True
443 return False
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500444
445
446def _validate_override_role_called(test_obj, actual_exception):
447 """Validates that :func:`rbac_utils.RbacUtils.override_role` is called
448 during each Patrole test.
449
450 Useful for validating that the expected exception isn't raised too early
451 (before ``override_role`` call) or too late (after ``override_call``) or
452 at all (which is a bad test).
453
454 :param test_obj: An instance or subclass of ``tempest.test.BaseTestCase``.
455 :param actual_exception: Actual exception raised by test.
456 :raises RbacOverrideRoleException: If ``override_role`` isn't called, is
457 called too early, or is called too late.
458 """
459 called = test_obj._validate_override_role_called()
460 base_msg = ('This error is unrelated to RBAC and is due to either '
461 'an API or override role failure. Exception: %s' %
462 actual_exception)
463
464 if not called:
465 if actual_exception is not None:
466 msg = ('Caught exception (%s) but it was raised before the '
467 '`override_role` context. ' % actual_exception.__class__)
468 else:
469 msg = 'Test missing required `override_role` call. '
470 msg += base_msg
471 LOG.error(msg)
472 raise rbac_exceptions.RbacOverrideRoleException(msg)
473 else:
474 exc_caught_in_ctx = test_obj._validate_override_role_caught_exc()
475 # This block is only executed if ``override_role`` is called. If
476 # an exception is raised and the exception wasn't raised in the
477 # ``override_role`` context and if the exception isn't a valid
478 # exception type (instance of ``BasePatroleException``), then this is
479 # a legitimate error.
480 if (not exc_caught_in_ctx and
481 actual_exception is not None and
482 not isinstance(actual_exception,
483 rbac_exceptions.BasePatroleException)):
484 msg = ('Caught exception (%s) but it was raised after the '
485 '`override_role` context. ' % actual_exception.__class__)
486 msg += base_msg
487 LOG.error(msg)
488 raise rbac_exceptions.RbacOverrideRoleException(msg)