tempurl: Support sha256 and sha512 signatures

Up the default to sha256 since

* the proxy has supported (and defaulted to allowing) it for four years
  now, and
* Rackspace has supported it for even longer.

Include a note in the --help about older clusters likely requiring sha1.

Change-Id: Ibac2bb7e2e4c9946c7384f0aab8e43d0d79ba645
Related-Change: Ia9dd1a91cc3c9c946f5f029cdefc9e66bcf01046
Related-Bug: #1733634
Closes-Bug: #1977867
This commit is contained in:
Tim Burke 2022-06-08 09:30:17 -07:00
parent 1dc635a32c
commit 9eee29d2e4
4 changed files with 151 additions and 48 deletions

@ -1427,6 +1427,8 @@ Optional arguments:
ISO 8601 UTC timestamp instead of a Unix timestamp. ISO 8601 UTC timestamp instead of a Unix timestamp.
--ip-range If present, the temporary URL will be restricted to the --ip-range If present, the temporary URL will be restricted to the
given ip or ip range. given ip or ip range.
--digest The digest algorithm to use. Defaults to sha256, but
older clusters may only support sha1.
'''.strip('\n') '''.strip('\n')
@ -1456,6 +1458,12 @@ def st_tempurl(parser, args, thread_manager, return_parser=False):
help=("If present, the temporary URL will be restricted to the " help=("If present, the temporary URL will be restricted to the "
"given ip or ip range."), "given ip or ip range."),
) )
parser.add_argument(
'--digest', choices=('sha1', 'sha256', 'sha512'),
default='sha256',
help=("The digest algorithm to use. Defaults to sha256, but "
"older clusters may only support sha1."),
)
# We return the parser to build up the bash_completion # We return the parser to build up the bash_completion
if return_parser: if return_parser:
@ -1480,7 +1488,8 @@ def st_tempurl(parser, args, thread_manager, return_parser=False):
absolute=options['absolute_expiry'], absolute=options['absolute_expiry'],
iso8601=options['iso8601'], iso8601=options['iso8601'],
prefix=options['prefix_based'], prefix=options['prefix_based'],
ip_range=options['ip_range']) ip_range=options['ip_range'],
digest=options['digest'])
except ValueError as err: except ValueError as err:
thread_manager.error(err) thread_manager.error(err)
return return

@ -14,6 +14,7 @@
# limitations under the License. # limitations under the License.
"""Miscellaneous utility functions for use with Swift.""" """Miscellaneous utility functions for use with Swift."""
import base64
from calendar import timegm from calendar import timegm
from collections.abc import Mapping from collections.abc import Mapping
import gzip import gzip
@ -70,7 +71,8 @@ def prt_bytes(num_bytes, human_flag):
def generate_temp_url(path, seconds, key, method, absolute=False, def generate_temp_url(path, seconds, key, method, absolute=False,
prefix=False, iso8601=False, ip_range=None): prefix=False, iso8601=False, ip_range=None,
digest='sha256'):
"""Generates a temporary URL that gives unauthenticated access to the """Generates a temporary URL that gives unauthenticated access to the
Swift object. Swift object.
@ -95,7 +97,11 @@ def generate_temp_url(path, seconds, key, method, absolute=False,
instead of a UNIX timestamp will be created. instead of a UNIX timestamp will be created.
:param ip_range: if a valid ip range, restricts the temporary URL to the :param ip_range: if a valid ip range, restricts the temporary URL to the
range of ips. range of ips.
:raises ValueError: if timestamp or path is not in valid format. :param digest: digest algorithm to use. Must be one of ``sha1``,
``sha256``, or ``sha512``.
:raises ValueError: if timestamp or path is not in valid format,
or if digest is not one of ``sha1``, ``sha256``, or
``sha512``.
:return: the path portion of a temporary URL :return: the path portion of a temporary URL
""" """
try: try:
@ -140,6 +146,11 @@ def generate_temp_url(path, seconds, key, method, absolute=False,
else: else:
path_for_body = path path_for_body = path
if isinstance(digest, str) and digest in ('sha1', 'sha256', 'sha512'):
digest = getattr(hashlib, digest)
if digest not in (hashlib.sha1, hashlib.sha256, hashlib.sha512):
raise ValueError('digest must be one of sha1, sha256, or sha512')
parts = path_for_body.split('/', 4) parts = path_for_body.split('/', 4)
if len(parts) != 5 or parts[0] or not all(parts[1:(4 if prefix else 5)]): if len(parts) != 5 or parts[0] or not all(parts[1:(4 if prefix else 5)]):
if prefix: if prefix:
@ -177,7 +188,12 @@ def generate_temp_url(path, seconds, key, method, absolute=False,
# Encode to UTF-8 for py3 compatibility # Encode to UTF-8 for py3 compatibility
if not isinstance(key, bytes): if not isinstance(key, bytes):
key = key.encode('utf-8') key = key.encode('utf-8')
sig = hmac.new(key, hmac_body.encode('utf-8'), hashlib.sha1).hexdigest() mac = hmac.new(key, hmac_body.encode('utf-8'), digest)
if digest == hashlib.sha512:
sig = 'sha512:' + base64.urlsafe_b64encode(
mac.digest()).decode('ascii').strip('=')
else:
sig = mac.hexdigest()
if iso8601: if iso8601:
expiration = time.strftime( expiration = time.strftime(

@ -2035,7 +2035,7 @@ class TestShell(unittest.TestCase):
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
temp_url.assert_called_with( temp_url.assert_called_with(
'/v1/AUTH_account/c/o', "60", 'secret_key', 'GET', absolute=False, '/v1/AUTH_account/c/o', "60", 'secret_key', 'GET', absolute=False,
iso8601=False, prefix=False, ip_range=None) iso8601=False, prefix=False, ip_range=None, digest='sha256')
@mock.patch('swiftclient.shell.generate_temp_url', return_value='') @mock.patch('swiftclient.shell.generate_temp_url', return_value='')
def test_temp_url_prefix_based(self, temp_url): def test_temp_url_prefix_based(self, temp_url):
@ -2044,7 +2044,7 @@ class TestShell(unittest.TestCase):
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
temp_url.assert_called_with( temp_url.assert_called_with(
'/v1/AUTH_account/c/', "60", 'secret_key', 'GET', absolute=False, '/v1/AUTH_account/c/', "60", 'secret_key', 'GET', absolute=False,
iso8601=False, prefix=True, ip_range=None) iso8601=False, prefix=True, ip_range=None, digest='sha256')
@mock.patch('swiftclient.shell.generate_temp_url', return_value='') @mock.patch('swiftclient.shell.generate_temp_url', return_value='')
def test_temp_url_iso8601_in(self, temp_url): def test_temp_url_iso8601_in(self, temp_url):
@ -2056,7 +2056,7 @@ class TestShell(unittest.TestCase):
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
temp_url.assert_called_with( temp_url.assert_called_with(
'/v1/AUTH_account/c/', d, 'secret_key', 'GET', absolute=False, '/v1/AUTH_account/c/', d, 'secret_key', 'GET', absolute=False,
iso8601=False, prefix=False, ip_range=None) iso8601=False, prefix=False, ip_range=None, digest='sha256')
@mock.patch('swiftclient.shell.generate_temp_url', return_value='') @mock.patch('swiftclient.shell.generate_temp_url', return_value='')
def test_temp_url_iso8601_out(self, temp_url): def test_temp_url_iso8601_out(self, temp_url):
@ -2065,7 +2065,7 @@ class TestShell(unittest.TestCase):
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
temp_url.assert_called_with( temp_url.assert_called_with(
'/v1/AUTH_account/c/', "60", 'secret_key', 'GET', absolute=False, '/v1/AUTH_account/c/', "60", 'secret_key', 'GET', absolute=False,
iso8601=True, prefix=False, ip_range=None) iso8601=True, prefix=False, ip_range=None, digest='sha256')
@mock.patch('swiftclient.shell.generate_temp_url', return_value='') @mock.patch('swiftclient.shell.generate_temp_url', return_value='')
def test_absolute_expiry_temp_url(self, temp_url): def test_absolute_expiry_temp_url(self, temp_url):
@ -2074,7 +2074,7 @@ class TestShell(unittest.TestCase):
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
temp_url.assert_called_with( temp_url.assert_called_with(
'/v1/AUTH_account/c/o', "60", 'secret_key', 'GET', absolute=True, '/v1/AUTH_account/c/o', "60", 'secret_key', 'GET', absolute=True,
iso8601=False, prefix=False, ip_range=None) iso8601=False, prefix=False, ip_range=None, digest='sha256')
@mock.patch('swiftclient.shell.generate_temp_url', return_value='') @mock.patch('swiftclient.shell.generate_temp_url', return_value='')
def test_temp_url_with_ip_range(self, temp_url): def test_temp_url_with_ip_range(self, temp_url):
@ -2083,11 +2083,11 @@ class TestShell(unittest.TestCase):
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
temp_url.assert_called_with( temp_url.assert_called_with(
'/v1/AUTH_account/c/o', "60", 'secret_key', 'GET', absolute=False, '/v1/AUTH_account/c/o', "60", 'secret_key', 'GET', absolute=False,
iso8601=False, prefix=False, ip_range='1.2.3.4') iso8601=False, prefix=False, ip_range='1.2.3.4', digest='sha256')
def test_temp_url_output(self): def test_temp_url_output(self):
argv = ["", "tempurl", "GET", "60", "/v1/a/c/o", argv = ["", "tempurl", "GET", "60", "/v1/a/c/o",
"secret_key", "--absolute"] "secret_key", "--absolute", "--digest", "sha1"]
with CaptureOutput(suppress_systemexit=True) as output: with CaptureOutput(suppress_systemexit=True) as output:
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
sig = "63bc77a473a1c2ce956548cacf916f292eb9eac3" sig = "63bc77a473a1c2ce956548cacf916f292eb9eac3"
@ -2095,14 +2095,14 @@ class TestShell(unittest.TestCase):
self.assertEqual(expected, output.out) self.assertEqual(expected, output.out)
argv = ["", "tempurl", "GET", "60", "http://saio:8080/v1/a/c/o", argv = ["", "tempurl", "GET", "60", "http://saio:8080/v1/a/c/o",
"secret_key", "--absolute"] "secret_key", "--absolute", "--digest", "sha1"]
with CaptureOutput(suppress_systemexit=True) as output: with CaptureOutput(suppress_systemexit=True) as output:
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
expected = "http://saio:8080%s" % expected expected = "http://saio:8080%s" % expected
self.assertEqual(expected, output.out) self.assertEqual(expected, output.out)
argv = ["", "tempurl", "GET", "60", "/v1/a/c/", argv = ["", "tempurl", "GET", "60", "/v1/a/c/",
"secret_key", "--absolute", "--prefix"] "secret_key", "--absolute", "--prefix", "--digest", "sha1"]
with CaptureOutput(suppress_systemexit=True) as output: with CaptureOutput(suppress_systemexit=True) as output:
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
sig = '00008c4be1573ba74fc2ab9bce02e3a93d04b349' sig = '00008c4be1573ba74fc2ab9bce02e3a93d04b349'
@ -2111,7 +2111,8 @@ class TestShell(unittest.TestCase):
self.assertEqual(expected, output.out) self.assertEqual(expected, output.out)
argv = ["", "tempurl", "GET", "60", "/v1/a/c/", argv = ["", "tempurl", "GET", "60", "/v1/a/c/",
"secret_key", "--absolute", "--prefix", '--iso8601'] "secret_key", "--absolute", "--prefix", '--iso8601',
"--digest", "sha1"]
with CaptureOutput(suppress_systemexit=True) as output: with CaptureOutput(suppress_systemexit=True) as output:
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
sig = '00008c4be1573ba74fc2ab9bce02e3a93d04b349' sig = '00008c4be1573ba74fc2ab9bce02e3a93d04b349'
@ -2124,7 +2125,7 @@ class TestShell(unittest.TestCase):
strftime(EXPIRES_ISO8601_FORMAT[:-1], localtime(60))) strftime(EXPIRES_ISO8601_FORMAT[:-1], localtime(60)))
for d in dates: for d in dates:
argv = ["", "tempurl", "GET", d, "/v1/a/c/o", argv = ["", "tempurl", "GET", d, "/v1/a/c/o",
"secret_key"] "secret_key", "--digest", "sha1"]
with CaptureOutput(suppress_systemexit=True) as output: with CaptureOutput(suppress_systemexit=True) as output:
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
sig = "63bc77a473a1c2ce956548cacf916f292eb9eac3" sig = "63bc77a473a1c2ce956548cacf916f292eb9eac3"
@ -2135,19 +2136,20 @@ class TestShell(unittest.TestCase):
mktime(strptime('2005-05-01', SHORT_EXPIRES_ISO8601_FORMAT)))) mktime(strptime('2005-05-01', SHORT_EXPIRES_ISO8601_FORMAT))))
argv = ["", "tempurl", "GET", ts, "/v1/a/c/", argv = ["", "tempurl", "GET", ts, "/v1/a/c/",
"secret_key", "--absolute"] "secret_key", "--absolute", "--digest", "sha1"]
with CaptureOutput(suppress_systemexit=True) as output: with CaptureOutput(suppress_systemexit=True) as output:
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
expected = output.out expected = output.out
argv = ["", "tempurl", "GET", '2005-05-01', "/v1/a/c/", argv = ["", "tempurl", "GET", '2005-05-01', "/v1/a/c/",
"secret_key", "--absolute"] "secret_key", "--absolute", "--digest", "sha1"]
with CaptureOutput(suppress_systemexit=True) as output: with CaptureOutput(suppress_systemexit=True) as output:
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
self.assertEqual(expected, output.out) self.assertEqual(expected, output.out)
argv = ["", "tempurl", "GET", "60", "/v1/a/c/o", argv = ["", "tempurl", "GET", "60", "/v1/a/c/o",
"secret_key", "--absolute", "--ip-range", "1.2.3.4"] "secret_key", "--absolute", "--ip-range", "1.2.3.4",
"--digest", "sha1"]
with CaptureOutput(suppress_systemexit=True) as output: with CaptureOutput(suppress_systemexit=True) as output:
swiftclient.shell.main(argv) swiftclient.shell.main(argv)
sig = "6a6ec8efa4be53904ecba8d055d841e24a937c98" sig = "6a6ec8efa4be53904ecba8d055d841e24a937c98"
@ -2157,6 +2159,39 @@ class TestShell(unittest.TestCase):
) )
self.assertEqual(expected, output.out) self.assertEqual(expected, output.out)
def test_temp_url_digests_output(self):
argv = ["", "tempurl", "GET", "60", "/v1/a/c/o",
"secret_key", "--absolute"]
with CaptureOutput(suppress_systemexit=True) as output:
swiftclient.shell.main(argv)
s = "db04994a589b1a2538bff694f0a4f57c7a397617ac2cb49f924d222bbe2b3e01"
expected = "/v1/a/c/o?temp_url_sig=%s&temp_url_expires=60\n" % s
self.assertEqual(expected, output.out)
argv = ["", "tempurl", "GET", "60", "/v1/a/c/o",
"secret_key", "--absolute", "--digest", "sha256"]
with CaptureOutput(suppress_systemexit=True) as output:
swiftclient.shell.main(argv)
# same signature/expectation
self.assertEqual(expected, output.out)
argv = ["", "tempurl", "GET", "60", "/v1/a/c/o",
"secret_key", "--absolute", "--digest", "sha1"]
with CaptureOutput(suppress_systemexit=True) as output:
swiftclient.shell.main(argv)
sig = "63bc77a473a1c2ce956548cacf916f292eb9eac3"
expected = "/v1/a/c/o?temp_url_sig=%s&temp_url_expires=60\n" % sig
self.assertEqual(expected, output.out)
argv = ["", "tempurl", "GET", "60", "/v1/a/c/o",
"secret_key", "--absolute", "--digest", "sha512"]
with CaptureOutput(suppress_systemexit=True) as output:
swiftclient.shell.main(argv)
sig = ("sha512:nMXwEAHu3jzlCZi4wWO1juEq4DikFlX8a729PLJVvUp"
"vg0GpgkJnX5uCG1x-v2KfTrmRtLOcT7KBK2RXLW1uKw")
expected = "/v1/a/c/o?temp_url_sig=%s&temp_url_expires=60\n" % sig
self.assertEqual(expected, output.out)
def test_temp_url_error_output(self): def test_temp_url_error_output(self):
expected = 'path must be full path to an object e.g. /v1/a/c/o\n' expected = 'path must be full path to an object e.g. /v1/a/c/o\n'
for bad_path in ('/v1/a/c', 'v1/a/c/o', '/v1/a/c/', '/v1/a//o', for bad_path in ('/v1/a/c', 'v1/a/c/o', '/v1/a/c/', '/v1/a//o',

@ -20,7 +20,7 @@ import unittest
from unittest import mock from unittest import mock
import tempfile import tempfile
from time import gmtime, localtime, mktime, strftime, strptime from time import gmtime, localtime, mktime, strftime, strptime
from hashlib import md5, sha1 import hashlib
from swiftclient import utils as u from swiftclient import utils as u
@ -127,17 +127,65 @@ class TestTempURL(unittest.TestCase):
seconds = 3600 seconds = 3600
key = 'correcthorsebatterystaple' key = 'correcthorsebatterystaple'
method = 'GET' method = 'GET'
expected_url = url + ('?temp_url_sig=temp_url_signature'
'&temp_url_expires=1400003600')
expected_body = '\n'.join([ expected_body = '\n'.join([
method, method,
'1400003600', '1400003600',
url, url,
]).encode('utf-8') ]).encode('utf-8')
@property
def expected_url(self):
if isinstance(self.url, bytes):
return self.url + (b'?temp_url_sig=temp_url_signature'
b'&temp_url_expires=1400003600')
return self.url + (u'?temp_url_sig=temp_url_signature'
u'&temp_url_expires=1400003600')
@property
def expected_sha512_url(self):
if isinstance(self.url, bytes):
return self.url + (b'?temp_url_sig=sha512:dGVtcF91cmxfc2lnbmF0dXJl'
b'&temp_url_expires=1400003600')
return self.url + (u'?temp_url_sig=sha512:dGVtcF91cmxfc2lnbmF0dXJl'
u'&temp_url_expires=1400003600')
@mock.patch('hmac.HMAC') @mock.patch('hmac.HMAC')
@mock.patch('time.time', return_value=1400000000) @mock.patch('time.time', return_value=1400000000)
def test_generate_temp_url(self, time_mock, hmac_mock): def test_generate_sha1_temp_url(self, time_mock, hmac_mock):
hmac_mock().hexdigest.return_value = 'temp_url_signature'
url = u.generate_temp_url(self.url, self.seconds,
self.key, self.method, digest='sha1')
key = self.key
if not isinstance(key, bytes):
key = key.encode('utf-8')
self.assertEqual(url, self.expected_url)
self.assertEqual(hmac_mock.mock_calls, [
mock.call(),
mock.call(key, self.expected_body, hashlib.sha1),
mock.call().hexdigest(),
])
self.assertIsInstance(url, type(self.url))
@mock.patch('hmac.HMAC')
@mock.patch('time.time', return_value=1400000000)
def test_generate_sha512_temp_url(self, time_mock, hmac_mock):
hmac_mock().digest.return_value = b'temp_url_signature'
url = u.generate_temp_url(self.url, self.seconds,
self.key, self.method, digest=hashlib.sha512)
key = self.key
if not isinstance(key, bytes):
key = key.encode('utf-8')
self.assertEqual(url, self.expected_sha512_url)
self.assertEqual(hmac_mock.mock_calls, [
mock.call(),
mock.call(key, self.expected_body, hashlib.sha512),
mock.call().digest(),
])
self.assertIsInstance(url, type(self.url))
@mock.patch('hmac.HMAC')
@mock.patch('time.time', return_value=1400000000)
def test_generate_sha256_temp_url_by_default(self, time_mock, hmac_mock):
hmac_mock().hexdigest.return_value = 'temp_url_signature' hmac_mock().hexdigest.return_value = 'temp_url_signature'
url = u.generate_temp_url(self.url, self.seconds, url = u.generate_temp_url(self.url, self.seconds,
self.key, self.method) self.key, self.method)
@ -147,7 +195,7 @@ class TestTempURL(unittest.TestCase):
self.assertEqual(url, self.expected_url) self.assertEqual(url, self.expected_url)
self.assertEqual(hmac_mock.mock_calls, [ self.assertEqual(hmac_mock.mock_calls, [
mock.call(), mock.call(),
mock.call(key, self.expected_body, sha1), mock.call(key, self.expected_body, hashlib.sha256),
mock.call().hexdigest(), mock.call().hexdigest(),
]) ])
self.assertIsInstance(url, type(self.url)) self.assertIsInstance(url, type(self.url))
@ -195,7 +243,7 @@ class TestTempURL(unittest.TestCase):
self.assertEqual(url, ip_range_expected_url) self.assertEqual(url, ip_range_expected_url)
self.assertEqual(hmac_mock.mock_calls, [ self.assertEqual(hmac_mock.mock_calls, [
mock.call(key, expected_body, sha1), mock.call(key, expected_body, hashlib.sha256),
mock.call().hexdigest(), mock.call().hexdigest(),
]) ])
self.assertIsInstance(url, type(path)) self.assertIsInstance(url, type(path))
@ -256,7 +304,7 @@ class TestTempURL(unittest.TestCase):
self.assertTrue(url.endswith(expires)) self.assertTrue(url.endswith(expires))
self.assertEqual(hmac_mock.mock_calls, [ self.assertEqual(hmac_mock.mock_calls, [
mock.call(), mock.call(),
mock.call(key, self.expected_body, sha1), mock.call(key, self.expected_body, hashlib.sha256),
mock.call().hexdigest(), mock.call().hexdigest(),
]) ])
self.assertIsInstance(url, type(self.url)) self.assertIsInstance(url, type(self.url))
@ -284,7 +332,7 @@ class TestTempURL(unittest.TestCase):
key = key.encode('utf-8') key = key.encode('utf-8')
self.assertEqual(url, expected_url) self.assertEqual(url, expected_url)
self.assertEqual(hmac_mock.mock_calls, [ self.assertEqual(hmac_mock.mock_calls, [
mock.call(key, expected_body, sha1), mock.call(key, expected_body, hashlib.sha256),
mock.call().hexdigest(), mock.call().hexdigest(),
]) ])
@ -374,8 +422,6 @@ class TestTempURL(unittest.TestCase):
class TestTempURLUnicodePathAndKey(TestTempURL): class TestTempURLUnicodePathAndKey(TestTempURL):
url = '/v1/\u00e4/c/\u00f3' url = '/v1/\u00e4/c/\u00f3'
key = 'k\u00e9y' key = 'k\u00e9y'
expected_url = ('%s?temp_url_sig=temp_url_signature'
'&temp_url_expires=1400003600') % url
expected_body = '\n'.join([ expected_body = '\n'.join([
'GET', 'GET',
'1400003600', '1400003600',
@ -386,8 +432,6 @@ class TestTempURLUnicodePathAndKey(TestTempURL):
class TestTempURLUnicodePathBytesKey(TestTempURL): class TestTempURLUnicodePathBytesKey(TestTempURL):
url = '/v1/\u00e4/c/\u00f3' url = '/v1/\u00e4/c/\u00f3'
key = 'k\u00e9y'.encode('utf-8') key = 'k\u00e9y'.encode('utf-8')
expected_url = ('%s?temp_url_sig=temp_url_signature'
'&temp_url_expires=1400003600') % url
expected_body = '\n'.join([ expected_body = '\n'.join([
'GET', 'GET',
'1400003600', '1400003600',
@ -398,8 +442,6 @@ class TestTempURLUnicodePathBytesKey(TestTempURL):
class TestTempURLBytesPathUnicodeKey(TestTempURL): class TestTempURLBytesPathUnicodeKey(TestTempURL):
url = '/v1/\u00e4/c/\u00f3'.encode('utf-8') url = '/v1/\u00e4/c/\u00f3'.encode('utf-8')
key = 'k\u00e9y' key = 'k\u00e9y'
expected_url = url + (b'?temp_url_sig=temp_url_signature'
b'&temp_url_expires=1400003600')
expected_body = b'\n'.join([ expected_body = b'\n'.join([
b'GET', b'GET',
b'1400003600', b'1400003600',
@ -410,8 +452,6 @@ class TestTempURLBytesPathUnicodeKey(TestTempURL):
class TestTempURLBytesPathAndKey(TestTempURL): class TestTempURLBytesPathAndKey(TestTempURL):
url = '/v1/\u00e4/c/\u00f3'.encode('utf-8') url = '/v1/\u00e4/c/\u00f3'.encode('utf-8')
key = 'k\u00e9y'.encode('utf-8') key = 'k\u00e9y'.encode('utf-8')
expected_url = url + (b'?temp_url_sig=temp_url_signature'
b'&temp_url_expires=1400003600')
expected_body = b'\n'.join([ expected_body = b'\n'.join([
b'GET', b'GET',
b'1400003600', b'1400003600',
@ -422,8 +462,6 @@ class TestTempURLBytesPathAndKey(TestTempURL):
class TestTempURLBytesPathAndNonUtf8Key(TestTempURL): class TestTempURLBytesPathAndNonUtf8Key(TestTempURL):
url = '/v1/\u00e4/c/\u00f3'.encode('utf-8') url = '/v1/\u00e4/c/\u00f3'.encode('utf-8')
key = b'k\xffy' key = b'k\xffy'
expected_url = url + (b'?temp_url_sig=temp_url_signature'
b'&temp_url_expires=1400003600')
expected_body = b'\n'.join([ expected_body = b'\n'.join([
b'GET', b'GET',
b'1400003600', b'1400003600',
@ -436,7 +474,7 @@ class TestReadableToIterable(unittest.TestCase):
def test_iter(self): def test_iter(self):
chunk_size = 4 chunk_size = 4
write_data = tuple(x.encode() for x in ('a', 'b', 'c', 'd')) write_data = tuple(x.encode() for x in ('a', 'b', 'c', 'd'))
actual_md5sum = md5() actual_md5sum = hashlib.md5()
with tempfile.TemporaryFile() as f: with tempfile.TemporaryFile() as f:
for x in write_data: for x in write_data:
@ -454,8 +492,8 @@ class TestReadableToIterable(unittest.TestCase):
def test_md5_creation(self): def test_md5_creation(self):
# Check creation with a real and noop md5 class # Check creation with a real and noop md5 class
data = u.ReadableToIterable(None, None, md5=True) data = u.ReadableToIterable(None, None, md5=True)
self.assertEqual(md5().hexdigest(), data.get_md5sum()) self.assertEqual(hashlib.md5().hexdigest(), data.get_md5sum())
self.assertIs(type(md5()), type(data.md5sum)) self.assertIs(type(hashlib.md5()), type(data.md5sum))
data = u.ReadableToIterable(None, None, md5=False) data = u.ReadableToIterable(None, None, md5=False)
self.assertEqual('', data.get_md5sum()) self.assertEqual('', data.get_md5sum())
@ -464,7 +502,7 @@ class TestReadableToIterable(unittest.TestCase):
def test_unicode(self): def test_unicode(self):
# Check no errors are raised if unicode data is feed in. # Check no errors are raised if unicode data is feed in.
unicode_data = 'abc' unicode_data = 'abc'
actual_md5sum = md5(unicode_data.encode()).hexdigest() actual_md5sum = hashlib.md5(unicode_data.encode()).hexdigest()
chunk_size = 2 chunk_size = 2
with tempfile.TemporaryFile(mode='w+') as f: with tempfile.TemporaryFile(mode='w+') as f:
@ -495,15 +533,17 @@ class TestLengthWrapper(unittest.TestCase):
self.assertEqual(42, len(data)) self.assertEqual(42, len(data))
self.assertEqual(42, len(read_data)) self.assertEqual(42, len(read_data))
self.assertEqual(s, read_data) self.assertEqual(s, read_data)
self.assertEqual(md5(s.encode()).hexdigest(), data.get_md5sum()) self.assertEqual(hashlib.md5(s.encode()).hexdigest(),
data.get_md5sum())
data.reset() data.reset()
self.assertEqual(md5().hexdigest(), data.get_md5sum()) self.assertEqual(hashlib.md5().hexdigest(), data.get_md5sum())
read_data = ''.join(iter(data.read, '')) read_data = ''.join(iter(data.read, ''))
self.assertEqual(42, len(read_data)) self.assertEqual(42, len(read_data))
self.assertEqual(s, read_data) self.assertEqual(s, read_data)
self.assertEqual(md5(s.encode()).hexdigest(), data.get_md5sum()) self.assertEqual(hashlib.md5(s.encode()).hexdigest(),
data.get_md5sum())
def test_bytesio(self): def test_bytesio(self):
contents = io.BytesIO(b'a' * 50 + b'b' * 50) contents = io.BytesIO(b'a' * 50 + b'b' * 50)
@ -515,7 +555,7 @@ class TestLengthWrapper(unittest.TestCase):
self.assertEqual(42, len(data)) self.assertEqual(42, len(data))
self.assertEqual(42, len(read_data)) self.assertEqual(42, len(read_data))
self.assertEqual(s, read_data) self.assertEqual(s, read_data)
self.assertEqual(md5(s).hexdigest(), data.get_md5sum()) self.assertEqual(hashlib.md5(s).hexdigest(), data.get_md5sum())
def test_tempfile(self): def test_tempfile(self):
with tempfile.NamedTemporaryFile(mode='wb') as f: with tempfile.NamedTemporaryFile(mode='wb') as f:
@ -529,7 +569,7 @@ class TestLengthWrapper(unittest.TestCase):
self.assertEqual(42, len(data)) self.assertEqual(42, len(data))
self.assertEqual(42, len(read_data)) self.assertEqual(42, len(read_data))
self.assertEqual(s, read_data) self.assertEqual(s, read_data)
self.assertEqual(md5(s).hexdigest(), data.get_md5sum()) self.assertEqual(hashlib.md5(s).hexdigest(), data.get_md5sum())
def test_segmented_file(self): def test_segmented_file(self):
with tempfile.NamedTemporaryFile(mode='wb') as f: with tempfile.NamedTemporaryFile(mode='wb') as f:
@ -548,15 +588,18 @@ class TestLengthWrapper(unittest.TestCase):
self.assertEqual(segment_length, len(data)) self.assertEqual(segment_length, len(data))
self.assertEqual(segment_length, len(read_data)) self.assertEqual(segment_length, len(read_data))
self.assertEqual(s, read_data) self.assertEqual(s, read_data)
self.assertEqual(md5(s).hexdigest(), data.get_md5sum()) self.assertEqual(hashlib.md5(s).hexdigest(),
data.get_md5sum())
data.reset() data.reset()
self.assertEqual(md5().hexdigest(), data.get_md5sum()) self.assertEqual(hashlib.md5().hexdigest(),
data.get_md5sum())
read_data = b''.join(iter(data.read, '')) read_data = b''.join(iter(data.read, ''))
self.assertEqual(segment_length, len(data)) self.assertEqual(segment_length, len(data))
self.assertEqual(segment_length, len(read_data)) self.assertEqual(segment_length, len(read_data))
self.assertEqual(s, read_data) self.assertEqual(s, read_data)
self.assertEqual(md5(s).hexdigest(), data.get_md5sum()) self.assertEqual(hashlib.md5(s).hexdigest(),
data.get_md5sum())
class TestGroupers(unittest.TestCase): class TestGroupers(unittest.TestCase):