I just started a project with a friend this month, and its going to be some type of CMS just like WordPress, but the difference is that you’re going to build informative websites, like a band website, e-commerce (just showing products and in the future allowing online budget calculation), web showroom etc. I’m going to post this how-we-made-it because I just couldn’t find any implementation that really does what I wanted.
So we started splitting the application in 2 modules:
visible for everyone around the globe that has access to your domain.
where you’re going to edit the website features/contents.
We designed the whole database structure, created the models, the application basics, the controllers and a little register/authentication system.
Users can now sign up/in/out, but at this point we didn’t have the access control system, that denies access to pages that the user doesn’t have access to. I used to use Zend_Acl for building systems like this, and querying on every action for the user access to the resource. But let’s agree that Zend_Acl isn’t that friendly for registering the users and resources.
I started thinking on a “generic” way for doing this without hard coding the whole ACL structure in the bootstrap and I ended up with this:
First I wanted to have a friendly way to deny/allow resources without touching the current application code, and my first tought was to write this into the application.ini, but that would be confusing when building some “complex” rules. So I discarded writing it into the application.ini.
My second thought was to use XML for building this rules, and I started working with Zend_Config_Xml for loading it, and the final structure of the XML was just HORRIBLY BAD because of the way Zend_Config_Xml reads the keys/values.
But I kept with the idea of using XML for doing this: I searched the Zend Framework documentation for a XML reader class, and I found nothing, which is expected since PHP already provides some XML readers. Now I had the XML reader which provides me all I need: simplexml_load_file
This time I started thinking on the structure of the XML using this basic rules:
- I don’t need to register every controller/action present on the application, just the module
- If there’s a module “user” and a module “admin”, I must be able to allow access on the whole module without the need to register every controller/action.
- If I want to allow access to an action “X” for the user type “Y” inside a module/controller that denies access to this user, I can.
- If I say module “X” allows “Y” to access, and I say a specific controller inside “X” must allow access to another type of user, the module rule will be overridden.
- I want the XML to support user type hierarchy like: admin has access to everything of the user + x, the user has access to everything of guest + y, guest has access only to public things.
- All the resources are by default denied on the system. (default behavior)
And I ended up with this:
<?xml version="1.0" encoding="UTF-8"?> <permissions> <roles> <role name="guest" /> <role name="user" inherits="guest" /> <role name="admin" inherits="user" /> </roles> <resources> <module name="default" allow="guest"> <controller name="account" allow="user"> <action name="sign-in" allow="guest" /> <action name="sign-out" deny="guest" /> <action name="sign-up" allow="guest" /> </controller> </module> </resources> </permissions>
The “permissions/roles” defines all the user roles of the system.
The “permissions/resources” defines the access the roles (user types) has all the modules/controllers/actions.
Splitting the example:
... <roles> <role name="guest" /> <role name="user" inherits="guest" /> <role name="admin" inherits="user" /> </roles> ...
We defined 3 user types (roles):
- guest (default for every user that is not signed in)
- user (has all privileges of guest + its own)
- admin (has all privileges of user + its own)
... <resources> <module name="site" allow="guest"> ... </module> </resources> ...
The module “default” allows all the user types (remember the role hierarchy) to have access to its resources.
This includes the index controller, and any other controller we have in the default module scope.
... <controller name="account" allow="user"> <action name="sign-in" allow="guest" /> <action name="sign-out" deny="guest" /> <action name="sign-up" allow="guest" /> </controller> ...
But since we have an account controller, which is where signed users manages its account, it’s accessible only for user type “user”, so we needed to define another rules inside of the default module denying access to the “guest” users. We defined that the controller “account” must allow access only to the role “user”, this creates another problem: we had 2 actions inside the account controller that must be available for guest users:
- sign-in action, which provides the guests a form to fill up the user/password.
- sign-up action, which provides the guests a form to create an account of type.
So users can now access “/default/account/sign-in” and “/default/account/sign-up” without getting a forbidden page error.
Continues in the part 2.