Keystone auth support
BUT you can disable authentication via config `enable_authentication` Change-Id: I16c5fbed6f8f0743f77ce59b229cfe76353c88be
This commit is contained in:
parent
170ca660bd
commit
1608d62535
|
@ -12,8 +12,13 @@
|
|||
|
||||
import pecan
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from evoque.api import auth
|
||||
from evoque.api import config as api_config
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
def get_pecan_config():
|
||||
# Set up the pecan configuration
|
||||
|
@ -33,7 +38,10 @@ def setup_app(config=None):
|
|||
**app_conf
|
||||
)
|
||||
|
||||
# TODO(liuqing): Add oslo.middleware cors and keystone auth
|
||||
# TODO(liuqing): Add oslo.middleware cors
|
||||
# http://docs.openstack.org/developer/oslo.middleware/cors.html
|
||||
|
||||
# Keystone auth middleware
|
||||
app = auth.install(app, CONF, config.app.acl_public_routes)
|
||||
|
||||
return app
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
# 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 Lists (ACL's) control access the API server."""
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from evoque.api.middleware import auth_token
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
def install(app, conf, public_routes):
|
||||
"""Install ACL check on application.
|
||||
:param app: A WSGI application.
|
||||
:param conf: Settings. Dict'ified and passed to keystone middleware
|
||||
:param public_routes: The list of the routes which will be allowed to
|
||||
access without authentication.
|
||||
:return: The same WSGI application with ACL installed.
|
||||
"""
|
||||
if not cfg.CONF.api.get('enable_authentication'):
|
||||
return app
|
||||
return auth_token.AuthTokenMiddleware(app,
|
||||
conf=dict(conf),
|
||||
public_api_routes=public_routes)
|
|
@ -21,6 +21,9 @@ app = {
|
|||
hooks.ContextHook(),
|
||||
hooks.RPCHook(),
|
||||
],
|
||||
'acl_public_routes': [
|
||||
'/'
|
||||
],
|
||||
}
|
||||
|
||||
# Custom Configurations must be in Python dictionary format::
|
||||
|
|
|
@ -12,9 +12,15 @@
|
|||
|
||||
from pecan import hooks
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from evoque.common import context
|
||||
from evoque.engine.ticket import api as ticket_api
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('auth_uri', 'keystonemiddleware.auth_token',
|
||||
group='keystone_authtoken')
|
||||
|
||||
|
||||
class ContextHook(hooks.PecanHook):
|
||||
"""Configures a request context and attaches it to the request.
|
||||
|
@ -52,8 +58,11 @@ class ContextHook(hooks.PecanHook):
|
|||
roles = headers.get('X-Roles', '').split(',')
|
||||
auth_token_info = state.request.environ.get('keystone.token_info')
|
||||
|
||||
auth_url = CONF.keystone_authtoken.auth_uri
|
||||
|
||||
state.request.context = context.make_context(
|
||||
auth_token=auth_token,
|
||||
auth_url=auth_url,
|
||||
auth_token_info=auth_token_info,
|
||||
user_name=user_name,
|
||||
user_id=user_id,
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
# 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 re
|
||||
|
||||
from keystonemiddleware import auth_token
|
||||
from oslo_log import log
|
||||
|
||||
from evoque.common import exceptions
|
||||
from evoque.common.i18n import _
|
||||
from evoque.common import utils
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class AuthTokenMiddleware(auth_token.AuthProtocol):
|
||||
"""A wrapper on Keystone auth_token middleware.
|
||||
|
||||
Does not perform verification of authentication tokens
|
||||
for public routes in the API.
|
||||
|
||||
"""
|
||||
def __init__(self, app, conf, public_api_routes=None):
|
||||
if public_api_routes is None:
|
||||
public_api_routes = []
|
||||
route_pattern_tpl = '%s\.json?$'
|
||||
|
||||
try:
|
||||
self.public_api_routes = [re.compile(route_pattern_tpl % route_tpl)
|
||||
for route_tpl in public_api_routes]
|
||||
except re.error as e:
|
||||
msg = _('Cannot compile public API routes: %s') % e
|
||||
|
||||
LOG.error(msg)
|
||||
raise exceptions.ConfigInvalid(error_msg=msg)
|
||||
|
||||
super(AuthTokenMiddleware, self).__init__(app, conf)
|
||||
|
||||
def __call__(self, env, start_response):
|
||||
path = utils.safe_rstrip(env.get('PATH_INFO'), '/')
|
||||
|
||||
# The information whether the API call is being performed against the
|
||||
# public API is required for some other components. Saving it to the
|
||||
# WSGI environment is reasonable thereby.
|
||||
env['is_public_api'] = any(map(lambda pattern: re.match(pattern, path),
|
||||
self.public_api_routes))
|
||||
|
||||
if env['is_public_api']:
|
||||
return self._app(env, start_response)
|
||||
|
||||
return super(AuthTokenMiddleware, self).__call__(env, start_response)
|
|
@ -11,7 +11,7 @@
|
|||
# under the License.
|
||||
|
||||
"""
|
||||
Glance Management Utility
|
||||
Evoque Management Utility
|
||||
"""
|
||||
|
||||
import os
|
||||
|
|
|
@ -10,6 +10,47 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from evoque.common.i18n import _
|
||||
from evoque.common.i18n import _LE
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NotImplementedError(NotImplementedError):
|
||||
pass
|
||||
|
||||
|
||||
class EvoqueException(Exception):
|
||||
"""Base Evoque Exception
|
||||
To correctly use this class, inherit from it and define
|
||||
a 'message' property. That message will get printf'd
|
||||
with the keyword arguments provided to the constructor.
|
||||
"""
|
||||
message = _("An unknown exception occurred.")
|
||||
code = 500
|
||||
|
||||
def __init__(self, message=None, **kwargs):
|
||||
self.kwargs = kwargs
|
||||
|
||||
if 'code' not in self.kwargs and hasattr(self, 'code'):
|
||||
self.kwargs['code'] = self.code
|
||||
|
||||
if message:
|
||||
self.message = message
|
||||
|
||||
try:
|
||||
self.message = self.message % kwargs
|
||||
except Exception as e:
|
||||
# kwargs doesn't match a variable in the message
|
||||
# log the issue and the kwargs
|
||||
LOG.exception(_LE('Exception in string format operation, '
|
||||
'kwargs: %s') % kwargs)
|
||||
raise e
|
||||
|
||||
super(EvoqueException, self).__init__(self.message)
|
||||
|
||||
|
||||
class ConfigInvalid(EvoqueException):
|
||||
message = _("Invalid configuration file. %(error_msg)s")
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
# 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.
|
||||
|
||||
"""Utilities and helper functions."""
|
||||
|
||||
import six
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from evoque.common.i18n import _LW
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def safe_rstrip(value, chars=None):
|
||||
"""Removes trailing characters from a string if that does not make it empty
|
||||
:param value: A string value that will be stripped.
|
||||
:param chars: Characters to remove.
|
||||
:return: Stripped value.
|
||||
"""
|
||||
if not isinstance(value, six.string_types):
|
||||
LOG.warn(_LW("Failed to remove trailing character. Returning original "
|
||||
"object. Supplied object is not a string: %s,"), value)
|
||||
return value
|
||||
|
||||
return value.rstrip(chars) or value
|
|
@ -42,6 +42,11 @@ def list_opts():
|
|||
default=1000,
|
||||
help=_('The maximum number of items returned in a '
|
||||
'single response from a collection resource')),
|
||||
cfg.BoolOpt('enable_authentication',
|
||||
default=True,
|
||||
help=_('This option enables or disables user '
|
||||
'authentication via Keystone. '
|
||||
'Default value is True.')),
|
||||
)),
|
||||
("DEFAULT", (
|
||||
cfg.StrOpt('host',
|
||||
|
|
|
@ -3,12 +3,16 @@
|
|||
# process, which may cause wedges in the gate later.
|
||||
|
||||
pbr>=1.6
|
||||
keystonemiddleware!=2.4.0,>=2.0.0
|
||||
oslo.config>=2.6.0 # Apache-2.0
|
||||
oslo.context>=0.2.0 # Apache-2.0
|
||||
oslo.db>=3.0.0 # Apache-2.0
|
||||
oslo.i18n>=1.5.0 # Apache-2.0
|
||||
oslo.log>=1.8.0 # Apache-2.0
|
||||
oslo.messaging!=1.17.0,!=1.17.1,!=2.6.0,!=2.6.1,>=1.16.0 # Apache-2.0
|
||||
oslo.serialization>=1.10.0 # Apache-2.0
|
||||
oslo.service>=0.10.0 # Apache-2.0
|
||||
oslo.utils!=2.6.0,>=2.4.0 # Apache-2.0
|
||||
pecan>=1.0.0
|
||||
SQLAlchemy<1.1.0,>=0.9.9
|
||||
sqlalchemy-migrate>=0.9.6
|
||||
|
|
Loading…
Reference in New Issue