"Fossies" - the Fresh Open Source Software Archive

Member "keystone-17.0.0/keystone/server/flask/application.py" (13 May 2020, 6066 Bytes) of package /linux/misc/openstack/keystone-17.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "application.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 16.0.1_vs_17.0.0.

    1 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    2 #    not use this file except in compliance with the License. You may obtain
    3 #    a copy of the License at
    4 #
    5 #         http://www.apache.org/licenses/LICENSE-2.0
    6 #
    7 #    Unless required by applicable law or agreed to in writing, software
    8 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    9 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   10 #    License for the specific language governing permissions and limitations
   11 #    under the License.
   12 
   13 from __future__ import absolute_import
   14 
   15 import functools
   16 import sys
   17 
   18 import flask
   19 import oslo_i18n
   20 from oslo_log import log
   21 from oslo_middleware import healthcheck
   22 
   23 try:
   24     # werkzeug 0.15.x
   25     from werkzeug.middleware import dispatcher as wsgi_dispatcher
   26 except ImportError:
   27     # werkzeug 0.14.x
   28     import werkzeug.wsgi as wsgi_dispatcher
   29 
   30 import keystone.api
   31 from keystone import exception
   32 from keystone.server.flask import common as ks_flask
   33 from keystone.server.flask.request_processing import json_body
   34 from keystone.server.flask.request_processing import req_logging
   35 
   36 from keystone.receipt import handlers as receipt_handlers
   37 
   38 LOG = log.getLogger(__name__)
   39 
   40 
   41 def fail_gracefully(f):
   42     """Log exceptions and aborts."""
   43     @functools.wraps(f)
   44     def wrapper(*args, **kw):
   45         try:
   46             return f(*args, **kw)
   47         except Exception as e:
   48             LOG.debug(e, exc_info=True)
   49 
   50             # exception message is printed to all logs
   51             LOG.critical(e)
   52             sys.exit(1)
   53 
   54     return wrapper
   55 
   56 
   57 def _add_vary_x_auth_token_header(response):
   58     # Add the expected Vary Header, this is run after every request in the
   59     # response-phase
   60     response.headers['Vary'] = 'X-Auth-Token'
   61     return response
   62 
   63 
   64 def _best_match_language():
   65     """Determine the best available locale.
   66 
   67     This returns best available locale based on the Accept-Language HTTP
   68     header passed in the request.
   69     """
   70     if not flask.request.accept_languages:
   71         return None
   72     return flask.request.accept_languages.best_match(
   73         oslo_i18n.get_available_languages('keystone'))
   74 
   75 
   76 def _handle_keystone_exception(error):
   77     # TODO(adriant): register this with its own specific handler:
   78     if isinstance(error, exception.InsufficientAuthMethods):
   79         return receipt_handlers.build_receipt(error)
   80 
   81     # Handle logging
   82     if isinstance(error, exception.Unauthorized):
   83         LOG.warning(
   84             "Authorization failed. %(exception)s from %(remote_addr)s",
   85             {'exception': error, 'remote_addr': flask.request.remote_addr})
   86     elif isinstance(error, exception.UnexpectedError):
   87         LOG.exception(str(error))
   88     else:
   89         LOG.warning(str(error))
   90 
   91     # Render the exception to something user "friendly"
   92     error_message = error.args[0]
   93     message = oslo_i18n.translate(error_message, _best_match_language())
   94     if message is error_message:
   95         # translate() didn't do anything because it wasn't a Message,
   96         # convert to a string.
   97         message = str(message)
   98 
   99     body = dict(
  100         error={
  101             'code': error.code,
  102             'title': error.title,
  103             'message': message}
  104     )
  105 
  106     if isinstance(error, exception.AuthPluginException):
  107         body['error']['identity'] = error.authentication
  108 
  109     # Create the response and set status code.
  110     response = flask.jsonify(body)
  111     response.status_code = error.code
  112 
  113     # Add the appropriate WWW-Authenticate header for Unauthorized
  114     if isinstance(error, exception.Unauthorized):
  115         url = ks_flask.base_url()
  116         response.headers['WWW-Authenticate'] = 'Keystone uri="%s"' % url
  117     return response
  118 
  119 
  120 def _handle_unknown_keystone_exception(error):
  121     # translate a python exception to something we can properly render as
  122     # an API error.
  123     if isinstance(error, TypeError):
  124         new_exc = exception.ValidationError(error)
  125     else:
  126         new_exc = exception.UnexpectedError(error)
  127     return _handle_keystone_exception(new_exc)
  128 
  129 
  130 @fail_gracefully
  131 def application_factory(name='public'):
  132     if name not in ('admin', 'public'):
  133         raise RuntimeError('Application name (for base_url lookup) must be '
  134                            'either `admin` or `public`.')
  135 
  136     app = flask.Flask(name)
  137 
  138     # Register Error Handler Function for Keystone Errors.
  139     # NOTE(morgan): Flask passes errors to an error handling function. All of
  140     # keystone's api errors are explicitly registered in
  141     # keystone.exception.KEYSTONE_API_EXCEPTIONS and those are in turn
  142     # registered here to ensure a proper error is bubbled up to the end user
  143     # instead of a 500 error.
  144     for exc in exception.KEYSTONE_API_EXCEPTIONS:
  145         app.register_error_handler(exc, _handle_keystone_exception)
  146 
  147     # Register extra (python) exceptions with the proper exception handler,
  148     # specifically TypeError. It will render as a 400 error, but presented in
  149     # a "web-ified" manner
  150     app.register_error_handler(TypeError, _handle_unknown_keystone_exception)
  151 
  152     # Add core before request functions
  153     app.before_request(req_logging.log_request_info)
  154     app.before_request(json_body.json_body_before_request)
  155 
  156     # Add core after request functions
  157     app.after_request(_add_vary_x_auth_token_header)
  158 
  159     # NOTE(morgan): Configure the Flask Environment for our needs.
  160     app.config.update(
  161         # We want to bubble up Flask Exceptions (for now)
  162         PROPAGATE_EXCEPTIONS=True)
  163 
  164     for api in keystone.api.__apis__:
  165         for api_bp in api.APIs:
  166             api_bp.instantiate_and_register_to_app(app)
  167 
  168     # Load in Healthcheck and map it to /healthcheck
  169     hc_app = healthcheck.Healthcheck.app_factory(
  170         {}, oslo_config_project='keystone')
  171 
  172     # Use the simple form of the dispatch middleware, no extra logic needed
  173     # for legacy dispatching. This is to mount /healthcheck at a consistent
  174     # place
  175     app.wsgi_app = wsgi_dispatcher.DispatcherMiddleware(
  176         app.wsgi_app,
  177         {'/healthcheck': hc_app})
  178     return app