phpmysqlzend-frameworkzend-acl

PHP MySQL Zend-ACL - Graphically display ACL:


I have one MySQL DB table like the following, the resources table:

+----+-----------+------------+
| id | name      | type       |
+----+-----------+------------+
| 1  | guest     | user       |
| 2  | member    | user       |
| 3  | moderator | user       |
| 4  | owner     | user       |
| 5  | admin     | user       |
| 6  | index     | controller |
+----+-----------+------------+

Onto the next table, the rules table:

+----+---------+------+-------------+----------------------+
| id | user_id | rule | resource_id | extras               |
+----+---------+------+-------------+----------------------+
| 1  | 2       | 3    | 1           | null                 |
| 2  | 3       | 3    | 2           | null                 |
| 3  | 4       | 3    | 3           | null                 |
| 4  | 5       | 3    | 4           | null                 |
| 5  | 6       | 1    | 1           | index,login,register |
| 6  | 6       | 2    | 2           | login,register       |
| 7  | 6       | 1    | 2           | logout               |
+----+---------+------+-------------+----------------------+

OK, sorry for the length, but I am trying to give a full picture of what I am trying to do. So the way it works, a role (aka user) can be granted (rule: 1) access to a controller, a role can inherit (rule: 3) access from another role or a role and be denied (rule: 2) access to a controller. (A user is a resource and a controller is a resource)

Access to actions are granted / denied using the extras column.

This all works, its not a problem with setting up the ACL within zend.


What I am now trying to do is show the relationships; to do that I need to find the lowest level a role is granted access to a controller stopping if it has explicitly been removed. I plan on listing the roles. When I click a role, I want it to show all the controllers that role has access to. Then clicking on a controller shows the actions the role is allowed to do.

So in the example above, a guest is allowed to view the index action of the index controller along with the login action. A member inherits the same access, but is then denied access to the login action and register action. A moderator inherits the rules of a member.

So if I were to select the role moderator. I want to see the controller index listed. If I click on the controller, it should show the allowed actions as being action: index. (which was originally granted to the guest, but hasn't since been dissallowed)

Is there any examples to doing this. I am obviously working with the Zend MVC (PHP) and MySQL. Even just a persudo code example would be a helpful starting point - this is one of the last parts of the jigsaw I am putting together.

P.S. Obviously I have the ACL object - is it going to be easier to interigate that or is it better to do it my self via PHP/MySQL?

The aim will be, show what a role can access which will then allow me to add or edit a role, controller and action in a GUI style (that is somewhat the easy bit) - currently I am updating the DB manually as I have been building the site.


Solution

  • Well after I did abit of searching, and couldn't find an answer, I had a bit more of a think about this and here is the solution I came up with (just incase its useful for someone else):

    Psuedo first:

    1. Show a page, listing all of the roles (user levels) from the ACL $acl->getRoles() as a link.
    2. Click a link, reload the page passing the role as a parameter.
      • Now grab all the controllers from ACL $acl->getResources() checking that the resource insn't a role (the array returned by getResources will also contain the roles).
      • loop through each controller, get all entries from the rules table where the controller id is in the resource_id field and explode the extras (comma seperated actions)
      • Next, loop through each action, calling isAllowed (I have the role, controller, and action). IF at least one "allowed" is found, I colour the controller green (allowed access to at least one action in the controller), otherwise its red (no access to anything in that controller) each list item being clickable to reload the page
    3. Now when a controller is clicked, I reload the page, now, when running through the list of actions, calling isAllowed I create a list of the actions for the selected controller, colouring the action green or red based on the result of isAllowed

    The answer its self is almost as long winded as the question, but it works for me, giving a very clear picture of what each role can do. Here it is if its going to help anyone:

    Now for the code:

    AdminController:

    public function aclAction()
    {
        $this->view->content_title = "Access Rules:";
    
        // Get the ACL - its stored in the session:
        $usersNs = new Zend_Session_Namespace("ZEND_SITE");
        $acl = $usersNs->acl;
    
        // List all Roles in the ACL:
        $roles = $acl->getRoles();
        // Pass the roles to the view:
        $this->view->roles = $roles;
    
        // Check if a role has been clicked on:
        $role = this->_getParam('role');
        if(!is_null($role))
        {
            // Pass the role to the view:
            $this->view->role = $role;
    
            // Get all the resources (controllers) from the ACL, don't add roles:
            $controllers = array();
            foreach ($acl->getResources() as $res)
            {
                if (!in_array($res, $roles))
                {
                    $controllers[] = $res;
                }
            }
    
            // Create a Rules Model:
            $rules = new Model_ACLrules();
    
            // Store controllers + access:
            $all_controllers = array();
    
            // Check if the controller has been passed:
            $cont = $this->_getParam('cont');
    
            // Loop through each controller:
            foreach ($controllers as $controller)
            {
                // Get all actions for the controller:
                // THIS IS THE PART I DON'T LIKE - BUT I SEE NO WAY TO GET
                // THE RULES FROM THE ACL - THERE LOOKS TO BE A METHOD
                // BUT IT IS A PROTECTED METHOD - SO I AM GETTING THE ACTIONS 
                // FROM THE DB, BUT THIS MEANS TWO SQL QUERIES - ONE TO FIND
                // THE RESOURCE FROM THE DB TO GET ITS ID THEN ONE TO FIND
                // ALL THE EXTRAS FOR IT:
                $all_rules = $rules->findAllActions($controller);
    
                // Store if the role is allowed access somewhere in the controller:
                $allowed = false;
    
                // Store selected controller actions:
                $cont_actions = array();
    
                // Loop through all returned row of actions for the resource:
                foreach ($all_rules as $rule)
                {
                    // Split the extras field:
                    $extras = explode(",", $rule->extras); 
    
                    // Check if the role has access to any of the actions:
                    foreach ($extras as $act)
                    {
                        // Store matching selected controller:
                        $match = ($cont==$controller)?true:false;
    
                        // Store the action if we are looking at a resource:
                        if ($match)$temp = array("action"=>$act,"allowed"=>false);
    
                        // Check if the role is allowed:
                        if ($acl->isAllowed($role,$controller,$act))
                        {
                            // Change the controllers allowed to ture as at least one item is allowed:
                            $allowed = true;
    
                            // Change the matched controllers action to true:
                            if ($match)$temp = array("action"=>$act,"allowed"=>true);
                        }
    
                        // Check if the action has already been added if we are looking at a resource:
                        if ($match)
                        {
                            $add = true;
                            // This is done because there could be several rows of extras, for example
                            // login is allowed for guest, then on another row login is denied for member,
                            // this means the login action will be found twice for the resource,
                            // no point in showing login action twice:
                            foreach ($cont_actions as $a)
                            {
                                // Action already in the array, don't add it again:
                                if ($a['action'] == $act) $add = false;
                            }
                            if($add) $cont_actions[] = $temp;
                        }
                    }
                }
    
                // Pass a list of controllers to the view:
                $all_controllers[] = array("controller" => $controller, "allowed" => $allowed);
    
                // Check if we had a controller:
                if(!is_null($cont))
                {
                    // Pass the selected controller to the view:
                    $this->view->controller = $cont;
    
                    // Check if this controller in the loop is the controller selected:
                    if ($cont == $controller)
                    {
                        // Add the controller + actions to the all rules:
                        $this->view->actions = $cont_actions;
                    }
                }
            }
    
            // Pass the full controller list to the view:
            $this->view->controllers = $all_controllers;
        }   
    }
    

    Next the view: acl.phtml:

    <h2>Roles:</h2>
    <ul>
        <?php 
            foreach ($this->roles as $name)
            {
                echo '<li><a href="'.$this->baseUrl('admin/acl') . '/role/' . $name . '">' . ucfirst($name) . '</a><br/></li>';
            }
        ?>
    </ul>
    
    <?php if (isset($this->controllers)): ?>
        <h2><?php echo ucfirst($this->role); ?>'s Controllers:</h2>
        <ul>
            <?php
                $array = $this->controllers;
                sort($array);
                foreach ($array as $controller)
                {
                    $font = ($controller['allowed'])?'green':'red';
                    echo '<li><a href="'.$this->baseUrl('admin/acl') . '/role/' . $this->role . '/cont/'.$controller['controller'].'" style="color:'.$font.';">'.ucfirst($controller['controller']).'</a></li>';    
                }   
            ?>
        </ul>
    
        <?php if (isset($this->controller)): ?>
            <h2><?php echo ucfirst($this->role)."'s, ".ucfirst($this->controller);?> Actions:</h2>
            <ul>
                <?php 
                    $array = $this->actions;
                    sort($array);
                    foreach ($array as $action)
                    {
                        $font = ($action['allowed'])?'green':'red';
                        echo '<li><font style="color:'.$font.';">'.ucfirst($action['action']).'</font></li>';
                    }
                ?>
            </ul>
        <?php endif;?>
    <?php endif; ?>
    

    Example:

    I hope this is helpful to someone, I will leave it open for now incase anyone can suggest a better solution - or maybe improve on the answer?