FJNR-inc / dry-rest-permissions

Rules based permissions for the Django Rest Framework
ISC License
75 stars 11 forks source link

Using DRYGlobalPermissions #13

Closed HtheChemist closed 3 years ago

HtheChemist commented 3 years ago

I have some difficulty understanding and using DRYGlobalPermissions.

I made a serializer that return the permissions for the model, however, I would need to have access to these value outside the usual route. Mainly, I am trying to add the value to a JWT Token claim, so the access is made programmatically and not through a view. I am uncertain what to give to the serializer as data for it to return the information for my user.

Any help in this regard would be appreciated.

RignonNoel commented 3 years ago

If you declared some permissions functions in your model you should see that these functions ask for a request, it's the HTTP request you got in your view, from this point you can use the model permission from your serializer or directly from your view as soon as you use the request and the model function.

    def has_object_read_permission(self, request):
        return True

    @staticmethod
    def has_write_permission(request):
        return False

If you want to use the permissions into authentication maybe you're looking into Django middleware, if it's the case you need to investigate how to get the request into your middleware logic.


I made a presentation of the package during an event in the last months, maybe it can help you understand the full potential and usage of Dry-Rest-Permissions. I speak in French, but the support is in English: https://youtu.be/f8732gD-ktQ?t=3553


Feel free to precise your research in this issue and i will make sure to help you as i can so we can found a solution to your problem.

HtheChemist commented 3 years ago

Merci beaucoup!

I am going through a view. I am using django-rest-framework-simplejwt to generate a JWT token through a view and I am trying to include the permission in the token pay load. I am able to get the permission through the API and I am able to get a token without problem. Where I am stuck is to mix both in one. I have two ideas which I have a hard time implementing:

I feel that both of these are pretty straigtforward but I am still begining with Django (and DRF).

Dans tous les cas, merci beaucoup pour ton aide et d'avoir repris la librairie.

RignonNoel commented 3 years ago

Hi @HtheChemist !

On my opinion the best way to work in your problem will be to customize a serializer to integrate both of your information, you're lucky since DryRestPermissions already allow you to integrate a custom field in a serializer to return you all the global permissions DryGlobalPermissionsField, so you just need to find a way to customize the serializer of your plugin to integrate this field in it.

I do not work with django-rest-framework-simplejwt but ont their documentation it seems to have an article on how to customize the serializer of the token. Maybe it could help you in this research.

Let me know if you find the solution, it could be helpful for others people having the same problem in the future!

HtheChemist commented 3 years ago

Thank you @RignonNoel .

This is actually what I was trying to do. In the meantime I found out that the error was caused by the fact that the class method get_token from simplejwt did not have access to the request (being a class method....). I am a bit wary of turning the get_token class method into a normal method at the moment since I am not certain of the final repercussion this would have on the token generation.

Currently I am simply doing a systematic API Call after refreshing the token, I may come back to this later. I will keep you posted on my advance.

Merci encore!

HtheChemist commented 3 years ago

To follow up and close, here is what I ended up doing.

I first made a Serializer that return the permission for a user:

class PermsSerializer(ModelSerializer):
    """
    This serializer is used to retrieve the permission from the app models using DRYGlobalPermissionsField. It uses
    the default User model as a general model to call, will still return all models that use DRYPermissions.
    """
    permissions = DRYGlobalPermissionsField()

    def to_representation(self, instance):
        perms = super().to_representation(instance)
        perms = perms["permissions"]
        for model, values in perms.items():
            perms[model] = {key: value for key, value in values.items() if value}
        return perms

    class Meta:
        model = get_user_model()
        fields = ("permissions",)

This serializer is then called by a function that modify the token.

def modify_token(token, context, data):
    user = context["request"].user
    if type(context['view']) == PermsRefreshView:
        refresh_token = RefreshToken(context["request"].data['refresh'])
        user = TokenUser(refresh_token)
        context["request"].user = user
    permissions = PermsSerializer(data=data, context=context)
    permissions.is_valid(raise_exception=True)
    token["permissions"] = permissions.data
    token["username"] = user.username
    token["is_staff"] = user.is_staff
    token["is_superuser"] = user.is_superuser
    token["id"] = user.id
    token["groups"] = list(
        context["request"].user.groups.values_list("name", flat=True)
    )

    return token

I then overwrote the validate function of the standard SimpleJWT token serializer and made view to include the modified Serializer:

class PermsObtainPairSerializer(TokenObtainPairSerializer):
    @classmethod
    def get_modified_token(cls, user, context, data):
        token = super().get_token(user)
        context["request"].user = user
        return modify_token(token, context, data)

    def validate(self, attrs):
        data = super().validate(attrs)

        refresh = self.get_modified_token(self.user, self.context, self.initial_data)

        data["refresh"] = str(refresh)
        data["access"] = str(refresh.access_token)

        return data

class PermsObtainPairView(TokenObtainPairView):
    serializer_class = PermsObtainPairSerializer

class PermsRefreshSerializer(Serializer):
    refresh = CharField()

    def validate(self, attrs):
        refresh = RefreshToken(attrs["refresh"])
        refresh = modify_token(refresh, self.context, self.initial_data)

        data = {"access": str(refresh.access_token)}

        if api_settings.ROTATE_REFRESH_TOKENS:
            if api_settings.BLACKLIST_AFTER_ROTATION:
                try:
                    # Attempt to blacklist the given refresh token
                    refresh.blacklist()
                except AttributeError:
                    # If blacklist app not installed, `blacklist` method will
                    # not be present
                    pass

            refresh.set_jti()
            refresh.set_exp()

            data["refresh"] = str(refresh)

        return data

class PermsRefreshView(TokenRefreshView):
    serializer_class = PermsRefreshSerializer