rsinger86 / drf-access-policy

Declarative access policies/permissions modeled after AWS' IAM policies.
https://rsinger86.github.io/drf-access-policy/
MIT License
472 stars 50 forks source link

Added possibility to add conditions and actions when setting read_only fields #82

Closed gianpieropa closed 2 years ago

gianpieropa commented 2 years ago

Now we can specify conditions and actions for setting read_only fields. The conditions will be evaluated only if the instance is available (so only for patch, put). To differentiate between conditions on statements and conditions to set read_only fields, the methods will be passed not as string but directly as methods and will have as input the instance. I implemented this code to satisfy the necessity to centralize the permissions so i created this pull request. The next step would be to have the ability to access request and view, but I'm not sure of the usefulness

rsinger86 commented 2 years ago

This looks good, thank you for pulling it together. There are two parts to my response (a) some questions about this pull and (b) and I wanted to pick your brain about taking an altogether different approach to field-level permissions. If you agree with (b) then you can ignore (a).

A)


(B)

Lately, I've started to think it may be better to take a procedural approach with field permissions. See pseudo code below.

The access policy class could define a scope_fields method like this:

class AccountAccessPolicy(AccessPolicy)
    @classmethod
    def scope_fields(cls, request, fields: dict, instance=None):
        return fields

Then the serializer mixin could be simplified:

class FieldsAccessMixin(object):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._apply_fields_access()

    @property
    def access_policy(self) -> AccessPolicy:
        meta = getattr(self, "Meta", None)

        if not meta:
            raise Exception("Must set access_policy inside Meta for FieldAccessMixin")

        access_policy = getattr(meta, "access_policy", None)

        if not access_policy:
            raise Exception("Must set access_policy inside Meta for FieldAccessMixin")

        return access_policy

    @property
    def request(self) -> Request:
        request = self.serializer_context.get("request")

        if not request:
            raise Exception("Must pass context with request to FieldAccessMixin")

        return request

    def _apply_fields_access(self):
        request = self._get_request()
        policy = self._get_access_policy()
        self.fields = policy.scope_fields(request, self.fields, instance=self.instance)
gianpieropa commented 2 years ago

Thank you for taking the time to reply. I believe you are right about your questions and they should be solved before merging. Regarding point b, I really like the idea but in this way, wouldn't we eliminate the declarative approach?

rsinger86 commented 2 years ago

Sorry for the late reply. Happy to merge this when you think it's ready.

Regarding point b, I really like the idea but in this way, wouldn't we eliminate the declarative approach?

Yes it would. I'm not sure if that matters, though. I guess maybe both could be available? Do you have a recommendation?

gianpieropa commented 2 years ago

So i tried to implement your idea and i really fell in love with it. Using this approach is similar to overriding the get_fields method of drf serializers, but all centralized in the AccessPolicy class. Let me know if there is something missing.

rsinger86 commented 2 years ago

awesome, thanks.

I will get this merged soon, just want to make things compatible with the old style field_permissions in case any projects use that approach.

rsinger86 commented 2 years ago

@gianpieropa this is released now, thank again for your help

in this commit, i added docs for the new feature and added in support for the old-style field_permissions: https://github.com/rsinger86/drf-access-policy/commit/58f6b707fb177d7142135c63eef6650529d8ebad