blob: 7a1c33fbeb8ed8e40e2a9bbaed18b95aff567c42 [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
Felipe Monteiro3e14f472017-08-17 23:02:11 +010023from tempest import clients
Felipe Monteiroe7e552e2017-05-02 17:04:12 +010024from tempest.common import credentials_factory as credentials
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010025from tempest import config
Felipe Monteirobf524fb2018-10-03 09:03:35 -050026from tempest.lib import exceptions as lib_exc
DavidPurcell029d8c32017-01-06 15:27:41 -050027
Felipe Monteiro34a138c2017-03-02 17:01:37 -050028from patrole_tempest_plugin import rbac_exceptions
DavidPurcell029d8c32017-01-06 15:27:41 -050029
DavidPurcell029d8c32017-01-06 15:27:41 -050030CONF = config.CONF
Felipe Monteiro34a138c2017-03-02 17:01:37 -050031LOG = logging.getLogger(__name__)
DavidPurcell029d8c32017-01-06 15:27:41 -050032
33
Sergey Vilgelmbab9e942018-10-11 14:04:48 -050034class _ValidateListContext(object):
35 """Context class responsible for validation of the list functions.
36
37 This class is used in ``override_role_and_validate_list`` function and
38 the result of a list function must be assigned to the ``ctx.resources``
39 variable.
40
41 Example::
42
Sergey Vilgelmd3d77ef2019-02-02 09:34:52 -060043 with self.override_role_and_validate_list(...) as ctx:
Sergey Vilgelmbab9e942018-10-11 14:04:48 -050044 ctx.resources = list_function()
45
46 """
47 def __init__(self, admin_resources=None, admin_resource_id=None):
48 """Constructor for ``ValidateListContext``.
49
50 Either ``admin_resources`` or ``admin_resource_id`` should be used,
51 not both.
52
53 :param list admin_resources: The list of resources received before
54 calling the ``override_role_and_validate_list`` function. To
55 validate will be used the ``_validate_len`` function.
56 :param UUID admin_resource_id: An ID of a resource created before
57 calling the ``override_role_and_validate_list`` function. To
58 validate will be used the ``_validate_resource`` function.
59 :raises RbacValidateListException: if both ``admin_resources`` and
60 ``admin_resource_id`` are set or unset.
61 """
62 self.resources = None
63 if admin_resources is not None and not admin_resource_id:
64 self._admin_len = len(admin_resources)
65 if not self._admin_len:
66 raise rbac_exceptions.RbacValidateListException(
67 reason="the list of admin resources cannot be empty")
68 self._validate_func = self._validate_len
69 elif admin_resource_id and admin_resources is None:
70 self._admin_resource_id = admin_resource_id
71 self._validate_func = self._validate_resource
72 else:
73 raise rbac_exceptions.RbacValidateListException(
74 reason="admin_resources and admin_resource_id are mutually "
75 "exclusive")
76
77 def _validate_len(self):
78 """Validates that the number of resources is less than admin resources.
79 """
80 if not len(self.resources):
81 raise rbac_exceptions.RbacEmptyResponseBody()
82 elif self._admin_len > len(self.resources):
83 raise rbac_exceptions.RbacPartialResponseBody(body=self.resources)
84
85 def _validate_resource(self):
86 """Validates that the admin resource is present in the resources.
87 """
88 for resource in self.resources:
89 if resource['id'] == self._admin_resource_id:
90 return
91 raise rbac_exceptions.RbacPartialResponseBody(body=self.resources)
92
93 def _validate(self):
94 """Calls the proper validation function.
95
96 :raises RbacValidateListException: if the ``ctx.resources`` variable is
97 not assigned.
98 """
99 if self.resources is None:
100 raise rbac_exceptions.RbacValidateListException(
101 reason="ctx.resources is not assigned")
102 self._validate_func()
103
104
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600105class RbacUtilsMixin(object):
106 """Utility mixin responsible for switching ``os_primary`` role.
107
108 Should be used as a mixin class alongside an instance of
109 :py:class:`tempest.test.BaseTestCase` to perform Patrole class setup for a
110 base RBAC class. Child classes should not use this mixin.
111
112 Example::
113
114 class BaseRbacTest(rbac_utils.RbacUtilsMixin, base.BaseV2ComputeTest):
115
116 @classmethod
117 def setup_clients(cls):
118 super(BaseRbacTest, cls).setup_clients()
119
120 cls.hosts_client = cls.os_primary.hosts_client
121 ...
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100122
123 This class is responsible for overriding the value of the primary Tempest
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000124 credential's role (i.e. ``os_primary`` role). By doing so, it is possible
125 to seamlessly swap between admin credentials, needed for setup and clean
126 up, and primary credentials, needed to perform the API call which does
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100127 policy enforcement. The primary credentials always cycle between roles
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000128 defined by ``CONF.identity.admin_role`` and
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500129 ``CONF.patrole.rbac_test_roles``.
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100130 """
DavidPurcell029d8c32017-01-06 15:27:41 -0500131
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600132 def __init__(self, *args, **kwargs):
133 super(RbacUtilsMixin, self).__init__(*args, **kwargs)
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100134
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600135 # Shows if override_role was called.
136 self.__override_role_called = False
137 # Shows if exception raised during override_role.
138 self.__override_role_caught_exc = False
Sergey Vilgelm19e3bec2019-01-07 11:59:41 -0600139
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600140 _admin_role_id = None
141 _rbac_role_ids = None
142 _project_id = None
143 _user_id = None
144 _role_map = None
145 _role_inferences_mapping = None
Lingxian Kongb73e1082021-01-13 15:04:39 +1300146 _orig_roles = []
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600147
148 admin_roles_client = None
149
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600150 @classmethod
Lingxian Kongb73e1082021-01-13 15:04:39 +1300151 def restore_roles(cls):
152 if cls._orig_roles:
153 LOG.info("Restoring original roles %s", cls._orig_roles)
154 roles_already_present = cls._list_and_clear_user_roles_on_project(
155 cls._orig_roles)
156
157 if not roles_already_present:
158 cls._create_user_role_on_project(cls._orig_roles)
159
160 @classmethod
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600161 def setup_clients(cls):
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100162 # Intialize the admin roles_client to perform role switching.
Felipe Monteiro3e14f472017-08-17 23:02:11 +0100163 admin_mgr = clients.Manager(
164 credentials.get_configured_admin_credentials())
Felipe Monteirobf524fb2018-10-03 09:03:35 -0500165 if CONF.identity_feature_enabled.api_v3:
Felipe Monteiro3e14f472017-08-17 23:02:11 +0100166 admin_roles_client = admin_mgr.roles_v3_client
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100167 else:
Felipe Monteirobf524fb2018-10-03 09:03:35 -0500168 raise lib_exc.InvalidConfiguration(
169 "Patrole role overriding only supports v3 identity API.")
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100170
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600171 cls.admin_roles_client = admin_roles_client
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500172
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600173 cls._project_id = cls.os_primary.credentials.tenant_id
174 cls._user_id = cls.os_primary.credentials.user_id
175 cls._role_inferences_mapping = cls._prepare_role_inferences_mapping()
176
177 cls._init_roles()
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500178
Lingxian Kongb73e1082021-01-13 15:04:39 +1300179 # Store the user's original roles and rollback after testing.
180 roles = cls.admin_roles_client.list_user_roles_on_project(
181 cls._project_id, cls._user_id)['roles']
182 cls._orig_roles = [role['id'] for role in roles]
183 cls.addClassResourceCleanup(cls.restore_roles)
184
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500185 # Change default role to admin
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600186 cls._override_role(False)
Lingxian Kongb73e1082021-01-13 15:04:39 +1300187
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600188 super(RbacUtilsMixin, cls).setup_clients()
Felipe Monteirob35de582017-05-05 00:16:53 +0100189
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600190 @classmethod
191 def _prepare_role_inferences_mapping(cls):
Sergey Vilgelm19e3bec2019-01-07 11:59:41 -0600192 """Preparing roles mapping to support role inferences
193
194 Making query to `list-all-role-inference-rules`_ keystone API
195 returns all inference rules, which makes it possible to prepare
196 roles mapping.
197
198 It walks recursively through the raw data::
199
200 {"role_inferences": [
201 {
202 "implies": [{"id": "3", "name": "reader"}],
203 "prior_role": {"id": "2", "name": "member"}
204 },
205 {
206 "implies": [{"id": "2", "name": "member"}],
207 "prior_role": {"id": "1", "name": "admin"}
208 }
209 ]
210 }
211
212 and converts it to the mapping::
213
214 {
215 "2": ["3"], # "member": ["reader"],
216 "1": ["2", "3"] # "admin": ["member", "reader"]
217 }
218
Andreas Jaegeree00c052019-07-25 17:32:11 +0200219 .. _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 -0600220 """ # noqa: E501
221 def process_roles(role_id, data):
222 roles = data.get(role_id, set())
223 for rid in roles.copy():
224 roles.update(process_roles(rid, data))
225
226 return roles
227
228 def convert_data(data):
229 res = {}
230 for rule in data:
231 prior_role = rule['prior_role']['id']
232 implies = {r['id'] for r in rule['implies']}
233 res[prior_role] = implies
234 return res
235
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600236 raw_data = cls.admin_roles_client.list_all_role_inference_rules()
Sergey Vilgelm19e3bec2019-01-07 11:59:41 -0600237 data = convert_data(raw_data['role_inferences'])
238 res = {}
239 for role_id in data:
240 res[role_id] = process_roles(role_id, data)
241 return res
242
243 def get_all_needed_roles(self, roles):
244 """Extending given roles with roles from mapping
245
246 Examples::
247 ["admin"] >> ["admin", "member", "reader"]
248 ["member"] >> ["member", "reader"]
249 ["reader"] >> ["reader"]
250 ["custom_role"] >> ["custom_role"]
251
252 :param roles: list of roles
253 :return: extended list of roles
254 """
255 res = set(r for r in roles)
256 for role in res.copy():
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600257 role_id = self.__class__._role_map.get(role)
258 implied_roles = self.__class__._role_inferences_mapping.get(
259 role_id, set())
260 role_names = {self.__class__._role_map[rid]
261 for rid in implied_roles}
Sergey Vilgelm19e3bec2019-01-07 11:59:41 -0600262 res.update(role_names)
263 LOG.debug('All needed roles: %s; Base roles: %s', res, roles)
264 return list(res)
DavidPurcell029d8c32017-01-06 15:27:41 -0500265
Sergey Vilgelmbab9e942018-10-11 14:04:48 -0500266 @contextlib.contextmanager
Sergey Vilgelmd3d77ef2019-02-02 09:34:52 -0600267 def override_role(self):
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000268 """Override the role used by ``os_primary`` Tempest credentials.
269
270 Temporarily change the role used by ``os_primary`` credentials to:
Masayuki Igawa80b9aab2018-01-09 17:00:45 +0900271
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500272 * ``[patrole] rbac_test_roles`` before test execution
Masayuki Igawa80b9aab2018-01-09 17:00:45 +0900273 * ``[identity] admin_role`` after test execution
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000274
275 Automatically switches to admin role after test execution.
276
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000277 :returns: None
278
279 .. warning::
280
281 This function can alter user roles for pre-provisioned credentials.
282 Work is underway to safely clean up after this function.
283
284 Example::
285
286 @rbac_rule_validation.action(service='test',
Felipe Monteiro59f538f2018-08-22 23:34:40 -0400287 rules=['a:test:rule'])
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000288 def test_foo(self):
289 # Allocate test-level resources here.
Sergey Vilgelmd3d77ef2019-02-02 09:34:52 -0600290 with self.override_role():
melissaml7cd21612018-05-23 21:00:50 +0800291 # The role for `os_primary` has now been overridden. Within
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000292 # this block, call the API endpoint that enforces the
293 # expected policy specified by "rule" in the decorator.
294 self.foo_service.bar_api_call()
295 # The role is switched back to admin automatically. Note that
296 # if the API call above threw an exception, any code below this
297 # point in the test is not executed.
298 """
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600299 self._set_override_role_called()
300 self._override_role(True)
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000301 try:
302 # Execute the test.
303 yield
304 finally:
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500305 # Check whether an exception was raised. If so, remember that
306 # for future validation.
307 exc = sys.exc_info()[0]
308 if exc is not None:
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600309 self._set_override_role_caught_exc()
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000310 # This code block is always executed, no matter the result of the
311 # test. Automatically switch back to the admin role for test clean
312 # up.
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600313 self._override_role(False)
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000314
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600315 @classmethod
316 def _override_role(cls, toggle_rbac_role=False):
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000317 """Private helper for overriding ``os_primary`` Tempest credentials.
318
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000319 :param toggle_rbac_role: Boolean value that controls the role that
320 overrides default role of ``os_primary`` credentials.
321 * If True: role is set to ``[patrole] rbac_test_role``
322 * If False: role is set to ``[identity] admin_role``
323 """
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000324 LOG.debug('Overriding role to: %s.', toggle_rbac_role)
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500325 roles_already_present = False
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100326
DavidPurcell029d8c32017-01-06 15:27:41 -0500327 try:
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600328 target_roles = (cls._rbac_role_ids
329 if toggle_rbac_role else [cls._admin_role_id])
330 roles_already_present = cls._list_and_clear_user_roles_on_project(
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500331 target_roles)
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100332
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000333 # Do not override roles if `target_role` already exists.
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500334 if not roles_already_present:
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600335 cls._create_user_role_on_project(target_roles)
DavidPurcell029d8c32017-01-06 15:27:41 -0500336 except Exception as exp:
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100337 with excutils.save_and_reraise_exception():
338 LOG.exception(exp)
Felipe Monteiro34a138c2017-03-02 17:01:37 -0500339 finally:
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600340 auth_providers = cls.get_auth_providers()
Mykola Yakovliev1d829782018-08-03 14:37:37 -0500341 for provider in auth_providers:
342 provider.clear_auth()
Felipe Monteiro7be94e82017-07-26 02:17:08 +0100343 # Fernet tokens are not subsecond aware so sleep to ensure we are
344 # passing the second boundary before attempting to authenticate.
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100345 # Only sleep if a token revocation occurred as a result of role
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000346 # overriding. This will optimize test runtime in the case where
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500347 # ``[identity] admin_role`` == ``[patrole] rbac_test_roles``.
348 if not roles_already_present:
Rick Bartra89f498f2017-03-20 15:54:45 -0400349 time.sleep(1)
Mykola Yakovliev1d829782018-08-03 14:37:37 -0500350
351 for provider in auth_providers:
352 provider.set_auth()
Felipe Monteiro34a138c2017-03-02 17:01:37 -0500353
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600354 @classmethod
355 def _init_roles(cls):
356 available_roles = cls.admin_roles_client.list_roles()['roles']
357 cls._role_map = {r['name']: r['id'] for r in available_roles}
358 LOG.debug('Available roles: %s', cls._role_map.keys())
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100359
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500360 rbac_role_ids = []
361 roles = CONF.patrole.rbac_test_roles
362 # TODO(vegasq) drop once CONF.patrole.rbac_test_role is removed
363 if CONF.patrole.rbac_test_role:
364 if not roles:
365 roles.append(CONF.patrole.rbac_test_role)
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100366
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500367 for role_name in roles:
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600368 rbac_role_ids.append(cls._role_map.get(role_name))
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500369
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600370 admin_role_id = cls._role_map.get(CONF.identity.admin_role)
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500371
372 if not all([admin_role_id, all(rbac_role_ids)]):
Felipe Monteiro2fc29292018-06-15 18:26:27 -0400373 missing_roles = []
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500374 msg = ("Could not find `[patrole] rbac_test_roles` or "
Felipe Monteiro2fc29292018-06-15 18:26:27 -0400375 "`[identity] admin_role`, both of which are required for "
376 "RBAC testing.")
377 if not admin_role_id:
378 missing_roles.append(CONF.identity.admin_role)
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500379 if not all(rbac_role_ids):
380 missing_roles += [role_name for role_name in roles
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600381 if role_name not in cls._role_map]
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500382
Felipe Monteiro2fc29292018-06-15 18:26:27 -0400383 msg += " Following roles were not found: %s." % (
384 ", ".join(missing_roles))
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600385 msg += " Available roles: %s." % ", ".join(cls._role_map)
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100386 raise rbac_exceptions.RbacResourceSetupFailed(msg)
387
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600388 cls._admin_role_id = admin_role_id
389 cls._rbac_role_ids = rbac_role_ids
Sergey Vilgelm19e3bec2019-01-07 11:59:41 -0600390 # Adding backward mapping
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600391 cls._role_map.update({v: k for k, v in cls._role_map.items()})
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100392
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600393 @classmethod
394 def _create_user_role_on_project(cls, role_ids):
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500395 for role_id in role_ids:
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600396 cls.admin_roles_client.create_user_role_on_project(
397 cls._project_id, cls._user_id, role_id)
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100398
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600399 @classmethod
400 def _list_and_clear_user_roles_on_project(cls, role_ids):
401 roles = cls.admin_roles_client.list_user_roles_on_project(
402 cls._project_id, cls._user_id)['roles']
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500403 all_role_ids = [role['id'] for role in roles]
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100404
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500405 # NOTE(felipemonteiro): We do not use ``role_id in all_role_ids`` here
406 # to avoid over-permission errors: if the current list of roles on the
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100407 # project includes "admin" and "Member", and we are switching to the
408 # "Member" role, then we must delete the "admin" role. Thus, we only
409 # return early if the user's roles on the project are an exact match.
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500410 if set(role_ids) == set(all_role_ids):
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100411 return True
Felipe Monteirob3b7bc82017-03-03 15:58:15 -0500412
413 for role in roles:
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600414 cls.admin_roles_client.delete_role_from_user_on_project(
415 cls._project_id, cls._user_id, role['id'])
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100416
417 return False
418
Sergey Vilgelmbab9e942018-10-11 14:04:48 -0500419 @contextlib.contextmanager
Sergey Vilgelmd3d77ef2019-02-02 09:34:52 -0600420 def override_role_and_validate_list(self,
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600421 admin_resources=None,
Sergey Vilgelmbab9e942018-10-11 14:04:48 -0500422 admin_resource_id=None):
423 """Call ``override_role`` and validate RBAC for a list API action.
424
425 List actions usually do soft authorization: partial or empty response
426 bodies are returned instead of exceptions. This helper validates
427 that unauthorized roles only return a subset of the available
428 resources.
429 Should only be used for validating list API actions.
430
431 :param test_obj: Instance of ``tempest.test.BaseTestCase``.
432 :param list admin_resources: The list of resources received before
433 calling the ``override_role_and_validate_list`` function.
434 :param UUID admin_resource_id: An ID of a resource created before
435 calling the ``override_role_and_validate_list`` function.
436 :return: py:class:`_ValidateListContext` object.
437
438 Example::
439
440 # the resource created by admin
441 admin_resource_id = (
442 self.ntp_client.create_dscp_marking_rule()
443 ["dscp_marking_rule"]["id'])
Sergey Vilgelmd3d77ef2019-02-02 09:34:52 -0600444 with self.override_role_and_validate_list(
445 admin_resource_id=admin_resource_id) as ctx:
Sergey Vilgelmbab9e942018-10-11 14:04:48 -0500446 # the list of resources available for member role
447 ctx.resources = self.ntp_client.list_dscp_marking_rules(
448 policy_id=self.policy_id)["dscp_marking_rules"]
449 """
450 ctx = _ValidateListContext(admin_resources, admin_resource_id)
Sergey Vilgelmace8ea32018-11-19 16:25:10 -0600451 with self.override_role():
Sergey Vilgelmbab9e942018-10-11 14:04:48 -0500452 yield ctx
453 ctx._validate()
454
Felipe Monteiro07a1c172017-12-10 04:26:08 +0000455 @classmethod
Mykola Yakovliev1d829782018-08-03 14:37:37 -0500456 def get_auth_providers(cls):
457 """Returns list of auth_providers used within test.
458
459 Tests may redefine this method to include their own or third party
460 client auth_providers.
461 """
462 return [cls.os_primary.auth_provider]
463
Mykola Yakovliev11376ab2018-08-06 15:34:22 -0500464 def _set_override_role_called(self):
465 """Helper for tracking whether ``override_role`` was called."""
466 self.__override_role_called = True
467
468 def _set_override_role_caught_exc(self):
469 """Helper for tracking whether exception was thrown inside
470 ``override_role``.
471 """
472 self.__override_role_caught_exc = True
473
474 def _validate_override_role_called(self):
475 """Idempotently validate that ``override_role`` is called and reset
476 its value to False for sequential tests.
477 """
478 was_called = self.__override_role_called
479 self.__override_role_called = False
480 return was_called
481
482 def _validate_override_role_caught_exc(self):
483 """Idempotently validate that exception was caught inside
484 ``override_role``, so that, by process of elimination, it can be
485 determined whether one was thrown outside (which is invalid).
486 """
487 caught_exception = self.__override_role_caught_exc
488 self.__override_role_caught_exc = False
489 return caught_exception
490
Felipe Monteiro07a1c172017-12-10 04:26:08 +0000491
Felipe Monteiro8a043fb2017-08-06 06:29:05 +0100492def is_admin():
493 """Verifies whether the current test role equals the admin role.
494
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500495 :returns: True if ``rbac_test_roles`` contain the admin role.
Felipe Monteiro8a043fb2017-08-06 06:29:05 +0100496 """
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500497 roles = CONF.patrole.rbac_test_roles
498 # TODO(vegasq) drop once CONF.patrole.rbac_test_role is removed
499 if CONF.patrole.rbac_test_role:
500 roles.append(CONF.patrole.rbac_test_role)
501 roles = list(set(roles))
502
Felipe Monteiro2fc29292018-06-15 18:26:27 -0400503 # TODO(felipemonteiro): Make this more robust via a context is admin
504 # lookup.
Mykola Yakovlieve0f35502018-09-26 18:26:57 -0500505 return CONF.identity.admin_role in roles