Come implementare in modo pulito l'accesso alla funzione basata su autorizzazione

7

Mi è stato assegnato il compito di scrivere un controllo on / off per le funzionalità del nostro prodotto in base a chi ha effettuato l'accesso, in linea di principio con un flag di attivazione / disattivazione per ciascuna funzione. In parole semplici, si tratta di un accesso basato su autorizzazioni che può essere attivato a livello di utente.

Il problema è che posso vedere questo diventare un incubo di complessità. Ad esempio, la persona A è a parte del gruppo G ma ha missioni speciali 1, 2, 3 ma A è completamente diversa dalla persona B che è a parte del gruppo H che ha permessi speciali 2, 3 e 4 ecc.

Gestire le autorizzazioni non sarà la parte più difficile: è solo l'impostazione di un valore nel database. La parte difficile sta permettendo alla persona A di essere in grado di usare la caratteristica F se hanno il permesso P. Non voglio che questo diventi centinaia di istruzioni se sparse sul codice base le cui combinazioni sono esponenziali.

Una soluzione che ho in mente è quella di separare la funzione dal permesso avendo un livello di controllo proprio sopra la funzione che tutto il resto usa, simile alla scrittura di una libreria e all'uso di un linguaggio di scripting per le strutture di controllo. Questo separerebbe le istruzioni if / else dalla funzione effettiva, ma il problema è che esistono ancora e le loro combinazioni sono ancora lì.

Conosci qualche soluzione elegante a questo problema o qualcosa di simile?

    
posta zzelman 28.03.2017 - 19:03
fonte

3 risposte

3

1.Organizza le autorizzazioni e il relativo controllo

In effetti, se si definiscono le autorizzazioni di accesso alle singole funzionalità, si avrà, come in commuta le funzioni , un if -clause in ogni funzione per verificare se l'utente ha il permesso giusto.

Se si estrae la funzione dall'autorizzazione, ad esempio utilizzando un livello di riferimento indiretto, come un gruppo di caratteristiche (ad esempio una funzione appartiene a un gruppo configurabile) o un profilo di autorizzazione (ad esempio, diversi utenti appartengono a un profilo, che ha diverse autorizzazioni per le funzionalità), sarà comunque necessaria una clausola if per ciascuna caratteristica.

Per renderlo facile da gestire, dovresti in ogni caso incapsulare il controllo dei permessi in un oggetto permesso. Ciò consentirebbe di implementare il collegamento tra la funzionalità e l'utente con una qualsiasi delle strategie sopra menzionate, senza dover cambiare il codice:

user.permission("feature_X").mandatory_check(); // if needed, throws a permission exception 
                                                // to be catched at right level. 

o

if (user.permission("feature_Y").optional_check() ) {
   ...                                         // execute optional feature 
}                                              // do nothing if access is not granted

2.Controllo include l'accesso anticipato

Se le funzionalità sono funzioni indipendenti, è possibile controllare l'accesso in anticipo.

Ciò significa che l'interfaccia utente deve garantire che l'utente possa solo richiamare le funzioni e i comandi per i quali l'utente ha il permesso. Ciò consente di isolare i controlli dei permessi nella tua architettura. Le varie funzionalità non dovranno quindi implementare ulteriori controlli.

Sfortunatamente, questo non funzionerà se la funzione è opzionale, cioè l'utente può richiamare una funzione, e la funzione viene eseguita in modo diverso se la funzione è presente o meno.

Gestisci le funzionalità come strategia

Questo approccio utilizza le strategie di runtime. La strategia o invoca una funzionalità o nulla. In questo caso, Context sarebbe una specie di oggetto profilo utente inizializzato all'accesso. Per ogni caratteristica ci sarebbe una strategia (questo potrebbe rimanere flessibile, usando un contenitore associativo come una mappa delle caratteristiche). Le strategie sarebbero inizializzate in base alle autorizzazioni dell'utente.

Successivamente, quando vengono eseguite le diverse funzioni del software, utilizzeranno la strategia pertinente (ad esempio, un oggetto che eseguirà la funzione giusta o un oggetto strategia vuoto che non fa nulla).

Questo approccio sembra piuttosto elegante. Tuttavia, anziché aggiungere un if nel codice di ciascuna funzionalità, sarebbe necessario implementare ciascuna funzionalità come strategia autonoma, che potrebbe essere estremamente impegnativa.

E allora?

Personalmente, anche se il secondo sembra più elegante, opterei per l'opzione 1: questo è molto più flessibile. L'incapsulamento corretto dell'autorizzazione può ridurre il controllo dell'autorizzazione nella funzionalità a una singola istruzione.

    
risposta data 28.03.2017 - 20:38
fonte
2

Ci sono molti modi diversi di fare le autorizzazioni, ma qui ci sono alcuni suggerimenti.

Ricorda che qui ci sono due programmi: l'interfaccia amministratore, che consente a un amministratore di configurare utenti, gruppi e permessi; e il programma principale, che deve sapere chi ha quali permessi.

Inizia con il programma principale

Le autorizzazioni vengono impostate solo occasionalmente. Ma vengono letti più e più volte. La lettura è molto più importante in questo senso.

Finirai per aggiungere questo tipo di codice a quasi ogni pagina / modulo:

bool ok = HasPermission(user, permissionCode);

e talvolta

AssertHasPermission(user, permissionCode);

o forse

AssertHasPermission(user, permissionCode, Action callbackIfFailed);

quest'ultimo potrebbe essere utile se chiamato in questo modo:

AssertHasPermission(context.User, "108", () => context.Redirect("~/Error.html"));

Vuoi che questo codice funzioni in modo molto efficiente. Qualunque struttura di dati tu usi, deve essere semplice; prendere un utente e un permesso e vedere se esiste. Suggerisco una struttura piatta / denormalizzata. Al momento dell'esecuzione, non dovrebbe essere necessario camminare su un albero, ad esempio, o applicare regole a cascata.

Se i tuoi requisiti includono l'appartenenza al gruppo e le autorizzazioni di gruppo, implementalo in un secondo momento ... vedi sotto.

Una funzione potrebbe non essere una funzione

Per un utente finale, una "funzione" potrebbe essere composta da diverse funzioni nel codice. Per questo motivo, è necessario almeno uno strato di riferimento indiretto tra un'autorizzazione e una base di codice. Ecco perché nell'esempio sopra sto passando un codice di autorizzazione e non, per esempio, il nome di una pagina. Avrai bisogno di questa astrazione nel caso tu abbia bisogno di aggiungere più pagine a una funzione, o se una pagina deve essere suddivisa in diverse funzionalità (ad esempio, leggi e scrivi per lo stesso oggetto dominio).

Gruppi, permessi a cascata e regole di aggiunta / sottrazione

Sembra che i tuoi requisiti includano le autorizzazioni di gruppo. Questo genere di cose è quasi estetico - è principalmente per rendere le cose più facili per un utente finale, cioè gestire più utenti in un blocco inserendoli in un gruppo. Ma poi ci sono delle eccezioni; forse hai bisogno della possibilità di sovrascrivere le autorizzazioni di gruppo per alcuni membri, ad esempio, o hai bisogno di due permessi diversi per accedere a una singola funzione. Puoi impazzire con le opzioni.

La cosa importante è ... mantenere queste opzioni fuori dalla base del codice principale. Il tuo codice principale non dovrebbe interessare se un utente fa parte di un gruppo; importa solo se ha il giusto codice di autorizzazione dopo l'applicazione di tutte le regole di gruppo.

Suggerirei che, dopo aver accettato l'input dall'amministratore, il programma di amministrazione debba mantenere una struttura separata per gruppi e utenti, quindi camminare sulla struttura e comporre la struttura piatta che verrà utilizzata dal programma principale. Puoi pensare a questo come "compilazione" o denormalizzazione.

Se lo fai correttamente, ti risparmierai molto lavoro. In seguito, se i concetti relativi all'appartenenza al gruppo e alle autorizzazioni di gruppo cambiano (ad esempio, aggiungi blacklist e whitelisting o ti integrano con i gruppi di Active Directory, ecc.), L'elenco delle autorizzazioni calcolate dovrebbe rimanere la stessa vecchia struttura piatta alla fine . In questo modo puoi modificare l'interfaccia utente di amministrazione in base al contenuto del tuo cuore e il tuo codice base principale non richiederà alcuna modifica.

YAGNI

Nella mia esperienza, le persone diventano molto creative quando escono le possibilità di gestire le autorizzazioni. In quasi tutti i casi che ho visto, l'implementazione risultante è molto più complessa di quanto sia effettivamente necessario. Mantieni le cose semplici. Non cercare di includere ogni caso d'uso immaginabile. Concentrati invece sull'estensibilità e ricorda che puoi sempre migliorare le strutture del gruppo senza influire sul programma principale.

    
risposta data 28.03.2017 - 23:38
fonte
1

Un approccio molto comune che in genere richiede un numero limitato di combinazioni è controllo dell'accesso basato sui ruoli . In RBAC, assegneresti ad ogni risorsa un nome, possibilmente in una gerarchia. Quindi crei ruoli con permessi per eseguire determinati verbi (come "crea", "leggi", "elenca", "guarda", "elimina", "esegui") su quelle risorse. Quindi assegni soggetti a quei ruoli.

Diciamo per il tuo esempio che le tue autorizzazioni sono mappate su:

  1. Crea nuovo cliente
  2. Leggi le informazioni del cliente
  3. Leggi log
  4. Esegui autotest

È possibile creare un ruolo "Servizio clienti" con autorizzazioni 1-3 e un ruolo "Tecnico" con autorizzazioni 2-4, quindi assegnare tali ruoli ai soggetti in modo appropriato. Potresti assegnare entrambi questi ruoli a un manager.

Il tuo codice per la lettura dei log deve solo verificare se l'oggetto corrente è assegnato a un ruolo con autorizzazione "lettura" sulla risorsa "log".

Ciò consente una mappatura flessibile dai ruoli utente ai gruppi di permessi che non devi indovinare fin dall'inizio, e non deve essere la stessa per ogni cliente.

    
risposta data 29.03.2017 - 14:53
fonte

Leggi altre domande sui tag