ionik Posted May 3, 2009 Share Posted May 3, 2009 Hello, 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, 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 I have it enabled to allow tracing of the resource permission checking it comes out quite large, while the resource has 4 parents and the role has 2 parents. Specific allow/deny access is based soley on array comparison but the parent looping is qhat I am wondering will cause a performance hit. Here is the code for the class 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; } } There is a small bug with the tracing that keeps adding the same role name.. {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 Quote Link to comment Share on other sites More sharing options...
ionik Posted May 4, 2009 Author Share Posted May 4, 2009 Edit To Above... I would post the rest of the system roles, resource, user but it is to many files (20) and ties directly back into the core registry, but the methods and design are straight forward and shouldn't be to hard to figure out what they do Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.