diff -Nru swift-1.13.1/debian/changelog swift-1.13.1/debian/changelog --- swift-1.13.1/debian/changelog 2014-06-26 07:05:38.000000000 +0000 +++ swift-1.13.1/debian/changelog 2015-08-06 04:16:42.000000000 +0000 @@ -1,8 +1,25 @@ -swift (1.13.1-0ubuntu1.1~cloud0) precise-icehouse; urgency=medium +swift (1.13.1-0ubuntu1.2~cloud0) precise-icehouse; urgency=medium * New update for the Ubuntu Cloud Archive. - -- Openstack Ubuntu Testing Bot Thu, 26 Jun 2014 03:05:38 -0400 + -- Openstack Ubuntu Testing Bot Thu, 06 Aug 2015 04:16:42 +0000 + +swift (1.13.1-0ubuntu1.2) trusty-security; urgency=medium + + * SECURITY UPDATE: metadata constraint bypass via multiple requests + - debian/patches/CVE-2014-7960.patch: add metadata checks to + swift/account/server.py, swift/common/constraints.py, + swift/common/db.py, swift/container/server.py, added tests to + test/functional/test_account.py, test/functional/test_container.py, + test/unit/common/test_db.py. + - CVE-2014-7960 + * SECURITY UPDATE: object deletion via x-versions-location container + - debian/patches/CVE-2015-1856.patch: prevent unauthorized delete in + swift/proxy/controllers/obj.py, added tests to + test/functional/tests.py, test/unit/proxy/test_server.py. + - CVE-2015-1856 + + -- Marc Deslauriers Wed, 22 Jul 2015 11:03:05 -0400 swift (1.13.1-0ubuntu1.1) trusty-security; urgency=medium diff -Nru swift-1.13.1/debian/patches/CVE-2014-7960.patch swift-1.13.1/debian/patches/CVE-2014-7960.patch --- swift-1.13.1/debian/patches/CVE-2014-7960.patch 1970-01-01 00:00:00.000000000 +0000 +++ swift-1.13.1/debian/patches/CVE-2014-7960.patch 2015-07-22 15:02:51.000000000 +0000 @@ -0,0 +1,404 @@ +From 2c4622a28ea04e1c6b2382189b0a1f6cccdc9c0f Mon Sep 17 00:00:00 2001 +From: "Richard (Rick) Hawkins" +Date: Wed, 1 Oct 2014 09:37:47 -0400 +Subject: [PATCH] Fix metadata overall limits bug + +Currently metadata limits are checked on a per request basis. If +multiple requests are sent within the per request limits, it is +possible to exceed the overall limits. This patch adds an overall +metadata check to ensure that multiple requests to add metadata to +an account/container will check overall limits before adding +the additional metadata. + +This is a backport to the stable/icehouse branch for commit SHA +5b2c27a5874c2b5b0a333e4955b03544f6a8119f. + +Closes-Bug: 1365350 + +Conflicts: + swift/common/db.py + swift/container/server.py + +Change-Id: Id9fca209c9c1216f1949de7108bbe332808f1045 +--- + swift/account/server.py | 4 +- + swift/common/constraints.py | 5 ++- + swift/common/db.py | 34 ++++++++++++++- + swift/container/server.py | 4 +- + test/functional/test_account.py | 66 ++++++++++++++++++++++++++++ + test/functional/test_container.py | 20 +++++++++ + test/unit/common/test_db.py | 90 ++++++++++++++++++++++++++++++++++++++- + 7 files changed, 216 insertions(+), 7 deletions(-) + +diff --git a/swift/account/server.py b/swift/account/server.py +index 53889a1..9a5084d 100644 +--- a/swift/account/server.py ++++ b/swift/account/server.py +@@ -153,7 +153,7 @@ class AccountController(object): + for key, value in req.headers.iteritems() + if is_sys_or_user_meta('account', key)) + if metadata: +- broker.update_metadata(metadata) ++ broker.update_metadata(metadata, validate_metadata=True) + if created: + return HTTPCreated(request=req) + else: +@@ -259,7 +259,7 @@ class AccountController(object): + for key, value in req.headers.iteritems() + if is_sys_or_user_meta('account', key)) + if metadata: +- broker.update_metadata(metadata) ++ broker.update_metadata(metadata, validate_metadata=True) + return HTTPNoContent(request=req) + + def __call__(self, env, start_response): +diff --git a/swift/common/constraints.py b/swift/common/constraints.py +index 7a480ea..3495e72 100644 +--- a/swift/common/constraints.py ++++ b/swift/common/constraints.py +@@ -92,7 +92,10 @@ FORMAT2CONTENT_TYPE = {'plain': 'text/plain', 'json': 'application/json', + + def check_metadata(req, target_type): + """ +- Check metadata sent in the request headers. ++ Check metadata sent in the request headers. This should only check ++ that the metadata in the request given is valid. Checks against ++ account/container overall metadata should be forwarded on to its ++ respective server to be checked. + + :param req: request object + :param target_type: str: one of: object, container, or account: indicates +diff --git a/swift/common/db.py b/swift/common/db.py +index 192d17d..7abdbe3 100644 +--- a/swift/common/db.py ++++ b/swift/common/db.py +@@ -31,7 +31,9 @@ import sqlite3 + + from swift.common.utils import json, normalize_timestamp, renamer, \ + mkdirs, lock_parent_directory, fallocate ++from swift.common.constraints import MAX_META_COUNT, MAX_META_OVERALL_SIZE + from swift.common.exceptions import LockTimeout ++from swift.common.swob import HTTPBadRequest + + + #: Whether calls will be made to preallocate disk space for database files. +@@ -643,7 +645,35 @@ class DatabaseBroker(object): + metadata = {} + return metadata + +- def update_metadata(self, metadata_updates): ++ @staticmethod ++ def validate_metadata(metadata): ++ """ ++ Validates that metadata_falls within acceptable limits. ++ ++ :param metadata: to be validated ++ :raises: HTTPBadRequest if MAX_META_COUNT or MAX_META_OVERALL_SIZE ++ is exceeded ++ """ ++ meta_count = 0 ++ meta_size = 0 ++ for key, (value, timestamp) in metadata.iteritems(): ++ key = key.lower() ++ if value != '' and (key.startswith('x-account-meta') or ++ key.startswith('x-container-meta')): ++ prefix = 'x-account-meta-' ++ if key.startswith('x-container-meta-'): ++ prefix = 'x-container-meta-' ++ key = key[len(prefix):] ++ meta_count = meta_count + 1 ++ meta_size = meta_size + len(key) + len(value) ++ if meta_count > MAX_META_COUNT: ++ raise HTTPBadRequest('Too many metadata items; max %d' ++ % MAX_META_COUNT) ++ if meta_size > MAX_META_OVERALL_SIZE: ++ raise HTTPBadRequest('Total metadata too large; max %d' ++ % MAX_META_OVERALL_SIZE) ++ ++ def update_metadata(self, metadata_updates, validate_metadata=False): + """ + Updates the metadata dict for the database. The metadata dict values + are tuples of (value, timestamp) where the timestamp indicates when +@@ -676,6 +706,8 @@ class DatabaseBroker(object): + value, timestamp = value_timestamp + if key not in md or timestamp > md[key][1]: + md[key] = value_timestamp ++ if validate_metadata: ++ DatabaseBroker.validate_metadata(md) + conn.execute('UPDATE %s_stat SET metadata = ?' % self.db_type, + (json.dumps(md),)) + conn.commit() +diff --git a/swift/container/server.py b/swift/container/server.py +index ebf7295..ec41396 100644 +--- a/swift/container/server.py ++++ b/swift/container/server.py +@@ -286,7 +286,7 @@ class ContainerController(object): + metadata['X-Container-Sync-To'][0] != \ + broker.metadata['X-Container-Sync-To'][0]: + broker.set_x_container_sync_points(-1, -1) +- broker.update_metadata(metadata) ++ broker.update_metadata(metadata, validate_metadata=True) + resp = self.account_update(req, account, container, broker) + if resp: + return resp +@@ -473,7 +473,7 @@ class ContainerController(object): + metadata['X-Container-Sync-To'][0] != \ + broker.metadata['X-Container-Sync-To'][0]: + broker.set_x_container_sync_points(-1, -1) +- broker.update_metadata(metadata) ++ broker.update_metadata(metadata, validate_metadata=True) + return HTTPNoContent(request=req) + + def __call__(self, env, start_response): +diff --git a/test/functional/test_account.py b/test/functional/test_account.py +index 30cef31..1cc61bc 100755 +--- a/test/functional/test_account.py ++++ b/test/functional/test_account.py +@@ -32,6 +32,42 @@ from test.functional.tests import load_constraint + + class TestAccount(unittest.TestCase): + ++ def setUp(self): ++ self.max_meta_count = load_constraint('max_meta_count') ++ self.max_meta_name_length = load_constraint('max_meta_name_length') ++ self.max_meta_overall_size = load_constraint('max_meta_overall_size') ++ self.max_meta_value_length = load_constraint('max_meta_value_length') ++ ++ def head(url, token, parsed, conn): ++ conn.request('HEAD', parsed.path, '', {'X-Auth-Token': token}) ++ return check_response(conn) ++ resp = retry(head) ++ self.existing_metadata = set([ ++ k for k, v in resp.getheaders() if ++ k.lower().startswith('x-account-meta')]) ++ ++ def tearDown(self): ++ def head(url, token, parsed, conn): ++ conn.request('HEAD', parsed.path, '', {'X-Auth-Token': token}) ++ return check_response(conn) ++ resp = retry(head) ++ resp.read() ++ new_metadata = set( ++ [k for k, v in resp.getheaders() if ++ k.lower().startswith('x-account-meta')]) ++ ++ def clear_meta(url, token, parsed, conn, remove_metadata_keys): ++ headers = {'X-Auth-Token': token} ++ headers.update((k, '') for k in remove_metadata_keys) ++ conn.request('POST', parsed.path, '', headers) ++ return check_response(conn) ++ extra_metadata = list(self.existing_metadata ^ new_metadata) ++ for i in range(0, len(extra_metadata), 90): ++ batch = extra_metadata[i:i + 90] ++ resp = retry(clear_meta, batch) ++ resp.read() ++ self.assertEqual(resp.status // 100, 2) ++ + def test_metadata(self): + if skip: + raise SkipTest +@@ -733,6 +769,21 @@ class TestAccount(unittest.TestCase): + resp.read() + self.assertEqual(resp.status, 400) + ++ def test_bad_metadata2(self): ++ if skip: ++ raise SkipTest ++ ++ def post(url, token, parsed, conn, extra_headers): ++ headers = {'X-Auth-Token': token} ++ headers.update(extra_headers) ++ conn.request('POST', parsed.path, '', headers) ++ return check_response(conn) ++ ++ # TODO: Find the test that adds these and remove them. ++ headers = {'x-remove-account-meta-temp-url-key': 'remove', ++ 'x-remove-account-meta-temp-url-key-2': 'remove'} ++ resp = retry(post, headers) ++ + headers = {} + for x in xrange(MAX_META_COUNT): + headers['X-Account-Meta-%d' % x] = 'v' +@@ -746,6 +797,21 @@ class TestAccount(unittest.TestCase): + resp.read() + self.assertEqual(resp.status, 400) + ++ def test_bad_metadata3(self): ++ if skip: ++ raise SkipTest ++ ++ def post(url, token, parsed, conn, extra_headers): ++ headers = {'X-Auth-Token': token} ++ headers.update(extra_headers) ++ conn.request('POST', parsed.path, '', headers) ++ return check_response(conn) ++ ++ # TODO: Find the test that adds these and remove them. ++ headers = {'x-remove-account-meta-temp-url-key': 'remove', ++ 'x-remove-account-meta-temp-url-key-2': 'remove'} ++ resp = retry(post, headers) ++ + headers = {} + header_value = 'k' * MAX_META_VALUE_LENGTH + size = 0 +diff --git a/test/functional/test_container.py b/test/functional/test_container.py +index 7c0fd3e..91702e9 100755 +--- a/test/functional/test_container.py ++++ b/test/functional/test_container.py +@@ -382,6 +382,16 @@ class TestContainer(unittest.TestCase): + resp.read() + self.assertEqual(resp.status, 400) + ++ def test_POST_bad_metadata2(self): ++ if skip: ++ raise SkipTest ++ ++ def post(url, token, parsed, conn, extra_headers): ++ headers = {'X-Auth-Token': token} ++ headers.update(extra_headers) ++ conn.request('POST', parsed.path + '/' + self.name, '', headers) ++ return check_response(conn) ++ + headers = {} + for x in xrange(MAX_META_COUNT): + headers['X-Container-Meta-%d' % x] = 'v' +@@ -395,6 +405,16 @@ class TestContainer(unittest.TestCase): + resp.read() + self.assertEqual(resp.status, 400) + ++ def test_POST_bad_metadata3(self): ++ if skip: ++ raise SkipTest ++ ++ def post(url, token, parsed, conn, extra_headers): ++ headers = {'X-Auth-Token': token} ++ headers.update(extra_headers) ++ conn.request('POST', parsed.path + '/' + self.name, '', headers) ++ return check_response(conn) ++ + headers = {} + header_value = 'k' * MAX_META_VALUE_LENGTH + size = 0 +diff --git a/test/unit/common/test_db.py b/test/unit/common/test_db.py +index aa9ae11..d62a1d0 100644 +--- a/test/unit/common/test_db.py ++++ b/test/unit/common/test_db.py +@@ -28,11 +28,14 @@ from mock import patch, MagicMock + from eventlet.timeout import Timeout + + import swift.common.db ++from swift.common.constraints import \ ++ MAX_META_VALUE_LENGTH, MAX_META_COUNT, MAX_META_OVERALL_SIZE + from swift.common.db import chexor, dict_factory, get_db_connection, \ + DatabaseBroker, DatabaseConnectionError, DatabaseAlreadyExists, \ + GreenDBConnection + from swift.common.utils import normalize_timestamp, mkdirs + from swift.common.exceptions import LockTimeout ++from swift.common.swob import HTTPException + + + class TestDatabaseConnectionError(unittest.TestCase): +@@ -230,7 +233,7 @@ class TestDatabaseBroker(unittest.TestCase): + conn.execute('CREATE TABLE test (one TEXT)') + conn.execute('CREATE TABLE test_stat (id TEXT)') + conn.execute('INSERT INTO test_stat (id) VALUES (?)', +- (str(uuid4),)) ++ (str(uuid4),)) + conn.execute('INSERT INTO test (one) VALUES ("1")') + conn.commit() + stub_called = [False] +@@ -679,6 +682,91 @@ class TestDatabaseBroker(unittest.TestCase): + [first_value, first_timestamp]) + self.assert_('Second' not in broker.metadata) + ++ @patch.object(DatabaseBroker, 'validate_metadata') ++ def test_validate_metadata_is_called_from_update_metadata(self, mock): ++ broker = self.get_replication_info_tester(metadata=True) ++ first_timestamp = normalize_timestamp(1) ++ first_value = '1' ++ metadata = {'First': [first_value, first_timestamp]} ++ broker.update_metadata(metadata, validate_metadata=True) ++ self.assertTrue(mock.called) ++ ++ @patch.object(DatabaseBroker, 'validate_metadata') ++ def test_validate_metadata_is_not_called_from_update_metadata(self, mock): ++ broker = self.get_replication_info_tester(metadata=True) ++ first_timestamp = normalize_timestamp(1) ++ first_value = '1' ++ metadata = {'First': [first_value, first_timestamp]} ++ broker.update_metadata(metadata) ++ self.assertFalse(mock.called) ++ ++ def test_metadata_with_max_count(self): ++ metadata = {} ++ for c in xrange(MAX_META_COUNT): ++ key = 'X-Account-Meta-F{0}'.format(c) ++ metadata[key] = ('B', normalize_timestamp(1)) ++ key = 'X-Account-Meta-Foo'.format(c) ++ metadata[key] = ('', normalize_timestamp(1)) ++ try: ++ DatabaseBroker.validate_metadata(metadata) ++ except HTTPException: ++ self.fail('Unexpected HTTPException') ++ ++ def test_metadata_raises_exception_over_max_count(self): ++ metadata = {} ++ for c in xrange(MAX_META_COUNT + 1): ++ key = 'X-Account-Meta-F{0}'.format(c) ++ metadata[key] = ('B', normalize_timestamp(1)) ++ message = '' ++ try: ++ DatabaseBroker.validate_metadata(metadata) ++ except HTTPException as e: ++ message = str(e) ++ self.assertEqual(message, '400 Bad Request') ++ ++ def test_metadata_with_max_overall_size(self): ++ metadata = {} ++ metadata_value = 'v' * MAX_META_VALUE_LENGTH ++ size = 0 ++ x = 0 ++ while size < (MAX_META_OVERALL_SIZE - 4 ++ - MAX_META_VALUE_LENGTH): ++ size += 4 + MAX_META_VALUE_LENGTH ++ metadata['X-Account-Meta-%04d' % x] = (metadata_value, ++ normalize_timestamp(1)) ++ x += 1 ++ if MAX_META_OVERALL_SIZE - size > 1: ++ metadata['X-Account-Meta-k'] = ( ++ 'v' * (MAX_META_OVERALL_SIZE - size - 1), ++ normalize_timestamp(1)) ++ try: ++ DatabaseBroker.validate_metadata(metadata) ++ except HTTPException: ++ self.fail('Unexpected HTTPException') ++ ++ def test_metadata_raises_exception_over_max_overall_size(self): ++ metadata = {} ++ metadata_value = 'k' * MAX_META_VALUE_LENGTH ++ size = 0 ++ x = 0 ++ while size < (MAX_META_OVERALL_SIZE - 4 ++ - MAX_META_VALUE_LENGTH): ++ size += 4 + MAX_META_VALUE_LENGTH ++ metadata['X-Account-Meta-%04d' % x] = (metadata_value, ++ normalize_timestamp(1)) ++ x += 1 ++ if MAX_META_OVERALL_SIZE - size > 1: ++ metadata['X-Account-Meta-k'] = ( ++ 'v' * (MAX_META_OVERALL_SIZE - size - 1), ++ normalize_timestamp(1)) ++ metadata['X-Account-Meta-k2'] = ('v', normalize_timestamp(1)) ++ message = '' ++ try: ++ DatabaseBroker.validate_metadata(metadata) ++ except HTTPException as e: ++ message = str(e) ++ self.assertEqual(message, '400 Bad Request') ++ + + if __name__ == '__main__': + unittest.main() +-- +1.9.1 + diff -Nru swift-1.13.1/debian/patches/CVE-2015-1856.patch swift-1.13.1/debian/patches/CVE-2015-1856.patch --- swift-1.13.1/debian/patches/CVE-2015-1856.patch 1970-01-01 00:00:00.000000000 +0000 +++ swift-1.13.1/debian/patches/CVE-2015-1856.patch 2015-07-22 15:02:58.000000000 +0000 @@ -0,0 +1,211 @@ +From f6525758ab2456d688430699338993439597a789 Mon Sep 17 00:00:00 2001 +From: Alistair Coles +Date: Fri, 3 Apr 2015 18:46:20 +0100 +Subject: [PATCH] Prevent unauthorized delete in versioned container + +An authenticated user can delete the most recent version of any +versioned object who's name is known if the user has listing access +to the x-versions-location container. Only Swift setups with +allow_version setting are affected. + +This patch closes this bug, tracked as CVE-2015-1856. + +Co-Authored-By: Clay Gerrard +Co-Authored-By: Christian Schwede +Co-Authored-By: Alistair Coles + +Closes-Bug: 1430645 + +Change-Id: Icde494a9a2c851034813cbc3855a20335b643f09 +--- + swift/proxy/controllers/obj.py | 12 ++++++--- + test/functional/tests.py | 45 ++++++++++++++++++++++++++++++++ + test/unit/proxy/test_server.py | 58 +++++++++++++++++++++++++++++++++++++++++- + 3 files changed, 110 insertions(+), 5 deletions(-) + +diff --git a/swift/proxy/controllers/obj.py b/swift/proxy/controllers/obj.py +index 3b65f93..74b984b 100644 +--- a/swift/proxy/controllers/obj.py ++++ b/swift/proxy/controllers/obj.py +@@ -762,6 +762,10 @@ class ObjectController(Controller): + req.acl = container_info['write_acl'] + req.environ['swift_sync_key'] = container_info['sync_key'] + object_versions = container_info['versions'] ++ if 'swift.authorize' in req.environ: ++ aresp = req.environ['swift.authorize'](req) ++ if aresp: ++ return aresp + if object_versions: + # this is a version manifest and needs to be handled differently + object_versions = unquote(object_versions) +@@ -818,10 +822,10 @@ class ObjectController(Controller): + # remove 'X-If-Delete-At', since it is not for the older copy + if 'X-If-Delete-At' in req.headers: + del req.headers['X-If-Delete-At'] +- if 'swift.authorize' in req.environ: +- aresp = req.environ['swift.authorize'](req) +- if aresp: +- return aresp ++ if 'swift.authorize' in req.environ: ++ aresp = req.environ['swift.authorize'](req) ++ if aresp: ++ return aresp + if not containers: + return HTTPNotFound(request=req) + partition, nodes = self.app.object_ring.get_nodes( +diff --git a/test/functional/tests.py b/test/functional/tests.py +index 7983815..d043bf7 100644 +--- a/test/functional/tests.py ++++ b/test/functional/tests.py +@@ -26,6 +26,7 @@ import threading + import unittest + import urllib + import uuid ++from copy import deepcopy + from nose import SkipTest + + from test import get_config +@@ -2102,6 +2103,14 @@ class TestObjectVersioningEnv(object): + cls.account = Account(cls.conn, config.get('account', + config['username'])) + ++ # Second connection for ACL tests ++ config2 = deepcopy(config) ++ config2['account'] = config['account2'] ++ config2['username'] = config['username2'] ++ config2['password'] = config['password2'] ++ cls.conn2 = Connection(config2) ++ cls.conn2.authenticate() ++ + # avoid getting a prefix that stops halfway through an encoded + # character + prefix = Utils.create_name().decode("utf-8")[:10].encode("utf-8") +@@ -2134,6 +2143,15 @@ class TestObjectVersioning(Base): + "Expected versioning_enabled to be True/False, got %r" % + (self.env.versioning_enabled,)) + ++ def tearDown(self): ++ super(TestObjectVersioning, self).tearDown() ++ try: ++ # delete versions first! ++ self.env.versions_container.delete_files() ++ self.env.container.delete_files() ++ except ResponseError: ++ pass ++ + def test_overwriting(self): + container = self.env.container + versions_container = self.env.versions_container +@@ -2165,6 +2183,33 @@ class TestObjectVersioning(Base): + versioned_obj.delete() + self.assertRaises(ResponseError, versioned_obj.read) + ++ def test_versioning_check_acl(self): ++ container = self.env.container ++ versions_container = self.env.versions_container ++ versions_container.create(hdrs={'X-Container-Read': '.r:*,.rlistings'}) ++ ++ obj_name = Utils.create_name() ++ versioned_obj = container.file(obj_name) ++ versioned_obj.write("aaaaa") ++ self.assertEqual("aaaaa", versioned_obj.read()) ++ ++ versioned_obj.write("bbbbb") ++ self.assertEqual("bbbbb", versioned_obj.read()) ++ ++ # Use token from second account and try to delete the object ++ org_token = self.env.account.conn.storage_token ++ self.env.account.conn.storage_token = self.env.conn2.storage_token ++ try: ++ self.assertRaises(ResponseError, versioned_obj.delete) ++ finally: ++ self.env.account.conn.storage_token = org_token ++ ++ # Verify with token from first account ++ self.assertEqual("bbbbb", versioned_obj.read()) ++ ++ versioned_obj.delete() ++ self.assertEqual("aaaaa", versioned_obj.read()) ++ + + class TestObjectVersioningUTF8(Base2, TestObjectVersioning): + set_up = False +diff --git a/test/unit/proxy/test_server.py b/test/unit/proxy/test_server.py +index 56adc70..6983e2d 100644 +--- a/test/unit/proxy/test_server.py ++++ b/test/unit/proxy/test_server.py +@@ -56,7 +56,7 @@ from swift.proxy.controllers.base import get_container_memcache_key, \ + import swift.proxy.controllers + from swift.common.request_helpers import get_sys_meta_prefix + from swift.common.swob import Request, Response, HTTPUnauthorized, \ +- HTTPException ++ HTTPException, HTTPForbidden + + # mocks + logging.getLogger().addHandler(logging.StreamHandler(sys.stdout)) +@@ -1111,6 +1111,62 @@ class TestObjectController(unittest.TestCase): + controller.DELETE(req) + self.assertEquals(test_errors, []) + ++ def test_denied_DELETE_of_versioned_object(self): ++ """ ++ Verify that a request with read access to a versions container ++ is unable to cause any write operations on the versioned container. ++ """ ++ methods = set() ++ authorize_call_count = [0] ++ ++ def test_connect(ipaddr, port, device, partition, method, path, ++ headers=None, query_string=None): ++ methods.add((method, path)) ++ ++ def fake_container_info(account, container, req): ++ return {'status': 200, 'sync_key': None, ++ 'meta': {}, 'cors': {'allow_origin': None, ++ 'expose_headers': None, ++ 'max_age': None}, ++ 'sysmeta': {}, 'read_acl': None, 'object_count': None, ++ 'write_acl': None, 'versions': 'foo', ++ 'partition': 1, 'bytes': None, 'storage_policy': '1', ++ 'nodes': [{'zone': 0, 'ip': '10.0.0.0', 'region': 0, ++ 'id': 0, 'device': 'sda', 'port': 1000}, ++ {'zone': 1, 'ip': '10.0.0.1', 'region': 1, ++ 'id': 1, 'device': 'sdb', 'port': 1001}, ++ {'zone': 2, 'ip': '10.0.0.2', 'region': 0, ++ 'id': 2, 'device': 'sdc', 'port': 1002}]} ++ ++ def fake_list_iter(container, prefix, env): ++ object_list = [{'name': '1'}, {'name': '2'}, {'name': '3'}] ++ for obj in object_list: ++ yield obj ++ ++ def fake_authorize(req): ++ # deny write access ++ authorize_call_count[0] += 1 ++ return HTTPForbidden(req) # allow the request ++ ++ with save_globals(): ++ controller = proxy_server.ObjectController(self.app, ++ 'a', 'c', 'o') ++ controller.container_info = fake_container_info ++ # patching _listing_iter simulates request being authorized ++ # to list versions container ++ controller._listing_iter = fake_list_iter ++ set_http_connect(give_connect=test_connect) ++ req = Request.blank('/v1/a/c/o', ++ environ={'REQUEST_METHOD': 'DELETE', ++ 'swift.authorize': fake_authorize}) ++ ++ self.app.memcache.store = {} ++ self.app.update_request(req) ++ resp = controller.DELETE(req) ++ self.assertEqual(403, resp.status_int) ++ self.assertFalse(methods, methods) ++ self.assertEquals(authorize_call_count[0], 1) ++ + def test_PUT_auto_content_type(self): + with save_globals(): + controller = proxy_server.ObjectController(self.app, 'account', +-- +1.9.1 + diff -Nru swift-1.13.1/debian/patches/series swift-1.13.1/debian/patches/series --- swift-1.13.1/debian/patches/series 2014-06-24 12:07:47.000000000 +0000 +++ swift-1.13.1/debian/patches/series 2015-07-22 15:02:58.000000000 +0000 @@ -2,3 +2,5 @@ fix-ubuntu-tests.patch ring-perms.patch CVE-2014-3497.patch +CVE-2014-7960.patch +CVE-2015-1856.patch