Acl Permission Class

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

Post Reply
nwhiting
Forum Newbie
Posts: 18
Joined: Sun May 03, 2009 8:30 pm

Acl Permission Class

Post by nwhiting »

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 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
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.

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;
    }
 
 
}
 
There is a small bug with the tracing that keeps adding the same role name..

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 
 
Post Reply