Extended Acls

bugs:issue in bugtracker
Author: WikiAdmin


1. Introduction

Introduction of idea


object <- page
right <- privilege
role <- user
role <- group <- user


group -> organising users
role -> organising rights

2. database

new proposed ACL scheme

2 of 2 Files accessible from this page :

acl_scheme.mwb as workbench file 8.7 KiB  27.09.2010 19:03 
acl_scheme.png new proposed ACL scheme 18.4 KiB  27.09.2010 19:03 

Database Changes

  • Required database changes to wacko_XYZ_table

CREATE TABLE IF NOT EXISTS `wacko_acl` (
  `acl_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `object_id` int(10) unsigned DEFAULT NULL,
  `object_type_id` int(2) unsigned DEFAULT NULL,
  `object_right_id` int(2) unsigned DEFAULT NULL,
  PRIMARY KEY (`acl_id`),
  UNIQUE KEY `idx_acl` (`object_id`,`object_type_id`,`object_right_id`)
) ENGINE=MyISAM;
 
 
CREATE TABLE IF NOT EXISTS `wacko_acl_privilege` (
  `acl_privilege_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `acl_id` int(11) DEFAULT NULL,
  `grant_type_id` int(11) DEFAULT NULL,
  `grant_id` int(11) DEFAULT NULL,
  `deny` tinyint(1) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`acl_privilege_id`),
  UNIQUE KEY `idx_privilege` (`acl_id`,`grant_type_id`,`grant_id`)
) ENGINE=MyISAM;
 
CREATE TABLE IF NOT EXISTS `wacko_acl_right` (
  `acl_right_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `object_right` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`acl_right_id`)
) ENGINE=MyISAM;
 
INSERT INTO `wacko_acl_right` (`acl_right_id`, `object_right`) VALUES
(1, 'read'),
(2, 'comment'),
(3, 'write'),
(4, 'add'),
(5, 'remove');
 
CREATE TABLE IF NOT EXISTS `wacko_acl_type` (
  `acl_type_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `object_type` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`acl_type_id`)
) ENGINE=MyISAM;
 
INSERT INTO `wacko_acl_type` (`acl_type_id`, `object_type`) VALUES
(1, 'page');

$alter_acl_r5_4_0 = "ALTER TABLE {$pref}acl ADD acl_right_id INT(10) UNSIGNED NOT NULL AFTER page_id";
$alter_acl_r5_4_1 = "ALTER TABLE {$pref}acl DROP privilege";

$update_acl_r5_4_0 = "UPDATE {$pref}acl AS acl, (SELECT acl_right_id, acl_right FROM {$pref}acl_right) AS acl_right SET acl.acl_right_id = acl_right.acl_right_id WHERE acl.privilege = acl_right.acl_right";	

Configuration

  • $_var indicates something

Implementation Log


Issues:

  • static objects
    • add an table e.g. 'acl_static_object' and map your objects manually there
      • acl_type = 'static'

3. functions

<?php
 
    #function save_acl($object_id, $object_type, $object_right, $acl_privileges)
    function save_acl($page_id, $privilege, $list)
    {
        #if ($acl = $this->load_acl($object_id, $object_type, $object_right, 0, 0, 0))
        if ($this->load_acl($page_id, $privilege, 0, 0, 0))
        {
            $this->query(
                "UPDATE ".$this->config['table_prefix']."acl SET ".
                    "list = '".quote($this->dblink, trim(str_replace("\r", '', $list)))."' ".
                "WHERE page_id = '".quote($this->dblink, $page_id)."' ".
                    "AND privilege = '".quote($this->dblink, $privilege)."' ");
 
            /*foreach ($acl_privileges as $acl_privilege)
            {
                $this->query(
                    "UPDATE ".$this->config['table_prefix']."acl_privilege SET ".
                        "grant_type_id = '".quote($this->dblink, $list)."' ".
                        "grant_id = '".quote($this->dblink, $list)."' ".
                        "deny = '".quote($this->dblink, $list)."' ".
                    "WHERE acl_id = '".quote($this->dblink, $page_id)."' ".
                        "AND privilege = '".quote($this->dblink, $privilege)."' ");
            }*/
        }
        else
        {
            $this->query(
                "INSERT INTO ".$this->config['table_prefix']."acl SET ".
                    "list        = '".quote($this->dblink, trim(str_replace("\r", '', $list)))."', ".
                    "page_id    = '".quote($this->dblink, $page_id)."', ".
                    "privilege    = '".quote($this->dblink, $privilege)."'");
 
 
        }
    }
 
    #function get_cached_acl($object_id, $object_type, $object_right, $use_defaults)
    function get_cached_acl($page_id, $privilege, $use_defaults)
    {
        #if (isset( $this->acl_cache[$page_id.'#'.$object_type.'#'.$object_right.'#'.$use_defaults] ))
        if (isset( $this->acl_cache[$page_id.'#'.$privilege.'#'.$use_defaults] ))
        {
            #return $this->acl_cache[$page_id.'#'.$object_type.'#'.$object_right.'#'.$use_defaults];
            return $this->acl_cache[$page_id.'#'.$privilege.'#'.$use_defaults];
        }
        else
        {
            return '';
        }
    }
 
    // $acl array must reflect acls table row structure
    #function cache_acl($object_id, $object_type, $object_right, $use_defaults, $acl_privilege)
    function cache_acl($page_id, $privilege, $use_defaults, $acl)
    {
        #$this->acl_cache[$object_id.'#'.$object_type.'#'.$object_right.'#'.$use_defaults] = $acl_privilege;
        $this->acl_cache[$page_id.'#'.$privilege.'#'.$use_defaults] = $acl;
    }
 
    # function load_acl($object_id, $object_type, $object_right, $use_defaults = 1, $use_cache = 1, $use_parent = 1, $new_tag = '')
    function load_acl($page_id, $privilege, $use_defaults = 1, $use_cache = 1, $use_parent = 1, $new_tag = '')
    {
        if (!isset($acl))
        {
            $acl = '';
        }
 
        if ($use_cache && $use_parent)
        {
            #if ($cached_acl = $this->get_cached_acl($object_id, $object_type, $object_right, $use_defaults))
            if ($cached_acl = $this->get_cached_acl($page_id, $privilege, $use_defaults))
            {
                $acl = $cached_acl;
            }
        }
 
        if (!$acl)
        {
            #if ($cached_acl = $this->get_cached_acl($object_id, $object_type, $object_right, $use_defaults))
            if ($cached_acl = $this->get_cached_acl($page_id, $privilege, $use_defaults))
            {
                $acl = $cached_acl;
            }
 
            if (!$acl)
            {
                if (!$new_tag)
                {
                    $acl = $this->load_single(
                        "SELECT * ".
                        "FROM ".$this->config['table_prefix']."acl ".
                        "WHERE page_id = '".quote($this->dblink, $page_id)."' ".
                            "AND privilege = '".quote($this->dblink, $privilege)."' ".
                        "LIMIT 1");
/*
                    $acl = $this->load_single(
                        "SELECT a.* ".
                        "FROM ".$this->config['table_prefix']."acl a ".
                        "INNER JOIN ".$this->config['table_prefix']."acl_type t ON (t.object_type_id = a.object_type_id) ".
                        "INNER JOIN ".$this->config['table_prefix']."acl_right r ON (r.object_right_id = a.object_right_id) ".
                        "WHERE a.object_id = '".quote($this->dblink, $object_id)."' ".
                            "AND t.object_type = '".quote($this->dblink, $object_type)."' ".
                            "AND r.object_type = '".quote($this->dblink, $object_right)."' ".
                        "LIMIT 1");
 
                    $acl_privilege = $this->load_all(
                        "SELECT * ".
                        "FROM ".$this->config['table_prefix']."acl_privilege ".
                        "WHERE acl_id = '".quote($this->dblink, $acl['acl_id'])."' ");
                    */
                }
 
                // if still no acl, use config defaults
                if (!$acl && $use_defaults)
                {
                    // First look for parent ACL, so that clusters/subpages
                    // work correctly.
                    if (!empty($page_id))
                    {
                        $tag = strtolower($this->get_page_tag_by_id($page_id));
                    }
                    else
                    {
                        // new page which is to be created
                        $tag = strtolower($new_tag);
                    }
 
                    if ( strstr($tag, '/') )
                    {
                        $parent_tag = preg_replace('/^(.*)\\/([^\\/]+)$/', '$1', $tag);
 
                        // By letting it fetch defaults, it will automatically recurse
                        // up the tree of parent pages... fetching the ACL on the root
                        // page if necessary.
                        $parent_id    = $this->get_page_id($parent_tag);
                        #$acl        = $this->load_acl($parent_id, $object_type, $object_right, 1);
                        $acl        = $this->load_acl($parent_id, $privilege, 1);
                    }
 
                    // if still no acl, use config defaults
                    if (!$acl)
                    {
                        $acl = array(
                            #'object_id' => $object_id,
                            'page_id' => $page_id,
                            #'object_type' => $object_type,
                            #'object_right' => $object_right,
                            'privilege' => $privilege,
 
                            'list' => $this->config['default_'.$privilege.'_acl'],
                            'time' => date('YmdHis'),
                            'default' => 1
                        );
                    }
                }
 
                #$this->cache_acl($object_id, $object_type, $object_right, $use_defaults, $acl_privilege);
                $this->cache_acl($page_id, $privilege, $use_defaults, $acl);
            }
        }
 
        // probably we want only $acl_privilege and we can skip  $acl because we do not need it
        #return array($acl, $acl_privilege);
        #return $acl_privilege;
        return $acl;
    }
 
    function load_acl_new($object_id, $object_type, $object_right, $use_defaults = 1, $use_cache = 1, $use_parent = 1, $new_tag = '')
    {
        if (!isset($acl))
        {
            $acl = '';
        }
 
        if ($use_cache && $use_parent)
        {
            if ($cached_acl = $this->get_cached_acl($object_id, $object_type, $object_right, $use_defaults))
            {
                $acl = $cached_acl;
            }
        }
 
        if (!$acl)
        {
            if ($cached_acl = $this->get_cached_acl($object_id, $object_type, $object_right, $use_defaults))
            {
                $acl = $cached_acl;
            }
 
            if (!$acl)
            {
                if (!$new_tag)
                {
 
                    $acl = $this->load_single(
                        "SELECT a.* ".
                        "FROM ".$this->config['table_prefix']."acl a ".
                        "INNER JOIN ".$this->config['table_prefix']."acl_type t ON (t.object_type_id = a.object_type_id) ".
                        "INNER JOIN ".$this->config['table_prefix']."acl_right r ON (r.object_right_id = a.object_right_id) ".
                        "WHERE a.object_id = '".quote($this->dblink, $object_id)."' ".
                            "AND t.object_type = '".quote($this->dblink, $object_type)."' ".
                            "AND r.object_right = '".quote($this->dblink, $object_right)."' ".
                        "LIMIT 1");
 
                    $acl_privilege = $this->load_all(
                        "SELECT * ".
                        "FROM ".$this->config['table_prefix']."acl_privilege ".
                        "WHERE acl_id = '".quote($this->dblink, $acl['acl_id'])."' ");
 
                }
 
                // if still no acl, use config defaults
                if (!$acl && $use_defaults)
                {
                    // First look for parent ACL, so that clusters/subpages
                    // work correctly.
                    if (!empty($page_id))
                    {
                        $tag = strtolower($this->get_page_tag_by_id($page_id));
                    }
                    else
                    {
                        // new page which is to be created
                        $tag = strtolower($new_tag);
                    }
 
                    if ( strstr($tag, '/') )
                    {
                        $parent_tag = preg_replace('/^(.*)\\/([^\\/]+)$/', '$1', $tag);
 
                        // By letting it fetch defaults, it will automatically recurse
                        // up the tree of parent pages... fetching the ACL on the root
                        // page if necessary.
                        $parent_id    = $this->get_page_id($parent_tag);
                        $acl        = $this->load_acl($parent_id, $object_type, $object_right, 1);
 
                    }
 
                    // if still no acl, use config defaults
                    if (!$acl)
                    {
                        $acl = array(
                            'object_id' => $object_id,
                            'object_type' => $object_type,
                            'object_right' => $object_right,
 
                            'list' => $this->config['default_'.$object_type.'_'.$object_right.'_acl'],
                            'time' => date('YmdHis'),
                            'default' => 1
                        );
                    }
                }
 
                $this->cache_acl($object_id, $object_type, $object_right, $use_defaults, $acl_privilege);
            }
        }
 
        // probably we want only $acl_privilege and we can skip  $acl because we do not need it
        #return array($acl, $acl_privilege);
        return $acl_privilege;
 
    }
    // returns true if $user (defaults to the current user) has access to $privilege on $page_tag (defaults to the current page)
    #function has_access($object_type, $object_right, $object_id = '', $user = '', $use_parent = 1)
    function has_access($privilege, $page_id = '', $user = '', $use_parent = 1)
    {
        if ($user == '')
        {
            $user_name = strtolower($this->get_user_name());
        }
        else if ($user == GUEST)
        {
            $user_name = GUEST;
        }
        else
        {
            $user_name = $user;
        }
 
        if (!$page_id = trim($page_id))
        {
            $page_id = $this->page['page_id'];
        }
 
        // if still no page_id, use tag
        if (empty($page_id))
        {
            // new page which is to be created
            $new_tag = $this->tag;
        }
        else
        {
            $new_tag = '';
        }
 
        if ($privilege == 'write')
        {
            $use_parent = 0;
        }
 
        // load acl
        #$acl        = $this->load_acl($object_id, $object_type, $object_right, 1, 1, $use_parent, $new_tag);
        $acl        = $this->load_acl($page_id, $privilege, 1, 1, $use_parent, $new_tag);
        $this->_acl    = $acl;
 
        // if current user is owner or admin, return true. they can do anything!
        if ($user == '' && $user_name != GUEST)
        {
            if ($this->user_is_owner($page_id) || $this->is_admin())
            {
                return true;
            }
        }
 
        return $this->check_acl($user_name, $acl['list'], true);
    }
 
      // ...
 
 
 
    // REMOVALS
    # function remove_acls(object_id, $cluster = false)
    function remove_acls($tag, $cluster = false)
    {
        if (!$tag)
        {
            return false;
        }
 
        $this->query(
            "DELETE a.* ".
            "FROM ".$this->config['table_prefix']."acl a ".
                "LEFT JOIN ".$this->config['table_prefix']."page p ".
                    "ON (a.page_id = p.page_id) ".
            "WHERE p.tag ".($cluster === true ? "LIKE" : "=")." '".quote($this->dblink, $tag.($cluster === true ? "/%" : ""))."' ");
 
        /*if ($acls = $this->load_all(
        "SELECT a.acl_id ".
            "FROM ".$this->config['table_prefix']."acl a ".
                "LEFT JOIN ".$this->config['table_prefix']."page p ".
                "INNER JOIN ".$this->config['table_prefix']."acl_type t ON (a.object_type_id = t.object_type_id) ".
                    "ON (a.object_id = p.page_id) ".
            "WHERE p.tag ".($cluster === true ? "LIKE" : "=")." '".quote($this->dblink, $tag.($cluster === true ? "/%" : ""))."' ".
                "AND t.object_type = 'page'"))// later we will have also other objects than page
        {
            foreach ($acls as $acl)
            {
                // delete acl
                $this->query(
                "DELETE a.* ".
                "FROM ".$this->config['table_prefix']."acl a ".
                "WHERE a.acl_id = '".quote($this->dblink, $acl['acl_id'])."' ");
 
                // delete acl privilege
                $this->query(
                "DELETE a.* ".
                "FROM ".$this->config['table_prefix']."acl_privilege a ".
                "WHERE a.acl_id = '".quote($this->dblink, $acl['acl_id'])."' ");
            }
        }*/
 
        return true;
    }
?>

4. default values

5. GUI

- write -
[] group
[] user


-- upload --


[Delete] [Add]


set permissions


select a role
[......] [user/group] [search]
-> result [add]


set permissions for this role

(default / extra) allow deny
write
read
upload
create
comment
properties
permissions
approve
publish
...

6. Migration

<?php
 
########################################################
##            MIGRATE ACLs to new scheme              ##
########################################################
 
if ($this->is_admin())
{
    if (!isset($_POST['migrate_acls']))
    {
        echo "<h3>3. Migrates acls to new scheme:</h3>";
        echo $this->form_open();
        ?>
        <input type="submit" name="migrate_acls" value="<?php echo $this->_t('CategoriesSaveButton');?>" />
        <?php
        echo $this->form_close();
    }
    // migrate acls to new acl and acl_privilege table
    else if (isset($_POST['migrate_acls']))
    {
        // load old ACLs
        $_acls = $this->load_all(
            "SELECT page_id, privilege, list ".
            "FROM {$this->config['table_prefix']}acl_old ");
 
        $old_acl_count = count($_acls);
 
        foreach ($_acls as $_acl)
        {
            echo $_acl['privilege'].'<br />';
            // get object_right_id (e.g. 'write' -> 1, 'read' -> 2)
            $_object_right_id = $this->load_single(
                "SELECT acl_right_id ".
                "FROM {$this->config['table_prefix']}acl_right ".
                "WHERE object_right = '{$_acl['privilege']}'
                ");
            $object_right_id = $_object_right_id['acl_right_id'];
 
            // get object_type_id (e.g. 'page' -> 1) / there is only 'page' so far
            $_object_type_id = $this->load_single(
                "SELECT acl_type_id ".
                "FROM {$this->config['table_prefix']}acl_type ".
                "WHERE object_type = 'page'
                ");
            $object_type_id = $_object_type_id['acl_type_id'];
 
            // INSERT rights in 'acl' table
            $sql =    "INSERT INTO {$this->config['table_prefix']}acl
                    (object_id, object_type_id, object_right_id)
                    VALUES ('{$_acl['page_id']}', '{$object_type_id}', '{$object_right_id}')";
 
            $this->query($sql);
 
            // get new created $acl_id
            $acl_id = $this->load_single(
                "SELECT acl_id ".
                "FROM {$this->config['table_prefix']}acl ".
                "WHERE object_id = '{$_acl['page_id']}' ".
                    "AND object_type_id = '{$object_type_id}' ".
                    "AND object_right_id = '{$object_right_id}'
                ");
            $acl_id = $acl_id['acl_id'];
 
            // get user and group privileges
            $privileges    = explode("\n", $_acl['list']);
            $this->debug_print_r($privileges);
 
            foreach ($privileges as $privilege)
            {
                if (!empty($privilege))
                {
                    $grant_id        = '';
                    $grant_type_id    = '';
                    $deny            = '';
 
                    #$privilege = (string)$privilege;
                    // look for '!' prefix, if true set $deny to true and remove it
                    if ($privilege[0] == '!')
                    {
                        $deny = 1;
                        $privilege = substr($privilege, 1);
                    }
                    else
                    {
                        $deny = 0;
                    }
 
                    echo $privilege.'<br />';
                    // is group?
                    // 1. default groups
                    // 1.1 Everybody
                    if ($privilege == '*')
                    {
                        $_grant_id = $this->load_single(
                            "SELECT group_id ".
                            "FROM {$this->config['table_prefix']}group ".
                            "WHERE group_name = 'Everybody'
                            ");
                        $grant_id = $_grant_id['group_id'];
                        $grant_type_id = 1;
                    }
                    // 1.2 Registered
                    else if  ($privilege == '$')
                    {
                        $_grant_id = $this->load_single(
                            "SELECT group_id ".
                            "FROM {$this->config['table_prefix']}group ".
                            "WHERE group_name = 'Registered'
                            ");
                        $grant_id = $_grant_id['group_id'];
                        $grant_type_id = 1;
                    }
                    // 1.3 Admins
                    else if  ($privilege == 'Admins')
                    {
                        $_grant_id = $this->load_single(
                            "SELECT group_id ".
                            "FROM {$this->config['table_prefix']}group ".
                            "WHERE group_name = 'Admins'
                            ");
                        $grant_id = $_grant_id['group_id'];
                        $grant_type_id = 1;
                    }
                    else
                    {
                        // 2. non default groups
                        if (!isset($this->groups))
                        {
                            $_groups = $this->load_all(
                                "SELECT group_name ".
                                "FROM {$this->config['table_prefix']}group ");
 
                            foreach ($_groups as $_group)
                            {
                                $groups[] = $_group['group_name'];
                            }
                            $this->groups = $groups;
                        }
 
                        $this->debug_print_r($groups);
 
                        if (in_array($privilege, $this->groups))
                        {
                            $grant_id = $this->load_single(
                                "SELECT group_id ".
                                "FROM {$this->config['table_prefix']}group ".
                                "WHERE group_name = '{$privilege}'
                                ");
                            $grant_id        = $grant_id['group_id'];
                            $grant_type_id    = 1;
                        }
                        else
                        {
                            // 3. users
                            if (!isset($this->users))
                            {
                                $_users = $this->load_all(
                                "SELECT user_name ".
                                "FROM {$this->config['table_prefix']}user ");
 
                                foreach ($_users as $_user)
                                {
                                    $users[] = $_user['user_name'];
                                }
                                $this->users = $users;
                            }
 
                            $this->debug_print_r($users);
 
                            if (in_array($privilege, $this->users))
                            {
                                $_grant_id = $this->load_single(
                                    "SELECT user_id ".
                                    "FROM {$this->config['table_prefix']}user ".
                                    "WHERE user_name = '{$privilege}'
                                    ");
                                $grant_id        = $_grant_id['user_id'];
                                $grant_type_id    = 2;
                            }
                        }
                    }
 
                    // INSERT privileges in 'acl_privilege' table
                    $sql =    "INSERT INTO {$this->config['table_prefix']}acl_privilege
                            (acl_id, grant_type_id, grant_id, deny)
                            VALUES ('{$acl_id}', '{$grant_type_id}', '{$grant_id}', '{$deny}')";
 
                    $this->query($sql);
                }
            }
        }
 
        echo '<br />'.$old_acl_count.' acl and '.$privilege_count.' privilege settings inserted.';
    }
}
 
?>

7. Related Links

8. Future Ideas

9. Feedback

  • Please provide feedback

Files

acl_scheme.mwb as workbench file 8.7 KiB  27.09.2010 19:03 
acl_scheme.png new proposed ACL scheme 18.4 KiB  27.09.2010 19:03