Thinking about Access Control Lists bound to a web MVC architecture, I have a doubt on where the verification should be done.
Within Models? Controllers? FrontController?
Imagine a multiple module web application (like a website). I have a public site and a restricted admin zone.
- All public site pages are acessible by anyone.
- The restricted admin area is only accessed by an authenticated user.
- There are some access levels for the users, such as root, admin and regular user.
I gave this some thought during my lunch time today and some ideas came out.
First, I needed to ask myself this question:
Is access control part of business logic?
After some time, I figure out that the answer should be no for most cases. Normally, for the situation I described, the business logic of the public area is about the same of the restricted one. When is not exact the same, what I normally do is to create a superset of both business logic rules, that contains all logic needed. So, even if there are several modules in my application, I normally have only one model layer.
Doing the ACL within my controllers leads to code duplication, since I'd have to check it everytime I'd need. Also, I'd get a high coupling between controllers and the ACL component.
Thinking about putting it inside FrontController seems to be the right approach. In deed, my initial thought was to put the ACL handler as a dependency for the FrontController.
This approach would have controller methods (actions) as resources of the ACL, creating roles for my users.
Some pseudo-code (mixing 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
This way, my FrontController would have something like:
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(...); // ...
This implementation makes an ACL component a mandatory parameter for the FrontController constructor, so, now it's part of its state. This could be a problem when I don't need an ACL, but can be overpassed by creating a mock implementation of ACL component, like AllowFromAllAcl
, that would allow any users to do anything.
Any ideas? Corrections? Suggestions?
goto
than use exceptions. But enough of ad hominem. I'll see the video first.