Pensando alle liste di controllo degli accessi legate a un'architettura MVC web, ho dei dubbi su dove dovrebbe essere eseguita la verifica.
All'interno dei modelli? Controllori? FrontController?
Immagina un'applicazione web con più moduli (come un sito Web). Ho un sito pubblico e una zona amministrativa riservata.
- Tutte le pagine del sito pubblico sono accessibili da chiunque.
- L'area di amministrazione riservata è accessibile solo da un utente autenticato.
- Ci sono alcuni livelli di accesso per gli utenti, come root, admin e utente regolare.
Ho dato questo un pensiero durante il mio pranzo oggi e alcune idee sono uscite.
Per prima cosa, dovevo pormi questa domanda:
Is access control part of business logic?
Dopo qualche tempo, ho capito che la risposta dovrebbe essere no per la maggior parte dei casi. Normalmente, per la situazione che ho descritto, la logica commerciale dell'area pubblica è circa la stessa di quella ristretta. Quando non è esatto lo stesso, quello che faccio normalmente è creare un superset di entrambe le regole della logica di business, che contenga tutta la logica necessaria. Quindi, anche se nella mia applicazione ci sono diversi moduli, normalmente ho solo un livello di modello.
Fare l'ACL all'interno dei miei controller porta alla duplicazione del codice, poiché dovrei controllarlo ogni volta che mi serve. Inoltre, otterrei un elevato accoppiamento tra i controller e il componente ACL.
Pensare di metterlo dentro FrontController sembra essere l'approccio giusto. In effetti, il mio primo pensiero era di mettere il gestore ACL come dipendenza per il FrontController.
Questo approccio avrebbe metodi di controllo (azioni) come risorse dell'ACL, creando ruoli per i miei utenti.
Alcuni pseudo-codice (combinazione di Java, C #, PHP):
aclHandler = new DefaultAcl();
aclHandler.addRole('none'); // not authenticated
aclHandler.addRole('user');
aclHandler.addRole('admin', extend='user');
aclHandler.addRole('root', extend='admin');
aclHandler.addUser('john', 'root');
aclHandler.addUser('mary', 'admin');
aclHandler.addUser('jane', 'user');
aclHandler.addResource('admin.content.create'); // MODULE.CONTROLLER.ACTION
aclHandler.addResource('admin.content.view');
aclHandler.addResource('admin.content.edit');
aclHandler.addResource('admin.content.remove');
aclHandler.addResource('public.content.view');
aclHandler.allow('*', 'public.*');
aclHandler.deny('*', 'admin.*'); // deny from all
aclHandler.allow('user', 'admin.content.view'); // allow user to view content
aclHandler.allow('admin', 'admin.content.{edit,create}'); // allow admin to edit and create content
aclHandler.allow('root', '*'); // root can do anything
In questo modo, il mio FrontController avrebbe qualcosa del tipo:
class DefaultFrontController implements FrontControllerInterface {
AclInterface acl {get};
User currentUser {get;set};
public DefaultFrontController(AclInterface acl, User user) {
this.acl = acl;
this.currentUser = user;
// ...
}
public void dispatch(ControllerRequest request, ControllerResponse response) {
try {
this.acl.checkPrivileges(request.resource, this.currentUser);
} catch (AclException e) {
this.forbiddenAccessCallback(request, response, this.acl, this.currentUser);
return;
}
// continue impl...
}
}
fc = new FrontController(aclHandler, new User(name='John', role='root'));
fc.dispatch(...); // ...
Questa implementazione rende un componente ACL un parametro obbligatorio per il costruttore di FrontController, quindi, ora fa parte del suo stato. Questo potrebbe essere un problema quando non ho bisogno di un ACL, ma può essere superato creando un'implementazione fittizia del componente ACL, come AllowFromAllAcl
, che consentirebbe a qualsiasi utente di fare qualsiasi cosa.
Qualche idea? Correzioni? Suggerimenti?