Repository: jazzband/django-oauth-toolkit

Issue:
ProtectResourceMixin denies OPTIONS request
The OPTIONS preflight request typically has all headers stripped by the browser (including HTTP_AUTORIZATION), which causes the dispatch method to return a 403 response.

My suggestion is to accept all OPTIONS requests.



Discussion:

Resolving patch:
diff --git a/oauth2_provider/views/mixins.py b/oauth2_provider/views/mixins.py
--- a/oauth2_provider/views/mixins.py
+++ b/oauth2_provider/views/mixins.py
@@ -192,9 +192,15 @@ def get_scopes(self, *args, **kwargs):
 
 class ProtectedResourceMixin(OAuthLibMixin):
     """
-    Helper mixin that implements OAuth2 protection on request dispatch, specially useful for Django Generic Views
+    Helper mixin that implements OAuth2 protection on request dispatch,
+    specially useful for Django Generic Views
     """
     def dispatch(self, request, *args, **kwargs):
+        # let preflight OPTIONS requests pass
+        if request.method.upper() == 'OPTIONS':
+            return super(ProtectedResourceMixin, self).dispatch(request, *args, **kwargs)
+
+        # check if the request is valid and the protected resource may be accessed
         valid, r = self.verify_request(request)
         if valid:
             request.resource_owner = r.user


Test patch:
diff --git a/oauth2_provider/tests/test_mixins.py b/oauth2_provider/tests/test_mixins.py
--- a/oauth2_provider/tests/test_mixins.py
+++ b/oauth2_provider/tests/test_mixins.py
@@ -3,18 +3,21 @@
 from django.core.exceptions import ImproperlyConfigured
 from django.views.generic import View
 from django.test import TestCase, RequestFactory
+from django.http import HttpResponse
 
 from oauthlib.oauth2 import Server
 
-from ..views.mixins import OAuthLibMixin, ScopedResourceMixin
+from ..views.mixins import OAuthLibMixin, ScopedResourceMixin, ProtectedResourceMixin
 from ..oauth2_validators import OAuth2Validator
 
 
-class TestOAuthLibMixin(TestCase):
+class BaseTest(TestCase):
     @classmethod
     def setUpClass(cls):
         cls.request_factory = RequestFactory()
 
+
+class TestOAuthLibMixin(BaseTest):
     def test_missing_server_class(self):
         class TestView(OAuthLibMixin, View):
             validator_class = OAuth2Validator
@@ -59,11 +62,7 @@ class TestView(OAuthLibMixin, View):
                          AnotherOauthLibBackend)
 
 
-class TestScopedResourceMixin(TestCase):
-    @classmethod
-    def setUpClass(cls):
-        cls.request_factory = RequestFactory()
-
+class TestScopedResourceMixin(BaseTest):
     def test_missing_required_scopes(self):
         class TestView(ScopedResourceMixin, View):
             pass
@@ -79,3 +78,19 @@ class TestView(ScopedResourceMixin, View):
         test_view = TestView()
 
         self.assertEqual(test_view.get_scopes(), ['scope1', 'scope2'])
+
+
+class TestProtectedResourceMixin(BaseTest):
+    def test_options_shall_pass(self):
+        class TestView(ProtectedResourceMixin, View):
+            server_class = Server
+            validator_class = OAuth2Validator
+
+            def options(self, request, *args, **kwargs):
+                """Django 1.4 doesn't provide a default options method"""
+                return HttpResponse()
+
+        request = self.request_factory.options("/fake-req")
+        view = TestView.as_view()
+        response = view(request)
+        self.assertEqual(response.status_code, 200)