web-dev-qa-db-fra.com

Django REST autorisations au niveau des objets du framework)

J'utilise Django REST Framework pour accéder à une ressource 'utilisateur').

Étant donné que les informations utilisateur sont personnelles, je ne souhaite pas qu'une demande GET répertorie tous les utilisateurs du système, À MOINS QU'ils ne soient administrateurs.

Si l'utilisateur spécifie son identifiant et qu'il est connecté, j'aimerais qu'il puisse voir ses coordonnées et les modifier (PUT POST DELETE) si nécessaire.

Donc, en résumé, désactivez la méthode GET pour toute personne qui n'est pas administrateur et autorisez GET POST DELETE PUT sur les utilisateurs connectés lors de l'affichage de leurs informations.

j'ai donc créé la classe d'autorisation personnalisée:

class UserPermissions(permissions.BasePermission):
    """
    Owners of the object or admins can do anything.
    Everyone else can do nothing.
"""

    def has_permission(self, request, view):
        # if admin: True otherwise False
    def has_object_permission(self, request, view, obj):
        # if request.user is the same user that is contained within the obj then allow

Ça n'a pas marché. Après quelques débogages, j'ai trouvé qu'il vérifie d'abord has_permission, puis vérifie has_object_permission. Donc, si nous ne dépassons pas ce premier obstacle GET/user /, il ne considérera même pas le prochain GET/user/id.

donc, en résumé, est-ce que quelqu'un sait comment je procéderais pour que cela fonctionne?

Merci :)

MODIFIER:

J'utilisais ModelViewSets, qui ont ce problème comme je l'ai décrit.

Mais si vous divisez la fonctionnalité de liste avec le détail, vous pouvez leur donner des classes d'autorisation distinctes:

class UserList(generics.ListCreateAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes=(UserPermissionsAll,)

class UserDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes=(UserPermissionsObj,)

class UserPermissionsAll(permissions.BasePermission):
"""
Owners of the object or admins can do anything.
Everyone else can do nothing.
"""

    def has_permission(self, request, view):
        if request.user.is_staff:
            return True
        else:
            return False

class UserPermissionsObj(permissions.BasePermission):
"""
Owners of the object or admins can do anything.
Everyone else can do nothing.
"""

    def has_object_permission(self, request, view, obj):
        if request.user.is_staff:
            return True

        return obj == request.user
25
user1830568

J'ai fait cela dans le passé en utilisant une autorisation personnalisée et j'ai remplacé has_object_permission comme suit:

from rest_framework import permissions


class MyUserPermissions(permissions.BasePermission):
    """
    Handles permissions for users.  The basic rules are

     - owner may GET, PUT, POST, DELETE
     - nobody else can access
     """

    def has_object_permission(self, request, view, obj):

        # check if user is owner
        return request.user == obj

Vous pouvez faire des choses plus détaillées telles que refuser des types de demande spécifiques (par exemple pour autoriser une demande GET pour tous les utilisateurs):

class MyUserPermissions(permissions.BasePermission):

    def has_object_permission(self, request, view, obj):

        # Allow get requests for all
        if request.method == 'GET':
            return True
        return request.user == obj

Ensuite, à votre avis, vous lui dites d'utiliser la classe d'autorisations:

from my_custom_permissions import MyUserPermissions

class UserView(generics.ListCreateAPIView):
    ...
    permission_classes = (MyUserPermissions, )
    ...
20
will-hart

J'ai un besoin similaire. Permet d'appeler mon application x. Voici ce que j'ai trouvé.

Tout d'abord, mettez ceci dans x/viewsets.py:

# viewsets.py
from rest_framework import mixins, viewsets

class DetailViewSet(
  mixins.CreateModelMixin,
  mixins.RetrieveModelMixin,
  mixins.UpdateModelMixin,
  mixins.DestroyModelMixin,
  viewsets.GenericViewSet):
    pass

class ReadOnlyDetailViewSet(
  mixins.RetrieveModelMixin,
  viewsets.GenericViewSet):
    pass

class ListViewSet(
  mixins.ListModelMixin,
  viewsets.GenericViewSet):
    pass

Puis dans x/permissions.py:

# permissions.py
from rest_framework import permissions

class UserIsOwnerOrAdmin(permissions.BasePermission):
    def has_permission(self, request, view):
        return request.user and request.user.is_authenticated()

    def check_object_permission(self, user, obj):
        return (user and user.is_authenticated() and
          (user.is_staff or obj == user))

    def has_object_permission(self, request, view, obj):
        return self.check_object_permission(request.user, obj)

Puis dans x/views.py:

# views.py
from x.viewsets import DetailViewSet, ListViewSet
from rest_framework import permissions

class UserDetailViewSet(DetailViewSet):
    queryset = User.objects.all()
    serializer_class = UserDetailSerializer
    permission_classes = (UserIsOwnerOrAdmin,)

class UserViewSet(ListViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes (permissions.IsAdminUser,)

Soit dit en passant, notez que vous pouvez utiliser un sérialiseur différent pour ces deux ensembles de vues, ce qui signifie que vous pouvez afficher différents attributs dans la vue list que dans la vue retrieve! Par exemple:

# serializers.py
class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ('username', 'url',)

class UserDetailSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ('url', 'username', 'groups', 'profile', 'password',)
        write_only_fields = ('password',)

Puis dans x/urls.py:

# urls.py
from x import views
from rest_framework import routers

router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'users', views.UserDetailViewSet)

...

J'ai été légèrement surpris que router ait accepté le même schéma deux fois, mais cela semble fonctionner.

Avertissement: j'ai confirmé que tout fonctionne via le navigateur API, mais je n'ai pas essayé mise à jour via l'API encore.

13
Tim Ruddick

Pour les trébuchements, la documentation sous les limitations de l'autorisation au niveau objet dit:

For performance reasons the generic views will not automatically apply object level permissions to each instance in a queryset when returning a list of objects.

Ainsi, la vue détaillée fonctionnera mais pour la liste, vous devrez filtrer par rapport à l'utilisateur actuel.

9
keni

Encore une chose à la réponse de @ will-hart.

Dans la documentation DRF3,

Remarque: La méthode has_object_permission au niveau de l'instance ne sera appelée que si les contrôles has_permission au niveau de la vue sont déjà passés.

Donc, has_permission doit être spécifié pour utiliser has_object_permission.

from rest_framework import permissions

class MyUserPermissions(permissions.BasePermission):

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

    def has_object_permission(self, request, view, obj):
        return request.user == obj

Cependant, le code ci-dessus donnera la permission à n'importe qui lorsque l'utilisateur essaie d'obtenir la liste des utilisateurs. Dans ce cas, il serait préférable de donner la permission selon action, pas le HTTP method.

from rest_framework import permissions

def has_permission(self, request, view):
    if request.user.is_superuser:
        return True
    Elif view.action == 'retrieve':
        return True
    else:
        return False

def has_object_permission(self, request, view, obj):
    if request.user.is_superuser:
        return True
    Elif view.action == 'retrieve':
        return obj == request.user or request.user.is_staff
7
Chemical Programmer