blob: 70fe0b7b43025daa3394b18fce27099e001f4a16 [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 Monteirofa01d5f2017-04-01 06:18:25 +010016import sys
17import testtools
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 Monteirofa01d5f2017-04-01 06:18:25 +010021import oslo_utils.uuidutils as uuid_utils
22import six
23
24from tempest import config
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
32class Singleton(type):
33 _instances = {}
34
35 def __call__(cls, *args, **kwargs):
36 if cls not in cls._instances:
37 cls._instances[cls] = super(Singleton, cls).__call__(*args,
38 **kwargs)
39 return cls._instances[cls]
40
41
42@six.add_metaclass(Singleton)
43class RbacUtils(object):
DavidPurcell029d8c32017-01-06 15:27:41 -050044
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010045 # References the last value of `switch_to_rbac_role` that was passed to
46 # `switch_role`. Used for ensuring that `switch_role` is correctly used
47 # in a test file, so that false positives are prevented. The key used
Felipe Monteiro521e5c12017-04-05 22:59:57 +010048 # to index into the dictionary is the module path plus class name, which is
49 # unique.
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010050 switch_role_history = {}
51 admin_role_id = None
52 rbac_role_id = None
DavidPurcell029d8c32017-01-06 15:27:41 -050053
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010054 def switch_role(self, test_obj, switchToRbacRole=False):
55 self.user_id = test_obj.auth_provider.credentials.user_id
56 self.project_id = test_obj.auth_provider.credentials.tenant_id
57 self.token = test_obj.auth_provider.get_token()
58 self.identity_version = test_obj.get_identity_version()
DavidPurcell029d8c32017-01-06 15:27:41 -050059
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010060 if self.identity_version.endswith('v3'):
61 self.roles_client = test_obj.os_admin.roles_v3_client
62 else:
63 self.roles_client = test_obj.os_admin.roles_client
64
DavidPurcell029d8c32017-01-06 15:27:41 -050065 LOG.debug('Switching role to: %s', switchToRbacRole)
DavidPurcell029d8c32017-01-06 15:27:41 -050066
67 try:
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010068 if not self.admin_role_id or not self.rbac_role_id:
69 self._get_roles()
DavidPurcell029d8c32017-01-06 15:27:41 -050070
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010071 rbac_utils._validate_switch_role(self, test_obj, switchToRbacRole)
DavidPurcell029d8c32017-01-06 15:27:41 -050072
73 if switchToRbacRole:
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010074 self._add_role_to_user(self.rbac_role_id)
DavidPurcell029d8c32017-01-06 15:27:41 -050075 else:
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010076 self._add_role_to_user(self.admin_role_id)
DavidPurcell029d8c32017-01-06 15:27:41 -050077 except Exception as exp:
78 LOG.error(exp)
79 raise
Felipe Monteiro34a138c2017-03-02 17:01:37 -050080 finally:
Felipe Monteiro23923f02017-03-17 02:15:07 +000081 # NOTE(felipemonteiro): These two comments below are copied from
82 # tempest.api.identity.v2/v3.test_users.
83 #
84 # Reset auth again to verify the password restore does work.
85 # Clear auth restores the original credentials and deletes
86 # cached auth data.
87 test_obj.auth_provider.clear_auth()
88 # Fernet tokens are not subsecond aware and Keystone should only be
89 # precise to the second. Sleep to ensure we are passing the second
Rick Bartra89f498f2017-03-20 15:54:45 -040090 # boundary before attempting to authenticate. If token is of type
91 # uuid, then do not sleep.
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010092 if not uuid_utils.is_uuid_like(self.token):
Rick Bartra89f498f2017-03-20 15:54:45 -040093 time.sleep(1)
Felipe Monteiro23923f02017-03-17 02:15:07 +000094 test_obj.auth_provider.set_auth()
Felipe Monteiro34a138c2017-03-02 17:01:37 -050095
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010096 def _add_role_to_user(self, role_id):
97 role_already_present = self._clear_user_roles(role_id)
98 if role_already_present:
99 return
100
101 self.roles_client.create_user_role_on_project(
102 self.project_id, self.user_id, role_id)
103
104 def _clear_user_roles(self, role_id):
105 roles = self.roles_client.list_user_roles_on_project(
106 self.project_id, self.user_id)['roles']
107
108 # If the user already has the role that is required, return early.
109 role_ids = [role['id'] for role in roles]
110 if role_ids == [role_id]:
111 return True
Felipe Monteirob3b7bc82017-03-03 15:58:15 -0500112
113 for role in roles:
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100114 self.roles_client.delete_role_from_user_on_project(
115 self.project_id, self.user_id, role['id'])
116
117 return False
118
119 def _validate_switch_role(self, test_obj, switchToRbacRole):
120 """Validates that the rbac role passed to `switch_role` is legal.
121
122 Throws an error for the following improper usages of `switch_role`:
123 * `switch_role` is not called with a boolean value
124 * `switch_role` is never called in a test file, except in tearDown
125 * `switch_role` is called with the same boolean value twice
126 """
127 if not isinstance(switchToRbacRole, bool):
128 raise rbac_exceptions.RbacResourceSetupFailed(
129 'switchToRbacRole must be a boolean value.')
130
Felipe Monteiro521e5c12017-04-05 22:59:57 +0100131 # The unique key is the combination of module path plus class name.
132 class_name = test_obj.__name__ if isinstance(test_obj, type) else \
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100133 test_obj.__class__.__name__
Felipe Monteiro521e5c12017-04-05 22:59:57 +0100134 module_name = test_obj.__module__
135 key = '%s.%s' % (module_name, class_name)
136
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100137 self.switch_role_history.setdefault(key, None)
138
139 if self.switch_role_history[key] == switchToRbacRole:
140 # If the test was skipped, then this is a legitimate use case,
141 # so do not throw an exception.
142 exc_value = sys.exc_info()[1]
143 if not isinstance(exc_value, testtools.TestCase.skipException):
144 self.switch_role_history[key] = False
145 error_message = '`switchToRbacRole` must not be called with '\
146 'the same bool value twice. Make sure that you included '\
147 'a rbac_utils.switch_role method call inside the test.'
148 LOG.error(error_message)
149 raise rbac_exceptions.RbacResourceSetupFailed(error_message)
150 else:
151 self.switch_role_history[key] = switchToRbacRole
152
153 def _get_roles(self):
154 available_roles = self.roles_client.list_roles()
155 admin_role_id = rbac_role_id = None
156
157 for role in available_roles['roles']:
158 if role['name'] == CONF.rbac.rbac_test_role:
159 rbac_role_id = role['id']
160 if role['name'] == 'admin':
161 admin_role_id = role['id']
162
163 if not admin_role_id or not rbac_role_id:
164 msg = "Role with name 'admin' does not exist in the system."\
165 if not admin_role_id else "Role defined by rbac_test_role "\
166 "does not exist in the system."
167 raise rbac_exceptions.RbacResourceSetupFailed(msg)
168
169 self.admin_role_id = admin_role_id
170 self.rbac_role_id = rbac_role_id
Felipe Monteirob3b7bc82017-03-03 15:58:15 -0500171
Felipe Monteiro34a138c2017-03-02 17:01:37 -0500172rbac_utils = RbacUtils