Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DjangoObjectPermissions behaviour #6596

Closed
Tommos0 opened this issue Apr 15, 2019 · 5 comments
Closed

DjangoObjectPermissions behaviour #6596

Tommos0 opened this issue Apr 15, 2019 · 5 comments

Comments

@Tommos0
Copy link

Tommos0 commented Apr 15, 2019

Hi,

I'm having some issues with the default DjangoObjectPermissions. The behaviour currently is not what I would expect. I can update the documentation to be more clear about the current implementation, or if you like I can create a pull request with my proposed fix.

My issue is as follows. If one uses DjangoObjectPermissions, since it extends DjangoModelPermissions, it also checks the basic model permission (DjangoModelPermissions.has_permission). At least I think the documentation should be more clear about this, but actually I think there is a better implementation. I would expect this permission class to ONLY check for the object permissions. Current behaviour can then simply be recreated by using DjangoObjectPermissions & DjangoModelPermissions, but this change would also allow DjangoObjectPermissions | DjangoModelPermissions (which I think is a nice default). The simplest fix would be to just add

    def has_permission(self, request, view):
        return True

to DjangoObjectPermissions.

There is a second issue: because in some cases it raises exceptions (see

if not user.has_perms(perms, obj):
# If the user does not have permissions we need to determine if
# they have read permissions to see 403, or not, and simply see
# a 404 response.
if request.method in SAFE_METHODS:
# Read permissions already checked and failed, no need
# to make another lookup.
raise Http404
read_perms = self.get_required_object_permissions('GET', model_cls)
if not user.has_perms(read_perms, obj):
raise Http404
# Has read permissions.
return False
), this actually breaks logically combining permission classes (the raise will ignore any other permission class giving positive permission).

I realize this could be a breaking change for many users so perhaps we can create this new permission class with a different name? Let me know what you think, I could create a PR with a change or update the docs to at least explain the current situation.

@Tommos0
Copy link
Author

Tommos0 commented Apr 16, 2019

I realize now that this is not how things are implemented, so my proposed solution doesn't work.

I'm using a workaround to get a Object | Model permission like this:

    def has_object_permission(self, request, view, obj):
        if DjangoModelPermissions.has_permission(self, request, view):
            return True
        queryset = self._queryset(view)
        model_cls = queryset.model
        user = request.user
        perms = self.get_required_object_permissions(request.method, model_cls)
        return user.has_perms(perms, obj)

    def has_permission(self, request, view):
        handler = getattr(view, request.method.lower(), None)
        if handler and handler.__name__ == 'list':
            return DjangoModelPermissions.has_permission(self, request, view)
        return True

@johnthagen
Copy link
Contributor

I too found it surprising that DjangoObjectPermissions inherited from DjangoModelPermissions rather than BasePermission, especially given how they are named (it's not DjangoObjectModelPermissions).

Could there be a new Permissions type created that solely performed object permissions? It seems like that would be a useful primitive to include in DRF directly.

@tomchristie
Copy link
Member

If someone else wants to implement a third party package for that, then sure. I don’t really see it a strong enough use case for it myself.

@johnthagen
Copy link
Contributor

johnthagen commented Jul 18, 2019

FWIW, in case this helps someone else, I ended up overriding has_permission to remove the DjangoModelPermissions behavior.

I also added support for view_modelname permissions added in Django 2.1. I'd suggest it be added to DRF, but DRF still supports Django 1.11 (#6230).

from rest_framework.permissions import DjangoObjectPermissions, SAFE_METHODS
from rest_framework.request import Request
from rest_framework.viewsets import GenericViewSet


class IsAuthenticatedDjangoObjectPermissions(DjangoObjectPermissions):
    """Allows access only to authenticated users who have the appropriate object level permissions.
    """

    perms_map = DjangoObjectPermissions.perms_map

    # Add default Django view permissions added in Django 2.1 and not included in DRF
    # DjangoObjectPermissions as of DRF 3.9.4.
    for method in SAFE_METHODS:
        perms_map[method] = ['%(app_label)s.view_%(model_name)s']

    def has_permission(self, request: Request, view: GenericViewSet) -> bool:
        # Override DjangoModelPermissions which DjangoObjectPermissions inherits from.
        # Taken from rest_framework.permissions.IsAuthenticated.
        return bool(request.user and request.user.is_authenticated)

@rpkilby
Copy link
Member

rpkilby commented Jul 18, 2019

#6325 will add support for the view permission. It should be merged whenever we're ramping up for the 3.11 release in December, which will add Django 3.0 support and drop support for 2.1 and below.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants