Abstract authentication function

Abstract authentication function so plugins for other authentication
backends can be implemented in cases where keystone is not used. Currently,
mistral is hard coded to support keystone and keycloak.

Change-Id: If6ff35e91c3d35c2741332c7e739bb92b1234c54
Implements: blueprint mistral-abstract-auth
This commit is contained in:
Winson Chan 2016-09-16 01:28:10 +00:00
parent 5438b20c46
commit 55d55bcdf1
12 changed files with 374 additions and 348 deletions

View File

@ -12,58 +12,15 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import six
from mistralclient.api.v2 import client as client_v2 from mistralclient.api.v2 import client as client_v2
from mistralclient.auth import auth_types
def client(mistral_url=None, username=None, api_key=None, def client(auth_type='keystone', **kwargs):
project_name=None, auth_url=None, project_id=None, return client_v2.Client(auth_type=auth_type, **kwargs)
endpoint_type='publicURL', service_type='workflow',
auth_token=None, user_id=None, cacert=None, insecure=False,
profile=None, auth_type=auth_types.KEYSTONE, client_id=None,
client_secret=None, target_username=None, target_api_key=None,
target_project_name=None, target_auth_url=None,
target_project_id=None, target_auth_token=None,
target_user_id=None, target_cacert=None, target_insecure=False,
**kwargs):
if mistral_url and not isinstance(mistral_url, six.string_types):
raise RuntimeError('Mistral url should be a string.')
return client_v2.Client(
mistral_url=mistral_url,
username=username,
api_key=api_key,
project_name=project_name,
auth_url=auth_url,
project_id=project_id,
endpoint_type=endpoint_type,
service_type=service_type,
auth_token=auth_token,
user_id=user_id,
cacert=cacert,
insecure=insecure,
profile=profile,
auth_type=auth_type,
client_id=client_id,
client_secret=client_secret,
target_username=target_username,
target_api_key=target_api_key,
target_project_name=target_project_name,
target_auth_url=target_auth_url,
target_project_id=target_project_id,
target_auth_token=target_auth_token,
target_user_id=target_user_id,
target_cacert=target_cacert,
target_insecure=target_insecure,
**kwargs
)
def determine_client_version(mistral_version): def determine_client_version(mistral_version):
if mistral_version.find("v2") != -1: if mistral_version.find("v2") != -1:
return 2 return 2
raise RuntimeError("Can not determine mistral API version") raise RuntimeError("Cannot determine mistral API version")

View File

@ -37,31 +37,31 @@ def log_request(func):
class HTTPClient(object): class HTTPClient(object):
def __init__(self, base_url, token=None, project_id=None, user_id=None, def __init__(self, base_url, **kwargs):
cacert=None, insecure=False, target_token=None,
target_auth_uri=None, **kwargs):
self.base_url = base_url self.base_url = base_url
self.token = token self.auth_token = kwargs.get('auth_token', None)
self.project_id = project_id self.project_id = kwargs.get('project_id', None)
self.user_id = user_id self.user_id = kwargs.get('user_id', None)
self.target_token = target_token self.target_auth_token = kwargs.get('target_auth_token', None)
self.target_auth_uri = target_auth_uri self.target_auth_url = kwargs.get('target_auth_url', None)
self.cacert = kwargs.get('cacert', None)
self.insecure = kwargs.get('insecure', False)
self.ssl_options = {} self.ssl_options = {}
if self.base_url.startswith('https'): if self.base_url.startswith('https'):
if cacert and not os.path.exists(cacert): if self.cacert and not os.path.exists(self.cacert):
raise ValueError('Unable to locate cacert file ' raise ValueError('Unable to locate cacert file '
'at %s.' % cacert) 'at %s.' % self.cacert)
if cacert and insecure: if self.cacert and self.insecure:
LOG.warning('Client is set to not verify even though ' LOG.warning('Client is set to not verify even though '
'cacert is provided.') 'cacert is provided.')
if insecure: if self.insecure:
self.ssl_options['verify'] = False self.ssl_options['verify'] = False
else: else:
if cacert: if self.cacert:
self.ssl_options['verify'] = cacert self.ssl_options['verify'] = self.cacert
else: else:
self.ssl_options['verify'] = True self.ssl_options['verify'] = True
@ -107,9 +107,9 @@ class HTTPClient(object):
if not headers: if not headers:
headers = {} headers = {}
token = headers.get('x-auth-token', self.token) auth_token = headers.get('x-auth-token', self.auth_token)
if token: if auth_token:
headers['x-auth-token'] = token headers['x-auth-token'] = auth_token
project_id = headers.get('X-Project-Id', self.project_id) project_id = headers.get('X-Project-Id', self.project_id)
if project_id: if project_id:
@ -119,14 +119,18 @@ class HTTPClient(object):
if user_id: if user_id:
headers['X-User-Id'] = user_id headers['X-User-Id'] = user_id
target_token = headers.get('X-Target-Auth-Token', self.target_token) target_auth_token = headers.get(
if target_token: 'X-Target-Auth-Token',
headers['X-Target-Auth-Token'] = target_token self.target_auth_token
)
target_auth_uri = headers.get('X-Target-Auth-Uri', if target_auth_token:
self.target_auth_uri) headers['X-Target-Auth-Token'] = target_auth_token
if target_auth_uri:
headers['X-Target-Auth-Uri'] = target_auth_uri target_auth_url = headers.get('X-Target-Auth-Uri',
self.target_auth_url)
if target_auth_url:
headers['X-Target-Auth-Uri'] = target_auth_url
if osprofiler_web: if osprofiler_web:
# Add headers for osprofiler. # Add headers for osprofiler.

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import copy
import six import six
from oslo_utils import importutils from oslo_utils import importutils
@ -28,9 +29,8 @@ from mistralclient.api.v2 import services
from mistralclient.api.v2 import tasks from mistralclient.api.v2 import tasks
from mistralclient.api.v2 import workbooks from mistralclient.api.v2 import workbooks
from mistralclient.api.v2 import workflows from mistralclient.api.v2 import workflows
from mistralclient.auth import auth_types from mistralclient import auth
from mistralclient.auth import keycloak
from mistralclient.auth import keystone
osprofiler_profiler = importutils.try_import("osprofiler.profiler") osprofiler_profiler = importutils.try_import("osprofiler.profiler")
@ -38,62 +38,25 @@ _DEFAULT_MISTRAL_URL = "http://localhost:8989/v2"
class Client(object): class Client(object):
def __init__(self, mistral_url=None, username=None, api_key=None, def __init__(self, auth_type='keystone', **kwargs):
project_name=None, auth_url=None, project_id=None, req = copy.deepcopy(kwargs)
endpoint_type='publicURL', service_type='workflowv2', mistral_url = req.get('mistral_url')
auth_token=None, user_id=None, cacert=None, insecure=False, auth_url = req.get('auth_url')
profile=None, auth_type=auth_types.KEYSTONE, client_id=None, auth_token = req.get('auth_token')
client_secret=None, target_username=None, target_api_key=None, project_id = req.get('project_id')
target_project_name=None, target_auth_url=None, user_id = req.get('user_id')
target_project_id=None, target_auth_token=None, profile = req.get('profile')
target_user_id=None, target_cacert=None,
target_insecure=False, **kwargs):
if mistral_url and not isinstance(mistral_url, six.string_types): if mistral_url and not isinstance(mistral_url, six.string_types):
raise RuntimeError('Mistral url should be a string.') raise RuntimeError('Mistral url should be a string.')
if auth_url: if auth_url and not auth_token:
if auth_type == auth_types.KEYSTONE: auth_handler = auth.get_auth_handler(auth_type)
(mistral_url, auth_token, project_id, user_id) = ( auth_response = auth_handler.authenticate(req) or {}
keystone.authenticate( mistral_url = auth_response.get('mistral_url') or mistral_url
mistral_url, req['auth_token'] = auth_response.get('token')
username, req['project_id'] = auth_response.get('project_id') or project_id
api_key, req['user_id'] = auth_response.get('user_id') or user_id
project_name,
auth_url,
project_id,
endpoint_type,
service_type,
auth_token,
user_id,
cacert,
insecure
)
)
elif auth_type == auth_types.KEYCLOAK_OIDC:
auth_token = keycloak.authenticate(
auth_url,
client_id,
client_secret,
project_name,
username,
api_key,
auth_token,
cacert,
insecure
)
# In case of KeyCloak OpenID Connect we can treat project
# name and id in the same way because KeyCloak realm is
# essentially a different OpenID Connect Issuer which in
# KeyCloak is represented just as a URL path component
# (see http://openid.net/specs/openid-connect-core-1_0.html).
project_id = project_name
else:
raise RuntimeError(
'Invalid authentication type [value=%s, valid_values=%s]'
% (auth_type, auth_types.ALL)
)
if not mistral_url: if not mistral_url:
mistral_url = _DEFAULT_MISTRAL_URL mistral_url = _DEFAULT_MISTRAL_URL
@ -101,33 +64,7 @@ class Client(object):
if profile: if profile:
osprofiler_profiler.init(profile) osprofiler_profiler.init(profile)
if target_auth_url: http_client = httpclient.HTTPClient(mistral_url, **req)
keystone.authenticate(
mistral_url,
target_username,
target_api_key,
target_project_name,
target_auth_url,
target_project_id,
endpoint_type,
service_type,
target_auth_token,
target_user_id,
target_cacert,
target_insecure
)
http_client = httpclient.HTTPClient(
mistral_url,
auth_token,
project_id,
user_id,
cacert=cacert,
insecure=insecure,
target_token=target_auth_token,
target_auth_uri=target_auth_url,
**kwargs
)
# Create all resource managers. # Create all resource managers.
self.workbooks = workbooks.WorkbookManager(http_client) self.workbooks = workbooks.WorkbookManager(http_client)

View File

@ -0,0 +1,37 @@
# Copyright 2016 - Brocade Communications Systems, Inc.
#
# 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 abc
import six
from stevedore import driver
def get_auth_handler(auth_type):
mgr = driver.DriverManager(
'mistralclient.auth',
auth_type,
invoke_on_load=True
)
return mgr.driver
@six.add_metaclass(abc.ABCMeta)
class AuthHandler(object):
"""Abstract base class for an authentication plugin."""
@abc.abstractmethod
def authenticate(self, req):
raise NotImplementedError()

View File

@ -12,15 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from stevedore import extension
# Valid authentication types. # Valid authentication types.
ALL = extension.ExtensionManager(
# Standard Keystone authentication. namespace='mistralclient.auth',
KEYSTONE = 'keystone' invoke_on_load=False
).names()
# Authentication using OpenID Connect protocol but specific to KeyCloak
# server regarding multi-tenancy support. KeyCloak has a notion of realm
# used as an analog of Keystone project/tenant.
KEYCLOAK_OIDC = 'keycloak-oidc'
ALL = [KEYSTONE, KEYCLOAK_OIDC]

View File

@ -16,119 +16,147 @@ import logging
import pprint import pprint
import requests import requests
from mistralclient import auth
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
def authenticate(auth_url, client_id, client_secret, realm_name, class KeycloakAuthHandler(auth.AuthHandler):
username=None, password=None, access_token=None,
cacert=None, insecure=False):
"""Performs authentication using Keycloak OpenID Protocol.
:param auth_url: Base authentication url of KeyCloak server (e.g. def authenticate(self, req):
"https://my.keycloak:8443/auth" """Performs authentication using Keycloak OpenID Protocol.
:param client_id: Client ID (according to OpenID Connect protocol).
:param client_secret: Client secret (according to OpenID Connect protocol).
:param realm_name: KeyCloak realm name.
:param username: User name (Optional, if None then access_token must be
provided).
:param password: Password (Optional).
:param access_token: Access token. If passed, username and password are
not used and this method just validates the token and refreshes it,
if needed. (Optional, if None then username must be provided)
:param cacert: SSL certificate file (Optional).
:param insecure: If True, SSL certificate is not verified (Optional).
""" :param req: Request dict containing list of parameters required
if not auth_url: for Keycloak authentication.
raise ValueError('Base authentication url is not provided.')
if not client_id: auth_url: Base authentication url of KeyCloak server (e.g.
raise ValueError('Client ID is not provided.') "https://my.keycloak:8443/auth"
client_id: Client ID (according to OpenID Connect protocol).
client_secret: Client secret (according to OpenID Connect
protocol).
realm_name: KeyCloak realm name.
username: User name (Optional, if None then access_token must be
provided).
password: Password (Optional).
access_token: Access token. If passed, username and password are
not used and this method just validates the token and refreshes
it if needed (Optional, if None then username must be
provided).
cacert: SSL certificate file (Optional).
insecure: If True, SSL certificate is not verified (Optional).
if not client_secret: """
raise ValueError('Client secret is not provided.') if not isinstance(req, dict):
raise TypeError('The input "req" is not typeof dict.')
if not realm_name: auth_url = req.get('auth_url')
raise ValueError('Project(realm) name is not provided.') client_id = req.get('client_id')
client_secret = req.get('client_secret')
realm_name = req.get('realm_name')
username = req.get('username')
password = req.get('password')
access_token = req.get('access_token')
cacert = req.get('cacert')
insecure = req.get('insecure', False)
if username and access_token: if not auth_url:
raise ValueError( raise ValueError('Base authentication url is not provided.')
"User name and access token can't be provided at the same time."
if not client_id:
raise ValueError('Client ID is not provided.')
if not client_secret:
raise ValueError('Client secret is not provided.')
if not realm_name:
raise ValueError('Project(realm) name is not provided.')
if username and access_token:
raise ValueError(
"User name and access token can't be "
"provided at the same time."
)
if not username and not access_token:
raise ValueError(
'Either user name or access token must be provided.'
)
if access_token:
response = self._authenticate_with_token(
auth_url,
client_id,
client_secret,
access_token,
cacert,
insecure
)
else:
response = self._authenticate_with_password(
auth_url,
client_id,
client_secret,
realm_name,
username,
password,
cacert,
insecure
)
response['project_id'] = realm_name
return response
def _authenticate_with_token(auth_url, client_id, client_secret,
auth_token, cacert=None, insecure=None):
# TODO(rakhmerov): Implement.
raise NotImplementedError
def _authenticate_with_password(auth_url, client_id, client_secret,
realm_name, username, password,
cacert=None, insecure=None):
access_token_endpoint = (
"%s/realms/%s/protocol/openid-connect/token" %
(auth_url, realm_name)
) )
if access_token: client_auth = (client_id, client_secret)
return _authenticate_with_token(
auth_url, body = {
client_id, 'grant_type': 'password',
client_secret, 'username': username,
access_token, 'password': password,
cacert, 'scope': 'profile'
insecure }
resp = requests.post(
access_token_endpoint,
auth=client_auth,
data=body,
verify=not insecure
) )
if not username: try:
raise ValueError('Either user name or access token must be provided.') resp.raise_for_status()
except Exception as e:
raise Exception("Failed to get access token:\n %s" % str(e))
return _authenticate_with_password( LOG.debug(
auth_url, "HTTP response from OIDC provider: %s" %
client_id, pprint.pformat(resp.json())
client_secret, )
realm_name,
username,
password,
cacert,
insecure
)
return resp.json()['access_token']
def _authenticate_with_token(auth_url, client_id, client_secret, auth_token,
cacert=None, insecure=None):
# TODO(rakhmerov): Implement.
raise NotImplementedError
def _authenticate_with_password(auth_url, client_id, client_secret,
realm_name, username, password,
cacert=None, insecure=None):
access_token_endpoint = (
"%s/realms/%s/protocol/openid-connect/token" % (auth_url, realm_name)
)
client_auth = (client_id, client_secret)
body = {
'grant_type': 'password',
'username': username,
'password': password,
'scope': 'profile'
}
resp = requests.post(
access_token_endpoint,
auth=client_auth,
data=body,
verify=not insecure
)
try:
resp.raise_for_status()
except Exception as e:
raise Exception("Failed to get access token:\n %s" % str(e))
LOG.debug(
"HTTP response from OIDC provider: %s" % pprint.pformat(resp.json())
)
return resp.json()['access_token']
# An example of using KeyCloak OpenID authentication. # An example of using KeyCloak OpenID authentication.
if __name__ == '__main__': if __name__ == '__main__':
print("Using username/password to get access token from KeyCloak...") print("Using username/password to get access token from KeyCloak...")
a_token = authenticate( auth_handler = KeycloakAuthHandler()
a_token = auth_handler.authenticate(
"https://my.keycloak:8443/auth", "https://my.keycloak:8443/auth",
client_id="mistral_client", client_id="mistral_client",
client_secret="4a080907-921b-409a-b793-c431609c3a47", client_secret="4a080907-921b-409a-b793-c431609c3a47",

View File

@ -12,60 +12,115 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from mistralclient import auth
def authenticate(mistral_url=None, username=None,
api_key=None, project_name=None, auth_url=None,
project_id=None, endpoint_type='publicURL',
service_type='workflowv2', auth_token=None, user_id=None,
cacert=None, insecure=False):
if project_name and project_id:
raise RuntimeError(
'Only project name or project id should be set'
)
if username and user_id:
raise RuntimeError(
'Only user name or user id should be set'
)
keystone_client = _get_keystone_client(auth_url)
keystone = keystone_client.Client(
username=username,
user_id=user_id,
password=api_key,
token=auth_token,
tenant_id=project_id,
tenant_name=project_name,
auth_url=auth_url,
endpoint=auth_url,
cacert=cacert,
insecure=insecure
)
keystone.authenticate()
token = keystone.auth_token
user_id = keystone.user_id
project_id = keystone.project_id
if not mistral_url:
try:
mistral_url = keystone.service_catalog.url_for(
service_type=service_type,
endpoint_type=endpoint_type
)
except Exception:
mistral_url = None
return mistral_url, token, project_id, user_id
def _get_keystone_client(auth_url): def _get_keystone_client(auth_url):
if "v2.0" in auth_url: if 'v2.0' in auth_url:
from keystoneclient.v2_0 import client from keystoneclient.v2_0 import client
else: else:
from keystoneclient.v3 import client from keystoneclient.v3 import client
return client return client
class KeystoneAuthHandler(auth.AuthHandler):
def authenticate(self, req):
"""Performs authentication via Keystone.
:param req: Request dict containing list of parameters required
for Keystone authentication.
"""
if not isinstance(req, dict):
raise TypeError('The input "req" is not typeof dict.')
auth_url = req.get('auth_url')
mistral_url = req.get('mistral_url')
endpoint_type = req.get('endpoint_type', 'publicURL')
service_type = req.get('service_type', 'workflow2')
username = req.get('username')
user_id = req.get('user_id')
api_key = req.get('api_key')
auth_token = req.get('auth_token')
project_name = req.get('project_name')
project_id = req.get('project_id')
cacert = req.get('cacert')
insecure = req.get('insecure', False)
target_username = req.get('target_username')
target_api_key = req.get('target_api_key')
target_project_name = req.get('target_project_name')
target_auth_url = req.get('target_auth_url')
target_project_id = req.get('target_project_id')
target_auth_token = req.get('target_auth_token')
target_user_id = req.get('target_user_id')
target_cacert = req.get('target_cacert')
target_insecure = req.get('target_insecure')
if project_name and project_id:
raise RuntimeError(
'Only project name or project id should be set'
)
if username and user_id:
raise RuntimeError(
'Only user name or user id should be set'
)
if auth_url:
keystone_client = _get_keystone_client(auth_url)
keystone = keystone_client.Client(
username=username,
user_id=user_id,
password=api_key,
token=auth_token,
tenant_id=project_id,
tenant_name=project_name,
auth_url=auth_url,
endpoint=auth_url,
cacert=cacert,
insecure=insecure
)
keystone.authenticate()
auth_token = keystone.auth_token
user_id = keystone.user_id
project_id = keystone.project_id
if target_auth_url:
target_keystone_client = _get_keystone_client(target_auth_url)
target_keystone = target_keystone_client.Client(
username=target_username,
user_id=target_user_id,
password=target_api_key,
token=target_auth_token,
tenant_id=target_project_id,
tenant_name=target_project_name,
auth_url=target_auth_url,
endpoint=target_auth_url,
cacert=target_cacert,
insecure=target_insecure
)
target_keystone.authenticate()
if not mistral_url:
try:
mistral_url = keystone.service_catalog.url_for(
service_type=service_type,
endpoint_type=endpoint_type
)
except Exception:
mistral_url = None
return {
'mistral_url': mistral_url,
'token': auth_token,
'project_id': target_project_id if target_auth_url else project_id,
'user_id': target_user_id if target_auth_url else user_id,
'target_auth_token': target_auth_token,
'target_auth_url': target_auth_url
}

View File

@ -317,7 +317,7 @@ class MistralShell(app.App):
'--auth-type', '--auth-type',
action='store', action='store',
dest='auth_type', dest='auth_type',
default=c.env('MISTRAL_AUTH_TYPE', default=auth_types.KEYSTONE), default=c.env('MISTRAL_AUTH_TYPE', default='keystone'),
help='Authentication type. Valid options are: %s.' help='Authentication type. Valid options are: %s.'
' (Env: MISTRAL_AUTH_TYPE)' % auth_types.ALL ' (Env: MISTRAL_AUTH_TYPE)' % auth_types.ALL
) )

View File

@ -92,16 +92,15 @@ class BaseClientTests(base.BaseTestCase):
expected_args = ( expected_args = (
MISTRAL_HTTP_URL, MISTRAL_HTTP_URL,
keystone_client_instance.auth_token,
keystone_client_instance.project_id,
keystone_client_instance.user_id
) )
expected_kwargs = { expected_kwargs = {
'cacert': None, 'username': 'mistral',
'insecure': False, 'project_name': 'mistral',
'target_auth_uri': None, 'auth_url': AUTH_HTTP_URL_v3,
'target_token': None 'auth_token': keystone_client_instance.auth_token,
'project_id': keystone_client_instance.project_id,
'user_id': keystone_client_instance.user_id
} }
client.client( client.client(
@ -111,8 +110,8 @@ class BaseClientTests(base.BaseTestCase):
) )
self.assertTrue(mocked.called) self.assertTrue(mocked.called)
self.assertEqual(mocked.call_args[0], expected_args) self.assertEqual(expected_args, mocked.call_args[0])
self.assertDictEqual(mocked.call_args[1], expected_kwargs) self.assertDictEqual(expected_kwargs, mocked.call_args[1])
@mock.patch('keystoneclient.v3.client.Client') @mock.patch('keystoneclient.v3.client.Client')
@mock.patch('mistralclient.api.httpclient.HTTPClient') @mock.patch('mistralclient.api.httpclient.HTTPClient')
@ -126,16 +125,18 @@ class BaseClientTests(base.BaseTestCase):
expected_args = ( expected_args = (
MISTRAL_HTTPS_URL, MISTRAL_HTTPS_URL,
keystone_client_instance.auth_token,
keystone_client_instance.project_id,
keystone_client_instance.user_id
) )
expected_kwargs = { expected_kwargs = {
'mistral_url': MISTRAL_HTTPS_URL,
'username': 'mistral',
'project_name': 'mistral',
'auth_url': AUTH_HTTP_URL_v3,
'cacert': None, 'cacert': None,
'insecure': True, 'insecure': True,
'target_auth_uri': None, 'auth_token': keystone_client_instance.auth_token,
'target_token': None 'project_id': keystone_client_instance.project_id,
'user_id': keystone_client_instance.user_id
} }
client.client( client.client(
@ -148,8 +149,8 @@ class BaseClientTests(base.BaseTestCase):
) )
self.assertTrue(mocked.called) self.assertTrue(mocked.called)
self.assertEqual(mocked.call_args[0], expected_args) self.assertEqual(expected_args, mocked.call_args[0])
self.assertDictEqual(mocked.call_args[1], expected_kwargs) self.assertDictEqual(expected_kwargs, mocked.call_args[1])
@mock.patch('keystoneclient.v3.client.Client') @mock.patch('keystoneclient.v3.client.Client')
@mock.patch('mistralclient.api.httpclient.HTTPClient') @mock.patch('mistralclient.api.httpclient.HTTPClient')
@ -163,16 +164,18 @@ class BaseClientTests(base.BaseTestCase):
expected_args = ( expected_args = (
MISTRAL_HTTPS_URL, MISTRAL_HTTPS_URL,
keystone_client_instance.auth_token,
keystone_client_instance.project_id,
keystone_client_instance.user_id
) )
expected_kwargs = { expected_kwargs = {
'mistral_url': MISTRAL_HTTPS_URL,
'username': 'mistral',
'project_name': 'mistral',
'auth_url': AUTH_HTTP_URL_v3,
'cacert': path, 'cacert': path,
'insecure': False, 'insecure': False,
'target_auth_uri': None, 'auth_token': keystone_client_instance.auth_token,
'target_token': None 'project_id': keystone_client_instance.project_id,
'user_id': keystone_client_instance.user_id
} }
try: try:
@ -189,8 +192,8 @@ class BaseClientTests(base.BaseTestCase):
os.unlink(path) os.unlink(path)
self.assertTrue(mock.called) self.assertTrue(mock.called)
self.assertEqual(mock.call_args[0], expected_args) self.assertEqual(expected_args, mock.call_args[0])
self.assertDictEqual(mock.call_args[1], expected_kwargs) self.assertDictEqual(expected_kwargs, mock.call_args[1])
@mock.patch('keystoneclient.v3.client.Client') @mock.patch('keystoneclient.v3.client.Client')
def test_mistral_url_https_bad_cacert(self, keystone_client_mock): def test_mistral_url_https_bad_cacert(self, keystone_client_mock):
@ -248,16 +251,16 @@ class BaseClientTests(base.BaseTestCase):
expected_args = ( expected_args = (
MISTRAL_HTTP_URL, MISTRAL_HTTP_URL,
keystone_client_instance.auth_token,
keystone_client_instance.project_id,
keystone_client_instance.user_id
) )
expected_kwargs = { expected_kwargs = {
'cacert': None, 'username': 'mistral',
'insecure': False, 'project_name': 'mistral',
'target_auth_uri': None, 'auth_url': AUTH_HTTP_URL_v3,
'target_token': None 'profile': PROFILER_HMAC_KEY,
'auth_token': keystone_client_instance.auth_token,
'project_id': keystone_client_instance.project_id,
'user_id': keystone_client_instance.user_id
} }
client.client( client.client(
@ -268,8 +271,8 @@ class BaseClientTests(base.BaseTestCase):
) )
self.assertTrue(mocked.called) self.assertTrue(mocked.called)
self.assertEqual(mocked.call_args[0], expected_args) self.assertEqual(expected_args, mocked.call_args[0])
self.assertDictEqual(mocked.call_args[1], expected_kwargs) self.assertDictEqual(expected_kwargs, mocked.call_args[1])
profiler = osprofiler.profiler.get() profiler = osprofiler.profiler.get()

View File

@ -73,9 +73,9 @@ class HTTPClientTest(base.BaseTestCase):
osprofiler.profiler.init(None) osprofiler.profiler.init(None)
self.client = httpclient.HTTPClient( self.client = httpclient.HTTPClient(
API_BASE_URL, API_BASE_URL,
AUTH_TOKEN, auth_token=AUTH_TOKEN,
PROJECT_ID, project_id=PROJECT_ID,
USER_ID user_id=USER_ID
) )
@mock.patch.object( @mock.patch.object(
@ -133,23 +133,23 @@ class HTTPClientTest(base.BaseTestCase):
mock.MagicMock(return_value=FakeResponse('get', EXPECTED_URL, 200)) mock.MagicMock(return_value=FakeResponse('get', EXPECTED_URL, 200))
) )
def test_get_request_options_with_headers_for_get(self): def test_get_request_options_with_headers_for_get(self):
target_auth_uri = str(uuid.uuid4()) target_auth_url = str(uuid.uuid4())
target_token = str(uuid.uuid4()) target_auth_token = str(uuid.uuid4())
target_client = httpclient.HTTPClient( target_client = httpclient.HTTPClient(
API_BASE_URL, API_BASE_URL,
AUTH_TOKEN, auth_token=AUTH_TOKEN,
PROJECT_ID, project_id=PROJECT_ID,
USER_ID, user_id=USER_ID,
target_auth_uri=target_auth_uri, target_auth_url=target_auth_url,
target_token=target_token target_auth_token=target_auth_token
) )
target_client.get(API_URL) target_client.get(API_URL)
expected_options = copy.deepcopy(EXPECTED_REQ_OPTIONS) expected_options = copy.deepcopy(EXPECTED_REQ_OPTIONS)
expected_options["headers"]["X-Target-Auth-Uri"] = target_auth_uri expected_options["headers"]["X-Target-Auth-Uri"] = target_auth_url
expected_options["headers"]["X-Target-Auth-Token"] = target_token expected_options["headers"]["X-Target-Auth-Token"] = target_auth_token
requests.get.assert_called_with( requests.get.assert_called_with(
EXPECTED_URL, EXPECTED_URL,

View File

@ -9,3 +9,4 @@ python-keystoneclient!=2.1.0,>=2.0.0 # Apache-2.0
PyYAML>=3.1.0 # MIT PyYAML>=3.1.0 # MIT
requests>=2.10.0 # Apache-2.0 requests>=2.10.0 # Apache-2.0
six>=1.9.0 # MIT six>=1.9.0 # MIT
stevedore>=1.16.0 # Apache-2.0

View File

@ -102,6 +102,15 @@ openstack.workflow_engine.v2 =
resource_member_delete = mistralclient.commands.v2.members:Delete resource_member_delete = mistralclient.commands.v2.members:Delete
resource_member_update = mistralclient.commands.v2.members:Update resource_member_update = mistralclient.commands.v2.members:Update
mistralclient.auth =
# Standard Keystone authentication.
keystone = mistralclient.auth.keystone:KeystoneAuthHandler
# Authentication using OpenID Connect protocol but specific to KeyCloak
# server regarding multi-tenancy support. KeyCloak has a notion of realm
# used as an analog of Keystone project/tenant.
keycloak-oidc = mistralclient.auth.keycloak:KeycloakAuthHandler
[nosetests] [nosetests]
cover-package = mistralclient cover-package = mistralclient