diff --git a/swift/common/middleware/tempurl.py b/swift/common/middleware/tempurl.py index cf3afe3..94c1445 100644 --- a/swift/common/middleware/tempurl.py +++ b/swift/common/middleware/tempurl.py @@ -122,11 +122,13 @@ from urllib import urlencode from urlparse import parse_qs from swift.proxy.controllers.base import get_account_info, get_container_info -from swift.common.swob import HeaderKeyDict, HTTPUnauthorized +from swift.common.swob import HeaderKeyDict, HTTPUnauthorized, HTTPBadRequest from swift.common.utils import split_path, get_valid_utf8_str, \ register_swift_info, get_hmac, streq_const_time, quote +DISALLOWED_INCOMING_HEADERS = 'x-object-manifest' + #: Default headers to remove from incoming requests. Simply a whitespace #: delimited list of header names and names can optionally end with '*' to #: indicate a prefix match. DEFAULT_INCOMING_ALLOW_HEADERS is a list of @@ -150,6 +152,10 @@ DEFAULT_OUTGOING_REMOVE_HEADERS = 'x-object-meta-*' DEFAULT_OUTGOING_ALLOW_HEADERS = 'x-object-meta-public-*' +CONTAINER_SCOPE = 'container' +ACCOUNT_SCOPE = 'account' + + def get_tempurl_keys_from_metadata(meta): """ Extracts the tempurl keys from metadata. @@ -170,6 +176,38 @@ def disposition_format(filename): quote(filename, safe=' /'), quote(filename)) +def authorize_same_account(account_to_match): + + def auth_callback_same_account(req): + try: + _ver, acc, _rest = req.split_path(2, 3, True) + except ValueError: + return None + + if acc == account_to_match: + return None + else: + return HTTPUnauthorized(request=req) + + return auth_callback_same_account + + +def authorize_same_container(account_to_match, container_to_match): + + def auth_callback_same_container(req): + try: + _ver, acc, con, _rest = req.split_path(3, 4, True) + except ValueError: + return None + + if acc == account_to_match and con == container_to_match: + return None + else: + return HTTPUnauthorized(request=req) + + return auth_callback_same_container + + class TempURL(object): """ WSGI Middleware to grant temporary URLs specific access to Swift @@ -230,6 +268,10 @@ class TempURL(object): #: The methods allowed with Temp URLs. self.methods = methods + self.disallowed_headers = set( + 'HTTP_' + h.upper().replace('-', '_') + for h in DISALLOWED_INCOMING_HEADERS.split()) + headers = DEFAULT_INCOMING_REMOVE_HEADERS if 'incoming_remove_headers' in conf: headers = conf['incoming_remove_headers'] @@ -298,10 +340,10 @@ class TempURL(object): return self.app(env, start_response) if not temp_url_sig or not temp_url_expires: return self._invalid(env, start_response) - account = self._get_account(env) + account, container = self._get_account_and_container(env) if not account: return self._invalid(env, start_response) - keys = self._get_keys(env, account) + keys = self._get_keys(env) if not keys: return self._invalid(env, start_response) if env['REQUEST_METHOD'] == 'HEAD': @@ -316,15 +358,32 @@ class TempURL(object): else: hmac_vals = self._get_hmacs(env, temp_url_expires, keys) - # While it's true that any() will short-circuit, this doesn't affect - # the timing-attack resistance since the only way this will - # short-circuit is when a valid signature is passed in. - is_valid_hmac = any(streq_const_time(temp_url_sig, hmac) - for hmac in hmac_vals) + is_valid_hmac = False + hmac_scope = None + for hmac, scope in hmac_vals: + # While it's true that we short-circuit, this doesn't affect the + # timing-attack resistance since the only way this will + # short-circuit is when a valid signature is passed in. + if streq_const_time(temp_url_sig, hmac): + is_valid_hmac = True + hmac_scope = scope + break if not is_valid_hmac: return self._invalid(env, start_response) + # disallowed headers prevent accidently allowing upload of a pointer + # to data that the PUT tempurl would not otherwise allow access for. + # It should be safe to provide a GET tempurl for data that an + # untrusted client just uploaded with a PUT tempurl. + resp = self._clean_disallowed_headers(env, start_response) + if resp: + return resp self._clean_incoming_headers(env) - env['swift.authorize'] = lambda req: None + + if hmac_scope == ACCOUNT_SCOPE: + env['swift.authorize'] = authorize_same_account(account) + else: + env['swift.authorize'] = authorize_same_container(account, + container) env['swift.authorize_override'] = True env['REMOTE_USER'] = '.wsgi.tempurl' qs = {'temp_url_sig': temp_url_sig, @@ -365,22 +424,23 @@ class TempURL(object): return self.app(env, _start_response) - def _get_account(self, env): + def _get_account_and_container(self, env): """ - Returns just the account for the request, if it's an object - request and one of the configured methods; otherwise, None is + Returns just the account and container for the request, if it's an + object request and one of the configured methods; otherwise, None is returned. :param env: The WSGI environment for the request. - :returns: Account str or None. + :returns: (Account str, container str) or (None, None). """ if env['REQUEST_METHOD'] in self.methods: try: ver, acc, cont, obj = split_path(env['PATH_INFO'], 4, 4, True) except ValueError: - return None + return (None, None) if ver == 'v1' and obj.strip('/'): - return acc + return (acc, cont) + return (None, None) def _get_temp_url_info(self, env): """ @@ -410,18 +470,23 @@ class TempURL(object): inline = True return temp_url_sig, temp_url_expires, filename, inline - def _get_keys(self, env, account): + def _get_keys(self, env): """ Returns the X-[Account|Container]-Meta-Temp-URL-Key[-2] header values - for the account or container, or an empty list if none are set. + for the account or container, or an empty list if none are set. Each + value comes as a 2-tuple (key, scope), where scope is either + CONTAINER_SCOPE or ACCOUNT_SCOPE. Returns 0-4 elements depending on how many keys are set in the account's or container's metadata. :param env: The WSGI environment for the request. - :param account: Account str. - :returns: [X-Account-Meta-Temp-URL-Key str value if set, - X-Account-Meta-Temp-URL-Key-2 str value if set] + :returns: [ + (X-Account-Meta-Temp-URL-Key str value, ACCOUNT_SCOPE) if set, + (X-Account-Meta-Temp-URL-Key-2 str value, ACCOUNT_SCOPE if set, + (X-Container-Meta-Temp-URL-Key str value, CONTAINER_SCOPE) if set, + (X-Container-Meta-Temp-URL-Key-2 str value, CONTAINER_SCOPE if set, + ] """ account_info = get_account_info(env, self.app, swift_source='TU') account_keys = get_tempurl_keys_from_metadata(account_info['meta']) @@ -430,25 +495,28 @@ class TempURL(object): container_keys = get_tempurl_keys_from_metadata( container_info.get('meta', [])) - return account_keys + container_keys + return ([(ak, ACCOUNT_SCOPE) for ak in account_keys] + + [(ck, CONTAINER_SCOPE) for ck in container_keys]) - def _get_hmacs(self, env, expires, keys, request_method=None): + def _get_hmacs(self, env, expires, scoped_keys, request_method=None): """ :param env: The WSGI environment for the request. :param expires: Unix timestamp as an int for when the URL expires. - :param keys: Key strings, from the X-Account-Meta-Temp-URL-Key[-2] of - the account. + :param scoped_keys: (key, scope) tuples like _get_keys() returns :param request_method: Optional override of the request in the WSGI env. For example, if a HEAD does not match, you may wish to override with GET to still allow the HEAD. + + :returns: a list of (hmac, scope) 2-tuples """ if not request_method: request_method = env['REQUEST_METHOD'] - return [get_hmac( - request_method, env['PATH_INFO'], expires, key) for key in keys] + return [ + (get_hmac(request_method, env['PATH_INFO'], expires, key), scope) + for (key, scope) in scoped_keys] def _invalid(self, env, start_response): """ @@ -465,6 +533,22 @@ class TempURL(object): body = '401 Unauthorized: Temp URL invalid\n' return HTTPUnauthorized(body=body)(env, start_response) + def _clean_disallowed_headers(self, env, start_response): + """ + Validate the absense of disallowed headers for "unsafe" operations. + + :returns: None for safe operations or swob.HTTPBadResponse if the + request includes disallowed headers. + """ + if env['REQUEST_METHOD'] in ('GET', 'HEAD', 'OPTIONS'): + return + for h in env: + if h in self.disallowed_headers: + return HTTPBadRequest( + body='The header %r is not allowed in this tempurl' % + h[len('HTTP_'):].title().replace('_', '-'))( + env, start_response) + def _clean_incoming_headers(self, env): """ Removes any headers from the WSGI environment as per the diff --git a/swift/proxy/controllers/base.py b/swift/proxy/controllers/base.py index 554469c..5ba370d 100644 --- a/swift/proxy/controllers/base.py +++ b/swift/proxy/controllers/base.py @@ -378,10 +378,8 @@ def _set_info_cache(app, env, account, container, resp): :param resp: the response received or None if info cache should be cleared """ - if container: - cache_time = app.recheck_container_existence - else: - cache_time = app.recheck_account_existence + cache_time = getattr(app, 'get_cache_time', lambda *args: 60)( + account, container) cache_key, env_key = _get_cache_key(account, container) if resp: @@ -528,6 +526,8 @@ def get_info(app, env, account, container=None, ret_not_found=False, # pick the one from environ[env_key] and use it to set the caller env resp = req.get_response(app) cache_key, env_key = _get_cache_key(account, container) + if env_key not in resp.environ: + _set_info_cache(app, req.environ, account, container, resp) try: info = resp.environ[env_key] env[env_key] = info diff --git a/swift/proxy/server.py b/swift/proxy/server.py index 65044a1..dc9fbdb 100644 --- a/swift/proxy/server.py +++ b/swift/proxy/server.py @@ -221,6 +221,13 @@ class Application(object): account_autocreate=self.account_autocreate, **constraints.EFFECTIVE_CONSTRAINTS) + def get_cache_time(self, account, container): + if container: + cache_time = self.recheck_container_existence + else: + cache_time = self.recheck_account_existence + return cache_time + def check_config(self): """ Check the configuration for possible errors diff --git a/test/functional/tests.py b/test/functional/tests.py index 18b3d47..96109df 100644 --- a/test/functional/tests.py +++ b/test/functional/tests.py @@ -2903,6 +2903,59 @@ class TestTempurl(Base): contents = self.env.obj.read(parms=parms, cfg={'no_auth_token': True}) self.assertEqual(contents, "obj contents") + def test_GET_DLO_inside_container(self): + seg1 = self.env.container.file( + "get-dlo-inside-seg1" + Utils.create_name()) + seg2 = self.env.container.file( + "get-dlo-inside-seg2" + Utils.create_name()) + seg1.write("one fish two fish ") + seg2.write("red fish blue fish") + + manifest = self.env.container.file("manifest" + Utils.create_name()) + manifest.write( + '', + hdrs={"X-Object-Manifest": "%s/get-dlo-inside-seg" % + (self.env.container.name,)}) + + expires = int(time.time()) + 86400 + sig = self.tempurl_sig( + 'GET', expires, self.env.conn.make_path(manifest.path), + self.env.tempurl_key) + parms = {'temp_url_sig': sig, + 'temp_url_expires': str(expires)} + + contents = manifest.read(parms=parms, cfg={'no_auth_token': True}) + self.assertEqual(contents, "one fish two fish red fish blue fish") + + def test_GET_DLO_outside_container(self): + seg1 = self.env.container.file( + "get-dlo-outside-seg1" + Utils.create_name()) + seg2 = self.env.container.file( + "get-dlo-outside-seg2" + Utils.create_name()) + seg1.write("one fish two fish ") + seg2.write("red fish blue fish") + + container2 = self.env.account.container(Utils.create_name()) + container2.create() + + manifest = container2.file("manifest" + Utils.create_name()) + manifest.write( + '', + hdrs={"X-Object-Manifest": "%s/get-dlo-outside-seg" % + (self.env.container.name,)}) + + expires = int(time.time()) + 86400 + sig = self.tempurl_sig( + 'GET', expires, self.env.conn.make_path(manifest.path), + self.env.tempurl_key) + parms = {'temp_url_sig': sig, + 'temp_url_expires': str(expires)} + + # cross container tempurl works fine for account tempurl key + contents = manifest.read(parms=parms, cfg={'no_auth_token': True}) + self.assertEqual(contents, "one fish two fish red fish blue fish") + self.assert_status([200]) + def test_PUT(self): new_obj = self.env.container.file(Utils.create_name()) @@ -2921,6 +2974,42 @@ class TestTempurl(Base): self.assertTrue(new_obj.info(parms=put_parms, cfg={'no_auth_token': True})) + def test_PUT_manifest_access(self): + new_obj = self.env.container.file(Utils.create_name()) + + # give out a signature which allows a PUT to new_obj + expires = int(time.time()) + 86400 + sig = self.tempurl_sig( + 'PUT', expires, self.env.conn.make_path(new_obj.path), + self.env.tempurl_key) + put_parms = {'temp_url_sig': sig, + 'temp_url_expires': str(expires)} + + # try to create manifest pointing to some random container + try: + new_obj.write('', { + 'x-object-manifest': '%s/foo' % 'some_random_container' + }, parms=put_parms, cfg={'no_auth_token': True}) + except ResponseError as e: + self.assertEqual(e.status, 400) + else: + self.fail('request did not error') + + # create some other container + other_container = self.env.account.container(Utils.create_name()) + if not other_container.create(): + raise ResponseError(self.conn.response) + + # try to create manifest pointing to new container + try: + new_obj.write('', { + 'x-object-manifest': '%s/foo' % other_container + }, parms=put_parms, cfg={'no_auth_token': True}) + except ResponseError as e: + self.assertEqual(e.status, 400) + else: + self.fail('request did not error') + def test_HEAD(self): expires = int(time.time()) + 86400 sig = self.tempurl_sig( @@ -3199,6 +3288,62 @@ class TestContainerTempurl(Base): 'Container TempURL key-2 found, should not be visible ' 'to readonly ACLs') + def test_GET_DLO_inside_container(self): + seg1 = self.env.container.file( + "get-dlo-inside-seg1" + Utils.create_name()) + seg2 = self.env.container.file( + "get-dlo-inside-seg2" + Utils.create_name()) + seg1.write("one fish two fish ") + seg2.write("red fish blue fish") + + manifest = self.env.container.file("manifest" + Utils.create_name()) + manifest.write( + '', + hdrs={"X-Object-Manifest": "%s/get-dlo-inside-seg" % + (self.env.container.name,)}) + + expires = int(time.time()) + 86400 + sig = self.tempurl_sig( + 'GET', expires, self.env.conn.make_path(manifest.path), + self.env.tempurl_key) + parms = {'temp_url_sig': sig, + 'temp_url_expires': str(expires)} + + contents = manifest.read(parms=parms, cfg={'no_auth_token': True}) + self.assertEqual(contents, "one fish two fish red fish blue fish") + + def test_GET_DLO_outside_container(self): + seg1 = self.env.container.file( + "get-dlo-outside-seg1" + Utils.create_name()) + seg2 = self.env.container.file( + "get-dlo-outside-seg2" + Utils.create_name()) + seg1.write("one fish two fish ") + seg2.write("red fish blue fish") + + container2 = self.env.account.container(Utils.create_name()) + container2.create() + + manifest = container2.file("manifest" + Utils.create_name()) + manifest.write( + '', + hdrs={"X-Object-Manifest": "%s/get-dlo-outside-seg" % + (self.env.container.name,)}) + + expires = int(time.time()) + 86400 + sig = self.tempurl_sig( + 'GET', expires, self.env.conn.make_path(manifest.path), + self.env.tempurl_key) + parms = {'temp_url_sig': sig, + 'temp_url_expires': str(expires)} + + # cross container tempurl does not work for container tempurl key + try: + manifest.read(parms=parms, cfg={'no_auth_token': True}) + except ResponseError as e: + self.assertEqual(e.status, 401) + else: + self.fail('request did not error') + class TestContainerTempurlUTF8(Base2, TestContainerTempurl): set_up = False diff --git a/test/unit/common/middleware/test_tempurl.py b/test/unit/common/middleware/test_tempurl.py index c2d9651..c840631 100644 --- a/test/unit/common/middleware/test_tempurl.py +++ b/test/unit/common/middleware/test_tempurl.py @@ -29,6 +29,7 @@ # limitations under the License. import hmac +import itertools import unittest from hashlib import sha1 from time import time @@ -44,10 +45,13 @@ class FakeApp(object): self.calls = 0 self.status_headers_body_iter = status_headers_body_iter if not self.status_headers_body_iter: - self.status_headers_body_iter = iter([('404 Not Found', { - 'x-test-header-one-a': 'value1', - 'x-test-header-two-a': 'value2', - 'x-test-header-two-b': 'value3'}, '')]) + self.status_headers_body_iter = iter( + itertools.repeat(( + '404 Not Found', { + 'x-test-header-one-a': 'value1', + 'x-test-header-two-a': 'value2', + 'x-test-header-two-b': 'value3'}, + ''))) self.request = None def __call__(self, env, start_response): @@ -69,16 +73,18 @@ class TestTempURL(unittest.TestCase): self.auth = tempauth.filter_factory({'reseller_prefix': ''})(self.app) self.tempurl = tempurl.filter_factory({})(self.auth) - def _make_request(self, path, environ=None, keys=(), **kwargs): + def _make_request(self, path, environ=None, keys=(), container_keys=None, + **kwargs): if environ is None: environ = {} _junk, account, _junk, _junk = utils.split_path(path, 2, 4) - self._fake_cache_environ(environ, account, keys) + self._fake_cache_environ(environ, account, keys, + container_keys=container_keys) req = Request.blank(path, environ=environ, **kwargs) return req - def _fake_cache_environ(self, environ, account, keys): + def _fake_cache_environ(self, environ, account, keys, container_keys=None): """ Fake out the caching layer for get_account_info(). Injects account data into environ such that keys are the tempurl keys, if set. @@ -96,8 +102,13 @@ class TestTempURL(unittest.TestCase): 'bytes': '0', 'meta': meta} + meta = {} + for i, key in enumerate(container_keys or []): + meta_name = 'Temp-URL-key' + (("-%d" % (i + 1) if i else "")) + meta[meta_name] = key + container_cache_key = 'swift.container/' + account + '/c' - environ.setdefault(container_cache_key, {'meta': {}}) + environ.setdefault(container_cache_key, {'meta': meta}) def test_passthrough(self): resp = self._make_request('/v1/a/c/o').get_response(self.tempurl) @@ -581,6 +592,81 @@ class TestTempURL(unittest.TestCase): self.assertTrue('Temp URL invalid' in resp.body) self.assertTrue('Www-Authenticate' in resp.headers) + def test_authorize_limits_scope(self): + req_other_object = Request.blank("/v1/a/c/o2") + req_other_container = Request.blank("/v1/a/c2/o2") + req_other_account = Request.blank("/v1/a2/c2/o2") + + key_kwargs = { + 'keys': ['account-key', 'shared-key'], + 'container_keys': ['container-key', 'shared-key'], + } + + # A request with the account key limits the pre-authed scope to the + # account level. + method = 'GET' + expires = int(time() + 86400) + path = '/v1/a/c/o' + + hmac_body = '%s\n%s\n%s' % (method, expires, path) + sig = hmac.new('account-key', hmac_body, sha1).hexdigest() + qs = '?temp_url_sig=%s&temp_url_expires=%s' % (sig, expires) + + # make request will setup the environ cache for us + req = self._make_request(path + qs, **key_kwargs) + resp = req.get_response(self.tempurl) + self.assertEquals(resp.status_int, 404) # sanity check + + authorize = req.environ['swift.authorize'] + # Requests for other objects happen if, for example, you're + # downloading a large object or creating a large-object manifest. + oo_resp = authorize(req_other_object) + self.assertEqual(oo_resp, None) + oc_resp = authorize(req_other_container) + self.assertEqual(oc_resp, None) + oa_resp = authorize(req_other_account) + self.assertEqual(oa_resp.status_int, 401) + + # A request with the container key limits the pre-authed scope to + # the container level; a different container in the same account is + # out of scope and thus forbidden. + hmac_body = '%s\n%s\n%s' % (method, expires, path) + sig = hmac.new('container-key', hmac_body, sha1).hexdigest() + qs = '?temp_url_sig=%s&temp_url_expires=%s' % (sig, expires) + + req = self._make_request(path + qs, **key_kwargs) + resp = req.get_response(self.tempurl) + self.assertEquals(resp.status_int, 404) # sanity check + + authorize = req.environ['swift.authorize'] + oo_resp = authorize(req_other_object) + self.assertEqual(oo_resp, None) + oc_resp = authorize(req_other_container) + self.assertEqual(oc_resp.status_int, 401) + oa_resp = authorize(req_other_account) + self.assertEqual(oa_resp.status_int, 401) + + # If account and container share a key (users set these, so this can + # happen by accident, stupidity, *or* malice!), limit the scope to + # account level. This prevents someone from shrinking the scope of + # account-level tempurls by reusing one of the account's keys on a + # container. + hmac_body = '%s\n%s\n%s' % (method, expires, path) + sig = hmac.new('shared-key', hmac_body, sha1).hexdigest() + qs = '?temp_url_sig=%s&temp_url_expires=%s' % (sig, expires) + + req = self._make_request(path + qs, **key_kwargs) + resp = req.get_response(self.tempurl) + self.assertEquals(resp.status_int, 404) # sanity check + + authorize = req.environ['swift.authorize'] + oo_resp = authorize(req_other_object) + self.assertEqual(oo_resp, None) + oc_resp = authorize(req_other_container) + self.assertEqual(oc_resp, None) + oa_resp = authorize(req_other_account) + self.assertEqual(oa_resp.status_int, 401) + def test_changed_path_invalid(self): method = 'GET' expires = int(time() + 86400) @@ -649,6 +735,25 @@ class TestTempURL(unittest.TestCase): self.assertTrue('Temp URL invalid' in resp.body) self.assertTrue('Www-Authenticate' in resp.headers) + def test_disallowed_header_object_manifest(self): + self.tempurl = tempurl.filter_factory({})(self.auth) + method = 'PUT' + expires = int(time() + 86400) + path = '/v1/a/c/o' + key = 'abc' + hmac_body = '%s\n%s\n%s' % (method, expires, path) + sig = hmac.new(key, hmac_body, sha1).hexdigest() + req = self._make_request( + path, method='PUT', keys=[key], + headers={'x-object-manifest': 'private/secret'}, + environ={'QUERY_STRING': 'temp_url_sig=%s&temp_url_expires=%s' % ( + sig, expires)}) + resp = req.get_response(self.tempurl) + self.assertEquals(resp.status_int, 400) + self.assertTrue('header' in resp.body) + self.assertTrue('not allowed' in resp.body) + self.assertTrue('X-Object-Manifest' in resp.body) + def test_removed_incoming_header(self): self.tempurl = tempurl.filter_factory({ 'incoming_remove_headers': 'x-remove-this'})(self.auth) @@ -809,35 +914,38 @@ class TestTempURL(unittest.TestCase): self.assertTrue('x-conflict-header-test' in resp.headers) self.assertEqual(resp.headers['x-conflict-header-test'], 'value') - def test_get_account(self): - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'HEAD', 'PATH_INFO': '/v1/a/c/o'}), 'a') - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a/c/o'}), 'a') - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'PUT', 'PATH_INFO': '/v1/a/c/o'}), 'a') - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'POST', 'PATH_INFO': '/v1/a/c/o'}), 'a') - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'DELETE', 'PATH_INFO': '/v1/a/c/o'}), 'a') - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'UNKNOWN', 'PATH_INFO': '/v1/a/c/o'}), None) - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a/c/'}), None) - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a/c//////'}), None) - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a/c///o///'}), 'a') - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a/c'}), None) - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a//o'}), None) - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1//c/o'}), None) - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'GET', 'PATH_INFO': '//a/c/o'}), None) - self.assertEquals(self.tempurl._get_account({ - 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v2/a/c/o'}), None) + def test_get_account_and_container(self): + self.assertEquals(self.tempurl._get_account_and_container({ + 'REQUEST_METHOD': 'HEAD', 'PATH_INFO': '/v1/a/c/o'}), ('a', 'c')) + self.assertEquals(self.tempurl._get_account_and_container({ + 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a/c/o'}), ('a', 'c')) + self.assertEquals(self.tempurl._get_account_and_container({ + 'REQUEST_METHOD': 'PUT', 'PATH_INFO': '/v1/a/c/o'}), ('a', 'c')) + self.assertEquals(self.tempurl._get_account_and_container({ + 'REQUEST_METHOD': 'POST', 'PATH_INFO': '/v1/a/c/o'}), ('a', 'c')) + self.assertEquals(self.tempurl._get_account_and_container({ + 'REQUEST_METHOD': 'DELETE', 'PATH_INFO': '/v1/a/c/o'}), ('a', 'c')) + self.assertEquals(self.tempurl._get_account_and_container({ + 'REQUEST_METHOD': 'UNKNOWN', 'PATH_INFO': '/v1/a/c/o'}), + (None, None)) + self.assertEquals(self.tempurl._get_account_and_container({ + 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a/c/'}), (None, None)) + self.assertEquals(self.tempurl._get_account_and_container({ + 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a/c//////'}), + (None, None)) + self.assertEquals(self.tempurl._get_account_and_container({ + 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a/c///o///'}), + ('a', 'c')) + self.assertEquals(self.tempurl._get_account_and_container({ + 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a/c'}), (None, None)) + self.assertEquals(self.tempurl._get_account_and_container({ + 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a//o'}), (None, None)) + self.assertEquals(self.tempurl._get_account_and_container({ + 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1//c/o'}), (None, None)) + self.assertEquals(self.tempurl._get_account_and_container({ + 'REQUEST_METHOD': 'GET', 'PATH_INFO': '//a/c/o'}), (None, None)) + self.assertEquals(self.tempurl._get_account_and_container({ + 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v2/a/c/o'}), (None, None)) def test_get_temp_url_info(self): s = 'f5d5051bddf5df7e27c628818738334f' @@ -889,13 +997,13 @@ class TestTempURL(unittest.TestCase): self.assertEquals( self.tempurl._get_hmacs( {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/a/c/o'}, - 1, ['abc']), - ['026d7f7cc25256450423c7ad03fc9f5ffc1dab6d']) + 1, [('abc', 'account')]), + [('026d7f7cc25256450423c7ad03fc9f5ffc1dab6d', 'account')]) self.assertEquals( self.tempurl._get_hmacs( {'REQUEST_METHOD': 'HEAD', 'PATH_INFO': '/v1/a/c/o'}, - 1, ['abc'], request_method='GET'), - ['026d7f7cc25256450423c7ad03fc9f5ffc1dab6d']) + 1, [('abc', 'account')], request_method='GET'), + [('026d7f7cc25256450423c7ad03fc9f5ffc1dab6d', 'account')]) def test_invalid(self): diff --git a/test/unit/proxy/controllers/test_base.py b/test/unit/proxy/controllers/test_base.py index 3938f03..b23ad98 100644 --- a/test/unit/proxy/controllers/test_base.py +++ b/test/unit/proxy/controllers/test_base.py @@ -114,14 +114,14 @@ class DynamicResponseFactory(object): class FakeApp(object): - recheck_container_existence = 30 - recheck_account_existence = 30 - def __init__(self, response_factory=None, statuses=None): self.responses = response_factory or \ DynamicResponseFactory(*statuses or []) self.sources = [] + def get_cache_time(self, account, container): + return 30 + def __call__(self, environ, start_response): self.sources.append(environ.get('swift.source')) response = self.responses.get_response(environ)