0 Apps/Droits
Pierre-antoine Comby edited this page 2020-03-21 18:36:12 +01:00

Droits

Le système de droit par défault de django n'est pas suffisament granulaire pour les besoins de la NoteKfet2020. Un système custom a donc été développé.

Il permet la création de Permission, qui autorise ou non a faire une action précise sur un ou des objets de la base de données.

Plusieurs permissions peuvent être regroupé dans un Role. Ce Role est relié a la responsabilité/fonction de la personne dans un Club. Ils sont valable au maximum tant que la personne est adhérente du Club concerné, mais ce rôle peut être modifié/supprimé avant le terme.

Permission

Une permission est un Model Django dont les principaux attributs sont:

  • model : Le model sur lequel cette permission va s'appliquer
  • type : Les différents types d'interaction sont : voir (view), modifier (change), ajouter (add) et supprimer (delete).
  • query : Requete sur la cible, encodé en JSON, traduit en un Q object (cf Query
  • field : le champ cible qui pourra etre modifié. (tous les champs si vide)

Pour savoir si un utilisateur a le droit sur un modèle ou non, la requête est compilée (voir ci-dessous) en un fitre de requête dans la base de données, un objet de la classe Q (En SQL l'objet Q s'interprete comme tout ce qui suit un WHERE ... Ils peuvent être combiné à l'aide d'opérateurs logiques. Plus d'information sur les Q object dans la documentation officiel )

Ce Q object sera donc utilisé pour savoir si l'instance que l'on veux modifier est concernée par notre permission.

Exception faite sur l'ajout d'objets : l'objet n'existant pas encore en base de données, il est ajouté puis supprimé à la volée, en prenant soin de désactiver les signaux.

Compilation de la query

La query est enregistrée sous un format JSON, puis est traduite en requête Q récursivement en appliquant certains paramètres. Le fonctionnemente de base des permission peux être décris avec les differents opérations :

opérations JSON Q object
ALL [] ou {} .objects.get(Q(pk=F("pk"))
ET ["AND", Q_1, Q_2, ... ] .objects.get(Q_1 & Q_2 & ...)
OU ["OR", Q_1, Q_2, ...] .objects.get(Q_1 | Q_2 | ...)
NON ["NOT", Q_1] .objects.get(~Q_1)
key == value {key: value} .objects.get(Q(key = value))

De plus value peux être utilisé de différetentes manières:

  • Un nombre, une chaîne de caractères, un booléen, null pour représenter des valeurs littérales
  • Si c'est une liste, c'est interprété comme un paramètre
    • Le premier argument de la liste représente le nom du paramètre. Ce peut être l'objet utilisateur user, l'objet club club ou des objets de classe (Note, ...).
    • S'il y a des arguments suivants dans la liste, ils vont être interprétés comme des sous-attributs du paramètre initial.
    • Si un argument est une liste, alors c'est interprété comme une fonction à appeler, dont les arguments sont donnés par les autres éléments de la liste. Si cette liste contient un dictionnaire, le dictionnaire est passé en argument à la fonction sous la forme de kwargs.
  • Si c'est un dictionnaire de la forme {"F": oper}, alors c'est interprété comme un objet F.
    • Si oper est un littéral, il est interprété tel quel.
    • Si oper = ["ADD", oper1, oper2, …], le résultat est la somme des objets F calculés d'après le reste de la liste.
    • Si oper = ["SUB", oper1, oper2,], le résultat est la différence des deux objets F.
    • Si oper = ["MUL", oper1, oper2, …], le résultat est le produit des objets F calculés d'après le reste de la liste.
    • Si oper = ["F", name], le résultat est F(name).
    • Dans les autres cas, oper est un paramètre calculé comme précédemment.

Vous n'avez rien compris ? Voilà des exemples:

Exemples

  • Permission sur le model User avec comme query:

    {"is_superuser": true}
    

    si l'utilisateur cible est un super utilisateur.

  • sur le model Note:

    {"pk": 
      ["user","note", "pk"]
    }
    

    si l'identifiant de la note cible est l'identifiant de l'utilisateur dont on regarde la permission.

  • sur le model Transaction:

    ["AND",
      {"source":
        ["user", "note"]},
      {"amount__lte":
        ["user", "note", "balance"]}
    ]
    

    si la source est la note de l'utilisateur et si le montant est inférieur à son solde.

  • Sur le model Alias

    ["OR",
      {"note__in": 
        ["NoteUser", "objects",[
          "filter",{
            "user__membership__club__name": "Kfet"
          }],
          ["all"]
        ]},
      {"note__in": 
        ["NoteClub", "objects", ["all"]]
      }
    ]
    

    si l'alias appartient à une note de club ou s'il appartient à la note d'un utilisateur membre du club Kfet.

  • sur le model Transaction

    ["AND",
      {"destination": ["club", "note"]},
      {"amount__lte": 
         {"F": [
           "ADD",
           ["F", "source__balance"],
           5000]
         }
       }
    ]
    

    si la destination est la note du club dont on est membre et si le montant est inférieur au solde de la source + 50 €, autrement dit le solde final est au-dessus de -50 €.

Masques de permissions

Chaque permission est associée à un masque. À la connexion, l'utilisateur choisit le masque de droits avec lequel il souhaite se connecter. Les masques sont ordonnés totalement, et l'utilisateur aura effectivement une permission s'il est en droit d'avoir la permission et si son masque est suffisamment haut.

Par exemple, si la permission de voir toutes les transactions est associée au masque "Droits note uniquement", se connecter avec le masque "Droits basiques" n'octroiera pas cette permission tandis que le masque "Tous mes droits" oui.

Signaux

À chaque fois qu'un modèle est modifié, ajouté ou supprimé, les droits sont contrôlés. Si les droits ne sont pas suffisants, une erreur est lancée. Pour ce qui est de la modification, on ne contrôle que les champs réellement modifiés en comparant l'ancienne et la nouvele instance.

Models

models_permission.svg