Cosa si intende per "Un utente non dovrebbe decidere se si tratta di un amministratore o meno. I privilegi o il sistema di sicurezza dovrebbero. "


55

L'esempio utilizzato nella domanda passa dati minimi nudi a una funzione che tocca il modo migliore per determinare se l'utente è un amministratore o meno. Una risposta comune è stata:

user.isAdmin()

Ciò ha provocato un commento che è stato ripetuto più volte e votato più volte:

Un utente non dovrebbe decidere se si tratta di un amministratore o meno. I privilegi o il sistema di sicurezza dovrebbero. Qualcosa che è strettamente accoppiato a una classe non significa che sia una buona idea farne parte.

Ho risposto,

L'utente non sta decidendo nulla. L'oggetto / tabella utente memorizza i dati relativi a ciascun utente. Gli utenti reali non possono cambiare tutto di se stessi.

Ma questo non è stato produttivo. Chiaramente c'è una differenza di prospettiva che sta rendendo difficile la comunicazione. Qualcuno può spiegarmi perché user.isAdmin () è male e dipingere un breve schizzo di come sembra fatto "giusto"?

Davvero, non vedo il vantaggio di separare la sicurezza dal sistema che protegge. Qualsiasi testo di sicurezza dirà che la sicurezza deve essere progettata in un sistema fin dall'inizio e considerata in ogni fase dello sviluppo, implementazione, manutenzione e persino del fine vita. Non è qualcosa che può essere imbullonato sul lato. Ma finora 17 voti positivi su questo commento dicono che mi manca qualcosa di importante.


8
Non penso che sia male, soprattutto se è solo una bandiera nella tabella db dell'utente. In caso di cambiamenti in futuro, cambiarlo in futuro. È inoltre possibile chiamare il sistema di sicurezza dall'oggetto utente. Una volta ho letto del modello di progettazione secondo cui ogni accesso db / risorsa dovrebbe passare attraverso l'oggetto utente corrente, per essere sicuro di non provare mai a fare qualcosa che non è permesso. Potrebbe essere un po 'troppo, ma l'utente potrebbe fornire un oggetto "privilegi" che copre tutte le cose relative alla sicurezza.
Cefalopode

Penso che ci sia stato un malinteso. Per te "Utente" indica la persona che utilizza il software. Per loro, "Utente" indica il codice che utilizza la tua funzionalità.
Philipp,

2
Non quello che mi hai chiesto, ma la cosa di cui starei più attento in questo caso è un metodo IsAdmin (), ovunque tu lo metta. Di solito consiglierei di avere un "Admin" non codificato, ma avere solo una serie di permessi possibili e un "Admin" sembra avere l'intero set. In questo modo, quando è necessario dividere il ruolo di amministratore in un secondo momento, le cose sono molto più semplici. E riduce la logica del caso speciale.
psr

1
@Philipp Non credo ... Entrambi i siti parlano esplicitamente di un Useroggetto con un isAdmin()metodo (qualunque cosa faccia dietro le quinte)
Izkata

Troppo tardi per modificarlo; Intendevo "Entrambe le parti", non "Entrambi i siti" ... = P
Izkata,

Risposte:


12

Pensa all'utente come chiave e alle autorizzazioni come valore.

Contesti diversi potrebbero avere autorizzazioni diverse per ciascun utente. Poiché le autorizzazioni sono pertinenti al contesto, è necessario modificare l'utente per ciascun contesto. Quindi è meglio che metti quella logica altrove (ad esempio la classe di contesto) e cerchi le autorizzazioni dove necessario.

Un effetto collaterale è che potrebbe rendere il tuo sistema più sicuro, perché non puoi dimenticare di cancellare il flag di amministrazione per i luoghi in cui non è rilevante.


40

Quel commento era formulato male.

Il punto non è se l'oggetto utente "decide" se si tratta di un amministratore, un utente di prova, un superutente o qualcosa dentro o fuori dall'ordinario. Ovviamente non decide nessuna di queste cose, il sistema software in generale, come implementato da te, lo fa. Il punto è se la classe "utente" deve rappresentare i ruoli dell'entità rappresentata dai suoi oggetti o se questa è una responsabilità di un'altra classe.


6
Ahi (era il mio commento), ma lo metti molto meglio di me.
Marjan Venema,

Stai dicendo di modellare i ruoli in una classe di ruolo separata, non nella classe utente? Non stai raccomandando un sistema separato come LDAP? Che cosa succede se ogni singola decisione che la classe Role prende richiede la query del record utente appropriato e non richiede altre informazioni? Dovrebbe essere comunque separato dall'Utente e, in tal caso, perché?
GlenPeterson,

2
@GlenPeterson: non necessariamente, solo quelle classi <> tabelle e di solito ci sono molte più classi da discernere rispetto alle tabelle. Essere orientati ai dati porta alla definizione di elenchi secondari sulle classi, quando molte volte questi elenchi devono essere definiti su un altro "libro mastro" (ordini) o su una tale classe che passa nell'utente (o nell'ordine) per il quale recuperare ... È più una questione di responsabilità che di verbi e sostantivi.
Marjan Venema,

9
Pensa all'utente come chiave e alle autorizzazioni come valore. Contesti diversi potrebbero avere autorizzazioni diverse per ciascun utente. Poiché le autorizzazioni sono rilevanti per il contesto, è necessario modificare l'utente per ciascun contesto. Quindi è meglio che metti quella logica altrove (ad esempio la classe di contesto).
Wilbert,

2
@Wilbert: Context! Ciò è particolarmente illuminante! Sì, se hai più di un contesto per quelle autorizzazioni, allora tutto ha senso per me. Di solito vedo queste autorizzazioni in un unico contesto e attaccarle all'utente è la soluzione più semplice in quel caso. Chiaramente non vi appartengono se si applicano in modo diverso in un secondo contesto. Se lo scrivi come risposta, probabilmente lo accetterò. Grazie.
GlenPeterson,

30

Non avrei scelto di pronunciare il commento originale nel modo in cui è stato formulato, ma identifica un problema potenzialmente legittimo.

In particolare, le preoccupazioni che giustificano la separazione sono l' autenticazione e l' autorizzazione .

L'autenticazione si riferisce al processo di accesso e acquisizione di un'identità. È come i sistemi sanno chi sei e usato per cose come la personalizzazione, la proprietà degli oggetti, ecc.

L'autorizzazione si riferisce a ciò che ti è permesso fare e questo (generalmente) non è determinato da chi sei . Invece, è determinato da alcune politiche di sicurezza come ruoli o autorizzazioni, a cui non interessano cose come il tuo nome o indirizzo email.

Questi due possono cambiare ortogonalmente tra loro. Ad esempio, è possibile modificare il modello di autenticazione aggiungendo provider OpenID / OpenAuth. E potresti cambiare la politica di sicurezza aggiungendo un nuovo ruolo o passando da RBAC ad ABAC.

Se tutto ciò rientra in una classe o astrazione, il tuo codice di sicurezza, che è uno dei tuoi strumenti più importanti per la mitigazione del rischio , diventa, ironicamente, ad alto rischio.

Ho lavorato con sistemi in cui l'autenticazione e l'autorizzazione erano troppo strettamente accoppiate. In un sistema c'erano due database utente paralleli, ciascuno per un tipo di "ruolo". La persona o il team che lo ha progettato apparentemente non ha mai considerato che un singolo utente fisico potesse essere in entrambi i ruoli o che potrebbero esserci determinate azioni comuni a più ruoli o che potrebbero esserci problemi con le collisioni dell'ID utente. Questo è certamente un esempio estremo, ma è stato / è incredibilmente doloroso lavorare con.

Microsoft e Sun / Oracle (Java) si riferiscono all'aggregato delle informazioni di autenticazione e autorizzazione come Responsabile della sicurezza . Non è perfetto, ma funziona abbastanza bene. In .NET, ad esempio, hai IPrincipal, che incapsula il IIdentityprimo essendo un oggetto policy (autorizzazione) mentre il secondo è un'identità (autenticazione). Potresti ragionevolmente mettere in dubbio la decisione di metterne uno dentro l'altro, ma la cosa importante è che la maggior parte del codice che scrivi sarà solo per una delle astrazioni, il che significa che è facile testare e refactoring.

Non c'è niente di sbagliato in un User.IsAdmincampo ... a meno che non ci sia anche un User.Namecampo. Ciò indicherebbe che il concetto di "Utente" non è definito correttamente e questo è, purtroppo, un errore molto comune tra gli sviluppatori che sono un po 'bagnati dietro le orecchie quando si tratta di sicurezza. In genere, l'unica cosa che dovrebbe essere condivisa dall'identità e dalla politica è l'ID utente, che, non a caso, è esattamente come viene implementato nei modelli di sicurezza Windows e * nix.

È completamente accettabile creare oggetti wrapper che incapsulino identità e criteri. Ad esempio, faciliterebbe la creazione di una schermata del dashboard in cui è necessario visualizzare un messaggio "ciao" oltre a vari widget o collegamenti a cui l'utente corrente può accedere. Fintanto che questo wrapper avvolge solo le informazioni sull'identità e sulla politica e non pretende di possederlo. In altre parole, purché non sia presentato come radice aggregata .

Un modello di sicurezza semplicistico sembra sempre una buona idea quando si progetta per la prima volta una nuova applicazione, a causa di YAGNI e tutto il resto, ma quasi sempre finisce per tornare a morderti più tardi, perché, sorpresa sorpresa, vengono aggiunte nuove funzionalità!

Quindi, se sai cosa è meglio per te, manterrai le informazioni di autenticazione e autorizzazione separate. Anche se "l'autorizzazione" in questo momento è semplice come un flag "IsAdmin", starai comunque meglio se non fa parte della stessa classe o tabella delle informazioni di autenticazione, quindi se e quando la tua politica di sicurezza deve cambiare, non è necessario eseguire interventi di chirurgia ricostruttiva sui sistemi di autenticazione che funzionano già bene.


11
Oh, vorrei aver avuto il tempo di scrivere questo. Inoltre, probabilmente non sarei stato in grado di dirlo come hai fatto senza pensarci molto di più da parte mia. Molte volte il mio istinto istintivo mi dice di andare in un modo o nell'altro, ma spiegare perché - a me stesso o a qualcun altro - ci vuole molto più pensiero e impegno. Grazie per questo post L'avrebbe fatto più volte, ma SE non me lo ha permesso ...
Marjan Venema il

Sembra sorprendentemente simile a un progetto a cui sto lavorando e la tua risposta mi ha dato un'altra prospettiva. Grazie mille!
Brandon,

"Non c'è niente di sbagliato in un campo User.IsAdmin ... a meno che non ci sia anche un campo User.Name" che scrivi. Ma cos'è isAdminun metodo? Potrebbe semplicemente delegare a un oggetto la cui responsabilità è tenere traccia delle autorizzazioni. Qual è la migliore pratica nella progettazione di un modello relazionale (quali campi ha un'entità) non è necessariamente traducibile in ciò che è la migliore pratica in un modello OO (a quali messaggi un oggetto può rispondere).
KaptajnKold,

@KaptajnKold: continuo a leggere questo sentimento in vari thread di domande e risposte in questo forum. Se esiste un metodo, non importa se esegue direttamente le operazioni o le delega , conta comunque come una responsabilità perché altre classi possono fare affidamento su di essa . Il vostro User.IsAdminpotrebbe benissimo essere una battuta che non fa altro che chiamata PermissionManager.IsAdmin(this.Id), ma se viene referenziato da 30 altre classi, non è possibile modificare o rimuoverla. Ecco perché esiste l'SRP.
Aaronaught il

E, @KaptajnKold, per quanto riguarda i modelli relazionali, non sono sicuro di cosa stai arrivando; è anche peggio avere un IsAdmin campo . È il tipo di campo che tende a rimanere a lungo dopo che il sistema si è evoluto in RBAC o qualcosa di più complesso, e gradualmente si sincronizza con le autorizzazioni "reali" e le persone dimenticano che non dovrebbero più utilizzarlo, e ... beh, basta dire di no. Anche se pensi di avere solo due ruoli, "admin" e "non admin", non memorizzarlo come un campo booleano / bit, normalizzare correttamente e disporre di una tabella di autorizzazioni.
Aaronaught il

28

Beh, almeno viola il principio della singola responsabilità . Dovrebbero esserci cambiamenti (livelli amministrativi multipli, ruoli ancora più diversi?) Questo tipo di design sarebbe abbastanza disordinato e terribile da mantenere.

Considerare il vantaggio di separare il sistema di sicurezza dalla classe utente, in modo che entrambi possano avere un ruolo unico e ben definito. Si finisce con un design più pulito, più facile da mantenere nel complesso.


2
@GlenPeterson: No, gli utenti possono benissimo esistere senza sicurezza. E il mio nome, il mio nome visualizzato, il mio indirizzo, il mio indirizzo e-mail ecc. Dove li metteresti? L'identificazione (autenticazione) e l'autorizzazione sono animali completamente diversi.
Marjan Venema,

2
class Userè un componente chiave nella tripletta di sicurezza di identificazione, autenticazione, autorizzazione . Questi sono intimamente legati a livello di progettazione, quindi non è irragionevole che il codice rifletta questa interdipendenza.
MSalters

1
La domanda è: se la classe User dovesse contenere tutta la logica per gestire la tripletta!
Zavior,

3
@MSalters: se si tratta di un'API per tale logica, ne è a conoscenza anche se delega altrove la logica esatta. Il punto in questione è che l'autorizzazione (autorizzazioni) riguarda una combinazione di un utente con qualcos'altro e quindi non dovrebbe far parte né dell'utente né di qualcos'altro, ma parte dei sistemi di autorizzazioni che sono giustamente a conoscenza sia degli utenti sia di qualunque altra cosa viene dato il permesso per.
Marjan Venema,

2
Should there be changes- beh, non sappiamo che ci saranno cambiamenti. YAGNI e tutti. Questi cambiamenti cambiano quando diventa necessario, giusto?
Izkata,

10

Penso che il problema, forse formulato male, sia che dà troppo peso alla Userclasse. No, non esiste alcun problema di sicurezza nell'avere un metodo User.isAdmin(), come è stato suggerito in questi commenti. Dopotutto, se qualcuno è abbastanza in profondità nel tuo codice per iniettare codice dannoso per una delle tue classi principali, hai seri problemi. D'altra parte, non ha senso Userdecidere se si tratta di un amministratore per ogni contesto. Immagina di aggiungere ruoli diversi: moderatori per il tuo forum, redattori che possono pubblicare post sulla prima pagina, scrittori che possono scrivere contenuti che gli editori possano pubblicare, e così via. Con l'approccio di metterlo Usersull'oggetto, finirai con troppi metodi e troppa logica che vivono su Userciò che non è direttamente correlato alla classe.

Invece, è possibile utilizzare un enum di diversi tipi di autorizzazioni e una PermissionsManagerclasse. Forse potresti avere un metodo come PermissionsManager.userHasPermission(User, Permission), restituire un valore booleano. In pratica, potrebbe apparire come (in Java):

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

È essenzialmente equivalente al metodo del before_filtermetodo Rails , che fornisce un esempio di questo tipo di controllo delle autorizzazioni nel mondo reale.


6
In che modo è diverso da user.hasPermission(Permissions.EDITOR), tranne che è più verboso e procedurale invece di OO?
Cefalopode

9
@Arian: perché user.hasPermissionsmette troppe responsabilità nella classe utente. È necessario che l'utente sia a conoscenza delle autorizzazioni, mentre un utente (classe) dovrebbe essere in grado di esistere senza tale conoscenza. Non vuoi che una classe utente sappia come stampare o visualizzare (compilare un modulo), lascialo a un renderer / builder stampante o a un renderer / classe builder display.
Marjan Venema,

4
user.hasPermission(P)è l'API corretta, ma è solo l'interfaccia. La vera decisione deve ovviamente essere presa in un sottosistema di sicurezza. Per rispondere al commento di Syrion sui test, durante i test è possibile scambiare quel sottosistema. Tuttavia, il vantaggio del semplice user.hasPermission(P)è che non inquini tutto il codice solo per poterlo testare class User.
MSalters

3
@MarjanVenema Secondo questa logica, l'utente rimarrebbe un oggetto dati senza alcuna responsabilità. Non sto dicendo che l'intera gestione delle autorizzazioni dovrebbe essere implementata nella classe utente, se è necessario un sistema così complesso. Dici che un utente non ha bisogno di conoscere le autorizzazioni, ma non vedo perché ogni altra classe debba sapere come risolvere le autorizzazioni per un utente. Questo rende molto più difficile deridere e testare.
Cefalopode

6
@Arian: mentre le classi anemiche sono un odore, alcune classi non hanno alcun comportamento ... L'utente, imho, è una di queste classi che è meglio usata come parametro per un sacco di altre cose (che vogliono fare cose / prendere decisioni in base a chi li sta chiedendo), piuttosto che essere usato come contenitore per tutti i tipi di comportamento solo perché sembra conveniente codificarlo in quel modo.
Marjan Venema,

9

Object.isX () viene utilizzato per rappresentare una chiara relazione tra l'oggetto e X, che può essere espressa come risultato booleano, ad esempio:

Water.isBoiling ()

Circuit.isOpen ()

User.isAdmin () assume un unico significato di "Amministratore" in ogni contesto del sistema , che un utente è, in ogni parte del sistema, un amministratore.

Anche se sembra abbastanza semplice, un programma del mondo reale non si adatterà quasi mai a questo modello, ci sarà sicuramente un requisito per un utente di amministrare risorse [X] ma non [Y], o per tipi più distinti di amministratori (un [progetto ] amministratore vs un amministratore di [sistema]).

Questa situazione di solito richiede un'indagine sui requisiti. Raramente, se mai, un cliente vorrà la relazione che User.isAdmin () rappresenta effettivamente, quindi esiterei a implementare tale soluzione senza chiarimenti.


6

Il poster aveva semplicemente in mente un design diverso e più complicato. Secondo me User.isAdmin()va bene . Anche se in seguito introduci qualche complicato modello di autorizzazioni, vedo poche ragioni per spostarmi User.isAdmin(). Successivamente, potresti voler dividere la classe User in un oggetto che rappresenta un utente connesso e un oggetto statico che rappresenta i dati sull'utente. Oppure no. Risparmia domani per domani.


4
Save tomorrow for tomorrow. Si. Quando capisci che il codice strettamente accoppiato che hai appena scritto ti ha incantato e devi riscriverlo perché non hai impiegato 20 minuti in più per disaccoppiarlo ...
MirroredFate

5
@MirroredFate: è del tutto possibile avere un User.isAdminmetodo collegato a un meccanismo di autorizzazione arbitrario senza accoppiare la classe User a quel meccanismo specifico.
Kevin Cline,

3
Affinché User.isAdmin sia accoppiato a un meccanismo di autorizzazioni arbitrario, anche senza accoppiamento a un meccanismo specifico, è necessario ... beh ... accoppiamento. Il minimo dei quali sarebbe un'interfaccia. E quell'interfaccia semplicemente non dovrebbe essere lì. Sta dando alla classe utente (e all'interfaccia) qualcosa di cui semplicemente non dovrebbe essere responsabile. Aaronaught lo spiega molto meglio di me (così come la combinazione di tutte le altre risposte a questa domanda).
Marjan Venema,

8
@MirroredFate: non mi interessa se devo riscrivere una semplice implementazione per soddisfare requisiti più complessi. Ma ho avuto esperienze davvero brutte con API eccessivamente complesse progettate per soddisfare requisiti futuri immaginati che non sono mai sorti.
Kevin Cline,

3
@kevincline Mentre le API troppo complesse sono (per definizione) una cosa negativa, non stavo suggerendo che si dovrebbero scrivere API troppo complesse. Stavo affermando che Save tomorrow for tomorrowè un terribile approccio progettuale perché rende i problemi che sarebbero banali se il sistema fosse implementato correttamente molto peggio. In effetti, questa mentalità è ciò che si traduce in API eccessivamente complesse, poiché un livello su un livello viene aggiunto per rattoppare il progetto iniziale scadente. Immagino che la mia posizione sia measure twice, cut once.
MirroredFate

5

Non è davvero un problema ...

Non vedo problemi con User.isAdmin(). Lo preferisco sicuramente a qualcosa del genere PermissionManager.userHasPermission(user, permissions.ADMIN), che nel santo nome di SRP rende il codice meno chiaro e non aggiunge nulla di valore.

Penso che SRP venga interpretato un po 'alla lettera da alcuni. Penso che vada bene, anche preferibile che una classe abbia un'interfaccia ricca. SRP significa solo che un oggetto deve delegare ai collaboratori tutto ciò che non rientra nella sua unica responsabilità. Se il ruolo di amministratore di un utente è qualcosa di più coinvolto di un campo booleano, potrebbe avere molto senso che l'oggetto utente deleghi a un PermissionsManager. Ma ciò non significa che non sia anche una buona idea per un utente mantenere il suo isAdminmetodo. In realtà significa che quando l'applicazione passa da semplice a complessa, è necessario modificare il codice che utilizza l'oggetto Utente. IOW, il tuo client non ha bisogno di sapere cosa serve veramente per rispondere alla domanda se un utente è o meno un amministratore.

... ma perché vuoi davvero saperlo?

Detto questo, mi sembra che raramente hai bisogno di sapere se un utente è un amministratore se non per essere in grado di rispondere ad altre domande, ad esempio se a un utente è permesso o meno di eseguire un'azione specifica, ad esempio aggiornando un widget. In tal caso, preferirei avere un metodo su Widget, come isUpdatableBy(User user):

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

In questo modo un Widget è responsabile di sapere quali criteri devono essere soddisfatti per essere aggiornato da un utente e un utente è responsabile di sapere se si tratta di un amministratore. Questo design comunica chiaramente le sue intenzioni e semplifica il passaggio a logiche aziendali più complesse se e quando arriverà quel momento.

[Modificare]

Il problema di non avere User.isAdmin()e utilizzarePermissionManager.userHasPermission(...)

Ho pensato di aggiungere alla mia risposta per spiegare perché preferisco di gran lunga chiamare un metodo sull'oggetto Utente piuttosto che chiamare un metodo su un oggetto PermissionManager se voglio sapere se un utente è un amministratore (o ha un ruolo di amministratore).

Penso che sia giusto presumere che dipenderai sempre dalla classe User ovunque tu debba porre la domanda : questo utente è un amministratore? Questa è una dipendenza di cui non puoi liberarti. Ma se è necessario passare l'utente a un altro oggetto per porre una domanda al riguardo, ciò crea una nuova dipendenza da quell'oggetto in aggiunta a quella che si ha già sull'Utente. Se la domanda viene posta molto, sono molti i luoghi in cui crei una dipendenza aggiuntiva, ed è un sacco di posti che potresti dover cambiare se una delle dipendenze lo richiede.

Confrontalo con lo spostamento della dipendenza nella classe User. Ora, improvvisamente hai un sistema in cui il codice client (codice che deve porre la domanda è questo utente un amministratore ) non è accoppiato all'implementazione di come questa domanda riceve risposta. Sei libero di cambiare completamente il sistema di autorizzazione e devi solo aggiornare un metodo in una classe per farlo. Tutto il codice client rimane lo stesso.

Insistere sul non avere un isAdminmetodo sull'Utente per paura di creare una dipendenza nella classe Utente dal sottosistema delle autorizzazioni, è secondo me simile a spendere un dollaro per guadagnare un centesimo. Certo, eviti una dipendenza nella classe Utente, ma a costo di crearne una in ogni luogo in cui è necessario porre la domanda. Non è un buon affare.


2
"SRP significa semplicemente che un oggetto deve delegare ai collaboratori tutto ciò che non rientra nella sua unica responsabilità" - falso . La definizione di SRP comprende tutto ciò che un oggetto fa direttamente e tutto ciò che delega. Una classe che esegue direttamente solo un'attività, ma esegue altre 50 attività indirettamente tramite delega, sta ancora (probabilmente) violando l'SRP.
Aaronaught,

KaptajnKold: Ho fatto +1 su perché sono d'accordo con te e mi sento un po 'in colpa per questo. Il tuo post si aggiunge alla discussione, ma non risponde alla domanda.
GlenPeterson,

@GlenPeterson Beh, ho cercato di affrontare la parte della domanda che era 'come sembra fatto "giusto"' Inoltre: non dovresti assolutamente sentirti in colpa per essere d'accordo con me :)
KaptajnKold

1
@JamesSnell Forse. Se. Ma questo è un dettaglio di implementazione che limiterei ancora all'interno del metodo isAdmin su User. In questo modo il codice client non deve cambiare quando l '"amministrazione" di un Utente si evolve da campo booleano a qualcosa di più avanzato. Potresti avere molti posti in cui devi sapere se un utente è un amministratore e non vuoi cambiarli ogni volta che cambia il tuo sistema di autorizzazione.
KaptajnKold,

1
@JamesSnell Forse ci siamo fraintesi? La domanda se un utente sia o meno un amministratore non è la stessa domanda se a un utente sia permesso o meno di eseguire un'azione specifica . La risposta alla prima domanda è sempre indipendente dal contesto. La risposta alla seconda dipende molto dal contesto. Questo è quello che ho cercato di affrontare nella seconda metà della mia risposta originale.
KaptajnKold,

1

Questa discussione mi ha ricordato un post sul blog di Eric Lippert, che è stato portato alla mia attenzione in una discussione simile su design di classe, sicurezza e correttezza.

Perché vengono sigillate così tante classi quadro?

In particolare, Eric solleva il punto:

4) Sicuro. il punto centrale del polimorfismo è che puoi passare intorno a oggetti che sembrano animali ma in realtà sono giraffe. Ci sono potenziali problemi di sicurezza qui.

Ogni volta che si implementa un metodo che accetta un'istanza di un tipo non sigillato, È NECESSARIO scrivere quel metodo per essere robusto di fronte a istanze potenzialmente ostili di quel tipo. Non puoi fare affidamento su eventuali invarianti che sai essere veri per le TUE implementazioni, perché alcune pagine web ostili potrebbero sottoclassare la tua implementazione, sovrascrivere i metodi virtuali per fare cose che incasinano la tua logica e la passano. Ogni volta che sigillo una classe , Posso scrivere metodi che usano quella classe con la certezza di sapere cosa fa quella classe.

Questo può sembrare tangente, ma penso che sia in linea con il punto sollevato da altri poster sul principio della responsabilità singola SOLID . Considera la seguente classe dannosa come motivo per non implementare un metodo o una proprietà.User.IsAdmin

public class MyUser : User
{

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

    // Anything else we can break today?

}

Certo, è un po 'allungato: nessuno ha ancora suggerito di creare IsAmdminun metodo virtuale, ma potrebbe accadere se la tua architettura utente / ruolo finisse per essere stranamente complessa. (O forse no. Considera public class Admin : User { ... }: una classe del genere potrebbe contenere esattamente quel codice sopra.) L'inserimento di un binario dannoso non è un vettore di attacco comune e ha molto più potenziale per il caos di una oscura libreria di utenti - poi di nuovo, questo potrebbe essere il bug di escalation di privilegi che apre le porte ai veri shenanigans. Infine, se un'istanza binaria o oggetto dannosa si è fatta strada nel tempo di esecuzione, non è molto più lungo immaginare che il "Privilege or Security System" venga sostituito in modo simile.

Ma tenendo a cuore il punto di vista di Eric, se metti il ​​tuo codice utente in un particolare tipo di sistema vulnerabile, forse hai perso il gioco.

Oh, e solo per essere precisi per la cronaca, sono d'accordo con il postulato nella domanda: “Un utente non dovrebbe decidere se si tratta di un amministratore o meno. I privilegi o il sistema di sicurezza dovrebbero. ”Un User.IsAdminmetodo è una cattiva idea se si esegue il codice sul sistema in modo da non mantenere il controllo del codice al 100% - è necessario invece Permissions.IsAdmin(User user).


Eh? Come è pertinente? Indipendentemente da dove metti la politica di sicurezza, potresti sempre potenzialmente sottoclassarla con qualcosa di insicuro. Non importa se si tratta di un Utente, Principal, Policy, PermissionSet o altro. È un problema totalmente indipendente.
Aaronaught,

È un argomento di sicurezza a favore della responsabilità singola. Per rispondere direttamente al tuo punto, il modulo di sicurezza e autorizzazioni è dannatamente una classe sigillata, ma alcuni progetti potrebbero desiderare che la classe Utente sia virtuale ed ereditata. È l'ultimo punto dell'articolo del blog (quindi forse il meno probabile / importante?), E forse sto unendo un po 'i problemi, ma data la fonte, è chiaro che il polimorfismo può essere una minaccia alla sicurezza e quindi limitare le responsabilità di una data classe / oggetto è più sicura.
Patrick M,

Non sono sicuro del perché lo dici. Molti framework di sicurezza e permessi sono altamente estensibili. La maggior parte dei tipi .NET principali relativi alla sicurezza (IPrincipal, IIdentity, ClaimSet, ecc.) Non solo non sono sigillati ma sono in realtà interfacce o classi astratte in modo da poter collegare tutto ciò che si desidera. Ciò di cui Eric sta parlando è che non vorresti che qualcosa di simile System.Stringfosse ereditabile perché tutti i tipi di codice quadro critico fanno ipotesi molto specifiche su come funziona. In pratica, la maggior parte del "codice utente" (inclusa la sicurezza) è ereditabile per consentire il doppio dei test.
Aaronaught,

1
Ad ogni modo, il polimorfismo non è menzionato da nessuna parte nella domanda, e se segui il link dell'autore su dove è stato fatto il commento originale, è chiaro che non si riferiva all'eredità o persino agli invarianti di classe in generale - si trattava di responsabilità.
Aaronaught,

So che non è stato menzionato, ma il principio delle responsabilità di classe è direttamente correlato al polimorfismo. Se non esiste un IsAdminmetodo per sostituire / cooptare, non esiste una potenziale falla nella sicurezza. Rivedere i metodi di quelle interfacce di sistema IPrincipale IIdentity, è chiaro che i progettisti non sono d'accordo con me, e quindi concedo il punto.
Patrick M,

0

Il problema qui è:

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

O hai impostato correttamente la tua sicurezza, nel qual caso il metodo privilegiato dovrebbe verificare se il chiamante ha autorità sufficiente, nel qual caso il controllo è superfluo. Oppure non hai e ti fidi di una classe non privilegiata per prendere le proprie decisioni in materia di sicurezza.


4
Che cos'è una classe senza privilegi?
Brandon,

1
Non c'è davvero nulla che tu possa fare per impedire che questo tipo di codice venga scritto. Non importa come si progetta la tua app, da qualche parte ci sarà un controllo esplicito come questo e qualcuno potrebbe scriverlo per essere insicuro. Anche se il tuo sistema di autorizzazioni è semplificato come la decorazione di un metodo con un ruolo o un attributo di autorizzazione - qualcuno potrebbe erroneamente lanciare un'autorizzazione "pubblica" o "tutti" su di essa. Quindi non vedo davvero come questo renda un metodo come User.IsAdmin specificamente un problema.
Aaronaught il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.