diff --git a/glance/api/middleware/cache.py b/glance/api/middleware/cache.py index 82e3c0c..71f79b1 100644 --- a/glance/api/middleware/cache.py +++ b/glance/api/middleware/cache.py @@ -28,6 +28,7 @@ import re import webob +from glance.api import policy from glance.api.common import size_checked_iter from glance.api.v1 import images from glance.common import exception @@ -54,9 +55,17 @@ class CacheFilter(wsgi.Middleware): def __init__(self, app): self.cache = image_cache.ImageCache() self.serializer = images.ImageSerializer() + self.policy = policy.Enforcer() LOG.info(_("Initialized image cache middleware")) super(CacheFilter, self).__init__(app) + def _enforce(self, req, action): + """Authorize an action against our policies""" + try: + self.policy.enforce(req.context, action, {}) + except exception.Forbidden: + raise webob.exc.HTTPForbidden() + def _verify_metadata(self, image_meta): """ Sanity check the 'deleted' and 'size' metadata values. @@ -110,12 +119,10 @@ class CacheFilter(wsgi.Middleware): if request.method != 'GET' or not self.cache.is_cached(image_id): return None - LOG.debug(_("Cache hit for image '%s'"), image_id) - image_iterator = self.get_from_cache(image_id) method = getattr(self, '_process_%s_request' % version) try: - return method(request, image_id, image_iterator) + return method(request, image_id) except exception.NotFound: msg = _("Image cache contained image file for image '%s', " "however the registry did not contain metadata for " @@ -145,7 +152,11 @@ class CacheFilter(wsgi.Middleware): else: return (image_id, method) - def _process_v1_request(self, request, image_id, image_iterator): + def _process_v1_request(self, request, image_id): + self._enforce(request, 'download_image') + LOG.debug(_("Cache hit for image '%s'"), image_id) + + image_iterator = self.get_from_cache(image_id) image_meta = registry.get_image_metadata(request.context, image_id) # Don't display location if 'location' in image_meta: @@ -160,12 +171,15 @@ class CacheFilter(wsgi.Middleware): } return self.serializer.show(response, raw_response) - def _process_v2_request(self, request, image_id, image_iterator): + def _process_v2_request(self, request, image_id): # We do some contortions to get the image_metadata so # that we can provide it to 'size_checked_iter' which # will generate a notification. # TODO(mclaren): Make notification happen more # naturally once caching is part of the domain model. + LOG.debug(_("Cache hit for image '%s'"), image_id) + image_iterator = self.get_from_cache(image_id) + db_api = glance.db.get_api() image_repo = glance.db.ImageRepo(request.context, db_api) image = image_repo.get(image_id) diff --git a/glance/tests/unit/test_cache_middleware.py b/glance/tests/unit/test_cache_middleware.py index 0c15a50..caa9179 100644 --- a/glance/tests/unit/test_cache_middleware.py +++ b/glance/tests/unit/test_cache_middleware.py @@ -17,12 +17,14 @@ import stubout import testtools import webob +from glance.api import policy import glance.api.middleware.cache from glance.common import exception from glance import context import glance.db.sqlalchemy.api as db import glance.registry.client.v1.api as registry from glance.tests import utils +from glance.tests.unit import base class TestCacheMiddlewareURLMatching(testtools.TestCase): @@ -125,6 +127,7 @@ class FakeImageSerializer(object): class ProcessRequestTestCacheFilter(glance.api.middleware.cache.CacheFilter): def __init__(self): self.serializer = FakeImageSerializer() + self.policy = policy.Enforcer() class DummyCache(object): def __init__(self): @@ -145,6 +148,31 @@ class ProcessRequestTestCacheFilter(glance.api.middleware.cache.CacheFilter): self.cache = DummyCache() +class TestCacheMiddlewarePolicyEnforcer(base.IsolatedUnitTest): + def test_v1_process_request_image_download_policy_forbidden(self): + """ + Test for determining that the 'image_download' policy is + enforced in the cache layer + """ + def fake_get_image_metadata(context, image_id): + return {'is_public': True, 'deleted': False, 'size': '20'} + + def dummy_img_iterator(): + for i in range(3): + yield i + + rules = {"download_image": '!'} + self.set_policy_rules(rules) + image_id = 'test1' + request = webob.Request.blank('/v1/images/%s' % image_id) + request.context = context.RequestContext() + cache_filter = ProcessRequestTestCacheFilter() + self.stubs.Set(registry, 'get_image_metadata', + fake_get_image_metadata) + self.assertRaises(webob.exc.HTTPForbidden, + cache_filter._process_v1_request, request, image_id) + + class TestCacheMiddlewareProcessRequest(utils.BaseTestCase): def setUp(self): super(TestCacheMiddlewareProcessRequest, self).setUp() @@ -170,18 +198,19 @@ class TestCacheMiddlewareProcessRequest(utils.BaseTestCase): self.stubs.Set(registry, 'get_image_metadata', fake_get_image_metadata) self.assertRaises(exception.NotFound, cache_filter._process_v1_request, - request, image_id, dummy_img_iterator) + request, image_id) def test_process_v1_request_for_deleted_but_cached_image(self): """ Test for determining image is deleted from cache when it is not found in Glance Registry. """ - def fake_process_v1_request(request, image_id, image_iterator): + def fake_process_v1_request(request, image_id): raise exception.NotFound() image_id = 'test1' request = webob.Request.blank('/v1/images/%s' % image_id) + request.context = context.RequestContext() cache_filter = ProcessRequestTestCacheFilter() self.stubs.Set(cache_filter, '_process_v1_request', @@ -205,7 +234,7 @@ class TestCacheMiddlewareProcessRequest(utils.BaseTestCase): self.stubs.Set(registry, 'get_image_metadata', fake_get_image_metadata) actual = cache_filter._process_v1_request( - request, image_id, dummy_img_iterator) + request, image_id) self.assertEqual(True, actual) def test_v1_remove_location_image_fetch(self): @@ -231,7 +260,7 @@ class TestCacheMiddlewareProcessRequest(utils.BaseTestCase): self.stubs.Set(registry, 'get_image_metadata', fake_get_image_metadata) actual = cache_filter._process_v1_request( - request, image_id, dummy_img_iterator) + request, image_id) self.assertFalse(actual) def test_verify_metadata_deleted_image(self): @@ -299,7 +328,7 @@ class TestCacheMiddlewareProcessRequest(utils.BaseTestCase): cache_filter = ProcessRequestTestCacheFilter() response = cache_filter._process_v2_request( - request, image_id, dummy_img_iterator) + request, image_id) self.assertEqual(response.headers['Content-Type'], 'application/octet-stream') self.assertEqual(response.headers['Content-MD5'],