"Fossies" - the Fresh Open Source Software Archive

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