freezer-api/freezer_api/api/common/middleware.py

208 lines
7.1 KiB
Python

"""
(c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
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 falcon
from oslo_log import log
import webob.dec
import webob.exc
from oslo_serialization import jsonutils as json
from freezer_api.common import exceptions as freezer_api_exc
from freezer_api import context
LOG = log.getLogger(__name__)
class Middleware(object):
"""
WSGI wrapper for all freezer middlewares. Use this will allow to manage
middlewares through paste
"""
def __init__(self, app):
self.app = app
def process_request(self, req):
"""
implement this function in your middleware to change the request
if the function return None the request will be handled in the next
level functions
"""
return None
def process_response(self, response):
"""
Implement this to modify your response
"""
return response
@classmethod
def factory(cls, global_conf, **local_conf):
def filter(app):
return cls(app)
return filter
@webob.dec.wsgify
def __call__(self, req, params=None):
response = self.process_request(req)
if response:
return response
response = req.get_response(self.app)
response.req = req
try:
return self.process_response(response)
except webob.exc.HTTPException as e:
LOG.error(e)
return e
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)
LOG.exception(ex)
raise freezer_api_exc.FreezerAPIException(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)
LOG.exception(ex)
raise freezer_api_exc.FreezerAPIException(message)
class RequireJSON(HookableMiddlewareMixin, object):
def process_request(self, req, resp):
if not req.client_accepts_json:
raise falcon.HTTPNotAcceptable(
'Freezer-api only supports responses encoded as JSON.',
href='http://docs.examples.com/api/json')
class JSONTranslator(HookableMiddlewareMixin, object):
def process_response(self, req, resp, resource):
if not hasattr(resp, 'body'):
return
if isinstance(resp.data, dict):
resp.data = json.dumps(resp.data)
if isinstance(resp.body, dict):
resp.body = json.dumps(resp.body)
class BaseContextMiddleware(Middleware):
def process_response(self, response):
try:
request_id = response.req.context.request_id
except AttributeError:
LOG.warning('Unable to retrieve request id from context')
else:
# For python 3 compatibility need to use bytes type
prefix = b'req-' if isinstance(request_id, bytes) else 'req-'
if not request_id.startswith(prefix):
request_id = prefix + request_id
response.headers['x-openstack-request-id'] = request_id
return response
class ContextMiddleware(BaseContextMiddleware):
"""
Simple WSGI app to support HAProxy polling.
If the requested url matches the configured path it replies
with a 200 otherwise passes the request to the inner app
"""
def get_context(self, req):
token = req.headers.get('X-Auth-Token')
userid = req.headers.get('X-User-Id')
domainid = req.headers.get('X-Domain-Id')
tenantid = req.headers.get('X-Tenant-Id')
user_domain_id = req.headers.get('X-User-Domain-Id')
projectid = req.headers.get('X-Project-Id')
project_domain_id = req.headers.get('X-Project-Domain-Id')
request_id = req.headers.get('X-Openstack-Request-ID')
roles_header = req.headers.get('X-Roles', '')
roles = [r.strip().lower() for r in roles_header.split(',')]
is_admin = 'admin' in roles
return context.FreezerContext(auth_token=token,
user=userid,
tenant=tenantid or projectid,
domain=domainid,
user_domain=user_domain_id,
project_domain=project_domain_id,
is_admin=is_admin,
request_id=request_id,
resource_uuid=None,
roles=roles
)
def process_request(self, req):
if req.headers.get('X-Identity-Status') == "Confirmed":
# falcon is overwriting the context variable in the request
# so we need to set the context in env variable
req.context = self.get_context(req)
req.environ['freezer.context'] = req.context
else:
raise webob.exc.HTTPUnauthorized()