diff --git a/designate/api/middleware.py b/designate/api/middleware.py index 85c02128f..dc05c881b 100644 --- a/designate/api/middleware.py +++ b/designate/api/middleware.py @@ -26,6 +26,7 @@ from oslo_utils import strutils from designate import exceptions from designate import notifications from designate import context +from designate import objects from designate.i18n import _LI from designate.i18n import _LW from designate.i18n import _LE @@ -268,3 +269,66 @@ class FaultWrapperMiddleware(base.Middleware): return flask.Response(status=status, headers=headers, response=json.dumps(response)) + + +class ValidationErrorMiddleware(base.Middleware): + + def __init__(self, application): + super(ValidationErrorMiddleware, self).__init__(application) + + LOG.info(_LI('Starting designate validation middleware')) + + @webob.dec.wsgify + def __call__(self, request): + try: + return request.get_response(self.application) + except exceptions.InvalidObject as e: + # Allow current views validation to pass through to FaultWapper + if not isinstance(e.errors, objects.ValidationErrorList): + raise + return self._handle_errors(request, e) + + def _handle_errors(self, request, exception): + + response = {} + + headers = [ + ('Content-Type', 'application/json'), + ] + + url = getattr(request, 'url', None) + + response['code'] = exception.error_code + + response['type'] = exception.error_type or 'unknown' + + response['errors'] = list() + + for error in exception.errors: + response['errors'].append(error.to_dict()) + + # Return the new response + if 'context' in request.environ: + response['request_id'] = request.environ['context'].request_id + + notifications.send_api_fault(request.environ['context'], url, + response['code'], exception) + else: + # TODO(ekarlso): Remove after verifying that there's actually a + # context always set + LOG.error(_LE('Missing context in request, please check.')) + + return flask.Response(status=exception.error_code, headers=headers, + response=json.dumps(response)) + + +class APIv1ValidationErrorMiddleware(ValidationErrorMiddleware): + def __init__(self, application): + super(APIv1ValidationErrorMiddleware, self).__init__(application) + self.api_version = 'API_v1' + + +class APIv2ValidationErrorMiddleware(ValidationErrorMiddleware): + def __init__(self, application): + super(APIv2ValidationErrorMiddleware, self).__init__(application) + self.api_version = 'API_v2' diff --git a/designate/objects/validation_error.py b/designate/objects/validation_error.py index fcf2aee5f..c9a479bac 100644 --- a/designate/objects/validation_error.py +++ b/designate/objects/validation_error.py @@ -30,8 +30,8 @@ class ValidationError(base.DesignateObject): """ e = cls() - e.relative_path = ".".join([str(x) for x in js_error.relative_path]) - e.absolute_path = ".".join([str(x) for x in js_error.absolute_path]) + e.relative_path = js_error.relative_path + e.absolute_path = js_error.absolute_path e.message = js_error.message e.validator = js_error.validator e.validator_value = js_error.validator_value diff --git a/designate/tests/test_api/test_v1/__init__.py b/designate/tests/test_api/test_v1/__init__.py index f2f1b8d5e..bad246ecd 100644 --- a/designate/tests/test_api/test_v1/__init__.py +++ b/designate/tests/test_api/test_v1/__init__.py @@ -42,6 +42,10 @@ class ApiV1Test(ApiTestCase): self.app.wsgi_app = middleware.FaultWrapperMiddleware( self.app.wsgi_app) + # Inject the ValidationError middleware + self.app.wsgi_app = middleware.APIv1ValidationErrorMiddleware( + self.app.wsgi_app) + # Inject the TestAuth middleware self.app.wsgi_app = middleware.TestContextMiddleware( self.app.wsgi_app, self.admin_context.tenant, diff --git a/designate/tests/test_api/test_v2/__init__.py b/designate/tests/test_api/test_v2/__init__.py index a39f7b299..764c9feb1 100644 --- a/designate/tests/test_api/test_v2/__init__.py +++ b/designate/tests/test_api/test_v2/__init__.py @@ -49,6 +49,9 @@ class ApiV2TestCase(ApiTestCase): # Inject the FaultWrapper middleware self.app = middleware.FaultWrapperMiddleware(self.app) + # Inject the ValidationError middleware + self.app = middleware.APIv2ValidationErrorMiddleware(self.app) + # Inject the TestContext middleware self.app = middleware.TestContextMiddleware( self.app, self.admin_context.tenant, diff --git a/designate/tests/test_objects/test_base.py b/designate/tests/test_objects/test_base.py index 6080fac16..9a7269002 100644 --- a/designate/tests/test_objects/test_base.py +++ b/designate/tests/test_objects/test_base.py @@ -381,8 +381,9 @@ class DesignateObjectTest(tests.TestCase): self.assertEqual('format', error.validator) # Ensure the nested ID field has triggered the failure. - self.assertEqual('nested.id', error.absolute_path) - self.assertEqual('nested.id', error.relative_path) + # For some reason testtools turns lists into deques :/ + self.assertEqual(list(error.absolute_path), ['nested', 'id']) + self.assertEqual(list(error.relative_path), ['nested', 'id']) # Set the Nested ID field to a valid value obj.nested.id = 'ffded5c4-e4f6-4e02-a175-48e13c5c12a0' diff --git a/etc/designate/api-paste.ini b/etc/designate/api-paste.ini index 159d1f58d..8e8f62fdb 100644 --- a/etc/designate/api-paste.ini +++ b/etc/designate/api-paste.ini @@ -9,16 +9,16 @@ paste.app_factory = designate.api.versions:factory [composite:osapi_dns_v1] use = call:designate.api.middleware:auth_pipeline_factory -noauth = request_id noauthcontext maintenance faultwrapper normalizeuri osapi_dns_app_v1 -keystone = request_id authtoken keystonecontext maintenance faultwrapper normalizeuri osapi_dns_app_v1 +noauth = request_id noauthcontext maintenance validation_API_v1 faultwrapper normalizeuri osapi_dns_app_v1 +keystone = request_id authtoken keystonecontext maintenance validation_API_v1 faultwrapper normalizeuri osapi_dns_app_v1 [app:osapi_dns_app_v1] paste.app_factory = designate.api.v1:factory [composite:osapi_dns_v2] use = call:designate.api.middleware:auth_pipeline_factory -noauth = request_id faultwrapper noauthcontext maintenance normalizeuri osapi_dns_app_v2 -keystone = request_id faultwrapper authtoken keystonecontext maintenance normalizeuri osapi_dns_app_v2 +noauth = request_id faultwrapper validation_API_v2 noauthcontext maintenance normalizeuri osapi_dns_app_v2 +keystone = request_id faultwrapper validation_API_v2 authtoken keystonecontext maintenance normalizeuri osapi_dns_app_v2 [app:osapi_dns_app_v2] paste.app_factory = designate.api.v2:factory @@ -43,3 +43,9 @@ paste.filter_factory = designate.api.middleware:NormalizeURIMiddleware.factory [filter:faultwrapper] paste.filter_factory = designate.api.middleware:FaultWrapperMiddleware.factory + +[filter:validation_API_v1] +paste.filter_factory = designate.api.middleware:APIv1ValidationErrorMiddleware.factory + +[filter:validation_API_v2] +paste.filter_factory = designate.api.middleware:APIv2ValidationErrorMiddleware.factory