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
This commit is contained in:
Thomas Herve 2016-01-19 17:48:34 +01:00
parent 8962837f88
commit 80474c762d
9 changed files with 87 additions and 23 deletions

View File

@ -16,10 +16,12 @@
from zaqarclient.auth import base from zaqarclient.auth import base
from zaqarclient.auth import keystone from zaqarclient.auth import keystone
from zaqarclient.auth import signed_url
_BACKENDS = { _BACKENDS = {
'noauth': base.NoAuth, 'noauth': base.NoAuth,
'keystone': keystone.KeystoneAuth 'keystone': keystone.KeystoneAuth,
'signed-url': signed_url.SignedURLAuth,
} }

View File

@ -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

View File

@ -42,6 +42,8 @@ class Client(object):
:type options: `dict` :type options: `dict`
""" """
queues_module = queues
def __init__(self, url=None, version=1, conf=None): def __init__(self, url=None, version=1, conf=None):
self.conf = conf or {} self.conf = conf or {}
@ -96,7 +98,7 @@ class Client(object):
:returns: A queue instance :returns: A queue instance
:rtype: `queues.Queue` :rtype: `queues.Queue`
""" """
return queues.Queue(self, ref, **kwargs) return self.queues_module.Queue(self, ref, **kwargs)
def queues(self, **params): def queues(self, **params):
"""Gets a list of queues from the server """Gets a list of queues from the server
@ -111,7 +113,7 @@ class Client(object):
return iterator._Iterator(self, return iterator._Iterator(self,
queue_list, queue_list,
'queues', 'queues',
queues.create_object(self)) self.queues_modules.create_object(self))
def follow(self, ref): def follow(self, ref):
"""Follows ref. """Follows ref.

View File

@ -23,6 +23,8 @@ from zaqarclient.queues.v1 import message
class Queue(object): class Queue(object):
message_module = message
def __init__(self, client, name, auto_create=True, force_create=False): def __init__(self, client, name, auto_create=True, force_create=False):
"""Initialize queue object """Initialize queue object
@ -153,7 +155,7 @@ class Queue(object):
req, trans = self.client._request_and_transport() req, trans = self.client._request_and_transport()
msg = core.message_get(trans, req, self._name, msg = core.message_get(trans, req, self._name,
message_id) message_id)
return message.Message(self, **msg) return self.message_module.Message(self, **msg)
def messages(self, *messages, **params): def messages(self, *messages, **params):
"""Gets a list of messages from the server """Gets a list of messages from the server
@ -195,7 +197,7 @@ class Queue(object):
return iterator._Iterator(self.client, return iterator._Iterator(self.client,
msgs, msgs,
'messages', 'messages',
message.create_object(self)) self.message_module.create_object(self))
def delete_messages(self, *messages): def delete_messages(self, *messages):
"""Deletes a set of messages from the server """Deletes a set of messages from the server
@ -223,7 +225,7 @@ class Queue(object):
return iterator._Iterator(self.client, return iterator._Iterator(self.client,
msgs, msgs,
'messages', 'messages',
message.create_object(self)) self.message_module.create_object(self))
def claim(self, id=None, ttl=None, grace=None, def claim(self, id=None, ttl=None, grace=None,
limit=None): limit=None):

View File

@ -19,6 +19,7 @@ from zaqarclient.common import decorators
from zaqarclient.queues.v1 import client from zaqarclient.queues.v1 import client
from zaqarclient.queues.v1 import iterator from zaqarclient.queues.v1 import iterator
from zaqarclient.queues.v2 import core from zaqarclient.queues.v2 import core
from zaqarclient.queues.v2 import queues
from zaqarclient.queues.v2 import subscription from zaqarclient.queues.v2 import subscription
@ -38,6 +39,8 @@ class Client(client.Client):
:type options: `dict` :type options: `dict`
""" """
queues_module = queues
def __init__(self, url=None, version=2, conf=None): def __init__(self, url=None, version=2, conf=None):
self.conf = conf or {} self.conf = conf or {}

View File

@ -43,6 +43,7 @@ queue_set_metadata = core.queue_set_metadata
queue_get_stats = core.queue_get_stats queue_get_stats = core.queue_get_stats
queue_delete = core.queue_delete queue_delete = core.queue_delete
queue_list = core.queue_list queue_list = core.queue_list
message_get = core.message_get
message_list = core.message_list message_list = core.message_list
message_post = core.message_post message_post = core.message_post
message_delete = core.message_delete message_delete = core.message_delete

View File

@ -13,7 +13,6 @@
# 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.
from zaqarclient import errors
from zaqarclient.queues.v1 import queues from zaqarclient.queues.v1 import queues
from zaqarclient.queues.v2 import core from zaqarclient.queues.v2 import core
from zaqarclient.queues.v2 import message from zaqarclient.queues.v2 import message
@ -21,19 +20,13 @@ from zaqarclient.queues.v2 import message
class Queue(queues.Queue): class Queue(queues.Queue):
def message(self, message_id): message_module = message
"""Gets a message by id
:param message_id: Message's reference def signed_url(self, paths=None, ttl_seconds=None, methods=None):
:type message_id: `six.text_type`
:returns: A message
:rtype: `dict`
"""
req, trans = self.client._request_and_transport() req, trans = self.client._request_and_transport()
if self.client.api_version >= 2: return core.signed_url_create(trans, req, self._name, paths=paths,
raise errors.InvalidOperation("Unavailable on versions >= 2") ttl_seconds=ttl_seconds, methods=methods)
else:
msg = core.message_get(trans, req, self._name,
message_id) def create_object(parent):
return message.Message(self, **msg) return lambda args: Queue(parent, args["name"], auto_create=False)

View File

@ -75,7 +75,7 @@ class TestBase(testtools.TestCase):
def _setup_auth_params(self): def _setup_auth_params(self):
self.creds = self._credentials().get_auth_args() 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 # 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. # without version. This should be fixed as long as the bug is fixed.
parsed_url = urllib_parse.urlparse(self.creds['auth_url']) parsed_url = urllib_parse.urlparse(self.creds['auth_url'])

View File

@ -17,6 +17,7 @@ import json
import mock import mock
from zaqarclient import errors from zaqarclient import errors
from zaqarclient.queues import client
from zaqarclient.queues.v1 import iterator from zaqarclient.queues.v1 import iterator
from zaqarclient.queues.v1 import message from zaqarclient.queues.v1 import message
from zaqarclient.tests.queues import base from zaqarclient.tests.queues import base
@ -468,4 +469,24 @@ class QueuesV2QueueUnitTest(QueuesV1_1QueueUnitTest):
class QueuesV2QueueFunctionalTest(QueuesV1_1QueueFunctionalTest): 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)