Merge "Abstract authentication function"
This commit is contained in:
@@ -12,58 +12,15 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import six
|
||||
|
||||
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,
|
||||
project_name=None, auth_url=None, project_id=None,
|
||||
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 client(auth_type='keystone', **kwargs):
|
||||
return client_v2.Client(auth_type=auth_type, **kwargs)
|
||||
|
||||
|
||||
def determine_client_version(mistral_version):
|
||||
if mistral_version.find("v2") != -1:
|
||||
return 2
|
||||
|
||||
raise RuntimeError("Can not determine mistral API version")
|
||||
raise RuntimeError("Cannot determine mistral API version")
|
||||
|
@@ -37,31 +37,31 @@ def log_request(func):
|
||||
|
||||
|
||||
class HTTPClient(object):
|
||||
def __init__(self, base_url, token=None, project_id=None, user_id=None,
|
||||
cacert=None, insecure=False, target_token=None,
|
||||
target_auth_uri=None, **kwargs):
|
||||
def __init__(self, base_url, **kwargs):
|
||||
self.base_url = base_url
|
||||
self.token = token
|
||||
self.project_id = project_id
|
||||
self.user_id = user_id
|
||||
self.target_token = target_token
|
||||
self.target_auth_uri = target_auth_uri
|
||||
self.auth_token = kwargs.get('auth_token', None)
|
||||
self.project_id = kwargs.get('project_id', None)
|
||||
self.user_id = kwargs.get('user_id', None)
|
||||
self.target_auth_token = kwargs.get('target_auth_token', None)
|
||||
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 = {}
|
||||
|
||||
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 '
|
||||
'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 '
|
||||
'cacert is provided.')
|
||||
|
||||
if insecure:
|
||||
if self.insecure:
|
||||
self.ssl_options['verify'] = False
|
||||
else:
|
||||
if cacert:
|
||||
self.ssl_options['verify'] = cacert
|
||||
if self.cacert:
|
||||
self.ssl_options['verify'] = self.cacert
|
||||
else:
|
||||
self.ssl_options['verify'] = True
|
||||
|
||||
@@ -107,9 +107,9 @@ class HTTPClient(object):
|
||||
if not headers:
|
||||
headers = {}
|
||||
|
||||
token = headers.get('x-auth-token', self.token)
|
||||
if token:
|
||||
headers['x-auth-token'] = token
|
||||
auth_token = headers.get('x-auth-token', self.auth_token)
|
||||
if auth_token:
|
||||
headers['x-auth-token'] = auth_token
|
||||
|
||||
project_id = headers.get('X-Project-Id', self.project_id)
|
||||
if project_id:
|
||||
@@ -119,14 +119,18 @@ class HTTPClient(object):
|
||||
if user_id:
|
||||
headers['X-User-Id'] = user_id
|
||||
|
||||
target_token = headers.get('X-Target-Auth-Token', self.target_token)
|
||||
if target_token:
|
||||
headers['X-Target-Auth-Token'] = target_token
|
||||
target_auth_token = headers.get(
|
||||
'X-Target-Auth-Token',
|
||||
self.target_auth_token
|
||||
)
|
||||
|
||||
target_auth_uri = headers.get('X-Target-Auth-Uri',
|
||||
self.target_auth_uri)
|
||||
if target_auth_uri:
|
||||
headers['X-Target-Auth-Uri'] = target_auth_uri
|
||||
if target_auth_token:
|
||||
headers['X-Target-Auth-Token'] = target_auth_token
|
||||
|
||||
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:
|
||||
# Add headers for osprofiler.
|
||||
|
@@ -13,6 +13,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import copy
|
||||
import six
|
||||
|
||||
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 workbooks
|
||||
from mistralclient.api.v2 import workflows
|
||||
from mistralclient.auth import auth_types
|
||||
from mistralclient.auth import keycloak
|
||||
from mistralclient.auth import keystone
|
||||
from mistralclient import auth
|
||||
|
||||
|
||||
osprofiler_profiler = importutils.try_import("osprofiler.profiler")
|
||||
|
||||
@@ -38,62 +38,25 @@ _DEFAULT_MISTRAL_URL = "http://localhost:8989/v2"
|
||||
|
||||
|
||||
class Client(object):
|
||||
def __init__(self, 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,
|
||||
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):
|
||||
def __init__(self, auth_type='keystone', **kwargs):
|
||||
req = copy.deepcopy(kwargs)
|
||||
mistral_url = req.get('mistral_url')
|
||||
auth_url = req.get('auth_url')
|
||||
auth_token = req.get('auth_token')
|
||||
project_id = req.get('project_id')
|
||||
user_id = req.get('user_id')
|
||||
profile = req.get('profile')
|
||||
|
||||
if mistral_url and not isinstance(mistral_url, six.string_types):
|
||||
raise RuntimeError('Mistral url should be a string.')
|
||||
|
||||
if auth_url:
|
||||
if auth_type == auth_types.KEYSTONE:
|
||||
(mistral_url, auth_token, project_id, user_id) = (
|
||||
keystone.authenticate(
|
||||
mistral_url,
|
||||
username,
|
||||
api_key,
|
||||
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 auth_url and not auth_token:
|
||||
auth_handler = auth.get_auth_handler(auth_type)
|
||||
auth_response = auth_handler.authenticate(req) or {}
|
||||
mistral_url = auth_response.get('mistral_url') or mistral_url
|
||||
req['auth_token'] = auth_response.get('token')
|
||||
req['project_id'] = auth_response.get('project_id') or project_id
|
||||
req['user_id'] = auth_response.get('user_id') or user_id
|
||||
|
||||
if not mistral_url:
|
||||
mistral_url = _DEFAULT_MISTRAL_URL
|
||||
@@ -101,33 +64,7 @@ class Client(object):
|
||||
if profile:
|
||||
osprofiler_profiler.init(profile)
|
||||
|
||||
if target_auth_url:
|
||||
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
|
||||
)
|
||||
http_client = httpclient.HTTPClient(mistral_url, **req)
|
||||
|
||||
# Create all resource managers.
|
||||
self.workbooks = workbooks.WorkbookManager(http_client)
|
||||
|
@@ -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()
|
||||
|
@@ -12,15 +12,10 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from stevedore import extension
|
||||
|
||||
# Valid authentication types.
|
||||
|
||||
# Standard Keystone authentication.
|
||||
KEYSTONE = 'keystone'
|
||||
|
||||
# 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]
|
||||
ALL = extension.ExtensionManager(
|
||||
namespace='mistralclient.auth',
|
||||
invoke_on_load=False
|
||||
).names()
|
||||
|
@@ -16,119 +16,147 @@ import logging
|
||||
import pprint
|
||||
import requests
|
||||
|
||||
from mistralclient import auth
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def authenticate(auth_url, client_id, client_secret, realm_name,
|
||||
username=None, password=None, access_token=None,
|
||||
cacert=None, insecure=False):
|
||||
"""Performs authentication using Keycloak OpenID Protocol.
|
||||
class KeycloakAuthHandler(auth.AuthHandler):
|
||||
|
||||
:param auth_url: Base authentication url of KeyCloak server (e.g.
|
||||
"https://my.keycloak:8443/auth"
|
||||
: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).
|
||||
def authenticate(self, req):
|
||||
"""Performs authentication using Keycloak OpenID Protocol.
|
||||
|
||||
"""
|
||||
if not auth_url:
|
||||
raise ValueError('Base authentication url is not provided.')
|
||||
:param req: Request dict containing list of parameters required
|
||||
for Keycloak authentication.
|
||||
|
||||
if not client_id:
|
||||
raise ValueError('Client ID is not provided.')
|
||||
auth_url: Base authentication url of KeyCloak server (e.g.
|
||||
"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:
|
||||
raise ValueError('Project(realm) name is not provided.')
|
||||
auth_url = req.get('auth_url')
|
||||
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:
|
||||
raise ValueError(
|
||||
"User name and access token can't be provided at the same time."
|
||||
if not auth_url:
|
||||
raise ValueError('Base authentication url is not provided.')
|
||||
|
||||
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:
|
||||
return _authenticate_with_token(
|
||||
auth_url,
|
||||
client_id,
|
||||
client_secret,
|
||||
access_token,
|
||||
cacert,
|
||||
insecure
|
||||
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
|
||||
)
|
||||
|
||||
if not username:
|
||||
raise ValueError('Either user name or access token must be provided.')
|
||||
try:
|
||||
resp.raise_for_status()
|
||||
except Exception as e:
|
||||
raise Exception("Failed to get access token:\n %s" % str(e))
|
||||
|
||||
return _authenticate_with_password(
|
||||
auth_url,
|
||||
client_id,
|
||||
client_secret,
|
||||
realm_name,
|
||||
username,
|
||||
password,
|
||||
cacert,
|
||||
insecure
|
||||
)
|
||||
LOG.debug(
|
||||
"HTTP response from OIDC provider: %s" %
|
||||
pprint.pformat(resp.json())
|
||||
)
|
||||
|
||||
|
||||
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']
|
||||
return resp.json()['access_token']
|
||||
|
||||
|
||||
# An example of using KeyCloak OpenID authentication.
|
||||
|
||||
if __name__ == '__main__':
|
||||
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",
|
||||
client_id="mistral_client",
|
||||
client_secret="4a080907-921b-409a-b793-c431609c3a47",
|
||||
|
@@ -12,60 +12,115 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
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
|
||||
from mistralclient import auth
|
||||
|
||||
|
||||
def _get_keystone_client(auth_url):
|
||||
if "v2.0" in auth_url:
|
||||
if 'v2.0' in auth_url:
|
||||
from keystoneclient.v2_0 import client
|
||||
else:
|
||||
from keystoneclient.v3 import 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
|
||||
}
|
||||
|
@@ -317,7 +317,7 @@ class MistralShell(app.App):
|
||||
'--auth-type',
|
||||
action='store',
|
||||
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.'
|
||||
' (Env: MISTRAL_AUTH_TYPE)' % auth_types.ALL
|
||||
)
|
||||
|
@@ -92,16 +92,15 @@ class BaseClientTests(base.BaseTestCase):
|
||||
|
||||
expected_args = (
|
||||
MISTRAL_HTTP_URL,
|
||||
keystone_client_instance.auth_token,
|
||||
keystone_client_instance.project_id,
|
||||
keystone_client_instance.user_id
|
||||
)
|
||||
|
||||
expected_kwargs = {
|
||||
'cacert': None,
|
||||
'insecure': False,
|
||||
'target_auth_uri': None,
|
||||
'target_token': None
|
||||
'username': 'mistral',
|
||||
'project_name': 'mistral',
|
||||
'auth_url': AUTH_HTTP_URL_v3,
|
||||
'auth_token': keystone_client_instance.auth_token,
|
||||
'project_id': keystone_client_instance.project_id,
|
||||
'user_id': keystone_client_instance.user_id
|
||||
}
|
||||
|
||||
client.client(
|
||||
@@ -111,8 +110,8 @@ class BaseClientTests(base.BaseTestCase):
|
||||
)
|
||||
|
||||
self.assertTrue(mocked.called)
|
||||
self.assertEqual(mocked.call_args[0], expected_args)
|
||||
self.assertDictEqual(mocked.call_args[1], expected_kwargs)
|
||||
self.assertEqual(expected_args, mocked.call_args[0])
|
||||
self.assertDictEqual(expected_kwargs, mocked.call_args[1])
|
||||
|
||||
@mock.patch('keystoneclient.v3.client.Client')
|
||||
@mock.patch('mistralclient.api.httpclient.HTTPClient')
|
||||
@@ -126,16 +125,18 @@ class BaseClientTests(base.BaseTestCase):
|
||||
|
||||
expected_args = (
|
||||
MISTRAL_HTTPS_URL,
|
||||
keystone_client_instance.auth_token,
|
||||
keystone_client_instance.project_id,
|
||||
keystone_client_instance.user_id
|
||||
)
|
||||
|
||||
expected_kwargs = {
|
||||
'mistral_url': MISTRAL_HTTPS_URL,
|
||||
'username': 'mistral',
|
||||
'project_name': 'mistral',
|
||||
'auth_url': AUTH_HTTP_URL_v3,
|
||||
'cacert': None,
|
||||
'insecure': True,
|
||||
'target_auth_uri': None,
|
||||
'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(
|
||||
@@ -148,8 +149,8 @@ class BaseClientTests(base.BaseTestCase):
|
||||
)
|
||||
|
||||
self.assertTrue(mocked.called)
|
||||
self.assertEqual(mocked.call_args[0], expected_args)
|
||||
self.assertDictEqual(mocked.call_args[1], expected_kwargs)
|
||||
self.assertEqual(expected_args, mocked.call_args[0])
|
||||
self.assertDictEqual(expected_kwargs, mocked.call_args[1])
|
||||
|
||||
@mock.patch('keystoneclient.v3.client.Client')
|
||||
@mock.patch('mistralclient.api.httpclient.HTTPClient')
|
||||
@@ -163,16 +164,18 @@ class BaseClientTests(base.BaseTestCase):
|
||||
|
||||
expected_args = (
|
||||
MISTRAL_HTTPS_URL,
|
||||
keystone_client_instance.auth_token,
|
||||
keystone_client_instance.project_id,
|
||||
keystone_client_instance.user_id
|
||||
)
|
||||
|
||||
expected_kwargs = {
|
||||
'mistral_url': MISTRAL_HTTPS_URL,
|
||||
'username': 'mistral',
|
||||
'project_name': 'mistral',
|
||||
'auth_url': AUTH_HTTP_URL_v3,
|
||||
'cacert': path,
|
||||
'insecure': False,
|
||||
'target_auth_uri': None,
|
||||
'target_token': None
|
||||
'auth_token': keystone_client_instance.auth_token,
|
||||
'project_id': keystone_client_instance.project_id,
|
||||
'user_id': keystone_client_instance.user_id
|
||||
}
|
||||
|
||||
try:
|
||||
@@ -189,8 +192,8 @@ class BaseClientTests(base.BaseTestCase):
|
||||
os.unlink(path)
|
||||
|
||||
self.assertTrue(mock.called)
|
||||
self.assertEqual(mock.call_args[0], expected_args)
|
||||
self.assertDictEqual(mock.call_args[1], expected_kwargs)
|
||||
self.assertEqual(expected_args, mock.call_args[0])
|
||||
self.assertDictEqual(expected_kwargs, mock.call_args[1])
|
||||
|
||||
@mock.patch('keystoneclient.v3.client.Client')
|
||||
def test_mistral_url_https_bad_cacert(self, keystone_client_mock):
|
||||
@@ -248,16 +251,16 @@ class BaseClientTests(base.BaseTestCase):
|
||||
|
||||
expected_args = (
|
||||
MISTRAL_HTTP_URL,
|
||||
keystone_client_instance.auth_token,
|
||||
keystone_client_instance.project_id,
|
||||
keystone_client_instance.user_id
|
||||
)
|
||||
|
||||
expected_kwargs = {
|
||||
'cacert': None,
|
||||
'insecure': False,
|
||||
'target_auth_uri': None,
|
||||
'target_token': None
|
||||
'username': 'mistral',
|
||||
'project_name': 'mistral',
|
||||
'auth_url': AUTH_HTTP_URL_v3,
|
||||
'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(
|
||||
@@ -268,8 +271,8 @@ class BaseClientTests(base.BaseTestCase):
|
||||
)
|
||||
|
||||
self.assertTrue(mocked.called)
|
||||
self.assertEqual(mocked.call_args[0], expected_args)
|
||||
self.assertDictEqual(mocked.call_args[1], expected_kwargs)
|
||||
self.assertEqual(expected_args, mocked.call_args[0])
|
||||
self.assertDictEqual(expected_kwargs, mocked.call_args[1])
|
||||
|
||||
profiler = osprofiler.profiler.get()
|
||||
|
||||
|
@@ -73,9 +73,9 @@ class HTTPClientTest(base.BaseTestCase):
|
||||
osprofiler.profiler.init(None)
|
||||
self.client = httpclient.HTTPClient(
|
||||
API_BASE_URL,
|
||||
AUTH_TOKEN,
|
||||
PROJECT_ID,
|
||||
USER_ID
|
||||
auth_token=AUTH_TOKEN,
|
||||
project_id=PROJECT_ID,
|
||||
user_id=USER_ID
|
||||
)
|
||||
|
||||
@mock.patch.object(
|
||||
@@ -133,23 +133,23 @@ class HTTPClientTest(base.BaseTestCase):
|
||||
mock.MagicMock(return_value=FakeResponse('get', EXPECTED_URL, 200))
|
||||
)
|
||||
def test_get_request_options_with_headers_for_get(self):
|
||||
target_auth_uri = str(uuid.uuid4())
|
||||
target_token = str(uuid.uuid4())
|
||||
target_auth_url = str(uuid.uuid4())
|
||||
target_auth_token = str(uuid.uuid4())
|
||||
|
||||
target_client = httpclient.HTTPClient(
|
||||
API_BASE_URL,
|
||||
AUTH_TOKEN,
|
||||
PROJECT_ID,
|
||||
USER_ID,
|
||||
target_auth_uri=target_auth_uri,
|
||||
target_token=target_token
|
||||
auth_token=AUTH_TOKEN,
|
||||
project_id=PROJECT_ID,
|
||||
user_id=USER_ID,
|
||||
target_auth_url=target_auth_url,
|
||||
target_auth_token=target_auth_token
|
||||
)
|
||||
|
||||
target_client.get(API_URL)
|
||||
|
||||
expected_options = copy.deepcopy(EXPECTED_REQ_OPTIONS)
|
||||
expected_options["headers"]["X-Target-Auth-Uri"] = target_auth_uri
|
||||
expected_options["headers"]["X-Target-Auth-Token"] = target_token
|
||||
expected_options["headers"]["X-Target-Auth-Uri"] = target_auth_url
|
||||
expected_options["headers"]["X-Target-Auth-Token"] = target_auth_token
|
||||
|
||||
requests.get.assert_called_with(
|
||||
EXPECTED_URL,
|
||||
|
@@ -9,3 +9,4 @@ python-keystoneclient!=2.1.0,>=2.0.0 # Apache-2.0
|
||||
PyYAML>=3.1.0 # MIT
|
||||
requests>=2.10.0 # Apache-2.0
|
||||
six>=1.9.0 # MIT
|
||||
stevedore>=1.16.0 # Apache-2.0
|
||||
|
@@ -102,6 +102,15 @@ openstack.workflow_engine.v2 =
|
||||
resource_member_delete = mistralclient.commands.v2.members:Delete
|
||||
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]
|
||||
cover-package = mistralclient
|
||||
|
||||
|
Reference in New Issue
Block a user