Repository: Kinto/kinto

Issue:
_since overflow error: long too big to convert
When since is too big with the Postgresql backend it fails with an OverflowError: https://sentry.prod.mozaws.net/operations/kinto-prod-1/issues/382250/


Discussion:
You can see that we received a request with ` ?_since=1.0979741403836703e+248` :) 

Resolving patch:
diff --git a/kinto/core/resource/schema.py b/kinto/core/resource/schema.py
--- a/kinto/core/resource/schema.py
+++ b/kinto/core/resource/schema.py
@@ -1,3 +1,4 @@
+from __future__ import division
 import warnings
 
 import colander
@@ -6,6 +7,10 @@
                                FieldList, TimeStamp, URL)
 from kinto.core.utils import native_value
 
+POSTGRESQL_MAX_INTEGER_VALUE = 2**64 // 2
+
+positive_big_integer = colander.Range(min=0, max=POSTGRESQL_MAX_INTEGER_VALUE)
+
 
 class TimeStamp(TimeStamp):
     """This schema is deprecated, you shoud use `kinto.core.schema.TimeStamp` instead."""
@@ -210,14 +215,14 @@ def deserialize(self, cstruct=colander.null):
 class CollectionQuerySchema(QuerySchema):
     """Querystring schema used with collections."""
 
-    _limit = QueryField(colander.Integer())
+    _limit = QueryField(colander.Integer(), validator=positive_big_integer)
     _sort = FieldList()
     _token = QueryField(colander.String())
-    _since = QueryField(colander.Integer())
-    _to = QueryField(colander.Integer())
-    _before = QueryField(colander.Integer())
+    _since = QueryField(colander.Integer(), validator=positive_big_integer)
+    _to = QueryField(colander.Integer(), validator=positive_big_integer)
+    _before = QueryField(colander.Integer(), validator=positive_big_integer)
     id = QueryField(colander.String())
-    last_modified = QueryField(colander.Integer())
+    last_modified = QueryField(colander.Integer(), validator=positive_big_integer)
 
 
 class RecordGetQuerySchema(QuerySchema):


Test patch:
diff --git a/tests/core/resource/test_schema.py b/tests/core/resource/test_schema.py
--- a/tests/core/resource/test_schema.py
+++ b/tests/core/resource/test_schema.py
@@ -288,3 +288,82 @@ def test_body_supports_binding_after_other_binds(self):
         deserialized = bound.deserialize({})
         self.assertEquals(deserialized['querystring'], {'foo': 'bar'})
         self.assertEquals(deserialized['body'], {'foo': 'beer'})
+
+
+class CollectionQuerySchemaTest(unittest.TestCase):
+
+    def setUp(self):
+        self.schema = schema.CollectionQuerySchema()
+        self.querystring = {
+            '_limit': '2',
+            '_sort': 'toto,tata',
+            '_token': 'abc',
+            '_since': '1234',
+            '_to': '7890',
+            '_before': '4567',
+            'id': 'toot',
+            'last_modified': '9874'
+        }
+
+    def test_decode_valid_querystring(self):
+        deserialized = self.schema.deserialize(self.querystring)
+        self.assertEquals(deserialized, {
+            '_limit': 2,
+            '_sort': ['toto', 'tata'],
+            '_token': 'abc',
+            '_since': 1234,
+            '_to': 7890,
+            '_before': 4567,
+            'id': 'toot',
+            'last_modified': 9874
+        })
+
+    def test_raises_invalid_for_to_big_integer_in_limit(self):
+        querystring = self.querystring.copy()
+        querystring['_limit'] = schema.POSTGRESQL_MAX_INTEGER_VALUE + 1
+        self.assertRaises(colander.Invalid, self.schema.deserialize, querystring)
+
+    def test_raises_invalid_for_to_big_integer_in_since(self):
+        querystring = self.querystring.copy()
+        querystring['_since'] = schema.POSTGRESQL_MAX_INTEGER_VALUE + 1
+        self.assertRaises(colander.Invalid, self.schema.deserialize, querystring)
+
+    def test_raises_invalid_for_to_big_integer_in_to(self):
+        querystring = self.querystring.copy()
+        querystring['_to'] = schema.POSTGRESQL_MAX_INTEGER_VALUE + 1
+        self.assertRaises(colander.Invalid, self.schema.deserialize, querystring)
+
+    def test_raises_invalid_for_to_big_integer_in_before(self):
+        querystring = self.querystring.copy()
+        querystring['_before'] = schema.POSTGRESQL_MAX_INTEGER_VALUE + 1
+        self.assertRaises(colander.Invalid, self.schema.deserialize, querystring)
+
+    def test_raises_invalid_for_to_big_integer_in_last_modified(self):
+        querystring = self.querystring.copy()
+        querystring['last_modified'] = schema.POSTGRESQL_MAX_INTEGER_VALUE + 1
+        self.assertRaises(colander.Invalid, self.schema.deserialize, querystring)
+
+    def test_raises_invalid_for_negative_integer_in_limit(self):
+        querystring = self.querystring.copy()
+        querystring['_limit'] = -1
+        self.assertRaises(colander.Invalid, self.schema.deserialize, querystring)
+
+    def test_raises_invalid_for_negative_integer_in_since(self):
+        querystring = self.querystring.copy()
+        querystring['_since'] = -1
+        self.assertRaises(colander.Invalid, self.schema.deserialize, querystring)
+
+    def test_raises_invalid_for_negative_integer_in_to(self):
+        querystring = self.querystring.copy()
+        querystring['_to'] = -1
+        self.assertRaises(colander.Invalid, self.schema.deserialize, querystring)
+
+    def test_raises_invalid_for_negative_integer_in_before(self):
+        querystring = self.querystring.copy()
+        querystring['_before'] = -1
+        self.assertRaises(colander.Invalid, self.schema.deserialize, querystring)
+
+    def test_raises_invalid_for_negative_integer_in_last_modified(self):
+        querystring = self.querystring.copy()
+        querystring['last_modified'] = -1
+        self.assertRaises(colander.Invalid, self.schema.deserialize, querystring)