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.
**** 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.