dfunckt / django-rules

Awesome Django authorization, without the database

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How do I access the post object in a predicate?

Routhinator opened this issue · comments

I'm trying to check if a user posting a discussion has access to the topic they are trying to associate the discussion with. However when the user posts, the second argument in the predicate, object is always None - I need to access the post body to check the topic attribute and compare to the users groups.

How do I access the post object? Is it available in the predicate context somewhere if I bind the predicate?

This is the logic I have so far which works for reading discussion objects, but it looks like I need different logic for POST requests due to the object being null:

@rules.predicate
def is_allowed(user, content):
    """
    | Accepts a user and content object and checks if the user is allowed to view it.
    :param user:
    :param content:
    :return:
    """
    if content is None or not (hasattr(content, 'topic') or not hasattr(content, 'group')):
        LOGGER.debug('Content was null or did not have topic')
        LOGGER.debug('Content: %s', repr(content))
        LOGGER.debug('User: %s', repr(user))
        return False
    if hasattr(content, 'group'):
        if content.group is None or not hasattr(content.group, 'name'):
            LOGGER.debug('Topic group was none or did not have name')
            return True
        LOGGER.debug('Checking if member is in group %s', content.group.name)
        if content.group.name in user.groups.all():
            return True
    if hasattr(content, 'topic'):
        if content.topic.group is None or not hasattr(content.topic.group, 'name'):
            LOGGER.debug('Topic group was none or did not have name')
            return True
        LOGGER.debug('Checking if member is in group %s', content.topic.group.name)
        if content.topic.group.name in user.groups.all():
            return True
    return False

After trying a few things, I don't think this is possible for a new object. I've resorted to only validating that they are authenticated, and I guess I'll build the permissions check for a new discussion into the create method of the serializer.

Yes, it is not possible. The arguments Django passes to rules only contain the user and the object, and on create views specifically, not even the object (because there's none).

I'm closing this as it's been a while and it also doesn't really have anything to do with rules, as this is Django's standard way of checking for the create permission.

For DRF mixin, would something like this work?

        elif self.action == "create":
            # Pass the permission a mock object that is not yet saved.
            # This replicates the behavior of the CreateMixin.
            tmp_serializer = self.get_serializer(
                data=self.request.data
            )
            tmp_serializer.is_valid(raise_exception=False)
            obj = model(**tmp_serializer.validated_data)

It would also be possible to override perform_create in the AutoPermissionViewSetMixin but that would make things sensitive to the order of the mixins.

But since the DRF is already plugging into `

EDIT: This actually doesn't work for more complex objects and because of: TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use ... instead

But passing the serialised data does work, with a couple of caveats:

    def perform_create(self, serializer):
        model = self.get_queryset().model
        perm = self.get_permission_for_model(model, "add")

        if not self.request.user.has_perm(perm, serializer.validated_data):
            raise PermissionDenied