📜 ⬆️ ⬇️

Access control system on cakePHP.

As you know there are various access control systems.
Some of them are simple, implemented only on the basis of sessions, others are complex, such as ACL. Each of them has its pros and cons. Simple systems are easy to understand and to use, but with an increase in the number of privileges and the need for their dynamic change, the corresponding difficulties will arise, while the ACL is rather cumbersome, not so flexible and difficult to understand. Having used both systems for a long time, I came to the conclusion that you need to develop your access control system, which would have the following possibilities:


Reading existing topics about access sharing systems, I often ran across comments: “Why make a garden, if there is an ACL”. Immediately answer what I do not like ACL.



And so we start with the theory.
Table structure

')
Description of the tables

Users - everything is simple, this table is used to store users, the key fields for us are id, lgn, pwd. In this table, the first entry with id = 0 will go to the user by default, so to speak visitor.

Groups are groups (for example User, Admin, Manager)

Statuses - each group consists of statuses. Roughly speaking each user is included into this or that group with a certain status. A user can be in several groups with different statuses, but within one group a user can have only one status. For example, the following groups have the statuses User - visitor, active, new; Admin - active, superadmin, deleted; Manager - active, blocked; The visitor (id = 0) will belong to the User group and be in the visitor status. Each group has a default status - defstats_id - other statuses can inherit access rights from the default status, and they can have their own rights to a specific object.

Users_statuses - a table for linking user and status

Objects - stores all security objects (for example, a security button can be a registration button that shows only visitors)

objects_categories - the very first operating experience of this system showed that the objects of safety must somehow be organized. For this, an additional table was introduced, which serves only to organize (when displayed) security objects. For example, among the security objects we will have such categories - default - all new objects fall into this category (and then the administrator himself transfers the object to another category), buttons - all objects associated with buttons, links - objects associated with links and so on, as far as your imagination and task allows you.

Access is the key table of our system. It links security objects, statuses and privileges. This table has five main columns for defining the type of access requested:
c - access to create (for example, check access to create a new blog entry)
u - change access (for example, check access to edit a blog entry)
r - read access (for example, checking read access to a blog entry)
d - delete access (for example, checking access to delete a post from a blog)
l - access to display the list (for example, check access to display all entries in the blog or display all users)

These fields can take the following values:
0 - access denied
1 - access is allowed only to the owner
2 - access is allowed to all

This table describes the default statuses of each group, all other statuses inherit the access rights of the default status of their group. If the status should have its own access rights (well, for example, the stunt block has all default rights of the active status, except that it cannot add new entries to the blog), then a line with the corresponding status_id is added to the access table.

how the system works

Let us analyze the logic of the system. The access check will be performed by the getAccess function (object, access, owners);
function parameters
object - the name of the object for which access is requested.
access - access type (may be c, u, r, d, l);
owners is an optional parameter that can be either a value of type int - user id, or a list of user id. At the moment we will leave this optional parameter without attention - later it will become clear how it is used by the system.

It is worth making a reservation that we always know the id of the current user — we store it in the session, for a non-pledged user, this is a user with id = 0 and in the user group with the visitor status.


And so how does the system work.
1. we check if there is an object with the same name in the objects table; if not, create and remember its id

2. from the session, we take the current user id and look at the statuses of this user (in implementation, the current user id is stored in the session)

3. according to the type of access required and depending on the system setting (weak, strong), we take max or min values ​​for the required access field (r, c, d, u, l) for the respective statuses. If the status was not in the access table, then we take the corresponding group def_status.

4. Possible results - 0 access denied, 2 - access is allowed to all, 1 - access is allowed only to the owners. If access is allowed only to owners then:
check whether the last parameter of the function checkAccess is owners or not, if it is empty, then access is allowed \ denied (optional). If it is not empty and of type int, then we check it with the user id from the session (current user). If they are equal, then access is allowed, otherwise denied. If the owners parameter is an array, then we check if the current user id (from session) is in this array (in_array), if there is an entry, then access is allowed otherwise denied.

That's actually the whole algorithm of work.

Usage example

Suppose we have a blog entry created by user creator_id. We want to display the "edit post" button for some users and hide for others.
It's very easy to write if (checkAccess ('BlogPost', 'u', $ post ['creator_id'])) {echo BUTTON}
For example, the User is in two groups and the statuses have the following privileges on Update
User \ active -1;
Admin \ active -2;
according to the system settings, we take max (or min) and return the result

Implementation

Implemented system on cakePHP with query caching as a component. It is enough to cache the four main labels: access, statuses, groups, objects, so that not a single query when checking access. (these tablets are small, so they won’t take up much space and we can allow them to be completely cached). Also, several controller / model / view were used - purely for visualization of the system control.

We load the plates into the cache (cake allows you to use two types of cache: file and memcached, so without a cache, we certainly will not stay, with any servers).
For convenience, we need to cache it so that the arrays look like this:
Access
permissions [status_id] [object_id] [access_type] = permission;
example:
permissions [status_id] [object_id] [s] = 1;
permissions [status_id] [object_id] [d] = 1;
permissions [status_id] [object_id] [r] = 1;
permissions [status_id] [object_id] [u] = 1;
permissions [status_id] [object_id] [l] = 1;

Statuses
statuses [status_id] = group_id ie each status is stored in which group it is

Groups
groups [group_id] = def_status_id ie each group keeps its default status
Objects
object [object_name] = object_id

The implementation of the main functions:
function getAccess($objName = "" ,$accessType = "r" ,$authorID=NULL) {

$objectID = 0;
$isAccess = true ;

/*Getting User ID*/
if ($ this ->Session->check( 'loggedUser' )) {
$userSession = $ this ->Session->read( 'loggedUser' );
$userID = $userSession[ 'id' ];
} else {
$userID = VISITOR_USER;
}

/*Check access
* 0 - deny;
* 1 - allow only for author;
* 2 - allow for ALL;
*/
$isAccess = $ this ->__returnAccess($objName,$accessType);

if ($isAccess == 2){
$isAccess = true ;
} elseif($isAccess == 1) {
/*Check author id*/
if (is_array($authorID) && in_array($userID,$authorID)){
$isAccess = true ;
} elseif($userID==$authorID){
$isAccess = true ;
} else {
$isAccess = false ;
}
/*EOF Checking author id*/
} else {
$isAccess = false ;
}

return $isAccess;
}

, 1,0 2
function __returnAccess($objName = "" ,$accessType = "r" ){

if (!$ this ->model){
$ this ->__initModel();
}

/*Getting User ID*/
if ($ this ->Session->check( 'loggedUser' )) {
$userSession = $ this ->Session->read( 'loggedUser' );
$userID = $userSession[ 'id' ];
} else {
$userID = VISITOR_USER;
}

/*Getting user statuses*/
if ($ this ->Session->check( 'loggedUserStatuses' )) {
$userStatuses = $ this ->Session->read( 'loggedUserStatuses' );
} else {
$userStatuses = $ this ->model->query( "SELECT user_id, status_id FROM " .$ this ->model->tablePrefix. "users_statuses AS users_statuses WHERE user_id=" .$userID);
$ this ->Session->write( 'loggedUserStatuses' ,$userStatuses);
}


$objectID = $ this ->getObjIdByName($objName);

if (!$objectID) {
/*Create new object*/
$objectID = $ this ->__createNewObject($objName);
}
//Permissions
$permissions = Cache::read( 'permissions' );
if (empty($permissions)) {
$ this ->loadobjToCache();
$permissions = Cache::read( 'permissions' );
}
//Groups
$groups = Cache::read( 'groups' );
if (empty($groups)) {
$ this ->loadobjToCache();
$groups = Cache::read( 'groups' );
}
//Statuses
$statuses = Cache::read( 'statuses' );
if (empty($statuses)) {
$ this ->loadobjToCache();
$statuses = Cache::read( 'statuses' );
}

$isAccess = 0;

foreach ($userStatuses as $userStat) {
if (isset($permissions[$userStat[ 'users_statuses' ][ 'status_id' ]][$objectID] [$accessType])) {

if (intval($permissions[$userStat[ 'users_statuses' ][ 'status_id' ]][$objectID][$accessType])>$isAccess) {
$isAccess = intval($permissions[$userStat[ 'users_statuses' ][ 'status_id' ]][$objectID][$accessType]);
}

} else {

/*Getting group ID*/
$def_status_id = $groups[$statuses[$userStat[ 'users_statuses' ][ 'status_id' ]]];

if (!isset($def_status_id)) {
$isAccess = 0;
} else {
if (intval($permissions[$def_status_id][$objectID][$accessType])>$isAccess) {
$isAccess = intval($permissions[$def_status_id][$objectID][$accessType]);
}
}
}

} /*EOF foreach*/

return $isAccess;

}

* This source code was highlighted with Source Code Highlighter .


That's all, if anyone needs the component itself, I can provide it as well. Access control visualization is shown below.

Vertically there are groups, statuses and types of access. default statuses are highlighted in red. Horizontally objects go, under them immediately is a list of categories for transfer. checkbox - to indicate that the access of this object for this status will be the same as the default status of this group (set for them - // -). access to me with the help of AJAX, ie without rebooting (at the same time we update the cache)

Source: https://habr.com/ru/post/51243/


All Articles