# HG changeset patch
# User Dan
# Date 1210568386 14400
# Node ID e87390b1f9b0d1c4f89eaa8da9ea8871717f686b
# Parent 7c7f08825cade770aeb4d2c57baa6242344bfbc0
Revamped some ACL code and added effective permissions calculation code into session manager
diff -r 7c7f08825cad -r e87390b1f9b0 includes/constants.php
--- a/includes/constants.php Sun May 11 21:05:27 2008 -0400
+++ b/includes/constants.php Mon May 12 00:59:46 2008 -0400
@@ -29,6 +29,17 @@
define('ACL_TYPE_USER', 2);
define('ACL_TYPE_PRESET', 3);
+// ACL inheritance debugging info
+define('ACL_INHERIT_GLOBAL_EVERYONE', 9);
+define('ACL_INHERIT_GLOBAL_GROUP', 8);
+define('ACL_INHERIT_GLOBAL_USER', 7);
+define('ACL_INHERIT_PG_EVERYONE', 6);
+define('ACL_INHERIT_PG_GROUP', 5);
+define('ACL_INHERIT_PG_USER', 4);
+define('ACL_INHERIT_LOCAL_EVERYONE', 3);
+define('ACL_INHERIT_LOCAL_GROUP', 2);
+define('ACL_INHERIT_LOCAL_USER', 1);
+
// ACL color scale minimal shade for red and green ends
// The lower, the more saturated the color on the scale.
// Purely cosmetic. 0x0 - 0xFF, 0xFF will basically disable the scale
diff -r 7c7f08825cad -r e87390b1f9b0 includes/sessions.php
--- a/includes/sessions.php Sun May 11 21:05:27 2008 -0400
+++ b/includes/sessions.php Mon May 12 00:59:46 2008 -0400
@@ -171,6 +171,13 @@
var $sw_timed_out = false;
/**
+ * Token appended to some important forms to prevent CSRF.
+ * @var string
+ */
+
+ var $csrf_token = false;
+
+ /**
* Switch to track if we're started or not.
* @access private
* @var bool
@@ -497,6 +504,8 @@
$this->reg_time = $userdata['reg_time'];
}
$this->auth_level = USER_LEVEL_MEMBER;
+ // generate an anti-CSRF token
+ $this->csrf_token = sha1($this->username . $this->sid . $this->user_id);
if(!isset($template->named_theme_list[$this->theme]))
{
if($this->compat || !is_object($template))
@@ -1072,7 +1081,7 @@
}
/**
- * Attempts to log in using the old table structure and algorithm.
+ * Attempts to log in using the old table structure and algorithm. This is for upgrades from old 1.0.x releases.
* @param string $username
* @param string $password This should be an MD5 hash
* @return string 'success' if successful, or error message on failure
@@ -2743,7 +2752,160 @@
$object =& $objcache[$namespace][$page_id];
return $object;
+ }
+
+ /**
+ * Fetch the permissions that apply to an arbitrary user for the page specified. The object you get will have the get_permissions method
+ * and several other abilities.
+ * @param int|string $user_id_or_name; user ID *or* username of the user
+ * @param string $page_id; if null, will be default effective permissions.
+ * @param string $namespace; if null, will be default effective permissions.
+ * @return object
+ */
+
+ function fetch_page_acl_user($user_id_or_name, $page_id, $namespace)
+ {
+ global $db, $session, $paths, $template, $plugins; // Common objects
+ // cache user info
+ static $user_info_cache = null;
+
+ if ( isset($user_info_cache[$user_id_or_name]) )
+ {
+ $user_id =& $user_info_cache[$user_id_or_name]['user_id'];
+ $groups =& $user_info_cache[$user_id_or_name]['groups'];
+ }
+ else
+ {
+ $uid_column = ( is_int($user_id_or_name) ) ? "user_id = $user_id_or_name" : "username = '" . $db->escape($user_id_or_name) . "'";
+ $q = $db->sql_query('SELECT u.user_id, m.group_id, g.group_name FROM ' . table_prefix . "users AS u\n"
+ . " LEFT JOIN " . table_prefix . "group_members AS m\n"
+ . " ON ( ( u.user_id = m.user_id AND m.pending = 0 ) OR m.member_id IS NULL )\n"
+ . " LEFT JOIN " . table_prefix . "groups AS g\n"
+ . " ON ( g.group_id = m.group_id )\n"
+ . " WHERE $uid_column;");
+ if ( !$q )
+ $db->_die();
+
+ $groups = array();
+
+ if ( $row = $db->fetchrow() )
+ {
+ $user_id = intval($row['user_id']);
+ if ( $row['group_id'] )
+ {
+ do
+ {
+ $groups[ intval($row['group_id'] ) ] = $row['group_name'];
+ }
+ while ( $row = $db->fetchrow() );
+ }
+ $db->free_result();
+ }
+ else
+ {
+ $db->free_result();
+ throw new Exception('Unknown user ID or username');
+ }
+
+ $user_info_cache[$user_id_or_name] = array(
+ 'user_id' => $user_id,
+ 'groups' => $groups
+ );
+ }
+
+ // cache base permissions
+ static $base_cache = array();
+ if ( !isset($base_cache[$user_id_or_name]) )
+ {
+ $base_cache[$user_id_or_name] = $this->acl_types;
+ $current_perms =& $base_cache[$user_id_or_name];
+ $current_perms['__resolve_table'] = array();
+
+ $bs = 'SELECT rules, target_type, target_id, rule_id, page_id, namespace FROM '.table_prefix.'acl' . "\n"
+ . ' WHERE page_id IS NULL AND namespace IS NULL AND' . "\n"
+ . ' ( ';
+
+ $q = Array();
+ $q[] = '( target_type='.ACL_TYPE_USER.' AND target_id= ' . $user_id . ' )';
+ if(count($groups) > 0)
+ {
+ foreach($groups as $g_id => $g_name)
+ {
+ $q[] = '( target_type='.ACL_TYPE_GROUP.' AND target_id='.intval($g_id).' )';
+ }
+ }
+ $bs .= implode(" OR \n ", $q) . " ) \n ORDER BY target_type ASC, target_id ASC;";
+ $q = $this->sql($bs);
+ foreach ( $this->acl_types as $perm_type => $_ )
+ {
+ // init the resolver table with blanks
+ $current_perms['__resolve_table'][$perm_type] = array(
+ 'src' => ACL_INHERIT_GLOBAL_EVERYONE,
+ 'rule_id' => -1
+ );
+ }
+ if ( $row = $db->fetchrow() )
+ {
+ do {
+ $rules = $this->string_to_perm($row['rules']);
+ $is_everyone = ( $row['target_type'] == ACL_TYPE_GROUP && $row['target_id'] == 1 );
+ // track where these rulings are coming from
+ $src = ( $is_everyone ) ? ACL_INHERIT_GLOBAL_EVERYONE : ( $row['target_type'] == ACL_TYPE_GROUP ? ACL_INHERIT_GLOBAL_GROUP : ACL_INHERIT_GLOBAL_USER );
+ foreach ( $rules as $perm_type => $_ )
+ {
+ $current_perms['__resolve_table'][$perm_type] = array(
+ 'src' => $src,
+ 'rule_id' => $row['rule_id']
+ );
+ }
+ // merge it in
+ $current_perms = $this->acl_merge($current_perms, $rules, $is_everyone, $_defaults_used);
+ } while ( $row = $db->fetchrow() );
+ }
+ }
+
+ // cache of permission objects (to save RAM and SQL queries)
+ static $objcache = array();
+
+ if ( count($objcache) == 0 )
+ {
+ foreach ( $paths->nslist as $key => $_ )
+ {
+ $objcache[$key] = array();
+ }
+ }
+
+ if ( !isset($objcache[$namespace][$page_id]) )
+ {
+ $objcache[$namespace][$page_id] = array();
+ }
+
+ if ( isset($objcache[$namespace][$page_id][$user_id_or_name]) )
+ {
+ return $objcache[$namespace][$page_id][$user_id_or_name];
+ }
+
+ //if ( !isset( $paths->pages[$paths->nslist[$namespace] . $page_id] ) )
+ //{
+ // // Page does not exist
+ // return false;
+ //}
+
+ $objcache[$namespace][$page_id][$user_id_or_name] = new Session_ACLPageInfo(
+ $page_id, // $page_id,
+ $namespace, // $namespace,
+ $this->acl_types, // $acl_types,
+ $this->acl_descs, // $acl_descs,
+ $this->acl_deps, // $acl_deps,
+ $base_cache[$user_id_or_name], // $base,
+ $user_info_cache[$user_id_or_name]['user_id'], // $user_id = null,
+ $user_info_cache[$user_id_or_name]['groups'], // $groups = null,
+ $base_cache[$user_id_or_name]['__resolve_table'] // $resolve_table = array()
+ );
+ $object =& $objcache[$namespace][$page_id][$user_id_or_name];
+
+ return $object;
}
/**
@@ -2898,25 +3060,56 @@
* Merges two ACL arrays. Both parameters should be permission list arrays. The second group takes precedence over the first, but AUTH_DENY always prevails.
* @param array $perm1 The first set of permissions
* @param array $perm2 The second set of permissions
+ * @param bool $is_everyone If true, applies exceptions for "Everyone" group
+ * @param array|reference $defaults_used Array that will be filled with default usage data
* @return array
*/
- function acl_merge($perm1, $perm2)
+ function acl_merge($perm1, $perm2, $is_everyone = false, &$defaults_used)
{
$ret = $perm1;
- foreach ( $perm2 as $type => $level )
+ if ( !is_array(@$defaults_used) )
+ {
+ $defaults_used = array();
+ }
+ foreach ( $perm1 as $i => $p )
{
- if ( isset( $ret[$type] ) )
+ if ( isset($perm2[$i]) )
{
- if ( $ret[$type] != AUTH_DENY )
- $ret[$type] = $level;
+ if ( $is_everyone && !$defaults_used[$i] )
+ continue;
+ // Decide precedence
+ if ( isset($defaults_used[$i]) )
+ {
+ // echo "$i: default in use, overriding to: {$perm2[$i]}
";
+ // Defaults are in use, override
+
+ // CHANGED - 1.1.4:
+ // For some time this has been intentionally relaxed so that the following
+ // exception is available to Deny permissions:
+ // If the rule applies to the group "Everyone" on the entire site,
+ // Deny settings could be overriden.
+ // This is documented at: http://docs.enanocms.org/Help:4.2
+ if ( $perm1[$i] != AUTH_DENY )
+ {
+ $perm1[$i] = $perm2[$i];
+ $defaults_used[$i] = ( $is_everyone );
+ }
+ }
+ else
+ {
+ // echo "$i: default NOT in use";
+ // Defaults are not in use, merge as normal
+ if ( $perm1[$i] != AUTH_DENY )
+ {
+ // echo ", but overriding";
+ $perm1[$i] = $perm2[$i];
+ }
+ // echo "
";
+ }
}
- // else
- // {
- // $ret[$type] = $level;
- // }
}
- return $ret;
+ return $perm1;
}
/**
@@ -2953,43 +3146,7 @@
function acl_merge_with_current($perm, $is_everyone = false, $scope = 2)
{
- foreach ( $this->perms as $i => $p )
- {
- if ( isset($perm[$i]) )
- {
- if ( $is_everyone && !$this->acl_defaults_used[$i] )
- continue;
- // Decide precedence
- if ( isset($this->acl_defaults_used[$i]) )
- {
- // echo "$i: default in use, overriding to: {$perm[$i]}
";
- // Defaults are in use, override
-
- // CHANGED - 1.1.4:
- // For some time this has been intentionally relaxed so that the following
- // exception is available to Deny permissions:
- // If the rule applies to the group "Everyone" on the entire site,
- // Deny settings could be overriden.
- // This is documented at: http://docs.enanocms.org/Help:4.2
- if ( $this->perms[$i] != AUTH_DENY )
- {
- $this->perms[$i] = $perm[$i];
- $this->acl_defaults_used[$i] = ( $is_everyone );
- }
- }
- else
- {
- // echo "$i: default NOT in use";
- // Defaults are not in use, merge as normal
- if ( $this->perms[$i] != AUTH_DENY )
- {
- // echo ", but overriding";
- $this->perms[$i] = $perm[$i];
- }
- // echo "
";
- }
- }
- }
+ $this->perms = $this->acl_merge($this->perms, $perm, $is_everyone, $this->acl_defaults_used);
}
/**
@@ -3658,6 +3815,39 @@
var $wiki_mode = false;
/**
+ * Tracks where permissions were calculated using the ACL_INHERIT_* constants. Layout:
+ * array(
+ * [permission_name] => array(
+ * [src] => ACL_INHERIT_*
+ * [rule_id] => integer
+ * ),
+ * ...
+ * )
+ *
+ * @var array
+ */
+
+ var $perm_resolve_table = array();
+
+ #
+ # USER PARAMETERS
+ #
+
+ /**
+ * User ID
+ * @var int
+ */
+
+ var $user_id = 1;
+
+ /**
+ * Group membership associative array (group_id => group_name)
+ * @var array
+ */
+
+ var $groups = array();
+
+ /**
* Constructor.
* @param string $page_id The ID of the page to check
* @param string $namespace The namespace of the page to check.
@@ -3665,12 +3855,20 @@
* @param array $acl_descs List of human-readable descriptions for permissions (associative)
* @param array $acl_deps List of dependencies for permissions. For example, viewing history/diffs depends on the ability to read the page.
* @param array $base What to start with - this is an attempt to reduce the number of SQL queries.
+ * @param int|string $user_id_or_name Username or ID to search for, defaults to current user
+ * @param array $resolve_table Debugging info for tracking where rules came from, defaults to a blank array.
*/
- function Session_ACLPageInfo($page_id, $namespace, $acl_types, $acl_descs, $acl_deps, $base)
+ function __construct($page_id, $namespace, $acl_types, $acl_descs, $acl_deps, $base, $user_id = null, $groups = null, $resolve_table = array())
{
global $db, $session, $paths, $template, $plugins; // Common objects
+ // hack
+ if ( isset($base['__resolve_table']) )
+ {
+ unset($base['__resolve_table']);
+ }
+
$this->acl_deps = $acl_deps;
$this->acl_types = $acl_types;
$this->acl_descs = $acl_descs;
@@ -3678,6 +3876,39 @@
$this->perms = $acl_types;
$this->perms = $session->acl_merge_complete($this->perms, $base);
+ $this->perm_resolve_table = array();
+ if ( is_array($resolve_table) )
+ $this->perm_resolve_table = $resolve_table;
+
+ if ( is_int($user_id) && is_array($groups) )
+ {
+ $this->user_id = $user_id;
+ $this->groups = $groups;
+ }
+ else
+ {
+ $this->user_id = $session->user_id;
+ $this->groups = $session->groups;
+ }
+
+ $this->page_id = $page_id;
+ $this->namespace = $namespace;
+
+ $this->__calculate();
+ }
+
+ /**
+ * Performs the actual permission calculation.
+ * @access private
+ */
+
+ private function __calculate()
+ {
+ global $db, $session, $paths, $template, $plugins; // Common objects
+
+ $page_id =& $this->page_id;
+ $namespace =& $this->namespace;
+
// PAGE group info
$pg_list = $paths->get_page_groups($page_id, $namespace);
$pg_info = '';
@@ -3687,20 +3918,20 @@
}
// Build a query to grab ACL info
- $bs = 'SELECT rules,target_type,target_id FROM '.table_prefix.'acl WHERE ' . "\n"
+ $bs = 'SELECT rules,target_type,target_id,page_id,namespace,rule_id FROM '.table_prefix.'acl WHERE ' . "\n"
. ' ( ';
$q = Array();
- $q[] = '( target_type='.ACL_TYPE_USER.' AND target_id='.$session->user_id.' )';
- if(count($session->groups) > 0)
+ $q[] = '( target_type='.ACL_TYPE_USER.' AND target_id='.$this->user_id.' )';
+ if(count($this->groups) > 0)
{
- foreach($session->groups as $g_id => $g_name)
+ foreach($this->groups as $g_id => $g_name)
{
$q[] = '( target_type='.ACL_TYPE_GROUP.' AND target_id='.intval($g_id).' )';
}
}
// The reason we're using an ORDER BY statement here is because ACL_TYPE_GROUP is less than ACL_TYPE_USER, causing the user's individual
// permissions to override group permissions.
- $bs .= implode(" OR\n ", $q) . ' ) AND (' . $pg_info . ' page_id=\''.$db->escape($page_id).'\' AND namespace=\''.$db->escape($namespace).'\' )
+ $bs .= implode(" OR\n ", $q) . ' ) AND (' . $pg_info . ' ( page_id=\''.$db->escape($page_id).'\' AND namespace=\''.$db->escape($namespace).'\' ) )
ORDER BY target_type ASC, page_id ASC, namespace ASC;';
$q = $session->sql($bs);
if ( $row = $db->fetchrow() )
@@ -3708,6 +3939,25 @@
do {
$rules = $session->string_to_perm($row['rules']);
$is_everyone = ( $row['target_type'] == ACL_TYPE_GROUP && $row['target_id'] == 1 );
+ // log where this comes from
+ if ( $row['namespace'] == '__PageGroup' )
+ {
+ $src = ( $is_everyone ) ? ACL_INHERIT_PG_EVERYONE : ( $row['target_type'] == ACL_TYPE_GROUP ? ACL_INHERIT_PG_GROUP : ACL_INHERIT_PG_USER );
+ }
+ else
+ {
+ $src = ( $is_everyone ) ? ACL_INHERIT_LOCAL_EVERYONE : ( $row['target_type'] == ACL_TYPE_GROUP ? ACL_INHERIT_LOCAL_GROUP : ACL_INHERIT_LOCAL_USER );
+ }
+ foreach ( $rules as $perm_type => $perm_value )
+ {
+ if ( $this->perms[$perm_type] == AUTH_DENY )
+ continue;
+
+ $this->perm_resolve_table[$perm_type] = array(
+ 'src' => $src,
+ 'rule_id' => $row['rule_id']
+ );
+ }
$this->acl_merge_with_current($rules, $is_everyone);
} while ( $row = $db->fetchrow() );
}