deckhand/deckhand/control/middleware.py

135 lines
4.8 KiB
Python

# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
#
# 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 yaml
import falcon
from oslo_config import cfg
from oslo_log import log as logging
from oslo_serialization import jsonutils as json
import deckhand.context
from deckhand import errors
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
class ContextMiddleware(object):
def process_request(self, req, resp):
"""Convert authentication information into a request context.
Generate a ``deckhand.context.RequestContext`` object from the
available authentication headers and store in the ``context`` attribute
of the ``req`` object.
:param req: ``falcon`` request object that will be given the context
object.
:raises: falcon.HTTPUnauthorized: when value of the
'X-Identity-Status' header is not 'Confirmed' and anonymous access
is disallowed.
"""
if req.headers.get('X-IDENTITY-STATUS') == 'Confirmed':
req.context = deckhand.context.RequestContext.from_environ(req.env)
elif CONF.allow_anonymous_access:
req.context = deckhand.context.get_context()
else:
raise falcon.HTTPUnauthorized()
class HookableMiddlewareMixin(object):
"""Provides methods to extract before and after hooks from WSGI Middleware
Prior to falcon 0.2.0b1, it's necessary to provide falcon with middleware
as "hook" functions that are either invoked before (to process requests)
or after (to process responses) the API endpoint code runs.
This mixin allows the process_request and process_response methods from a
typical WSGI middleware object to be extracted for use as these hooks, with
the appropriate method signatures.
"""
def as_before_hook(self):
"""Extract process_request method as "before" hook
:return: before hook function
"""
# Need to wrap this up in a closure because the parameter counts
# differ
def before_hook(req, resp, params=None):
return self.process_request(req, resp)
try:
return before_hook
except AttributeError as ex:
# No such method, we presume.
message_template = ("Failed to get before hook from middleware "
"{0} - {1}")
message = message_template.format(self.__name__, ex.message)
LOG.error(message)
raise errors.DeckhandException(message)
def as_after_hook(self):
"""Extract process_response method as "after" hook
:return: after hook function
"""
# Need to wrap this up in a closure because the parameter counts
# differ
def after_hook(req, resp, resource=None):
return self.process_response(req, resp, resource)
try:
return after_hook
except AttributeError as ex:
# No such method, we presume.
message_template = ("Failed to get after hook from middleware "
"{0} - {1}")
message = message_template.format(self.__name__, ex.message)
LOG.error(message)
raise errors.DeckhandException(message)
class YAMLTranslator(HookableMiddlewareMixin, object):
"""Middleware for converting all responses (error and success) to YAML.
``falcon`` error exceptions use JSON formatting and headers by default.
This middleware will intercept all responses and guarantee they are YAML
format.
.. note::
This does not include the 401 Unauthorized that is raised by
``keystonemiddleware`` which is executed in the pipeline before
``falcon`` middleware.
"""
def process_response(self, req, resp, resource):
resp.set_header('Content-Type', 'application/x-yaml')
for attr in ('body', 'data'):
if not hasattr(resp, attr):
continue
resp_attr = getattr(resp, attr)
try:
resp_attr = json.loads(resp_attr)
except (TypeError, ValueError):
pass
if isinstance(resp_attr, dict):
setattr(resp, attr, yaml.safe_dump(resp_attr))
elif isinstance(resp_attr, (list, tuple)):
setattr(resp, attr, yaml.safe_dump_all(resp_attr))