236 lines
9.6 KiB
Python
236 lines
9.6 KiB
Python
# Copyright 2017 AT&T Corporation.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import binascii
|
|
import hashlib
|
|
import hmac
|
|
import random
|
|
import time
|
|
from urllib import parse as urlparse
|
|
|
|
from oslo_serialization import jsonutils as json
|
|
|
|
from tempest.lib.common import rest_client
|
|
|
|
|
|
class OAUTHTokenClient(rest_client.RestClient):
|
|
api_version = "v3"
|
|
|
|
def _escape(self, s):
|
|
"""Escape a unicode string in an OAuth-compatible fashion."""
|
|
safe = b'~'
|
|
s = s.encode('utf-8') if isinstance(s, str) else s
|
|
s = urlparse.quote(s, safe)
|
|
if isinstance(s, bytes):
|
|
s = s.decode('utf-8')
|
|
return s
|
|
|
|
def _generate_params_with_signature(self, client_key, uri,
|
|
client_secret=None,
|
|
resource_owner_key=None,
|
|
resource_owner_secret=None,
|
|
callback_uri=None,
|
|
verifier=None,
|
|
http_method='GET'):
|
|
"""Generate OAUTH params along with signature."""
|
|
timestamp = str(int(time.time()))
|
|
nonce = str(random.getrandbits(64)) + timestamp
|
|
oauth_params = [
|
|
('oauth_nonce', nonce),
|
|
('oauth_timestamp', timestamp),
|
|
('oauth_version', '1.0'),
|
|
('oauth_signature_method', 'HMAC-SHA1'),
|
|
('oauth_consumer_key', client_key),
|
|
]
|
|
if resource_owner_key:
|
|
oauth_params.append(('oauth_token', resource_owner_key))
|
|
if callback_uri:
|
|
oauth_params.append(('oauth_callback', callback_uri))
|
|
if verifier:
|
|
oauth_params.append(('oauth_verifier', verifier))
|
|
|
|
# normalize_params
|
|
key_values = [(self._escape(k), self._escape(v))
|
|
for k, v in oauth_params]
|
|
key_values.sort()
|
|
parameter_parts = ['{0}={1}'.format(k, v) for k, v in key_values]
|
|
normalized_params = '&'.join(parameter_parts)
|
|
|
|
# normalize_uri
|
|
scheme, netloc, path, params, _, _ = urlparse.urlparse(uri)
|
|
scheme = scheme.lower()
|
|
netloc = netloc.lower()
|
|
path = path.replace('//', '/')
|
|
normalized_uri = urlparse.urlunparse((scheme, netloc, path,
|
|
params, '', ''))
|
|
|
|
# construct base string
|
|
base_string = self._escape(http_method.upper())
|
|
base_string += '&'
|
|
base_string += self._escape(normalized_uri)
|
|
base_string += '&'
|
|
base_string += self._escape(normalized_params)
|
|
|
|
# sign using hmac-sha1
|
|
key = self._escape(client_secret or '')
|
|
key += '&'
|
|
key += self._escape(resource_owner_secret or '')
|
|
key_utf8 = key.encode('utf-8')
|
|
text_utf8 = base_string.encode('utf-8')
|
|
signature = hmac.new(key_utf8, text_utf8, hashlib.sha1)
|
|
sig = binascii.b2a_base64(signature.digest())[:-1].decode('utf-8')
|
|
|
|
oauth_params.append(('oauth_signature', sig))
|
|
return oauth_params
|
|
|
|
def _generate_oauth_header(self, oauth_params):
|
|
authorization_header = {}
|
|
authorization_header_parameters_parts = []
|
|
for oauth_parameter_name, value in oauth_params:
|
|
escaped_name = self._escape(oauth_parameter_name)
|
|
escaped_value = self._escape(value)
|
|
part = '{0}="{1}"'.format(escaped_name, escaped_value)
|
|
authorization_header_parameters_parts.append(part)
|
|
|
|
authorization_header_parameters = ', '.join(
|
|
authorization_header_parameters_parts)
|
|
oauth_string = 'OAuth %s' % authorization_header_parameters
|
|
authorization_header['Authorization'] = oauth_string
|
|
|
|
return authorization_header
|
|
|
|
def create_request_token(self, consumer_key, consumer_secret, project_id):
|
|
"""Create request token.
|
|
|
|
For more information, please refer to the official API reference:
|
|
https://docs.openstack.org/api-ref/identity/v3-ext/#create-request-token
|
|
"""
|
|
endpoint = 'OS-OAUTH1/request_token'
|
|
headers = {'Requested-Project-Id': project_id}
|
|
oauth_params = self._generate_params_with_signature(
|
|
consumer_key,
|
|
self.base_url + '/' + endpoint,
|
|
client_secret=consumer_secret,
|
|
callback_uri='oob',
|
|
http_method='POST')
|
|
oauth_header = self._generate_oauth_header(oauth_params)
|
|
headers.update(oauth_header)
|
|
resp, body = self.post(endpoint,
|
|
body=None,
|
|
headers=headers)
|
|
self.expected_success(201, resp.status)
|
|
if not isinstance(body, str):
|
|
body = body.decode('utf-8')
|
|
body = dict(item.split("=") for item in body.split("&"))
|
|
return rest_client.ResponseBody(resp, body)
|
|
|
|
def authorize_request_token(self, request_token_id, role_ids):
|
|
"""Authorize request token.
|
|
|
|
For more information, please refer to the official API reference:
|
|
https://docs.openstack.org/api-ref/identity/v3-ext/#authorize-request-token
|
|
"""
|
|
roles = [{'id': role_id} for role_id in role_ids]
|
|
body = {'roles': roles}
|
|
post_body = json.dumps(body)
|
|
resp, body = self.put("OS-OAUTH1/authorize/%s" % request_token_id,
|
|
post_body)
|
|
self.expected_success(200, resp.status)
|
|
body = json.loads(body)
|
|
return rest_client.ResponseBody(resp, body)
|
|
|
|
def create_access_token(self, consumer_key, consumer_secret, request_key,
|
|
request_secret, oauth_verifier):
|
|
"""Create access token.
|
|
|
|
For more information, please refer to the official API reference:
|
|
https://docs.openstack.org/api-ref/identity/v3-ext/#create-access-token
|
|
"""
|
|
endpoint = 'OS-OAUTH1/access_token'
|
|
oauth_params = self._generate_params_with_signature(
|
|
consumer_key,
|
|
self.base_url + '/' + endpoint,
|
|
client_secret=consumer_secret,
|
|
resource_owner_key=request_key,
|
|
resource_owner_secret=request_secret,
|
|
verifier=oauth_verifier,
|
|
http_method='POST')
|
|
headers = self._generate_oauth_header(oauth_params)
|
|
resp, body = self.post(endpoint, body=None, headers=headers)
|
|
self.expected_success(201, resp.status)
|
|
if not isinstance(body, str):
|
|
body = body.decode('utf-8')
|
|
body = dict(item.split("=") for item in body.split("&"))
|
|
return rest_client.ResponseBody(resp, body)
|
|
|
|
def get_access_token(self, user_id, access_token_id):
|
|
"""Get access token.
|
|
|
|
For more information, please refer to the official API reference:
|
|
https://docs.openstack.org/api-ref/identity/v3-ext/#get-access-token
|
|
"""
|
|
resp, body = self.get("users/%s/OS-OAUTH1/access_tokens/%s"
|
|
% (user_id, access_token_id))
|
|
self.expected_success(200, resp.status)
|
|
body = json.loads(body)
|
|
return rest_client.ResponseBody(resp, body)
|
|
|
|
def revoke_access_token(self, user_id, access_token_id):
|
|
"""Revoke access token.
|
|
|
|
For more information, please refer to the official API reference:
|
|
https://docs.openstack.org/api-ref/identity/v3-ext/#revoke-access-token
|
|
"""
|
|
resp, body = self.delete("users/%s/OS-OAUTH1/access_tokens/%s"
|
|
% (user_id, access_token_id))
|
|
self.expected_success(204, resp.status)
|
|
return rest_client.ResponseBody(resp, body)
|
|
|
|
def list_access_tokens(self, user_id):
|
|
"""List access tokens.
|
|
|
|
For more information, please refer to the official API reference:
|
|
https://docs.openstack.org/api-ref/identity/v3-ext/#list-access-tokens
|
|
"""
|
|
resp, body = self.get("users/%s/OS-OAUTH1/access_tokens"
|
|
% (user_id))
|
|
self.expected_success(200, resp.status)
|
|
body = json.loads(body)
|
|
return rest_client.ResponseBody(resp, body)
|
|
|
|
def list_access_token_roles(self, user_id, access_token_id):
|
|
"""List roles for an access token.
|
|
|
|
For more information, please refer to the official API reference:
|
|
https://docs.openstack.org/api-ref/identity/v3-ext/#list-roles-for-an-access-token
|
|
"""
|
|
resp, body = self.get("users/%s/OS-OAUTH1/access_tokens/%s/roles"
|
|
% (user_id, access_token_id))
|
|
self.expected_success(200, resp.status)
|
|
body = json.loads(body)
|
|
return rest_client.ResponseBody(resp, body)
|
|
|
|
def get_access_token_role(self, user_id, access_token_id, role_id):
|
|
"""Show role details for an access token.
|
|
|
|
For more information, please refer to the official API reference:
|
|
https://docs.openstack.org/api-ref/identity/v3-ext/#show-role-details-for-an-access-token
|
|
"""
|
|
resp, body = self.get("users/%s/OS-OAUTH1/access_tokens/%s/roles/%s"
|
|
% (user_id, access_token_id, role_id))
|
|
self.expected_success(200, resp.status)
|
|
body = json.loads(body)
|
|
return rest_client.ResponseBody(resp, body)
|