OpenStack Identity (Keystone)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

application.py 9.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  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. from __future__ import absolute_import
  13. import collections
  14. import functools
  15. import itertools
  16. import sys
  17. import flask
  18. from oslo_log import log
  19. from oslo_middleware import healthcheck
  20. import routes
  21. import werkzeug.wsgi
  22. import keystone.api
  23. from keystone.application_credential import routers as app_cred_routers
  24. from keystone.assignment import routers as assignment_routers
  25. from keystone.auth import routers as auth_routers
  26. from keystone.catalog import routers as catalog_routers
  27. from keystone.common import wsgi as keystone_wsgi
  28. from keystone.contrib.ec2 import routers as ec2_routers
  29. from keystone.contrib.s3 import routers as s3_routers
  30. from keystone.endpoint_policy import routers as endpoint_policy_routers
  31. from keystone.federation import routers as federation_routers
  32. from keystone.identity import routers as identity_routers
  33. from keystone.oauth1 import routers as oauth1_routers
  34. from keystone.policy import routers as policy_routers
  35. from keystone.resource import routers as resource_routers
  36. # TODO(morgan): _MOVED_API_PREFIXES to be removed when the legacy dispatch
  37. # support is removed.
  38. _MOVED_API_PREFIXES = frozenset(
  39. ['credentials',
  40. 'OS-OAUTH1',
  41. 'OS-EP-FILTER',
  42. 'OS-REVOKE',
  43. 'OS-SIMPLE-CERT',
  44. 'OS-TRUST',
  45. 'limits',
  46. 'regions',
  47. 'registered_limits',
  48. 'services',
  49. ]
  50. )
  51. LOG = log.getLogger(__name__)
  52. ALL_API_ROUTERS = [auth_routers,
  53. assignment_routers,
  54. catalog_routers,
  55. identity_routers,
  56. app_cred_routers,
  57. policy_routers,
  58. resource_routers,
  59. federation_routers,
  60. oauth1_routers,
  61. endpoint_policy_routers,
  62. ec2_routers,
  63. s3_routers]
  64. def fail_gracefully(f):
  65. """Log exceptions and aborts."""
  66. @functools.wraps(f)
  67. def wrapper(*args, **kw):
  68. try:
  69. return f(*args, **kw)
  70. except Exception as e:
  71. LOG.debug(e, exc_info=True)
  72. # exception message is printed to all logs
  73. LOG.critical(e)
  74. sys.exit(1)
  75. return wrapper
  76. class KeystoneDispatcherMiddleware(werkzeug.wsgi.DispatcherMiddleware):
  77. """Allows one to mount middlewares or applications in a WSGI application.
  78. This is useful if you want to combine multiple WSGI applications::
  79. app = DispatcherMiddleware(app, {
  80. '/app2': app2,
  81. '/app3': app3
  82. })
  83. This is a modified version of the werkzeurg.wsgi.DispatchMiddleware to
  84. handle the "SCRIPT_NAME" and "PATH_INFO" mangling in a way that is
  85. compatible with the way paste.deploy and routes.Mapper works. For
  86. Migration from legacy routes.Mapper to native flask blueprints, we are
  87. treating each subsystem as their own "app".
  88. This Dispatcher also logs (debug) if we are dispatching a request to
  89. a non-native flask Mapper.
  90. """
  91. @property
  92. def config(self):
  93. return self.app.config
  94. def __call__(self, environ, start_response):
  95. script = environ.get('PATH_INFO', '')
  96. original_script_name = environ.get('SCRIPT_NAME', '')
  97. last_element = ''
  98. path_info = ''
  99. while '/' in script:
  100. if script in self.mounts:
  101. LOG.debug('Dispatching request to legacy mapper: %s',
  102. script)
  103. app = self.mounts[script]
  104. # NOTE(morgan): Simply because we're doing something "odd"
  105. # here and internally routing magically to another "wsgi"
  106. # router even though we're already deep in the stack we
  107. # need to re-add the last element pulled off. This is 100%
  108. # legacy and only applies to the "apps" that make up each
  109. # keystone subsystem.
  110. #
  111. # This middleware is only used in support of the transition
  112. # process from webob and home-rolled WSGI framework to
  113. # Flask
  114. if script.rindex('/') > 0:
  115. script, last_element = script.rsplit('/', 1)
  116. last_element = '/%s' % last_element
  117. environ['SCRIPT_NAME'] = original_script_name + script
  118. # Ensure there is only 1 slash between these items, the
  119. # mapper gets horribly confused if we have // in there,
  120. # which occasionally. As this is temporary to dispatch
  121. # to the Legacy mapper, fix the string until we no longer
  122. # need this logic.
  123. environ['PATH_INFO'] = '%s/%s' % (last_element.rstrip('/'),
  124. path_info.strip('/'))
  125. break
  126. script, last_item = script.rsplit('/', 1)
  127. path_info = '/%s%s' % (last_item, path_info)
  128. else:
  129. app = self.mounts.get(script, self.app)
  130. if app != self.app:
  131. LOG.debug('Dispatching (fallthrough) request to legacy '
  132. 'mapper: %s', script)
  133. else:
  134. LOG.debug('Dispatching back to Flask native app.')
  135. environ['SCRIPT_NAME'] = original_script_name + script
  136. environ['PATH_INFO'] = path_info
  137. # NOTE(morgan): remove extra trailing slashes so the mapper can do the
  138. # right thing and get the requests mapped to the right place. For
  139. # example, "/v3/projects/" is not the same as "/v3/projects". We do not
  140. # want to blindly rstrip for the case of '/'.
  141. if environ['PATH_INFO'][-1] == '/' and len(environ['PATH_INFO']) > 1:
  142. environ['PATH_INFO'] = environ['PATH_INFO'][0:-1]
  143. LOG.debug('SCRIPT_NAME: `%s`, PATH_INFO: `%s`',
  144. environ['SCRIPT_NAME'], environ['PATH_INFO'])
  145. return app(environ, start_response)
  146. class _ComposibleRouterStub(keystone_wsgi.ComposableRouter):
  147. def __init__(self, routers):
  148. self._routers = routers
  149. def _add_vary_x_auth_token_header(response):
  150. # Add the expected Vary Header, this is run after every request in the
  151. # response-phase
  152. response.headers['Vary'] = 'X-Auth-Token'
  153. return response
  154. @fail_gracefully
  155. def application_factory(name='public'):
  156. if name not in ('admin', 'public'):
  157. raise RuntimeError('Application name (for base_url lookup) must be '
  158. 'either `admin` or `public`.')
  159. # NOTE(morgan): The Flask App actually dispatches nothing until we migrate
  160. # some routers to Flask-Blueprints, it is simply a placeholder.
  161. app = flask.Flask(name)
  162. app.after_request(_add_vary_x_auth_token_header)
  163. # NOTE(morgan): Configure the Flask Environment for our needs.
  164. app.config.update(
  165. # We want to bubble up Flask Exceptions (for now)
  166. PROPAGATE_EXCEPTIONS=True)
  167. # TODO(morgan): Convert Subsystems over to Flask-Native, for now, we simply
  168. # dispatch to another "application" [e.g "keystone"]
  169. # NOTE(morgan): as each router is converted to flask-native blueprint,
  170. # remove from this list. WARNING ORDER MATTERS; ordered dict used to
  171. # ensure sane ordering of the routers in the legacy-dispatch model.
  172. dispatch_map = collections.OrderedDict()
  173. # Load in Healthcheck and map it to /healthcheck
  174. hc_app = healthcheck.Healthcheck.app_factory(
  175. {}, oslo_config_project='keystone')
  176. dispatch_map['/healthcheck'] = hc_app
  177. # More legacy code to instantiate all the magic for the dispatchers.
  178. # The move to blueprints (FLASK) will allow this to be eliminated.
  179. _routers = []
  180. sub_routers = []
  181. mapper = routes.Mapper()
  182. for api_routers in ALL_API_ROUTERS:
  183. moved_found = [pfx for
  184. pfx in getattr(api_routers, '_path_prefixes', [])
  185. if pfx in _MOVED_API_PREFIXES]
  186. if moved_found:
  187. raise RuntimeError('An API Router is trying to register path '
  188. 'prefix(s) `%(pfx)s` that is handled by the '
  189. 'native Flask app. Keystone cannot '
  190. 'start.' %
  191. {'pfx': ', '.join([p for p in moved_found])})
  192. routers_instance = api_routers.Routers()
  193. _routers.append(routers_instance)
  194. routers_instance.append_v3_routers(mapper, sub_routers)
  195. # TODO(morgan): Remove "API version registration". For now this is kept
  196. # for ease of conversion (minimal changes)
  197. keystone.api.discovery.register_version('v3')
  198. # NOTE(morgan): We add in all the keystone.api blueprints here, this
  199. # replaces (as they are implemented) the legacy dispatcher work.
  200. for api in keystone.api.__apis__:
  201. for api_bp in api.APIs:
  202. api_bp.instantiate_and_register_to_app(app)
  203. # Build and construct the dispatching for the Legacy dispatching model
  204. sub_routers.append(_ComposibleRouterStub(_routers))
  205. legacy_dispatcher = keystone_wsgi.ComposingRouter(mapper, sub_routers)
  206. for pfx in itertools.chain(*[rtr.Routers._path_prefixes for
  207. rtr in ALL_API_ROUTERS]):
  208. dispatch_map['/v3/%s' % pfx] = legacy_dispatcher
  209. app.wsgi_app = KeystoneDispatcherMiddleware(
  210. app.wsgi_app,
  211. dispatch_map)
  212. return app