1
votes

Django restreint l'accès aux objets utilisateur

J'ai des modèles Node et User qui appartiennent tous deux à une organisation. Je veux m'assurer qu'un utilisateur ne verra jamais que les instances de nœud appartenant à son organisation.

Pour cela, je souhaite remplacer le gestionnaire d'objets de nœud par un qui renvoie un ensemble de requêtes de résultats filtrés appartenant à l'utilisateur.

Basé sur https://docs.djangoproject.com/en/2.1/topics/db/managers/#modifying-a-manager-s-initial-queryset le code models.py que je possède est ci-dessous:

class NodeListView(LoginRequiredMixin, generic.ListView):
    model = Node

    def get_queryset(self):
        return Node.objects.filter(organisation__users__id=self.request.user.pk)

views.py

class NodeListView(LoginRequiredMixin, generic.ListView):
    model = Node

MODIFIER Je peux ajouter un ensemble de requêtes personnalisé à des vues individuelles et cela fonctionne comme ci-dessous:

views.py

class Organisation(models.Model):
    users = models.ManyToManyField(User, related_name='organisation')
    ...

class UserNodeManager(models.Manager):
    def get_queryset(self, request):
        return super().get_queryset().filter(organisation=self.request.user.organisation.first())


class Node(models.Model):
    organisation = models.ForeignKey(
        Organisation, related_name='nodes', on_delete=models.CASCADE)

    uuid = models.UUIDField(primary_key=True, verbose_name="UUID")
    ...

    objects = UserNodeManager

Cependant, mon l'intention est d'être DRY et de remplacer une méthode query_set 'maître' en un seul point afin que toute vue (par exemple, liste déroulante de formulaire, point de terminaison d'API) exécute la requête restreinte par l'utilisateur sans code supplémentaire.

Par exemple, J'utilise les vues de liste génériques de django ont un formulaire pour ajouter un objet Scan qui oblige un utilisateur à sélectionner un nœud auquel appartient le scan. Le formulaire affiche actuellement les nœuds d'autres organisations, ce qui va à l'encontre de la logique des autorisations dont j'ai besoin.

Malheureusement, la propriété Node.objects remplacée ne semble pas avoir d'effet et tout utilisateur peut voir tous les nœuds. Est-ce que je prends la bonne approche?


6 commentaires

Essayez-vous ce return super (). Get_queryset (). Filter (organisation__users_id = self.re‌ quest.user.pk)


Cela entraîne l'erreur: Le champ associé a obtenu une recherche non valide: user_id


Essayez de changer pour avoir 2 traits de soulignement comme organisation__users__id


@SergeyPugach: oui, cela fonctionne pour un query_set défini dans une vue mais n'a toujours pas d'effet dans le cadre de la classe UserNodeManager ci-dessus.


Comment appelez-vous la méthode get_queryset ? écrasez-vous toutes les méthodes du gestionnaire (c'est-à-dire mettre à jour, obtenir, supprimer)?


@ruddra merci, j'ai ajouté mon code de vue dans la question. Je n'appelle pas explicitement get_queryset . Je ne remplace aucune des méthodes du gestionnaire, uniquement get_queryset .


3 Réponses :


0
votes

Le meilleur moyen d'y parvenir est d'utiliser des groupes et des autorisations personnalisées. Vous pouvez ajouter un groupe pour chaque organisation et définir les autorisations appropriées pour ces groupes sur vos nœuds.

Jetez un œil à cet article, il pourrait vous aider: Groupes d'utilisateurs avec des autorisations personnalisées dans Django


2 commentaires

Merci pour la suggestion Raydel mais si je suis sur la bonne voie et que je peux définir le query_set au niveau du modèle, il semble que cela devrait être moins de travail / maintenance que de définir des groupes supplémentaires. Le but est d'éviter le référencement direct non sécurisé des objets.


@ user1330 Je comprends



1
votes

Je pense que le problème est ici:

from crequest.middleware import CrequestMiddleware

class UserNodeManager(models.Manager):
    def all(self):
       qs = super(UserNodeManager, self).all()
       request = CrequestMiddleware.get_request()
       return qs.filter(...)

Vous devez lancer l'instance UserNodeManager comme ceci:

class NodeListView(LoginRequiredMixin, generic.ListView):
    model = Node

    def get_queryset(self):
        return Node.objects.all(self.request)  # where you can obviously use filter(...) or Model.objects.user_specific_nodes(self.request)

De plus, il devrait générer une erreur lorsque vous appelez la méthode YourModel.objects.all () (qui est appelée à partir de la méthode get_queryset en vue), car lorsqu'elle appelle get_queryset () , il ne passe pas la requête . Donc, je pense que ce serait une meilleure approche:

class UserNodeManager(models.Manager):
    def user_specific_nodes(self, request):
       return self.get_queryset().filter(...)

Ou vous pouvez créer une nouvelle méthode de gestion comme celle-ci (facultatif):

class UserNodeManager(models.Manager):
    def all(self, request=None):
       qs = super(UserNodeManager, self).all()
       if request:
          return qs.filter(...)
       return qs


3 commentaires

Merci Ruddra, je peux ajouter un query_set personnalisé à chaque vue, mais je recherche un seul point que je peux modifier qui limitera les requêtes Nodes à l'échelle de l'application. J'ai mis à jour la question pour clarifier.


dans votre réponse, vous donnez la possibilité d'ajouter UserNodeManager et indiquez également que le get_queryset de NodeListView doit être remplacé. Cependant, je comprends cela car toutes les vues nécessiteront la répétition du même get_queryset , ce qui n'est pas DRY. Mon intention est de changer le signe UserNodeManager afin que toute requête (à partir de n'importe quelle vue HTML, point de terminaison API, etc.) utilise cet ensemble de requêtes «maître». Est-ce possible?


@ user1330734 veuillez consulter la section mise à jour de la réponse. J'espère que cela aide.



0
votes

@ruddra merci encore pour vos conseils.

Bien que votre exemple de middleware n'ait pas eu d'effet pour moi (car l'utilisateur pouvait encore voir les objets des autres), j'ai pu l'utiliser avec la documentation de django pour enfin implémenter le Manager similaire à:

class UserDeviceManager(models.Manager):
    def get_queryset(self):
        request = CrequestMiddleware.get_request()
        return super().get_queryset().filter(organisation=request.user.organisation)


0 commentaires