Was ist mit „Ein Benutzer sollte sich nicht entscheiden, ob es sich um einen Administrator handelt oder nicht. Das Privilegien- oder Sicherheitssystem sollte. “

55

Das in der Frage verwendete Beispiel für die Übergabe von Mindestdaten an eine Funktion gibt Aufschluss darüber, ob der Benutzer ein Administrator ist oder nicht. Eine häufige Antwort war:

user.isAdmin()

Dies führte zu einem Kommentar, der mehrmals wiederholt und mehrfach hochgestuft wurde:

Ein Benutzer sollte sich nicht entscheiden, ob es sich um einen Administrator handelt oder nicht. Das Privilegien- oder Sicherheitssystem sollte. Etwas, das eng mit einer Klasse verbunden ist, bedeutet nicht, dass es eine gute Idee ist, sie zu einem Teil dieser Klasse zu machen.

Ich antwortete,

Der Benutzer entscheidet nichts. Das Benutzerobjekt / die Tabelle speichert Daten über jeden Benutzer. Tatsächliche Benutzer können nicht alles an sich selbst ändern.

Dies war jedoch nicht produktiv. Es liegt eindeutig ein Unterschied in der Perspektive zugrunde, der die Kommunikation erschwert. Kann mir jemand erklären, warum user.isAdmin () schlecht ist, und eine kurze Skizze darüber zeichnen, wie es aussieht, als wäre es "richtig" gemacht worden?

Wirklich, ich sehe keinen Vorteil darin, die Sicherheit von dem zu schützenden System zu trennen. Jeder Sicherheitstext besagt, dass Sicherheit von Anfang an in ein System integriert und in jeder Phase der Entwicklung, Bereitstellung, Wartung und sogar am Ende der Lebensdauer berücksichtigt werden muss. Es ist nicht etwas, das an der Seite verschraubt werden kann. Bisher haben 17 Stimmen zu diesem Kommentar zugelegt, dass ich etwas Wichtiges verpasse.

GlenPeterson
quelle
8
Ich denke nicht, dass es schlecht ist, besonders wenn es nur ein Flag in der Benutzer-DB-Tabelle ist. Sollten sich in der Zukunft Änderungen ergeben, ändern Sie diese in Zukunft. Sie können das Sicherheitssystem auch vom Benutzerobjekt aus aufrufen. Ich habe einmal über das Entwurfsmuster gelesen, dass jeder Datenbank- / Ressourcenzugriff das aktuelle Benutzerobjekt durchlaufen sollte, um sicherzustellen, dass Sie niemals versuchen, etwas zu tun, das nicht zulässig ist. Könnte ein bisschen viel sein, aber der Benutzer könnte ein "Privilegien" -Objekt bereitstellen, das alle sicherheitsrelevanten Dinge abdeckt.
Kopffüßer
Ich denke, es gab ein Missverständnis. Für Sie bedeutet "Benutzer" die Person, die die Software verwendet. Für sie bedeutet "Benutzer" den Code, der Ihre Funktionalität verwendet.
Philipp
2
Nicht, dass Sie danach gefragt hätten, aber in diesem Fall wäre eine IsAdmin () -Methode am vorsichtigsten, egal wo Sie sie einsetzen. Normalerweise würde ich empfehlen, dass ein "Admin" nicht fest programmiert ist, sondern nur über eine Reihe möglicher Berechtigungen verfügt, und ein "Admin" hat zufällig den gesamten Satz. Auf diese Weise ist es viel einfacher , wenn Sie die Administratorrolle später aufteilen müssen . Und es reduziert die Sonderfalllogik.
PSR
1
@Philipp Ich glaube nicht ... Beide Seiten sprechen explizit über ein UserObjekt mit einer isAdmin()Methode (was auch immer es hinter den Kulissen tut)
Izkata
Zu spät zum Bearbeiten; Ich meinte "Beide Seiten", nicht "Beide Seiten" ... = P
Izkata

Antworten:

12

Stellen Sie sich den Benutzer als Schlüssel und die Berechtigungen als Wert vor.

Unterschiedliche Kontexte haben möglicherweise unterschiedliche Berechtigungen für jeden Benutzer. Da die Berechtigungen kontextrelevant sind, müssten Sie den Benutzer für jeden Kontext ändern. Also ist es besser, wenn Sie diese Logik an einer anderen Stelle ablegen (z. B. in der Kontextklasse) und die erforderlichen Berechtigungen nachschlagen.

Ein Nebeneffekt ist, dass Ihr System dadurch möglicherweise sicherer wird, da Sie nicht vergessen können, das Admin-Flag an Stellen zu löschen, an denen es nicht relevant ist.

Wilbert
quelle
40

Dieser Kommentar war falsch formuliert.

Es geht nicht darum, ob das Benutzerobjekt "entscheidet", ob es sich um einen Administrator, einen Testbenutzer, einen Superbenutzer oder um etwas Ungewöhnliches handelt. Offensichtlich entscheidet es nichts davon, was das von Ihnen implementierte Softwaresystem im Allgemeinen tut. Der Punkt ist , ob die „user“ Klasse sollte repräsentiert die Rollen des Hauptes, die ihre Objekte darstellen, oder ob dies eine Verantwortung einer anderen Klasse.

Kilian Foth
quelle
6
Autsch (es war mein Kommentar), aber du sagst es viel besser als ich.
Marjan Venema
Sie möchten Rollen in einer separaten Rollenklasse modellieren, nicht in der Benutzerklasse? Sie empfehlen kein separates System wie LDAP? Was ist, wenn für jede einzelne Entscheidung, die die Rollenklasse trifft, der entsprechende Benutzerdatensatz abgefragt werden muss und keine weiteren Informationen erforderlich sind? Sollte es immer noch vom Benutzer getrennt sein und wenn ja, warum?
GlenPeterson
2
@ GlenPeterson: Nicht unbedingt, nur, dass Klassen <> Tabellen sind und normalerweise viel mehr Klassen zu unterscheiden sind als Tabellen. Datenorientiert zu sein, führt jedoch dazu, dass Unterlisten für Klassen definiert werden, wenn diese Listen oft in einem anderen "Hauptbuch" (Bestellungen) oder einer solchen Klasse definiert werden müssen, die an den Benutzer (oder die Bestellung) übergeben wird, für die die Liste abgerufen werden soll. Es geht mehr um Verantwortlichkeiten als um Verben und Nomen.
Marjan Venema
9
Stellen Sie sich den Benutzer als Schlüssel und die Berechtigungen als Wert vor. Unterschiedliche Kontexte haben möglicherweise unterschiedliche Berechtigungen für jeden Benutzer. Da die Berechtigungen kontextrelevant sind, müssten Sie entweder den Benutzer für jeden Kontext ändern. Also ist es besser, wenn Sie diese Logik an einer anderen Stelle platzieren (z. B. in der Kontextklasse).
Wilbert
2
@Wilbert: Kontext! Das ist besonders aufschlussreich! Ja, wenn Sie mehr als einen Kontext für diese Berechtigungen haben, ist für mich alles sinnvoll. Normalerweise sehe ich diese Berechtigungen in einem einzigen Kontext und es ist in diesem Fall die einfachste Lösung, sie dem Benutzer zuzuweisen. Offensichtlich gehören sie nicht dorthin, wenn sie in einem zweiten Kontext anders gelten. Wenn Sie das als Antwort schreiben, werde ich es wahrscheinlich akzeptieren. Danke.
GlenPeterson
30

Ich hätte mich nicht dafür entschieden, den ursprünglichen Kommentar so zu formulieren, wie er formuliert wurde, aber er identifiziert ein potenziell legitimes Problem.

Insbesondere sind die Bedenken, die eine Trennung rechtfertigen, die Authentifizierung gegenüber der Autorisierung .

Authentifizierung bezieht sich auf den Prozess der Anmeldung und des Abrufs einer Identität. Auf diese Weise wissen die Systeme, wer Sie sind , und werden für Dinge wie Personalisierung, Objekteigentum usw. verwendet.

Die Autorisierung bezieht sich auf das , was Sie tun dürfen , und dies hängt (im Allgemeinen) nicht davon ab , wer Sie sind . Stattdessen wird es durch einige Sicherheitsrichtlinien wie Rollen oder Berechtigungen festgelegt, die sich nicht um Dinge wie Ihren Namen oder Ihre E-Mail-Adresse kümmern.

Diese beiden können sich orthogonal zueinander ändern. Beispielsweise können Sie das Authentifizierungsmodell ändern, indem Sie OpenID / OpenAuth-Anbieter hinzufügen. Außerdem können Sie die Sicherheitsrichtlinie ändern, indem Sie eine neue Rolle hinzufügen oder von RBAC zu ABAC wechseln.

Wenn all dies in eine Klasse oder Abstraktion fällt, ist Ihr Sicherheitscode, der eines Ihrer wichtigsten Instrumente zur Risikominderung ist, ironischerweise ein hohes Risiko.

Ich habe mit Systemen gearbeitet, bei denen Authentifizierung und Autorisierung zu eng miteinander verbunden waren. In einem System gab es zwei parallele Benutzerdatenbanken für jeweils einen Typ von "Rolle". Die Person oder das Team, die / das es entworfen hat, hat anscheinend nie in Betracht gezogen, dass ein einzelner physischer Benutzer möglicherweise beide Rollen innehat, dass bestimmte Aktionen für mehrere Rollen gelten oder dass es Probleme mit Benutzer-ID-Kollisionen gibt. Dies ist ein zugegebenermaßen extremes Beispiel, aber es war / ist unglaublich schmerzhaft, damit zu arbeiten.

Microsoft und Sun / Oracle (Java) bezeichnen die Gesamtheit der Authentifizierungs- und Autorisierungsinformationen als Sicherheitsprinzipal . Es ist nicht perfekt, aber es funktioniert einigermaßen gut. In .NET, zum Beispiel, haben Sie IPrincipal, die kapselt das IIdentity- die ehemalige ein Wesen Politik (Autorisierung) Objekt , während die letztere ist eine Identität (Authentifizierung). Sie könnten die Entscheidung, eines in das andere zu setzen, vernünftigerweise in Frage stellen , aber das Wichtigste ist, dass der meiste Code, den Sie schreiben, nur für eine der Abstraktionen bestimmt ist, was bedeutet, dass es leicht zu testen und umzugestalten ist.

An einem User.IsAdminFeld ist nichts auszusetzen ... es sei denn, es gibt auch ein User.NameFeld. Dies würde darauf hinweisen, dass das "Benutzer" -Konzept nicht richtig definiert ist, und dies ist leider ein sehr häufiger Fehler bei Entwicklern, die in Bezug auf Sicherheit etwas hinter den Ohren liegen. In der Regel ist die Benutzer-ID das einzige, was von Identität und Richtlinie gemeinsam genutzt werden sollte. Dies ist nicht zufällig genau so, wie es in den Sicherheitsmodellen von Windows und * nix implementiert ist.

Es ist völlig akzeptabel, Wrapper-Objekte zu erstellen, die sowohl Identität als auch Richtlinie kapseln. Dies würde beispielsweise die Erstellung eines Dashboard-Bildschirms erleichtern, auf dem Sie neben verschiedenen Widgets oder Links, auf die der aktuelle Benutzer zugreifen darf, eine "Hallo" -Nachricht anzeigen müssen. Solange diese Wrapper nur hüllen die Identität und politische Informationen und erheben nicht den Anspruch , es zu besitzen. Mit anderen Worten, solange es nicht als Gesamtwurzel dargestellt wird .

Ein simplifiziertes Sicherheitsmodell scheint immer eine gute Idee zu sein, wenn Sie aufgrund von YAGNI und all dem zuerst eine neue Anwendung entwerfen, aber es kommt fast immer wieder auf Sie zu, um Sie später zu beißen, da überraschenderweise neue Funktionen hinzugefügt werden!

Wenn Sie also wissen, was für Sie am besten ist, trennen Sie Authentifizierungs- und Autorisierungsinformationen. Selbst wenn die "Autorisierung" im Moment so einfach wie ein "IsAdmin" -Flag ist, sind Sie immer noch besser dran, wenn sie nicht Teil derselben Klasse oder Tabelle wie die Authentifizierungsinformationen sind, und wenn Ihre Sicherheitsrichtlinie dies erfordert Sie müssen keine rekonstruktiven Eingriffe an Ihren Authentifizierungssystemen vornehmen, die bereits ordnungsgemäß funktionieren.

Aaronaught
quelle
11
Oh, ich wünschte, ich hätte die Zeit gehabt, dies zu schreiben. Andererseits hätte ich es wahrscheinlich nicht so gut ausdrücken können wie Sie, ohne dass ich viel mehr darüber nachgedacht hätte. Oft sagt mir mein Bauchgefühl, ich solle in die eine oder andere Richtung gehen, aber zu erklären, warum - für mich selbst oder für jemanden anderen - viel mehr Nachdenken und Anstrengung erforderlich ist. Danke für diesen Beitrag. Hätte es mehrfach angezettelt, aber SE lässt mich nicht ...
Marjan Venema
Das klingt schockierend ähnlich wie bei einem Projekt, an dem ich arbeite, und Ihre Antwort hat mir eine andere Perspektive gegeben. Ich danke dir sehr!
Brandon
"Es ist nichts falsch mit einem User.IsAdmin-Feld ... es sei denn, es gibt auch ein User.Name-Feld", schreiben Sie. Aber was isAdminist eine Methode? Es wird möglicherweise nur an ein Objekt delegiert, dessen Verantwortung es ist, die Berechtigungen zu überwachen. Was für den Entwurf eines relationalen Modells die beste Vorgehensweise ist (über welche Felder eine Entität verfügt), lässt sich nicht unbedingt in die beste Vorgehensweise in einem OO-Modell (auf welche Nachrichten ein Objekt antworten kann) übersetzen.
KaptajnKold
@KaptajnKold: Ich lese dieses Gefühl immer wieder in verschiedenen Q & A-Threads in diesem Forum. Wenn eine Methode vorhanden ist, spielt es keine Rolle, ob sie die Operationen direkt ausführt oder delegiert , sie zählt dennoch als Verantwortung, da andere Klassen möglicherweise darauf angewiesen sind . Sie User.IsAdminkönnen durchaus ein Einzeiler sein, der nur callt PermissionManager.IsAdmin(this.Id), aber wenn auf ihn von 30 anderen Klassen verwiesen wird, können Sie ihn nicht ändern oder entfernen. Deshalb gibt es das SRP.
Aaronaught
Und, @KaptajnKold, was relationale Modelle angeht, bin ich mir nicht sicher, worauf du hinaus willst. Es ist noch schlimmer , ein IsAdmin Feld zu haben . Es ist die Art von Feld, das lange nach der Entwicklung des Systems zu RBAC oder etwas Komplexerem bestehen bleibt und allmählich nicht mehr mit den "echten" Berechtigungen synchronisiert wird, und die Leute vergessen, dass sie es nicht mehr verwenden sollen. und ... nun, sag einfach nein. Selbst wenn Sie der Meinung sind, dass Sie nur zwei Rollen haben, "admin" und "non-admin", speichern Sie sie nicht als Boolesches / Bit-Feld, normalisieren Sie sie ordnungsgemäß und verfügen Sie über eine Berechtigungstabelle.
Aaronaught
28

Zumindest verstößt es gegen das Prinzip der Einzelverantwortung . Sollte es Änderungen geben (mehrere Administratorebenen, noch mehr verschiedene Rollen?), Wäre diese Art von Design ziemlich chaotisch und schrecklich zu pflegen.

Berücksichtigen Sie den Vorteil der Trennung des Sicherheitssystems von der Benutzerklasse, sodass beide eine einzelne, genau definierte Rolle haben können. Sie erhalten ein saubereres und insgesamt leichter zu pflegendes Design.

Verhalten
quelle
2
@ GlenPeterson: Nein, Benutzer können sehr gut ohne Sicherheit existieren. Was ist mit meinem Namen, meinem Anzeigenamen, meiner Adresse, meiner E-Mail-Adresse usw.? Identifizierung (Authentifizierung) und Autorisierung sind verschiedene Tiere.
Marjan Venema
2
class Userist eine Kernkomponente im Sicherheitsdreiklang von Identifizierung, Authentifizierung und Autorisierung . Diese sind auf Designebene eng miteinander verknüpft, sodass es für den Code nicht unangemessen ist, diese gegenseitige Abhängigkeit widerzuspiegeln.
MSalters
1
Die Frage ist, ob die User-Klasse die gesamte Logik für den Umgang mit dem Triplet enthält!
Verhalten
3
@MSalters: Wenn es sich um eine API für eine solche Logik handelt, weiß es Bescheid, auch wenn es die genaue Logik an eine andere Stelle delegiert. Der Punkt, der gemacht wird, ist, dass die Autorisierung (Berechtigungen) eine Kombination eines Benutzers mit etwas anderem ist und daher weder Teil des Benutzers noch des anderen sein sollte, sondern Teil des Berechtigungssystems, das beide Benutzer und was auch immer richtig kennt erhält die Erlaubnis für.
Marjan Venema
2
Should there be changes- Nun, wir wissen nicht, dass es Änderungen geben wird. YAGNI und alle. Nehmen Sie diese Änderungen vor, wenn es notwendig wird, richtig?
Izkata
10

Ich denke, das Problem, das vielleicht schlecht formuliert ist, ist, dass es der UserKlasse zu viel Gewicht gibt . Nein, es gibt keine Sicherheitsprobleme mit einer Methode User.isAdmin(), wie in diesen Kommentaren vorgeschlagen. Wenn jemand tief genug in Ihrem Code steckt, um schädlichen Code für eine Ihrer Kernklassen einzufügen, haben Sie schließlich ernsthafte Probleme. Auf der anderen Seite ist es nicht sinnvoll Userzu entscheiden, ob es sich bei jedem Kontext um einen Administrator handelt. Stellen Sie sich vor, Sie fügen verschiedene Rollen hinzu: Moderatoren für Ihr Forum, Redakteure, die Beiträge auf der Startseite veröffentlichen können, Autoren, die Inhalte für die Redakteure verfassen können, und so weiter. Mit dem Ansatz, es auf das UserObjekt zu setzen, werden Sie am Ende zu viele Methoden und zu viel Logik haben, die auf dem Userbasieren, was nicht direkt mit der Klasse zusammenhängt.

Stattdessen könnten Sie eine Aufzählung verschiedener Arten von Berechtigungen und eine PermissionsManagerKlasse verwenden. Vielleicht könnten Sie eine Methode haben PermissionsManager.userHasPermission(User, Permission), die einen booleschen Wert zurückgibt. In der Praxis könnte es so aussehen (in Java):

if (!PermissionsManager.userHasPermission(user, Permissions.EDITOR)) {
  Site.renderSecurityRejectionPage();
}

Es entspricht im Wesentlichen der Rails-Methode before_filter, die ein Beispiel für diese Art der Berechtigungsprüfung in der realen Welt darstellt.

asthasr
quelle
6
Worin unterscheidet sich das user.hasPermission(Permissions.EDITOR), außer dass es ausführlicher und prozeduraler ist als OO?
Kopffüßer
9
@Arian: weil user.hasPermissionszu viele Verantwortlichkeiten in die Benutzerklasse gestellt werden. Es setzt voraus, dass der Benutzer die Berechtigungen kennt, während ein Benutzer (eine Klasse) ohne diese Kenntnisse existieren kann. Sie möchten nicht, dass eine Benutzerklasse weiß, wie sie selbst druckt oder anzeigt (ein Formular erstellt). Überlassen Sie dies einem Drucker-Renderer / Builder oder einer Anzeige-Renderer / Builder-Klasse.
Marjan Venema
4
user.hasPermission(P)ist die richtige API, aber das ist nur die Schnittstelle. Die eigentliche Entscheidung muss natürlich in einem Sicherheitsteilsystem getroffen werden. Um den Kommentar von syrion zum Testen zu beantworten, können Sie während des Tests dieses Subsystem austauschen. Der Vorteil des Unkomplizierten user.hasPermission(P)ist jedoch, dass Sie nicht den gesamten Code verschmutzen, nur damit Sie ihn testen können class User.
MSalters
3
@MarjanVenema Nach dieser Logik bleibt der Benutzer ein Datenobjekt ohne jegliche Verantwortlichkeit. Ich sage nicht, dass die gesamte Berechtigungsverwaltung in der Benutzerklasse implementiert werden sollte, wenn ein solch komplexes System benötigt wird. Sie sagen, ein Benutzer muss nichts über Berechtigungen wissen, aber ich verstehe nicht, warum jede andere Klasse wissen muss, wie Berechtigungen für einen Benutzer aufgelöst werden. Dies erschwert das Verspotten und Testen erheblich.
Kopffüßer
6
@Arian: Während anämische Klassen ein Geruch sind, haben einige Klassen einfach kein Verhalten ... User, imho, ist eine solche Klasse, die besser als Parameter für eine ganze Reihe anderer Dinge verwendet wird (die Dinge tun wollen) Ich treffe Entscheidungen basierend darauf, wer nach ihnen fragt, als Container für alle Arten von Verhalten, nur weil es zweckmäßig erscheint, es so zu codieren.
Marjan Venema
9

Object.isX () wird verwendet, um eine eindeutige Beziehung zwischen dem Objekt und X darzustellen, die als boolesches Ergebnis ausgedrückt werden kann. Beispiel:

Wasser.istKochen ()

Circuit.isOpen ()

User.isAdmin () nimmt in jedem Kontext des Systems eine einzelne Bedeutung von 'Administrator' an, dh ein Benutzer ist in jedem Teil des Systems ein Administrator.

Obwohl es sich einfach anhört, wird ein Programm aus der realen Welt so gut wie nie in dieses Modell passen. Es wird jedoch höchstwahrscheinlich erforderlich sein, dass ein Benutzer [X] -Ressourcen verwaltet, jedoch nicht [Y], oder dass es sich um spezifischere Administratortypen handelt (ein [Projekt] ] Administrator gegen einen [System] Administrator).

Diese Situation erfordert normalerweise eine Untersuchung der Anforderungen. In seltenen Fällen möchte ein Client die Beziehung, die User.isAdmin () tatsächlich darstellt, so dass ich zögern würde, eine solche Lösung ohne Klärung zu implementieren.

FMJaguar
quelle
6

Das Plakat hatte lediglich ein anderes und komplizierteres Design im Sinn. Meiner Meinung nach User.isAdmin()ist in Ordnung . Auch wenn Sie später ein kompliziertes Berechtigungsmodell einführen, sehe ich wenig Grund, umzuziehen User.isAdmin(). Später möchten Sie möglicherweise die Benutzerklasse in ein Objekt, das einen angemeldeten Benutzer darstellt, und ein statisches Objekt, das die Daten über den Benutzer darstellt, aufteilen. Oder du darfst nicht. Sparen Sie morgen für morgen.

Kevin Cline
quelle
4
Save tomorrow for tomorrow. Ja. Wenn Sie herausfinden, dass der eng gekoppelte Code, den Sie gerade geschrieben haben, Sie in eine Schublade gesteckt hat und Sie ihn neu schreiben müssen, weil Sie nicht die zusätzlichen 20 Minuten gebraucht haben, um ihn zu entkoppeln ...
MirroredFate
5
@MirroredFate: Es ist durchaus möglich, eine User.isAdminMethode mit einem beliebigen Berechtigungsmechanismus zu verbinden, ohne die User-Klasse an diesen bestimmten Mechanismus zu koppeln .
Kevin Cline
3
Damit User.isAdmin auch ohne Kopplung an einen bestimmten Mechanismus an einen beliebigen Berechtigungsmechanismus gekoppelt werden kann, muss ... nun ... eine Kopplung erfolgen. Das Minimum wäre eine Schnittstelle. Und diese Schnittstelle sollte einfach nicht da sein. Es gibt der Benutzerklasse (und der Schnittstelle) etwas, für das sie einfach nicht verantwortlich sein sollte. Aaronaught erklärt es viel besser als ich (wie auch die Kombination aller anderen Antworten auf diese Frage).
Marjan Venema
8
@MirroredFate: Es ist mir egal, ob ich eine einfache Implementierung neu schreiben muss, um komplexere Anforderungen zu erfüllen. Aber ich habe wirklich schlechte Erfahrungen mit übermäßig komplexen APIs gemacht, die entworfen wurden, um imaginäre zukünftige Anforderungen zu erfüllen, die nie entstanden sind.
Kevin Cline
3
@kevincline Während überkomplexe APIs (per Definition) eine schlechte Sache sind, habe ich nicht vorgeschlagen, dass man überkomplexe APIs schreiben sollte. Ich sagte, dass dies Save tomorrow for tomorrowein schrecklicher Entwurfsansatz ist, da er Probleme, die trivial wären, wenn das System korrekt implementiert würde, weitaus schlimmer macht. Tatsächlich führt diese Mentalität zu übermäßig komplexen APIs, da Schicht für Schicht hinzugefügt wird, um das anfängliche schlechte Design auszugleichen. Ich denke meine Haltung ist measure twice, cut once.
MirroredFate
5

Kein wirkliches Problem ...

Ich sehe kein Problem mit User.isAdmin(). Ich bevorzuge es auf jeden Fall so etwas wie PermissionManager.userHasPermission(user, permissions.ADMIN), was im heiligen Namen von SRP den Code weniger klar macht und nichts von Wert hinzufügt.

Ich denke, SRP wird von einigen ein wenig zu wörtlich interpretiert. Ich denke, es ist in Ordnung, noch besser für eine Klasse, eine reichhaltige Oberfläche zu haben. SRP bedeutet lediglich, dass ein Objekt alles an Mitarbeiter delegieren muss, was nicht in seine alleinige Verantwortung fällt. Wenn die Administratorrolle eines Benutzers mehr als ein boolesches Feld ist, ist es möglicherweise sinnvoll, das Benutzerobjekt an einen PermissionsManager zu delegieren. Das bedeutet aber nicht, dass es für einen Benutzer auch keine gute Idee ist, seine isAdminMethode beizubehalten. Tatsächlich bedeutet dies, dass Sie den Code, der das Benutzerobjekt verwendet, ändern müssen, wenn Ihre Anwendung von einfach zu komplex wechselt. IOW, Ihr Kunde muss nicht wissen, was es wirklich braucht, um die Frage zu beantworten, ob ein Benutzer ein Administrator ist oder nicht.

... aber warum willst du das wirklich wissen?

Das heißt, es scheint mir, dass Sie selten wissen müssen, ob ein Benutzer ein Administrator ist, es sei denn, Sie können eine andere Frage beantworten, z. B. ob ein Benutzer eine bestimmte Aktion ausführen darf, z. B. das Aktualisieren eines Widgets. Wenn dies der Fall ist, würde ich es vorziehen, eine Methode in Widget zu haben, wie zum Beispiel isUpdatableBy(User user):

boolean isUpdatableBy(User user) {
    return user.isAdmin();
}

Auf diese Weise muss ein Widget wissen, welche Kriterien erfüllt sein müssen, damit es von einem Benutzer aktualisiert wird, und ein Benutzer muss wissen, ob es sich um einen Administrator handelt. Dieses Design kommuniziert klar über seine Absichten und macht es einfacher, zu komplexeren Geschäftslogiken überzugehen, wenn und wann dies der Fall ist.

[Bearbeiten]

Das Problem nicht zu haben User.isAdmin()und zu benutzenPermissionManager.userHasPermission(...)

Ich dachte, ich würde meiner Antwort hinzufügen, um zu erklären, warum ich es sehr bevorzuge, eine Methode für das Benutzerobjekt aufzurufen, anstatt eine Methode für ein PermissionManager-Objekt aufzurufen, wenn ich wissen möchte, ob ein Benutzer ein Administrator ist (oder eine Administratorrolle hat).

Ich denke, es ist fair anzunehmen, dass Sie immer von der Benutzerklasse abhängen werden, wo immer Sie die Frage stellen müssen. Ist dieser Benutzer ein Administrator? Das ist eine Abhängigkeit, die man nicht loswerden kann. Wenn Sie den Benutzer jedoch an ein anderes Objekt übergeben müssen, um eine Frage zu stellen, wird eine neue Abhängigkeit von diesem Objekt über die Abhängigkeit erstellt, die Sie bereits vom Benutzer haben. Wenn die Frage häufig gestellt wird, ist dies eine Menge Stellen, an denen Sie eine zusätzliche Abhängigkeit erstellen, und es sind viele Stellen, an denen Sie möglicherweise Änderungen vornehmen müssen, wenn eine der Abhängigkeiten dies erfordert.

Vergleichen Sie dies mit dem Verschieben der Abhängigkeit in die Benutzerklasse. Jetzt haben Sie plötzlich ein System, in dem der Client-Code (Code, der die Frage stellen muss, ist dieser Benutzer ein Administrator ) nicht an die Implementierung gekoppelt ist, wie diese Frage beantwortet wird. Es steht Ihnen frei, das Berechtigungssystem vollständig zu ändern, und Sie müssen nur eine Methode in einer Klasse aktualisieren, um dies zu tun. Der gesamte Client-Code bleibt unverändert.

Wenn ich darauf bestehe, keine isAdminMethode für den Benutzer zu haben, weil ich befürchte, eine Abhängigkeit in der Benutzerklasse vom Berechtigungssubsystem zu schaffen, ist dies meiner Meinung nach vergleichbar damit, einen Dollar auszugeben, um einen Cent zu verdienen. Sicher, Sie vermeiden eine Abhängigkeit in der Benutzerklasse, müssen jedoch an jeder Stelle, an der Sie die Frage stellen müssen, eine Abhängigkeit erstellen. Kein gutes Geschäft.

KaptajnKold
quelle
2
"SRP bedeutet nur, dass ein Objekt alles an Mitarbeiter delegieren muss, was nicht in seine Einzelverantwortung fällt" - false . Die Definition des SRP umfasst alles, was ein Objekt direkt tut und alles, was es delegiert. Eine Klasse, die nur eine Aufgabe direkt, aber 50 andere Aufgaben indirekt über die Delegierung ausführt, verstößt (wahrscheinlich) immer noch gegen die SRP.
Aaronaught
KaptajnKold: Ich würde +1 geben, weil ich Ihnen zustimme und mich ein wenig schuldig fühle. Ihr Beitrag wird zur Diskussion hinzugefügt, aber die Frage wird nicht beantwortet.
GlenPeterson
@GlenPeterson Nun, ich versuchte , den Teil der Frage zu befassen , das war ‚ wie es getan‚richtig‘sieht‘ auch: Sie sollten absolut nicht schuldig fühlen , mit mir einverstanden :)
KaptajnKold
1
@ JamesSnell Vielleicht. Wenn. Aber das ist ein Implementierungsdetail, das ich immer noch auf die isAdmin-Methode für User beschränken würde. Auf diese Weise muss sich Ihr Client-Code nicht ändern, wenn sich die "Administration" eines Benutzers von einem booleschen Feld zu einem etwas fortgeschritteneren entwickelt. Möglicherweise müssen Sie an vielen Stellen wissen, ob ein Benutzer ein Administrator ist, und nicht jedes Mal, wenn sich das Autorisierungssystem ändert, Änderungen vornehmen.
KaptajnKold
1
@ JamesSnell Vielleicht verstehen wir uns falsch? Die Frage, ob ein Benutzer ein Administrator ist oder nicht, entspricht nicht der Frage, ob ein Benutzer eine bestimmte Aktion ausführen darf . Die Antwort auf die erste Frage ist immer kontextunabhängig. Die Antwort auf die zweite Frage hängt stark vom Kontext ab. Dies habe ich in der zweiten Hälfte meiner ursprünglichen Antwort versucht.
KaptajnKold
1

Diese Diskussion erinnerte mich an einen Blogbeitrag von Eric Lippert, auf den ich in einer ähnlichen Diskussion über Klassendesign, Sicherheit und Korrektheit aufmerksam wurde.

Warum sind so viele Framework-Klassen versiegelt?

Insbesondere bringt Eric Folgendes zur Sprache:

4) Sicher. Der springende Punkt beim Polymorphismus ist, dass Sie Objekte herumreichen können, die wie Tiere aussehen, aber tatsächlich Giraffen sind. Hier gibt es potenzielle Sicherheitsprobleme.

Jedes Mal, wenn Sie eine Methode implementieren, die eine Instanz eines nicht versiegelten Typs verwendet, MÜSSEN Sie diese Methode so schreiben, dass sie gegenüber potenziell feindlichen Instanzen dieses Typs robust ist. Sie können sich nicht auf Invarianten verlassen, von denen Sie wissen, dass sie für IHRE Implementierungen zutreffend sind, da eine feindliche Webseite Ihre Implementierung unterklassifizieren, die virtuellen Methoden überschreiben kann, um Dinge zu tun, die Ihre Logik durcheinander bringen, und sie weitergeben. Jedes Mal, wenn ich eine Klasse versiegele Ich kann Methoden schreiben, die diese Klasse verwenden, mit der Gewissheit, dass ich weiß, was diese Klasse tut.

Dies mag wie eine Tangente erscheinen, aber ich denke, es passt genau zu dem Punkt, den andere Plakate über das SOLID -Prinzip der Einzelverantwortung angesprochen haben . Betrachten Sie die folgende böswillige Klasse als Grund, eine User.IsAdminMethode oder Eigenschaft nicht zu implementieren .

public class MyUser : User
{

    public new boolean IsAdmin()
    {
        // You know it!
        return true;
    }

    // Anything else we can break today?

}

Zugegeben, es ist ein bisschen IsAmdminkompliziert : Noch hat niemand vorgeschlagen, eine virtuelle Methode zu entwickeln, aber es könnte passieren, dass Ihre Benutzer- / Rollenarchitektur bizarr komplex wird. (Oder vielleicht auch nicht. Bedenken Sie public class Admin : User { ... }, dass in einer solchen Klasse möglicherweise genau der oben genannte Code enthalten ist.) Das Einrichten einer schädlichen Binärdatei ist kein häufiger Angriffsmechanismus und birgt viel mehr Chaospotenzial als eine obskure Benutzerbibliothek Es könnte sich um den Fehler der Privilegieneskalation handeln , der den echten Spielereien die Tür öffnet. Wenn schließlich eine böswillige Binär- oder Objektinstanz in die Laufzeit gelangt ist, ist es nicht viel schwieriger, sich vorzustellen, dass das "Berechtigungs- oder Sicherheitssystem" auf ähnliche Weise ersetzt wird.

Aber wenn Sie Ihren Benutzercode in einem bestimmten verwundbaren System ablegen, haben Sie das Spiel vielleicht einfach verloren.

Um genau zu sein, stimme ich dem Postulat in der Frage zu: „Ein Benutzer sollte sich nicht entscheiden, ob es sich um einen Administrator handelt oder nicht. Das Privilegien- oder Sicherheitssystem sollte. “Eine User.IsAdminMethode ist eine schlechte Idee, wenn Sie den Code auf einem System ausführen, das Sie nicht zu 100% kontrollieren, sondern tun sollten Permissions.IsAdmin(User user).

Patrick M
quelle
Huh? Wie ist das relevant? Unabhängig davon, wo Sie die Sicherheitsrichtlinie ablegen, können Sie sie möglicherweise immer mit etwas Unsicherem unterordnen. Es spielt keine Rolle, ob dies ein Benutzer, ein Prinzipal, eine Richtlinie, ein Berechtigungssatz oder was auch immer ist. Es ist ein völlig unabhängiges Thema.
Aaronaught
Es ist ein Sicherheitsargument zugunsten von Single Responsibility. Um Ihren Standpunkt direkt zu beantworten, ist das Sicherheits- und Berechtigungsmodul zweifellos eine versiegelte Klasse, aber bestimmte Designs möchten möglicherweise, dass die Benutzerklasse virtuell und geerbt ist. Es ist der letzte Punkt im Blog-Artikel (also vielleicht der unwahrscheinlichste / wichtigste?), Und vielleicht bringe ich die Themen ein wenig in Konflikt, aber angesichts der Quelle ist klar, dass Polymorphismus eine Sicherheitsbedrohung sein und daher die Verantwortlichkeiten von einschränken kann Eine bestimmte Klasse / ein bestimmtes Objekt ist sicherer.
Patrick M
Ich bin mir nicht sicher, warum du das sagst. Viele Sicherheits- und Berechtigungs-Frameworks sind hochgradig erweiterbar. Die meisten .NET-Kerntypen, die mit Sicherheit zu tun haben (IPrincipal, IIdentity, ClaimSet usw.), sind nicht nur nicht versiegelt, sondern tatsächlich Schnittstellen oder abstrakte Klassen, sodass Sie alles anschließen können, was Sie möchten. Wovon Eric spricht ist, dass Sie nicht möchten, dass etwas System.Stringvererbbar ist, da alle Arten von kritischem Framework-Code sehr spezifische Annahmen darüber machen, wie es funktioniert. In der Praxis ist der meiste "Benutzercode" (einschließlich Sicherheit) vererbbar, um Testdoppel zu ermöglichen.
Aaronaught
1
Wie auch immer, Polymorphismus wird in der Frage nirgendwo erwähnt, und wenn Sie dem Link des Autors folgen, wo der ursprüngliche Kommentar abgegeben wurde, ist klar, dass es sich nicht um Vererbung oder gar Klasseninvarianten im Allgemeinen handelt - es ging um Verantwortlichkeiten.
Aaronaught
Ich weiß, dass es nicht erwähnt wurde, aber das Prinzip der Klassenverantwortung hängt direkt mit dem Polymorphismus zusammen. Wenn es keine IsAdminMethode zum Überschreiben / Kooptieren gibt, gibt es keine potenzielle Sicherheitslücke. Die Überprüfung der Methoden dieser Systemschnittstellen, IPrincipalund IIdentityes ist klar , dass die Designer mit mir nicht einverstanden sind , und so zugeben ich den Punkt.
Patrick M
0

Das Problem ist hier:

if (user.isAdmin()) {
   doPriviledgedOp();
} else {
   // maybe we can anyway!
   doPriviledgedOp();
}

Entweder haben Sie Ihre Sicherheit richtig eingestellt, in welchem ​​Fall die privilegierte Methode prüfen soll, ob der Anrufer über ausreichende Berechtigungen verfügt - in welchem ​​Fall die Prüfung überflüssig ist. Oder Sie haben keine und vertrauen darauf, dass eine nicht privilegierte Klasse ihre eigenen Sicherheitsentscheidungen trifft.

James Anderson
quelle
4
Was ist eine benachteiligte Klasse?
Brandon
1
Es gibt wirklich nichts, was Sie tun können, um das Schreiben dieser Art von Code zu verhindern. Unabhängig davon, wie Sie Ihre App entwerfen, wird irgendwo eine solche explizite Prüfung durchgeführt, und jemand könnte sie unsicher schreiben. Selbst wenn Ihr Berechtigungssystem so rationalisiert ist wie das Dekorieren einer Methode mit einer Rolle oder einem Berechtigungsattribut, kann jemand fälschlicherweise eine "öffentliche" oder "jedermann" -Berechtigung darauf werfen. Ich verstehe also nicht wirklich, wie dies eine Methode wie User.IsAdmin spezifisch zu einem Problem macht.
Aaronaught