Added keystone token authorization

Change-Id: I585dfd3e2ece26dab7f18d1117cad0dca0baf72b
This commit is contained in:
Alexander Kuznetsov 2013-12-10 12:39:51 +04:00
parent bb6dd45ec4
commit 49643c4ae8
8 changed files with 152 additions and 34 deletions

View 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]

View File

@ -17,12 +17,12 @@ import pecan
from mistral.api import config as api_config
from mistral.db import api as db_api
from mistral.services import periodic
from mistral.api import access_control as ac
def get_pecan_config():
# Set up the pecan configuration
filename = api_config.__file__.replace('.pyc', '.py')
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
periodic.setup()
return pecan.make_app(
app = pecan.make_app(
app_conf.pop('root'),
logging=getattr(config, 'logging', {}),
**app_conf
)
app = ac.install(app, config)
return app

View File

@ -18,6 +18,7 @@ app = {
'root': 'mistral.api.controllers.root.RootController',
'modules': ['mistral.api'],
'debug': True,
'auth_enable': True
}
# Custom Configurations must be in Python dictionary format::

View File

@ -34,7 +34,8 @@ class FunctionalTest(BaseTest):
'app': {
'root': 'mistral.api.controllers.root.RootController',
'modules': ['mistral.api'],
'debug': False
'debug': False,
'auth_enable': False
}
})

View File

@ -16,6 +16,8 @@
import six
from keystoneclient.v3 import client as keystone_client
from mistralclient.api import httpclient
from mistralclient.api import workbooks
from mistralclient.api import executions
@ -24,19 +26,55 @@ from mistralclient.api import listeners
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):
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:
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
token = "TBD"
self.http_client = httpclient.HTTPClient(mistral_url, token)
if not mistral_url:
mistral_url = "http://localhost:8386/v1.0"
self.http_client = httpclient.HTTPClient(mistral_url,
token,
project_id,
user_id)
# Create all resource managers.
self.workbooks = workbooks.WorkbookManager(self)

View File

@ -18,38 +18,45 @@ import requests
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.token = token
self.project_id = project_id
self.user_id = user_id
def get(self, url, headers=None):
if not headers:
headers = {}
headers['x-auth-token'] = self.token
headers = self._update_headers(headers)
return requests.get(self.base_url + url, headers=headers)
def post(self, url, body, headers=None):
if not headers:
headers = {'content-type': 'application/json'}
headers['x-auth-token'] = self.token
headers = self._update_headers(headers)
content_type = headers.get('content-type', 'application/json')
headers['content-type'] = content_type
return requests.post(self.base_url + url, body, headers=headers)
def put(self, url, body, headers=None):
if not headers:
headers = {'content-type': 'application/json'}
headers['x-auth-token'] = self.token
headers = self._update_headers(headers)
content_type = headers.get('content-type', 'application/json')
headers['content-type'] = content_type
return requests.put(self.base_url + url, body, headers=headers)
def delete(self, url, headers=None):
if not headers:
headers = {}
headers['x-auth-token'] = self.token
headers = self._update_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

View File

@ -32,25 +32,29 @@ class FakeResponse(object):
class BaseClientTest(unittest2.TestCase):
def setUp(self):
self._client = client.Client()
@mock.patch('keystoneclient.v3.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.executions = self._client.executions
self.tasks = self._client.tasks
self.listeners = self._client.listeners
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))
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))
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))
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))

View File

@ -8,4 +8,5 @@ argparse
croniter
oslo.config>=1.2.0
requests
python-keystoneclient>=0.3.2
pika>=0.9.13