From 52d39bebc11979fa1be5090ff75466710638e561 Mon Sep 17 00:00:00 2001 From: "Zack M. Davis" Date: Fri, 4 Sep 2015 14:57:30 -0700 Subject: [PATCH] absolute expiry option for tempURL generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `tempurl` subcommand's second positional argument is called `seconds` and has heretofore interpreted as the number of seconds for which the tempURL should be valid, counting from the moment of running the command. This is indeed a common, if not the most common, use-case. But some users, occasionally, might want to generate a tempURL that expires at some particular ("absolute") time, rather than a particular amount of time relative to the moment of happening to run the command. (One might make an analogy to the way in which Swift's expiring object support supports an `X-Delete-At` header in addition to `X-Delete-After`—and it's the former that must be regarded as ontologically prior.) Thus, this commit adds an `--absolute` optional argument to the `tempurl` subcommand; if present, the `seconds` argument will be interpreted as a Unix timestamp of when the tempURL should be expire, rather than a duration for which the tempURL should be valid starting from "now". Change-Id: If9ded96f2799800958d5063127f3de812f50ef06 --- doc/manpages/swift.1 | 12 +++++++----- swiftclient/shell.py | 20 +++++++++++++++++--- swiftclient/utils.py | 9 ++++++--- tests/unit/test_shell.py | 16 +++++++++++----- tests/unit/test_utils.py | 15 +++++++++++---- 5 files changed, 52 insertions(+), 20 deletions(-) diff --git a/doc/manpages/swift.1 b/doc/manpages/swift.1 index 446ade49..4cfc23fd 100644 --- a/doc/manpages/swift.1 +++ b/doc/manpages/swift.1 @@ -104,12 +104,14 @@ is not provided the storage-url retrieved after authentication is used as proxy-url. .RE -\fBtempurl\fR method seconds path key +\fBtempurl\fR \fImethod\fR \fIseconds\fR \fIpath\fR \fIkey\fR [\fI--absolute\fR] .RS 4 -Generates a temporary URL allowing unauthenticated access to the Swift object at -the given path, using the given HTTP method, for the given number of seconds, -using the given TempURL key. \fBExample\fR: tempurl GET 86400 -/v1/AUTH_foo/bar_container/quux.md my_secret_tempurl_key +Generates a temporary URL allowing unauthenticated access to the Swift object +at the given path, using the given HTTP method, for the given number of +seconds, using the given TempURL key. If optional --absolute argument is +provided, seconds is instead interpreted as a Unix timestamp at which the URL +should expire. \fBExample\fR: tempurl GET $(date -d "Jan 1 2016" +%s) +/v1/AUTH_foo/bar_container/quux.md my_secret_tempurl_key --absolute .RE .SH OPTIONS diff --git a/swiftclient/shell.py b/swiftclient/shell.py index d908d667..652980f3 100755 --- a/swiftclient/shell.py +++ b/swiftclient/shell.py @@ -1013,17 +1013,30 @@ Generates a temporary URL for a Swift object. Positional arguments: An HTTP method to allow for this temporary URL. Usually 'GET' or 'PUT'. - The amount of time in seconds the temporary URL will - be valid for. + The amount of time in seconds the temporary URL will be + valid for; or, if --absolute is passed, the Unix + timestamp when the temporary URL will expire. The full path to the Swift object. Example: /v1/AUTH_account/c/o. The secret temporary URL key set on the Swift cluster. To set a key, run \'swift post -m "Temp-URL-Key:b3968d0207b54ece87cccc06515a89d4"\' + +Optional arguments: + --absolute Interpet the positional argument as a Unix + timestamp rather than a number of seconds in the + future. '''.strip('\n') def st_tempurl(parser, args, thread_manager): + parser.add_option( + '--absolute', action='store_true', + dest='absolute_expiry', default=False, + help=("If present, seconds argument will be interpreted as a Unix " + "timestamp representing when the tempURL should expire, rather " + "than an offset from the current time") + ) (options, args) = parse_args(parser, args) args = args[1:] if len(args) < 4: @@ -1040,7 +1053,8 @@ def st_tempurl(parser, args, thread_manager): thread_manager.print_msg('WARNING: Non default HTTP method %s for ' 'tempurl specified, possibly an error' % method.upper()) - url = generate_temp_url(path, seconds, key, method) + url = generate_temp_url(path, seconds, key, method, + absolute=options.absolute_expiry) thread_manager.print_msg(url) diff --git a/swiftclient/utils.py b/swiftclient/utils.py index 6ff62594..8316a8f8 100644 --- a/swiftclient/utils.py +++ b/swiftclient/utils.py @@ -65,8 +65,8 @@ def prt_bytes(bytes, human_flag): return bytes -def generate_temp_url(path, seconds, key, method): - """ Generates a temporary URL that gives unauthenticated access to the +def generate_temp_url(path, seconds, key, method, absolute=False): + """Generates a temporary URL that gives unauthenticated access to the Swift object. :param path: The full path to the Swift object. Example: @@ -85,7 +85,10 @@ def generate_temp_url(path, seconds, key, method): if seconds < 0: raise ValueError('seconds must be a positive integer') try: - expiration = int(time.time() + seconds) + if not absolute: + expiration = int(time.time() + seconds) + else: + expiration = int(seconds) except TypeError: raise TypeError('seconds must be an integer') diff --git a/tests/unit/test_shell.py b/tests/unit/test_shell.py index 12ceadbb..e2b87d0f 100644 --- a/tests/unit/test_shell.py +++ b/tests/unit/test_shell.py @@ -922,15 +922,21 @@ class TestShell(unittest.TestCase): self.assertTrue(output.err != '') self.assertTrue(output.err.startswith('Usage')) - @mock.patch('swiftclient.shell.generate_temp_url') + @mock.patch('swiftclient.shell.generate_temp_url', return_value='') def test_temp_url(self, temp_url): argv = ["", "tempurl", "GET", "60", "/v1/AUTH_account/c/o", - "secret_key" - ] - temp_url.return_value = "" + "secret_key"] swiftclient.shell.main(argv) temp_url.assert_called_with( - '/v1/AUTH_account/c/o', 60, 'secret_key', 'GET') + '/v1/AUTH_account/c/o', 60, 'secret_key', 'GET', absolute=False) + + @mock.patch('swiftclient.shell.generate_temp_url', return_value='') + def test_absolute_expiry_temp_url(self, temp_url): + argv = ["", "tempurl", "GET", "60", "/v1/AUTH_account/c/o", + "secret_key", "--absolute"] + swiftclient.shell.main(argv) + temp_url.assert_called_with( + '/v1/AUTH_account/c/o', 60, 'secret_key', 'GET', absolute=True) @mock.patch('swiftclient.service.Connection') def test_capabilities(self, connection): diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index ca3531e1..7d7f6b67 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -132,11 +132,9 @@ class TestTempURL(testtools.TestCase): self.key = 'correcthorsebatterystaple' self.method = 'GET' - @mock.patch('hmac.HMAC.hexdigest') - @mock.patch('time.time') + @mock.patch('hmac.HMAC.hexdigest', return_value='temp_url_signature') + @mock.patch('time.time', return_value=1400000000) def test_generate_temp_url(self, time_mock, hmac_mock): - time_mock.return_value = 1400000000 - hmac_mock.return_value = 'temp_url_signature' expected_url = ( '/v1/AUTH_account/c/o?' 'temp_url_sig=temp_url_signature&' @@ -145,6 +143,15 @@ class TestTempURL(testtools.TestCase): self.method) self.assertEqual(url, expected_url) + @mock.patch('hmac.HMAC.hexdigest', return_value="temp_url_signature") + def test_generate_absolute_expiry_temp_url(self, hmac_mock): + expected_url = ('/v1/AUTH_account/c/o?' + 'temp_url_sig=temp_url_signature&' + 'temp_url_expires=2146636800') + url = u.generate_temp_url(self.url, 2146636800, self.key, self.method, + absolute=True) + self.assertEqual(url, expected_url) + def test_generate_temp_url_bad_seconds(self): self.assertRaises(TypeError, u.generate_temp_url,