Repository: Kinto/kinto

Issue:
An OPTIONS request on "/v1" is answered with a 307 instead of a 200
example query:

```
curl 'https://kinto.dev.mozaws.net/v1' -X OPTIONS -H 'Host: kinto.dev.mozaws.net' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:50.0) Gecko/20100101 Firefox/50.0' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H 'Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3' --compressed -H 'Access-Control-Request-Method: GET' -H 'Access-Control-Request-Headers: authorization' -H 'Origin: http://localhost:8000' -H 'Connection: keep-alive' -H 'Cache-Control: max-age=0'
```

As this OPTIONS request is made/used by the browser for CORS validation, it breaks on the 307.

An OPTIONS request on "/v1" is answered with a 307 instead of a 200
example query:

```
curl 'https://kinto.dev.mozaws.net/v1' -X OPTIONS -H 'Host: kinto.dev.mozaws.net' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:50.0) Gecko/20100101 Firefox/50.0' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H 'Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3' --compressed -H 'Access-Control-Request-Method: GET' -H 'Access-Control-Request-Headers: authorization' -H 'Origin: http://localhost:8000' -H 'Connection: keep-alive' -H 'Cache-Control: max-age=0'
```

As this OPTIONS request is made/used by the browser for CORS validation, it breaks on the 307.



Discussion:
It seems this also happens on other queries: we receive the expected status code... but on the pre-flight request instead of having a 200 on the pre-flight, and then the status code on the request itself.

Eg (note that this should return a 404 because it's asking for `collectionss` instead of `collections`):

```
curl 'https://kinto.dev.mozaws.net/v1/buckets/default/collectionss/test-items/records' -X OPTIONS -H 'Host: kinto.dev.mozaws.net' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:50.0) Gecko/20100101 Firefox/50.0' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H 'Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3' --compressed -H 'Access-Control-Request-Method: GET' -H 'Access-Control-Request-Headers: authorization' -H 'Origin: http://localhost:8000' -H 'Connection: keep-alive' -H 'Cache-Control: max-age=0' --verbose
```

OPTIONS should always return a 200.

It seems this also happens on other queries: we receive the expected status code... but on the pre-flight request instead of having a 200 on the pre-flight, and then the status code on the request itself.

Eg (note that this should return a 404 because it's asking for `collectionss` instead of `collections`):

```
curl 'https://kinto.dev.mozaws.net/v1/buckets/default/collectionss/test-items/records' -X OPTIONS -H 'Host: kinto.dev.mozaws.net' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:50.0) Gecko/20100101 Firefox/50.0' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H 'Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3' --compressed -H 'Access-Control-Request-Method: GET' -H 'Access-Control-Request-Headers: authorization' -H 'Origin: http://localhost:8000' -H 'Connection: keep-alive' -H 'Cache-Control: max-age=0' --verbose
```

OPTIONS should always return a 200.


Resolving patch:
diff --git a/kinto/core/initialization.py b/kinto/core/initialization.py
--- a/kinto/core/initialization.py
+++ b/kinto/core/initialization.py
@@ -9,6 +9,7 @@
 from pyramid.httpexceptions import (HTTPTemporaryRedirect, HTTPGone,
                                     HTTPBadRequest)
 from pyramid.renderers import JSON as JSONRenderer
+from pyramid.response import Response
 from pyramid.security import NO_PERMISSION_REQUIRED
 from pyramid.interfaces import IAuthenticationPolicy
 from pyramid.settings import asbool, aslist
@@ -72,6 +73,10 @@ def setup_version_redirection(config):
         return
 
     def _redirect_to_version_view(request):
+        if request.method.lower() == 'options':
+            # CORS responses should always have status 200.
+            return utils.reapply_cors(request, Response())
+
         path = request.matchdict['path']
         querystring = request.url[(request.url.rindex(request.path) +
                                    len(request.path)):]


Test patch:
diff --git a/tests/core/test_views_errors.py b/tests/core/test_views_errors.py
--- a/tests/core/test_views_errors.py
+++ b/tests/core/test_views_errors.py
@@ -159,6 +159,14 @@ def test_redirect_to_version(self):
         self.assertEqual(response.status_int, 307)
         self.assertEqual(response.location, 'http://localhost/v0/mushrooms')
 
+    def test_redirects_benefits_from_cors_setup(self):
+        headers = {
+            'Origin': 'lolnet.org',
+            'Access-Control-Request-Method': 'GET'
+        }
+        resp = self.app.options('/', headers=headers, status=200)
+        self.assertIn('Access-Control-Allow-Origin', resp.headers)
+
     def test_do_not_redirect_to_version_if_disabled_in_settings(self):
         # GET on the hello view.
         app = self.make_app({'version_prefix_redirect_enabled': False})