Django-guardian: user.has_perm("perm", obj) verhält sich unerwartet

Erstellt am 3. Sept. 2011  ·  28Kommentare  ·  Quelle: django-guardian/django-guardian

Wenn ich die Standardmethode user.has_perm("perm") verwende, wird nur True zurückgegeben, wenn der Benutzer eine globale Berechtigung "perm" hat.
Und wenn user.has_perm("perm", obj) verwendet wird, wird True zurückgegeben, wenn der Benutzer die Berechtigung hat, auf dieses bestimmte Objekt zuzugreifen.
Aber es wird False zurückgeben, selbst wenn der Benutzer eine globale Berechtigung "perm" hat, was ziemlich unerwartet für mich ist, weil ich annehme, dass die globale Berechtigung dem Benutzer Zugriff auf alle Objekte geben sollte. Habe ich recht?

Enhancement

Hilfreichster Kommentar

Können Sie Ihre _AUTHENTICATION_BACKENDS_-Einstellung einfügen?

Da Sie die Django-Dokumentation bereits gelesen haben, wissen Sie, dass die Reihenfolge der angegebenen Backends wichtig ist.

Ich nehme an, Sie haben in Ihrer Bewerbung so etwas wie Folgendes:

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

Oder dieses:

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

Stellen Sie einfach sicher, dass zuerst das Standard-Backend angegeben ist.

Können Sie bestätigen, dass dies das Problem ist? Wenn nicht, fügen Sie bitte weitere Informationen hinzu (vielleicht verwenden Sie auch einige andere Backends oder einige andere App-Affen-Patches, die die _User.has_perm_-Methode verwenden?).

Alle 28 Kommentare

Ich habe etwas mehr ausgegraben und herausgefunden, dass jedes Permission-Backend unabhängig arbeiten sollte, also sollte es keine Situation geben, wie ich sie oben beschrieben habe. Aber aus irgendeinem Grund versucht der Aufruf user.has_perm , der eine Objektinstanz bereitstellt, nur die Berechtigungen auf Objektebene zu prüfen und überspringt die globale Berechtigungsprüfung. Ich habe keine Ahnung, was der Grund für dieses Verhalten ist. Ich verwende Django 1.2.5.

Können Sie Ihre _AUTHENTICATION_BACKENDS_-Einstellung einfügen?

Da Sie die Django-Dokumentation bereits gelesen haben, wissen Sie, dass die Reihenfolge der angegebenen Backends wichtig ist.

Ich nehme an, Sie haben in Ihrer Bewerbung so etwas wie Folgendes:

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

Oder dieses:

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

Stellen Sie einfach sicher, dass zuerst das Standard-Backend angegeben ist.

Können Sie bestätigen, dass dies das Problem ist? Wenn nicht, fügen Sie bitte weitere Informationen hinzu (vielleicht verwenden Sie auch einige andere Backends oder einige andere App-Affen-Patches, die die _User.has_perm_-Methode verwenden?).

Ok, ich scheine zu müde zu sein. Die Reihenfolge sollte das _has_perm_-Ergebnis nicht beeinflussen. Fügen Sie also bitte weitere Informationen zu den von Ihnen verwendeten Anwendungseinstellungen hinzu. Darüber hinaus können Sie sicherstellen, dass Guardian ordnungsgemäß funktioniert, indem Sie die Testsuite (_python manage.py test guardian_) ausführen.

Oh, ok, ich habe dein Problem noch einmal gelesen. Standardmäßig unterstützt _auth.ModelBackend_ _supports_object_permissions_ NICHT (dieses Attribut ist _False_). Laut Django-Dokumentation würden sie diese Unterstützung für das Standard-Backend von 1.4 hinzufügen.

Für Ihre Situation ist das Verhalten also absolut korrekt und wird erwartet. Das Standard-Backend wird einfach weggelassen.

Sie sollten globale Berechtigungen überprüfen, bevor Sie Berechtigungen auf Objektebene in Ihrer App überprüfen. Das ist die einfachste Lösung, die ich mir vorstellen kann.

Schließt als _ungültig_, aber wenn Sie es mit einem neuen Kommentar wieder öffnen möchten, können Sie dies gerne tun!

Ok, sieht so aus, als hättest du recht. Aber das Überprüfen der globalen Berechtigungen bringt einige ernsthafte Schwierigkeiten für mich mit sich. Zum Beispiel in guardian.decorators.permission_required , das, wie ich annahm, die Funktionalität des gewöhnlichen django.contrib.auth.decorators.permission_required um eine zusätzliche Berechtigungsprüfung auf Objektebene erweitern sollte.
Das Problem ist, dass ich eine funktionierende Anwendung habe, die globale Berechtigungen verwendet, und ich wollte ihr einige zusätzliche Berechtigungen auf Objektebene hinzufügen, also habe ich die Standarddekoratoren permission_required in diejenigen von django-guardian auf Objektebene geändert, aber dann hatten Benutzer ihre Zugriffsrechte basierend auf globalen Berechtigungen verloren.

Sieht für mich nach einem unerwarteten Verhalten aus. Könnten Sie diesen Fall bitte zumindest der Dokumentation hinzufügen, da dies nicht offensichtlich ist, ohne sich den Code anzusehen.

Für mich ist dies ein Designfehler, der zu einer zusätzlichen Überprüfung des gesamten Codes führt, bei der ich die Berechtigung sowohl global als auch pro Objekt überprüfen muss.

@coagulant : Es wäre nicht flexibel, Decorators aus Djangos Auth zu _erweitern_. Diese App beabsichtigt, _Objektberechtigungen_ zu implementieren und nicht die ursprüngliche zu erweitern. Das heißt, was ist, wenn Sie eine andere App verwenden möchten, die nur Berechtigungen auf Wächter- und Objektebene verwendet? Was ist, wenn Sie globale Berechtigungen nur auf Administratorebene und Berechtigungen auf Objektebene in Apps verwenden möchten, die normalen Benutzern erteilt werden? Was wäre, wenn die App sowohl eine globale Berechtigung als auch eine Berechtigung auf Objektebene erfordern würde, damit der Benutzer eine Aktion für das Objekt ausführen kann? Es gibt viele Fälle, der Vormund würde nicht alle abdecken.

Andererseits kann ich zugeben, dass dieser spezielle Anwendungsfall häufiger vorkommt als die anderen. Für alle, die daran interessiert sind, reichen Sie bitte ein weiteres Problem ein, in dem Sie die Anforderungen angeben. Ich glaube, dass dies leicht ohne Backword-Inkompatibilität erreicht werden kann - dh mit neuen Konfigurationseinstellungen.

Es wäre für mich nützlich, wenn nur ein weiterer permission_required-Dekorator hinzugefügt würde, der sowohl nach Berechtigungen auf Objektebene als auch nach globalen Berechtigungen sucht. Das würde mein Problem abdecken.

@Dzejkob , können Sie bitte den letzten Commit überprüfen und sagen, ob dies ausreichen würde (ich habe ein Flag zum Akzeptieren globaler Berechtigungen hinzugefügt). Lassen Sie mich auch wissen, ob das Erweitern von Docstring bei Decorator selbst ausreicht, oder sollte ich weitere Beispiele/aussagekräftigere Dokumente hinzufügen?

Hinweis: Commit befindet sich im neuen Zweig: _feature/add-accept_global_perms-flag-for-decorators_

@lukaszb Ja, ich habe eine neue Branch-Version von Guardian in meinem Projekt installiert, Parameter in permission_required decorators accept_global_perms=True hinzugefügt, und es scheint zu funktionieren, wie es sollte. Vielen Dank, dass Sie diese Funktion erstellt haben. Ich denke, es ist eine gute Idee, es in den Kofferraum zu verschmelzen.
Über Dokumente, sie sind mir ziemlich klar, also denke ich, dass es genug ist.

Dies wurde vor langer Zeit behoben.

Hallo, ich habe dieses Problem gefunden, als ich Jango-Guardian ausprobiert habe, und ich fand dieses Verhalten fehlerhaft/sehr verwirrend.

Ich habe einem Benutzer (nennen wir sie joe ) eine globale Berechtigung (nennen wir sie view_user ) hinzugefügt und dann überprüft, ob Joe einen bestimmten Benutzer (nennen wir sie other_user ) anzeigen kann ) und überraschenderweise gab es False zurück.

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?

Jetzt verwende ich den Dekorator permission_required nicht explizit, da meine Ansichtssätze die Berechtigungen aufgrund meiner REST_FRAMEWORK/DEFAULT_PERMISSION_CLASSES und AUTHENTICATION_BACKENDS in settings.py "automatisch" überprüfen . Wie sage ich dem Vormund, dass er sich dann wie erwartet verhalten soll?

(Entschuldigung, wenn ich etwas wirklich Dummes übersehe, das ist sozusagen Tag 2/3 in Djangoland)

Entschuldigung für den Lärm heute, noch interessanter/verwirrender ist, dass guardian.shortcuts.get_objects_for_user() standardmäßig die auth/global-Berechtigungen anerkennt ( accept_global_perms ).

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

Obwohl dieses Thema zugunsten eines anderen geschlossen wurde, gibt es hier viele Erkenntnisse, also schreibe ich hier, um die Konversation wieder anzuregen. Zen der Python sagt:

Es muss einen – und am besten nur einen – offensichtlichen Weg geben, dies zu tun.

Und für mich ist das _Fallback auf global, wenn lokal (Objektebene) nicht angegeben ist_. Die Reihenfolge ändert das Ergebnis nicht (seit Django returns False if obj is not None ), kann aber die Leistung verändern.

Ich stimme zu, dass es kein gutes Design ist, den gesamten Code doppelt zu überprüfen, und in gewissem Sinne auch gegen das DRY-Prinzip. Die Guardian-Implementierungsfunktionalität, die im Standard-Backend von Django verfügbar ist, ist jedoch auch nicht DRY. Dass Django mehrere Backends zulässt und eines nach dem anderen der Reihe nach überprüft, zeigt an, dass Backends zusammenspielen und sich nicht gegenseitig ersetzen sollen. Wenn dies richtig ist, weigert sich Django, Globals zu überprüfen, wenn ein obj is not None _falsch_ ist. Wenn Django obj ignoriert, könnte dies der globale Fallback für die Objektprüfer sein.

Ich denke, wir sollten ein Ticket mit Django öffnen und fragen, ob und wie Autorisierungs-Backends miteinander spielen, und dann von dort aus weitermachen.

Abgesehen davon denke ich, dass es ziemlich unwahrscheinlich ist, dass Django sein Verhalten ändert (obwohl ich immer noch denke, dass wir fragen sollten). kann entweder über Einstellungen oder ein Argument für die Funktion festgelegt werden). Aber das Standardverhalten sollte meiner Meinung nach der _lokale Fallback auf global sein, wenn falsch_ auf der ganzen Linie.

Ich würde es eher lieben, wenn Django ein dreistufiges Berechtigungssystem bevorzugen würde: True, False und None. In diesem Fall könnten lokale Checker die globalen über False _überschreiben_; und über None könnte jedes Backend sagen: "Ich weiß es nicht, frag den nächsten in der Reihe". In diesem Fall würde Django die Überprüfung beenden, nachdem es True or False in einem der Backends erhalten hat, und keine Rechenleistung mehr verschwenden.

Dies würde jedem Backend mehr Macht geben: kann eine endgültige Antwort geben oder auf andere verweisen.

@doganmeh Django implementiert ein Tri-State-Berechtigungssystem (mindestens ab 1.10). Die Optionen sind „True“, „None“ und „PermissionDenied“. Letzteres führt dazu, dass Django die Überprüfung beendet und False zurückgibt.

In Bezug auf das vorliegende Problem glaube ich, dass es sich um ein Contrib.Auth-Problem handelt. Das ist das Backend, das sich mit „globalen Berechtigungen“ befasst. Das Problem ist, dass sie eine indirekte Konvention etabliert haben, die has_perm mit obj=None nur „Tabellenberechtigungen“ prüft und has_perm mit obj nur „Zeilenberechtigungen“ prüft. Sie werden das wahrscheinlich nicht ändern, aber _sollten_ bereit sein, ihre API zu erweitern, um die Überprüfung beider zu unterstützen.

(Zumindest würde ich hoffen, dass sie bereit sind, Verhalten hinzuzufügen, um beides zu überprüfen. Es scheint ein klarer Fehler in ihrem System zu sein.)

Was wären Ihre Vorschläge für die „Check both“-API? Das Beste, was mir einfällt, ist ein Kwarg.

Ich sprach von einem expliziten Tristate-System, bei dem es ein NullBooleanField in der Berechtigungstabelle geben würde. Richtig, wenn Sie das Fehlen von True (oder das Fehlen der Erlaubnis) als None betrachten, könnte es als Tristate betrachtet werden. In diesem Fall sollten Berechtigungen, die nicht vom Erziehungsberechtigten angegeben wurden, als None angenommen werden, und die Entscheidung sollte an den Globalen delegiert werden. Wenn ich jedoch die Wahl hätte, würde ich das explizite Design nehmen.

@airstandley Ich denke, du meinst eine Hinzufügung eines Kwarg zu user.has_perm wie zum Beispiel:

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

Ich denke, das würde helfen. Im Fall von fallback=True würde Guardian das Django-Backend aufrufen und die globale. Wenn ich jedoch die Wahl hätte, würde ich es vorziehen, dass der Fallback auf Django auf natürliche Weise vom Framework gehandhabt wird, anstatt seine Interna zu kapern.

@doganmeh Entschuldigung, ich habe mich falsch ausgedrückt. Das hätte "True, False und Raising PermissionDenied" lauten sollen. Nicht:

Die Optionen sind „True“, „None“ und „PermissionDenied“.

Je später denke ich an die Renditen in meinem Kopf ...

Ich glaube, ich habe vielleicht auch missverstanden, was du meinst

Ich würde es eher lieben, wenn Django ein Tri-State-Berechtigungssystem bevorzugen würde

Zur Verdeutlichung: Sie haben speziell über die Implementierung des Berechtigungsmodells gesprochen und nicht über das System als Ganzes?

Mein Punkt war, dass das Authentifizierungs-Backend-System genau die von Ihnen beschriebene Abkürzung zulässt (zumindest für die has_perm , has_module_perms API). Es ist explizit, wenn auch nicht einfach:
Jedes Backend kann entweder selbst eine Entscheidung treffen (True oder PermissionDenied zurückgeben) oder die Entscheidung an das nächste Backend in der Kette delegieren (False zurückgeben). Das ist eine implementierungsspezifische Entscheidung und keine Eigenschaft des Systems selbst.

Ein explizites ObjectPermissionBackend wäre ein Problem für mein Projekt, daher würde ich mich dafür entscheiden, nicht explizit zu sein. ("Explizitheit" macht die Integration mit anderen Backends zur Berechtigungsprüfung umständlich.) Ich vermute, dass andere wie Sie es bevorzugen würden, wenn es explizit wäre. Daher macht es für mich Sinn, die "Explizite" als Einstellung zu haben.

@doganmeh
Also über den Kwarg.

Zunächst mache ich mir immer wieder Sorgen, dass wir nicht auf derselben Seite sind, wie das Verhalten des Backend-Systems von auth funktionieren sollte. Also um es klar zu sagen:
Mein Verständnis ist, dass die Absicht war, dass Backends zusammenarbeiten. Wenn eine App das Berechtigungssystem von auth verwenden möchte (dh „globale Berechtigungen“, obwohl es technisch gesehen „Tabellenberechtigungen“ wären, aber Potatoe, Potato), würde sie das Auth ModelBackend in AUTH_BACKENDS installieren. Wenn diese App das ObjectPermission-System von Guardian verwenden wollte (dh „Zeilenberechtigung“), wäre das ObjectPermissionBackend von Guardian nicht in AUTH_BACKENDS enthalten.
ModelBackend würde die globale Berechtigung handhaben.
ObjectPermissionBackend würde Objektberechtigungen verarbeiten.
Wenn ein Benutzer nur eine Objektberechtigung hätte, würde ModelBackend niemals True für has_perm zurückgeben. Wenn ein Benutzer nur eine globale Berechtigung hätte, würde ObjectPermissionBackend niemals True für has_perm zurückgeben. In beiden Fällen sollte ein Aufruf von user.has_perm(perm, obj) immer noch true zurückgeben, da der Benutzer die Berechtigung für _eines_ der installierten Backends hat. (Hier sind wir auf ein Problem gestoßen, weil ModelBackend bei diesem Konto fehlschlägt.)

Ok, angesichts all dessen.

In einer idealen Welt, in der Kompatibilität kein Thema wäre, würde auch ich eine einfache Änderung zum ModelBackend von contrib.auth bevorzugen. ModelBackend.has_perm(user_obj, perm, obj=None) sollte True zurückgeben, wenn der Benutzer die durch Perm angegebene 'globale Berechtigung' hat; unabhängig davon, ob obj None ist oder nicht.

Kompatibilität ist jedoch ein Problem, weshalb ich frage, ob Sie eine Lösung für das Problem haben.

Die einzigen _abwärtskompatiblen_ Lösungen, die mir einfallen, sind entweder das Hinzufügen eines Kwarg zur API oder das Hinzufügen einer AUTH-Einstellung.

Also kwargs:
Nennen wir es obj_shortcut , denn das ist das Beste, was mir spontan einfällt. object_shortcut wäre standardmäßig True. Wenn object_shortcut True ist, sollten sich die Backends so verhalten, wie es das ModelBackend jetzt tut: Sie sollten _nur_ die 'table/global'-Berechtigungen prüfen, wenn obj None ist, andernfalls sollten sie _nur_ die 'row/object'-Berechtigungen prüfen. Wenn jedoch object_shortcut False ist, sollten sich Backends so verhalten, wie wir es beide bevorzugen würden: Sie würden _sowohl_ globale als auch Objektberechtigungen prüfen, wenn Objekt nicht None wäre. Dann könnten Add-Ons wie Guardian immer ein Mixin mit einer has_perm -Methode mit object_shortcut=False als Standard bereitstellen. user.has_perm(perm, obj, object_shortcut=False) würde korrekt True zurückgeben, wenn die durch perm dargestellte Berechtigung dem Benutzer zugewiesen wurde, während Legacy-Aufrufe an user.has_perm(perm, obj) weiterhin False zurückgeben würden.

Eine Einstellung hätte das gleiche Ergebnis, würde aber im Grunde zu einem Wechsel in ModelBackend._get_permissions führen.

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

würde sowas werden wie:

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

Nicht sicher, was besser ist. Ich denke, eine Einstellung ist sauberer, aber ich bin sicher, dass die Django-Entwickler qualifizierter wären, dies zu bestimmen.

Der Punkt ist, dass das Problem behoben werden kann, und ich bin fest davon überzeugt, dass es sich bei dem Problem um einen Django-Fehler handelt, nicht um Guardian.

@airstandley Du hast recht, False oder None spielt keine Rolle. Beide meinen, ich weiß es nicht . Schließlich, wenn niemand weiß, wird die Erlaubnis nicht erteilt.

Ich bin im Allgemeinen auf derselben Seite wie Sie, obwohl Django, das Backends dazu zwingt, sich auf die eine oder andere Weise zu verhalten, die lose Kopplung zu beeinträchtigen scheint. Ich denke, es sollte Mechanismen haben, die es jedem Backend ermöglichen, die Informationen zu erhalten, die es benötigt, um seine Arbeit zu erledigen. Ich bin zwar bei den Schlüsselwortargumenten bei Ihnen, aber erzwinge nicht nur die eine oder andere Weise.

Dieses Gespräch scheint Zeitverschwendung zu sein, aber es hat mir geholfen, mir klar zu machen, dass es keine Erlaubnis gibt, wenn ein Vormund erteilt wird, keine Verweigerung, sondern nur ein Mangel an Informationen.

Wie auch immer, ich habe einen Pull-Request #546 gemacht. Bitte überprüfen Sie es und lassen Sie mich wissen, was Sie denken.

Ich habe ein Ticket mit Django erstellt: https://code.djangoproject.com/ticket/29012 und frage mich, was sie sagen würden.

Anscheinend gab es einige andere Tickets mit Django: https://code.djangoproject.com/ticket/20218

Ich habe meine geschlossen.

@doganmeh
Ich mag die Richtung, in die Sie mit #546 gehen. Wenn es helfen würde, sollte ich Zeit haben, mir die restlichen Einheiten anzusehen und an diesem Wochenende einige Tests für diese Fallbacks zu schreiben.

Auf einer Tangente. Ihr Kommentar zu "Django zwingt Backends zu einem bestimmten Verhalten" verwirrt mich, hat mich aber auch zum Nachdenken gebracht. Backends können sich verhalten, wie sie möchten; Meine anfängliche Besorgnis über das ObjectPermissionBackend von Guardian rührt von der Dokumentation her, die darauf hindeutet, dass es zusammen mit dem ModelBackend von Auth ausgeführt werden sollte. Guardian könnte mehrere Backends anbieten, eines für die Zusammenarbeit mit ModelBackend und eines für die alleinige Arbeit. (Z. B. einer, der nur die UserObjectPermission/GroupObjectPermission-Tabellen von Guardian überprüft, der andere sowohl die UserObjectPermission/GroupObjectPermission-Tabellen als auch die Berechtigungstabelle von Auth.)
Persönlich bevorzuge ich den aktuellen Ansatz mit einer Einstellung und kwargs. Ich denke, der größte Nachteil eines Ansatzes mit mehreren Backends wäre, dass unklar wird, wie sich die Verknüpfungen und Komfortfunktionen verhalten sollten.

Ich habe den Beitrag zur Django-Entwickler-Mailingliste gesehen. Ich hoffe, sie akzeptieren das oder stimmen zu, das Verhalten auf unvereinbare Weise zu ändern. Die aktuelle Beschränkung der API ist einfach klobig.

Kannst du es dort unterstützen? 😊

Gesendet von Mail für Windows 10

Von: airstandley
Gesendet: Freitag, 12. Januar 2018 12:06 Uhr
An: Django-Wächter/Django-Wächter
CC: Mehmet Dogan; Erwähnen
Betreff: Re: [django-guardian/django-guardian] user.has_perm("perm", obj)verhält sich unerwartet (#49)

Ich habe den Beitrag zur Django-Entwickler-Mailingliste gesehen. Ich hoffe, sie akzeptieren das oder stimmen zu, das Verhalten auf unvereinbare Weise zu ändern. Die aktuelle Beschränkung der API ist einfach klobig.

Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail, zeigen Sie sie auf GitHub an oder schalten Sie den Thread stumm.

Nachdem ich mich jetzt etwa eine Woche lang mit diesem Problem beschäftigt habe, bin ich fester davon überzeugt, dass jedes Backend nur eine Sache tun muss, nämlich Objektberechtigungen für Guardian. Damit dies geschieht, muss Django obj besser behandeln.

Dafür gibt es den Patch, den ich an Django geschickt habe: https://github.com/django/django/pull/9581 (bitte kommentieren, wenn du kannst). Bei denen, die es schaffen, können wir den Abruf von Modellberechtigungen bereinigen, wo immer es in Guardian gibt, und einfach das Standard-Backend aufrufen.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

brianmay picture brianmay  ·  16Kommentare

lukaszb picture lukaszb  ·  14Kommentare

ad-m picture ad-m  ·  13Kommentare

Allan-Nava picture Allan-Nava  ·  35Kommentare

g-as picture g-as  ·  10Kommentare