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:
parent
9a838a8b34
commit
2a01e6ef75
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue