Repository: Kinto/kinto

Issue:
Crash in batch view deserialization
```
AttributeError: 'bytes' object has no attribute 'get'
  File "pyramid/tweens.py", line 39, in excview_tween
    response = handler(request)
  File "kinto/core/events.py", line 76, in tween
    response = handler(request)
  File "pyramid/router.py", line 156, in handle_request
    view_name
  File "pyramid/view.py", line 642, in _call_view
    response = view_callable(context, request)
  File "pyramid/config/views.py", line 181, in __call__
    return view(context, request)
  File "pyramid/viewderivers.py", line 390, in attr_view
    return view(context, request)
  File "pyramid/viewderivers.py", line 368, in predicate_wrapper
    return view(context, request)
  File "pyramid/viewderivers.py", line 439, in rendered_view
    result = view(context, request)
  File "pyramid/viewderivers.py", line 148, in _requestonly_view
    response = view(request)
  File "cornice/service.py", line 487, in wrapper
    validator(request, **args)
  File "cornice/validators/_colander.py", line 113, in validator
    deserialized = schema.deserialize(cstruct)
  File "colander/__init__.py", line 2073, in deserialize
    appstruct = self.typ.deserialize(self, cstruct)
  File "colander/__init__.py", line 724, in deserialize
    return self._impl(node, cstruct, callback)
  File "colander/__init__.py", line 683, in _impl
    sub_result = callback(subnode, subval)
  File "colander/__init__.py", line 722, in callback
    return subnode.deserialize(subcstruct)
  File "kinto/core/views/batch.py", line 68, in deserialize
    defaults = cstruct.get('defaults')
```
Crash in batch view deserialization
```
AttributeError: 'bytes' object has no attribute 'get'
  File "pyramid/tweens.py", line 39, in excview_tween
    response = handler(request)
  File "kinto/core/events.py", line 76, in tween
    response = handler(request)
  File "pyramid/router.py", line 156, in handle_request
    view_name
  File "pyramid/view.py", line 642, in _call_view
    response = view_callable(context, request)
  File "pyramid/config/views.py", line 181, in __call__
    return view(context, request)
  File "pyramid/viewderivers.py", line 390, in attr_view
    return view(context, request)
  File "pyramid/viewderivers.py", line 368, in predicate_wrapper
    return view(context, request)
  File "pyramid/viewderivers.py", line 439, in rendered_view
    result = view(context, request)
  File "pyramid/viewderivers.py", line 148, in _requestonly_view
    response = view(request)
  File "cornice/service.py", line 487, in wrapper
    validator(request, **args)
  File "cornice/validators/_colander.py", line 113, in validator
    deserialized = schema.deserialize(cstruct)
  File "colander/__init__.py", line 2073, in deserialize
    appstruct = self.typ.deserialize(self, cstruct)
  File "colander/__init__.py", line 724, in deserialize
    return self._impl(node, cstruct, callback)
  File "colander/__init__.py", line 683, in _impl
    sub_result = callback(subnode, subval)
  File "colander/__init__.py", line 722, in callback
    return subnode.deserialize(subcstruct)
  File "kinto/core/views/batch.py", line 68, in deserialize
    defaults = cstruct.get('defaults')
```


Discussion:
Do you have info on what type of request caused this?
Unfortunately I can't see the request body on the Sentry error :( 
Do you have info on what type of request caused this?
Unfortunately I can't see the request body on the Sentry error :( 

Resolving patch:
diff --git a/kinto/core/views/batch.py b/kinto/core/views/batch.py
--- a/kinto/core/views/batch.py
+++ b/kinto/core/views/batch.py
@@ -9,6 +9,7 @@
 from kinto.core import Service
 from kinto.core.errors import ErrorSchema
 from kinto.core.utils import merge_dicts, build_request, build_response
+from kinto.core.resource.viewset import CONTENT_TYPES
 
 
 subrequest_logger = logging.getLogger('subrequest.summary')
@@ -112,6 +113,7 @@ class ErrorResponseSchema(colander.MappingSchema):
 
 @batch.post(schema=BatchRequest,
             validators=(colander_validator,),
+            content_type=CONTENT_TYPES,
             permission=NO_PERMISSION_REQUIRED,
             tags=['Batch'], operation_id='batch',
             response_schemas=batch_responses)


Test patch:
diff --git a/tests/core/test_views_batch.py b/tests/core/test_views_batch.py
--- a/tests/core/test_views_batch.py
+++ b/tests/core/test_views_batch.py
@@ -105,6 +105,11 @@ def test_batch_validates_json(self):
                              headers={'Content-Type': 'application/json'})
         self.assertIn('Invalid JSON', resp.json['message'])
 
+    def test_batch_should_reject_unaccepted_request_content_type(self):
+        request = {'requests': [{'path': '/v0/mushrooms'}]}
+        self.app.post('/batch', request, status=415,
+                      headers={'Content-Type': 'text/plain'})
+
     def test_responses_are_resolved_with_api_with_prefix(self):
         request = {'path': '/'}
         body = {'requests': [request]}