2009-09-23 8 views
8

J'essaie d'implémenter des contrôles de sécurité basés sur les lignes pour les modèles Django. L'idée est que lorsque j'accède au gestionnaire de modèles, je spécifie des informations supplémentaires qui sont utilisées dans les requêtes de base de données, de sorte que seules les instances autorisées sont extraites de la base de données. Par exemple, nous pouvons avoir deux modèles: Utilisateurs et, disons, Articles. Chaque article appartient à un utilisateur et l'utilisateur peut être connecté à plusieurs articles. Et laissez-y quelques restrictions, selon lesquelles un utilisateur peut voir ou ne pas voir les articles d'un autre utilisateur. Je veux séparer ces restrictions d'autres éléments de la requête et écrire quelque chose comme:Modèles Django - transmettre des informations supplémentaires au gestionnaire

items = Item.scoped.forceRule('user1').all() # all items visible for 'user1' 

ou

# show all items of 'user2' visible by 'user1' 
items = Item.scoped.forceRule('user1').filter(author__username__exact = 'user2') 

Pour acheive cela, je fait ce qui suit:

class SecurityManager(models.Manager): 

    def forceRule(self, onBehalf) : 
     modelSecurityScope = getattr(self.model, 'securityScope', None) 
     if modelSecurityScope : 
      return super(SecurityManager, self).get_query_set().filter(self.model.securityScope(onBehalf)) 
     else : 
      return super(SecurityManager, self).get_query_set() 

    def get_query_set(self) : 
     # 
     # I need to know that 'onBehalf' parameter here 
     # 
     return super(SecurityManager, self).get_query_set() 

class User(models.Model) : 
    username = models.CharField(max_length=32, unique=True) 

class Item(models.Model) : 
    author = models.ForeignKey(User) 
    private = models.BooleanField() 
    name = models.CharField(max_length=32) 

    scoped = SecurityManager() 

    @staticmethod 
    def securityScope(onBehalf) : 
     return Q(author__username__exact = onBehalf) | Q(bookmark__private__exact = False) 

Pour des exemples présentés, il fonctionne très bien , mais meurt le suivant:

items = Item.scoped.forceRule('user1').filter(author__username__exact = 'user2') # (*) 
items2 = items[0].author.item_set.all() # (**) 

Certainement, items2 est rempli par tous les éléments de 'user2', pas seulement ceux qui sont conformes à la règle. En effet, lorsque la commande all() est exécutée, SecurityManager.get_query_set() ne contient aucune information sur l'ensemble de restrictions. Bien que ça puisse. Par exemple, dans forceRule(), je pourrais ajouter un champ pour chaque instance et ensuite, si je pouvais accéder à ce champ depuis le gestionnaire, appliquer la règle nécessaire. Donc, la question est - est-il un moyen de passer un argument fourni à forceRule() dans l'instruction (*) au gestionnaire, appelé dans l'instruction (**).

Ou une autre question - est-ce que je fais des choses étranges que je ne devrais pas faire du tout?

Merci.

+0

Je suis confronté au même cas d'utilisation aussi, je me demande comment vous avez réussi à contourner cela? Merci! – ultrajohn

Répondre

4

De ma lecture de la documentation, je pense qu'il ya deux problèmes:

  1. Le SecurityManager ne sera pas utilisé pour les objets associés (et par exemple de django.db.models.Manager sera utilisé à la place)
  2. Vous pouvez corriger ce qui précède, mais la documentation passe à lengths pour spécifier que get_query_set() ne doit filtrer aucune ligne pour les requêtes associées.

Je suggère de créer une fonction qui prend un QuerySet et applique le filtre dont vous avez besoin. Cela peut ensuite être utilisé lorsque vous arrivez à un QS d'éléments et que vous voulez les traiter plus loin.

+0

Merci pour la réponse. 1. J'ai lu la documentation et j'ai été très surpris quand pendant 'items [0] .author.item_set.all()' Django s'appelait SecurityManager.get_query_set(). Je ne leur ai pas demandé d'utiliser use_for_related_fields. Mais c'était le seul manager pour le modèle, peut-être que c'est important. 2. Oui, je l'ai lu mais pense toujours que ce n'est pas mon cas, parce que je veux vraiment que Django ne voit pas toute la base de données mais une partie de celui-ci. Probablement, je vais faire comme vous l'avez suggéré, mais ma question n'est pas comment filtrer les données non pertinentes, mais pour rendre ce filtrage aussi facile et aussi automatique que possible. –

Questions connexes