Repository: Kinto/kinto

Issue:
id and last_modified should be stripped before validating the JSON schema
Otherwise it obliges everyone to add `id` and `last_modified` to their JSON schema or use `additionalProperties : true`.
- http://spacetelescope.github.io/understanding-json-schema/reference/object.html#properties
- See #256  
- See #548 

``` diff
         try:
-            jsonschema.validate(new, schema)
+            stripped = copy.deepcopy(new)
+            stripped.pop(self.model.id_field, None)
+            stripped.pop(self.model.modified_field, None)
+            jsonschema.validate(stripped, schema)
```

id and last_modified should be stripped before validating the JSON schema
Otherwise it obliges everyone to add `id` and `last_modified` to their JSON schema or use `additionalProperties : true`.
- http://spacetelescope.github.io/understanding-json-schema/reference/object.html#properties
- See #256  
- See #548 

``` diff
         try:
-            jsonschema.validate(new, schema)
+            stripped = copy.deepcopy(new)
+            stripped.pop(self.model.id_field, None)
+            stripped.pop(self.model.modified_field, None)
+            jsonschema.validate(stripped, schema)
```



Discussion:

Resolving patch:
diff --git a/kinto/views/records.py b/kinto/views/records.py
--- a/kinto/views/records.py
+++ b/kinto/views/records.py
@@ -1,3 +1,5 @@
+import copy
+
 import jsonschema
 from cliquet import resource
 from cliquet.errors import raise_invalid
@@ -64,12 +66,17 @@ def process_record(self, new, old=None):
         collection_timestamp = self._collection[self.model.modified_field]
 
         try:
-            jsonschema.validate(new, schema)
-            new[self.schema_field] = collection_timestamp
+            stripped = copy.deepcopy(new)
+            stripped.pop(self.model.id_field, None)
+            stripped.pop(self.model.modified_field, None)
+            stripped.pop(self.model.permissions_field, None)
+            stripped.pop(self.schema_field, None)
+            jsonschema.validate(stripped, schema)
         except jsonschema_exceptions.ValidationError as e:
             field = e.path.pop() if e.path else e.validator_value.pop()
             raise_invalid(self.request, name=field, description=e.message)
 
+        new[self.schema_field] = collection_timestamp
         return new
 
     def collection_get(self):


Test patch:
diff --git a/kinto/tests/test_views_collections_schema.py b/kinto/tests/test_views_collections_schema.py
--- a/kinto/tests/test_views_collections_schema.py
+++ b/kinto/tests/test_views_collections_schema.py
@@ -177,3 +177,38 @@ def test_records_can_filtered_by_schema_version(self):
         resp = self.app.get(RECORDS_URL + '?min_schema=%s' % schema_version,
                             headers=self.headers)
         self.assertEqual(len(resp.json['data']), 1)
+
+
+class ExtraPropertiesValidationTest(BaseWebTestWithSchema, unittest.TestCase):
+    def setUp(self):
+        super(ExtraPropertiesValidationTest, self).setUp()
+        schema = SCHEMA.copy()
+        schema['additionalProperties'] = False
+        resp = self.app.put_json(COLLECTION_URL,
+                                 {'data': {'schema': schema}},
+                                 headers=self.headers)
+        self.collection = resp.json['data']
+
+    def test_record_can_be_validated_on_post(self):
+        self.app.post_json(RECORDS_URL,
+                           {'data': VALID_RECORD},
+                           headers=self.headers)
+
+    def test_record_can_be_validated_on_put(self):
+        record_id = '5443d83f-852a-481a-8e9d-5aa804b05b08'
+        self.app.put_json('%s/%s' % (RECORDS_URL, record_id),
+                          {'data': VALID_RECORD},
+                          headers=self.headers)
+
+    def test_records_are_validated_on_patch(self):
+        record_id = '5443d83f-852a-481a-8e9d-5aa804b05b08'
+        record_url = '%s/%s' % (RECORDS_URL, record_id)
+        resp = self.app.put_json(record_url,
+                                 {'data': VALID_RECORD},
+                                 headers=self.headers)
+        record = resp.json['data']
+        assert 'schema' in record
+        record['title'] = 'hey'
+        self.app.patch_json(record_url,
+                            {'data': record},
+                            headers=self.headers)