Matthew Treinish | 9e26ca8 | 2016-02-23 11:43:20 -0500 | [diff] [blame] | 1 | # Copyright 2015 NEC Corporation. All rights reserved. |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may |
| 4 | # not use this file except in compliance with the License. You may obtain |
| 5 | # a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 12 | # License for the specific language governing permissions and limitations |
| 13 | # under the License. |
| 14 | |
| 15 | from oslo_log import log as logging |
| 16 | from oslo_serialization import jsonutils as json |
| 17 | |
| 18 | from tempest.lib.common import rest_client |
| 19 | from tempest.lib import exceptions |
| 20 | |
| 21 | |
| 22 | class V3TokenClient(rest_client.RestClient): |
| 23 | |
| 24 | def __init__(self, auth_url, disable_ssl_certificate_validation=None, |
zhufl | 071e94c | 2016-07-12 10:26:34 +0800 | [diff] [blame] | 25 | ca_certs=None, trace_requests=None, **kwargs): |
Andrea Frittoli | 8b8db53 | 2016-12-22 11:21:47 +0000 | [diff] [blame] | 26 | """Initialises the Token client |
| 27 | |
| 28 | :param auth_url: URL to which the token request is sent |
| 29 | :param disable_ssl_certificate_validation: pass-through to rest client |
| 30 | :param ca_certs: pass-through to rest client |
| 31 | :param trace_requests: pass-through to rest client |
| 32 | :param kwargs: any extra parameter to pass through the rest client. |
| 33 | Three kwargs are forbidden: region, service and auth_provider |
| 34 | as they are not meaningful for token client |
| 35 | """ |
Matthew Treinish | 9e26ca8 | 2016-02-23 11:43:20 -0500 | [diff] [blame] | 36 | dscv = disable_ssl_certificate_validation |
Andrea Frittoli | 8b8db53 | 2016-12-22 11:21:47 +0000 | [diff] [blame] | 37 | for unwanted_kwargs in ['region', 'service', 'auth_provider']: |
| 38 | kwargs.pop(unwanted_kwargs, None) |
Matthew Treinish | 9e26ca8 | 2016-02-23 11:43:20 -0500 | [diff] [blame] | 39 | super(V3TokenClient, self).__init__( |
| 40 | None, None, None, disable_ssl_certificate_validation=dscv, |
zhufl | 071e94c | 2016-07-12 10:26:34 +0800 | [diff] [blame] | 41 | ca_certs=ca_certs, trace_requests=trace_requests, **kwargs) |
Matthew Treinish | 9e26ca8 | 2016-02-23 11:43:20 -0500 | [diff] [blame] | 42 | |
| 43 | if auth_url is None: |
| 44 | raise exceptions.IdentityError("Couldn't determine auth_url") |
| 45 | |
| 46 | if 'auth/tokens' not in auth_url: |
| 47 | auth_url = auth_url.rstrip('/') + '/auth/tokens' |
| 48 | |
| 49 | self.auth_url = auth_url |
| 50 | |
| 51 | def auth(self, user_id=None, username=None, password=None, project_id=None, |
| 52 | project_name=None, user_domain_id=None, user_domain_name=None, |
| 53 | project_domain_id=None, project_domain_name=None, domain_id=None, |
Colleen Murphy | 0e52d4e | 2018-02-17 21:29:40 +0100 | [diff] [blame] | 54 | domain_name=None, token=None, app_cred_id=None, |
| 55 | app_cred_secret=None): |
Matthew Treinish | 9e26ca8 | 2016-02-23 11:43:20 -0500 | [diff] [blame] | 56 | """Obtains a token from the authentication service |
| 57 | |
| 58 | :param user_id: user id |
| 59 | :param username: user name |
| 60 | :param user_domain_id: the user domain id |
| 61 | :param user_domain_name: the user domain name |
| 62 | :param project_domain_id: the project domain id |
| 63 | :param project_domain_name: the project domain name |
| 64 | :param domain_id: a domain id to scope to |
| 65 | :param domain_name: a domain name to scope to |
| 66 | :param project_id: a project id to scope to |
| 67 | :param project_name: a project name to scope to |
| 68 | :param token: a token to re-scope. |
| 69 | |
| 70 | Accepts different combinations of credentials. |
| 71 | Sample sample valid combinations: |
| 72 | - token |
| 73 | - token, project_name, project_domain_id |
| 74 | - user_id, password |
| 75 | - username, password, user_domain_id |
| 76 | - username, password, project_name, user_domain_id, project_domain_id |
| 77 | Validation is left to the server side. |
| 78 | """ |
| 79 | creds = { |
| 80 | 'auth': { |
| 81 | 'identity': { |
| 82 | 'methods': [], |
| 83 | } |
| 84 | } |
| 85 | } |
| 86 | id_obj = creds['auth']['identity'] |
| 87 | if token: |
| 88 | id_obj['methods'].append('token') |
| 89 | id_obj['token'] = { |
| 90 | 'id': token |
| 91 | } |
| 92 | |
| 93 | if (user_id or username) and password: |
| 94 | id_obj['methods'].append('password') |
| 95 | id_obj['password'] = { |
| 96 | 'user': { |
| 97 | 'password': password, |
| 98 | } |
| 99 | } |
| 100 | if user_id: |
| 101 | id_obj['password']['user']['id'] = user_id |
| 102 | else: |
| 103 | id_obj['password']['user']['name'] = username |
| 104 | |
| 105 | _domain = None |
| 106 | if user_domain_id is not None: |
| 107 | _domain = dict(id=user_domain_id) |
| 108 | elif user_domain_name is not None: |
| 109 | _domain = dict(name=user_domain_name) |
| 110 | if _domain: |
| 111 | id_obj['password']['user']['domain'] = _domain |
| 112 | |
Colleen Murphy | 0e52d4e | 2018-02-17 21:29:40 +0100 | [diff] [blame] | 113 | if app_cred_id and app_cred_secret: |
| 114 | id_obj['methods'].append('application_credential') |
| 115 | id_obj['application_credential'] = { |
| 116 | 'id': app_cred_id, |
| 117 | 'secret': app_cred_secret, |
| 118 | } |
| 119 | |
Matthew Treinish | 9e26ca8 | 2016-02-23 11:43:20 -0500 | [diff] [blame] | 120 | if (project_id or project_name): |
| 121 | _project = dict() |
| 122 | |
| 123 | if project_id: |
| 124 | _project['id'] = project_id |
| 125 | elif project_name: |
| 126 | _project['name'] = project_name |
| 127 | |
| 128 | if project_domain_id is not None: |
| 129 | _project['domain'] = {'id': project_domain_id} |
| 130 | elif project_domain_name is not None: |
| 131 | _project['domain'] = {'name': project_domain_name} |
| 132 | |
| 133 | creds['auth']['scope'] = dict(project=_project) |
| 134 | elif domain_id: |
| 135 | creds['auth']['scope'] = dict(domain={'id': domain_id}) |
| 136 | elif domain_name: |
| 137 | creds['auth']['scope'] = dict(domain={'name': domain_name}) |
| 138 | |
| 139 | body = json.dumps(creds, sort_keys=True) |
| 140 | resp, body = self.post(self.auth_url, body=body) |
| 141 | self.expected_success(201, resp.status) |
| 142 | return rest_client.ResponseBody(resp, body) |
| 143 | |
| 144 | def request(self, method, url, extra_headers=False, headers=None, |
Jordan Pittier | 4408c4a | 2016-04-29 15:05:09 +0200 | [diff] [blame] | 145 | body=None, chunked=False): |
| 146 | """A simple HTTP request interface. |
| 147 | |
| 148 | Note: this overloads the `request` method from the parent class and |
| 149 | thus must implement the same method signature. |
| 150 | """ |
Matthew Treinish | 9e26ca8 | 2016-02-23 11:43:20 -0500 | [diff] [blame] | 151 | if headers is None: |
| 152 | # Always accept 'json', for xml token client too. |
| 153 | # Because XML response is not easily |
| 154 | # converted to the corresponding JSON one |
| 155 | headers = self.get_headers(accept_type="json") |
| 156 | elif extra_headers: |
| 157 | try: |
| 158 | headers.update(self.get_headers(accept_type="json")) |
| 159 | except (ValueError, TypeError): |
| 160 | headers = self.get_headers(accept_type="json") |
| 161 | |
| 162 | resp, resp_body = self.raw_request(url, method, |
| 163 | headers=headers, body=body) |
| 164 | self._log_request(method, url, resp, req_headers=headers, |
| 165 | req_body='<omitted>', resp_body=resp_body) |
| 166 | |
| 167 | if resp.status in [401, 403]: |
| 168 | resp_body = json.loads(resp_body) |
| 169 | raise exceptions.Unauthorized(resp_body['error']['message']) |
| 170 | elif resp.status not in [200, 201, 204]: |
| 171 | raise exceptions.IdentityError( |
| 172 | 'Unexpected status code {0}'.format(resp.status)) |
| 173 | |
| 174 | return resp, json.loads(resp_body) |
| 175 | |
| 176 | def get_token(self, **kwargs): |
| 177 | """Returns (token id, token data) for supplied credentials""" |
| 178 | |
| 179 | auth_data = kwargs.pop('auth_data', False) |
| 180 | |
| 181 | if not (kwargs.get('user_domain_id') or |
| 182 | kwargs.get('user_domain_name')): |
| 183 | kwargs['user_domain_name'] = 'Default' |
| 184 | |
| 185 | if not (kwargs.get('project_domain_id') or |
| 186 | kwargs.get('project_domain_name')): |
| 187 | kwargs['project_domain_name'] = 'Default' |
| 188 | |
| 189 | body = self.auth(**kwargs) |
| 190 | |
| 191 | token = body.response.get('x-subject-token') |
| 192 | if auth_data: |
| 193 | return token, body['token'] |
| 194 | else: |
| 195 | return token |
| 196 | |
| 197 | |
| 198 | class V3TokenClientJSON(V3TokenClient): |
| 199 | LOG = logging.getLogger(__name__) |
| 200 | |
| 201 | def _warn(self): |
Jordan Pittier | 525ec71 | 2016-12-07 17:51:26 +0100 | [diff] [blame] | 202 | self.LOG.warning("%s class was deprecated and renamed to %s", |
| 203 | self.__class__.__name__, 'V3TokenClient') |
Matthew Treinish | 9e26ca8 | 2016-02-23 11:43:20 -0500 | [diff] [blame] | 204 | |
| 205 | def __init__(self, *args, **kwargs): |
| 206 | self._warn() |
| 207 | super(V3TokenClientJSON, self).__init__(*args, **kwargs) |