Enable anonymous access through context middleware

Certain deployments need to allow anonymous access to its
images. This patch allows a user that has failed to authenticate with
Keystone to access the API in read-only context.

* Configure through 'allow_anonymous_access' option, defaults to False
* Implements bp api-v2-anonymous-access

Change-Id: Ia8f57e54bd141a2da1ca4600d1970558fb497f67
This commit is contained in:
Brian Waldon 2012-05-23 08:05:16 -07:00 committed by Eoghan Glynn
parent 9a838a8b34
commit 2a01e6ef75
4 changed files with 101 additions and 45 deletions

View File

@ -56,6 +56,10 @@ workers = 0
# Role used to identify an authenticated user as administrator
#admin_role = admin
# Allow unauthenticated users to access the API with read-only
# privileges. This only applies when using ContextMiddleware.
#allow_anonymous_access = False
# ================= Syslog Options ============================
# Send logs to syslog (/dev/log) instead of to file specified

View File

@ -132,6 +132,7 @@ class ContextMiddleware(wsgi.Middleware):
opts = [
cfg.BoolOpt('owner_is_tenant', default=True),
cfg.StrOpt('admin_role', default='admin'),
cfg.BoolOpt('allow_anonymous_access', default=False),
]
def __init__(self, app, conf, **local_conf):
@ -140,7 +141,7 @@ class ContextMiddleware(wsgi.Middleware):
super(ContextMiddleware, self).__init__(app)
def process_request(self, req):
"""Convert authentication informtion into a request context
"""Convert authentication information into a request context
Generate a RequestContext object from the available
authentication headers and store on the 'context' attribute
@ -148,11 +149,27 @@ class ContextMiddleware(wsgi.Middleware):
:param req: wsgi request object that will be given the context object
:raises webob.exc.HTTPUnauthorized: when value of the X-Identity-Status
header is not 'Confirmed'
header is not 'Confirmed' and
anonymous access is disallowed
"""
if req.headers.get('X-Identity-Status') != 'Confirmed':
if req.headers.get('X-Identity-Status') == 'Confirmed':
req.context = self._get_authenticated_context(req)
elif self.conf.allow_anonymous_access:
req.context = self._get_anonymous_context()
else:
raise webob.exc.HTTPUnauthorized()
def _get_anonymous_context(self):
kwargs = {
'user': None,
'tenant': None,
'roles': [],
'is_admin': False,
'read_only': True,
}
return RequestContext(**kwargs)
def _get_authenticated_context(self, req):
#NOTE(bcwaldon): X-Roles is a csv string, but we need to parse
# it into a list to be useful
roles_header = req.headers.get('X-Roles', '')
@ -170,7 +187,7 @@ class ContextMiddleware(wsgi.Middleware):
'owner_is_tenant': self.conf.owner_is_tenant,
}
req.context = RequestContext(**kwargs)
return RequestContext(**kwargs)
class UnauthenticatedContextMiddleware(wsgi.Middleware):

View File

@ -0,0 +1,76 @@
import webob
from glance.common import context
from glance.tests.unit import base
class TestContextMiddleware(base.IsolatedUnitTest):
def _build_request(self, roles=None, identity_status='Confirmed'):
req = webob.Request.blank('/')
req.headers['x-auth-token'] = 'token1'
req.headers['x-identity-status'] = identity_status
req.headers['x-user-id'] = 'user1'
req.headers['x-tenant-id'] = 'tenant1'
_roles = roles or ['role1', 'role2']
req.headers['x-roles'] = ','.join(_roles)
return req
def _build_middleware(self, **extra_config):
for k, v in extra_config.items():
setattr(self.conf, k, v)
return context.ContextMiddleware(None, self.conf)
def test_header_parsing(self):
req = self._build_request()
self._build_middleware().process_request(req)
self.assertEqual(req.context.auth_tok, 'token1')
self.assertEqual(req.context.user, 'user1')
self.assertEqual(req.context.tenant, 'tenant1')
self.assertEqual(req.context.roles, ['role1', 'role2'])
def test_is_admin_flag(self):
# is_admin check should look for 'admin' role by default
req = self._build_request(roles=['admin', 'role2'])
self._build_middleware().process_request(req)
self.assertTrue(req.context.is_admin)
# without the 'admin' role, is_admin shoud be False
req = self._build_request()
self._build_middleware().process_request(req)
self.assertFalse(req.context.is_admin)
# if we change the admin_role attribute, we should be able to use it
req = self._build_request()
self._build_middleware(admin_role='role1').process_request(req)
self.assertTrue(req.context.is_admin)
def test_anonymous_access_enabled(self):
req = self._build_request(identity_status='Nope')
middleware = self._build_middleware(allow_anonymous_access=True)
middleware.process_request(req)
self.assertEqual(req.context.auth_tok, None)
self.assertEqual(req.context.user, None)
self.assertEqual(req.context.tenant, None)
self.assertEqual(req.context.roles, [])
self.assertFalse(req.context.is_admin)
self.assertTrue(req.context.read_only)
def test_anonymous_access_defaults_to_disabled(self):
req = self._build_request(identity_status='Nope')
middleware = self._build_middleware()
self.assertRaises(webob.exc.HTTPUnauthorized,
middleware.process_request, req)
class TestUnauthenticatedContextMiddleware(base.IsolatedUnitTest):
def test_request(self):
middleware = context.UnauthenticatedContextMiddleware(None, self.conf)
req = webob.Request.blank('/')
middleware.process_request(req)
self.assertEqual(req.context.auth_tok, None)
self.assertEqual(req.context.user, None)
self.assertEqual(req.context.tenant, None)
self.assertEqual(req.context.roles, [])
self.assertTrue(req.context.is_admin)

View File

@ -3110,44 +3110,3 @@ class TestImageSerializer(base.IsolatedUnitTest):
self.serializer.image_send_notification(17, 19, image_meta, req)
self.assertTrue(called['notified'])
class TestContextMiddleware(base.IsolatedUnitTest):
def _build_request(self, roles=None):
req = webob.Request.blank('/')
req.headers['x-auth-token'] = 'token1'
req.headers['x-identity-status'] = 'Confirmed'
req.headers['x-user-id'] = 'user1'
req.headers['x-tenant-id'] = 'tenant1'
_roles = roles or ['role1', 'role2']
req.headers['x-roles'] = ','.join(_roles)
return req
def _build_middleware(self, **extra_config):
for k, v in extra_config.items():
setattr(self.conf, k, v)
return context.ContextMiddleware(None, self.conf)
def test_header_parsing(self):
req = self._build_request()
self._build_middleware().process_request(req)
self.assertEqual(req.context.auth_tok, 'token1')
self.assertEqual(req.context.user, 'user1')
self.assertEqual(req.context.tenant, 'tenant1')
self.assertEqual(req.context.roles, ['role1', 'role2'])
def test_is_admin_flag(self):
# is_admin check should look for 'admin' role by default
req = self._build_request(roles=['admin', 'role2'])
self._build_middleware().process_request(req)
self.assertTrue(req.context.is_admin)
# without the 'admin' role, is_admin shoud be False
req = self._build_request()
self._build_middleware().process_request(req)
self.assertFalse(req.context.is_admin)
# if we change the admin_role attribute, we should be able to use it
req = self._build_request()
self._build_middleware(admin_role='role1').process_request(req)
self.assertTrue(req.context.is_admin)