Explorar o código

fix(api): skip authentication for auth action redirects, fixes #494

Peter Thomassen %!s(int64=4) %!d(string=hai) anos
pai
achega
52dff2060a

+ 2 - 1
api/api/settings_quick_test.py

@@ -26,7 +26,8 @@ CACHES = {
 }
 }
 
 
 REST_FRAMEWORK['PAGE_SIZE'] = 20
 REST_FRAMEWORK['PAGE_SIZE'] = 20
-REST_FRAMEWORK['DEFAULT_THROTTLE_CLASSES'] = []
+REST_FRAMEWORK['DEFAULT_THROTTLE_CLASSES'] = ['rest_framework.throttling.UserRateThrottle']
+REST_FRAMEWORK['DEFAULT_THROTTLE_RATES'] = {'user': '1000/s'}
 
 
 # Carry email backend connection over to test mail outbox
 # Carry email backend connection over to test mail outbox
 CELERY_EMAIL_MESSAGE_EXTRA_ATTRIBUTES = ['connection']
 CELERY_EMAIL_MESSAGE_EXTRA_ATTRIBUTES = ['connection']

+ 5 - 0
api/desecapi/tests/test_user_management.py

@@ -506,6 +506,11 @@ class NoUserAccountTestCase(UserLifeCycleTestCase):
     def test_home(self):
     def test_home(self):
         self.assertResponse(self.client.get(reverse('v1:root')), status.HTTP_200_OK)
         self.assertResponse(self.client.get(reverse('v1:root')), status.HTTP_200_OK)
 
 
+    def test_authenticated_action_redirect_with_invalid_code(self):
+        # This tests that the code is not processed when Accept: text/html is not set (redirect without further ado)
+        confirmation_link = self.reverse('v1:confirm-activate-account', code='foobar')
+        self.assertConfirmationLinkRedirect(confirmation_link)
+
     def test_registration(self):
     def test_registration(self):
         self._test_registration(password=self.random_password())
         self._test_registration(password=self.random_password())
 
 

+ 11 - 6
api/desecapi/views.py

@@ -593,13 +593,23 @@ class AuthenticatedActionView(generics.GenericAPIView):
     """
     """
     Abstract class. Deserializes the given payload according the serializers specified by the view extending
     Abstract class. Deserializes the given payload according the serializers specified by the view extending
     this class. If the `serializer.is_valid`, `act` is called on the action object.
     this class. If the `serializer.is_valid`, `act` is called on the action object.
+
+    Summary of the behavior depending on HTTP method and Accept: header:
+
+                        GET	                                POST                other method
+    Accept: text/html	forward to `self.html_url` if any   perform action      405 Method Not Allowed
+    else                HTTP 406 Not Acceptable             perform action      405 Method Not Allowed
     """
     """
     action = None
     action = None
-    authentication_classes = (auth.AuthenticatedBasicUserActionAuthentication,)
     html_url = None  # Redirect GET requests to this webapp GUI URL
     html_url = None  # Redirect GET requests to this webapp GUI URL
     http_method_names = ['get', 'post']  # GET is for redirect only
     http_method_names = ['get', 'post']  # GET is for redirect only
     renderer_classes = [JSONRenderer, StaticHTMLRenderer]
     renderer_classes = [JSONRenderer, StaticHTMLRenderer]
 
 
+    @property
+    def authentication_classes(self):
+        # This prevents both code evaluation and user-specific throttling when we only want a redirect
+        return () if self.request.method in SAFE_METHODS else (auth.AuthenticatedBasicUserActionAuthentication,)
+
     @property
     @property
     def throttle_scope(self):
     def throttle_scope(self):
         return 'account_management_passive' if self.request.method in SAFE_METHODS else 'account_management_active'
         return 'account_management_passive' if self.request.method in SAFE_METHODS else 'account_management_active'
@@ -611,11 +621,6 @@ class AuthenticatedActionView(generics.GenericAPIView):
             'validity_period': self.get_serializer_class().validity_period,
             'validity_period': self.get_serializer_class().validity_period,
         }
         }
 
 
-    def perform_authentication(self, request):
-        # Delay authentication until request.auth or request.user is first accessed.
-        # This allows returning a redirect or status 405 without validating the action code.
-        pass
-
     def get(self, request, *args, **kwargs):
     def get(self, request, *args, **kwargs):
         # Redirect browsers to frontend if available
         # Redirect browsers to frontend if available
         is_redirect = (request.accepted_renderer.format == 'html') and self.html_url is not None
         is_redirect = (request.accepted_renderer.format == 'html') and self.html_url is not None