From 946984cb84414c84fe38d4b94b73174960e023b8 Mon Sep 17 00:00:00 2001 From: Fabien Boucher Date: Tue, 10 Dec 2013 18:31:55 +0100 Subject: [PATCH] Handle COPY verb in container quota middleware The patch fix the ability to bypass the account quota using the COPY verb. The quotas for destination container are now checked before the copy. Change-Id: I9f409ea6c0893f36972aa1fabe31fb143e151ab5 Fixes: bug #1259565 --- swift/common/middleware/container_quotas.py | 25 +++++++--- test/unit/common/middleware/test_quotas.py | 54 +++++++++++++++++++++ 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/swift/common/middleware/container_quotas.py b/swift/common/middleware/container_quotas.py index 35a9b98eb7..3edaf1fb2a 100644 --- a/swift/common/middleware/container_quotas.py +++ b/swift/common/middleware/container_quotas.py @@ -79,9 +79,21 @@ class ContainerQuotaMiddleware(object): return HTTPBadRequest(body='Invalid count quota.') # check user uploads against quotas - elif obj and req.method == 'PUT': - container_info = get_container_info( - req.environ, self.app, swift_source='CQ') + elif obj and req.method in ('PUT', 'COPY'): + container_info = None + if req.method == 'PUT': + container_info = get_container_info( + req.environ, self.app, swift_source='CQ') + if req.method == 'COPY' and 'Destination' in req.headers: + dest = req.headers.get('Destination').lstrip('/') + path_info = req.environ['PATH_INFO'] + req.environ['PATH_INFO'] = "/%s/%s/%s" % ( + version, account, dest) + try: + container_info = get_container_info( + req.environ, self.app, swift_source='CQ') + finally: + req.environ['PATH_INFO'] = path_info if not container_info or not is_success(container_info['status']): # this will hopefully 404 later return self.app @@ -90,10 +102,11 @@ class ContainerQuotaMiddleware(object): 'bytes' in container_info and \ container_info['meta']['quota-bytes'].isdigit(): content_length = (req.content_length or 0) - if 'x-copy-from' in req.headers: - src_cont, src_obj = check_copy_from_header(req) + if 'x-copy-from' in req.headers or req.method == 'COPY': + if 'x-copy-from' in req.headers: + container, obj = check_copy_from_header(req) path = '/%s/%s/%s/%s' % (version, account, - src_cont, src_obj) + container, obj) object_info = get_object_info(req.environ, self.app, path) if not object_info or not object_info['length']: content_length = 0 diff --git a/test/unit/common/middleware/test_quotas.py b/test/unit/common/middleware/test_quotas.py index 1afcbf78c9..da060e1229 100644 --- a/test/unit/common/middleware/test_quotas.py +++ b/test/unit/common/middleware/test_quotas.py @@ -106,6 +106,18 @@ class TestContainerQuotas(unittest.TestCase): res = req.get_response(app) self.assertEquals(res.status_int, 413) + def test_exceed_bytes_quota_copy_verb(self): + app = container_quotas.ContainerQuotaMiddleware(FakeApp(), {}) + cache = FakeCache({'bytes': 0, 'meta': {'quota-bytes': '2'}}) + + req = Request.blank('/v1/a/c2/o2', + environ={'REQUEST_METHOD': 'COPY', + 'swift.object/a/c2/o2': {'length': 10}, + 'swift.cache': cache}, + headers={'Destination': '/c/o'}) + res = req.get_response(app) + self.assertEquals(res.status_int, 413) + def test_not_exceed_bytes_quota(self): app = container_quotas.ContainerQuotaMiddleware(FakeApp(), {}) cache = FakeCache({'bytes': 0, 'meta': {'quota-bytes': '100'}}) @@ -127,6 +139,17 @@ class TestContainerQuotas(unittest.TestCase): res = req.get_response(app) self.assertEquals(res.status_int, 200) + def test_not_exceed_bytes_quota_copy_verb(self): + app = container_quotas.ContainerQuotaMiddleware(FakeApp(), {}) + cache = FakeCache({'bytes': 0, 'meta': {'quota-bytes': '100'}}) + req = Request.blank('/v1/a/c2/o2', + environ={'REQUEST_METHOD': 'COPY', + 'swift.object/a/c2/o2': {'length': 10}, + 'swift.cache': cache}, + headers={'Destination': '/c/o'}) + res = req.get_response(app) + self.assertEquals(res.status_int, 200) + def test_bytes_quota_copy_from_no_src(self): app = container_quotas.ContainerQuotaMiddleware(FakeApp(), {}) cache = FakeCache({'bytes': 0, 'meta': {'quota-bytes': '100'}}) @@ -148,6 +171,17 @@ class TestContainerQuotas(unittest.TestCase): res = req.get_response(app) self.assertEquals(res.status_int, 412) + def test_bytes_quota_copy_verb_no_src(self): + app = container_quotas.ContainerQuotaMiddleware(FakeApp(), {}) + cache = FakeCache({'bytes': 0, 'meta': {'quota-bytes': '100'}}) + req = Request.blank('/v1/a/c2/o3', + environ={'REQUEST_METHOD': 'COPY', + 'swift.object/a/c2/o2': {'length': 10}, + 'swift.cache': cache}, + headers={'Destination': '/c/o'}) + res = req.get_response(app) + self.assertEquals(res.status_int, 200) + def test_exceed_counts_quota(self): app = container_quotas.ContainerQuotaMiddleware(FakeApp(), {}) cache = FakeCache({'object_count': 1, 'meta': {'quota-count': '1'}}) @@ -169,6 +203,16 @@ class TestContainerQuotas(unittest.TestCase): res = req.get_response(app) self.assertEquals(res.status_int, 413) + def test_exceed_counts_quota_copy_verb(self): + app = container_quotas.ContainerQuotaMiddleware(FakeApp(), {}) + cache = FakeCache({'object_count': 1, 'meta': {'quota-count': '1'}}) + req = Request.blank('/v1/a/c2/o2', + environ={'REQUEST_METHOD': 'COPY', + 'swift.cache': cache}, + headers={'Destination': '/c/o'}) + res = req.get_response(app) + self.assertEquals(res.status_int, 413) + def test_not_exceed_counts_quota(self): app = container_quotas.ContainerQuotaMiddleware(FakeApp(), {}) cache = FakeCache({'object_count': 1, 'meta': {'quota-count': '2'}}) @@ -189,6 +233,16 @@ class TestContainerQuotas(unittest.TestCase): res = req.get_response(app) self.assertEquals(res.status_int, 200) + def test_not_exceed_counts_quota_copy_verb(self): + app = container_quotas.ContainerQuotaMiddleware(FakeApp(), {}) + cache = FakeCache({'object_count': 1, 'meta': {'quota-count': '2'}}) + req = Request.blank('/v1/a/c2/o2', + environ={'REQUEST_METHOD': 'COPY', + 'swift.cache': cache}, + headers={'Destination': '/c/o'}) + res = req.get_response(app) + self.assertEquals(res.status_int, 200) + def test_invalid_quotas(self): req = Request.blank( '/v1/a/c',