From adc7693eeb51eca1cc376e26bdee06775d9fde31 Mon Sep 17 00:00:00 2001 From: Andrew Melton Date: Tue, 26 Feb 2013 16:42:32 -0500 Subject: [PATCH] Validating uuids on api calls --- stacktach/dbapi.py | 6 +- stacktach/stacky_server.py | 41 ++++++++++--- stacktach/utils.py | 18 ++++++ tests/unit/test_dbapi.py | 11 ++++ tests/unit/test_stacktach_utils.py | 45 +++++++++++++- tests/unit/test_stacky_server.py | 99 ++++++++++++++++++++++++++++-- tests/unit/utils.py | 14 ++--- 7 files changed, 214 insertions(+), 20 deletions(-) diff --git a/stacktach/dbapi.py b/stacktach/dbapi.py index 1b2cbb1..8d22b82 100644 --- a/stacktach/dbapi.py +++ b/stacktach/dbapi.py @@ -103,7 +103,11 @@ def _check_has_field(klass, field_name): def _get_filter_args(klass, request): filter_args = {} if 'instance' in request.GET: - filter_args['instance'] = request.GET['instance'] + uuid = request.GET['instance'] + filter_args['instance'] = uuid + if not utils.is_uuid_like(uuid): + msg = "%s is not uuid-like" % uuid + raise BadRequestException(msg) for (key, value) in request.GET.items(): diff --git a/stacktach/stacky_server.py b/stacktach/stacky_server.py index 2f64f52..e484ab1 100644 --- a/stacktach/stacky_server.py +++ b/stacktach/stacky_server.py @@ -8,7 +8,7 @@ from django.shortcuts import get_object_or_404 import datetime_to_decimal as dt import models -import views +import utils SECS_PER_HOUR = 60 * 60 SECS_PER_DAY = SECS_PER_HOUR * 24 @@ -76,6 +76,11 @@ def rsp(data, status=200): status=status) +def error_response(status, type, message): + results = [["Error", "Message"], [type, message]] + return rsp(results, status) + + def do_deployments(request): deployments = get_deployments() results = [["#", "Name"]] @@ -102,6 +107,10 @@ def do_hosts(request): def do_uuid(request): uuid = str(request.GET['uuid']) + if not utils.is_uuid_like(uuid): + msg = "%s is not uuid-like" % uuid + return error_response(400, 'Bad Request', msg) + related = models.RawData.objects.select_related().filter(instance=uuid)\ .order_by('when') results = [["#", "?", "When", "Deployment", "Event", "Host", "State", @@ -116,6 +125,10 @@ def do_uuid(request): def do_timings_uuid(request): uuid = request.GET['uuid'] + if not utils.is_uuid_like(uuid): + msg = "%s is not uuid-like" % uuid + return error_response(400, 'Bad Request', msg) + return rsp(get_timings_for_uuid(uuid)) @@ -167,6 +180,10 @@ def do_summary(request): def do_request(request): request_id = request.GET['request_id'] + if not utils.is_request_id_like(request_id): + msg = "%s is not request-id-like" % request_id + return error_response(400, 'Bad Request', msg) + events = models.RawData.objects.filter(request_id=request_id) \ .order_by('when') results = [["#", "?", "When", "Deployment", "Event", "Host", @@ -270,10 +287,8 @@ def do_watch(request, deployment_id): def do_kpi(request, tenant_id=None): if tenant_id: if models.RawData.objects.filter(tenant=tenant_id).count() == 0: - results = [["Error", "Message"]] message = "Could not find raws for tenant %s" % tenant_id - results.append(["NotFound", message]) - return rsp(results, 404) + return error_response(404, 'Not Found', message) yesterday = datetime.datetime.utcnow() - datetime.timedelta(days=1) yesterday = dt.dt_to_decimal(yesterday) @@ -297,7 +312,11 @@ def do_list_usage_launches(request): filter_args = {} if 'instance' in request.GET: - filter_args['instance'] = request.GET['instance'] + uuid = request.GET['instance'] + if not utils.is_uuid_like(uuid): + msg = "%s is not uuid-like" % uuid + return error_response(400, 'Bad Request', msg) + filter_args['instance'] = uuid if len(filter_args) > 0: launches = models.InstanceUsage.objects.filter(**filter_args) @@ -319,7 +338,11 @@ def do_list_usage_deletes(request): filter_args = {} if 'instance' in request.GET: - filter_args['instance'] = request.GET['instance'] + uuid = request.GET['instance'] + if not utils.is_uuid_like(uuid): + msg = "%s is not uuid-like" % uuid + return error_response(400, 'Bad Request', msg) + filter_args['instance'] = uuid if len(filter_args) > 0: deletes = models.InstanceDeletes.objects.filter(**filter_args) @@ -344,7 +367,11 @@ def do_list_usage_exists(request): filter_args = {} if 'instance' in request.GET: - filter_args['instance'] = request.GET['instance'] + uuid = request.GET['instance'] + if not utils.is_uuid_like(uuid): + msg = "%s is not uuid-like" % uuid + return error_response(400, 'Bad Request', msg) + filter_args['instance'] = uuid if len(filter_args) > 0: exists = models.InstanceExists.objects.filter(**filter_args) diff --git a/stacktach/utils.py b/stacktach/utils.py index e4f05a4..7145971 100644 --- a/stacktach/utils.py +++ b/stacktach/utils.py @@ -1,7 +1,9 @@ import datetime +import uuid from stacktach import datetime_to_decimal as dt + def str_time_to_unix(when): if 'T' in when: try: @@ -23,3 +25,19 @@ def str_time_to_unix(when): print "BAD DATE: ", e return dt.dt_to_decimal(when) + + +def is_uuid_like(val): + try: + converted = str(uuid.UUID(val)) + if '-' not in val: + converted = converted.replace('-', '') + return converted == val + except (TypeError, ValueError, AttributeError): + return False + + +def is_request_id_like(val): + if val[0:4] == 'req-': + val = val[4:] + return is_uuid_like(val) \ No newline at end of file diff --git a/tests/unit/test_dbapi.py b/tests/unit/test_dbapi.py index 3b5df59..5c891cf 100644 --- a/tests/unit/test_dbapi.py +++ b/tests/unit/test_dbapi.py @@ -47,6 +47,17 @@ class DBAPITestCase(unittest.TestCase): self.assertEquals(filter_args.get('launched_at__lte'), end_decimal) + def test_get_filter_args_bad_uuid(self): + fake_model = self.make_fake_model() + fake_request = self.mox.CreateMockAnything() + fake_request.GET = {'instance': 'obviouslybaduuid'} + self.mox.ReplayAll() + + self.assertRaises(dbapi.BadRequestException, dbapi._get_filter_args, + fake_model, fake_request) + + self.mox.VerifyAll() + def test_get_filter_args_bad_min_value(self): fake_request = self.mox.CreateMockAnything() fake_request.GET = {'launched_at_min': 'obviouslybaddatetime'} diff --git a/tests/unit/test_stacktach_utils.py b/tests/unit/test_stacktach_utils.py index 54006a2..765f37f 100644 --- a/tests/unit/test_stacktach_utils.py +++ b/tests/unit/test_stacktach_utils.py @@ -1 +1,44 @@ -__author__ = 'andrewmelton' +import unittest + +import mox + +from stacktach import utils as stacktach_utils +from utils import INSTANCE_ID_1 +from utils import MESSAGE_ID_1 +from utils import REQUEST_ID_1 + + +class StacktachUtilsTestCase(unittest.TestCase): + def setUp(self): + self.mox = mox.Mox() + + def tearDown(self): + self.mox.UnsetStubs() + + def test_is_uuid_like(self): + uuid = INSTANCE_ID_1 + self.assertTrue(stacktach_utils.is_uuid_like(uuid)) + + def test_is_uuid_like_no_dashes(self): + uuid = "08f685d963524dbc827196cc54bf14cd" + self.assertTrue(stacktach_utils.is_uuid_like(uuid)) + + def test_is_uuid_like_invalid(self): + uuid = "$-^&#$" + self.assertFalse(stacktach_utils.is_uuid_like(uuid)) + + def test_is_request_id_like_with_uuid(self): + uuid = MESSAGE_ID_1 + self.assertTrue(stacktach_utils.is_request_id_like(uuid)) + + def test_is_message_id_like_with_req_uuid(self): + uuid = REQUEST_ID_1 + self.assertTrue(stacktach_utils.is_request_id_like(uuid)) + + def test_is_message_id_like_invalid_req(self): + uuid = "req-$-^&#$" + self.assertFalse(stacktach_utils.is_request_id_like(uuid)) + + def test_is_message_id_like_invalid(self): + uuid = "$-^&#$" + self.assertFalse(stacktach_utils.is_request_id_like(uuid)) \ No newline at end of file diff --git a/tests/unit/test_stacky_server.py b/tests/unit/test_stacky_server.py index 8e094c3..015da55 100644 --- a/tests/unit/test_stacky_server.py +++ b/tests/unit/test_stacky_server.py @@ -10,6 +10,7 @@ from stacktach import stacky_server import utils from utils import INSTANCE_ID_1 from utils import INSTANCE_ID_2 +from utils import REQUEST_ID_1 class StackyServerTestCase(unittest.TestCase): @@ -58,7 +59,7 @@ class StackyServerTestCase(unittest.TestCase): raw.publisher = "api.example.com" raw.service = 'api' raw.host = 'example.com' - raw.request_id = 'req-1' + raw.request_id = REQUEST_ID_1 raw.json = '{"key": "value"}' return raw @@ -250,6 +251,36 @@ class StackyServerTestCase(unittest.TestCase): self.assertEqual(json_resp[1], body) self.mox.VerifyAll() + def test_do_uuid_bad_uuid(self): + fake_request = self.mox.CreateMockAnything() + fake_request.GET = {'uuid': "obviouslybaduuid"} + self.mox.ReplayAll() + + resp = stacky_server.do_uuid(fake_request) + + self.assertEqual(resp.status_code, 400) + resp_json = json.loads(resp.content) + self.assertEqual(len(resp_json), 2) + self.assertEqual(resp_json[0], ['Error', 'Message']) + msg = 'obviouslybaduuid is not uuid-like' + self.assertEqual(resp_json[1], ['Bad Request', msg]) + self.mox.VerifyAll() + + def test_do_timings_uuid_bad_uuid(self): + fake_request = self.mox.CreateMockAnything() + fake_request.GET = {'uuid': "obviouslybaduuid"} + self.mox.ReplayAll() + + resp = stacky_server.do_timings_uuid(fake_request) + + self.assertEqual(resp.status_code, 400) + resp_json = json.loads(resp.content) + self.assertEqual(len(resp_json), 2) + self.assertEqual(resp_json[0], ['Error', 'Message']) + msg = 'obviouslybaduuid is not uuid-like' + self.assertEqual(resp_json[1], ['Bad Request', msg]) + self.mox.VerifyAll() + def test_do_timings(self): fake_request = self.mox.CreateMockAnything() fake_request.GET = {'name': 'test.event'} @@ -314,10 +345,10 @@ class StackyServerTestCase(unittest.TestCase): def test_do_request(self): fake_request = self.mox.CreateMockAnything() - fake_request.GET = {'request_id': 'req-1'} + fake_request.GET = {'request_id': REQUEST_ID_1} raw = self._create_raw() results = self.mox.CreateMockAnything() - models.RawData.objects.filter(request_id='req-1').AndReturn(results) + models.RawData.objects.filter(request_id=REQUEST_ID_1).AndReturn(results) results.order_by('when').AndReturn(results) results.__iter__().AndReturn([raw].__iter__()) self.mox.ReplayAll() @@ -341,6 +372,21 @@ class StackyServerTestCase(unittest.TestCase): self.assertEqual(json_resp[1][8], None) self.mox.VerifyAll() + def test_do_request_bad_request_id(self): + fake_request = self.mox.CreateMockAnything() + fake_request.GET = {'request_id': "obviouslybaduuid"} + self.mox.ReplayAll() + + resp = stacky_server.do_request(fake_request) + + self.assertEqual(resp.status_code, 400) + resp_json = json.loads(resp.content) + self.assertEqual(len(resp_json), 2) + self.assertEqual(resp_json[0], ['Error', 'Message']) + msg = 'obviouslybaduuid is not request-id-like' + self.assertEqual(resp_json[1], ['Bad Request', msg]) + self.mox.VerifyAll() + def _assert_on_show(self, values, raw): self.assertEqual(len(values), 12) self.assertEqual(values[0], ["Key", "Value"]) @@ -587,7 +633,7 @@ class StackyServerTestCase(unittest.TestCase): self.assertEqual(len(body), 2) self.assertEqual(body[0], ['Error', 'Message']) msg = 'Could not find raws for tenant 55555' - self.assertEqual(body[1], ['NotFound', msg]) + self.assertEqual(body[1], ['Not Found', msg]) self.mox.VerifyAll() @@ -642,6 +688,21 @@ class StackyServerTestCase(unittest.TestCase): self.mox.VerifyAll() + def test_do_list_usage_launches_bad_instance(self): + fake_request = self.mox.CreateMockAnything() + fake_request.GET = {'instance': "obviouslybaduuid"} + self.mox.ReplayAll() + + resp = stacky_server.do_list_usage_launches(fake_request) + + self.assertEqual(resp.status_code, 400) + resp_json = json.loads(resp.content) + self.assertEqual(len(resp_json), 2) + self.assertEqual(resp_json[0], ['Error', 'Message']) + msg = 'obviouslybaduuid is not uuid-like' + self.assertEqual(resp_json[1], ['Bad Request', msg]) + self.mox.VerifyAll() + def test_do_list_usage_deletes(self): fake_request = self.mox.CreateMockAnything() fake_request.GET = {} @@ -693,6 +754,21 @@ class StackyServerTestCase(unittest.TestCase): self.assertEqual(resp_json[1][2], str(delete_time_str)) self.mox.VerifyAll() + def test_do_list_usage_deletes_bad_instance(self): + fake_request = self.mox.CreateMockAnything() + fake_request.GET = {'instance': "obviouslybaduuid"} + self.mox.ReplayAll() + + resp = stacky_server.do_list_usage_deletes(fake_request) + + self.assertEqual(resp.status_code, 400) + resp_json = json.loads(resp.content) + self.assertEqual(len(resp_json), 2) + self.assertEqual(resp_json[0], ['Error', 'Message']) + msg = 'obviouslybaduuid is not uuid-like' + self.assertEqual(resp_json[1], ['Bad Request', msg]) + self.mox.VerifyAll() + def test_do_list_usage_exists(self): fake_request = self.mox.CreateMockAnything() fake_request.GET = {} @@ -752,3 +828,18 @@ class StackyServerTestCase(unittest.TestCase): self.assertEqual(resp_json[1][2], str(delete_time_str)) self.mox.VerifyAll() + def test_do_list_usage_exists_bad_instance(self): + fake_request = self.mox.CreateMockAnything() + fake_request.GET = {'instance': "obviouslybaduuid"} + self.mox.ReplayAll() + + resp = stacky_server.do_list_usage_exists(fake_request) + + self.assertEqual(resp.status_code, 400) + resp_json = json.loads(resp.content) + self.assertEqual(len(resp_json), 2) + self.assertEqual(resp_json[0], ['Error', 'Message']) + msg = 'obviouslybaduuid is not uuid-like' + self.assertEqual(resp_json[1], ['Bad Request', msg]) + self.mox.VerifyAll() + diff --git a/tests/unit/utils.py b/tests/unit/utils.py index 29591d2..dfc90bf 100644 --- a/tests/unit/utils.py +++ b/tests/unit/utils.py @@ -7,15 +7,15 @@ TENANT_ID_1 = 'testtenantid1' from stacktach import datetime_to_decimal as dt -INSTANCE_ID_1 = 'testinstanceid1' -INSTANCE_ID_2 = 'testinstanceid2' +INSTANCE_ID_1 = "08f685d9-6352-4dbc-8271-96cc54bf14cd" +INSTANCE_ID_2 = "515adf96-41d3-b86d-5467-e584edc61dab" -MESSAGE_ID_1 = 'testmessageid1' -MESSAGE_ID_2 = 'testmessageid2' +MESSAGE_ID_1 = "7f28f81b-29a2-43f2-9ba1-ccb3e53ab6c8" +MESSAGE_ID_2 = "4d596126-0f04-4329-865f-7b9a7bd69bcf" -REQUEST_ID_1 = 'testrequestid1' -REQUEST_ID_2 = 'testrequestid2' -REQUEST_ID_3 = 'testrequestid3' +REQUEST_ID_1 = 'req-611a4d70-9e47-4b27-a95e-27996cc40c06' +REQUEST_ID_2 = 'req-a951dec0-52ee-425d-9f56-d68bd1ad00ac' +REQUEST_ID_3 = 'req-039a33f7-5849-4406-8166-4db8cd085f52' def decimal_utc(t = datetime.datetime.utcnow()):