Django-guardian: user.has_perm("perm", obj) se comporte de manière inattendue

Créé le 3 sept. 2011  ·  28Commentaires  ·  Source: django-guardian/django-guardian

Si j'utilise la méthode standard user.has_perm("perm") , elle renverra uniquement True si l'utilisateur dispose d'une autorisation globale "perm" .
Et si user.has_perm("perm", obj) est utilisé, il retournera True si l'utilisateur a la permission d'accéder à cet objet particulier.
Mais il renverra False , même si l'utilisateur a une autorisation globale "perm" , ce qui est assez inattendu pour moi, car je suppose que l'autorisation globale devrait donner à l'utilisateur l'accès à tous les objets. Ai-je raison?

Enhancement

Commentaire le plus utile

Salut, pouvez-vous coller votre paramètre _AUTHENTICATION_BACKENDS_ ?

Comme vous avez déjà lu la documentation de Django, vous savez que l'ordre des backends spécifiés EST important.

Je suppose que vous avez quelque chose comme suit dans votre application:

AUTHENTICATION_BACKENDS = (
    'guardian.backends.ObjectPermissionBackend',
    'django.contrib.auth.backends.ModelBackend',
)

Ou ca:

from django.conf import global_settings
AUTHENTICATION_BACKENDS = (
    'guardian.backends.ObjectPermissionBackend',
) + global_settings.AUTHENTICATION_BACKENDS

Assurez-vous simplement que le backend par défaut est spécifié en premier.

Pouvez-vous confirmer que c'est bien le problème ? Si ce n'est pas le cas, veuillez ajouter quelques informations supplémentaires (peut-être utilisez-vous également d'autres backends, ou d'autres correctifs de singe d'application qui utilisent la méthode _User.has_perm_ ?).

Tous les 28 commentaires

J'ai creusé un peu plus et découvert que chaque backend d'autorisation devrait fonctionner indépendamment, il ne devrait donc pas y avoir de situation comme je l'ai décrit ci-dessus. Mais pour une raison quelconque, appeler user.has_perm fournissant une instance d'objet, cherche à vérifier uniquement les autorisations au niveau de l'objet et ignore la vérification des autorisations globales. Je n'ai aucune idée, quelle est la raison de ce comportement. J'utilise Django 1.2.5.

Salut, pouvez-vous coller votre paramètre _AUTHENTICATION_BACKENDS_ ?

Comme vous avez déjà lu la documentation de Django, vous savez que l'ordre des backends spécifiés EST important.

Je suppose que vous avez quelque chose comme suit dans votre application:

AUTHENTICATION_BACKENDS = (
    'guardian.backends.ObjectPermissionBackend',
    'django.contrib.auth.backends.ModelBackend',
)

Ou ca:

from django.conf import global_settings
AUTHENTICATION_BACKENDS = (
    'guardian.backends.ObjectPermissionBackend',
) + global_settings.AUTHENTICATION_BACKENDS

Assurez-vous simplement que le backend par défaut est spécifié en premier.

Pouvez-vous confirmer que c'est bien le problème ? Si ce n'est pas le cas, veuillez ajouter quelques informations supplémentaires (peut-être utilisez-vous également d'autres backends, ou d'autres correctifs de singe d'application qui utilisent la méthode _User.has_perm_ ?).

Ok, il semble que je sois trop fatigué. L'ordre ne doit pas affecter le résultat _has_perm_. Veuillez donc ajouter plus d'informations sur les paramètres d'application que vous utilisez. De plus, vous pouvez vous assurer que le tuteur fonctionne correctement en exécutant la suite de tests (_python manage.py test guardian_).

Oh, d'accord, j'ai lu votre problème une fois de plus. _auth.ModelBackend_ par défaut ne prend PAS en charge _supports_object_permissions_ (cet attribut est _False_). Selon la documentation de Django, ils ajouteraient la prise en charge du backend par défaut à partir de la version 1.4.

Donc, pour votre situation, le comportement est absolument correct et attendu. Le backend par défaut est simplement omis.

Vous devez vérifier les autorisations globales avant de vérifier les autorisations au niveau de l'objet dans votre application. C'est la solution la plus simple à laquelle je puisse penser.

Fermeture en tant que _invalide_, mais si vous souhaitez le rouvrir avec un nouveau commentaire, n'hésitez pas à le faire !

Ok, on dirait que tu as raison. Mais ne pas vérifier les autorisations globales m'apporte de sérieuses difficultés. Par exemple, dans guardian.decorators.permission_required , qui, comme je l'ai supposé, devrait étendre les fonctionnalités de django.contrib.auth.decorators.permission_required ordinaire avec une vérification supplémentaire des autorisations au niveau de l'objet.
Le problème est que j'ai une application fonctionnelle qui utilise des autorisations globales et que je voulais y ajouter des autorisations supplémentaires au niveau de l'objet. J'ai donc changé les décorateurs permission_required par défaut en décorateurs au niveau objet de django-guardian, puis les utilisateurs avaient perdu leurs droits d'accès basés sur les autorisations globales.

Cela ressemble à un comportement inattendu pour moi, pourriez-vous au moins ajouter ce cas à la documentation, car ce n'est pas évident sans regarder dans le code.

Quant à moi, il s'agit d'un défaut de conception, entraînant une vérification supplémentaire dans tout le code où je dois vérifier l'autorisation à la fois globalement et par objet.

@coagulant : il ne serait pas flexible de réellement _étendre_ les décorateurs de l'authentification de Django. Cette application a pour but d'implémenter les _autorisations d'objet_, sans étendre celle d'origine. Par exemple, que se passe-t-il si vous souhaitez utiliser une autre application qui utilise uniquement les autorisations au niveau du tuteur et de l'objet ? Que se passe-t-il si vous souhaitez utiliser les autorisations globales au niveau de l'administrateur uniquement et les autorisations au niveau de l'objet dans les applications accordées aux utilisateurs réguliers ? Et si l'application nécessitait à la fois une autorisation globale et une autorisation au niveau de l'objet pour que l'utilisateur puisse effectuer une action sur l'objet ? Il existe de nombreux cas, le tuteur ne les couvrirait pas tous.

D'un autre côté, je peux admettre que ce cas d'utilisation particulier pourrait être plus courant que les autres. Pour toute personne intéressée par cela - veuillez déposer un autre numéro en précisant les exigences. Je pense que cela peut être facilement réalisé sans incompatibilité des backwords - c'est-à-dire avec un nouveau paramètre de configuration.

Ce serait utile pour moi, s'il y avait juste un autre décorateur permission_required ajouté, qui vérifie à la fois les autorisations au niveau de l'objet et les autorisations globales. Cela couvrirait mon problème.

@Dzejkob , pouvez-vous s'il vous plaît vérifier le dernier commit et dire si cela suffirait (j'ai ajouté un indicateur pour accepter les autorisations globales). Aussi, faites-moi savoir si l'extension de docstring au décorateur lui-même est suffisante, ou devrais-je ajouter plus d'exemples/plus de documents descriptifs ?

Remarque : le commit est dans la nouvelle branche : _feature/add-accept_global_perms-flag-for-decorators_

@lukaszb Oui, j'ai installé une nouvelle version de branche de Guardian dans mon projet, ajouté un paramètre dans les décorateurs $ permission_required accept_global_perms=True , et cela semble fonctionner comme il se doit. Alors merci d'avoir créé cette fonctionnalité. Je pense que c'est une bonne idée de le fusionner avec le coffre.
À propos de docs, ils sont assez clairs pour moi, donc je pense que c'est suffisant.

Cela a été corrigé il y a longtemps.

Bonjour, j'ai trouvé ce problème alors que j'essayais jango-guardian et j'ai trouvé ce comportement bogué/très déroutant.

J'ai ajouté une autorisation globale (appelons-la view_user ) à un utilisateur (appelons-la joe ) puis vérifié si Joe peut voir un utilisateur spécifique (appelons-le other_user ) et étonnamment, il a renvoyé False .

joe = User.objects.get(username="joe")
other_user = User.objects.get(username="other_user")
assign_perm("myapp.view_user", joe)
joe.has_perm("myapp.view_user") # True as expected
joe.has_perm("myapp.view_user", other_user) # False, whaaaat?

Maintenant, je n'utilise pas explicitement le décorateur permission_required car mes ensembles de vues vérifient "automatiquement" les autorisations à cause de mes REST_FRAMEWORK/DEFAULT_PERMISSION_CLASSES et AUTHENTICATION_BACKENDS dans settings.py . Comment puis-je dire au tuteur de se comporter comme prévu alors ?

(Désolé si je rate quelque chose de vraiment stupide, c'est un peu le jour 2/3 à Djangoland)

Désolé pour le bruit aujourd'hui, encore plus intéressant/déroutant que guardian.shortcuts.get_objects_for_user() respecte les autorisations auth/globales par défaut ( accept_global_perms ).

accept_global_perms: if True takes global permissions into account. 
[...]
Default is True.

Bien que ce problème ait été clos au profit d'un autre, il y a beaucoup d'idées ici, alors j'écris ici pour relancer la conversation. Zen du Python dit :

Il doit y avoir une - et de préférence une seule - manière évidente de le faire.

Et pour moi, c'est _revenir à global lorsque local (niveau objet) n'est pas spécifié_. La commande ne change pas le résultat (depuis Django returns False if obj is not None ), mais peut changer les performances.

Je suis d'accord qu'avoir des doubles vérifications partout dans le code n'est pas une bonne conception, et dans un certain sens contre le principe DRY aussi. Cependant, la fonctionnalité d'implémentation du tuteur disponible dans le backend par défaut de Django n'est pas DRY non plus. Le fait que Django autorise plusieurs backends et vérifie un par un dans l'ordre indique que les backends sont censés jouer ensemble et non se remplacer. Si c'est vrai, alors Django refuse de vérifier les globales quand un obj is not None est _faux_. Si Django a ignoré le obj , cela pourrait être la solution de repli globale pour les vérificateurs d'objets.

Je pense que nous devrions ouvrir un ticket avec Django, demandant si, et comment, les backends d'autorisation jouent les uns avec les autres, puis partir de là.

Cela étant dit, je pense que Django change son comportement est assez peu probable (bien que je pense toujours que nous devrions demander), donc le statu quo indique que, le tuteur doit évoluer pour pouvoir fournir les deux en fonction de celui qui est souhaité (ce qui peut être défini via les paramètres ou un argument de la fonction). Mais le comportement par défaut, je pense, devrait être le repli _local sur global lorsque false_ dans tous les domaines.

Je préférerais que Django préfère un système d'autorisation à trois états : vrai, faux et aucun. Dans ce cas, les vérificateurs locaux pourraient _écraser_ les variables globales via False ; et via None chaque backend pourrait dire : "Je ne sais pas, demandez au suivant". Dans ce cas, Django arrêterait de vérifier après avoir obtenu un True or False dans l'un des backends, et arrêterait de gaspiller de la puissance de traitement.

Cela donnerait plus de pouvoir à chaque backend : peut donner une réponse définitive, ou se référer à d'autres.

@doganmeh Django implémente un système d'autorisations à trois états (au moins à partir de la version 1.10). Les options sont True, None et augmentation de PermissionDenied. Faire le plus tard entraînera l'arrêt de la vérification par Django et renverra False.

En ce qui concerne le problème en question, je pense qu'il s'agit d'un problème de Contrib.Auth. C'est le backend qui gère les "autorisations globales". Le problème est qu'ils ont établi une convention indirecte selon laquelle has_perm avec obj=None ne vérifie que les "autorisations de table" et has_perm avec obj ne vérifie que les "autorisations de ligne". Il est peu probable qu'ils changent cela, mais _devraient_ être ouverts à l'extension de leur API pour prendre en charge la vérification des deux.

(Au moins, j'espère qu'ils seraient ouverts à l'ajout d'un comportement pour vérifier les deux. Cela semble être une faille évidente dans leur système.)

Quelles seraient vos suggestions pour l'API "vérifier les deux" ? Le mieux auquel je puisse penser est un kwarg.

Je parlais d'un système explicitement à trois états où il y aurait un NullBooleanField sur la table Permission. Certes, si vous considérez l'absence de True (ou l'absence de permission) comme un None , cela pourrait être considéré comme un état triple. Dans ce cas, les autorisations non spécifiées par le tuteur doivent être considérées comme None , et la décision doit être déléguée au global. Si j'avais le choix, cependant, je prendrais la conception explicite.

@airstandley Je suppose que vous voulez dire l'ajout d'un kwarg à user.has_perm tel que :

def has_perm(self, perm, obj=None, fallback=False)

Je pense que cela aiderait. Dans le cas où fallback=True , le tuteur appellerait le backend Django et renverrait le global. Si j'avais le choix, cependant, je préférerais que le recours à Django soit naturellement géré par le framework, et non en détournant ses composants internes.

@doganmeh Désolé je me suis mal exprimé. Cela aurait dû se lire "True, False , and raise PermissionDenied". Pas:

Les options sont True, None et augmentation de PermissionDenied

Le plus tard, c'est comme ça que je pense aux retours dans ma tête...

Je pense que j'ai peut-être aussi mal compris ce que tu voulais dire par

Je préférerais que Django préfère un système de permission à trois états

Pour clarifier, vous parliez spécifiquement de la mise en œuvre du modèle d'autorisation et non du système dans son ensemble ?

Mon point était que le système backend d'authentification permet le raccourci exact que vous avez décrit (au moins pour l'API has_perm , has_module_perms ). C'est explicite, sinon direct :
Tout serveur principal peut soit prendre une décision lui-même (retourner True ou PermissionDenied), soit déléguer la décision au serveur suivant dans la chaîne (retourner False). Il s'agit d'une décision spécifique à la mise en œuvre, et non d'une qualité du système lui-même.

Un ObjectPermissionBackend explicite serait un problème pour mon projet, donc je choisirais de ne pas être explicite. ("Explicitness" rend l'intégration avec d'autres backends de vérification des autorisations fastidieuse.) Je soupçonne que, comme vous, d'autres préféreraient que ce soit explicite. Donc, avoir le « explicite » comme paramètre a du sens pour moi.

@doganmeh
Donc à propos du kwarg.

Tout d'abord, je continue de m'inquiéter, nous ne sommes pas sur la même page quant à la façon dont le comportement du système backend d'auth était censé fonctionner. Donc pour être clair :
Je crois comprendre que l'intention était que les backends fonctionnent ensemble. Si une application voulait utiliser le système d'autorisations d'auth (c'est-à-dire des "autorisations globales", bien que techniquement, il s'agirait de "autorisations de table", mais potatoe, potatoe), elle installerait le ModelBackend auth dans AUTH_BACKENDS. Si cette application souhaitait utiliser le système ObjectPermission du tuteur (c'est-à-dire "l'autorisation de ligne"), ce ne serait pas le ObjectPermissionBackend du tuteur dans AUTH_BACKENDS.
ModelBackend gérerait l'autorisation globale.
ObjectPermissionBackend gérerait les autorisations d'objet.
si un utilisateur n'avait qu'une autorisation d'objet, ModelBackend ne renverrait jamais True pour has_perm . Si un utilisateur n'avait qu'une autorisation globale, ObjectPermissionBackend ne renverrait jamais True pour has_perm . Dans les deux cas, un appel à user.has_perm(perm, obj) doit toujours renvoyer true, car l'utilisateur a l'autorisation pour _one_ des backends installés. (C'est là que nous avons rencontré un problème car ModelBackend échoue sur ce compte)

Ok donc étant donné tout ça.

Dans un monde idéal où la compatibilité n'était pas un problème, je préférerais moi aussi un simple changement au ModelBackend de contrib.auth. ModelBackend.has_perm(user_obj, perm, obj=None) doit renvoyer True si l'utilisateur dispose de la "permission globale" spécifiée par perm ; que obj soit None ou non.

Cependant, la compatibilité est un problème, c'est pourquoi je vous demande si vous avez une solution à ce problème.

Les seules solutions _rétrocompatibles_ auxquelles je peux penser sont soit d'ajouter un kwarg à l'API, soit d'ajouter un paramètre AUTH.

Alors kwargs :
Appelons ça obj_shortcut parce que c'est le mieux que je puisse trouver en tête. object_shortcut serait par défaut sur True. Si object_shortcut est True, alors les backends doivent se comporter comme le ModelBackend le fait maintenant : ils doivent _uniquement_ vérifier les autorisations 'table/global' si obj est None, sinon ils doivent _uniquement_ vérifier les autorisations 'row/object'. Cependant, si object_shortcut est False, les backends devraient se comporter comme nous le préférerions tous les deux : ils vérifieraient _les deux_ autorisations globales et d'objet lorsque l'objet n'était pas Aucun. Ensuite, des add-ons comme Guardian pourraient toujours fournir un mixin avec une méthode has_perm avec object_shortcut=False par défaut. user.has_perm(perm, obj, object_shortcut=False) renverrait correctement True si l'autorisation représentée par perm était attribuée à User, tandis que les appels hérités à user.has_perm(perm, obj) continueraient à renvoyer False.

Un paramètre aurait le même résultat mais entraînerait essentiellement un changement dans ModelBackend._get_permissions .

if not user_obj.is_active or user_obj.is_anonymous or obj is not None:
    return set()

deviendrait quelque chose comme :

if not user_obj.is_active or user_obj.is_anonymous or (obj is not None and legacy_behaviour_setting_is _on):
    return set()

Je ne sais pas ce qui est mieux. Je pense qu'un paramètre est plus propre, mais je suis sûr que les développeurs de Django seraient plus qualifiés pour le déterminer.

Le fait est que le problème peut être résolu, et je crois fermement que le problème est un bogue Django, pas Guardian.

@airstandley Vous avez raison, False ou None n'a pas d'importance. Ils signifient tous les deux que je ne sais pas . Finalement, si personne ne le sait, la permission n'est pas accordée.

Je suis généralement sur la même longueur d'onde que vous, bien que django oblige les backends à se comporter d'une manière ou d'une autre semble dégrader vaguement le couplage. Je pense qu'il devrait avoir des mécanismes pour permettre à chaque backend d'obtenir les informations dont il a besoin pour faire son travail. Je suis avec vous sur le ou les arguments de mot-clé, mais pas seulement en forçant d'une manière ou d'une autre.

Cette conversation semble être une perte de temps, mais elle m'a aidé à clarifier à moi-même qu'il n'y a pas d'autorisation accordée au tuteur n'est pas un déni, c'est juste un manque d'informations.

Bref, j'ai fait une pull request #546. S'il vous plaît vérifier et laissez-moi savoir ce que vous pensez.

J'ai créé un ticket avec Django : https://code.djangoproject.com/ticket/29012 , et je me demande ce qu'ils diraient.

Il semble qu'il y ait eu d'autres tickets avec Django : https://code.djangoproject.com/ticket/20218

J'ai fermé le mien.

@doganmeh
J'aime la direction que vous prenez avec #546. Si cela pouvait aider, je devrais avoir le temps de regarder le reste des unités et d'écrire quelques tests pour ces replis ce week-end.

Sur une tangente. Votre commentaire sur "Django forçant les backends" à se comporter d'une manière spécifique me déroute, mais cela m'a aussi fait réfléchir. Les backends peuvent se comporter comme ils le souhaitent ; ma préoccupation initiale concernant ObjectPermissionBackend de Guardian découle de la documentation suggérant qu'il a été conçu pour fonctionner avec ModelBackend d'Auth. Guardian pourrait proposer plusieurs backends, un conçu pour fonctionner avec ModelBackend et un conçu pour fonctionner seul. (C'est-à-dire l'un qui vérifie simplement les tables UserObjectPermission/GroupObjectPermission de Guardian, l'autre vérifie à la fois les tables UserObjectPermission/GroupObjectPermission et la table Permission d'Auth.)
Personnellement je préfère l'approche actuelle avec un réglage et des kwargs. Je pense que le principal inconvénient d'une approche multi-backend serait qu'il devient difficile de savoir comment les raccourcis et les fonctions de commodité doivent se comporter.

J'ai vu le message sur la liste de diffusion Django dev. J'espère qu'ils acceptent cela, ou acceptent de changer le comportement d'une manière incompatible. La limitation actuelle de l'API est juste maladroite.

Pouvez-vous le soutenir là-bas? 😊

Envoyé depuis Mail pour Windows 10

De : airstandley
Envoyé : vendredi 12 janvier 2018 12:06
À : django-guardian/django-guardian
Copie : Mehmet Dogan ; Mention
Objet : Re : [django-guardian/django-guardian] user.has_perm("perm", obj)se comporte de manière inattendue (#49)

J'ai vu le message sur la liste de diffusion Django dev. J'espère qu'ils acceptent cela, ou acceptent de changer le comportement d'une manière incompatible. La limitation actuelle de l'API est juste maladroite.

Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub ou désactivez le fil de discussion.

Après avoir traité ce problème pendant environ une semaine maintenant, j'en suis venu à croire plus fermement que chaque backend ne doit faire qu'une seule chose, à savoir les autorisations d'objet pour Guardian. Pour que cela se produise, Django doit traiter les obj plus gentiment.

Il y a le patch que j'ai envoyé à Django pour ça : https://github.com/django/django/pull/9581 (veuillez commenter si vous le pouvez). Ceux qui réussissent, nous pouvons nettoyer la récupération des autorisations de modèle partout où il y en a dans Guardian, et il suffit de faire un appel au backend par défaut.

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

BenDevelopment picture BenDevelopment  ·  5Commentaires

lukaszb picture lukaszb  ·  14Commentaires

David-OConnor picture David-OConnor  ·  6Commentaires

g-as picture g-as  ·  10Commentaires

johnthagen picture johnthagen  ·  9Commentaires