Welcome Guest, Not a member yet? Register   Sign In
Khaos :: KhACL
#1

[eluser]Neophyte[/eluser]
Updated - 31/03/2008 - Download - Khaos::KhACL - 0.1-alpha5

Introduction
If you are looking for a fast, easy to use and quick to install user auth library then this library is not for you. If how ever you have an existing user auth library or are looking to develop your own and need extremely fine grained control over your users actions then this library may be what you are looking for.

KhACL takes a very traditional approach to ACL akin to the phpgacl script where you have a core ACL class which manages ARO, ACO and AXO objects. Obviously discussing the background of this approach and how it all works is way beyond the scope of a forum post so im just going to give a basic run down of the main methods available for use within the library. - edit - as sophistry pointed out below the The phpgACL manual gives a reasonable overview.

Quick Reference
Code:
/*
* Helper
* -------
* bool kh_acl_check  ( string $aro, string $aco [, string $axo ] )
*
* Library
* -------
* KhACL
* bool allow  ( string $aro, string $aco [, string $axo ] )
* bool deny  ( string $aro, string $aco [, string $axo ] )
* bool check  ( string $aro, string $aco [, string $axo ] )
*
* KhACL->ARO
* bool create ( string $aro [, string $aro_parent [, int $link ]] )
* bool delete ( string $aro )
*
* KhACL->ACO
* bool create ( string $aco [, string $aco_parent [, int $link ]] )
* bool delete ( string $aco )
*
* KhACL->AXO
* bool create ( string $axo )
* bool delete ( string $axo )
*/

// Helper Examples
$allowed = kh_acl_check('neophyte', 'news', 'comment');

// Library Examples - KhACL
$this->khacl->allow('editors', 'news', 'publish');
$this->khacl->deny('anonymous', 'news', 'comment');
$allowed = $this->khacl->check('neophyte', 'news', 'comment');

// Library Examples - KhACL->ARO
$this->khacl->aro->create('neophyte', 'editors');
$this->khacl->aro->delete('neophyte');

// Library Examples - KhACL->ACO
$this->khacl->aco->create('news', 'modules');
$this->khacl->aco->delete('news');

// Examples - KhACL->AXO
$this->khacl->axo->create('publish');
$this->khacl->axo->delete('publish');
General Usage - Typically you should only add the helper to autoload for general page requests manually loading the library as and when you need to modify the ACL, this is to make checks as fast as possible.

AXO objects - As with phpgacl are completely optional however they are stored as a simple list so you cannot build up tree heirarchies of AXO objects like you can with the ARO and ACO objects (through use of a modified preorder tree traversal table schema). Personally i have never needed tree based heirarchies for the AXO objects even when i have needed extremely fine levels of control however if this did end up being a requested feature i could code it in.

Links - The link argument you see when creating ARO or ACO objects as you can see is completely optional and an idea i stole from cakephp which i think may come in handy for some people (myself included) where you can specify an ID to what this represents in your own database (such as a user_id) so if you wish to perform your own SQL queries you can join the relevent records.

Optional Dependencies
>= KhCache-0.3
If detected this will allow khacl to cache check results greatly increasing the speed of checks.
#2

[eluser]Craig A Rodway[/eluser]
Hi Neophyte. This looks very interesting! Can't wait to see a bit more of the flesh you mentioned and how it might be used in a small 'real-world' example application.

Thanks for sharing this library Smile
#3

[eluser]sophistry[/eluser]
I found the PDF manual for the phpgacl library to be helpful in understanding what Neophyte has contributed here.

The phpgACL manual

Nice work.
#4

[eluser]wemago[/eluser]
good work Smile
you saved me a lot of work
#5

[eluser]wiredesignz[/eluser]
Great work Neophyte, You're making some really nice contributions here.
#6

[eluser]bardelot[/eluser]
I found some bugs in the library when I tried to use it:

Code:
function map($aro_tree, $aco_tree)
    {                
        $map        = array(); // Holds final access map
        $interested = array(); // Holds records for which we are interested in

        // Build the array of aro/aco pairs were interested in      
        foreach ($aro_tree as $aro)
            foreach ($aco_tree as $aco)
                $interested[] = '('.$this->_Tables['access'].'.aro_id = '.$aro->id.' AND '.$this->_Tables['access'].'.aco_id = '.$aco->id.')';

// add - BEGIN
        if (count($interested) == 0)
            return false;
// add - END
[...]
            // If there are any AXO build the extensions array
            if ($rs_axo->num_rows(0) > 0)
            {
// change next line
                foreach ($rs_axo->result() as $axo_map)
Code:
function _set($aro, $aco, $axo = null, $allow = true)
[...]
        /*
         * If needed create/modify the access -> action link in the access_actions table
         */
        
        if ($axo !== null)
        {
            if (($rs = $this->_CI->db->query('SELECT id, allow FROM '.$this->_Tables['access_actions'].' WHERE access_id = ? AND axo_id = ? LIMIT 1', array($access_id, $axo_id))) !== false)
            {
                if ($rs->num_rows() === 0) // create link
                {
// change next line
                    if (!$this->_CI->db->query('INSERT INTO '.$this->_Tables['access_actions'].' (access_id, axo_id, allow) VALUES (?, ?, ?)', array($access_id, $axo_id, $allow)))
Code:
/**
     * Create ARO
     *
     * @param string $aro
     * @param string $parent
     * @param int    $link
     *
     * @return bool
     * @access public
     */
    function create($aro, $parent = null, $link = null)
[...]
                // Update all records past the left point by 2 to make room for the new ARO
                $this->_CI->db->query('UPDATE '.$this->_Tables['aros'].' SET rgt = rgt + 2 WHERE rgt > '.$left);
                $this->_CI->db->query('UPDATE '.$this->_Tables['aros'].' SET lft = lft + 2 WHERE lft > '.$left);
                
                // Insert the record
// change next line
                $this->_CI->db->query('INSERT INTO '.$this->_Tables['aros'].' (lft, rgt, name, link) VALUES ('.($left + 1).', '.($left + 2).', '.$this->_CI->db->escape($aro).', '.$link.')');
Code:
/**
     * Create ACO
     *
     * @param string $aco
     * @param string $parent
     * @param int    $link
     *
     * @return bool
     * @access public
     */
    function create($aco, $parent = null, $link = null)
[...]
                // Update all records past the left point by 2 to make room for the new ARO
                $this->_CI->db->query('UPDATE '.$this->_Tables['acos'].' SET rgt = rgt + 2 WHERE rgt > '.$left);
                $this->_CI->db->query('UPDATE '.$this->_Tables['acos'].' SET lft = lft + 2 WHERE lft > '.$left);
                
                // Insert the record
// change next line
                $this->_CI->db->query('INSERT INTO '.$this->_Tables['acos'].' (lft, rgt, name, link) VALUES ('.($left + 1).', '.($left + 2).', '.$this->_CI->db->escape($aco).', '.$link.')');
Code:
In your example you might want to change:
// Examples - KhACL->AXO
$this->khacl->aro->create('publish');
$this->khacl->aro->delete('publish');

into

// Examples - KhACL->AXO
$this->khacl->axo->create('publish');
$this->khacl->axo->delete('publish');
#7

[eluser]Neophyte[/eluser]
ChangeLog - KhACL - 0.1-alpha2

Thanks to bardelot for picking up on these ones

Fixed - Updated khacl->map() to check interested array before continuing
Fixed - Bug in khacl->_set() which was using CI->query instead of CI->db->query
Fixed - Bug in khacl->aro->create() final insert was using right instead of rgt
Fixed - Bug in khacl->aco->create() final insert was using right instead of rgt
Fixed - Bug in khacl->check() extra paramater $axo is not needed when calling khacl->query()
Fixed - Bug in khacl->_set() which resulted in incorrect permissions being set
#8

[eluser]bardelot[/eluser]
Code:
/**
     * Check Access
     *
     * @param mixed $aro
     * @param mixed $aco
     * @param mixed $axo
     *
     * @return bool
     */
    function check($aro, $aco, $axo = null)
    {
        $result = $this->query($aro, $aco, $axo);
[...]

In the last line $this->query there is an $axo argument but the query function misses exactly that argument. So it won't look at the $axo and altough you might have access to a specific axo it will return "access = N". So I added the $axo argument to the query function and changed the code where the result['access'] is assigned. I'm not sure if I haven't broken anything, so you might want to check first.

Code:
/**
     * Query ARO Access
     *
     * Determines if the ARO has access to the ACO along with
     * any extra AXOs.
     *
     * Sample Response:
     *
     *   Array
     *   (
     *       [access] => Y
     *       [extensions] => Array
     *           (
     *               [publish] => N
     *               [create] => Y
     *               [delete] => N
     *           )
     *   )
     *
     * @param mixed $aro
     * @param mixed $aco
     *
     * @return array
     */
    function query($aro, $aco, $axo)
    {
[...]
                    if (($access['aro_id'] == $aro->id) && ($access['aco_id'] == $aco->id))
                    {
                        if ($axo != NULL && array_key_exists($axo, $access['extensions']))
                            $result['access'] = $access['extensions'][$axo];
                        else    
                            $result['access'] = $access['allow'];
                            
                        $result['extensions'] = array_merge($result['extensions'], $access['extensions']);
                    }
[...]

Anyway, thanks for the library I really like it, you saved me lots of work.
#9

[eluser]Neophyte[/eluser]
hey bardelot, adding $axo as an arg when calling khacl->query() from khacl->check() was a bug in the code.

khacl->query() just retrieves a full access array (this means it doesnt need to know the axo as it retrieves all of the AXO permissions assigned to the aro->aco relationship). its then upto khacl->check() to determine if the user should be allowed access or not as such your code will result in some odd behaviour under certain circumstances.

Just to clarify this ive restructured the check method to
Code:
function check($aro, $aco, $axo = null)
{
    $result = $this->query($aro, $aco);
        
    // ARO lacks any access - DENY
    if ($result['access'] == 'N')
        return false;
    
    if ($axo === null)
    {
        /*
         * No AXO specified and we know the ARO has access to the ACO
         * from the above check. - ALLOW
         */
            
           return true;            
    }
    else
    {
        /*
         * AXO specified and we know the ARO has access to the ACO so now we just
         * have to make sure the user also has access to the AXO.
         */
            
        if ((isset($result['extensions'][$axo])) && ($result['extensions'][$axo] == 'Y'))
            return true;  
        else // AXO is set to deny - DENY
            return false;        
    }
        
    // DENY    
    return false;    
}
hopefuly this makes it a bit clearer
#10

[eluser]bardelot[/eluser]
Well the problem is I get the following result array.
Code:
Array
(
    [access] => N
    [extensions] => Array
        (
            [view] => Y
            [delete] => N
        )

)

view is set to Y which is correct but access is N and so access gets denied. And I couldn't yet find out why it is set to N.
This is how it is set up:

Code:
// There's an admin usergroup with the user admin_1 in it.
$this->khacl->aro->create('admin');
$this->khacl->aro->create('admin_1', 'admin');

// They manage the posts. Post 1 is the first post in that group
$this->khacl->aco->create('posts');
$this->khacl->aco->create('post_1', 'posts');

// It's possible to view or delete those posts
$this->khacl->axo->create('view');
$this->khacl->axo->create('delete');

// The admin group is allowed to view or delete them
$this->khacl->allow('admin', 'posts', 'view');
$this->khacl->allow('admin', 'posts', 'delete');

// admin_1 is not allowed to delete post_1
$this->khacl->deny('admin_1', 'post_1', 'delete');

Code:
// admin_1 is in the admin group and they are allowed to view the posts but it gets denied.
$this->khacl->check('admin_1', 'post_1', 'view')

Edit: Everything is fine as long as i do not use the deny function.




Theme © iAndrew 2016 - Forum software by © MyBB