Finding Inherited Permissions in CDbAuthManager
February 13, 2012
So I was working on a custom user interface for Yii's CDbAuthManager Role-Based Access Control (RBAC) system today when I ran into an age old problem - Multilevel Inheritance. Basically, an Authorization Item can inherit other Authorization Items, which may be inheriting different Authorization Items (I think you see where I'm going with this).
The way that I've seen many developers handle this problem is to write two recursive functions; the first one builds a huge multilevel array that mirrors the inheritance structure, and the second function flattens the array. This always seemed strange to me... Why recurse through the entire array twice?
Here's some code I wrote today that recursively searches through the inheritance schema of Yii's RBAC structure, and creates a single-level array from it. In reality, I create an array of single-level arrays, because there are three main types of Authorization Items - Operations, Tasks, and Roles.
public function getPermissionsArray($role, $hierarchy = '') {
$retArray = array('operations' => array(), 'roles' => array(), 'tasks' => array());
if (isset($role)) {
$children = Yii::app()->authManager->getItemChildren($role);
foreach ($children as $child) {
$type = '';
if (!$child->type == AuthItem::TYPE_OPERATION) {
if ($child->type == AuthItem::TYPE_ROLE) {
$type = 'roles';
} else {
$type = 'tasks';
}
$retArray = array_merge_recursive($retArray, $this->getPermissionsArray(
$child->name, $hierarchy . '|' . $child->name
. ':type=' . $child->type
. ':description=' . $child->description
));
} else {
$type = 'operations';
}
if (substr($hierarchy, 0, 1) == '|') {
$hierarchy = substr($hierarchy, 1);
}
$retArray[$type][$child->name] = $hierarchy;
}
}
return $retArray;
}
So let's break this up a bit. After we initialize our return value $retArray, we check that a $role has been passed to our function (we could go further and validate it, but for this example we know exactly what will be sent).
Now we use one of CDbAuthManager's handy functions and get all of the immediate children for the role.
$children = Yii::app()->authManager->getItemChildren($role);
Now that we have all of the immediate children, we can loop through them and check their children, etc. The big idea here is that, as we encounter an Authorization Item, we want to push it onto the appropriate array - roles, tasks, and operations. We ALSO want to build a string that shows the hierarchy or inheritance we passed through to reach that specific Authorization Item.
If we reach a task or role, it may have children, so we need to recurse through its children and see.
if (!$child->type == AuthItem::TYPE_OPERATION) {
if ($child->type == AuthItem::TYPE_ROLE) {
$type = 'roles';
} else {
$type = 'tasks';
}
$retArray = array_merge_recursive($retArray, $this->getPermissionsArray(
$child->name, $hierarchy . '|' . $child->name
. ':type=' . $child->type
. ':description=' . $child->description
));
} else {
$type = 'operations';
}
You'll notice that we're keeping track of each inherited items type and description. This isn't necessary but it may be helpful for the User Interface (UI). You'll also see the we're keeping track of a variable $type - this is simply so we know which array to push the item to on our way back up the recursion loop.
The MOST IMPORTANT thing here to pay attention to is that we are using array_merge_recursive. We do this because it is possible that a specific Authorization Item is inherited more than once. If this happens, we want to end up creating an array with all of the inheritance tokens in it. If we just used array_merge, we would end up overwriting the $hierarchy token instead of appending a new token to it.
Lastly, we use some laziness/trickery to remove the leading delimiter, and then push our value onto the appropriate array.
if (substr($hierarchy, 0, 1) == '|') {
$hierarchy = substr($hierarchy, 1);
}
$retArray[$type][$child->name] = $hierarchy;
Once all is said and done, you'll end up with an array that looks something like this (for 'admin' - reference):
array
'operations' =>
array
'createPost' => string 'author:type=2:description=' (length=26)
'deletePost' => string '' (length=0)
'readPost' =>
array
0 => string 'author:type=2:description=|reader:type=2:description=' (length=53)
1 => string 'editor:type=2:description=|reader:type=2:description=' (length=53)
'updatePost' =>
array
0 => string 'author:type=2:description=|updateOwnPost:type=1:description=update a post by author himself' (length=91)
1 => string 'editor:type=2:description=' (length=26)
'roles' =>
array
'author' => string '' (length=0)
'editor' => string '' (length=0)
'reader' =>
array
0 => string 'author:type=2:description=' (length=26)
1 => string 'editor:type=2:description=' (length=26)
'tasks' =>
array
'updateOwnPost' => string 'author:type=2:description=' (length=26)
===
Now we have a neat array of arrays that show all of the permissions a specific $role has access to, as well as an easily manipulated $hierachy for each item.
Enjoy.
Tags:
yii, rbac, recursion
Permalink |
Comments (0) |
Last updated on February 13, 2012