Added keystone token authorization
Change-Id: I585dfd3e2ece26dab7f18d1117cad0dca0baf72b
This commit is contained in:
parent
bb6dd45ec4
commit
49643c4ae8
62
mistral/api/access_control.py
Normal file
62
mistral/api/access_control.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# Copyright 2013 - Mirantis, 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.
|
||||||
|
|
||||||
|
"""Access Control API server."""
|
||||||
|
|
||||||
|
from keystoneclient.middleware import auth_token
|
||||||
|
from oslo.config import cfg
|
||||||
|
|
||||||
|
|
||||||
|
_ENFORCER = None
|
||||||
|
OPT_GROUP_NAME = 'keystone_authtoken'
|
||||||
|
|
||||||
|
|
||||||
|
def register_opts(conf):
|
||||||
|
"""Register keystoneclient middleware options
|
||||||
|
"""
|
||||||
|
conf.register_opts(auth_token.opts,
|
||||||
|
group=OPT_GROUP_NAME)
|
||||||
|
auth_token.CONF = conf
|
||||||
|
|
||||||
|
|
||||||
|
register_opts(cfg.CONF)
|
||||||
|
|
||||||
|
|
||||||
|
def install(app, conf):
|
||||||
|
if conf.app.auth_enable:
|
||||||
|
return auth_token.AuthProtocol(app,
|
||||||
|
conf=dict(cfg.CONF.keystone_authtoken))
|
||||||
|
else:
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def get_limited_to(headers):
|
||||||
|
"""Return the user and project the request should be limited to.
|
||||||
|
|
||||||
|
:param headers: HTTP headers dictionary
|
||||||
|
:return: A tuple of (user, project), set to None if there's no limit on
|
||||||
|
one of these.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return headers.get('X-User-Id'), headers.get('X-Project-Id')
|
||||||
|
|
||||||
|
|
||||||
|
def get_limited_to_project(headers):
|
||||||
|
"""Return the project the request should be limited to.
|
||||||
|
|
||||||
|
:param headers: HTTP headers dictionary
|
||||||
|
:return: A project, or None if there's no limit on it.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return get_limited_to(headers)[1]
|
@ -17,12 +17,12 @@ import pecan
|
|||||||
from mistral.api import config as api_config
|
from mistral.api import config as api_config
|
||||||
from mistral.db import api as db_api
|
from mistral.db import api as db_api
|
||||||
from mistral.services import periodic
|
from mistral.services import periodic
|
||||||
|
from mistral.api import access_control as ac
|
||||||
|
|
||||||
|
|
||||||
def get_pecan_config():
|
def get_pecan_config():
|
||||||
# Set up the pecan configuration
|
# Set up the pecan configuration
|
||||||
filename = api_config.__file__.replace('.pyc', '.py')
|
filename = api_config.__file__.replace('.pyc', '.py')
|
||||||
|
|
||||||
return pecan.configuration.conf_from_file(filename)
|
return pecan.configuration.conf_from_file(filename)
|
||||||
|
|
||||||
|
|
||||||
@ -36,8 +36,12 @@ def setup_app(config=None):
|
|||||||
##TODO(akuznetsov) move this to event scheduling to separate process
|
##TODO(akuznetsov) move this to event scheduling to separate process
|
||||||
periodic.setup()
|
periodic.setup()
|
||||||
|
|
||||||
return pecan.make_app(
|
app = pecan.make_app(
|
||||||
app_conf.pop('root'),
|
app_conf.pop('root'),
|
||||||
logging=getattr(config, 'logging', {}),
|
logging=getattr(config, 'logging', {}),
|
||||||
**app_conf
|
**app_conf
|
||||||
)
|
)
|
||||||
|
|
||||||
|
app = ac.install(app, config)
|
||||||
|
|
||||||
|
return app
|
||||||
|
@ -18,6 +18,7 @@ app = {
|
|||||||
'root': 'mistral.api.controllers.root.RootController',
|
'root': 'mistral.api.controllers.root.RootController',
|
||||||
'modules': ['mistral.api'],
|
'modules': ['mistral.api'],
|
||||||
'debug': True,
|
'debug': True,
|
||||||
|
'auth_enable': True
|
||||||
}
|
}
|
||||||
|
|
||||||
# Custom Configurations must be in Python dictionary format::
|
# Custom Configurations must be in Python dictionary format::
|
||||||
|
@ -34,7 +34,8 @@ class FunctionalTest(BaseTest):
|
|||||||
'app': {
|
'app': {
|
||||||
'root': 'mistral.api.controllers.root.RootController',
|
'root': 'mistral.api.controllers.root.RootController',
|
||||||
'modules': ['mistral.api'],
|
'modules': ['mistral.api'],
|
||||||
'debug': False
|
'debug': False,
|
||||||
|
'auth_enable': False
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from keystoneclient.v3 import client as keystone_client
|
||||||
|
|
||||||
from mistralclient.api import httpclient
|
from mistralclient.api import httpclient
|
||||||
from mistralclient.api import workbooks
|
from mistralclient.api import workbooks
|
||||||
from mistralclient.api import executions
|
from mistralclient.api import executions
|
||||||
@ -24,19 +26,55 @@ from mistralclient.api import listeners
|
|||||||
|
|
||||||
|
|
||||||
class Client(object):
|
class Client(object):
|
||||||
def __init__(self, mistral_url=None):
|
|
||||||
# TODO: add all required parameters for Keystone authentication
|
|
||||||
|
|
||||||
|
def __init__(self, username=None, api_key=None, project_id=None,
|
||||||
|
project_name=None, auth_url=None, mistral_url=None,
|
||||||
|
endpoint_type='publicURL', service_type='workflow',
|
||||||
|
input_auth_token=None):
|
||||||
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 string')
|
||||||
|
if (isinstance(project_name, six.string_types) or
|
||||||
|
isinstance(project_id, six.string_types)):
|
||||||
|
if project_name and project_id:
|
||||||
|
raise RuntimeError('Only project name or '
|
||||||
|
'project id should be set')
|
||||||
|
|
||||||
|
if "v2.0" in auth_url:
|
||||||
|
raise RuntimeError('Mistral support only v3 '
|
||||||
|
'kyestone api')
|
||||||
|
print keystone_client
|
||||||
|
keystone = keystone_client.Client(username=username,
|
||||||
|
password=api_key,
|
||||||
|
token=input_auth_token,
|
||||||
|
tenant_id=project_id,
|
||||||
|
tenant_name=project_name,
|
||||||
|
auth_url=auth_url)
|
||||||
|
|
||||||
|
keystone.authenticate()
|
||||||
|
token = keystone.auth_token
|
||||||
|
user_id = keystone.user_id
|
||||||
|
if project_name and not project_id:
|
||||||
|
if keystone.tenants.find(name=project_name):
|
||||||
|
project_id = str(keystone.tenants.find(
|
||||||
|
name=project_name).id)
|
||||||
|
else:
|
||||||
|
raise RuntimeError('Project name or project id should'
|
||||||
|
' not be empty and should be string')
|
||||||
|
|
||||||
if not mistral_url:
|
if not mistral_url:
|
||||||
mistral_url = "http://localhost:8989/v1"
|
catalog = keystone.service_catalog.get_endpoints(service_type)
|
||||||
|
if service_type in catalog:
|
||||||
|
for e_type, endpoint in catalog.get[service_type][0].items():
|
||||||
|
if str(e_type).lower() == str(endpoint_type).lower():
|
||||||
|
mistral_url = endpoint
|
||||||
|
break
|
||||||
|
|
||||||
# TODO: add Keystone authentication later
|
if not mistral_url:
|
||||||
token = "TBD"
|
mistral_url = "http://localhost:8386/v1.0"
|
||||||
|
self.http_client = httpclient.HTTPClient(mistral_url,
|
||||||
self.http_client = httpclient.HTTPClient(mistral_url, token)
|
token,
|
||||||
|
project_id,
|
||||||
|
user_id)
|
||||||
|
|
||||||
# Create all resource managers.
|
# Create all resource managers.
|
||||||
self.workbooks = workbooks.WorkbookManager(self)
|
self.workbooks = workbooks.WorkbookManager(self)
|
||||||
|
@ -18,38 +18,45 @@ import requests
|
|||||||
|
|
||||||
|
|
||||||
class HTTPClient(object):
|
class HTTPClient(object):
|
||||||
def __init__(self, base_url, token):
|
def __init__(self, base_url, token, project_id, user_id):
|
||||||
self.base_url = base_url
|
self.base_url = base_url
|
||||||
self.token = token
|
self.token = token
|
||||||
|
self.project_id = project_id
|
||||||
|
self.user_id = user_id
|
||||||
|
|
||||||
def get(self, url, headers=None):
|
def get(self, url, headers=None):
|
||||||
if not headers:
|
headers = self._update_headers(headers)
|
||||||
headers = {}
|
|
||||||
|
|
||||||
headers['x-auth-token'] = self.token
|
|
||||||
|
|
||||||
return requests.get(self.base_url + url, headers=headers)
|
return requests.get(self.base_url + url, headers=headers)
|
||||||
|
|
||||||
def post(self, url, body, headers=None):
|
def post(self, url, body, headers=None):
|
||||||
if not headers:
|
headers = self._update_headers(headers)
|
||||||
headers = {'content-type': 'application/json'}
|
content_type = headers.get('content-type', 'application/json')
|
||||||
|
headers['content-type'] = content_type
|
||||||
headers['x-auth-token'] = self.token
|
|
||||||
|
|
||||||
return requests.post(self.base_url + url, body, headers=headers)
|
return requests.post(self.base_url + url, body, headers=headers)
|
||||||
|
|
||||||
def put(self, url, body, headers=None):
|
def put(self, url, body, headers=None):
|
||||||
if not headers:
|
headers = self._update_headers(headers)
|
||||||
headers = {'content-type': 'application/json'}
|
content_type = headers.get('content-type', 'application/json')
|
||||||
|
headers['content-type'] = content_type
|
||||||
headers['x-auth-token'] = self.token
|
|
||||||
|
|
||||||
return requests.put(self.base_url + url, body, headers=headers)
|
return requests.put(self.base_url + url, body, headers=headers)
|
||||||
|
|
||||||
def delete(self, url, headers=None):
|
def delete(self, url, headers=None):
|
||||||
if not headers:
|
headers = self._update_headers(headers)
|
||||||
headers = {}
|
|
||||||
|
|
||||||
headers['x-auth-token'] = self.token
|
|
||||||
|
|
||||||
return requests.delete(self.base_url + url, headers=headers)
|
return requests.delete(self.base_url + url, headers=headers)
|
||||||
|
|
||||||
|
def _update_headers(self, headers):
|
||||||
|
if not headers:
|
||||||
|
headers = {}
|
||||||
|
token = headers.get('x-auth-token', self.token)
|
||||||
|
headers['x-auth-token'] = token
|
||||||
|
|
||||||
|
project_id = headers.get('X-Project-Id', self.project_id)
|
||||||
|
headers['X-Project-Id'] = project_id
|
||||||
|
|
||||||
|
user_id = headers.get('X-User-Id', self.user_id)
|
||||||
|
headers['X-User-Id'] = user_id
|
||||||
|
return headers
|
||||||
|
@ -32,25 +32,29 @@ class FakeResponse(object):
|
|||||||
|
|
||||||
|
|
||||||
class BaseClientTest(unittest2.TestCase):
|
class BaseClientTest(unittest2.TestCase):
|
||||||
def setUp(self):
|
@mock.patch('keystoneclient.v3.client.Client')
|
||||||
self._client = client.Client()
|
def setUp(self, keystone):
|
||||||
|
keystone.return_value = mock.Mock()
|
||||||
|
self._client = client.Client(project_name="test",
|
||||||
|
auth_url="v3.0",
|
||||||
|
mistral_url="test")
|
||||||
self.workbooks = self._client.workbooks
|
self.workbooks = self._client.workbooks
|
||||||
self.executions = self._client.executions
|
self.executions = self._client.executions
|
||||||
self.tasks = self._client.tasks
|
self.tasks = self._client.tasks
|
||||||
self.listeners = self._client.listeners
|
self.listeners = self._client.listeners
|
||||||
|
|
||||||
def mock_http_get(self, json, status_code=200):
|
def mock_http_get(self, json, status_code=200):
|
||||||
self._client.http_client.get =\
|
self._client.http_client.get = \
|
||||||
mock.MagicMock(return_value=FakeResponse(status_code, json))
|
mock.MagicMock(return_value=FakeResponse(status_code, json))
|
||||||
|
|
||||||
def mock_http_post(self, json, status_code=201):
|
def mock_http_post(self, json, status_code=201):
|
||||||
self._client.http_client.post =\
|
self._client.http_client.post = \
|
||||||
mock.MagicMock(return_value=FakeResponse(status_code, json))
|
mock.MagicMock(return_value=FakeResponse(status_code, json))
|
||||||
|
|
||||||
def mock_http_put(self, json, status_code=200):
|
def mock_http_put(self, json, status_code=200):
|
||||||
self._client.http_client.put =\
|
self._client.http_client.put = \
|
||||||
mock.MagicMock(return_value=FakeResponse(status_code, json))
|
mock.MagicMock(return_value=FakeResponse(status_code, json))
|
||||||
|
|
||||||
def mock_http_delete(self, status_code=204):
|
def mock_http_delete(self, status_code=204):
|
||||||
self._client.http_client.delete =\
|
self._client.http_client.delete = \
|
||||||
mock.MagicMock(return_value=FakeResponse(status_code))
|
mock.MagicMock(return_value=FakeResponse(status_code))
|
||||||
|
@ -8,4 +8,5 @@ argparse
|
|||||||
croniter
|
croniter
|
||||||
oslo.config>=1.2.0
|
oslo.config>=1.2.0
|
||||||
requests
|
requests
|
||||||
|
python-keystoneclient>=0.3.2
|
||||||
pika>=0.9.13
|
pika>=0.9.13
|
Loading…
Reference in New Issue
Block a user