
We have in support an internal project that uses a very inconvenient access control system.
The so-called
ACL .
It is inconvenient because in order to provide any access, the administrator needs to perform a lot of actions for each individual resource that requires access.
Well, after user complaints began to reach a critical level, I was tasked with simplifying or redoing the system.
I looked at the existing code, did not feel the desire in myself for rework, and decided to find a ready-made solution. I will say straight away that I am a big opponent of “bicycles”, before I write something, I always try to find a ready and suitable solution. So it was this time, but rather unsuccessfully. There are several solutions, for example, in the
PEAR collection, but I did not choose the right one. A lot of code is outdated (we are using PHP 5.3 version), so it was decided to write the ACL yourself.
This is what I want to tell. I will try to give examples of the code as little as possible; I will only tell you the idea and principles of work.
But anyone who wants can
get the whole library code
upd-2011 (
utf8 ), see and, if desired, use it.
')
To begin with, I decided to clarify for myself the general principle of its work, for which I imagined such an analogy with the real world:
CAT. (checkpoint)
Sits babuska, in front of her magazine. The magazine has a list of rooms in which visitors pass through the checkpoint.
In fact, there are a lot of premises at the facility, but in the magazine babuska has fewer of them. There are only those where you need to control access.
For each room that is in the magazine attached a list of visitors who are allowed to enter there,
where in addition to specific people are listed and groups. (For example, access to office 303 is allowed for all employees of the technical department).
Babuska is guided by this magazine, and documents on the hands of visitors provides access control to the object.
This is the client part of the system.
Now the administrative part:
Periodically, the security chief comes to the checkpoint. It makes changes to the log.
The requirements that I set for the future system:
- Resource rights can be granted to: user, users, group, groups.
- From point 1 it follows that users can be in groups.
- The system (at least in the first version) will be the simplest, which means that it either gives a certain user the right to a resource or not.
- Resource rights must be inherited.
That is, if only the user Pupkin owns a resource, say example.com/someitem , then automatically he and only he (but see clause 5) has access to the resources:
But only if rights are not redefined for some subordinate resource. If the rights are redefined, then they are inherited in a new way further along the tree.
- The system must have mandatory (system) groups:
- not authorized users
- authorized users
- superuser
In this case, the superuser has the right to any resources, including the resources of Pupkin from the previous paragraph of these requirements.
- Well, in accordance with paragraph 4, resources can be subordinate and parental.
By resource in general, here I understand any way on the site.
A parent resource can be obtained if the given element is deleted from the last element in the path to the slash (/).
For example, the same example.com/someitem/item2 - erase / item2, - we get the parent:
- example.com/someitem .
- From the preceding paragraph, it follows that there must be one common ancestor for all the resources of the site — this is the root resource.
In this case, it is example.com , but we will store it as '/', and it will also be mandatory for the system, that is, the system one.
- System elements (user groups and root resource) cannot be removed from the system.
- The system should be as simple as possible for use in client code.
- For starters in my opinion it is enough.
The requirements are determined, and the database structure becomes clear:
Tables: (minimum required fields are specified)
Users+ --------------------------
+ id | name
+ --------------------------
Groups+ --------------------------
+ id | name
+ --------------------------
Resources+ --------------------------
+ id | name
+ --------------------------
Users in groups+ -------------------------------------------------
+ id | group id | User ID
+ -------------------------------------------------
User rights to resources+ -------------------------------------------------
+ id | id_resource | User ID
+ -------------------------------------------------
Rights of groups to resources+ -------------------------------------------------
+ id | id_resource | group_id
+ -------------------------------------------------
The database schema for MySQL is archived with the library in the docs directory.Here, you immediately need to stipulate that I wrote the authorization system separately, first I used
PEAR :: Auth , and it was for it that I did the ACL, but in the existing version of PEAR :: Auth there is also a lot of outdated (for PHP 5.3) code, therefore the library authorization decided to write your own. She was generally the simplest. In this article I will not describe. Let me just say that I tried to abstract from the authorization system, any system can be used, the only condition is that it must implement the IAuth interface (it is in the archive of the described library)
In addition, the described database schema was needed temporarily, only for organizing development testing, since it was immediately decided to write a system independent of the storage containers. That is, real data with users, groups, resources, rights can be stored in different places, in different databases, someone can just be in files, someone even directly somewhere in the code (well, you never know, resource so small, it is known that a couple of users will work, maybe the database is not needed). The point is that the system knows nothing, and it does not need to know where and how data is stored.
It follows that the system should immediately be abstracted from data storage, and work with data through
adapters .
Therefore, it is necessary to describe the adapter interface, and implement one adapter for now, say for MySQL.
The interface for the adapter is something like this:
and so on and so forth.
It should be clear what actions will be needed from the adapters.
An example of the implementation of a single method in the adapter for MySQL
public function getGroups($orderBy=null, $getUsers=false, $getResources=false) { if ( empty($orderBy) ) { $orderBy = $this->tables['group']['namecol']; } $query = "SELECT * FROM {$this->tables['group']['name']} ORDER BY {$orderBy}"; $res = $this->_connection->query($query); if ( !$res ) { return false; } $groups = $res->fetchAll(PDO::FETCH_ASSOC); if ( !$getUsers && !$getResources ) { return $groups; } foreach ($groups as & $_g) { if ( $getUsers ) { $_g['users'] = $this->getUsersForGroup($_g[ $this->tables['group']['idcol'] ]); } if ( $getResources ) { $_g['resources'] = $this->getResourcesForGroup($_g[ $this->tables['group']['idcol'] ]); } } return $groups; }
The system itself has two classes, the heirs of a common ancestor:
1. Customer
2. Administrator
Client's task:
Check the rights to the requested resource for the current user, and either give him access or send to the "Access denied" page.
By the way, the outputs from the system, if there is no access, are defined in separate functions, and only the names of these functions are transferred to the system during configuration.
There is a need only in two functions:
send a login to the page () and
access is denied () .
By the way, as a rule, in my own projects I usually make one page for errors 403 and 404, - “Page not found”.
It seems to me that this is a more reliable option, because when displaying “Access is denied” (403 error), we already provide the potential attacker with extra information that “... such page exists, only you, Vasya, are not supposed to go there .. . "
The task of the administrative part:
System management
Add groups, users to groups, resources, rights, delete all of the above, and so on.
Using the system in the client code.
The system in the client code is used very simply, for example:
index.php (application entry point)
<?php $acl = new AuthAcl_Client($options); $acl->start();
That is, the code will either not reach the client application, if the user who requested the resource does not have rights (for example, page 404 is displayed), or the application continues.
In the constructor of the
$ acl object, we pass the necessary parameters, this is usually an array, which in the simplest case may not exist at all, since the system itself has default values ​​for most of its elements.
But usually in this array of parameters there are such elements as:
- The names of the fields for entering the login and password in the form of authorization (the system passes them on to the authorization system).
- The name of the adapter for accessing data (for example, 'MySQL')
- Information to connect to the database.
- The names of the tables storing users, groups, resources, rights.
- Is the password encrypted, if so, in what way (for example, true, md5)
- And everything else that can be configured and configured.
In the start () method, approximately the following work occurs:
- The rights to the requested resource are determined.
The resource may belong to the group “Unauthorized users”, that is, be accessible to everyone, then the system stops working on it and control is transferred to the client application.
- Otherwise, the user must be authorized.
- An authorization system object is created and control is transferred to it. At this stage, the code may go to the login page, and the work will stop.
- If the authorization object reported that the user is authorized, then the user's rights to this resource are checked.
In the absence of those, the code calls the “no access ()” function, which sends the user far away at his own discretion. - Otherwise, the method ends its work and transfers control to the client application.
I always try to cover my code with tests to the maximum, so it was this time. Were written
unit tests (are in the archive), after which all conceived functionality was implemented.
Here, in principle, and all that I wanted to tell.
The code of the entire library is attached to the article.
I would be very happy if someone like this experience. I am also happy to hear criticism of the weak points of the proposed solution.
AuthAcl library code (in zip archive)UPD: 07/31/2011Version in utf-8:
AuthAcl-utf8 library code