From 2a01e6ef755692240b4ed14f9005296e0653a9a1 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 23 May 2012 08:05:16 -0700 Subject: [PATCH] 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 --- etc/glance-api.conf | 4 ++ glance/common/context.py | 25 +++++-- glance/tests/unit/test_context_middleware.py | 76 ++++++++++++++++++++ glance/tests/unit/v1/test_api.py | 41 ----------- 4 files changed, 101 insertions(+), 45 deletions(-) create mode 100644 glance/tests/unit/test_context_middleware.py diff --git a/etc/glance-api.conf b/etc/glance-api.conf index 5873827895..0b8635eb81 100644 --- a/etc/glance-api.conf +++ b/etc/glance-api.conf @@ -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 diff --git a/glance/common/context.py b/glance/common/context.py index be37ec8418..56daca013f 100644 --- a/glance/common/context.py +++ b/glance/common/context.py @@ -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): diff --git a/glance/tests/unit/test_context_middleware.py b/glance/tests/unit/test_context_middleware.py new file mode 100644 index 0000000000..17b9d2d2c2 --- /dev/null +++ b/glance/tests/unit/test_context_middleware.py @@ -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) diff --git a/glance/tests/unit/v1/test_api.py b/glance/tests/unit/v1/test_api.py index ef51fed659..33b4e8002a 100644 --- a/glance/tests/unit/v1/test_api.py +++ b/glance/tests/unit/v1/test_api.py @@ -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)