diff options
author | Endre Karlson <endre.karlson@hp.com> | 2014-02-22 13:56:05 +0100 |
---|---|---|
committer | Endre Karlson <endre.karlson@hp.com> | 2014-03-03 19:42:32 +0100 |
commit | 20bef4e133ca3433d353203990ec9179659e0328 (patch) | |
tree | 2dd28e2bd1fd19ebfbc38b398452196671c5afd3 | |
parent | c44b8f81c64dd5665d888cbb0e44d7c14123b12f (diff) | |
download | designate-20bef4e133ca3433d353203990ec9179659e0328.tar.gz |
UUID changes to api / utils
* Remove uuidutils as noted and move the is_valid_like into designate.utils.
* Add UUID validation to the V2 API endpoints
Closes-Bug: #1282672
Change-Id: Ib30a4c2657323145c8500fae503f75871281675f
-rw-r--r-- | designate/api/v1/__init__.py | 4 | ||||
-rw-r--r-- | designate/api/v2/controllers/blacklists.py | 3 | ||||
-rw-r--r-- | designate/api/v2/controllers/records.py | 13 | ||||
-rw-r--r-- | designate/api/v2/controllers/recordsets.py | 13 | ||||
-rw-r--r-- | designate/api/v2/controllers/tlds.py | 3 | ||||
-rw-r--r-- | designate/api/v2/controllers/zones.py | 3 | ||||
-rw-r--r-- | designate/exceptions.py | 4 | ||||
-rw-r--r-- | designate/openstack/common/uuidutils.py | 37 | ||||
-rw-r--r-- | designate/tests/test_api/test_v2/__init__.py | 15 | ||||
-rw-r--r-- | designate/tests/test_api/test_v2/test_blacklists.py | 9 | ||||
-rw-r--r-- | designate/tests/test_api/test_v2/test_recordsets.py | 22 | ||||
-rw-r--r-- | designate/tests/test_api/test_v2/test_tlds.py | 9 | ||||
-rw-r--r-- | designate/tests/test_api/test_v2/test_zones.py | 59 | ||||
-rw-r--r-- | designate/utils.py | 37 | ||||
-rw-r--r-- | openstack-common.conf | 1 |
15 files changed, 119 insertions, 113 deletions
diff --git a/designate/api/v1/__init__.py b/designate/api/v1/__init__.py index 090335e0..815c2c41 100644 --- a/designate/api/v1/__init__.py +++ b/designate/api/v1/__init__.py @@ -22,9 +22,9 @@ from werkzeug.routing import BaseConverter from werkzeug.routing import ValidationError from oslo.config import cfg from designate.openstack.common import log as logging -from designate.openstack.common import uuidutils from designate.openstack.common import jsonutils from designate import exceptions +from designate import utils LOG = logging.getLogger(__name__) @@ -126,7 +126,7 @@ class UUIDConverter(BaseConverter): """ Validates UUID URL paramaters """ def to_python(self, value): - if not uuidutils.is_uuid_like(value): + if not utils.is_uuid_like(value): raise ValidationError() return value diff --git a/designate/api/v2/controllers/blacklists.py b/designate/api/v2/controllers/blacklists.py index 8da31d2f..b354ebee 100644 --- a/designate/api/v2/controllers/blacklists.py +++ b/designate/api/v2/controllers/blacklists.py @@ -33,6 +33,7 @@ class BlacklistsController(rest.RestController): SORT_KEYS = ['created_at', 'id', 'updated_at', 'pattern'] @pecan.expose(template='json:', content_type='application/json') + @utils.validate_uuid('blacklist_id') def get_one(self, blacklist_id): """ Get Blacklist """ @@ -90,6 +91,7 @@ class BlacklistsController(rest.RestController): @pecan.expose(template='json:', content_type='application/json') @pecan.expose(template='json:', content_type='application/json-patch+json') + @utils.validate_uuid('blacklist_id') def patch_one(self, blacklist_id): """ Update Blacklisted Zone """ request = pecan.request @@ -121,6 +123,7 @@ class BlacklistsController(rest.RestController): return self._view.show(context, request, blacklist) @pecan.expose(template=None, content_type='application/json') + @utils.validate_uuid('blacklist_id') def delete_one(self, blacklist_id): """ Delete Blacklisted Zone """ request = pecan.request diff --git a/designate/api/v2/controllers/records.py b/designate/api/v2/controllers/records.py index a0042667..06e57e5f 100644 --- a/designate/api/v2/controllers/records.py +++ b/designate/api/v2/controllers/records.py @@ -33,10 +33,9 @@ class RecordsController(rest.RestController): 'recordset_id', 'status'] @pecan.expose(template='json:', content_type='application/json') + @utils.validate_uuid('zone_id', 'recordset_id', 'record_id') def get_one(self, zone_id, recordset_id, record_id): """ Get Record """ - # TODO(kiall): Validate we have a sane UUID for zone_id, recordset_id - # and record_id request = pecan.request context = request.environ['context'] @@ -46,6 +45,7 @@ class RecordsController(rest.RestController): return self._view.show(context, request, record) @pecan.expose(template='json:', content_type='application/json') + @utils.validate_uuid('zone_id', 'recordset_id') def get_all(self, zone_id, recordset_id, **params): """ List Records """ request = pecan.request @@ -69,6 +69,7 @@ class RecordsController(rest.RestController): [zone_id, recordset_id]) @pecan.expose(template='json:', content_type='application/json') + @utils.validate_uuid('zone_id', 'recordset_id') def post_all(self, zone_id, recordset_id): """ Create Record """ request = pecan.request @@ -101,6 +102,7 @@ class RecordsController(rest.RestController): @pecan.expose(template='json:', content_type='application/json') @pecan.expose(template='json:', content_type='application/json-patch+json') + @utils.validate_uuid('zone_id', 'recordset_id', 'record_id') def patch_one(self, zone_id, recordset_id, record_id): """ Update Record """ request = pecan.request @@ -108,9 +110,6 @@ class RecordsController(rest.RestController): body = request.body_dict response = pecan.response - # TODO(kiall): Validate we have a sane UUID for zone_id and - # recordset_id - # Fetch the existing record record = central_api.get_record(context, zone_id, recordset_id, record_id) @@ -138,15 +137,13 @@ class RecordsController(rest.RestController): return self._view.show(context, request, record) @pecan.expose(template=None, content_type='application/json') + @utils.validate_uuid('zone_id', 'recordset_id', 'record_id') def delete_one(self, zone_id, recordset_id, record_id): """ Delete Record """ request = pecan.request response = pecan.response context = request.environ['context'] - # TODO(kiall): Validate we have a sane UUID for zone_id and - # recordset_id - record = central_api.delete_record(context, zone_id, recordset_id, record_id) diff --git a/designate/api/v2/controllers/recordsets.py b/designate/api/v2/controllers/recordsets.py index 7d42fd9f..5068e5d6 100644 --- a/designate/api/v2/controllers/recordsets.py +++ b/designate/api/v2/controllers/recordsets.py @@ -36,10 +36,9 @@ class RecordSetsController(rest.RestController): records = records.RecordsController() @pecan.expose(template='json:', content_type='application/json') + @utils.validate_uuid('zone_id', 'recordset_id') def get_one(self, zone_id, recordset_id): """ Get RecordSet """ - # TODO(kiall): Validate we have a sane UUID for zone_id and - # recordset_id request = pecan.request context = request.environ['context'] @@ -48,6 +47,7 @@ class RecordSetsController(rest.RestController): return self._view.show(context, request, recordset) @pecan.expose(template='json:', content_type='application/json') + @utils.validate_uuid('zone_id') def get_all(self, zone_id, **params): """ List RecordSets """ request = pecan.request @@ -69,6 +69,7 @@ class RecordSetsController(rest.RestController): return self._view.list(context, request, recordsets, [zone_id]) @pecan.expose(template='json:', content_type='application/json') + @utils.validate_uuid('zone_id') def post_all(self, zone_id): """ Create RecordSet """ request = pecan.request @@ -96,6 +97,7 @@ class RecordSetsController(rest.RestController): @pecan.expose(template='json:', content_type='application/json') @pecan.expose(template='json:', content_type='application/json-patch+json') + @utils.validate_uuid('zone_id', 'recordset_id') def patch_one(self, zone_id, recordset_id): """ Update RecordSet """ request = pecan.request @@ -103,9 +105,6 @@ class RecordSetsController(rest.RestController): body = request.body_dict response = pecan.response - # TODO(kiall): Validate we have a sane UUID for zone_id and - # recordset_id - # Fetch the existing recordset recordset = central_api.get_recordset(context, zone_id, recordset_id) @@ -129,15 +128,13 @@ class RecordSetsController(rest.RestController): return self._view.show(context, request, recordset) @pecan.expose(template=None, content_type='application/json') + @utils.validate_uuid('zone_id', 'recordset_id') def delete_one(self, zone_id, recordset_id): """ Delete RecordSet """ request = pecan.request response = pecan.response context = request.environ['context'] - # TODO(kiall): Validate we have a sane UUID for zone_id and - # recordset_id - central_api.delete_recordset(context, zone_id, recordset_id) response.status_int = 204 diff --git a/designate/api/v2/controllers/tlds.py b/designate/api/v2/controllers/tlds.py index ed66a9b5..8e04bd55 100644 --- a/designate/api/v2/controllers/tlds.py +++ b/designate/api/v2/controllers/tlds.py @@ -31,6 +31,7 @@ class TldsController(rest.RestController): SORT_KEYS = ['created_at', 'id', 'updated_at', 'name'] @pecan.expose(template='json:', content_type='application/json') + @utils.validate_uuid('tld_id') def get_one(self, tld_id): """ Get Tld """ @@ -84,6 +85,7 @@ class TldsController(rest.RestController): @pecan.expose(template='json:', content_type='application/json') @pecan.expose(template='json:', content_type='application/json-patch+json') + @utils.validate_uuid('tld_id') def patch_one(self, tld_id): """ Update Tld """ request = pecan.request @@ -113,6 +115,7 @@ class TldsController(rest.RestController): return self._view.show(context, request, tld) @pecan.expose(template=None, content_type='application/json') + @utils.validate_uuid('tld_id') def delete_one(self, tld_id): """ Delete Tld """ request = pecan.request diff --git a/designate/api/v2/controllers/zones.py b/designate/api/v2/controllers/zones.py index 9bb2f135..87b07a08 100644 --- a/designate/api/v2/controllers/zones.py +++ b/designate/api/v2/controllers/zones.py @@ -41,6 +41,7 @@ class ZonesController(rest.RestController): @pecan.expose(template=None, content_type='text/dns') @pecan.expose(template='json:', content_type='application/json') + @utils.validate_uuid('zone_id') def get_one(self, zone_id): """ Get Zone """ # TODO(kiall): Validate we have a sane UUID for zone_id @@ -179,6 +180,7 @@ class ZonesController(rest.RestController): @pecan.expose(template='json:', content_type='application/json') @pecan.expose(template='json:', content_type='application/json-patch+json') + @utils.validate_uuid('zone_id') def patch_one(self, zone_id): """ Update Zone """ # TODO(kiall): This needs cleanup to say the least.. @@ -226,6 +228,7 @@ class ZonesController(rest.RestController): return self._view.show(context, request, zone) @pecan.expose(template=None, content_type='application/json') + @utils.validate_uuid('zone_id') def delete_one(self, zone_id): """ Delete Zone """ request = pecan.request diff --git a/designate/exceptions.py b/designate/exceptions.py index 6571714b..183e531b 100644 --- a/designate/exceptions.py +++ b/designate/exceptions.py @@ -82,6 +82,10 @@ class BadRequest(Base): error_type = 'bad_request' +class InvalidUUID(BadRequest): + error_type = 'invalid_uuid' + + class NetworkEndpointNotFound(BadRequest): error_type = 'no_endpoint' error_code = 403 diff --git a/designate/openstack/common/uuidutils.py b/designate/openstack/common/uuidutils.py deleted file mode 100644 index 234b880c..00000000 --- a/designate/openstack/common/uuidutils.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (c) 2012 Intel Corporation. -# All Rights Reserved. -# -# 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. - -""" -UUID related utilities and helper functions. -""" - -import uuid - - -def generate_uuid(): - return str(uuid.uuid4()) - - -def is_uuid_like(val): - """Returns validation of a value as a UUID. - - For our purposes, a UUID is a canonical form string: - aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa - - """ - try: - return str(uuid.UUID(val)) == val - except (TypeError, ValueError, AttributeError): - return False diff --git a/designate/tests/test_api/test_v2/__init__.py b/designate/tests/test_api/test_v2/__init__.py index fe46c291..40ef657f 100644 --- a/designate/tests/test_api/test_v2/__init__.py +++ b/designate/tests/test_api/test_v2/__init__.py @@ -13,6 +13,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +import itertools from webtest import TestApp from designate.openstack.common import log as logging from designate.api import v2 as api_v2 @@ -23,6 +24,13 @@ from designate.tests.test_api import ApiTestCase LOG = logging.getLogger(__name__) +INVALID_ID = [ + '2fdadfb1-cf96-4259-ac6b-bb7b6d2ff98g', + '2fdadfb1cf964259ac6bbb7b6d2ff9GG', + '12345' +] + + class ApiV2TestCase(ApiTestCase): def setUp(self): super(ApiV2TestCase, self).setUp() @@ -53,6 +61,13 @@ class ApiV2TestCase(ApiTestCase): super(ApiV2TestCase, self).tearDown() + def _assert_invalid_uuid(self, method, url_format, *args, **kw): + count = url_format.count('%s') + for i in itertools.product(INVALID_ID, repeat=count): + response = method(url_format % i, status=400) + self.assertEqual(400, response.json['code']) + self.assertEqual('invalid_uuid', response.json['type']) + def _assert_paging(self, data, url, key=None, limit=5, sort_dir='asc', sort_key='created_at', marker=None, status=200): def _page(marker=None): diff --git a/designate/tests/test_api/test_v2/test_blacklists.py b/designate/tests/test_api/test_v2/test_blacklists.py index 44ec1331..39864dc6 100644 --- a/designate/tests/test_api/test_v2/test_blacklists.py +++ b/designate/tests/test_api/test_v2/test_blacklists.py @@ -69,6 +69,9 @@ class ApiV2BlacklistsTest(ApiV2TestCase): self.assertEqual(self.get_blacklist_fixture(0)['pattern'], response.json['blacklist']['pattern']) + def test_get_bkaclist_invalid_id(self): + self._assert_invalid_uuid(self.client.get, '/blacklists/%s') + def test_create_blacklist(self): self.policy({'create_blacklist': '@'}) fixture = self.get_blacklist_fixture(0) @@ -97,6 +100,9 @@ class ApiV2BlacklistsTest(ApiV2TestCase): self.client.delete('/blacklists/%s' % blacklist['id'], status=204) + def test_delete_bkaclist_invalid_id(self): + self._assert_invalid_uuid(self.client.delete, '/blacklists/%s') + def test_update_blacklist(self): blacklist = self.create_blacklist(fixture=0) self.policy({'update_blacklist': '@'}) @@ -123,3 +129,6 @@ class ApiV2BlacklistsTest(ApiV2TestCase): self.assertIsNotNone(response.json['blacklist']['updated_at']) self.assertEqual('prefix-%s' % blacklist['description'], response.json['blacklist']['description']) + + def test_update_bkaclist_invalid_id(self): + self._assert_invalid_uuid(self.client.patch_json, '/blacklists/%s') diff --git a/designate/tests/test_api/test_v2/test_recordsets.py b/designate/tests/test_api/test_v2/test_recordsets.py index a10e98c2..b3dad2e9 100644 --- a/designate/tests/test_api/test_v2/test_recordsets.py +++ b/designate/tests/test_api/test_v2/test_recordsets.py @@ -50,6 +50,9 @@ class ApiV2RecordSetsTest(ApiV2TestCase): for k in fixture: self.assertEqual(fixture[k], response.json['recordset'][k]) + def test_create_recordset_invalid_id(self): + self._assert_invalid_uuid(self.client.post, '/zones/%s/recordsets') + def test_create_recordset_validation(self): # NOTE: The schemas should be tested separatly to the API. So we # don't need to test every variation via the API itself. @@ -121,6 +124,9 @@ class ApiV2RecordSetsTest(ApiV2TestCase): url = '/zones/%s/recordsets' % self.domain['id'] self._assert_paging(data, url, key='recordsets') + def test_get_recordsets_invalid_id(self): + self._assert_invalid_uuid(self.client.get, '/zones/%s/recordsets') + @patch.object(central_service.Service, 'find_recordsets', side_effect=rpc_common.Timeout()) def test_get_recordsets_timeout(self, _): @@ -151,6 +157,9 @@ class ApiV2RecordSetsTest(ApiV2TestCase): self.assertEqual(recordset['name'], response.json['recordset']['name']) self.assertEqual(recordset['type'], response.json['recordset']['type']) + def test_get_recordset_invalid_id(self): + self._assert_invalid_uuid(self.client.get, '/zones/%s/recordsets/%s') + @patch.object(central_service.Service, 'get_recordset', side_effect=rpc_common.Timeout()) def test_get_recordset_timeout(self, _): @@ -167,9 +176,6 @@ class ApiV2RecordSetsTest(ApiV2TestCase): headers={'Accept': 'application/json'}, status=404) - def test_get_recordset_invalid_id(self): - self.skip('We don\'t guard against this in APIv2 yet') - def test_update_recordset(self): # Create a recordset recordset = self.create_recordset(self.domain) @@ -195,6 +201,10 @@ class ApiV2RecordSetsTest(ApiV2TestCase): self.assertIsNotNone(response.json['recordset']['updated_at']) self.assertEqual('Tester', response.json['recordset']['description']) + def test_update_recordset_invalid_id(self): + self._assert_invalid_uuid( + self.client.patch_json, '/zones/%s/recordsets/%s') + def test_update_recordset_validation(self): # NOTE: The schemas should be tested separatly to the API. So we # don't need to test every variation via the API itself. @@ -250,9 +260,6 @@ class ApiV2RecordSetsTest(ApiV2TestCase): % (self.domain['id'])) self.client.patch_json(url, body, status=404) - def test_update_recordset_invalid_id(self): - self.skip('We don\'t guard against this in APIv2 yet') - def test_delete_recordset(self): recordset = self.create_recordset(self.domain) @@ -276,4 +283,5 @@ class ApiV2RecordSetsTest(ApiV2TestCase): self.client.delete(url, status=404) def test_delete_recordset_invalid_id(self): - self.skip('We don\'t guard against this in APIv2 yet') + self._assert_invalid_uuid( + self.client.delete, '/zones/%s/recordsets/%s') diff --git a/designate/tests/test_api/test_v2/test_tlds.py b/designate/tests/test_api/test_v2/test_tlds.py index 45a9a5ac..c0e8fbe2 100644 --- a/designate/tests/test_api/test_v2/test_tlds.py +++ b/designate/tests/test_api/test_v2/test_tlds.py @@ -90,12 +90,18 @@ class ApiV2TldsTest(ApiV2TestCase): self.assertEqual(self.get_tld_fixture(0)['name'], response.json['tld']['name']) + def test_get_tld_invalid_id(self): + self._assert_invalid_uuid(self.client.get, '/tlds/%s') + def test_delete_tld(self): tld = self.create_tld(fixture=0) self.policy({'delete_tld': '@'}) self.client.delete('/tlds/%s' % tld['id'], status=204) + def test_delete_tld_invalid_id(self): + self._assert_invalid_uuid(self.client.delete, '/tlds/%s') + def test_update_tld(self): tld = self.create_tld(fixture=0) self.policy({'update_tld': '@'}) @@ -120,3 +126,6 @@ class ApiV2TldsTest(ApiV2TestCase): self.assertIsNotNone(response.json['tld']['updated_at']) self.assertEqual('prefix-%s' % tld['description'], response.json['tld']['description']) + + def test_update_tld_invalid_id(self): + self._assert_invalid_uuid(self.client.patch_json, '/tlds/%s') diff --git a/designate/tests/test_api/test_v2/test_zones.py b/designate/tests/test_api/test_v2/test_zones.py index 0d611681..0c951989 100644 --- a/designate/tests/test_api/test_v2/test_zones.py +++ b/designate/tests/test_api/test_v2/test_zones.py @@ -186,6 +186,9 @@ class ApiV2ZonesTest(ApiV2TestCase): self.assertEqual(zone['name'], response.json['zone']['name']) self.assertEqual(zone['email'], response.json['zone']['email']) + def test_get_zone_invalid_id(self): + self._assert_invalid_uuid(self.client.get, '/zones/%s') + @patch.object(central_service.Service, 'get_domain', side_effect=rpc_common.Timeout()) def test_get_zone_timeout(self, _): @@ -200,24 +203,6 @@ class ApiV2ZonesTest(ApiV2TestCase): headers={'Accept': 'application/json'}, status=404) - def test_get_zone_invalid_id(self): - self.skip('We don\'t guard against this in APIv2 yet') - - # The letter "G" is not valid in a UUID - self.client.get('/zones/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff9GG', - headers={'Accept': 'application/json'}, - status=404) - - # Badly formed UUID - self.client.get('/zones/2fdadfb1cf964259ac6bbb7b6d2ff9GG', - headers={'Accept': 'application/json'}, - status=404) - - # Integer - self.client.get('/zones/12345', - headers={'Accept': 'application/json'}, - status=404) - def test_update_zone(self): # Create a zone zone = self.create_domain() @@ -244,6 +229,9 @@ class ApiV2ZonesTest(ApiV2TestCase): self.assertEqual('prefix-%s' % zone['email'], response.json['zone']['email']) + def test_update_zone_invalid_id(self): + self._assert_invalid_uuid(self.client.patch_json, '/zones/%s') + def test_update_zone_validation(self): # NOTE: The schemas should be tested separatly to the API. So we # don't need to test every variation via the API itself. @@ -294,29 +282,14 @@ class ApiV2ZonesTest(ApiV2TestCase): self.client.patch_json('/zones/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980', body, status=404) - def test_update_zone_invalid_id(self): - self.skip('We don\'t guard against this in APIv2 yet') - - # Prepare an update body - body = {'zone': {'email': 'example@example.org'}} - - # The letter "G" is not valid in a UUID - self.client.patch_json('/zones/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff9GG', - body, status=404) - - # Badly formed UUID - self.client.patch_json('/zones/2fdadfb1cf964259ac6bbb7b6d2ff980', - body, status=404) - - # Integer - self.client.patch_json('/zones/12345', - body, status=404) - def test_delete_zone(self): zone = self.create_domain() self.client.delete('/zones/%s' % zone['id'], status=204) + def test_delete_zone_invalid_id(self): + self._assert_invalid_uuid(self.client.delete, '/zones/%s') + @patch.object(central_service.Service, 'delete_domain', side_effect=rpc_common.Timeout()) def test_delete_zone_timeout(self, _): @@ -329,20 +302,6 @@ class ApiV2ZonesTest(ApiV2TestCase): self.client.delete('/zones/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980', status=404) - def test_delete_zone_invalid_id(self): - self.skip('We don\'t guard against this in APIv2 yet') - - # The letter "G" is not valid in a UUID - self.client.delete('/zones/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff9GG', - status=404) - - # Badly formed UUID - self.client.delete('/zones/2fdadfb1cf964259ac6bbb7b6d2ff980', - status=404) - - # Integer - self.client.delete('/zones/12345', status=404) - # Zone import/export def test_missing_origin(self): self.client.post('/zones', diff --git a/designate/utils.py b/designate/utils.py index fa7571b7..df1d4cef 100644 --- a/designate/utils.py +++ b/designate/utils.py @@ -15,6 +15,8 @@ # under the License. import copy import json +import functools +import inspect import os import pkg_resources import uuid @@ -236,3 +238,38 @@ def deep_dict_merge(a, b): def generate_uuid(): return str(uuid.uuid4()) + + +def is_uuid_like(val): + """Returns validation of a value as a UUID. + + For our purposes, a UUID is a canonical form string: + aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa + + """ + try: + return str(uuid.UUID(val)) == val + except (TypeError, ValueError, AttributeError): + return False + + +def validate_uuid(*check): + """ + A wrapper to ensure that API controller methods arguments are valid UUID's. + + Usage: + @validate_uuid('zone_id') + def get_all(self, zone_id): + return {} + """ + def inner(f): + def wrapper(*args, **kwargs): + arg_spec = inspect.getargspec(f).args + for name in check: + pos = arg_spec.index(name) + if not is_uuid_like(args[pos]): + msg = 'Invalid UUID %s: %s' % (name, args[pos]) + raise exceptions.InvalidUUID(msg) + return f(*args, **kwargs) + return functools.wraps(f)(wrapper) + return inner diff --git a/openstack-common.conf b/openstack-common.conf index 9edf292b..5e5e5c4c 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -20,7 +20,6 @@ module=service module=strutils module=test module=timeutils -module=uuidutils # Modules needed for the deprecated oslo.wsgi we're still using module=xmlutils |