Skip to content

Commit

Permalink
Merge pull request #271 from mozilla-services/ensure-json-objects
Browse files Browse the repository at this point in the history
Ensure passed JSON is an object.

r=@ametaireau
  • Loading branch information
Natim committed Feb 24, 2015
2 parents d062cc2 + f24f7e7 commit 448a9c5
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 16 deletions.
5 changes: 4 additions & 1 deletion cornice/errors.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
import simplejson as json
try:
import simplejson as json
except ImportError:
import json


class Errors(list):
Expand Down
4 changes: 2 additions & 2 deletions cornice/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ def wrapper(klass):
service_args[k] = kw[k]

# create service
service_name = (service_args.pop('name', None)
or klass.__name__.lower())
service_name = (service_args.pop('name', None) or
klass.__name__.lower())
service_name = prefix + service_name
service = services[service_name] = Service(name=service_name,
depth=2, **service_args)
Expand Down
12 changes: 11 additions & 1 deletion cornice/schemas.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
from pyramid.path import DottedNameResolver
import colander
import webob.multidict

from pyramid.path import DottedNameResolver
from cornice.util import to_list, extract_request_data


class SchemaError(Exception):
pass


class CorniceSchema(object):
"""Defines a cornice schema"""

Expand Down Expand Up @@ -91,6 +97,10 @@ def validate_colander_schema(schema, request):
"""Validates that the request is conform to the given schema"""
from colander import Invalid, Sequence, drop, null

if not isinstance(schema.colander_schema, colander.MappingSchema):
raise SchemaError('schema is not a MappingSchema: %s' %
type(schema.colander_schema))

def _validate_fields(location, data):
if location == 'body':
try:
Expand Down
3 changes: 1 addition & 2 deletions cornice/tests/test_cors.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pyramid import testing
from pyramid.exceptions import NotFound, HTTPBadRequest
from pyramid.response import Response
from pyramid.view import view_config

from webtest import TestApp

Expand Down Expand Up @@ -85,8 +86,6 @@ def post_some_bacon(request):
def put_some_bacon(request):
raise HTTPBadRequest()

from pyramid.view import view_config


@view_config(route_name='noservice')
def noservice(request):
Expand Down
19 changes: 17 additions & 2 deletions cornice/tests/test_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
# You can obtain one at http://mozilla.org/MPL/2.0/.
from cornice.errors import Errors
from cornice.tests.support import TestCase
from cornice.schemas import CorniceSchema, validate_colander_schema
from cornice.schemas import (
CorniceSchema, validate_colander_schema, SchemaError
)
from cornice.util import extract_json_data

try:
from colander import (
deferred,
Mapping,
MappingSchema,
SequenceSchema,
SchemaNode,
String,
Int,
Expand Down Expand Up @@ -44,6 +47,9 @@ class TestingSchema(MappingSchema):
bar = SchemaNode(String(), type='str', location="body")
baz = SchemaNode(String(), type='str', location="querystring")

class WrongSchema(SequenceSchema):
items = TestingSchema()

class InheritedSchema(TestingSchema):
foo = SchemaNode(Int(), missing=1)

Expand Down Expand Up @@ -160,7 +166,9 @@ def test_colander_inheritance(self):
self.assertEqual(len(base_schema.get_attributes()),
len(inherited_schema.get_attributes()))

foo_filter = lambda x: x.name == "foo"
def foo_filter(obj):
return obj.name == "foo"

base_foo = list(filter(foo_filter,
base_schema.get_attributes()))[0]
inherited_foo = list(filter(foo_filter,
Expand Down Expand Up @@ -308,6 +316,13 @@ def test_colander_schema_default_value(self):
# default value should be available
self.assertEqual(dummy_request.validated['bar'], 10)

def test_only_mapping_is_accepted(self):
schema = CorniceSchema.from_colander(WrongSchema)
dummy_request = get_mock_request('', {'foo': 'test',
'bar': 'test'})
self.assertRaises(SchemaError,
validate_colander_schema, schema, dummy_request)

def test_extra_params_qs(self):
schema = CorniceSchema.from_colander(QsSchema)
dummy_request = get_mock_request('', {'foo': 'test',
Expand Down
14 changes: 10 additions & 4 deletions cornice/tests/test_service.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
from cornice.resource import resource
from cornice.service import (Service, get_services, clear_services,
decorate_view, _UnboundView)
from cornice.tests import validationapp
from cornice.tests.support import TestCase, DummyRequest
from cornice.util import func_name

_validator = lambda req: True
_validator2 = lambda req: True
_stub = lambda req: None

def _validator(req):
return True

from cornice.resource import resource

def _validator2(req):
return True


def _stub(req):
return None


@resource(collection_path='/pets', path='/pets/{id}')
Expand Down
13 changes: 12 additions & 1 deletion cornice/tests/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,16 @@ def test_invalid_json(self):
error_description = response.json['errors'][0]['description']
self.assertIn('Invalid JSON', error_description)

def test_json_text(self):
app = self.make_ordinary_app()
response = app.post('/foobar?yeah=test',
'"invalid json input"',
headers={'content-type': 'application/json'},
status=400)
self.assertEqual(response.json['status'], 'error')
error_description = response.json['errors'][0]['description']
self.assertIn('Should be a JSON object', error_description)

def test_www_form_urlencoded(self):
app = self.make_ordinary_app()
response = app.post('/foobar?yeah=test', {
Expand All @@ -348,7 +358,8 @@ def test_deserializer_from_view_config(self):
self.assertEqual(response.json['test'], 'succeeded')

def test_view_config_has_priority_over_global_config(self):
low_priority_deserializer = lambda request: "we don't want this"
def low_priority_deserializer(request):
return "we don't want this"
app = self.make_app_with_deserializer(low_priority_deserializer)
response = app.post('/custom_deserializer?yeah=test',
"hello,open,yeah",
Expand Down
12 changes: 9 additions & 3 deletions cornice/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,13 @@ def extract_json_data(request):
if request.body:
try:
body = simplejson.loads(request.body)
return body
if isinstance(body, dict):
return body
request.errors.add(
'body', None,
"Invalid JSON: Should be a JSON object, got %s" % body
)
return {}
except ValueError as e:
request.errors.add(
'body', None,
Expand All @@ -151,8 +157,8 @@ def extract_request_data(request):
registry = request.registry
if hasattr(request, 'deserializer'):
body = request.deserializer(request)
elif (hasattr(registry, 'cornice_deserializers')
and content_type in registry.cornice_deserializers):
elif (hasattr(registry, 'cornice_deserializers') and
content_type in registry.cornice_deserializers):
deserializer = registry.cornice_deserializers[content_type]
body = deserializer(request)
# otherwise, don't block but it will be an empty body, decode
Expand Down

0 comments on commit 448a9c5

Please sign in to comment.