Search code examples
python-3.xdjangodjango-rest-frameworkdjango-rest-viewsets

DRF requires the user to be logged in to use login_user api


I am trying to create a CustomUserViewset, and add a login_user api to it. The problem is that although I set the permission_classes to AllowAny, still when calling the login_user api, it says: {"detail":"Please login to perform this action"}.

Here is my API:

class CustomUserViewset(AutoPermissionViewSetMixin, viewsets.ModelViewSet):
    queryset = User.objects.none()
    serializer_class = CustomUserSerializer

    permission_type_map = {
        "create": "add",
        "destroy": "delete",
        "partial_update": "change",
        "retrieve": "view",
        "update": "change",
        "register": "view",
        "login_user": "view",
        "logout": "change",
    }

    @decorators.action(methods=["POST"], detail=False, permission_classes=[permissions.AllowAny])
    def login_user(self, request):
        serializer = LoginSerializer(data=request.data)

        if not serializer.is_valid():
            raise exceptions.ValidationError({"detail": "Invalid username or password"})

        username = serializer.validated_data["username"]
        password = serializer.validated_data["password"]

        user = authenticate(request, username=username, password=password)

        if user is not None:
            login(request, user)
            return Response(CustomUserSerializer(user).data, status=status.HTTP_200_OK)
        else:
            raise exceptions.AuthenticationFailed({"detail": "Invalid username or password"})

As you see, I have permission_classes=[permissions.AllowAny] in the api action. Also, giving this permission class in the action was the last thing I tried, before that, I tried to adjust the permission in rules.py:

import typing

import rules

if typing.TYPE_CHECKING:
    from .models import User
rules.add_perm("accounts.login_user", rules.predicates.always_allow)

None of the above methods has worked, and I still get the same message that I need to log in to perform this action.


Solution

  • **** UPDATE ON THE ANSWER ****

    I solved this problem with first adding a few permissions to permission_type_map, then adjusting the rules.py:

    class CustomUserViewset(AutoPermissionViewSetMixin, viewsets.ModelViewSet):
        queryset = User.objects.none()
        serializer_class = serializers.CustomUserSerializer
    
        permission_type_map = {
            "list": "list",
            "create": "add",
            "destroy": "delete",
            "partial_update": "change",
            "retrieve": "view",
            "update": "change",
            "register": "register",
            "login": "login",
            "logout": "logout",
        }
    
        @decorators.action(methods=["post"], detail=False)
        def register(self, request):
            register_serializer = serializers.RegisterSerializer(data=request.data)
    
            register_serializer.is_valid(raise_exception=True)
    
            username = register_serializer.validated_data["username"]
            password = register_serializer.validated_data["password"]
            # email = register_serializer.validated_data["email"] // for now, email is auto-generated
            email = f"{username}@example.com"
    
            new_user = User.objects.create_user(username=username, email=email, password=password)
            user_serializer = serializers.CustomUserSerializer(new_user)
            return Response(user_serializer.data, status=status.HTTP_201_CREATED)
    
        @decorators.action(methods=["post"], detail=False)
        def login(self, request):
            login_serializer = serializers.LoginSerializer(data=request.data)
    
            login_serializer.is_valid(raise_exception=True)
    
            username = login_serializer.validated_data["username"]
            password = login_serializer.validated_data["password"]
    
            user = authenticate(request, username=username, password=password)
    
            if user:
                login(request, user)
                return Response(
                    serializers.CustomUserSerializer(user).data,
                    status=status.HTTP_200_OK,
                )
            raise exceptions.AuthenticationFailed({"detail": "Invalid username or password"})
    
        @decorators.action(detail=False, methods=["get"], permission_classes=[permissions.IsAuthenticated])
        def logout(self, request):
            logout(request)
            return Response({"detail": "Successfully logged out"})
    

    And in the rules.py:

    import typing
    
    import rules
    
    if typing.TYPE_CHECKING:
        from .models import User
    
    
    @rules.predicate(bind=True)
    def is_active(self, user: "User"):
        return getattr(user, "is_active", False)
    
    
    @rules.predicate(bind=True)
    def is_admin(self, user: "User"):
        return is_active(user) if user.is_superuser else False
    
    
    @rules.predicate(bind=True)
    def is_staff(self, user: "User"):
        return is_active(user) if user.is_staff or user.is_superuser else False
    
    
    @rules.predicate(bind=True)
    def is_same_user(self, auth_user: "User", target_user: "User"):
        """
        Is authenticated user the same user as target_user?
        """
        if auth_user and target_user:
            print("auth_user:", auth_user)
            print("target_user:", target_user)
            return auth_user.pk == target_user.pk
        return False
    
    
    rules.add_perm("accounts.view_user", is_admin | is_same_user)
    rules.add_perm("accounts.list_user", is_active)
    rules.add_perm("accounts.change_user", is_admin | is_same_user)
    rules.add_perm("accounts.add_user", is_admin)
    rules.add_perm("accounts.delete_user", is_admin)
    rules.add_perm("accounts.login_user", rules.predicates.always_allow)
    rules.add_perm("accounts.register_user", rules.predicates.always_allow)
    rules.add_perm("accounts.logout_user", is_active)
    

    Now with these new permissions and rules, all three APIs work as expected.