summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2011-12-23 23:55:34 +0000
committerGerrit Code Review <review@openstack.org>2011-12-23 23:55:34 +0000
commit1a57df3da11c7b627f810b61a95237d1a5ccc3cc (patch)
treec41fa0416a1cebaf07daa62970b42ed579e76d9b
parente1be8d5d07e87c38ea1a59cb620fc83960a30ff3 (diff)
parentaca713d5e0e0f5cd3e58dd45e1fd1d5c3fed141e (diff)
Merge "Implement Multiple Choices Response (bug 843051)"
-rw-r--r--keystone/content/multiple_choice.json.tpl26
-rw-r--r--keystone/content/multiple_choice.xml.tpl16
-rw-r--r--keystone/controllers/version.py54
-rw-r--r--keystone/frontends/legacy_token_auth.py2
-rw-r--r--keystone/middleware/url.py49
-rw-r--r--keystone/test/unit/test_normalizingfilter.py9
6 files changed, 141 insertions, 15 deletions
diff --git a/keystone/content/multiple_choice.json.tpl b/keystone/content/multiple_choice.json.tpl
new file mode 100644
index 00000000..578502ac
--- /dev/null
+++ b/keystone/content/multiple_choice.json.tpl
@@ -0,0 +1,26 @@
+{
+ "choices": [
+ {
+ "id": "v{{API_VERSION}}",
+ "status": "{{API_VERSION_STATUS}}",
+ "links": [
+ {
+ "rel": "self",
+ "href": "{{PROTOCOL}}://{{HOST}}:{{PORT}}/v{{API_VERSION}}{{RESOURCE_PATH}}"
+ }
+ ],
+ "media-types": {
+ "values": [
+ {
+ "base": "application/xml",
+ "type": "application/vnd.openstack.identity+xml;version={{API_VERSION}}"
+ },
+ {
+ "base": "application/json",
+ "type": "application/vnd.openstack.identity+json;version={{API_VERSION}}"
+ }
+ ]
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/keystone/content/multiple_choice.xml.tpl b/keystone/content/multiple_choice.xml.tpl
new file mode 100644
index 00000000..9f3b88a4
--- /dev/null
+++ b/keystone/content/multiple_choice.xml.tpl
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<choices
+ xmlns="http://docs.openstack.org/common/api/v1.0"
+ xmlns:atom="http://www.w3.org/2005/Atom">
+ <version id="v{{API_VERSION}}" status="{{API_VERSION_STATUS}}">
+ <media-types>
+ <media-type
+ base="application/xml"
+ type="application/vnd.openstack.identity+xml;version={{API_VERSION}}" />
+ <media-type
+ base="application/json"
+ type="application/vnd.openstack.identity+json;version={{API_VERSION}}" />
+ </media-types>
+ <atom:link rel="self" href="{{PROTOCOL}}://{{HOST}}:{{PORT}}/v{{API_VERSION}}{{RESOURCE_PATH}}" />
+ </version>
+</choices> \ No newline at end of file
diff --git a/keystone/controllers/version.py b/keystone/controllers/version.py
index 30fe1c31..efdd93b6 100644
--- a/keystone/controllers/version.py
+++ b/keystone/controllers/version.py
@@ -2,7 +2,7 @@ import os
from webob import Response
# Calculate root path (to get to static files)
-possible_topdir = os.path.normpath(os.path.join(os.path.dirname(__file__),
+POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.dirname(__file__),
os.pardir,
os.pardir))
@@ -18,11 +18,11 @@ class VersionController(wsgi.Controller):
self.options = options
@utils.wrap_error
- def get_version_info(self, req, file="version"):
+ def get_version_info(self, req, file="version"):
resp = Response()
resp.charset = 'UTF-8'
if utils.is_xml_response(req):
- resp_file = os.path.join(possible_topdir,
+ resp_file = os.path.join(POSSIBLE_TOPDIR,
"keystone/content/%s.xml.tpl" % file)
resp.content_type = "application/xml"
elif utils.is_atom_response(req):
@@ -30,14 +30,19 @@ class VersionController(wsgi.Controller):
"keystone/content/%s.atom.tpl" % file)
resp.content_type = "application/atom+xml"
else:
- resp_file = os.path.join(possible_topdir,
+ resp_file = os.path.join(POSSIBLE_TOPDIR,
"keystone/content/%s.json.tpl" % file)
resp.content_type = "application/json"
hostname = req.environ.get("SERVER_NAME")
port = req.environ.get("SERVER_PORT")
+ if 'HTTPS' in req.environ:
+ protocol = 'https'
+ else:
+ protocol = 'http'
resp.unicode_body = template.template(resp_file,
+ PROTOCOL=protocol,
HOST=hostname,
PORT=port,
API_VERSION=version.API_VERSION,
@@ -45,3 +50,44 @@ class VersionController(wsgi.Controller):
API_VERSION_DATE=version.API_VERSION_DATE)
return resp
+
+ @utils.wrap_error
+ def get_multiple_choice(self, req, file="multiple_choice", path=None):
+ """ Returns a multiple-choices response based on API spec
+
+ Response will include in it only one choice, which is for the
+ current API version. The response is a 300 Multiple Choice
+ response with either an XML or JSON body.
+
+ """
+ if path is None:
+ path = ''
+ resp = Response(status="300 Multiple Choices")
+ resp.charset = 'UTF-8'
+ if utils.is_xml_response(req):
+ resp_file = os.path.join(POSSIBLE_TOPDIR,
+ "keystone/content/%s.xml.tpl" % file)
+ resp.content_type = "application/xml"
+ else:
+ resp_file = os.path.join(POSSIBLE_TOPDIR,
+ "keystone/content/%s.json.tpl" % file)
+ resp.content_type = "application/json"
+
+ hostname = req.environ.get("SERVER_NAME")
+ port = req.environ.get("SERVER_PORT")
+ if 'HTTPS' in req.environ:
+ protocol = 'https'
+ else:
+ protocol = 'http'
+
+ resp.unicode_body = template.template(resp_file,
+ PROTOCOL=protocol,
+ HOST=hostname,
+ PORT=port,
+ API_VERSION=version.API_VERSION,
+ API_VERSION_STATUS=version.API_VERSION_STATUS,
+ API_VERSION_DATE=version.API_VERSION_DATE,
+ RESOURCE_PATH=path
+ )
+
+ return resp
diff --git a/keystone/frontends/legacy_token_auth.py b/keystone/frontends/legacy_token_auth.py
index 3dcc1c84..498357e7 100644
--- a/keystone/frontends/legacy_token_auth.py
+++ b/keystone/frontends/legacy_token_auth.py
@@ -52,7 +52,7 @@ class AuthProtocol(object):
def __call__(self, env, start_response):
""" Handle incoming request. Transform. And send downstream. """
request = Request(env)
- if env['KEYSTONE_API_VERSION'] in ['1.0', '1.1']:
+ if env.get('KEYSTONE_API_VERSION') in ['1.0', '1.1']:
params = {"auth": {"passwordCredentials":
{"username": utils.get_auth_user(request),
"password": utils.get_auth_key(request)}}}
diff --git a/keystone/middleware/url.py b/keystone/middleware/url.py
index fa2f6183..ddad6036 100644
--- a/keystone/middleware/url.py
+++ b/keystone/middleware/url.py
@@ -27,7 +27,11 @@ overwrites the Accept header in the request, if present.
"""
+import logging
import webob.acceptparse
+from webob import Request
+
+logger = logging.getLogger('keystone.middleware.url')
# Maps supported URL prefixes to API_VERSION
PATH_PREFIXES = {
@@ -43,18 +47,23 @@ PATH_SUFFIXES = {
# Maps supported Accept headers to RESPONSE_ENCODING and API_VERSION
ACCEPT_HEADERS = {
+ 'application/vnd.openstack.identity+json;version=2.0': ('json', '2.0'),
+ 'application/vnd.openstack.identity+xml;version=2.0': ('xml', '2.0'),
'application/vnd.openstack.identity-v2.0+json': ('json', '2.0'),
'application/vnd.openstack.identity-v2.0+xml': ('xml', '2.0'),
+ 'application/vnd.openstack.identity+json;version=1.1': ('json', '1.1'),
+ 'application/vnd.openstack.identity+xml;version=1.1': ('xml', '1.1'),
'application/vnd.openstack.identity-v1.1+json': ('json', '1.1'),
'application/vnd.openstack.identity-v1.1+xml': ('xml', '1.1'),
'application/vnd.openstack.identity-v1.0+json': ('json', '1.0'),
'application/vnd.openstack.identity-v1.0+xml': ('xml', '1.0'),
+ 'application/vnd.openstack.identity+json;version=1.0': ('json', '1.0'),
+ 'application/vnd.openstack.identity+xml;version=1.0': ('xml', '1.0'),
'application/json': ('json', None),
'application/xml': ('xml', None),
'application/atom+xml': ('atom+xml', None)}
DEFAULT_RESPONSE_ENCODING = 'json'
-DEFAULT_API_VERSION = '2.0'
class NormalizingFilter(object):
@@ -74,13 +83,22 @@ class NormalizingFilter(object):
env['PATH_INFO'] = normalize_trailing_slash(env['PATH_INFO'])
# Fall back on defaults, if necessary
- env['KEYSTONE_API_VERSION'] = env.get(
- 'KEYSTONE_API_VERSION') or DEFAULT_API_VERSION
env['KEYSTONE_RESPONSE_ENCODING'] = env.get(
'KEYSTONE_RESPONSE_ENCODING') or DEFAULT_RESPONSE_ENCODING
env['HTTP_ACCEPT'] = 'application/' + (env.get(
'KEYSTONE_RESPONSE_ENCODING') or DEFAULT_RESPONSE_ENCODING)
+ if 'KEYSTONE_API_VERSION' not in env:
+ # Version was not specified in path or headers
+ # return multiple choice unless the version controller can handle
+ # this request
+ if env['PATH_INFO'] not in ['/', '']:
+ from keystone.controllers.version import VersionController
+ controller = VersionController(options=None)
+ response = controller.get_multiple_choice(req=Request(env),
+ file='multiple_choice')
+ return response(env, start_response)
+
return self.app(env, start_response)
@@ -88,26 +106,37 @@ def normalize_accept_header(env):
"""Matches the preferred Accept encoding to supported encodings.
Sets KEYSTONE_RESPONSE_ENCODING and KEYSTONE_API_VERSION, if appropriate.
+
+ Note:: webob.acceptparse ignores ';version=' values
"""
accept_value = env.get('HTTP_ACCEPT')
if accept_value:
- try:
- accept = webob.acceptparse.Accept(accept_value)
- except TypeError:
- # Support `webob` v1.1 and older.
- accept = webob.acceptparse.Accept('Accept', accept_value)
-
- best_accept = accept.best_match(ACCEPT_HEADERS.keys())
+ if accept_value in ACCEPT_HEADERS.keys():
+ # Check for direct match first
+ best_accept = accept_value
+ else:
+ try:
+ accept = webob.acceptparse.Accept(accept_value)
+ except TypeError:
+ # Support `webob` v1.1 and older.
+ accept = webob.acceptparse.Accept('Accept', accept_value)
+
+ best_accept = accept.best_match(ACCEPT_HEADERS.keys())
if best_accept:
response_encoding, api_version = ACCEPT_HEADERS[best_accept]
+ logger.debug('%s header matched with %s (API=%s, TYPE=%s)',
+ accept_value, best_accept, api_version,
+ response_encoding)
if response_encoding:
env['KEYSTONE_RESPONSE_ENCODING'] = response_encoding
if api_version:
env['KEYSTONE_API_VERSION'] = api_version
+ else:
+ logger.debug('%s header could not be matched', accept_value)
return env
diff --git a/keystone/test/unit/test_normalizingfilter.py b/keystone/test/unit/test_normalizingfilter.py
index 1b608729..4286ab2f 100644
--- a/keystone/test/unit/test_normalizingfilter.py
+++ b/keystone/test/unit/test_normalizingfilter.py
@@ -72,6 +72,15 @@ class NormalizingFilterTest(unittest.TestCase):
self.assertEqual('/someresource', env['PATH_INFO'])
self.assertEqual('application/json', env['HTTP_ACCEPT'])
+ def test_version_header(self):
+ env = {'PATH_INFO': '/someresource',
+ 'HTTP_ACCEPT':
+ 'application/vnd.openstack.identity+xml;version=2.0'}
+ self.filter(env, _start_response)
+ self.assertEqual('/someresource', env['PATH_INFO'])
+ self.assertEqual('application/xml', env['HTTP_ACCEPT'])
+ self.assertEqual('2.0', env['KEYSTONE_API_VERSION'])
+
def test_extension_overrides_header(self):
env = {
'PATH_INFO': '/v2.0/someresource.json',