Selaa lähdekoodia

fix(api): authenticate actions after validating HTTP method

Peter Thomassen 5 vuotta sitten
vanhempi
commit
a753f2222e
2 muutettua tiedostoa jossa 18 lisäystä ja 15 poistoa
  1. 6 1
      api/desecapi/tests/test_user_management.py
  2. 12 14
      api/desecapi/views.py

+ 6 - 1
api/desecapi/tests/test_user_management.py

@@ -660,11 +660,16 @@ class HasUserAccountTestCase(UserManagementTestCase):
         self.assertUserDoesNotExist(new_email)
         self.assertUserDoesNotExist(new_email)
         self.assertPassword(self.email, new_password)
         self.assertPassword(self.email, new_password)
 
 
+    def test_reset_password_via_get(self):
+        confirmation_link = self._start_reset_password()
+        response = self.client.verify(confirmation_link)
+        self.assertResponse(response, status.HTTP_405_METHOD_NOT_ALLOWED)
+
     def test_reset_password_validation_unknown_user(self):
     def test_reset_password_validation_unknown_user(self):
         confirmation_link = self._start_reset_password()
         confirmation_link = self._start_reset_password()
         self._test_delete_account(self.email, self.password)
         self._test_delete_account(self.email, self.password)
         self.assertVerificationFailureUnknownUserResponse(
         self.assertVerificationFailureUnknownUserResponse(
-            response=self.client.verify(confirmation_link)
+            response=self.client.verify(confirmation_link, new_password='foobar')
         )
         )
         self.assertNoEmailSent()
         self.assertNoEmailSent()
 
 

+ 12 - 14
api/desecapi/views.py

@@ -524,8 +524,7 @@ class AuthenticatedActionView(generics.GenericAPIView):
         view's 'code' kwarg and (b) the request payload for POST requests. Request methods other than GET and POST will
         view's 'code' kwarg and (b) the request payload for POST requests. Request methods other than GET and POST will
         fail authentication regardless of other conditions.
         fail authentication regardless of other conditions.
 
 
-        If the request is valid, the AuthenticatedAction instance will be attached to the view as `authenticated_action`
-        attribute.
+        If the request is valid, the AuthenticatedAction instance will be attached to the request as `auth` attribute.
 
 
         Note that this class will raise ValidationError instead of AuthenticationFailed, usually resulting in status
         Note that this class will raise ValidationError instead of AuthenticationFailed, usually resulting in status
         400 instead of 403.
         400 instead of 403.
@@ -540,19 +539,20 @@ class AuthenticatedActionView(generics.GenericAPIView):
             serializer = self.view.serializer_class(data=data, context=self.view.get_serializer_context())
             serializer = self.view.serializer_class(data=data, context=self.view.get_serializer_context())
             serializer.is_valid(raise_exception=True)
             serializer.is_valid(raise_exception=True)
             try:
             try:
-                self.view.authenticated_action = serializer.Meta.model(**serializer.validated_data)
+                action = serializer.Meta.model(**serializer.validated_data)
             except ValueError:
             except ValueError:
                 raise ValidationError()
                 raise ValidationError()
 
 
-            return self.view.authenticated_action.user, None
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.authenticated_action = None
+            return action.user, action
 
 
     def get_authenticators(self):
     def get_authenticators(self):
         return [self.AuthenticatedActionAuthenticator(self)]
         return [self.AuthenticatedActionAuthenticator(self)]
 
 
+    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):
         return self.take_action()
         return self.take_action()
 
 
@@ -563,9 +563,7 @@ class AuthenticatedActionView(generics.GenericAPIView):
         raise NotImplementedError
         raise NotImplementedError
 
 
     def take_action(self):
     def take_action(self):
-        # execute the action
-        self.authenticated_action.act()
-
+        self.request.auth.act()  # execute the action (triggers authentication if not yet done)
         return self.finalize()
         return self.finalize()
 
 
 
 
@@ -575,7 +573,7 @@ class AuthenticatedActivateUserActionView(AuthenticatedActionView):
     serializer_class = serializers.AuthenticatedActivateUserActionSerializer
     serializer_class = serializers.AuthenticatedActivateUserActionSerializer
 
 
     def finalize(self):
     def finalize(self):
-        if not self.authenticated_action.domain:
+        if not self.request.auth.domain:
             return self._finalize_without_domain()
             return self._finalize_without_domain()
         else:
         else:
             domain = self._create_domain()
             domain = self._create_domain()
@@ -585,7 +583,7 @@ class AuthenticatedActivateUserActionView(AuthenticatedActionView):
                 return self._finalize_with_domain()
                 return self._finalize_with_domain()
 
 
     def _create_domain(self):
     def _create_domain(self):
-        action = self.authenticated_action
+        action = self.request.auth
         serializer = serializers.DomainSerializer(
         serializer = serializers.DomainSerializer(
             data={'name': action.domain},
             data={'name': action.domain},
             context=self.get_serializer_context()
             context=self.get_serializer_context()
@@ -633,7 +631,7 @@ class AuthenticatedChangeEmailUserActionView(AuthenticatedActionView):
 
 
     def finalize(self):
     def finalize(self):
         return Response({
         return Response({
-            'detail': f'Success! Your email address has been changed to {self.authenticated_action.user.email}.'
+            'detail': f'Success! Your email address has been changed to {self.request.user.email}.'
         })
         })