Merge "Adding Swift Temporary URL support"
This commit is contained in:
commit
4cc2201e21
@ -38,7 +38,7 @@ except ImportError:
|
|||||||
|
|
||||||
from swiftclient import Connection, RequestException
|
from swiftclient import Connection, RequestException
|
||||||
from swiftclient import command_helpers
|
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.multithreading import MultiThreadingManager
|
||||||
from swiftclient.exceptions import ClientException
|
from swiftclient.exceptions import ClientException
|
||||||
from swiftclient import __version__ as client_version
|
from swiftclient import __version__ as client_version
|
||||||
@ -1240,6 +1240,45 @@ def st_capabilities(parser, args, thread_manager):
|
|||||||
st_info = st_capabilities
|
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):
|
def split_headers(options, prefix='', thread_manager=None):
|
||||||
"""
|
"""
|
||||||
Splits 'Key: Value' strings and returns them as a dictionary.
|
Splits 'Key: Value' strings and returns them as a dictionary.
|
||||||
@ -1269,6 +1308,10 @@ def parse_args(parser, args, enforce_requires=True):
|
|||||||
args = ['-h']
|
args = ['-h']
|
||||||
(options, args) = parser.parse_args(args)
|
(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)):
|
if (not (options.auth and options.user and options.key)):
|
||||||
# Use 2.0 auth if none of the old args are present
|
# Use 2.0 auth if none of the old args are present
|
||||||
options.auth_version = '2.0'
|
options.auth_version = '2.0'
|
||||||
@ -1370,6 +1413,7 @@ Positional arguments:
|
|||||||
or object.
|
or object.
|
||||||
upload Uploads files or directories to the given container.
|
upload Uploads files or directories to the given container.
|
||||||
capabilities List cluster capabilities.
|
capabilities List cluster capabilities.
|
||||||
|
tempurl Create a temporary URL
|
||||||
|
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@ -1509,7 +1553,7 @@ Examples:
|
|||||||
parser.enable_interspersed_args()
|
parser.enable_interspersed_args()
|
||||||
|
|
||||||
commands = ('delete', 'download', 'list', 'post',
|
commands = ('delete', 'download', 'list', 'post',
|
||||||
'stat', 'upload', 'capabilities', 'info')
|
'stat', 'upload', 'capabilities', 'info', 'tempurl')
|
||||||
if not args or args[0] not in commands:
|
if not args or args[0] not in commands:
|
||||||
parser.print_usage()
|
parser.print_usage()
|
||||||
if args:
|
if args:
|
||||||
|
@ -13,6 +13,10 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
"""Miscellaneous utility functions for use with Swift."""
|
"""Miscellaneous utility functions for use with Swift."""
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
@ -59,6 +63,51 @@ def prt_bytes(bytes, human_flag):
|
|||||||
return(bytes)
|
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):
|
class LengthWrapper(object):
|
||||||
|
|
||||||
def __init__(self, readable, length):
|
def __init__(self, readable, length):
|
||||||
|
@ -22,6 +22,7 @@ import six
|
|||||||
|
|
||||||
import swiftclient
|
import swiftclient
|
||||||
import swiftclient.shell
|
import swiftclient.shell
|
||||||
|
import swiftclient.utils
|
||||||
|
|
||||||
|
|
||||||
if six.PY2:
|
if six.PY2:
|
||||||
@ -328,6 +329,16 @@ class TestShell(unittest.TestCase):
|
|||||||
'Content-Type': 'text/plain',
|
'Content-Type': 'text/plain',
|
||||||
'X-Object-Meta-Color': 'Blue'})
|
'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')
|
@mock.patch('swiftclient.shell.Connection')
|
||||||
def test_capabilities(self, connection):
|
def test_capabilities(self, connection):
|
||||||
argv = ["", "capabilities"]
|
argv = ["", "capabilities"]
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
import testtools
|
import testtools
|
||||||
|
|
||||||
|
import mock
|
||||||
import six
|
import six
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
@ -122,6 +123,44 @@ class TestPrtBytes(testtools.TestCase):
|
|||||||
self.assertEqual('1024Y', u.prt_bytes(bytes_, True).lstrip())
|
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):
|
class TestLengthWrapper(testtools.TestCase):
|
||||||
|
|
||||||
def test_stringio(self):
|
def test_stringio(self):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user