I’m going to talk more about ACLs than Auth. Auth is simple, it’s the ACL that will trip you up. Since both concepts are coupled together when you’re making a login system, I feel it’s appropriate to at least touch on Auth. What I want to cover is the ways we can create the ACL object to suit needs based on the scale of the project. I’m going to assume that readers have a passing familiarity with using the Auth and Acl objects and may have even implemented them into projects.
The reason I say Auth is simple is because Zend Framework makes it simple with their Zend_Auth class. You can pick an auth style, implement and then go from there. For the purpose of this discussion, I’ll be talking using Database authentication. So after one has set up a login page that uses Zend_Auth (there are already a lot of articles that cover this so i’ll move on), the major bear you have to tackle is using the Zend_Auth_Result to determine proper access. Sounds simple, that’s what Zend_Acl is for.
So how do we create our Acl? Again, ZF’s reference guide gives us some handy-dandy examples of using their object but how you utilize the object depends on your scale. Other articles give you a way to bind the two together, viagra otc usa, usually via a controller plugin of some sort. The thing that can be tricky is how you want the Acl and Auth to interact with each other within that plugin. I utilize a controller plugin that fires off an Auth/Acl check in the dispatchLoopStartup() method. If no Zend_Auth_Result object exists, it assigns a guest role and if one does exist, it searches the Zend_Auth_Storage for a role value (assigning guest if one doesn’t exist somehow) and checks that value against the Acl that was created in my Bootstrap. My biggest conundrum has always been translating Requests into Resources. I’ll talk more on that later.
I tend to put my scaling into one of the following categories: small,decent or ZOMG. Small is something like a personal site or a proof-of-concept/self-tutorial project where my users will probably be a 1-5 roles and 1-5 resources at best. Decent is most collaborative niche projects with 50-100 resources. ZOMG is where the number of resources I’m dealing with is >100, unknown or potentially large. I define my ACLs in my Bootstrap in one of three ways:
This one is easy to implement since if you follow the ZF reference or the numerous guides/posts you find when you google for auth/acl systems, you’ll be able to hardcode your ACL. I find this ideal for small projects where the number of resources is relatively low and the growth of adding additional resources is non-existent. This is the simplest way to do things.
This method makes use of the Zend_Navigation object. Brandon Savage wrote up a nice guide to integrating ACLs into you r Navigation. I usually instantiate my Navigation objects in the Bootstrap from a XML file. The only tricky thing I found with this is finding the appropriate Navigation_Page (that tells us our resource/permissions) from the Request object. I tend to utilize this method when the growth of resources occurs at a slow pace.
This particular method is what spurred me to write about Auth and Acl. I see this solution as ideal for a high amount of resources or where the growth of resources to have fast spurts. This solution can allow for ease of maintenance and is scalable. Even though it is a scalable solution, I kind of see it as overkill for smaller, low-growth projects.
Multiple project ACLs
Since I’ve began working with Zend Framework, I have used it to create both internal apps in addition to running my company’s main site. To date, I have a total of eleven internal applications, some of which require Auth/Acl and some of which do not. Those that do not require them will soon be requiring them due to future features we wish to implement for them. At first I was working on a drag and drop solution for setting up an Auth/ACL system for any given project. I was going to work it a stand-alone module where all the developer has to do is call the Module Bootstrap within the Application Bootstrap. The drag and drop solution was going to rely on the project’s Navigation object for determining the ACL of a requested page. Then it occurred to me that even if I could make this ‘simple’ drag and drop solution, we’d be faced with reapplying it to ten more projects and have subsequent setup processes for each additional project. This seemed highly inefficient and a pain in my ass.
My next realization was that there was multiple overlap on the users accessing various internal applications and way too many vhosts to make for each project. It seemed like it would be better to do the ultimate refactor and create a centralized control panel for people to authenticate and access internal applications.
I was still hung up on wanting to reuse my Navigation based code when I figured out that maintaining the navigation XML would be another nightmare. I had attempted a database solution for one project and it became a pain to maintain the ACL when I introduced Routing to the project. It was a bitter and hateful experience to get it working and since both the Auth/ACL and Routing parts of the project were done before I had embraced unit testing, sorting through my crazy monkey patching logic would only intensify my rage. I also recalled that the biggest issue with that proto-solution was translating the Request into a Resource.
In the schema shown on the right, the Resource.Name corresponded to the Request’s Controller and Privilege.Name corresponded to Request’s Action. When the ACL was created in the Bootstrap, it would create Module.Name_Resource.Name as the resource then associate privileges to that resource. Routing and Controller forwarding made this whole thing hard to mentally track. I ended up having a lot of monkey patch entries to make it work in the end.
So here I was, just generally damned when I thought up a way around the issues I had with the previous schema. I would need to have the Resource.Name independent of the Request URI in order to avoid routing issues. By associating the Resource to a URI, I don’t have to account for if Routing exists or not. The URI will always be available and the core ACL system will be independent of any Routes that do/don’t/might exist. Privileges bound to action seemed like a lot of overkill since all privileges come down to CRUD (though I always like the BREAD acronym better). In addition, privileges would be off in their own world and reused when necessary (the last schema had too many redundant privileges associated to different resources).
As I stated earlier, the goal is a unified login for internal apps (there are currently eleven and this number will grow). The user table consists of a username, password and control panel role. This role allows for future interfaces to be built that can manage the control panel such as adding a project or adding users. If you’re wondering why I have the password column as able to be null, it’s to support some legacy items. Next major table is the project table which lets me have a project_user table that is also has a role associated with it. I made role a separate table since a lot of the projects overlap on roles such as guest and admin. Resources can be specifically named or generalized as necessary. All my links (URIs) can share a resource name if necessary. Finally there is the privilege table along with a resource_privilege table that allows me to maintain those relationships. While I have a separate control_panel_role, I decided that the control panel resource/privilege system doesn’t need to be separate. I only wanted to make sure that the control panel system was accessible even if there was no projects available for selection. I’m planning on making the past projects into modules under this system and all future internal apps accessible/controlled from here. I lack any co-workers that I’m able to debate the merits of this idea with and given that this will be a major refactor of my work thus far, I am asking for feedback on the idea. Am I missing any other viable ways of defining the ACL that would be easier/better? I’m not a DBA so I’m wondering if my tables are actually optimized well enough. Any other issues or thoughts I haven’t considered?