Merge "Allow 2 TempURL keys per account."
This commit is contained in:
commit
7d625f6ea4
@ -66,9 +66,15 @@ Using this in combination with browser form post translation
|
||||
middleware could also allow direct-from-browser uploads to specific
|
||||
locations in Swift.
|
||||
|
||||
Note that changing the X-Account-Meta-Temp-URL-Key will invalidate
|
||||
any previously generated temporary URLs within 60 seconds (the
|
||||
memcache time for the key).
|
||||
TempURL supports up to two keys, specified by X-Account-Meta-Temp-URL-Key and
|
||||
X-Account-Meta-Temp-URL-Key-2. Signatures are checked against both keys, if
|
||||
present. This is to allow for key rotation without invalidating all existing
|
||||
temporary URLs.
|
||||
|
||||
Note that changing either X-Account-Meta-Temp-URL-Key or
|
||||
X-Account-Meta-Temp-URL-Key-2 will invalidate any previously generated
|
||||
temporary URLs signed with that key within 60 seconds (the memcache lifetime
|
||||
for the key). It is not instantaneous.
|
||||
|
||||
With GET TempURLs, a Content-Disposition header will be set on the
|
||||
response so that browsers will interpret this as a file attachment to
|
||||
@ -246,20 +252,20 @@ class TempURL(object):
|
||||
account = self._get_account(env)
|
||||
if not account:
|
||||
return self._invalid(env, start_response)
|
||||
key = self._get_key(env, account)
|
||||
if not key:
|
||||
keys = self._get_keys(env, account)
|
||||
if not keys:
|
||||
return self._invalid(env, start_response)
|
||||
if env['REQUEST_METHOD'] == 'HEAD':
|
||||
hmac_val = self._get_hmac(env, temp_url_expires, key,
|
||||
request_method='GET')
|
||||
if temp_url_sig != hmac_val:
|
||||
hmac_val = self._get_hmac(env, temp_url_expires, key,
|
||||
request_method='PUT')
|
||||
if temp_url_sig != hmac_val:
|
||||
hmac_vals = self._get_hmacs(env, temp_url_expires, keys,
|
||||
request_method='GET')
|
||||
if temp_url_sig not in hmac_vals:
|
||||
hmac_vals = self._get_hmacs(env, temp_url_expires, keys,
|
||||
request_method='PUT')
|
||||
if temp_url_sig not in hmac_vals:
|
||||
return self._invalid(env, start_response)
|
||||
else:
|
||||
hmac_val = self._get_hmac(env, temp_url_expires, key)
|
||||
if temp_url_sig != hmac_val:
|
||||
hmac_vals = self._get_hmacs(env, temp_url_expires, keys)
|
||||
if temp_url_sig not in hmac_vals:
|
||||
return self._invalid(env, start_response)
|
||||
self._clean_incoming_headers(env)
|
||||
env['swift.authorize'] = lambda req: None
|
||||
@ -339,40 +345,57 @@ class TempURL(object):
|
||||
filename = qs['filename'][0]
|
||||
return temp_url_sig, temp_url_expires, filename
|
||||
|
||||
def _get_key(self, env, account):
|
||||
def _get_keys(self, env, account):
|
||||
"""
|
||||
Returns the X-Account-Meta-Temp-URL-Key header value for the
|
||||
account, or None if none is set.
|
||||
Returns the X-Account-Meta-Temp-URL-Key[-2] header values for the
|
||||
account, or an empty list if none is set.
|
||||
|
||||
Returns 0, 1, or 2 elements depending on how many keys are set
|
||||
in the account's metadata.
|
||||
|
||||
:param env: The WSGI environment for the request.
|
||||
:param account: Account str.
|
||||
:returns: X-Account-Meta-Temp-URL-Key str value, or None.
|
||||
:returns: [X-Account-Meta-Temp-URL-Key str value if set,
|
||||
X-Account-Meta-Temp-URL-Key-2 str value if set]
|
||||
"""
|
||||
key = None
|
||||
keys = None
|
||||
memcache = env.get('swift.cache')
|
||||
memcache_hash_key = 'temp-url-keys/%s' % account
|
||||
if memcache:
|
||||
key = memcache.get('temp-url-key/%s' % account)
|
||||
if not key:
|
||||
keys = memcache.get(memcache_hash_key)
|
||||
if keys is None:
|
||||
newenv = make_pre_authed_env(env, 'HEAD', '/v1/' + account,
|
||||
self.agent, swift_source='TU')
|
||||
newenv['CONTENT_LENGTH'] = '0'
|
||||
newenv['wsgi.input'] = StringIO('')
|
||||
key = [None]
|
||||
keys = []
|
||||
|
||||
def _start_response(status, response_headers, exc_info=None):
|
||||
for h, v in response_headers:
|
||||
if h.lower() == 'x-account-meta-temp-url-key':
|
||||
key[0] = v
|
||||
keys.append(v)
|
||||
elif h.lower() == 'x-account-meta-temp-url-key-2':
|
||||
keys.append(v)
|
||||
|
||||
i = iter(self.app(newenv, _start_response))
|
||||
try:
|
||||
i.next()
|
||||
except StopIteration:
|
||||
pass
|
||||
key = key[0]
|
||||
if key and memcache:
|
||||
memcache.set('temp-url-key/%s' % account, key, time=60)
|
||||
return key
|
||||
if memcache:
|
||||
memcache.set(memcache_hash_key, keys, time=60)
|
||||
return keys
|
||||
|
||||
def _get_hmacs(self, env, expires, 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.
|
||||
"""
|
||||
return [self._get_hmac(env, expires, key, request_method)
|
||||
for key in keys]
|
||||
|
||||
def _get_hmac(self, env, expires, key, request_method=None):
|
||||
"""
|
||||
|
@ -110,7 +110,7 @@ class TestTempURL(unittest.TestCase):
|
||||
req = self._make_request(path,
|
||||
environ={'QUERY_STRING':
|
||||
'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)})
|
||||
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||
req.environ['swift.cache'].set('temp-url-keys/a', [key])
|
||||
self.tempurl.app = FakeApp(iter([('200 Ok', (), '123')]))
|
||||
resp = req.get_response(self.tempurl)
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
@ -119,6 +119,28 @@ class TestTempURL(unittest.TestCase):
|
||||
self.assertEquals(req.environ['swift.authorize_override'], True)
|
||||
self.assertEquals(req.environ['REMOTE_USER'], '.wsgi.tempurl')
|
||||
|
||||
def test_get_valid_key2(self):
|
||||
method = 'GET'
|
||||
expires = int(time() + 86400)
|
||||
path = '/v1/a/c/o'
|
||||
key1 = 'abc123'
|
||||
key2 = 'def456'
|
||||
hmac_body = '%s\n%s\n%s' % (method, expires, path)
|
||||
sig1 = hmac.new(key1, hmac_body, sha1).hexdigest()
|
||||
sig2 = hmac.new(key2, hmac_body, sha1).hexdigest()
|
||||
for sig in (sig1, sig2):
|
||||
req = self._make_request(path,
|
||||
environ={'QUERY_STRING':
|
||||
'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)})
|
||||
req.environ['swift.cache'].set('temp-url-keys/a', [key1, key2])
|
||||
self.tempurl.app = FakeApp(iter([('200 Ok', (), '123')]))
|
||||
resp = req.get_response(self.tempurl)
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
self.assertEquals(resp.headers['content-disposition'],
|
||||
'attachment; filename="o"')
|
||||
self.assertEquals(req.environ['swift.authorize_override'], True)
|
||||
self.assertEquals(req.environ['REMOTE_USER'], '.wsgi.tempurl')
|
||||
|
||||
def test_get_valid_with_filename(self):
|
||||
method = 'GET'
|
||||
expires = int(time() + 86400)
|
||||
@ -129,7 +151,7 @@ class TestTempURL(unittest.TestCase):
|
||||
req = self._make_request(path, environ={
|
||||
'QUERY_STRING': 'temp_url_sig=%s&temp_url_expires=%s&'
|
||||
'filename=bob%%20%%22killer%%22.txt' % (sig, expires)})
|
||||
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||
req.environ['swift.cache'].set('temp-url-keys/a', [key])
|
||||
self.tempurl.app = FakeApp(iter([('200 Ok', (), '123')]))
|
||||
resp = req.get_response(self.tempurl)
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
@ -148,7 +170,7 @@ class TestTempURL(unittest.TestCase):
|
||||
req = self._make_request(path,
|
||||
environ={'QUERY_STRING':
|
||||
'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)})
|
||||
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||
req.environ['swift.cache'].set('temp-url-keys/a', [key])
|
||||
resp = req.get_response(self.tempurl)
|
||||
self.assertEquals(resp.status_int, 404)
|
||||
self.assertFalse('content-disposition' in resp.headers)
|
||||
@ -166,7 +188,7 @@ class TestTempURL(unittest.TestCase):
|
||||
environ={'REQUEST_METHOD': 'PUT',
|
||||
'QUERY_STRING':
|
||||
'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)})
|
||||
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||
req.environ['swift.cache'].set('temp-url-keys/a', [key])
|
||||
resp = req.get_response(self.tempurl)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
self.assertTrue('Temp URL invalid' in resp.body)
|
||||
@ -182,7 +204,7 @@ class TestTempURL(unittest.TestCase):
|
||||
environ={'REQUEST_METHOD': 'PUT',
|
||||
'QUERY_STRING':
|
||||
'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)})
|
||||
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||
req.environ['swift.cache'].set('temp-url-keys/a', [key])
|
||||
resp = req.get_response(self.tempurl)
|
||||
self.assertEquals(resp.status_int, 404)
|
||||
self.assertEquals(req.environ['swift.authorize_override'], True)
|
||||
@ -198,7 +220,7 @@ class TestTempURL(unittest.TestCase):
|
||||
req = self._make_request(path,
|
||||
environ={'QUERY_STRING':
|
||||
'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)})
|
||||
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||
req.environ['swift.cache'].set('temp-url-keys/a', [key])
|
||||
resp = req.get_response(self.tempurl)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
self.assertTrue('Temp URL invalid' in resp.body)
|
||||
@ -212,7 +234,7 @@ class TestTempURL(unittest.TestCase):
|
||||
hmac.new(key, hmac_body, sha1).hexdigest()
|
||||
req = self._make_request(path,
|
||||
environ={'QUERY_STRING': 'temp_url_expires=%s' % expires})
|
||||
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||
req.environ['swift.cache'].set('temp-url-keys/a', [key])
|
||||
resp = req.get_response(self.tempurl)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
self.assertTrue('Temp URL invalid' in resp.body)
|
||||
@ -271,7 +293,7 @@ class TestTempURL(unittest.TestCase):
|
||||
environ={'REQUEST_METHOD': 'HEAD',
|
||||
'QUERY_STRING':
|
||||
'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)})
|
||||
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||
req.environ['swift.cache'].set('temp-url-keys/a', [key])
|
||||
resp = req.get_response(self.tempurl)
|
||||
self.assertEquals(resp.status_int, 404)
|
||||
self.assertEquals(req.environ['swift.authorize_override'], True)
|
||||
@ -288,7 +310,7 @@ class TestTempURL(unittest.TestCase):
|
||||
environ={'REQUEST_METHOD': 'HEAD',
|
||||
'QUERY_STRING':
|
||||
'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)})
|
||||
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||
req.environ['swift.cache'].set('temp-url-keys/a', [key])
|
||||
resp = req.get_response(self.tempurl)
|
||||
self.assertEquals(resp.status_int, 404)
|
||||
self.assertEquals(req.environ['swift.authorize_override'], True)
|
||||
@ -356,7 +378,7 @@ class TestTempURL(unittest.TestCase):
|
||||
environ={'REQUEST_METHOD': 'DELETE',
|
||||
'QUERY_STRING':
|
||||
'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)})
|
||||
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||
req.environ['swift.cache'].set('temp-url-keys/a', [key])
|
||||
resp = req.get_response(self.tempurl)
|
||||
self.assertEquals(resp.status_int, 404)
|
||||
|
||||
@ -371,7 +393,7 @@ class TestTempURL(unittest.TestCase):
|
||||
environ={'REQUEST_METHOD': 'UNKNOWN',
|
||||
'QUERY_STRING':
|
||||
'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)})
|
||||
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||
req.environ['swift.cache'].set('temp-url-keys/a', [key])
|
||||
resp = req.get_response(self.tempurl)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
self.assertTrue('Temp URL invalid' in resp.body)
|
||||
@ -386,7 +408,7 @@ class TestTempURL(unittest.TestCase):
|
||||
req = self._make_request(path + '2',
|
||||
environ={'QUERY_STRING':
|
||||
'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)})
|
||||
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||
req.environ['swift.cache'].set('temp-url-keys/a', [key])
|
||||
resp = req.get_response(self.tempurl)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
self.assertTrue('Temp URL invalid' in resp.body)
|
||||
@ -405,7 +427,7 @@ class TestTempURL(unittest.TestCase):
|
||||
req = self._make_request(path,
|
||||
environ={'QUERY_STRING':
|
||||
'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)})
|
||||
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||
req.environ['swift.cache'].set('temp-url-keys/a', [key])
|
||||
resp = req.get_response(self.tempurl)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
self.assertTrue('Temp URL invalid' in resp.body)
|
||||
@ -421,7 +443,7 @@ class TestTempURL(unittest.TestCase):
|
||||
environ={'QUERY_STRING':
|
||||
'temp_url_sig=%s&temp_url_expires=%s' %
|
||||
(sig, expires + 1)})
|
||||
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||
req.environ['swift.cache'].set('temp-url-keys/a', [key])
|
||||
resp = req.get_response(self.tempurl)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
self.assertTrue('Temp URL invalid' in resp.body)
|
||||
@ -436,7 +458,7 @@ class TestTempURL(unittest.TestCase):
|
||||
req = self._make_request(path,
|
||||
environ={'QUERY_STRING':
|
||||
'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)})
|
||||
req.environ['swift.cache'].set('temp-url-key/a', key + '2')
|
||||
req.environ['swift.cache'].set('temp-url-keys/a', [key + '2'])
|
||||
resp = req.get_response(self.tempurl)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
self.assertTrue('Temp URL invalid' in resp.body)
|
||||
@ -453,7 +475,7 @@ class TestTempURL(unittest.TestCase):
|
||||
req = self._make_request(path, headers={'x-remove-this': 'value'},
|
||||
environ={'QUERY_STRING':
|
||||
'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)})
|
||||
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||
req.environ['swift.cache'].set('temp-url-keys/a', [key])
|
||||
resp = req.get_response(self.tempurl)
|
||||
self.assertEquals(resp.status_int, 404)
|
||||
self.assertTrue('x-remove-this' not in self.app.request.headers)
|
||||
@ -473,7 +495,7 @@ class TestTempURL(unittest.TestCase):
|
||||
'x-remove-this-except-this': 'value2'},
|
||||
environ={'QUERY_STRING':
|
||||
'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)})
|
||||
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||
req.environ['swift.cache'].set('temp-url-keys/a', [key])
|
||||
resp = req.get_response(self.tempurl)
|
||||
self.assertEquals(resp.status_int, 404)
|
||||
self.assertTrue('x-remove-this-one' not in self.app.request.headers)
|
||||
@ -492,7 +514,7 @@ class TestTempURL(unittest.TestCase):
|
||||
req = self._make_request(path,
|
||||
environ={'QUERY_STRING':
|
||||
'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)})
|
||||
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||
req.environ['swift.cache'].set('temp-url-keys/a', [key])
|
||||
resp = req.get_response(self.tempurl)
|
||||
self.assertEquals(resp.status_int, 404)
|
||||
self.assertTrue('x-test-header-one-a' not in resp.headers)
|
||||
@ -511,7 +533,7 @@ class TestTempURL(unittest.TestCase):
|
||||
req = self._make_request(path,
|
||||
environ={'QUERY_STRING':
|
||||
'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)})
|
||||
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||
req.environ['swift.cache'].set('temp-url-keys/a', [key])
|
||||
resp = req.get_response(self.tempurl)
|
||||
self.assertEquals(resp.status_int, 404)
|
||||
self.assertEquals(resp.headers['x-test-header-one-a'], 'value1')
|
||||
@ -571,25 +593,44 @@ class TestTempURL(unittest.TestCase):
|
||||
def test_get_key_memcache(self):
|
||||
self.app.status_headers_body_iter = iter([('404 Not Found', {}, '')])
|
||||
self.assertEquals(
|
||||
self.tempurl._get_key({}, 'a'), None)
|
||||
self.tempurl._get_keys({}, 'a'), [])
|
||||
self.app.status_headers_body_iter = iter([('404 Not Found', {}, '')])
|
||||
self.assertEquals(
|
||||
self.tempurl._get_key({'swift.cache': None}, 'a'), None)
|
||||
self.tempurl._get_keys({'swift.cache': None}, 'a'), [])
|
||||
mc = FakeMemcache()
|
||||
self.app.status_headers_body_iter = iter([('404 Not Found', {}, '')])
|
||||
self.assertEquals(
|
||||
self.tempurl._get_key({'swift.cache': mc}, 'a'), None)
|
||||
mc.set('temp-url-key/a', 'abc')
|
||||
self.tempurl._get_keys({'swift.cache': mc}, 'a'), [])
|
||||
mc.set('temp-url-keys/a', ['abc', 'def'])
|
||||
self.assertEquals(
|
||||
self.tempurl._get_key({'swift.cache': mc}, 'a'), 'abc')
|
||||
self.tempurl._get_keys({'swift.cache': mc}, 'a'), ['abc', 'def'])
|
||||
|
||||
def test_get_key_from_source(self):
|
||||
def test_get_keys_from_source(self):
|
||||
self.app.status_headers_body_iter = \
|
||||
iter([('200 Ok', {'x-account-meta-temp-url-key': 'abc'}, '')])
|
||||
mc = FakeMemcache()
|
||||
self.assertEquals(
|
||||
self.tempurl._get_key({'swift.cache': mc}, 'a'), 'abc')
|
||||
self.assertEquals(mc.get('temp-url-key/a'), 'abc')
|
||||
self.tempurl._get_keys({'swift.cache': mc}, 'a'), ['abc'])
|
||||
self.assertEquals(mc.get('temp-url-keys/a'), ['abc'])
|
||||
|
||||
self.app.status_headers_body_iter = \
|
||||
iter([('200 Ok',
|
||||
{'x-account-meta-temp-url-key': 'abc',
|
||||
'x-account-meta-temp-url-key-2': 'def'},
|
||||
'')])
|
||||
mc = FakeMemcache()
|
||||
self.assertEquals(
|
||||
sorted(self.tempurl._get_keys({'swift.cache': mc}, 'a')),
|
||||
['abc', 'def'])
|
||||
self.assertEquals(sorted(mc.get('temp-url-keys/a')), ['abc', 'def'])
|
||||
|
||||
# no keys at all: still gets cached
|
||||
self.app.status_headers_body_iter = iter([('200 Ok', {}, '')])
|
||||
mc = FakeMemcache()
|
||||
self.assertEquals(
|
||||
sorted(self.tempurl._get_keys({'swift.cache': mc}, 'a')),
|
||||
[])
|
||||
self.assertEquals(sorted(mc.get('temp-url-keys/a')), [])
|
||||
|
||||
def test_get_hmac(self):
|
||||
self.assertEquals(self.tempurl._get_hmac(
|
||||
|
Loading…
x
Reference in New Issue
Block a user