Got ACL scope logic working again and began enforcing it. Breaking API change: assigning page title with $template->tpl_strings['PAGE_NAME'] will no longer work, use $template->assign_vars(). Workaround may be added later. Test for assign_vars method if compatibility needed. Added namespace processor API (non-breaking change). Several other things tweaked around as well.
authorDan
Sun, 15 Jun 2008 00:59:37 -0400
changeset 571 66e14e61613e
parent 570 4d0d5dae61e5
child 572 c196e8f336b9
Got ACL scope logic working again and began enforcing it. Breaking API change: assigning page title with $template->tpl_strings['PAGE_NAME'] will no longer work, use $template->assign_vars(). Workaround may be added later. Test for assign_vars method if compatibility needed. Added namespace processor API (non-breaking change). Several other things tweaked around as well.
includes/pageprocess.php
includes/paths.php
includes/sessions.php
includes/template.php
--- a/includes/pageprocess.php	Sat Jun 14 22:01:24 2008 -0400
+++ b/includes/pageprocess.php	Sun Jun 15 00:59:37 2008 -0400
@@ -194,6 +194,15 @@
         return false;
       }
     }
+    
+    // Is there a custom function registered for handling this namespace?
+    if ( $proc = $paths->get_namespace_processor($this->namespace) )
+    {
+      // yes, just call that
+      // this is protected aggressively by the PathManager against overriding critical namespaces
+      return call_user_func($proc, $this);
+    }
+    
     $pathskey = $paths->nslist[ $this->namespace ] . $this->page_id;
     $strict_no_headers = false;
     if ( $this->namespace == 'Admin' && strstr($this->page_id, '/') )
--- a/includes/paths.php	Sat Jun 14 22:01:24 2008 -0400
+++ b/includes/paths.php	Sun Jun 15 00:59:37 2008 -0400
@@ -17,8 +17,18 @@
  * @see http://enanocms.org/Help:API_Documentation
  */
  
-class pathManager {
-  var $pages, $custom_page, $cpage, $page, $fullpage, $page_exists, $page_id, $namespace, $nslist, $admin_tree, $wiki_mode, $page_protected, $template_cache, $anonymous_page;
+class pathManager
+{
+  public $pages, $custom_page, $cpage, $page, $fullpage, $page_exists, $page_id, $namespace, $nslist, $admin_tree, $wiki_mode, $page_protected, $template_cache, $anonymous_page;
+  
+  /**
+   * List of custom processing functions for namespaces. This is protected so trying to do anything with it will throw an error.
+   * @access private
+   * @var array
+   */
+  
+  protected $namespace_processors;
+  
   function __construct()
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
@@ -76,7 +86,7 @@
     $session->register_acl_type('html_in_pages',          AUTH_DISALLOW, 'perm_html_in_pages',          Array('edit_page'),                                       'Article|User|Project|Template|File|Help|System|Category|Admin');
     $session->register_acl_type('php_in_pages',           AUTH_DISALLOW, 'perm_php_in_pages',           Array('edit_page', 'html_in_pages'),                      'Article|User|Project|Template|File|Help|System|Category|Admin');
     $session->register_acl_type('custom_user_title',      AUTH_DISALLOW, 'perm_custom_user_title',      Array(),                                                  'User|Special');
-    $session->register_acl_type('edit_acl',               AUTH_DISALLOW, 'perm_edit_acl',               Array('read', 'post_comments', 'edit_comments', 'edit_page', 'view_source', 'mod_comments', 'history_view', 'history_rollback', 'history_rollback_extra', 'protect', 'rename', 'clear_logs', 'vote_delete', 'vote_reset', 'delete_page', 'set_wiki_mode', 'password_set', 'password_reset', 'mod_misc', 'edit_cat', 'even_when_protected', 'upload_files', 'upload_new_version', 'create_page', 'php_in_pages'));
+    $session->register_acl_type('edit_acl',               AUTH_DISALLOW, 'perm_edit_acl',               Array());
     
     // DO NOT add new admin pages here! Use a plugin to call $paths->addAdminNode();
     $this->addAdminNode('adm_cat_general',    'adm_page_general_config', 'GeneralConfig', scriptPath . '/images/icons/applets/generalconfig.png');
@@ -573,6 +583,56 @@
   }
   
   /**
+   * Registers a handler to manually process a namespace instead of the default PageProcessor behavior.
+   * The first and only parameter passed to the processing function will be the PageProcessor instance.
+   * @param string Namespace to process
+   * @param mixed Function address. Either a function name or an array of the form array(0 => mixed (string:class name or object), 1 => string:method)
+   */
+  
+  function register_namespace_processor($namespace, $function)
+  {
+    if ( isset($this->namespace_processors[$namespace]) )
+    {
+      $processorname = ( is_string($this->namespace_processors[$namespace]) ) ?
+        $this->namespace_processors[$namespace] :
+        ( is_object($this->namespace_processors[$namespace][0]) ? get_class($this->namespace_processors[$namespace][0]) : $this->namespace_processors[$namespace][0] ) . '::' .
+          $this->namespace_processors[$namespace][1];
+          
+      trigger_error("Namespace \"$namespace\" is already being processed by $processorname - replacing caller", E_USER_WARNING);
+    }
+    if ( !is_string($function) )
+    {
+      if ( !is_array($function) )
+        return false;
+      if ( count($function) != 2 )
+        return false;
+      if ( !is_string($function[0]) && !is_object($function[0]) )
+        return false;
+      if ( !is_string($function[1]) )
+        return false;
+    }
+    
+    // security: don't allow Special or Admin namespaces to be overridden
+    if ( $namespace == 'Special' || $namespace == 'Admin' )
+    {
+      trigger_error("Security manager denied attempt to override processor for $namespace", E_USER_ERROR);
+    }
+    
+    $this->namespace_processors[$namespace] = $function;
+  }
+  
+  /**
+   * Returns a namespace processor if one exists, otherwise returns false.
+   * @param string Namespace
+   * @return mixed
+   */
+  
+  function get_namespace_processor($namespace)
+  {
+    return ( isset($this->namespace_processors[$namespace]) ) ? $this->namespace_processors[$namespace] : false;
+  }
+  
+  /**
    * Fetches the page texts for searching
    */
    
@@ -829,7 +889,17 @@
     $row = $db->fetchrow();
     $db->free_result();
     $search = new Searcher();
-    $search->buildIndex(Array("ns={$namespace};pid={$page_id}"=>$row['page_text'] . ' ' . $this->pages[$idstring]['name']));
+    
+    // if the page shouldn't be indexed, send a blank set of strings to the indexing engine
+    if ( $this->pages[$idstring]['visible'] == 0 )
+    {
+      $search->buildIndex(Array("ns={$namespace};pid={$page_id}"=>''));
+    }
+    else
+    {
+      $search->buildIndex(Array("ns={$namespace};pid={$page_id}"=>$row['page_text'] . ' ' . $this->pages[$idstring]['name']));
+    }
+    
     $new_index = $search->index;
     
     if ( ENANO_DBLAYER == 'MYSQL' )
--- a/includes/sessions.php	Sat Jun 14 22:01:24 2008 -0400
+++ b/includes/sessions.php	Sun Jun 15 00:59:37 2008 -0400
@@ -2935,6 +2935,22 @@
   }
   
   /**
+   * Checks if the given ACL rule type applies to a namespace.
+   * @param string ACL rule type
+   * @param string Namespace
+   * @return bool
+   */
+  
+  function check_acl_scope($acl_rule, $namespace)
+  {
+    if ( !isset($this->acl_scope[$acl_rule]) )
+      return false;
+    if ( $this->acl_scope[$acl_rule] === array('All') )
+      return true;
+    return ( in_array($namespace, $this->acl_scope[$acl_rule]) ) ? true : false;
+  }
+  
+  /**
    * Read all of our permissions from the database and process/apply them. This should be called after the page is determined.
    * @access private
    */
@@ -3038,7 +3054,8 @@
       }
       else
       {
-        $this->acl_scope[$perm_type][] = $ns;
+        if ( $this->acl_scope[$perm_type] !== array('All') )
+          $this->acl_scope[$perm_type][] = $ns;
         if ( isset($this->acl_types[$perm_type]) && !isset($this->perms[$perm_type]) )
         {
           $this->perms[$perm_type] = $this->acl_types[$perm_type];
@@ -3895,6 +3912,17 @@
       unset($base['__resolve_table']);
     }
     
+    foreach ( $acl_types as $perm_type => $_ )
+    {
+      if ( !$session->check_acl_scope($perm_type, $namespace) )
+      {
+        unset($acl_types[$perm_type]);
+        unset($acl_deps[$perm_type]);
+        unset($acl_descs[$perm_type]);
+        unset($base[$perm_type]);
+      }
+    }
+    
     $this->acl_deps = $acl_deps;
     $this->acl_types = $acl_types;
     $this->acl_descs = $acl_descs;
@@ -3991,6 +4019,9 @@
           if ( $this->perms[$perm_type] == AUTH_DENY )
             continue;
           
+          if ( !$session->check_acl_scope($perm_type, $this->namespace) )
+            continue;
+          
           $this->perm_resolve_table[$perm_type] = array(
               'src' => $src,
               'rule_id' => $row['rule_id']
@@ -4093,7 +4124,23 @@
     else
     {
       // ACL type is undefined
-      trigger_error('Unknown access type "' . $type . '"', E_USER_WARNING);
+      $caller = 'unknown';
+      if ( function_exists('debug_backtrace') )
+      {
+        if ( $bt = @debug_backtrace() )
+        {
+          foreach ( $bt as $trace )
+          {
+            $file = basename($trace['file']);
+            if ( $file != 'sessions.php' )
+            {
+              $caller = $file . ':' . $trace['line'];
+              break;
+            }
+          }
+        }
+      }
+      trigger_error('Unknown access type "' . $type . '", called from ' . $caller . '', E_USER_WARNING);
       return false; // Be on the safe side and deny access
     }
     if ( !$no_deps )
--- a/includes/template.php	Sat Jun 14 22:01:24 2008 -0400
+++ b/includes/template.php	Sun Jun 15 00:59:37 2008 -0400
@@ -558,7 +558,7 @@
     
     // Page toolbar
     // Comments button
-    if ( $perms->get_permissions('read') && getConfig('enable_comments')=='1' && $local_namespace != 'Special' && $local_namespace != 'Admin' && $local_cdata['comments_on'] == 1 )
+    if ( $perms->get_permissions('read') && getConfig('enable_comments')=='1' && $local_cdata['comments_on'] == 1 )
     {
       
       $e = $db->sql_query('SELECT approved FROM '.table_prefix.'comments WHERE page_id=\''.$local_page_id.'\' AND namespace=\''.$local_namespace.'\';');
@@ -610,7 +610,7 @@
       $tb .= $button->run();
     }
     // Edit button
-    if($perms->get_permissions('read') && ($local_namespace != 'Special' && $local_namespace != 'Admin' && $local_namespace != 'Anonymous') && ( $perms->get_permissions('edit_page') && ( ( $paths->page_protected && $perms->get_permissions('even_when_protected') ) || !$paths->page_protected ) ) )
+    if($perms->get_permissions('read') && $session->check_acl_scope('edit_page', $local_namespace) && ( $perms->get_permissions('edit_page') && ( ( $paths->page_protected && $perms->get_permissions('even_when_protected') ) || !$paths->page_protected ) ) )
     {
       $button->assign_vars(array(
         'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxEditor()); return false; }" title="' . $lang->get('onpage_tip_edit') . '" accesskey="e"',
@@ -621,7 +621,7 @@
       $tb .= $button->run();
     // View source button
     }
-    else if($perms->get_permissions('view_source') && ( !$perms->get_permissions('edit_page') || !$perms->get_permissions('even_when_protected') && $paths->page_protected ) && $local_namespace != 'Special' && $local_namespace != 'Admin' && $local_namespace != 'Anonymous') 
+    else if ( $session->check_acl_scope('view_source', $local_namespace) && $perms->get_permissions('view_source') && ( !$perms->get_permissions('edit_page') || !$perms->get_permissions('even_when_protected') && $paths->page_protected ) && $local_namespace != 'Anonymous') 
     {
       $button->assign_vars(array(
         'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxEditor()); return false; }" title="' . $lang->get('onpage_tip_viewsource') . '" accesskey="e"',
@@ -632,7 +632,7 @@
       $tb .= $button->run();
     }
     // History button
-    if ( $perms->get_permissions('read') /* && $paths->wiki_mode */ && $local_page_exists && $local_namespace != 'Special' && $local_namespace != 'Admin' && $perms->get_permissions('history_view') )
+    if ( $perms->get_permissions('read') && $session->check_acl_scope('history_view', $local_namespace) && $local_page_exists && $perms->get_permissions('history_view') )
     {
       $button->assign_vars(array(
         'FLAGS'       => 'onclick="if ( !KILL_SWITCH ) { void(ajaxHistory()); return false; }" title="' . $lang->get('onpage_tip_history') . '" accesskey="h"',
@@ -647,7 +647,7 @@
     
     // Additional actions menu
     // Rename button
-    if ( $perms->get_permissions('read') && $local_page_exists && ( $perms->get_permissions('rename') && ( $paths->page_protected && $perms->get_permissions('even_when_protected') || !$paths->page_protected ) ) && $local_namespace != 'Special' && $local_namespace != 'Admin' )
+    if ( $perms->get_permissions('read') && $session->check_acl_scope('rename', $local_namespace) && $local_page_exists && ( $perms->get_permissions('rename') && ( $paths->page_protected && $perms->get_permissions('even_when_protected') || !$paths->page_protected ) ) )
     {
       $menubtn->assign_vars(array(
           'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxRename()); return false; }" title="' . $lang->get('onpage_tip_rename') . '" accesskey="r"',
@@ -658,7 +658,7 @@
     }
     
     // Vote-to-delete button
-    if ( $paths->wiki_mode && $perms->get_permissions('vote_delete') && $local_page_exists && $local_namespace != 'Special' && $local_namespace != 'Admin')
+    if ( $paths->wiki_mode && $session->check_acl_scope('vote_delete', $local_namespace) && $perms->get_permissions('vote_delete') && $local_page_exists)
     {
       $menubtn->assign_vars(array(
           'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxDelVote()); return false; }" title="' . $lang->get('onpage_tip_delvote') . '" accesskey="d"',
@@ -669,7 +669,7 @@
     }
     
     // Clear-votes button
-    if ( $perms->get_permissions('read') && $paths->wiki_mode && $local_page_exists && $local_namespace != 'Special' && $local_namespace != 'Admin' && $perms->get_permissions('vote_reset') && $local_cdata['delvotes'] > 0)
+    if ( $perms->get_permissions('read') && $session->check_acl_scope('vote_reset', $local_namespace) && $paths->wiki_mode && $local_page_exists && $perms->get_permissions('vote_reset') && $local_cdata['delvotes'] > 0)
     {
       $menubtn->assign_vars(array(
           'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxResetDelVotes()); return false; }" title="' . $lang->get('onpage_tip_resetvotes') . '" accesskey="y"',
@@ -680,7 +680,7 @@
     }
     
     // Printable page button
-    if ( $local_page_exists && $local_namespace != 'Special' && $local_namespace != 'Admin' )
+    if ( $local_page_exists )
     {
       $menubtn->assign_vars(array(
           'FLAGS' => 'title="' . $lang->get('onpage_tip_printable') . '"',
@@ -691,7 +691,7 @@
     }
     
     // Protect button
-    if($perms->get_permissions('read') && $paths->wiki_mode && $local_page_exists && $local_namespace != 'Special' && $local_namespace != 'Admin' && $perms->get_permissions('protect'))
+    if($perms->get_permissions('read') && $session->check_acl_scope('protect', $local_namespace) && $paths->wiki_mode && $local_page_exists && $perms->get_permissions('protect'))
     {
       
       $label = $this->makeParserText($tplvars['toolbar_label']);
@@ -745,7 +745,7 @@
     }
     
     // Wiki mode button
-    if($perms->get_permissions('read') && $local_page_exists && $perms->get_permissions('set_wiki_mode') && $local_namespace != 'Special' && $local_namespace != 'Admin')
+    if($perms->get_permissions('read') && $session->check_acl_scope('set_wiki_mode', $local_namespace) && $local_page_exists && $perms->get_permissions('set_wiki_mode'))
     {
       // label at start
       $label = $this->makeParserText($tplvars['toolbar_label']);
@@ -803,7 +803,7 @@
     }
     
     // Clear logs button
-    if ( $perms->get_permissions('read') && $perms->get_permissions('clear_logs') && $local_namespace != 'Special' && $local_namespace != 'Admin' )
+    if ( $perms->get_permissions('read') && $session->check_acl_scope('clear_logs', $local_namespace) && $perms->get_permissions('clear_logs') )
     {
       $menubtn->assign_vars(array(
           'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxClearLogs()); return false; }" title="' . $lang->get('onpage_tip_flushlogs') . '" accesskey="l"',
@@ -814,7 +814,7 @@
     }
     
     // Delete page button
-    if ( $perms->get_permissions('read') && $perms->get_permissions('delete_page') && $local_page_exists && $local_namespace != 'Special' && $local_namespace != 'Admin' )
+    if ( $perms->get_permissions('read') && $session->check_acl_scope('delete_page', $local_namespace) && $perms->get_permissions('delete_page') && $local_page_exists )
     {
       $s = $lang->get('onpage_btn_deletepage');
       if ( $local_cdata['delvotes'] == 1 )
@@ -844,7 +844,7 @@
     }
     
     // Password-protect button
-    if(isset($local_cdata['password']))
+    if(isset($local_cdata['password']) && $session->check_acl_scope('password_set', $local_namespace) && $session->check_acl_scope('password_reset', $local_namespace))
     {
       if ( $local_cdata['password'] == '' )
       {
@@ -855,11 +855,15 @@
         $a = $perms->get_permissions('password_reset');
       }
     }
-    else
+    else if ( $session->check_acl_scope('password_set', $local_namespace) )
     {
       $a = $perms->get_permissions('password_set');
     }
-    if ( $a && $perms->get_permissions('read') && $local_page_exists && $local_namespace != 'Special' && $local_namespace != 'Admin' )
+    else
+    {
+      $a = false;
+    }
+    if ( $a && $perms->get_permissions('read') && $local_page_exists )
     {
       // label at start
       $label = $this->makeParserText($tplvars['toolbar_label']);
@@ -877,7 +881,7 @@
     }
     
     // Manage ACLs button
-    if ( !$paths->anonymous_page && ( $perms->get_permissions('edit_acl') || ( defined('ACL_ALWAYS_ALLOW_ADMIN_EDIT_ACL') &&  $session->user_level >= USER_LEVEL_ADMIN ) ) )
+    if ( !$paths->anonymous_page && $session->check_acl_scope('edit_acl', $local_namespace) && ( $perms->get_permissions('edit_acl') || ( defined('ACL_ALWAYS_ALLOW_ADMIN_EDIT_ACL') &&  $session->user_level >= USER_LEVEL_ADMIN ) ) )
     {
       $menubtn->assign_vars(array(
           'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { return ajaxOpenACLManager(); }" title="' . $lang->get('onpage_tip_aclmanager') . '" accesskey="m"',
@@ -888,7 +892,7 @@
     }
     
     // Administer page button
-    if ( $session->user_level >= USER_LEVEL_ADMIN && $local_page_exists && $local_namespace != 'Special' && $local_namespace != 'Admin' )
+    if ( $session->user_level >= USER_LEVEL_ADMIN && $local_page_exists )
     {
       $menubtn->assign_vars(array(
           'FLAGS' => 'onclick="if ( !KILL_SWITCH ) { void(ajaxAdminPage()); return false; }" title="' . $lang->get('onpage_tip_adminoptions') . '" accesskey="g"',
@@ -940,9 +944,9 @@
     /* if($this->sidebar_extra == '') $this->tpl_bool['right_sidebar'] = false;
     else */ $this->tpl_bool['right_sidebar'] = true;
     
-    $this->tpl_bool['auth_rename'] = ( $local_page_exists && ( $perms->get_permissions('rename') && ( $paths->page_protected && $perms->get_permissions('even_when_protected') || !$paths->page_protected ) ) && $local_namespace != 'Special' && $local_namespace != 'Admin');
+    $this->tpl_bool['auth_rename'] = ( $local_page_exists && $session->check_acl_scope('rename', $local_namespace) && ( $perms->get_permissions('rename') && ( $paths->page_protected && $perms->get_permissions('even_when_protected') || !$paths->page_protected ) ));
     
-    $this->tpl_bool['enable_uploads'] = ( getConfig('enable_uploads') == '1' && $perms->get_permissions('upload_files') ) ? true : false;
+    $this->tpl_bool['enable_uploads'] = ( getConfig('enable_uploads') == '1' && $session->get_permissions('upload_files') ) ? true : false;
     
     $this->tpl_bool['stupid_mode'] = false;
     
@@ -1002,6 +1006,15 @@
     $urlname_jssafe = sanitize_page_id($local_fullpage);
     $physical_urlname_jssafe = sanitize_page_id($paths->fullpage);
     
+    if ( $session->check_acl_scope('even_when_protected', $local_namespace) )
+    {
+      $protected = $paths->page_protected && !$perms->get_permissions('even_when_protected');
+    }
+    else
+    {
+      $protected = false;
+    }
+    
     // Generate the dynamic javascript vars
     $js_dynamic = '    <script type="text/javascript">// <![CDATA[
       // This section defines some basic and very important variables that are used later in the static Javascript library.
@@ -1023,7 +1036,7 @@
       var pref_disable_js_fx = ' . ( @$session->user_extra['disable_js_fx'] == 1 ? '1' : '0' ) . ';
       var csrf_token = "' . $session->csrf_token . '";
       var editNotice = \'' . ( (getConfig('wiki_edit_notice')=='1') ? str_replace("\n", "\\\n", RenderMan::render(getConfig('wiki_edit_notice_text'))) : '' ) . '\';
-      var prot = ' . ( ($paths->page_protected && !$perms->get_permissions('even_when_protected')) ? 'true' : 'false' ) .'; // No, hacking this var won\'t work, it\'s re-checked on the server
+      var prot = ' . ( ($protected) ? 'true' : 'false' ) .'; // No, hacking this var won\'t work, it\'s re-checked on the server
       var ENANO_SPECIAL_CREATEPAGE = \''. makeUrl($paths->nslist['Special'].'CreatePage') .'\';
       var ENANO_CREATEPAGE_PARAMS = \'_do=&pagename='. $urlname_clean .'&namespace=' . $local_namespace . '\';
       var ENANO_SPECIAL_CHANGESTYLE = \''. makeUrlNS('Special', 'ChangeStyle') .'\';