I have constructed a very in-depth and quite complex ACL system.
Question I am wondering is will it take a performance hit on checking permissions and some improvments, the structure for the ACL is quite simple, there are roles and there are resources. A user can have an unlimited number of roles assigned.
Simple permission is based on a scale of 1-4 1 being guest 4 being root, all roles can be children of another role.
Resources work the same way as roles.
The resource permissions can be finely tuned down to allowing/denying a user access, the hierarchy for resource permission check with each one overwriting the previous permission.
Here is a traceback of a check
- Resource Parents are Check (if exist)
- Resource Level is checked against roles and all parent roles level
- Resource specific role access is checked
- Resource specific role deny is checked
- Resource specific user allow is checked
- Resource specific user deny is checked
Specific allow/deny access is based soley on array comparison but the parent looping is qhat I am wondering will cause a performance hit.
I would post the rest of the code for the entire system but it 21 files and to much code for a single topic
Everything is straightforward and shouldn't be a problem understanding what it does
Here is the code for the class
Code: Select all
class Unus_Acl extends Unus_Object
{
/*
* Builds the registry
*/
public function __construct()
{
$this->setData('roles', new Unus_Acl_Role())
->setData('resource', new Unus_Acl_Resource());
}
/*
* Overload to the data object
*/
public function __get($name)
{
return $this->getData($name);
}
/*
* Overload to the data object
*/
public function __set($name, $value)
{
$this->setData($name, $value);
}
/*
* Checks if a user is allowed to access a resource
*
* Resource permission are checked in the following order each check overwrites the previous
*
* 1. Parents (if exists)
* 2. Level
* 3. Role Specific Permissions (if set)
* 4. User Specific Permissions (if set)
*
*
* @param string resource Resource name to check
*/
public function isAllowed($resource)
{
$traceRoute = array();
$return = false;
// get the resource level
$level = $this->resource->getLevel($resource);
if (!$this->resource->hasParent($resource)) {
$traceRoute[] = 'Resource has no parents; inheritance checks will be skipped';
} else {
// Check Parent Information
$parent = $this->resource->getResource($this->resource->getParent($resource));
$traceRoute[] = 'Resource is child of '.$parent->getIdentifier().' checking inheritance permissions';
$traceRoute[] = 'Begin Parent Checks';
if ($this->isAllowed($parent->getIdentifier())) {
$return = true;
$traceRoute[] = 'Parent resource allowes permission';
} else {
$traceRoute[] = 'Parent resource denies permission';
}
// add parent trace to trace
$traceRoute = array_merge($traceRoute, $this->getData('traceroute_'.$parent->getIdentifier()));
$traceRoute[] = 'End Parent Checks';
}
// allowed roles
$rolesAllow = $this->resource->getRolesAllow($resource);
// Allow Trace
if (count($rolesAllow) == 0) {
$traceRoute[] = 'Resource has no role defined allow access checks will be skipped';
}
// denied roles
$rolesDeny = $this->resource->getRolesDeny($resource);
// Deny Trace
if (null == $rolesAllow) {
$traceRoute[] = 'Resource has no role defined deny access checks will be skipped';
}
// allowed users
$usersAllow = $this->resource->getUsersAllow($resource);
// Allow Trace
if (null == $usersAllow) {
$traceRoute[] = 'Resource has no user defined allow access checks will be skipped';
}
// denied users
$usersDeny = $this->resource->getUsersDeny($resource);
// Deny Trace
if (null == $usersDeny) {
$traceRoute[] = 'Resource has no user defined deny access checks will be skipped';
}
// get a user's roles
$roles = Unus::registry('user')->getRoles();
if (is_array($roles)) {
foreach ($roles as $k => $v) {
$return = $this->_checkRolePermission($v, $level, $rolesAllow, $rolesDeny);
// add parent trace to trace
$traceRoute = array_merge($traceRoute, $this->getData('tmp_trace_'.$v->getIdentifier()));
$this->unsetData('tmp_trace_'.$v->getIdentifier());
}
} else {
$return = $this->_checkPermission($roles);
$traceRoute = array_merge($traceRoute, $this->getData('tmp_trace_'.$v->getIdentifier()));
$this->unsetData('tmp_trace_'.$v->getIdentifier());
}
// User Based Permissions
if (null != $usersAllow) {
if (in_array(Unus::registry('user')->id, $usersAllow)) {
$traceRoute[] = 'User is allowed based on per-user permissions';
$return = true;
}
}
if (null != $usersDeny) {
if (in_array(Unus::registry('user')->id, $usersDeny)) {
$traceRoute[] = 'User is denied based on per-user permissions';
$return = false;
}
}
// Set the traceroute
$this->setTraceRoute($resource, $traceRoute);
return $return;
}
private function _checkRolePermission($role, $level, $roleAllow, $roleDeny)
{
$traceRoute = array();
// check if this role has parents build array
if ($role->hasParents()) {
$traceRoute[] = 'Role '.$role->getIdentifier().' is a child role checking parents';
if(is_array($role->getParents())) {
foreach($role->getParents() as $pk => $pv) {
$pObject = $this->roles->getRole($pv);
$traceRoute[] = 'Checking parent role '.$pObject->getIdentifier().' permissions';
if ($pObject->getLevel() >= $level) {
$return = true;
$traceRoute[] = 'Parent Role '.$pObject->getIdentifier().' allows access';
} else {
$traceRoute[] = 'Parent Role '.$pObject->getIdentifier().' denies access';
}
}
} else {
$pObject = $this->roles->getRole($role->getParents());
$traceRoute[] = 'Checking parent role '.$pObject->getIdentifier().' permissions';
if ($pObject->getLevel() >= $level) {
$return = true;
$traceRoute[] = 'Parent Role '.$pObject->getIdentifier().' allows access';
} else {
$traceRoute[] = 'Parent Role '.$pObject->getIdentifier().' denies access';
}
}
}
// First we check the level
if ($role->getLevel() >= $level) {
$return = true;
$traceRoute[] = 'Role '.$role->getIdentifier().' is allowed based on level';
} else {
$traceRoute[] = 'Role '.$role->getIdentifier().' is denied based on level';
}
// Then we check the roles
// these overwrite the level permission
// roles not in this will be set to deny
// root roles are not affected by this
if (null != $rolesAllow) {
if (in_array($role->getId(), $rolesAllow)) {
$return = true;
$traceRoute[] = 'Role '.$role->getIdentifier().' is allowed based on specific roles';
} elseif ($role->getId() != 4) {
$return = false;
$traceRoute[] = 'Role '.$role->getIdentifier().' is denied as based that it is not a Root role and not in allowed list';
}
}
// DENY
// This specifically denies roles only
// roles not in this array are not considered being allowed
if (null != $rolesDeny) {
if (in_array($role->getId(), $rolesDeny) && $role->getId() != 4) {
$return = true;
$traceRoute[] = 'Role '.$role->getIdentifier().' is denied based on specific roles';
}
}
$this->setData('tmp_trace_'.$role->getIdentifier(), $traceRoute);
return $return;
}
/*
* Builds a user's role permission traceroute
*
* @param array traceroute Traced array of how the permission is parsed
*
* @return this
*/
public function setTraceRoute($resource, $traceRoute)
{
$this->setData('traceroute_'.$resource, $traceRoute);
return $this;
}
/*
* Gets a resource traceroute check
*
* @param array traceroute Traced array of how the permission is parsed
*
* @return
*/
public function getTraceRoute($resource)
{
$trace = $this->getData('traceroute_'.$resource);
$str = null;
foreach ($trace as $k => $v) {
$str .= '{'.$k.'} '.$v.' <br />';
}
return $str;
}
}
Code: Select all
{0} Resource is child of test_3 checking inheritance permissions
{1} Begin Parent Checks
{2} Parent resource allowes permission
{3} Resource is child of test_2 checking inheritance permissions
{4} Begin Parent Checks
{5} Parent resource allowes permission
{6} Resource is child of test checking inheritance permissions
{7} Begin Parent Checks
{8} Parent resource allowes permission
{9} Resource is child of main checking inheritance permissions
{10} Begin Parent Checks
{11} Parent resource denies permission
{12} Resource has no parents; inheritance checks will be skipped
{13} Resource has no role defined allow access checks will be skipped
{14} Resource has no role defined deny access checks will be skipped
{15} Resource has no user defined allow access checks will be skipped
{16} Resource has no user defined deny access checks will be skipped
{17} Role Guest is denied based on level
{18} Role Ioana is a child role checking parents
{19} Checking parent role Ioana permissions
{20} Parent Role Ioana denies access
{21} Role Ioana is denied based on level
{22} End Parent Checks
{23} Resource has no role defined allow access checks will be skipped
{24} Resource has no role defined deny access checks will be skipped
{25} Resource has no user defined allow access checks will be skipped
{26} Resource has no user defined deny access checks will be skipped
{27} Role Guest is allowed based on level
{28} Role Ioana is a child role checking parents
{29} Checking parent role Ioana permissions
{30} Parent Role Ioana allows access
{31} Role Ioana is allowed based on level
{32} End Parent Checks
{33} Resource has no role defined allow access checks will be skipped
{34} Resource has no role defined deny access checks will be skipped
{35} Resource has no user defined allow access checks will be skipped
{36} Resource has no user defined deny access checks will be skipped
{37} Role Guest is allowed based on level
{38} Role Ioana is a child role checking parents
{39} Checking parent role Ioana permissions
{40} Parent Role Ioana allows access
{41} Role Ioana is allowed based on level
{42} End Parent Checks
{43} Resource has no role defined allow access checks will be skipped
{44} Resource has no role defined deny access checks will be skipped
{45} Resource has no user defined allow access checks will be skipped
{46} Resource has no user defined deny access checks will be skipped
{47} Role Guest is allowed based on level
{48} Role Ioana is a child role checking parents
{49} Checking parent role Ioana permissions
{50} Parent Role Ioana allows access
{51} Role Ioana is allowed based on level
{52} End Parent Checks
{53} Resource has no role defined allow access checks will be skipped
{54} Resource has no role defined deny access checks will be skipped
{55} Resource has no user defined allow access checks will be skipped
{56} Resource has no user defined deny access checks will be skipped
{57} Role Guest is allowed based on level
{58} Role Ioana is a child role checking parents
{59} Checking parent role Ioana permissions
{60} Parent Role Ioana allows access
{61} Role Ioana is allowed based on level