blob: 29d41d35943b02359450a5233a56189e9adb1257 [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
Rick Bartraed950052017-06-29 17:20:33 -040016import abc
17import six
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010018import sys
DavidPurcell029d8c32017-01-06 15:27:41 -050019import time
Felipe Monteiro34a138c2017-03-02 17:01:37 -050020
Rajiv Kumar645dfc92017-01-19 13:48:27 +053021from oslo_log import log as logging
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010022import oslo_utils.uuidutils as uuid_utils
Felipe Monteiroe8d93e02017-07-19 20:52:20 +010023import testtools
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010024
Felipe Monteiroe7e552e2017-05-02 17:04:12 +010025from tempest.common import credentials_factory as credentials
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010026from tempest import config
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
DavidPurcell029d8c32017-01-06 15:27:41 -050034class RbacUtils(object):
Felipe Monteiroe8d93e02017-07-19 20:52:20 +010035 """Utility class responsible for switching os_primary role.
36
37 This class is responsible for overriding the value of the primary Tempest
38 credential's role (i.e. "os_primary" role). By doing so, it is possible to
39 seamlessly swap between admin credentials, needed for setup and clean up,
40 and primary credentials, needed to perform the API call which does
41 policy enforcement. The primary credentials always cycle between roles
42 defined by ``CONF.identity.admin_role`` and `CONF.rbac.rbac_test_role``.
43 """
DavidPurcell029d8c32017-01-06 15:27:41 -050044
Felipe Monteirob35de582017-05-05 00:16:53 +010045 def __init__(self, test_obj):
Felipe Monteiroe8d93e02017-07-19 20:52:20 +010046 """Constructor for ``RbacUtils``.
47
48 :param test_obj: A Tempest test instance.
49 :type test_obj: tempest.lib.base.BaseTestCase or
50 tempest.test.BaseTestCase
51 """
52 # Since we are going to instantiate a client manager with
53 # admin credentials, first check if admin is available.
54 if not credentials.is_admin_available(
55 identity_version=test_obj.get_identity_version()):
56 msg = "Missing Identity Admin API credentials in configuration."
57 raise testtools.TestCase.skipException(msg)
58
59 # Intialize the admin roles_client to perform role switching.
60 admin_creds = test_obj.get_client_manager(credential_type='admin')
61 if test_obj.get_identity_version() == 'v3':
62 admin_roles_client = admin_creds.roles_v3_client
63 else:
64 admin_roles_client = admin_creds.roles_client
65
66 self.admin_roles_client = admin_roles_client
Felipe Monteirob35de582017-05-05 00:16:53 +010067 self.switch_role(test_obj, toggle_rbac_role=False)
68
Felipe Monteiro75f23632017-04-07 15:56:26 +010069 # References the last value of `toggle_rbac_role` that was passed to
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010070 # `switch_role`. Used for ensuring that `switch_role` is correctly used
71 # in a test file, so that false positives are prevented. The key used
Felipe Monteiro521e5c12017-04-05 22:59:57 +010072 # to index into the dictionary is the module path plus class name, which is
73 # unique.
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010074 switch_role_history = {}
75 admin_role_id = None
76 rbac_role_id = None
DavidPurcell029d8c32017-01-06 15:27:41 -050077
Felipe Monteiro75f23632017-04-07 15:56:26 +010078 def switch_role(self, test_obj, toggle_rbac_role=False):
Felipe Monteiro7be94e82017-07-26 02:17:08 +010079 """Switch the role used by `os_primary` Tempest credentials.
80
81 Switch the role used by `os_primary` credentials to:
82 * admin if `toggle_rbac_role` is False
83 * `CONF.rbac.rbac_test_role` if `toggle_rbac_role` is True
84
85 :param test_obj: test object of type tempest.lib.base.BaseTestCase
86 :param toggle_rbac_role: role to switch `os_primary` Tempest creds to
87 """
Felipe Monteiroe8d93e02017-07-19 20:52:20 +010088 self.user_id = test_obj.os_primary.credentials.user_id
89 self.project_id = test_obj.os_primary.credentials.tenant_id
90 self.token = test_obj.os_primary.auth_provider.get_token()
DavidPurcell029d8c32017-01-06 15:27:41 -050091
Felipe Monteiroe8d93e02017-07-19 20:52:20 +010092 LOG.debug('Switching role to: %s.', toggle_rbac_role)
DavidPurcell029d8c32017-01-06 15:27:41 -050093 try:
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010094 if not self.admin_role_id or not self.rbac_role_id:
95 self._get_roles()
DavidPurcell029d8c32017-01-06 15:27:41 -050096
Felipe Monteirob35de582017-05-05 00:16:53 +010097 self._validate_switch_role(test_obj, toggle_rbac_role)
DavidPurcell029d8c32017-01-06 15:27:41 -050098
Felipe Monteiro75f23632017-04-07 15:56:26 +010099 if toggle_rbac_role:
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100100 self._add_role_to_user(self.rbac_role_id)
DavidPurcell029d8c32017-01-06 15:27:41 -0500101 else:
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100102 self._add_role_to_user(self.admin_role_id)
DavidPurcell029d8c32017-01-06 15:27:41 -0500103 except Exception as exp:
Felipe Monteiro7be94e82017-07-26 02:17:08 +0100104 LOG.exception(exp)
DavidPurcell029d8c32017-01-06 15:27:41 -0500105 raise
Felipe Monteiro34a138c2017-03-02 17:01:37 -0500106 finally:
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100107 test_obj.os_primary.auth_provider.clear_auth()
Felipe Monteiro7be94e82017-07-26 02:17:08 +0100108 # Fernet tokens are not subsecond aware so sleep to ensure we are
109 # passing the second boundary before attempting to authenticate.
110 #
111 # FIXME(felipemonteiro): Rather than skipping sleep if the token
112 # is not uuid, this should instead be skipped if the token is not
113 # Fernet.
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100114 if not uuid_utils.is_uuid_like(self.token):
Rick Bartra89f498f2017-03-20 15:54:45 -0400115 time.sleep(1)
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100116 test_obj.os_primary.auth_provider.set_auth()
Felipe Monteiro34a138c2017-03-02 17:01:37 -0500117
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100118 def _add_role_to_user(self, role_id):
119 role_already_present = self._clear_user_roles(role_id)
120 if role_already_present:
121 return
122
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100123 self.admin_roles_client.create_user_role_on_project(
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100124 self.project_id, self.user_id, role_id)
125
126 def _clear_user_roles(self, role_id):
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100127 roles = self.admin_roles_client.list_user_roles_on_project(
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100128 self.project_id, self.user_id)['roles']
129
130 # If the user already has the role that is required, return early.
131 role_ids = [role['id'] for role in roles]
132 if role_ids == [role_id]:
133 return True
Felipe Monteirob3b7bc82017-03-03 15:58:15 -0500134
135 for role in roles:
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100136 self.admin_roles_client.delete_role_from_user_on_project(
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100137 self.project_id, self.user_id, role['id'])
138
139 return False
140
Felipe Monteiro75f23632017-04-07 15:56:26 +0100141 def _validate_switch_role(self, test_obj, toggle_rbac_role):
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100142 """Validates that the rbac role passed to `switch_role` is legal.
143
144 Throws an error for the following improper usages of `switch_role`:
145 * `switch_role` is not called with a boolean value
146 * `switch_role` is never called in a test file, except in tearDown
147 * `switch_role` is called with the same boolean value twice
Felipe Monteiro7be94e82017-07-26 02:17:08 +0100148
149 If a `skipException` is thrown then this is a legitimate reason why
150 `switch_role` is not called.
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100151 """
Felipe Monteiro75f23632017-04-07 15:56:26 +0100152 if not isinstance(toggle_rbac_role, bool):
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100153 raise rbac_exceptions.RbacResourceSetupFailed(
Felipe Monteiro75f23632017-04-07 15:56:26 +0100154 'toggle_rbac_role must be a boolean value.')
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100155
Felipe Monteiro521e5c12017-04-05 22:59:57 +0100156 # The unique key is the combination of module path plus class name.
157 class_name = test_obj.__name__ if isinstance(test_obj, type) else \
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100158 test_obj.__class__.__name__
Felipe Monteiro521e5c12017-04-05 22:59:57 +0100159 module_name = test_obj.__module__
160 key = '%s.%s' % (module_name, class_name)
161
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100162 self.switch_role_history.setdefault(key, None)
163
Felipe Monteiro75f23632017-04-07 15:56:26 +0100164 if self.switch_role_history[key] == toggle_rbac_role:
Felipe Monteiroba4881b2017-04-09 02:11:25 +0100165 # If an exception was thrown, like a skipException or otherwise,
166 # then this is a legitimate reason why `switch_role` was not
167 # called, so only raise an exception if no current exception is
168 # being handled.
169 if sys.exc_info()[0] is None:
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100170 self.switch_role_history[key] = False
Felipe Monteiro75f23632017-04-07 15:56:26 +0100171 error_message = '`toggle_rbac_role` must not be called with '\
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100172 'the same bool value twice. Make sure that you included '\
173 'a rbac_utils.switch_role method call inside the test.'
174 LOG.error(error_message)
175 raise rbac_exceptions.RbacResourceSetupFailed(error_message)
176 else:
Felipe Monteiro75f23632017-04-07 15:56:26 +0100177 self.switch_role_history[key] = toggle_rbac_role
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100178
179 def _get_roles(self):
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100180 available_roles = self.admin_roles_client.list_roles()
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100181 admin_role_id = rbac_role_id = None
182
183 for role in available_roles['roles']:
184 if role['name'] == CONF.rbac.rbac_test_role:
185 rbac_role_id = role['id']
Felipe Monteirof6b69e22017-05-04 21:55:04 +0100186 if role['name'] == CONF.identity.admin_role:
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100187 admin_role_id = role['id']
188
189 if not admin_role_id or not rbac_role_id:
190 msg = "Role with name 'admin' does not exist in the system."\
191 if not admin_role_id else "Role defined by rbac_test_role "\
192 "does not exist in the system."
193 raise rbac_exceptions.RbacResourceSetupFailed(msg)
194
195 self.admin_role_id = admin_role_id
196 self.rbac_role_id = rbac_role_id
Felipe Monteiro17e9b492017-05-27 05:45:20 +0100197
Felipe Monteiro17e9b492017-05-27 05:45:20 +0100198
Felipe Monteiro8a043fb2017-08-06 06:29:05 +0100199def is_admin():
200 """Verifies whether the current test role equals the admin role.
201
202 :returns: True if ``rbac_test_role`` is the admin role.
203 """
204 return CONF.rbac.rbac_test_role == CONF.identity.admin_role
Rick Bartraed950052017-06-29 17:20:33 -0400205
206
207@six.add_metaclass(abc.ABCMeta)
208class RbacAuthority(object):
209 # TODO(rb560u): Add documentation explaining what this class is for
210
211 @abc.abstractmethod
212 def allowed(self, rule_name, role):
213 """Determine whether the role should be able to perform the API"""
214 return