blob: ed65a740d0a6c0e521ed5a04db6b899535f0bcde [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
Sergey Vilgelmbab9e942018-10-11 14:04:48 -050016import contextlib
Mykola Yakovliev11376ab2018-08-06 15:34:22 -050017import sys
DavidPurcell029d8c32017-01-06 15:27:41 -050018import time
Felipe Monteiro34a138c2017-03-02 17:01:37 -050019
Rajiv Kumar645dfc92017-01-19 13:48:27 +053020from oslo_log import log as logging
Felipe Monteiro2693bf72017-08-12 22:56:47 +010021from oslo_utils import excutils
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010022
23from tempest import config
Felipe Monteirobf524fb2018-10-03 09:03:35 -050024from tempest.lib import exceptions as lib_exc
DavidPurcell029d8c32017-01-06 15:27:41 -050025
Felipe Monteiro34a138c2017-03-02 17:01:37 -050026from patrole_tempest_plugin import rbac_exceptions
DavidPurcell029d8c32017-01-06 15:27:41 -050027
DavidPurcell029d8c32017-01-06 15:27:41 -050028CONF = config.CONF
Felipe Monteiro34a138c2017-03-02 17:01:37 -050029LOG = logging.getLogger(__name__)
DavidPurcell029d8c32017-01-06 15:27:41 -050030
31
Sergey Vilgelmbab9e942018-10-11 14:04:48 -050032class _ValidateListContext(object):
33 """Context class responsible for validation of the list functions.
34
35 This class is used in ``override_role_and_validate_list`` function and
36 the result of a list function must be assigned to the ``ctx.resources``
37 variable.
38
39 Example::
40
Sergey Vilgelmd3d77ef2019-02-02 09:34:52 -060041 with self.override_role_and_validate_list(...) as ctx:
Sergey Vilgelmbab9e942018-10-11 14:04:48 -050042 ctx.resources = list_function()
43
44 """
45 def __init__(self, admin_resources=None, admin_resource_id=None):
46 """Constructor for ``ValidateListContext``.
47
48 Either ``admin_resources`` or ``admin_resource_id`` should be used,
49 not both.
50
51 :param list admin_resources: The list of resources received before
52 calling the ``override_role_and_validate_list`` function. To
53 validate will be used the ``_validate_len`` function.
54 :param UUID admin_resource_id: An ID of a resource created before
55 calling the ``override_role_and_validate_list`` function. To
56 validate will be used the ``_validate_resource`` function.
57 :raises RbacValidateListException: if both ``admin_resources`` and
58 ``admin_resource_id`` are set or unset.
59 """
60 self.resources = None
61 if admin_resources is not None and not admin_resource_id:
62 self._admin_len = len(admin_resources)
63 if not self._admin_len:
64 raise rbac_exceptions.RbacValidateListException(
65 reason="the list of admin resources cannot be empty")
66 self._validate_func = self._validate_len
67 elif admin_resource_id and admin_resources is None:
68 self._admin_resource_id = admin_resource_id
69 self._validate_func = self._validate_resource
70 else:
71 raise rbac_exceptions.RbacValidateListException(
72 reason="admin_resources and admin_resource_id are mutually "
73 "exclusive")
74
75 def _validate_len(self):
76 """Validates that the number of resources is less than admin resources.
77 """
78 if not len(self.resources):
79 raise rbac_exceptions.RbacEmptyResponseBody()
80 elif self._admin_len > len(self.resources):
81 raise rbac_exceptions.RbacPartialResponseBody(body=self.resources)
82
83 def _validate_resource(self):
84 """Validates that the admin resource is present in the resources.
85 """
86 for resource in self.resources:
87 if resource['id'] == self._admin_resource_id:
88 return
89 raise rbac_exceptions.RbacPartialResponseBody(body=self.resources)
90
91 def _validate(self):
92 """Calls the proper validation function.
93
94 :raises RbacValidateListException: if the ``ctx.resources`` variable is
95 not assigned.
96 """
97 if self.resources is None:
98 raise rbac_exceptions.RbacValidateListException(
99 reason="ctx.resources is not assigned")
100 self._validate_func()
101
102
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600103class RbacUtilsMixin(object):
104 """Utility mixin responsible for switching ``os_primary`` role.
105
106 Should be used as a mixin class alongside an instance of
107 :py:class:`tempest.test.BaseTestCase` to perform Patrole class setup for a
108 base RBAC class. Child classes should not use this mixin.
109
110 Example::
111
112 class BaseRbacTest(rbac_utils.RbacUtilsMixin, base.BaseV2ComputeTest):
113
114 @classmethod
115 def setup_clients(cls):
116 super(BaseRbacTest, cls).setup_clients()
117
118 cls.hosts_client = cls.os_primary.hosts_client
119 ...
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100120
121 This class is responsible for overriding the value of the primary Tempest
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000122 credential's role (i.e. ``os_primary`` role). By doing so, it is possible
123 to seamlessly swap between admin credentials, needed for setup and clean
124 up, and primary credentials, needed to perform the API call which does
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100125 policy enforcement. The primary credentials always cycle between roles
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000126 defined by ``CONF.identity.admin_role`` and
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500127 ``CONF.patrole.rbac_test_roles``.
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100128 """
Lingxian Kong27f671f2020-12-30 21:23:03 +1300129 credentials = ['primary', 'admin']
DavidPurcell029d8c32017-01-06 15:27:41 -0500130
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600131 def __init__(self, *args, **kwargs):
132 super(RbacUtilsMixin, self).__init__(*args, **kwargs)
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100133
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600134 # Shows if override_role was called.
135 self.__override_role_called = False
136 # Shows if exception raised during override_role.
137 self.__override_role_caught_exc = False
Sergey Vilgelm19e3bec2019-01-07 11:59:41 -0600138
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600139 _admin_role_id = None
140 _rbac_role_ids = None
141 _project_id = None
142 _user_id = None
143 _role_map = None
144 _role_inferences_mapping = None
Lingxian Kongb73e1082021-01-13 15:04:39 +1300145 _orig_roles = []
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600146
147 admin_roles_client = None
148
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600149 @classmethod
Lingxian Kongb73e1082021-01-13 15:04:39 +1300150 def restore_roles(cls):
151 if cls._orig_roles:
152 LOG.info("Restoring original roles %s", cls._orig_roles)
153 roles_already_present = cls._list_and_clear_user_roles_on_project(
154 cls._orig_roles)
155
156 if not roles_already_present:
157 cls._create_user_role_on_project(cls._orig_roles)
158
159 @classmethod
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600160 def setup_clients(cls):
Felipe Monteirobf524fb2018-10-03 09:03:35 -0500161 if CONF.identity_feature_enabled.api_v3:
Lingxian Kong27f671f2020-12-30 21:23:03 +1300162 admin_roles_client = cls.os_admin.roles_v3_client
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100163 else:
Felipe Monteirobf524fb2018-10-03 09:03:35 -0500164 raise lib_exc.InvalidConfiguration(
165 "Patrole role overriding only supports v3 identity API.")
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100166
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600167 cls.admin_roles_client = admin_roles_client
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500168
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600169 cls._project_id = cls.os_primary.credentials.tenant_id
170 cls._user_id = cls.os_primary.credentials.user_id
171 cls._role_inferences_mapping = cls._prepare_role_inferences_mapping()
172
173 cls._init_roles()
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500174
Lingxian Kongb73e1082021-01-13 15:04:39 +1300175 # Store the user's original roles and rollback after testing.
176 roles = cls.admin_roles_client.list_user_roles_on_project(
177 cls._project_id, cls._user_id)['roles']
178 cls._orig_roles = [role['id'] for role in roles]
179 cls.addClassResourceCleanup(cls.restore_roles)
180
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500181 # Change default role to admin
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600182 cls._override_role(False)
Lingxian Kongb73e1082021-01-13 15:04:39 +1300183
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600184 super(RbacUtilsMixin, cls).setup_clients()
Felipe Monteirob35de582017-05-05 00:16:53 +0100185
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600186 @classmethod
187 def _prepare_role_inferences_mapping(cls):
Sergey Vilgelm19e3bec2019-01-07 11:59:41 -0600188 """Preparing roles mapping to support role inferences
189
190 Making query to `list-all-role-inference-rules`_ keystone API
191 returns all inference rules, which makes it possible to prepare
192 roles mapping.
193
194 It walks recursively through the raw data::
195
196 {"role_inferences": [
197 {
198 "implies": [{"id": "3", "name": "reader"}],
199 "prior_role": {"id": "2", "name": "member"}
200 },
201 {
202 "implies": [{"id": "2", "name": "member"}],
203 "prior_role": {"id": "1", "name": "admin"}
204 }
205 ]
206 }
207
208 and converts it to the mapping::
209
210 {
211 "2": ["3"], # "member": ["reader"],
212 "1": ["2", "3"] # "admin": ["member", "reader"]
213 }
214
Andreas Jaegeree00c052019-07-25 17:32:11 +0200215 .. _list-all-role-inference-rules: https://docs.openstack.org/api-ref/identity/v3/#list-all-role-inference-rules
Sergey Vilgelm19e3bec2019-01-07 11:59:41 -0600216 """ # noqa: E501
217 def process_roles(role_id, data):
218 roles = data.get(role_id, set())
219 for rid in roles.copy():
220 roles.update(process_roles(rid, data))
221
222 return roles
223
224 def convert_data(data):
225 res = {}
226 for rule in data:
227 prior_role = rule['prior_role']['id']
228 implies = {r['id'] for r in rule['implies']}
229 res[prior_role] = implies
230 return res
231
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600232 raw_data = cls.admin_roles_client.list_all_role_inference_rules()
Sergey Vilgelm19e3bec2019-01-07 11:59:41 -0600233 data = convert_data(raw_data['role_inferences'])
234 res = {}
235 for role_id in data:
236 res[role_id] = process_roles(role_id, data)
237 return res
238
239 def get_all_needed_roles(self, roles):
240 """Extending given roles with roles from mapping
241
242 Examples::
243 ["admin"] >> ["admin", "member", "reader"]
244 ["member"] >> ["member", "reader"]
245 ["reader"] >> ["reader"]
246 ["custom_role"] >> ["custom_role"]
247
248 :param roles: list of roles
249 :return: extended list of roles
250 """
251 res = set(r for r in roles)
252 for role in res.copy():
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600253 role_id = self.__class__._role_map.get(role)
254 implied_roles = self.__class__._role_inferences_mapping.get(
255 role_id, set())
256 role_names = {self.__class__._role_map[rid]
257 for rid in implied_roles}
Sergey Vilgelm19e3bec2019-01-07 11:59:41 -0600258 res.update(role_names)
259 LOG.debug('All needed roles: %s; Base roles: %s', res, roles)
260 return list(res)
DavidPurcell029d8c32017-01-06 15:27:41 -0500261
Sergey Vilgelmbab9e942018-10-11 14:04:48 -0500262 @contextlib.contextmanager
Sergey Vilgelmd3d77ef2019-02-02 09:34:52 -0600263 def override_role(self):
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000264 """Override the role used by ``os_primary`` Tempest credentials.
265
266 Temporarily change the role used by ``os_primary`` credentials to:
Masayuki Igawa80b9aab2018-01-09 17:00:45 +0900267
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500268 * ``[patrole] rbac_test_roles`` before test execution
Masayuki Igawa80b9aab2018-01-09 17:00:45 +0900269 * ``[identity] admin_role`` after test execution
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000270
271 Automatically switches to admin role after test execution.
272
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000273 :returns: None
274
275 .. warning::
276
277 This function can alter user roles for pre-provisioned credentials.
278 Work is underway to safely clean up after this function.
279
280 Example::
281
282 @rbac_rule_validation.action(service='test',
Felipe Monteiro59f538f2018-08-22 23:34:40 -0400283 rules=['a:test:rule'])
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000284 def test_foo(self):
285 # Allocate test-level resources here.
Sergey Vilgelmd3d77ef2019-02-02 09:34:52 -0600286 with self.override_role():
melissaml7cd21612018-05-23 21:00:50 +0800287 # The role for `os_primary` has now been overridden. Within
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000288 # this block, call the API endpoint that enforces the
289 # expected policy specified by "rule" in the decorator.
290 self.foo_service.bar_api_call()
291 # The role is switched back to admin automatically. Note that
292 # if the API call above threw an exception, any code below this
293 # point in the test is not executed.
294 """
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600295 self._set_override_role_called()
296 self._override_role(True)
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000297 try:
298 # Execute the test.
299 yield
300 finally:
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500301 # Check whether an exception was raised. If so, remember that
302 # for future validation.
303 exc = sys.exc_info()[0]
304 if exc is not None:
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600305 self._set_override_role_caught_exc()
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000306 # This code block is always executed, no matter the result of the
307 # test. Automatically switch back to the admin role for test clean
308 # up.
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600309 self._override_role(False)
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000310
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600311 @classmethod
312 def _override_role(cls, toggle_rbac_role=False):
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000313 """Private helper for overriding ``os_primary`` Tempest credentials.
314
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000315 :param toggle_rbac_role: Boolean value that controls the role that
316 overrides default role of ``os_primary`` credentials.
317 * If True: role is set to ``[patrole] rbac_test_role``
318 * If False: role is set to ``[identity] admin_role``
319 """
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000320 LOG.debug('Overriding role to: %s.', toggle_rbac_role)
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500321 roles_already_present = False
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100322
DavidPurcell029d8c32017-01-06 15:27:41 -0500323 try:
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600324 target_roles = (cls._rbac_role_ids
325 if toggle_rbac_role else [cls._admin_role_id])
326 roles_already_present = cls._list_and_clear_user_roles_on_project(
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500327 target_roles)
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100328
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000329 # Do not override roles if `target_role` already exists.
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500330 if not roles_already_present:
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600331 cls._create_user_role_on_project(target_roles)
DavidPurcell029d8c32017-01-06 15:27:41 -0500332 except Exception as exp:
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100333 with excutils.save_and_reraise_exception():
334 LOG.exception(exp)
Felipe Monteiro34a138c2017-03-02 17:01:37 -0500335 finally:
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600336 auth_providers = cls.get_auth_providers()
Mykola Yakovliev1d829782018-08-03 14:37:37 -0500337 for provider in auth_providers:
338 provider.clear_auth()
Felipe Monteiro7be94e82017-07-26 02:17:08 +0100339 # Fernet tokens are not subsecond aware so sleep to ensure we are
340 # passing the second boundary before attempting to authenticate.
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100341 # Only sleep if a token revocation occurred as a result of role
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000342 # overriding. This will optimize test runtime in the case where
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500343 # ``[identity] admin_role`` == ``[patrole] rbac_test_roles``.
344 if not roles_already_present:
Rick Bartra89f498f2017-03-20 15:54:45 -0400345 time.sleep(1)
Mykola Yakovliev1d829782018-08-03 14:37:37 -0500346
347 for provider in auth_providers:
348 provider.set_auth()
Felipe Monteiro34a138c2017-03-02 17:01:37 -0500349
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600350 @classmethod
351 def _init_roles(cls):
352 available_roles = cls.admin_roles_client.list_roles()['roles']
353 cls._role_map = {r['name']: r['id'] for r in available_roles}
354 LOG.debug('Available roles: %s', cls._role_map.keys())
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100355
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500356 rbac_role_ids = []
357 roles = CONF.patrole.rbac_test_roles
358 # TODO(vegasq) drop once CONF.patrole.rbac_test_role is removed
359 if CONF.patrole.rbac_test_role:
360 if not roles:
361 roles.append(CONF.patrole.rbac_test_role)
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100362
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500363 for role_name in roles:
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600364 rbac_role_ids.append(cls._role_map.get(role_name))
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500365
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600366 admin_role_id = cls._role_map.get(CONF.identity.admin_role)
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500367
368 if not all([admin_role_id, all(rbac_role_ids)]):
Felipe Monteiro2fc29292018-06-15 18:26:27 -0400369 missing_roles = []
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500370 msg = ("Could not find `[patrole] rbac_test_roles` or "
Felipe Monteiro2fc29292018-06-15 18:26:27 -0400371 "`[identity] admin_role`, both of which are required for "
372 "RBAC testing.")
373 if not admin_role_id:
374 missing_roles.append(CONF.identity.admin_role)
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500375 if not all(rbac_role_ids):
376 missing_roles += [role_name for role_name in roles
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600377 if role_name not in cls._role_map]
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500378
Felipe Monteiro2fc29292018-06-15 18:26:27 -0400379 msg += " Following roles were not found: %s." % (
380 ", ".join(missing_roles))
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600381 msg += " Available roles: %s." % ", ".join(cls._role_map)
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100382 raise rbac_exceptions.RbacResourceSetupFailed(msg)
383
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600384 cls._admin_role_id = admin_role_id
385 cls._rbac_role_ids = rbac_role_ids
Sergey Vilgelm19e3bec2019-01-07 11:59:41 -0600386 # Adding backward mapping
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600387 cls._role_map.update({v: k for k, v in cls._role_map.items()})
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100388
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600389 @classmethod
390 def _create_user_role_on_project(cls, role_ids):
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500391 for role_id in role_ids:
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600392 cls.admin_roles_client.create_user_role_on_project(
393 cls._project_id, cls._user_id, role_id)
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100394
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600395 @classmethod
396 def _list_and_clear_user_roles_on_project(cls, role_ids):
397 roles = cls.admin_roles_client.list_user_roles_on_project(
398 cls._project_id, cls._user_id)['roles']
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500399 all_role_ids = [role['id'] for role in roles]
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100400
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500401 # NOTE(felipemonteiro): We do not use ``role_id in all_role_ids`` here
402 # to avoid over-permission errors: if the current list of roles on the
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100403 # project includes "admin" and "Member", and we are switching to the
404 # "Member" role, then we must delete the "admin" role. Thus, we only
405 # return early if the user's roles on the project are an exact match.
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500406 if set(role_ids) == set(all_role_ids):
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100407 return True
Felipe Monteirob3b7bc82017-03-03 15:58:15 -0500408
409 for role in roles:
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600410 cls.admin_roles_client.delete_role_from_user_on_project(
411 cls._project_id, cls._user_id, role['id'])
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100412
413 return False
414
Sergey Vilgelmbab9e942018-10-11 14:04:48 -0500415 @contextlib.contextmanager
Sergey Vilgelmd3d77ef2019-02-02 09:34:52 -0600416 def override_role_and_validate_list(self,
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600417 admin_resources=None,
Sergey Vilgelmbab9e942018-10-11 14:04:48 -0500418 admin_resource_id=None):
419 """Call ``override_role`` and validate RBAC for a list API action.
420
421 List actions usually do soft authorization: partial or empty response
422 bodies are returned instead of exceptions. This helper validates
423 that unauthorized roles only return a subset of the available
424 resources.
425 Should only be used for validating list API actions.
426
427 :param test_obj: Instance of ``tempest.test.BaseTestCase``.
428 :param list admin_resources: The list of resources received before
429 calling the ``override_role_and_validate_list`` function.
430 :param UUID admin_resource_id: An ID of a resource created before
431 calling the ``override_role_and_validate_list`` function.
432 :return: py:class:`_ValidateListContext` object.
433
434 Example::
435
436 # the resource created by admin
437 admin_resource_id = (
438 self.ntp_client.create_dscp_marking_rule()
439 ["dscp_marking_rule"]["id'])
Sergey Vilgelmd3d77ef2019-02-02 09:34:52 -0600440 with self.override_role_and_validate_list(
441 admin_resource_id=admin_resource_id) as ctx:
Sergey Vilgelmbab9e942018-10-11 14:04:48 -0500442 # the list of resources available for member role
443 ctx.resources = self.ntp_client.list_dscp_marking_rules(
444 policy_id=self.policy_id)["dscp_marking_rules"]
445 """
446 ctx = _ValidateListContext(admin_resources, admin_resource_id)
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600447 with self.override_role():
Sergey Vilgelmbab9e942018-10-11 14:04:48 -0500448 yield ctx
449 ctx._validate()
450
Felipe Monteiro07a1c172017-12-10 04:26:08 +0000451 @classmethod
Mykola Yakovliev1d829782018-08-03 14:37:37 -0500452 def get_auth_providers(cls):
453 """Returns list of auth_providers used within test.
454
455 Tests may redefine this method to include their own or third party
456 client auth_providers.
457 """
458 return [cls.os_primary.auth_provider]
459
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500460 def _set_override_role_called(self):
461 """Helper for tracking whether ``override_role`` was called."""
462 self.__override_role_called = True
463
464 def _set_override_role_caught_exc(self):
465 """Helper for tracking whether exception was thrown inside
466 ``override_role``.
467 """
468 self.__override_role_caught_exc = True
469
470 def _validate_override_role_called(self):
471 """Idempotently validate that ``override_role`` is called and reset
472 its value to False for sequential tests.
473 """
474 was_called = self.__override_role_called
475 self.__override_role_called = False
476 return was_called
477
478 def _validate_override_role_caught_exc(self):
479 """Idempotently validate that exception was caught inside
480 ``override_role``, so that, by process of elimination, it can be
481 determined whether one was thrown outside (which is invalid).
482 """
483 caught_exception = self.__override_role_caught_exc
484 self.__override_role_caught_exc = False
485 return caught_exception
486
Felipe Monteiro07a1c172017-12-10 04:26:08 +0000487
Felipe Monteiro8a043fb2017-08-06 06:29:05 +0100488def is_admin():
489 """Verifies whether the current test role equals the admin role.
490
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500491 :returns: True if ``rbac_test_roles`` contain the admin role.
Felipe Monteiro8a043fb2017-08-06 06:29:05 +0100492 """
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500493 roles = CONF.patrole.rbac_test_roles
494 # TODO(vegasq) drop once CONF.patrole.rbac_test_role is removed
495 if CONF.patrole.rbac_test_role:
496 roles.append(CONF.patrole.rbac_test_role)
497 roles = list(set(roles))
498
Felipe Monteiro2fc29292018-06-15 18:26:27 -0400499 # TODO(felipemonteiro): Make this more robust via a context is admin
500 # lookup.
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500501 return CONF.identity.admin_role in roles