View source for Extended Acls

bugs:issue in bugtracker
Author: ((user:WikiAdmin WikiAdmin))
{{toc numerate=1}}

===Introduction===
Introduction of idea

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

group -> organising users
role -> organising rights

===database===
file:acl_scheme.png
{{files}}

Database Changes
  * Required database changes to wacko_XYZ_table

%%(hl sql)
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'

===functions===
%%(hl php)
<?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;
	}
?>
%%
===default values===
===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 | | ||
||... | | ||
|#

===Migration===
%%(hl php)
<?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.';
	}
}

?>
%%

===Related Links===

===Future Ideas===

===Feedback===
  * Please provide feedback