Browse Source

fix(api): move and simplify AuthenticatedActionAuthenticator

Peter Thomassen 5 years ago
parent
commit
dd5a104eed

+ 22 - 0
api/desecapi/authentication.py

@@ -118,6 +118,28 @@ class EmailPasswordPayloadAuthentication(BaseAuthentication):
         return self.authenticate_credentials(serializer.data['email'], serializer.data['password'], request)
         return self.authenticate_credentials(serializer.data['email'], serializer.data['password'], request)
 
 
 
 
+class AuthenticatedActionAuthentication(BaseAuthentication):
+    """
+    Authenticates a request based on whether the serializer determines the validity of the given verification code
+    and additional data (using `serializer.is_valid()`). The serializer's input data will be determined by (a) the
+    view's 'code' kwarg and (b) the request payload for POST requests.
+
+    If the request is valid, the AuthenticatedAction instance will be attached to the request as `auth` attribute.
+    """
+    def authenticate(self, request):
+        view = request.parser_context['view']
+        data = {**request.data, 'code': view.kwargs['code']}  # order crucial to avoid override from payload!
+        serializer = view.serializer_class(data=data, context=view.get_serializer_context())
+        serializer.is_valid(raise_exception=True)
+        try:
+            action = serializer.Meta.model(**serializer.validated_data)
+        except ValueError:
+            exc = getattr(view, 'authentication_exception', exceptions.AuthenticationFailed)
+            raise exc('Invalid code.')
+
+        return action.user, action
+
+
 class TokenHasher(PBKDF2PasswordHasher):
 class TokenHasher(PBKDF2PasswordHasher):
     algorithm = 'pbkdf2_sha256_iter1'
     algorithm = 'pbkdf2_sha256_iter1'
     iterations = 1
     iterations = 1

+ 2 - 2
api/desecapi/serializers.py

@@ -623,9 +623,9 @@ class AuthenticatedActionSerializer(serializers.ModelSerializer):
             # decode from single string
             # decode from single string
             unpacked_data = self._unpack_code(data.pop('code'))
             unpacked_data = self._unpack_code(data.pop('code'))
         except KeyError:
         except KeyError:
-            raise serializers.ValidationError({'code': ['No verification code.']})
+            raise serializers.ValidationError({'code': ['This field is required.']})
         except ValueError:
         except ValueError:
-            raise serializers.ValidationError({'code': ['Invalid verification code.']})
+            raise serializers.ValidationError({'code': ['Invalid code.']})
 
 
         # add extra fields added by the user
         # add extra fields added by the user
         unpacked_data.update(**data)
         unpacked_data.update(**data)

+ 2 - 2
api/desecapi/tests/test_user_management.py

@@ -352,14 +352,14 @@ class UserManagementTestCase(DesecTestCase, PublicSuffixMockMixin):
     def assertVerificationFailureInvalidCodeResponse(self, response):
     def assertVerificationFailureInvalidCodeResponse(self, response):
         return self.assertContains(
         return self.assertContains(
             response=response,
             response=response,
-            text="Invalid input.",
+            text="Invalid code.",
             status_code=status.HTTP_400_BAD_REQUEST
             status_code=status.HTTP_400_BAD_REQUEST
         )
         )
 
 
     def assertVerificationFailureExpiredCodeResponse(self, response):
     def assertVerificationFailureExpiredCodeResponse(self, response):
         return self.assertContains(
         return self.assertContains(
             response=response,
             response=response,
-            text="Invalid verification code.",
+            text="Invalid code.",
             status_code=status.HTTP_400_BAD_REQUEST
             status_code=status.HTTP_400_BAD_REQUEST
         )
         )
 
 

+ 2 - 31
api/desecapi/views.py

@@ -516,37 +516,8 @@ 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.
     """
     """
-
-    class AuthenticatedActionAuthenticator(BaseAuthentication):
-        """
-        Authenticates a request based on whether the serializer determines the validity of the given verification code
-        and additional data (using `serializer.is_valid()`). The serializer's input data will be determined by (a) the
-        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.
-
-        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
-        400 instead of 403.
-        """
-
-        def __init__(self, view):
-            super().__init__()
-            self.view = view
-
-        def authenticate(self, request):
-            data = {**request.data, 'code': self.view.kwargs['code']}  # order crucial to avoid override from payload!
-            serializer = self.view.serializer_class(data=data, context=self.view.get_serializer_context())
-            serializer.is_valid(raise_exception=True)
-            try:
-                action = serializer.Meta.model(**serializer.validated_data)
-            except ValueError:
-                raise ValidationError()
-
-            return action.user, action
-
-    def get_authenticators(self):
-        return [self.AuthenticatedActionAuthenticator(self)]
+    authentication_classes = (auth.AuthenticatedActionAuthentication,)
+    authentication_exception = ValidationError
 
 
     def perform_authentication(self, request):
     def perform_authentication(self, request):
         # Delay authentication until request.auth or request.user is first accessed.
         # Delay authentication until request.auth or request.user is first accessed.