summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2014-07-18 03:22:41 +0000
committerGerrit Code Review <review@openstack.org>2014-07-18 03:22:41 +0000
commit4cc2201e21a68c61adc3ce572b979553b786fc0f (patch)
tree02eb9c8fd0c7987e6375a8fc72575be830bf6c67
parentd5a45fcd2d92c1328d35c54e09712c1152f1af19 (diff)
parentdef0e0a6435deee5c55b7859e1b132590ea0860c (diff)
downloadpython-swiftclient-4cc2201e21a68c61adc3ce572b979553b786fc0f.tar.gz
Merge "Adding Swift Temporary URL support"
-rwxr-xr-xswiftclient/shell.py48
-rw-r--r--swiftclient/utils.py49
-rw-r--r--tests/unit/test_shell.py11
-rw-r--r--tests/unit/test_utils.py39
4 files changed, 145 insertions, 2 deletions
diff --git a/swiftclient/shell.py b/swiftclient/shell.py
index d10fc70..3b3757c 100755
--- a/swiftclient/shell.py
+++ b/swiftclient/shell.py
@@ -38,7 +38,7 @@ except ImportError:
from swiftclient import Connection, RequestException
from swiftclient import command_helpers
-from swiftclient.utils import config_true_value, prt_bytes
+from swiftclient.utils import config_true_value, prt_bytes, generate_temp_url
from swiftclient.multithreading import MultiThreadingManager
from swiftclient.exceptions import ClientException
from swiftclient import __version__ as client_version
@@ -1240,6 +1240,45 @@ def st_capabilities(parser, args, thread_manager):
st_info = st_capabilities
+st_tempurl_options = '<method> <seconds> <path> <key>'
+
+st_tempurl_help = '''
+Generates a temporary URL for a Swift object.
+
+Positions arguments:
+ [method] An HTTP method to allow for this temporary URL.
+ Usually 'GET' or 'PUT'.
+ [seconds] The amount of time in seconds the temporary URL will
+ be valid for.
+ [path] The full path to the Swift object. Example:
+ /v1/AUTH_account/c/o.
+ [key] The secret temporary URL key set on the Swift cluster.
+ To set a key, run \'swift post -m
+ "Temp-URL-Key:b3968d0207b54ece87cccc06515a89d4"\'
+'''.strip('\n')
+
+
+def st_tempurl(parser, args, thread_manager):
+ (options, args) = parse_args(parser, args)
+ args = args[1:]
+ if len(args) < 4:
+ thread_manager.error('Usage: %s tempurl %s\n%s', BASENAME,
+ st_tempurl_options, st_tempurl_help)
+ return
+ method, seconds, path, key = args[:4]
+ try:
+ seconds = int(seconds)
+ except ValueError:
+ thread_manager.error('Seconds must be an integer')
+ return
+ if method.upper() not in ['GET', 'PUT', 'HEAD', 'POST', 'DELETE']:
+ 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)
+ thread_manager.print_msg(url)
+
+
def split_headers(options, prefix='', thread_manager=None):
"""
Splits 'Key: Value' strings and returns them as a dictionary.
@@ -1269,6 +1308,10 @@ def parse_args(parser, args, enforce_requires=True):
args = ['-h']
(options, args) = parser.parse_args(args)
+ # Short circuit for tempurl, which doesn't need auth
+ if len(args) > 0 and args[0] == 'tempurl':
+ return options, args
+
if (not (options.auth and options.user and options.key)):
# Use 2.0 auth if none of the old args are present
options.auth_version = '2.0'
@@ -1370,6 +1413,7 @@ Positional arguments:
or object.
upload Uploads files or directories to the given container.
capabilities List cluster capabilities.
+ tempurl Create a temporary URL
Examples:
@@ -1509,7 +1553,7 @@ Examples:
parser.enable_interspersed_args()
commands = ('delete', 'download', 'list', 'post',
- 'stat', 'upload', 'capabilities', 'info')
+ 'stat', 'upload', 'capabilities', 'info', 'tempurl')
if not args or args[0] not in commands:
parser.print_usage()
if args:
diff --git a/swiftclient/utils.py b/swiftclient/utils.py
index 058181d..0f442b3 100644
--- a/swiftclient/utils.py
+++ b/swiftclient/utils.py
@@ -13,6 +13,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Miscellaneous utility functions for use with Swift."""
+import hashlib
+import hmac
+import logging
+import time
import six
@@ -59,6 +63,51 @@ 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
+ Swift object.
+
+ :param path: The full path to the Swift object. Example:
+ /v1/AUTH_account/c/o.
+ :param seconds: The amount of time in seconds the temporary URL will
+ be valid for.
+ :param key: The secret temporary URL key set on the Swift cluster.
+ To set a key, run 'swift post -m
+ "Temp-URL-Key:b3968d0207b54ece87cccc06515a89d4"'
+ :param method: A HTTP method, typically either GET or PUT, to allow for
+ this temporary URL.
+ :raises: ValueError if seconds is not a positive integer
+ :raises: TypeError if seconds is not an integer
+ :return: the path portion of a temporary URL
+ """
+ if seconds < 0:
+ raise ValueError('seconds must be a positive integer')
+ try:
+ expiration = int(time.time() + seconds)
+ except TypeError:
+ raise TypeError('seconds must be an integer')
+
+ standard_methods = ['GET', 'PUT', 'HEAD', 'POST', 'DELETE']
+ if method.upper() not in standard_methods:
+ logger = logging.getLogger("swiftclient")
+ logger.warning('Non default HTTP method %s for tempurl specified, '
+ 'possibly an error', method.upper())
+
+ hmac_body = '\n'.join([method.upper(), str(expiration), path])
+
+ # Encode to UTF-8 for py3 compatibility
+ sig = hmac.new(key.encode(),
+ hmac_body.encode(),
+ hashlib.sha1).hexdigest()
+
+ return ('{path}?temp_url_sig='
+ '{sig}&temp_url_expires={exp}'.format(
+ path=path,
+ sig=sig,
+ exp=expiration)
+ )
+
+
class LengthWrapper(object):
def __init__(self, readable, length):
diff --git a/tests/unit/test_shell.py b/tests/unit/test_shell.py
index 0c28297..33cc91a 100644
--- a/tests/unit/test_shell.py
+++ b/tests/unit/test_shell.py
@@ -22,6 +22,7 @@ import six
import swiftclient
import swiftclient.shell
+import swiftclient.utils
if six.PY2:
@@ -328,6 +329,16 @@ class TestShell(unittest.TestCase):
'Content-Type': 'text/plain',
'X-Object-Meta-Color': 'Blue'})
+ @mock.patch('swiftclient.shell.generate_temp_url')
+ def test_temp_url(self, temp_url):
+ argv = ["", "tempurl", "GET", "60", "/v1/AUTH_account/c/o",
+ "secret_key"
+ ]
+ temp_url.return_value = ""
+ swiftclient.shell.main(argv)
+ temp_url.assert_called_with(
+ '/v1/AUTH_account/c/o', 60, 'secret_key', 'GET')
+
@mock.patch('swiftclient.shell.Connection')
def test_capabilities(self, connection):
argv = ["", "capabilities"]
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
index d9d74c5..f072aed 100644
--- a/tests/unit/test_utils.py
+++ b/tests/unit/test_utils.py
@@ -15,6 +15,7 @@
import testtools
+import mock
import six
import tempfile
@@ -122,6 +123,44 @@ class TestPrtBytes(testtools.TestCase):
self.assertEqual('1024Y', u.prt_bytes(bytes_, True).lstrip())
+class TestTempURL(testtools.TestCase):
+
+ def setUp(self):
+ super(TestTempURL, self).setUp()
+ self.url = '/v1/AUTH_account/c/o'
+ self.seconds = 3600
+ self.key = 'correcthorsebatterystaple'
+ self.method = 'GET'
+
+ @mock.patch('hmac.HMAC.hexdigest')
+ @mock.patch('time.time')
+ 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&'
+ 'temp_url_expires=1400003600')
+ url = u.generate_temp_url(self.url, self.seconds, self.key,
+ self.method)
+ self.assertEqual(url, expected_url)
+
+ def test_generate_temp_url_bad_seconds(self):
+ self.assertRaises(TypeError,
+ u.generate_temp_url,
+ self.url,
+ 'not_an_int',
+ self.key,
+ self.method)
+
+ self.assertRaises(ValueError,
+ u.generate_temp_url,
+ self.url,
+ -1,
+ self.key,
+ self.method)
+
+
class TestLengthWrapper(testtools.TestCase):
def test_stringio(self):