Merge "ISO 8601 timestamps for tempurl"

This commit is contained in:
Jenkins 2017-01-24 22:22:04 +00:00 committed by Gerrit Code Review
commit 2914e04493
5 changed files with 137 additions and 86 deletions

View File

@ -1091,8 +1091,10 @@ swiftinfo_sig:
temp_url_expires:
description: |
The date and time in `UNIX Epoch time stamp
format <https://en.wikipedia.org/wiki/Unix_time>`_ when the
signature for temporary URLs expires. For example, ``1440619048``
format <https://en.wikipedia.org/wiki/Unix_time>`_ or
`ISO 8601 UTC timestamp <https://en.wikipedia.org/wiki/ISO_8601>`_
when the signature for temporary URLs expires.
For example, ``1440619048`` or ``2015-08-26T19:57:28Z``
is equivalent to ``Mon, Wed, 26 Aug 2015 19:57:28 GMT``. For more
information about temporary URLs, see `Temporary URL middleware
<http://docs.openstack.org/developer/swift/api/temporary

View File

@ -51,8 +51,9 @@ The example shows these elements:
the allowed HTTP method, expiration date, full path to the object, and the
secret key for the temporary URL.
**temp\_url\_expires**: Required. An expiration date as a UNIX Epoch timestamp,
which is an integer value. For example, ``1390852007`` represents
**temp\_url\_expires**: Required. An expiration date as a UNIX Epoch timestamp
or ISO 8601 UTC timestamp. For example, ``1390852007`` or
``2014-01-27T19:46:47Z`` can be used to represent
``Mon, 27 Jan 2014 19:46:47 GMT``.
For more information, see `Epoch & Unix Timestamp Conversion
@ -72,7 +73,7 @@ by all object names for which the URL is valid.
https://swift-cluster.example.com/v1/my_account/container/my_prefix/object
?temp_url_sig=da39a3ee5e6b4b0d3255bfef95601890afd80709
&temp_url_expires=1323479485
&temp_url_expires=2011-12-10T01:11:25Z
&temp_url_prefix=my_prefix
.. _secret_keys:
@ -126,7 +127,9 @@ signature includes these elements:
- Expiry time. In the example for the HMAC-SHA1 signature for temporary
URLs below, the expiry time is set to ``86400`` seconds (or 1 day)
into the future.
into the future. Please be aware that you have to use a UNIX timestamp
for generating the signature (in the API request it is also allowed to
use an ISO 8601 UTC timestamp).
- The path. Starting with ``/v1/`` onwards and including a container
name and object. The path for prefix-based signatures must start with
@ -168,7 +171,6 @@ temporary URLs:
hmac_body = '%s\n%s\n%s' % (method, expires, path)
signature = hmac.new(key, hmac_body, sha1).hexdigest()
Do not URL-encode the path when you generate the HMAC-SHA1 signature.
However, when you make the actual HTTP request, you should properly
URL-encode the URL.
@ -179,6 +181,14 @@ in :ref:`secret_keys`.
For more information, see `RFC 2104: HMAC: Keyed-Hashing for Message
Authentication <http://www.ietf.org/rfc/rfc2104.txt>`__.
If you want to transform a UNIX timestamp into an ISO 8601 UTC timestamp,
you can use following code snippet:
.. code::
import time
time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(timestamp))
Using the ``swift`` tool to generate a Temporary URL
~~~~~~~~~~~~~~~~~~~~~

View File

@ -82,6 +82,15 @@ Let's say ``sig`` ends up equaling
temp_url_sig=da39a3ee5e6b4b0d3255bfef95601890afd80709&
temp_url_expires=1323479485
You may also use ISO 8601 UTC timestamps with the format
``"%Y-%m-%dT%H:%M:%SZ"`` instead of UNIX timestamps in the URL
(but NOT in the code above for generating the signature!).
So, the latter URL could also be formulated as:
https://swift-cluster.example.com/v1/AUTH_account/container/object?
temp_url_sig=da39a3ee5e6b4b0d3255bfef95601890afd80709&
temp_url_expires=2011-12-10T01:11:25Z
If a prefix-based signature with the prefix ``pre`` is desired, set path to::
path = 'prefix:/v1/AUTH_account/container/pre'
@ -198,9 +207,9 @@ __all__ = ['TempURL', 'filter_factory',
'DEFAULT_OUTGOING_REMOVE_HEADERS',
'DEFAULT_OUTGOING_ALLOW_HEADERS']
from calendar import timegm
from os.path import basename
from time import time, strftime, gmtime
from time import time, strftime, strptime, gmtime
from six.moves.urllib.parse import parse_qs
from six.moves.urllib.parse import urlencode
@ -241,6 +250,8 @@ DEFAULT_OUTGOING_ALLOW_HEADERS = 'x-object-meta-public-*'
CONTAINER_SCOPE = 'container'
ACCOUNT_SCOPE = 'account'
EXPIRES_ISO8601_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
def get_tempurl_keys_from_metadata(meta):
"""
@ -533,7 +544,12 @@ class TempURL(object):
try:
temp_url_expires = int(qs['temp_url_expires'][0])
except ValueError:
temp_url_expires = 0
try:
temp_url_expires = timegm(strptime(
qs['temp_url_expires'][0],
EXPIRES_ISO8601_FORMAT))
except ValueError:
temp_url_expires = 0
if temp_url_expires < time():
temp_url_expires = 0
if 'temp_url_prefix' in qs:

View File

@ -17,12 +17,13 @@
import hmac
import hashlib
import json
import time
from copy import deepcopy
from six.moves import urllib
from unittest2 import SkipTest
from time import time, strftime, gmtime
import test.functional as tf
from swift.common.middleware import tempurl
from test.functional import cluster_info
from test.functional.tests import Utils, Base, Base2, BaseEnv
from test.functional import requires_acls
@ -98,7 +99,9 @@ class TestTempurl(Base):
"Expected tempurl_enabled to be True/False, got %r" %
(self.env.tempurl_enabled,))
self.expires = int(time.time()) + 86400
self.expires = int(time()) + 86400
self.expires_8601 = strftime(
tempurl.EXPIRES_ISO8601_FORMAT, gmtime(self.expires))
self.obj_tempurl_parms = self.tempurl_parms(
'GET', self.expires, self.env.conn.make_path(self.env.obj.path),
self.env.tempurl_key)
@ -111,17 +114,20 @@ class TestTempurl(Base):
return {'temp_url_sig': sig, 'temp_url_expires': str(expires)}
def test_GET(self):
contents = self.env.obj.read(
parms=self.obj_tempurl_parms,
cfg={'no_auth_token': True})
self.assertEqual(contents, "obj contents")
for e in (str(self.expires), self.expires_8601):
self.obj_tempurl_parms['temp_url_expires'] = e
# GET tempurls also allow HEAD requests
self.assertTrue(self.env.obj.info(parms=self.obj_tempurl_parms,
cfg={'no_auth_token': True}))
contents = self.env.obj.read(
parms=self.obj_tempurl_parms,
cfg={'no_auth_token': True})
self.assertEqual(contents, "obj contents")
# GET tempurls also allow HEAD requests
self.assertTrue(self.env.obj.info(parms=self.obj_tempurl_parms,
cfg={'no_auth_token': True}))
def test_GET_with_key_2(self):
expires = int(time.time()) + 86400
expires = int(time()) + 86400
parms = self.tempurl_parms(
'GET', expires, self.env.conn.make_path(self.env.obj.path),
self.env.tempurl_key2)
@ -143,7 +149,7 @@ class TestTempurl(Base):
hdrs={"X-Object-Manifest": "%s/get-dlo-inside-seg" %
(self.env.container.name,)})
expires = int(time.time()) + 86400
expires = int(time()) + 86400
parms = self.tempurl_parms(
'GET', expires, self.env.conn.make_path(manifest.path),
self.env.tempurl_key)
@ -168,7 +174,7 @@ class TestTempurl(Base):
hdrs={"X-Object-Manifest": "%s/get-dlo-outside-seg" %
(self.env.container.name,)})
expires = int(time.time()) + 86400
expires = int(time()) + 86400
parms = self.tempurl_parms(
'GET', expires, self.env.conn.make_path(manifest.path),
self.env.tempurl_key)
@ -181,24 +187,29 @@ class TestTempurl(Base):
def test_PUT(self):
new_obj = self.env.container.file(Utils.create_name())
expires = int(time.time()) + 86400
expires = int(time()) + 86400
expires_8601 = strftime(
tempurl.EXPIRES_ISO8601_FORMAT, gmtime(expires))
put_parms = self.tempurl_parms(
'PUT', expires, self.env.conn.make_path(new_obj.path),
self.env.tempurl_key)
for e in (str(expires), expires_8601):
put_parms['temp_url_expires'] = e
new_obj.write('new obj contents',
parms=put_parms, cfg={'no_auth_token': True})
self.assertEqual(new_obj.read(), "new obj contents")
new_obj.write('new obj contents',
parms=put_parms, cfg={'no_auth_token': True})
self.assertEqual(new_obj.read(), "new obj contents")
# PUT tempurls also allow HEAD requests
self.assertTrue(new_obj.info(parms=put_parms,
cfg={'no_auth_token': True}))
# PUT tempurls also allow HEAD requests
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
expires = int(time()) + 86400
put_parms = self.tempurl_parms(
'PUT', expires, self.env.conn.make_path(new_obj.path),
self.env.tempurl_key)
@ -230,7 +241,7 @@ class TestTempurl(Base):
# try again using a tempurl POST to an already created object
new_obj.write('', {}, parms=put_parms, cfg={'no_auth_token': True})
expires = int(time.time()) + 86400
expires = int(time()) + 86400
post_parms = self.tempurl_parms(
'POST', expires, self.env.conn.make_path(new_obj.path),
self.env.tempurl_key)
@ -243,24 +254,25 @@ class TestTempurl(Base):
self.fail('request did not error')
def test_HEAD(self):
expires = int(time.time()) + 86400
expires = int(time()) + 86400
head_parms = self.tempurl_parms(
'HEAD', expires, self.env.conn.make_path(self.env.obj.path),
self.env.tempurl_key)
self.assertTrue(self.env.obj.info(parms=head_parms,
cfg={'no_auth_token': True}))
# HEAD tempurls don't allow PUT or GET requests, despite the fact that
# PUT and GET tempurls both allow HEAD requests
self.assertRaises(ResponseError, self.env.other_obj.read,
cfg={'no_auth_token': True},
parms=self.obj_tempurl_parms)
parms=head_parms)
self.assert_status([401])
self.assertRaises(ResponseError, self.env.other_obj.write,
'new contents',
cfg={'no_auth_token': True},
parms=self.obj_tempurl_parms)
parms=head_parms)
self.assert_status([401])
def test_different_object(self):
@ -428,7 +440,7 @@ class TestContainerTempurl(Base):
"Expected tempurl_enabled to be True/False, got %r" %
(self.env.tempurl_enabled,))
expires = int(time.time()) + 86400
expires = int(time()) + 86400
sig = self.tempurl_sig(
'GET', expires, self.env.conn.make_path(self.env.obj.path),
self.env.tempurl_key)
@ -452,7 +464,7 @@ class TestContainerTempurl(Base):
cfg={'no_auth_token': True}))
def test_GET_with_key_2(self):
expires = int(time.time()) + 86400
expires = int(time()) + 86400
sig = self.tempurl_sig(
'GET', expires, self.env.conn.make_path(self.env.obj.path),
self.env.tempurl_key2)
@ -465,7 +477,7 @@ class TestContainerTempurl(Base):
def test_PUT(self):
new_obj = self.env.container.file(Utils.create_name())
expires = int(time.time()) + 86400
expires = int(time()) + 86400
sig = self.tempurl_sig(
'PUT', expires, self.env.conn.make_path(new_obj.path),
self.env.tempurl_key)
@ -481,7 +493,7 @@ class TestContainerTempurl(Base):
cfg={'no_auth_token': True}))
def test_HEAD(self):
expires = int(time.time()) + 86400
expires = int(time()) + 86400
sig = self.tempurl_sig(
'HEAD', expires, self.env.conn.make_path(self.env.obj.path),
self.env.tempurl_key)
@ -588,7 +600,7 @@ class TestContainerTempurl(Base):
hdrs={"X-Object-Manifest": "%s/get-dlo-inside-seg" %
(self.env.container.name,)})
expires = int(time.time()) + 86400
expires = int(time()) + 86400
sig = self.tempurl_sig(
'GET', expires, self.env.conn.make_path(manifest.path),
self.env.tempurl_key)
@ -614,7 +626,7 @@ class TestContainerTempurl(Base):
hdrs={"X-Object-Manifest": "%s/get-dlo-outside-seg" %
(container2.name,)})
expires = int(time.time()) + 86400
expires = int(time()) + 86400
sig = self.tempurl_sig(
'GET', expires, self.env.conn.make_path(manifest.path),
self.env.tempurl_key)
@ -701,7 +713,7 @@ class TestSloTempurl(Base):
hashlib.sha1).hexdigest()
def test_GET(self):
expires = int(time.time()) + 86400
expires = int(time()) + 86400
sig = self.tempurl_sig(
'GET', expires, self.env.conn.make_path(self.env.manifest.path),
self.env.tempurl_key)

View File

@ -1076,54 +1076,65 @@ class TestTempURL(unittest.TestCase):
def test_get_temp_url_info(self):
s = 'f5d5051bddf5df7e27c628818738334f'
e = int(time() + 86400)
e_ts = int(time() + 86400)
e_8601 = strftime(tempurl.EXPIRES_ISO8601_FORMAT, gmtime(e_ts))
for e in (e_ts, e_8601):
self.assertEqual(
self.tempurl._get_temp_url_info(
{'QUERY_STRING': 'temp_url_sig=%s&temp_url_expires=%s' % (
s, e)}),
(s, e_ts, None, None, None))
self.assertEqual(
self.tempurl._get_temp_url_info(
{'QUERY_STRING':
'temp_url_sig=%s&temp_url_expires=%s&temp_url_prefix=%s'
% (s, e, 'prefix')}),
(s, e_ts, 'prefix', None, None))
self.assertEqual(
self.tempurl._get_temp_url_info(
{'QUERY_STRING': 'temp_url_sig=%s&temp_url_expires=%s&'
'filename=bobisyouruncle' % (s, e)}),
(s, e_ts, None, 'bobisyouruncle', None))
self.assertEqual(
self.tempurl._get_temp_url_info({}),
(None, None, None, None, None))
self.assertEqual(
self.tempurl._get_temp_url_info(
{'QUERY_STRING': 'temp_url_expires=%s' % e}),
(None, e_ts, None, None, None))
self.assertEqual(
self.tempurl._get_temp_url_info(
{'QUERY_STRING': 'temp_url_sig=%s' % s}),
(s, None, None, None, None))
self.assertEqual(
self.tempurl._get_temp_url_info(
{'QUERY_STRING': 'temp_url_sig=%s&temp_url_expires=bad' % (
s)}),
(s, 0, None, None, None))
self.assertEqual(
self.tempurl._get_temp_url_info(
{'QUERY_STRING': 'temp_url_sig=%s&temp_url_expires=%s&'
'inline=' % (s, e)}),
(s, e_ts, None, None, True))
self.assertEqual(
self.tempurl._get_temp_url_info(
{'QUERY_STRING': 'temp_url_sig=%s&temp_url_expires=%s&'
'filename=bobisyouruncle&inline=' % (s, e)}),
(s, e_ts, None, 'bobisyouruncle', True))
e_ts = int(time() - 1)
e_8601 = strftime(tempurl.EXPIRES_ISO8601_FORMAT, gmtime(e_ts))
for e in (e_ts, e_8601):
self.assertEqual(
self.tempurl._get_temp_url_info(
{'QUERY_STRING': 'temp_url_sig=%s&temp_url_expires=%s' % (
s, e)}),
(s, 0, None, None, None))
# Offsets not supported (yet?).
e_8601 = strftime('%Y-%m-%dT%H:%M:%S+0000', gmtime(e_ts))
self.assertEqual(
self.tempurl._get_temp_url_info(
{'QUERY_STRING': 'temp_url_sig=%s&temp_url_expires=%s' % (
s, e)}),
(s, e, None, None, None))
self.assertEqual(
self.tempurl._get_temp_url_info(
{'QUERY_STRING':
'temp_url_sig=%s&temp_url_expires=%s&temp_url_prefix=%s' % (
s, e, 'prefix')}),
(s, e, 'prefix', None, None))
self.assertEqual(
self.tempurl._get_temp_url_info(
{'QUERY_STRING': 'temp_url_sig=%s&temp_url_expires=%s&'
'filename=bobisyouruncle' % (s, e)}),
(s, e, None, 'bobisyouruncle', None))
self.assertEqual(
self.tempurl._get_temp_url_info({}),
(None, None, None, None, None))
self.assertEqual(
self.tempurl._get_temp_url_info(
{'QUERY_STRING': 'temp_url_expires=%s' % e}),
(None, e, None, None, None))
self.assertEqual(
self.tempurl._get_temp_url_info(
{'QUERY_STRING': 'temp_url_sig=%s' % s}),
(s, None, None, None, None))
self.assertEqual(
self.tempurl._get_temp_url_info(
{'QUERY_STRING': 'temp_url_sig=%s&temp_url_expires=bad' % (
s)}),
(s, 0, None, None, None))
self.assertEqual(
self.tempurl._get_temp_url_info(
{'QUERY_STRING': 'temp_url_sig=%s&temp_url_expires=%s&'
'inline=' % (s, e)}),
(s, e, None, None, True))
self.assertEqual(
self.tempurl._get_temp_url_info(
{'QUERY_STRING': 'temp_url_sig=%s&temp_url_expires=%s&'
'filename=bobisyouruncle&inline=' % (s, e)}),
(s, e, None, 'bobisyouruncle', True))
e = int(time() - 1)
self.assertEqual(
self.tempurl._get_temp_url_info(
{'QUERY_STRING': 'temp_url_sig=%s&temp_url_expires=%s' % (
s, e)}),
s, e_8601)}),
(s, 0, None, None, None))
def test_get_hmacs(self):