fix: bad marker behaves like non-existing marker

From the point of view of the interface, a marker should has only
two states: returning some messages, or returning no messages.
Currently a malformed message marker results in a 400 response,
which makes the marker no longer opaque.

This patch makes the transport respond 204 for a malformed marker
by making storage return an empty iterator in such a case:

  GET /queues/q/messages?marker=malformed  ->  204

This patch also include some clean up on test code.

Change-Id: I1ac7d4bb42af1644bd16d6bca6b7c50a1d700e10
Closes-Bug: #1212389
This commit is contained in:
Zhihao Yuan
2013-08-15 17:50:57 -04:00
committed by Zhihao Yuan
parent 564365a705
commit 1b38014e36
6 changed files with 35 additions and 54 deletions

View File

@@ -34,14 +34,6 @@ class Conflict(Exception):
"""
class MalformedID(ValueError):
"""ID was malformed."""
class MalformedMarker(ValueError):
"""Pagination marker was malformed."""
class MessageConflict(Conflict):
def __init__(self, queue, project, message_ids):

View File

@@ -417,7 +417,7 @@ class MessageController(storage.MessageBase):
try:
marker = int(marker)
except ValueError:
raise exceptions.MalformedMarker()
yield iter([])
messages = self._list(queue_name, marker, echo, client_uuid,
include_claimed=include_claimed, project=project)
@@ -467,7 +467,7 @@ class MessageController(storage.MessageBase):
def bulk_get(self, queue_name, message_ids, project=None):
message_ids = [mid for mid in map(utils.to_oid, message_ids) if mid]
if not message_ids:
return ()
return iter([])
now = timeutils.utcnow()

View File

@@ -66,7 +66,7 @@ def marker_decode(id):
return int(id, 8) ^ 0x3c96a355
except ValueError:
raise exceptions.MalformedMarker()
return None
def cid_encode(id):

View File

@@ -355,49 +355,41 @@ class MessageControllerTest(ControllerBaseTest):
self.assertEquals(countof['messages']['free'], 0)
def test_bad_id(self):
# A malformed ID should result in an error. This
# doesn't hurt anything, since an attacker could just
# read the source code anyway to find out how IDs are
# implemented. Plus, if someone is just trying to
# get a message that they don't own, they would
# more likely just list the messages, not try to
# guess an ID of an arbitrary message.
# NOTE(cpp-cabrera): A malformed ID should result in an empty
# query. Raising an exception for validating IDs makes the
# implementation more verbose instead of taking advantage of
# the Maybe/Optional protocol, particularly when dealing with
# bulk operations.
queue = 'foo'
project = '480924'
self.queue_controller.create(queue, project)
bad_message_id = 'xyz'
self.controller.delete(queue, bad_message_id, project)
self.controller.delete(self.queue_name,
bad_message_id,
project=self.project)
with testing.expect(exceptions.MessageDoesNotExist):
self.controller.get(queue, bad_message_id, project)
self.controller.get(self.queue_name,
bad_message_id,
project=self.project)
def test_bad_claim_id(self):
self.queue_controller.create('unused', '480924')
[msgid] = self.controller.post('unused',
[msgid] = self.controller.post(self.queue_name,
[{'body': {}, 'ttl': 10}],
project='480924',
client_uuid='unused')
project=self.project,
client_uuid='my_uuid')
bad_claim_id = '; DROP TABLE queues'
self.controller.delete('unused', msgid,
project='480924',
self.controller.delete(self.queue_name,
msgid,
project=self.project,
claim=bad_claim_id)
def test_bad_marker(self):
queue = 'foo'
project = '480924'
self.queue_controller.create(queue, project)
bad_marker = 'xyz'
func = self.controller.list
results = func(queue, project, marker=bad_marker)
self.assertRaises(exceptions.MalformedMarker, results.next)
interaction = self.controller.list(self.queue_name,
project=self.project,
marker=bad_marker)
messages = list(next(interaction))
self.assertEquals(messages, [])
class ClaimControllerTest(ControllerBaseTest):
@@ -552,12 +544,20 @@ class ClaimControllerTest(ControllerBaseTest):
def test_illformed_id(self):
# any ill-formed IDs should be regarded as non-existing ones.
self.queue_controller.create('unused', '480924')
self.controller.delete('unused', 'illformed', '480924')
self.controller.delete(self.queue_name,
'illformed',
project=self.project)
with testing.expect(exceptions.DoesNotExist):
self.controller.update('unused', 'illformed',
{'ttl': 40}, '480924')
self.controller.get(self.queue_name,
'illformed',
project=self.project)
with testing.expect(exceptions.DoesNotExist):
self.controller.update(self.queue_name,
'illformed',
{'ttl': 40},
project=self.project)
def _insert_fixtures(controller, queue_name, project=None,

View File

@@ -289,7 +289,7 @@ class MessagesBaseTest(base.TestBase):
query_string=query_string,
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
def test_no_uuid(self):
path = self.queue_path + '/messages'

View File

@@ -102,17 +102,6 @@ class CollectionResource(object):
except storage_exceptions.DoesNotExist:
raise falcon.HTTPNotFound()
except storage_exceptions.MalformedMarker:
title = _(u'Invalid query string parameter')
description = _(u'The value for the query string '
u'parameter "marker" could not be '
u'parsed. We recommend using the '
u'"next" URI from a previous '
u'request directly, rather than '
u'constructing the URI manually. ')
raise falcon.HTTPBadRequest(title, description)
except Exception as ex:
LOG.exception(ex)
description = _(u'Messages could not be listed.')