summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJamie Lennox <jamielennox@redhat.com>2014-09-04 17:58:49 +1000
committerJamie Lennox <jamielennox@redhat.com>2014-09-05 13:18:27 +1000
commit563991136646eb6d913e94e482323966b0393db2 (patch)
tree6bdd7e5a6f3e0ec4c90567e0573d08f6bb89539d
parentf61fe33f558ba3f02fc2400507d5f71de21e6d12 (diff)
downloadkeystonemiddleware-563991136646eb6d913e94e482323966b0393db2.tar.gz
Always add auth URI to unauthorized requests
For those services that use delay_auth_decision we need to support adding the keystone URI rejection headers to the response in a uniform way. I feel this should be more generic and that every 401 response should contain this header. Create a WSGI wrapper so that if a 401 is ever returned through auth_token middleware we can add an additional WWW-Authenticate header. Closes-Bug: #1349364 Change-Id: Ib5231a09fd5c6cb6cd17f07c87e982d2e8fde2bf
-rw-r--r--keystonemiddleware/auth_token.py26
-rw-r--r--keystonemiddleware/tests/test_auth_token_middleware.py40
2 files changed, 60 insertions, 6 deletions
diff --git a/keystonemiddleware/auth_token.py b/keystonemiddleware/auth_token.py
index 15d3df7..c84e6de 100644
--- a/keystonemiddleware/auth_token.py
+++ b/keystonemiddleware/auth_token.py
@@ -549,6 +549,18 @@ class AuthProtocol(object):
else:
return CONF.keystone_authtoken[name]
+ def _call_app(self, env, start_response):
+ # NOTE(jamielennox): We wrap the given start response so that if an
+ # application with a 'delay_auth_decision' setting fails, or otherwise
+ # raises Unauthorized that we include the Authentication URL headers.
+ def _fake_start_response(status, response_headers, exc_info=None):
+ if status.startswith('401'):
+ response_headers.extend(self._reject_auth_headers)
+
+ return start_response(status, response_headers, exc_info)
+
+ return self._app(env, _fake_start_response)
+
def __call__(self, env, start_response):
"""Handle incoming request.
@@ -567,14 +579,14 @@ class AuthProtocol(object):
env['keystone.token_info'] = token_info
user_headers = self._build_user_headers(token_info)
self._add_headers(env, user_headers)
- return self._app(env, start_response)
+ return self._call_app(env, start_response)
except InvalidUserToken:
if self._delay_auth_decision:
self._LOG.info(
'Invalid user token - deferring reject downstream')
self._add_headers(env, {'X-Identity-Status': 'Invalid'})
- return self._app(env, start_response)
+ return self._call_app(env, start_response)
else:
self._LOG.info('Invalid user token - rejecting request')
return self._reject_request(env, start_response)
@@ -635,6 +647,11 @@ class AuthProtocol(object):
self._LOG.debug('Headers: %s', env)
raise InvalidUserToken('Unable to find token in headers')
+ @property
+ def _reject_auth_headers(self):
+ header_val = 'Keystone uri=\'%s\'' % self._identity_server.auth_uri
+ return [('WWW-Authenticate', header_val)]
+
def _reject_request(self, env, start_response):
"""Redirect client to auth server.
@@ -643,9 +660,8 @@ class AuthProtocol(object):
:returns HTTPUnauthorized http response
"""
- header_val = 'Keystone uri=\'%s\'' % self._identity_server.auth_uri
- headers = [('WWW-Authenticate', header_val)]
- resp = _MiniResp('Authentication required', env, headers)
+ resp = _MiniResp('Authentication required',
+ env, self._reject_auth_headers)
start_response('401 Unauthorized', resp.headers)
return resp.body
diff --git a/keystonemiddleware/tests/test_auth_token_middleware.py b/keystonemiddleware/tests/test_auth_token_middleware.py
index 55e79d0..6933fb5 100644
--- a/keystonemiddleware/tests/test_auth_token_middleware.py
+++ b/keystonemiddleware/tests/test_auth_token_middleware.py
@@ -17,6 +17,7 @@ import datetime
import json
import os
import shutil
+import six
import stat
import tempfile
import time
@@ -34,6 +35,7 @@ import testresources
import testtools
from testtools import matchers
import webob
+import webob.dec
from keystonemiddleware import auth_token
from keystonemiddleware.openstack.common import jsonutils
@@ -201,6 +203,22 @@ class v3FakeApp(FakeApp):
super(v3FakeApp, self).__init__(v3_default_env_additions)
+def new_app(status, body, headers={}):
+
+ class _App(object):
+
+ def __init__(self, expected_env=None):
+ self.expected_env = expected_env
+
+ @webob.dec.wsgify
+ def __call__(self, req):
+ resp = webob.Response(body, status)
+ resp.headers.update(headers)
+ return resp
+
+ return _App
+
+
class BaseAuthTokenMiddlewareTest(testtools.TestCase):
"""Base test class for auth_token middleware.
@@ -261,7 +279,7 @@ class BaseAuthTokenMiddlewareTest(testtools.TestCase):
self.middleware._token_revocation_list = jsonutils.dumps(
{"revoked": [], "extra": "success"})
- def start_fake_response(self, status, headers):
+ def start_fake_response(self, status, headers, exc_info=None):
self.response_status = int(status.split(' ', 1)[0])
self.response_headers = dict(headers)
@@ -2016,5 +2034,25 @@ class CatalogConversionTests(BaseAuthTokenMiddlewareTest):
self.assertIn(e, expected)
+class DelayedAuthTests(BaseAuthTokenMiddlewareTest):
+
+ def test_header_in_401(self):
+ body = uuid.uuid4().hex
+ auth_uri = 'http://local.test'
+ conf = {'delay_auth_decision': True, 'auth_uri': auth_uri}
+
+ self.fake_app = new_app('401 Unauthorized', body)
+ self.set_middleware(conf=conf)
+
+ req = webob.Request.blank('/')
+ resp = self.middleware(req.environ, self.start_fake_response)
+
+ self.assertEqual([six.b(body)], resp)
+
+ self.assertEqual(401, self.response_status)
+ self.assertEqual("Keystone uri='%s'" % auth_uri,
+ self.response_headers['WWW-Authenticate'])
+
+
def load_tests(loader, tests, pattern):
return testresources.OptimisingTestSuite(tests)