From 80474c762d1401a88b7c7bdb72f543c517e9440b Mon Sep 17 00:00:00 2001 From: Thomas Herve Date: Tue, 19 Jan 2016 17:48:34 +0100 Subject: [PATCH] Add a way to create a signed URL from a queue Add a new signed_url method on v2 Queue object which returns a signature for that queue, and add a new auth backend which can use that signature. Change-Id: Iceb3cd0ab99a5a53d54ab79172c6228c4e239d8c --- zaqarclient/auth/__init__.py | 4 ++- zaqarclient/auth/signed_url.py | 40 ++++++++++++++++++++++++++++++ zaqarclient/queues/v1/client.py | 6 +++-- zaqarclient/queues/v1/queues.py | 8 +++--- zaqarclient/queues/v2/client.py | 3 +++ zaqarclient/queues/v2/core.py | 1 + zaqarclient/queues/v2/queues.py | 23 ++++++----------- zaqarclient/tests/base.py | 2 +- zaqarclient/tests/queues/queues.py | 23 ++++++++++++++++- 9 files changed, 87 insertions(+), 23 deletions(-) create mode 100644 zaqarclient/auth/signed_url.py diff --git a/zaqarclient/auth/__init__.py b/zaqarclient/auth/__init__.py index 34a3beba..2bdd64f0 100644 --- a/zaqarclient/auth/__init__.py +++ b/zaqarclient/auth/__init__.py @@ -16,10 +16,12 @@ from zaqarclient.auth import base from zaqarclient.auth import keystone +from zaqarclient.auth import signed_url _BACKENDS = { 'noauth': base.NoAuth, - 'keystone': keystone.KeystoneAuth + 'keystone': keystone.KeystoneAuth, + 'signed-url': signed_url.SignedURLAuth, } diff --git a/zaqarclient/auth/signed_url.py b/zaqarclient/auth/signed_url.py new file mode 100644 index 00000000..7411e423 --- /dev/null +++ b/zaqarclient/auth/signed_url.py @@ -0,0 +1,40 @@ +# Copyright (c) 2016 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from zaqarclient.auth import base + + +class SignedURLAuth(base.AuthBackend): + """Authenticate using signature. + + The returned client will only work on one dedicated queue which has been + signed. + + :params conf: A dictionary with the signed URL data: + - expires + - methods + - paths + - signature + - os_project_id + :type conf: `dict` + """ + + def authenticate(self, api_version, request): + """Set the necessary headers on the request.""" + request.headers['URL-Expires'] = self.conf['expires'] + request.headers['URL-Methods'] = ','.join(self.conf['methods']) + request.headers['URL-Paths'] = ','.join(self.conf['paths']) + request.headers['URL-Signature'] = self.conf['signature'] + return request diff --git a/zaqarclient/queues/v1/client.py b/zaqarclient/queues/v1/client.py index 65c8048e..a48455a6 100644 --- a/zaqarclient/queues/v1/client.py +++ b/zaqarclient/queues/v1/client.py @@ -42,6 +42,8 @@ class Client(object): :type options: `dict` """ + queues_module = queues + def __init__(self, url=None, version=1, conf=None): self.conf = conf or {} @@ -96,7 +98,7 @@ class Client(object): :returns: A queue instance :rtype: `queues.Queue` """ - return queues.Queue(self, ref, **kwargs) + return self.queues_module.Queue(self, ref, **kwargs) def queues(self, **params): """Gets a list of queues from the server @@ -111,7 +113,7 @@ class Client(object): return iterator._Iterator(self, queue_list, 'queues', - queues.create_object(self)) + self.queues_modules.create_object(self)) def follow(self, ref): """Follows ref. diff --git a/zaqarclient/queues/v1/queues.py b/zaqarclient/queues/v1/queues.py index 63f0c933..bad35502 100644 --- a/zaqarclient/queues/v1/queues.py +++ b/zaqarclient/queues/v1/queues.py @@ -23,6 +23,8 @@ from zaqarclient.queues.v1 import message class Queue(object): + message_module = message + def __init__(self, client, name, auto_create=True, force_create=False): """Initialize queue object @@ -153,7 +155,7 @@ class Queue(object): req, trans = self.client._request_and_transport() msg = core.message_get(trans, req, self._name, message_id) - return message.Message(self, **msg) + return self.message_module.Message(self, **msg) def messages(self, *messages, **params): """Gets a list of messages from the server @@ -195,7 +197,7 @@ class Queue(object): return iterator._Iterator(self.client, msgs, 'messages', - message.create_object(self)) + self.message_module.create_object(self)) def delete_messages(self, *messages): """Deletes a set of messages from the server @@ -223,7 +225,7 @@ class Queue(object): return iterator._Iterator(self.client, msgs, 'messages', - message.create_object(self)) + self.message_module.create_object(self)) def claim(self, id=None, ttl=None, grace=None, limit=None): diff --git a/zaqarclient/queues/v2/client.py b/zaqarclient/queues/v2/client.py index 391d2f44..6c1b7d06 100644 --- a/zaqarclient/queues/v2/client.py +++ b/zaqarclient/queues/v2/client.py @@ -19,6 +19,7 @@ from zaqarclient.common import decorators from zaqarclient.queues.v1 import client from zaqarclient.queues.v1 import iterator from zaqarclient.queues.v2 import core +from zaqarclient.queues.v2 import queues from zaqarclient.queues.v2 import subscription @@ -38,6 +39,8 @@ class Client(client.Client): :type options: `dict` """ + queues_module = queues + def __init__(self, url=None, version=2, conf=None): self.conf = conf or {} diff --git a/zaqarclient/queues/v2/core.py b/zaqarclient/queues/v2/core.py index 707539fa..9ac54e1e 100644 --- a/zaqarclient/queues/v2/core.py +++ b/zaqarclient/queues/v2/core.py @@ -43,6 +43,7 @@ queue_set_metadata = core.queue_set_metadata queue_get_stats = core.queue_get_stats queue_delete = core.queue_delete queue_list = core.queue_list +message_get = core.message_get message_list = core.message_list message_post = core.message_post message_delete = core.message_delete diff --git a/zaqarclient/queues/v2/queues.py b/zaqarclient/queues/v2/queues.py index 3cf0fbf4..c9811321 100644 --- a/zaqarclient/queues/v2/queues.py +++ b/zaqarclient/queues/v2/queues.py @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from zaqarclient import errors from zaqarclient.queues.v1 import queues from zaqarclient.queues.v2 import core from zaqarclient.queues.v2 import message @@ -21,19 +20,13 @@ from zaqarclient.queues.v2 import message class Queue(queues.Queue): - def message(self, message_id): - """Gets a message by id + message_module = message - :param message_id: Message's reference - :type message_id: `six.text_type` - - :returns: A message - :rtype: `dict` - """ + def signed_url(self, paths=None, ttl_seconds=None, methods=None): req, trans = self.client._request_and_transport() - if self.client.api_version >= 2: - raise errors.InvalidOperation("Unavailable on versions >= 2") - else: - msg = core.message_get(trans, req, self._name, - message_id) - return message.Message(self, **msg) + return core.signed_url_create(trans, req, self._name, paths=paths, + ttl_seconds=ttl_seconds, methods=methods) + + +def create_object(parent): + return lambda args: Queue(parent, args["name"], auto_create=False) diff --git a/zaqarclient/tests/base.py b/zaqarclient/tests/base.py index 4f4fca67..f70853c8 100644 --- a/zaqarclient/tests/base.py +++ b/zaqarclient/tests/base.py @@ -75,7 +75,7 @@ class TestBase(testtools.TestCase): def _setup_auth_params(self): self.creds = self._credentials().get_auth_args() - # FIXME(flwang): Now we're hardcode the keystone auth versioin, since + # FIXME(flwang): Now we're hardcode the keystone auth version, since # there is a 'bug' with the osc-config which is returning the auth_url # without version. This should be fixed as long as the bug is fixed. parsed_url = urllib_parse.urlparse(self.creds['auth_url']) diff --git a/zaqarclient/tests/queues/queues.py b/zaqarclient/tests/queues/queues.py index dfe9c619..63da202e 100644 --- a/zaqarclient/tests/queues/queues.py +++ b/zaqarclient/tests/queues/queues.py @@ -17,6 +17,7 @@ import json import mock from zaqarclient import errors +from zaqarclient.queues import client from zaqarclient.queues.v1 import iterator from zaqarclient.queues.v1 import message from zaqarclient.tests.queues import base @@ -468,4 +469,24 @@ class QueuesV2QueueUnitTest(QueuesV1_1QueueUnitTest): class QueuesV2QueueFunctionalTest(QueuesV1_1QueueFunctionalTest): - pass + + def test_signed_url(self): + queue = self.client.queue('test_queue') + messages = [{'ttl': 300, 'body': 'Post It!'}] + queue.post(messages) + self.addCleanup(queue.delete) + signature = queue.signed_url() + opts = { + 'paths': signature['paths'], + 'expires': signature['expires'], + 'methods': signature['methods'], + 'signature': signature['signature'], + 'os_project_id': signature['project'], + } + auth_opts = {'backend': 'signed-url', + 'options': opts} + conf = {'auth_opts': auth_opts} + signed_client = client.Client(self.url, self.version, conf) + queue = signed_client.queue('test_queue') + [message] = list(queue.messages()) + self.assertEqual('Post It!', message.body)