diff options
author | Ruan HE <ruan.he@orange.com> | 2016-06-09 08:12:34 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@172.30.200.206> | 2016-06-09 08:12:34 +0000 |
commit | 4bc079a2664f9a407e332291f34d174625a9d5ea (patch) | |
tree | 7481cd5d0a9b3ce37c44c797a1e0d39881221cbe /keystone-moon/keystone/common/wsgi.py | |
parent | 2f179c5790fbbf6144205d3c6e5089e6eb5f048a (diff) | |
parent | 2e7b4f2027a1147ca28301e4f88adf8274b39a1f (diff) |
Merge "Update Keystone core to Mitaka."
Diffstat (limited to 'keystone-moon/keystone/common/wsgi.py')
-rw-r--r-- | keystone-moon/keystone/common/wsgi.py | 129 |
1 files changed, 69 insertions, 60 deletions
diff --git a/keystone-moon/keystone/common/wsgi.py b/keystone-moon/keystone/common/wsgi.py index 8b99c87d..04528a0c 100644 --- a/keystone-moon/keystone/common/wsgi.py +++ b/keystone-moon/keystone/common/wsgi.py @@ -20,6 +20,7 @@ import copy import itertools +import re import wsgiref.util from oslo_config import cfg @@ -71,9 +72,6 @@ def validate_token_bind(context, token_ref): # permissive and strict modes don't require there to be a bind permissive = bind_mode in ('permissive', 'strict') - # get the named mode if bind_mode is not one of the known - name = None if permissive or bind_mode == 'required' else bind_mode - if not bind: if permissive: # no bind provided and none required @@ -82,6 +80,9 @@ def validate_token_bind(context, token_ref): LOG.info(_LI("No bind information present in token")) raise exception.Unauthorized() + # get the named mode if bind_mode is not one of the known + name = None if permissive or bind_mode == 'required' else bind_mode + if name and name not in bind: LOG.info(_LI("Named bind mode %s not in bind information"), name) raise exception.Unauthorized() @@ -112,10 +113,11 @@ def validate_token_bind(context, token_ref): def best_match_language(req): - """Determines the best available locale from the Accept-Language - HTTP header passed in the request. - """ + """Determines the best available locale. + This returns best available locale based on the Accept-Language HTTP + header passed in the request. + """ if not req.accept_language: return None return req.accept_language.best_match( @@ -208,8 +210,7 @@ class Application(BaseApplication): context['headers'] = dict(req.headers.items()) context['path'] = req.environ['PATH_INFO'] - scheme = (None if not CONF.secure_proxy_ssl_header - else req.environ.get(CONF.secure_proxy_ssl_header)) + scheme = req.environ.get(CONF.secure_proxy_ssl_header) if scheme: # NOTE(andrey-mp): "wsgi.url_scheme" contains the protocol used # before the proxy removed it ('https' usually). So if @@ -305,7 +306,6 @@ class Application(BaseApplication): does not have the admin role """ - if not context['is_admin']: user_token_ref = utils.get_token_ref(context) @@ -329,9 +329,7 @@ class Application(BaseApplication): self.policy_api.enforce(creds, 'admin_required', {}) def _attribute_is_empty(self, ref, attribute): - """Returns true if the attribute in the given ref (which is a - dict) is empty or None. - """ + """Determine if the attribute in ref is empty or None.""" return ref.get(attribute) is None or ref.get(attribute) == '' def _require_attribute(self, ref, attribute): @@ -378,13 +376,19 @@ class Application(BaseApplication): itertools.chain(CONF.items(), CONF.eventlet_server.items())) url = url % substitutions + elif 'environment' in context: + url = wsgiref.util.application_uri(context['environment']) + # remove version from the URL as it may be part of SCRIPT_NAME but + # it should not be part of base URL + url = re.sub(r'/v(3|(2\.0))/*$', '', url) + + # now remove the standard port + url = utils.remove_standard_port(url) else: - # NOTE(jamielennox): if url is not set via the config file we - # should set it relative to the url that the user used to get here - # so as not to mess with version discovery. This is not perfect. - # host_url omits the path prefix, but there isn't another good - # solution that will work for all urls. - url = context['host_url'] + # if we don't have enough information to come up with a base URL, + # then fall back to localhost. This should never happen in + # production environment. + url = 'http://localhost:%d' % CONF.eventlet_server.public_port return url.rstrip('/') @@ -400,32 +404,10 @@ class Middleware(Application): """ @classmethod - def factory(cls, global_config, **local_config): - """Used for paste app factories in paste.deploy config files. - - Any local configuration (that is, values under the [filter:APPNAME] - section of the paste config) will be passed into the `__init__` method - as kwargs. - - A hypothetical configuration would look like: - - [filter:analytics] - redis_host = 127.0.0.1 - paste.filter_factory = keystone.analytics:Analytics.factory - - which would result in a call to the `Analytics` class as - - import keystone.analytics - keystone.analytics.Analytics(app, redis_host='127.0.0.1') - - You could of course re-implement the `factory` method in subclasses, - but using the kwarg passing it shouldn't be necessary. - - """ + def factory(cls, global_config): + """Used for paste app factories in paste.deploy config files.""" def _factory(app): - conf = global_config.copy() - conf.update(local_config) - return cls(app, **local_config) + return cls(app) return _factory def __init__(self, application): @@ -601,6 +583,7 @@ class ExtensionRouter(Router): Expects to be subclassed. """ + def __init__(self, application, mapper=None): if mapper is None: mapper = routes.Mapper() @@ -737,8 +720,8 @@ class V3ExtensionRouter(ExtensionRouter, RoutersBase): response_data = jsonutils.loads(response.body) self._update_version_response(response_data) - response.body = jsonutils.dumps(response_data, - cls=utils.SmarterEncoder) + response.body = jsonutils.dump_as_bytes(response_data, + cls=utils.SmarterEncoder) return response @@ -751,7 +734,7 @@ def render_response(body=None, status=None, headers=None, method=None): headers.append(('Vary', 'X-Auth-Token')) if body is None: - body = '' + body = b'' status = status or (204, 'No Content') else: content_types = [v for h, v in headers if h == 'Content-Type'] @@ -761,11 +744,41 @@ def render_response(body=None, status=None, headers=None, method=None): content_type = None if content_type is None or content_type in JSON_ENCODE_CONTENT_TYPES: - body = jsonutils.dumps(body, cls=utils.SmarterEncoder) + body = jsonutils.dump_as_bytes(body, cls=utils.SmarterEncoder) if content_type is None: headers.append(('Content-Type', 'application/json')) status = status or (200, 'OK') + # NOTE(davechen): `mod_wsgi` follows the standards from pep-3333 and + # requires the value in response header to be binary type(str) on python2, + # unicode based string(str) on python3, or else keystone will not work + # under apache with `mod_wsgi`. + # keystone needs to check the data type of each header and convert the + # type if needed. + # see bug: + # https://bugs.launchpad.net/keystone/+bug/1528981 + # see pep-3333: + # https://www.python.org/dev/peps/pep-3333/#a-note-on-string-types + # see source from mod_wsgi: + # https://github.com/GrahamDumpleton/mod_wsgi(methods: + # wsgi_convert_headers_to_bytes(...), wsgi_convert_string_to_bytes(...) + # and wsgi_validate_header_value(...)). + def _convert_to_str(headers): + str_headers = [] + for header in headers: + str_header = [] + for value in header: + if not isinstance(value, str): + str_header.append(str(value)) + else: + str_header.append(value) + # convert the list to the immutable tuple to build the headers. + # header's key/value will be guaranteed to be str type. + str_headers.append(tuple(str_header)) + return str_headers + + headers = _convert_to_str(headers) + resp = webob.Response(body=body, status='%s %s' % status, headerlist=headers) @@ -789,7 +802,6 @@ def render_response(body=None, status=None, headers=None, method=None): def render_exception(error, context=None, request=None, user_locale=None): """Forms a WSGI response based on the current error.""" - error_message = error.args[0] message = oslo_i18n.translate(error_message, desired_locale=user_locale) if message is error_message: @@ -806,18 +818,15 @@ def render_exception(error, context=None, request=None, user_locale=None): if isinstance(error, exception.AuthPluginException): body['error']['identity'] = error.authentication elif isinstance(error, exception.Unauthorized): - url = CONF.public_endpoint - if not url: - if request: - context = {'host_url': request.host_url} - if context: - url = Application.base_url(context, 'public') - else: - url = 'http://localhost:%d' % CONF.eventlet_server.public_port - else: - substitutions = dict( - itertools.chain(CONF.items(), CONF.eventlet_server.items())) - url = url % substitutions + # NOTE(gyee): we only care about the request environment in the + # context. Also, its OK to pass the environemt as it is read-only in + # Application.base_url() + local_context = {} + if request: + local_context = {'environment': request.environ} + elif context and 'environment' in context: + local_context = {'environment': context['environment']} + url = Application.base_url(local_context, 'public') headers.append(('WWW-Authenticate', 'Keystone uri="%s"' % url)) return render_response(status=(error.code, error.title), |