Merge "Remove format constraint of client id"
This commit is contained in:
commit
3dff7fd057
@ -1,16 +1,21 @@
|
|||||||
#### variables in header #####################################################
|
#### variables in header #####################################################
|
||||||
|
|
||||||
client_id:
|
client_id:
|
||||||
type: UUID
|
type: string
|
||||||
in: header
|
in: header
|
||||||
description: |
|
description: |
|
||||||
A UUID for each client instance. The UUID must be submitted in its
|
The identification for each client instance. The format of client id is
|
||||||
|
UUID by default, but Zaqar also supports a Non-UUID string by setting
|
||||||
|
configuration "client_id_uuid_safe=off". The UUID must be submitted in its
|
||||||
canonical form (for example, 3381af92-2b9e-11e3-b191-71861300734c). The
|
canonical form (for example, 3381af92-2b9e-11e3-b191-71861300734c). The
|
||||||
client generates the Client-ID once. Client-ID persists between restarts
|
string must be longer than "min_length_client_id=20" and smaller than
|
||||||
of the client so the client should reuse that same Client-ID. Note: All
|
"max_length_client_id=300" by default. User can control the length of
|
||||||
message-related operations require the use of ``Client-ID`` in the headers
|
client id by using those two options. The client generates the Client-ID
|
||||||
to ensure that messages are not echoed back to the client that posted
|
once. Client-ID persists between restarts of the client so the client
|
||||||
them, unless the client explicitly requests this.
|
should reuse that same Client-ID. Note: All message-related operations
|
||||||
|
require the use of ``Client-ID`` in the headers to ensure that messages
|
||||||
|
are not echoed back to the client that posted them, unless the client
|
||||||
|
explicitly requests this.
|
||||||
|
|
||||||
#### variables in path #######################################################
|
#### variables in path #######################################################
|
||||||
|
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Since some clients use different format of client id not only uuid, like
|
||||||
|
user id of ldap, so Zaqar will remove the format contrain of client id.
|
||||||
|
Add one option 'client_id_uuid_safe' to allow user to control the
|
||||||
|
validation of client id. Add two options 'min_length_client_id' and
|
||||||
|
'max_length_client_id' to allow user to control the length of client id
|
||||||
|
if not using uuid. This also requires user to ensure the client id is
|
||||||
|
immutable.
|
@ -293,6 +293,7 @@ class Endpoints(object):
|
|||||||
try:
|
try:
|
||||||
kwargs = api_utils.get_headers(req)
|
kwargs = api_utils.get_headers(req)
|
||||||
|
|
||||||
|
self._validate.client_id_uuid_safe(req._headers.get('Client-ID'))
|
||||||
client_uuid = api_utils.get_client_uuid(req)
|
client_uuid = api_utils.get_client_uuid(req)
|
||||||
|
|
||||||
self._validate.message_listing(**kwargs)
|
self._validate.message_listing(**kwargs)
|
||||||
@ -468,6 +469,7 @@ class Endpoints(object):
|
|||||||
return api_utils.error_response(req, ex, headers)
|
return api_utils.error_response(req, ex, headers)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
self._validate.client_id_uuid_safe(req._headers.get('Client-ID'))
|
||||||
client_uuid = api_utils.get_client_uuid(req)
|
client_uuid = api_utils.get_client_uuid(req)
|
||||||
|
|
||||||
self._validate.message_posting(messages)
|
self._validate.message_posting(messages)
|
||||||
@ -477,7 +479,8 @@ class Endpoints(object):
|
|||||||
messages=messages,
|
messages=messages,
|
||||||
project=project_id,
|
project=project_id,
|
||||||
client_uuid=client_uuid)
|
client_uuid=client_uuid)
|
||||||
except (api_errors.BadRequest, validation.ValidationFailed) as ex:
|
except (ValueError, api_errors.BadRequest,
|
||||||
|
validation.ValidationFailed) as ex:
|
||||||
LOG.debug(ex)
|
LOG.debug(ex)
|
||||||
headers = {'status': 400}
|
headers = {'status': 400}
|
||||||
return api_utils.error_response(req, ex, headers)
|
return api_utils.error_response(req, ex, headers)
|
||||||
|
@ -136,16 +136,13 @@ def get_client_uuid(req):
|
|||||||
"""Read a required Client-ID from a request.
|
"""Read a required Client-ID from a request.
|
||||||
|
|
||||||
:param req: Request object
|
:param req: Request object
|
||||||
:raises BadRequest: if the Client-ID header is missing or
|
:returns: A UUID object or A string of client id
|
||||||
does not represent a valid UUID
|
|
||||||
:returns: A UUID object
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return uuid.UUID(req._headers.get('Client-ID'))
|
return uuid.UUID(req._headers.get('Client-ID'))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
description = _(u'Malformed hexadecimal UUID.')
|
return req._headers.get('Client-ID')
|
||||||
raise api_errors.BadRequest(description)
|
|
||||||
|
|
||||||
|
|
||||||
def get_headers(req):
|
def get_headers(req):
|
||||||
|
@ -67,17 +67,13 @@ def get_client_uuid(req):
|
|||||||
"""Read a required Client-ID from a request.
|
"""Read a required Client-ID from a request.
|
||||||
|
|
||||||
:param req: A falcon.Request object
|
:param req: A falcon.Request object
|
||||||
:raises HTTPBadRequest: if the Client-ID header is missing or
|
:returns: A UUID object or A string of client id
|
||||||
does not represent a valid UUID
|
|
||||||
:returns: A UUID object
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return uuid.UUID(req.get_header('Client-ID', required=True))
|
return uuid.UUID(req.get_header('Client-ID', required=True))
|
||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
description = _(u'Malformed hexadecimal UUID.')
|
return req.get_header('Client-ID', required=True)
|
||||||
raise falcon.HTTPBadRequest('Wrong UUID value', description)
|
|
||||||
|
|
||||||
|
|
||||||
def extract_project_id(req, resp, params):
|
def extract_project_id(req, resp, params):
|
||||||
@ -112,10 +108,13 @@ def extract_project_id(req, resp, params):
|
|||||||
_(u'The header X-PROJECT-ID was missing'))
|
_(u'The header X-PROJECT-ID was missing'))
|
||||||
|
|
||||||
|
|
||||||
def require_client_id(req, resp, params):
|
def require_client_id(validate, req, resp, params):
|
||||||
"""Makes sure the header `Client-ID` is present in the request
|
"""Makes sure the header `Client-ID` is present in the request
|
||||||
|
|
||||||
Use as a before hook.
|
Use as a before hook.
|
||||||
|
:param validate: A validator function that will
|
||||||
|
be used to check the format of client id against configured
|
||||||
|
limits.
|
||||||
:param req: request sent
|
:param req: request sent
|
||||||
:type req: falcon.request.Request
|
:type req: falcon.request.Request
|
||||||
:param resp: response object to return
|
:param resp: response object to return
|
||||||
@ -126,9 +125,24 @@ def require_client_id(req, resp, params):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if req.path.startswith('/v1.1/') or req.path.startswith('/v2/'):
|
if req.path.startswith('/v1.1/') or req.path.startswith('/v2/'):
|
||||||
# NOTE(flaper87): `get_client_uuid` already raises 400
|
try:
|
||||||
# it the header is missing.
|
validate(req.get_header('Client-ID', required=True))
|
||||||
get_client_uuid(req)
|
except ValueError:
|
||||||
|
description = _(u'Malformed hexadecimal UUID.')
|
||||||
|
raise falcon.HTTPBadRequest('Wrong UUID value', description)
|
||||||
|
except validation.ValidationFailed as ex:
|
||||||
|
raise falcon.HTTPBadRequest(six.text_type(ex))
|
||||||
|
else:
|
||||||
|
# NOTE(wanghao): Since we changed the get_client_uuid to support
|
||||||
|
# other format of client id, so need to check the uuid here for
|
||||||
|
# v1 API.
|
||||||
|
try:
|
||||||
|
client_id = req.get_header('Client-ID')
|
||||||
|
if client_id or client_id == '':
|
||||||
|
uuid.UUID(client_id)
|
||||||
|
except ValueError:
|
||||||
|
description = _(u'Malformed hexadecimal UUID.')
|
||||||
|
raise falcon.HTTPBadRequest('Wrong UUID value', description)
|
||||||
|
|
||||||
|
|
||||||
def validate_queue_identification(validate, req, resp, params):
|
def validate_queue_identification(validate, req, resp, params):
|
||||||
|
@ -124,6 +124,25 @@ max_pools_per_page = cfg.IntOpt(
|
|||||||
help='Defines the maximum number of pools per page.')
|
help='Defines the maximum number of pools per page.')
|
||||||
|
|
||||||
|
|
||||||
|
client_id_uuid_safe = cfg.StrOpt(
|
||||||
|
'client_id_uuid_safe', default='strict', choices=['strict', 'off'],
|
||||||
|
help='Defines the format of client id, the value could be '
|
||||||
|
'"strict" or "off". "strict" means the format of client id'
|
||||||
|
' must be uuid, "off" means the restriction be removed.')
|
||||||
|
|
||||||
|
|
||||||
|
min_length_client_id = cfg.IntOpt(
|
||||||
|
'min_length_client_id', default='10',
|
||||||
|
help='Defines the minimum length of client id if remove the '
|
||||||
|
'uuid restriction. Default is 10.')
|
||||||
|
|
||||||
|
|
||||||
|
max_length_client_id = cfg.IntOpt(
|
||||||
|
'max_length_client_id', default='36',
|
||||||
|
help='Defines the maximum length of client id if remove the '
|
||||||
|
'uuid restriction. Default is 36.')
|
||||||
|
|
||||||
|
|
||||||
GROUP_NAME = 'transport'
|
GROUP_NAME = 'transport'
|
||||||
ALL_OPTS = [
|
ALL_OPTS = [
|
||||||
default_message_ttl,
|
default_message_ttl,
|
||||||
@ -143,7 +162,10 @@ ALL_OPTS = [
|
|||||||
max_claim_grace,
|
max_claim_grace,
|
||||||
subscriber_types,
|
subscriber_types,
|
||||||
max_flavors_per_page,
|
max_flavors_per_page,
|
||||||
max_pools_per_page
|
max_pools_per_page,
|
||||||
|
client_id_uuid_safe,
|
||||||
|
min_length_client_id,
|
||||||
|
max_length_client_id
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -127,6 +127,54 @@ class TestQueueLifecycleMongoDB(base.V2Base):
|
|||||||
self.simulate_get(gumshoe_queue_path_stats, headers=headers)
|
self.simulate_get(gumshoe_queue_path_stats, headers=headers)
|
||||||
self.assertEqual(falcon.HTTP_200, self.srmock.status)
|
self.assertEqual(falcon.HTTP_200, self.srmock.status)
|
||||||
|
|
||||||
|
@ddt.data('1234567890', '11111111111111111111111111111111111')
|
||||||
|
def test_basics_thoroughly_with_different_client_id(self, client_id):
|
||||||
|
self.conf.set_override('client_id_uuid_safe', 'off', 'transport')
|
||||||
|
headers = {
|
||||||
|
'Client-ID': client_id,
|
||||||
|
'X-Project-ID': '480924'
|
||||||
|
}
|
||||||
|
gumshoe_queue_path_stats = self.gumshoe_queue_path + '/stats'
|
||||||
|
|
||||||
|
# Stats are empty - queue not created yet
|
||||||
|
self.simulate_get(gumshoe_queue_path_stats, headers=headers)
|
||||||
|
self.assertEqual(falcon.HTTP_200, self.srmock.status)
|
||||||
|
|
||||||
|
# Create
|
||||||
|
doc = '{"messages": {"ttl": 600}}'
|
||||||
|
self.simulate_put(self.gumshoe_queue_path,
|
||||||
|
headers=headers, body=doc)
|
||||||
|
self.assertEqual(falcon.HTTP_201, self.srmock.status)
|
||||||
|
|
||||||
|
location = self.srmock.headers_dict['Location']
|
||||||
|
self.assertEqual(location, self.gumshoe_queue_path)
|
||||||
|
|
||||||
|
# Fetch metadata
|
||||||
|
result = self.simulate_get(self.gumshoe_queue_path,
|
||||||
|
headers=headers)
|
||||||
|
result_doc = jsonutils.loads(result[0])
|
||||||
|
self.assertEqual(falcon.HTTP_200, self.srmock.status)
|
||||||
|
ref_doc = jsonutils.loads(doc)
|
||||||
|
ref_doc['_default_message_ttl'] = 3600
|
||||||
|
ref_doc['_max_messages_post_size'] = 262144
|
||||||
|
ref_doc['_default_message_delay'] = 0
|
||||||
|
ref_doc['_dead_letter_queue'] = None
|
||||||
|
ref_doc['_dead_letter_queue_messages_ttl'] = None
|
||||||
|
ref_doc['_max_claim_count'] = None
|
||||||
|
self.assertEqual(ref_doc, result_doc)
|
||||||
|
|
||||||
|
# Stats empty queue
|
||||||
|
self.simulate_get(gumshoe_queue_path_stats, headers=headers)
|
||||||
|
self.assertEqual(falcon.HTTP_200, self.srmock.status)
|
||||||
|
|
||||||
|
# Delete
|
||||||
|
self.simulate_delete(self.gumshoe_queue_path, headers=headers)
|
||||||
|
self.assertEqual(falcon.HTTP_204, self.srmock.status)
|
||||||
|
|
||||||
|
# Get non-existent stats
|
||||||
|
self.simulate_get(gumshoe_queue_path_stats, headers=headers)
|
||||||
|
self.assertEqual(falcon.HTTP_200, self.srmock.status)
|
||||||
|
|
||||||
def test_name_restrictions(self):
|
def test_name_restrictions(self):
|
||||||
self.simulate_put(self.queue_path + '/Nice-Boat_2',
|
self.simulate_put(self.queue_path + '/Nice-Boat_2',
|
||||||
headers=self.headers)
|
headers=self.headers)
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
|
import uuid
|
||||||
|
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
import six
|
import six
|
||||||
@ -649,3 +650,21 @@ class Validator(object):
|
|||||||
if limit is not None and not (0 < limit <= uplimit):
|
if limit is not None and not (0 < limit <= uplimit):
|
||||||
msg = _(u'Limit must be at least 1 and no greater than {0}.')
|
msg = _(u'Limit must be at least 1 and no greater than {0}.')
|
||||||
raise ValidationFailed(msg, self._limits_conf.max_pools_per_page)
|
raise ValidationFailed(msg, self._limits_conf.max_pools_per_page)
|
||||||
|
|
||||||
|
def client_id_uuid_safe(self, client_id):
|
||||||
|
"""Restrictions the format of client id
|
||||||
|
|
||||||
|
:param client_id: the client id of request
|
||||||
|
:raises ValidationFailed: if the limit is exceeded
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._limits_conf.client_id_uuid_safe == 'off':
|
||||||
|
if (len(client_id) < self._limits_conf.min_length_client_id) or \
|
||||||
|
(len(client_id) > self._limits_conf.max_length_client_id):
|
||||||
|
msg = _(u'Length of client id must be at least {0} and no '
|
||||||
|
'greater than {1}.')
|
||||||
|
raise ValidationFailed(msg,
|
||||||
|
self._limits_conf.min_length_client_id,
|
||||||
|
self._limits_conf.max_length_client_id)
|
||||||
|
if self._limits_conf.client_id_uuid_safe == 'strict':
|
||||||
|
uuid.UUID(client_id)
|
||||||
|
@ -72,6 +72,10 @@ class Driver(transport.DriverBase):
|
|||||||
return helpers.validate_queue_identification(
|
return helpers.validate_queue_identification(
|
||||||
self._validate.queue_identification, req, resp, params)
|
self._validate.queue_identification, req, resp, params)
|
||||||
|
|
||||||
|
def _require_client_id(self, req, resp, params):
|
||||||
|
return helpers.require_client_id(
|
||||||
|
self._validate.client_id_uuid_safe, req, resp, params)
|
||||||
|
|
||||||
@decorators.lazy_property(write=False)
|
@decorators.lazy_property(write=False)
|
||||||
def before_hooks(self):
|
def before_hooks(self):
|
||||||
"""Exposed to facilitate unit testing."""
|
"""Exposed to facilitate unit testing."""
|
||||||
@ -79,7 +83,7 @@ class Driver(transport.DriverBase):
|
|||||||
self._verify_pre_signed_url,
|
self._verify_pre_signed_url,
|
||||||
helpers.require_content_type_be_non_urlencoded,
|
helpers.require_content_type_be_non_urlencoded,
|
||||||
helpers.require_accepts_json,
|
helpers.require_accepts_json,
|
||||||
helpers.require_client_id,
|
self._require_client_id,
|
||||||
helpers.extract_project_id,
|
helpers.extract_project_id,
|
||||||
|
|
||||||
# NOTE(jeffrey4l): Depends on the project_id and client_id being
|
# NOTE(jeffrey4l): Depends on the project_id and client_id being
|
||||||
|
Loading…
Reference in New Issue
Block a user