From 6c2bb1005f9be48e398a5b41494d235d7061b7bb Mon Sep 17 00:00:00 2001
From: Andreas Gohr <gohr@cosmocode.de>
Date: Thu, 2 Mar 2006 11:18:50 +0100
Subject: [PATCH] Allow non-ID names in ACLs

Some auth backends allow special chars like whitespaces in user and group
names. This made problems with the existing ACL checks and ACL manager.
This patch makes the ACL system work with these cases by (url)encoding all
special chars below 128.

darcs-hash:20060302101850-6e07b-14bda9dbdb3528904325419b35bb9eddb0d1dde3.gz
---
 _test/cases/inc/auth_nameencode.test.php |  28 ++++
 conf/acl.auth.php.dist                   |   9 +-
 inc/auth.php                             |  23 ++-
 inc/auth/plain.class.php                 |   2 +
 lib/plugins/acl/admin.php                | 171 ++++++++++++-----------
 5 files changed, 145 insertions(+), 88 deletions(-)
 create mode 100644 _test/cases/inc/auth_nameencode.test.php

diff --git a/_test/cases/inc/auth_nameencode.test.php b/_test/cases/inc/auth_nameencode.test.php
new file mode 100644
index 000000000..6deb7de9f
--- /dev/null
+++ b/_test/cases/inc/auth_nameencode.test.php
@@ -0,0 +1,28 @@
+<?php
+
+require_once DOKU_INC.'inc/init.php';
+require_once DOKU_INC.'inc/auth.php';
+
+class auth_nameencode_test extends UnitTestCase {
+
+    function test_simple(){
+        $in  = 'hey$you';
+        $out = 'hey%24you';
+        $this->assertEqual(auth_nameencode($in),$out);
+    }
+
+    function test_complex(){
+        $in  = 'hey $ you !$%! foo ';
+        $out = 'hey%20%24%20you%20%21%24%25%21%20foo%20';
+        $this->assertEqual(auth_nameencode($in),$out);
+    }
+
+    function test_complexutf8(){
+        $in  = 'häü $ yü !$%! foo ';
+        $out = 'häü%20%24%20yü%20%21%24%25%21%20foo%20';
+        $this->assertEqual(auth_nameencode($in),$out);
+    }
+
+}
+
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/conf/acl.auth.php.dist b/conf/acl.auth.php.dist
index 3b0745648..3fa9741c5 100644
--- a/conf/acl.auth.php.dist
+++ b/conf/acl.auth.php.dist
@@ -2,7 +2,14 @@
 # <?php exit()?>
 # Don't modify the lines above
 #
-# Access Control
+# Access Control Lists
+#
+# Editing this file by hand shouldn't be necessary. Use the ACL
+# Manager interface instead.
+#
+# If your auth backend allows special char like spaces in groups
+# or user names you need to urlencode them (only chars <128, leave
+# UTF-8 multibyte chars as is)
 #
 # none   0
 # read   1
diff --git a/inc/auth.php b/inc/auth.php
index 6280cf1c1..79cae52e7 100644
--- a/inc/auth.php
+++ b/inc/auth.php
@@ -264,16 +264,18 @@ function auth_aclcheck($id,$user,$groups){
   # if no ACL is used always return upload rights
   if(!$conf['useacl']) return AUTH_UPLOAD;
 
+  $user = auth_nameencode($user);
+
   //if user is superuser return 255 (acl_admin)
   if($conf['superuser'] == $user) { return AUTH_ADMIN; }
 
   //make sure groups is an array
   if(!is_array($groups)) $groups = array();
 
-  //prepend groups with @
+  //prepend groups with @ and nameencode
   $cnt = count($groups);
   for($i=0; $i<$cnt; $i++){
-    $groups[$i] = '@'.$groups[$i];
+    $groups[$i] = '@'.auth_nameencode($groups[$i]);
   }
   //if user is in superuser group return 255 (acl_admin)
   if(in_array($conf['superuser'], $groups)) { return AUTH_ADMIN; }
@@ -350,6 +352,23 @@ function auth_aclcheck($id,$user,$groups){
   return AUTH_NONE;
 }
 
+/**
+ * Encode ASCII special chars
+ *
+ * Some auth backends allow special chars in their user and groupnames
+ * The special chars are encoded with this function. Only ASCII chars
+ * are encoded UTF-8 multibyte are left as is (different from usual
+ * urlencoding!).
+ *
+ * Decoding can be done with rawurldecode
+ *
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ * @see rawurldecode()
+ */
+function auth_nameencode($name){
+  return preg_replace('/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/e',"'%'.dechex(ord('\\1'))",$name);
+}
+
 /**
  * Create a pronouncable password
  *
diff --git a/inc/auth/plain.class.php b/inc/auth/plain.class.php
index 3ed014262..d3df1c0dd 100644
--- a/inc/auth/plain.class.php
+++ b/inc/auth/plain.class.php
@@ -15,6 +15,8 @@ define('AUTH_USERFILE',DOKU_CONF.'users.auth.php');
 // we only accept page ids for auth_plain
 if(isset($_REQUEST['u']))
   $_REQUEST['u'] = cleanID($_REQUEST['u']);
+if(isset($_REQUEST['acl_user']))
+  $_REQUEST['acl_user'] = cleanID($_REQUEST['acl_user']);
 
 class auth_plain extends auth_basic {
 
diff --git a/lib/plugins/acl/admin.php b/lib/plugins/acl/admin.php
index 5dc62edab..7295141cd 100644
--- a/lib/plugins/acl/admin.php
+++ b/lib/plugins/acl/admin.php
@@ -8,7 +8,7 @@
 if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
 if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
 require_once(DOKU_PLUGIN.'admin.php');
- 
+
 /**
  * All DokuWiki plugins to extend the admin function
  * need to inherit from this class
@@ -16,12 +16,12 @@ require_once(DOKU_PLUGIN.'admin.php');
 class admin_plugin_acl extends DokuWiki_Admin_Plugin {
 
 
-		function admin_plugin_acl(){
-			$this->setupLocale();
-		}
+        function admin_plugin_acl(){
+            $this->setupLocale();
+        }
+
 
 
- 
     /**
      * return some info
      */
@@ -35,33 +35,33 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
         'url'    => 'http://wiki.splitbrain.org/wiki:acl',
       );
     }
- 
+
     /**
      * return prompt for admin menu
      */
     function getMenuText($language) {
         return $this->lang['admin_acl'];
     }
- 
+
     /**
      * return sort order for position in admin menu
      */
     function getMenuSort() {
       return 1;
     }
- 
+
     /**
      * handle user request
      */
     function handle() {
       global $AUTH_ACL;
-    
+
       $cmd   = $_REQUEST['acl_cmd'];
       $scope = $_REQUEST['acl_scope'];
       $type  = $_REQUEST['acl_type'];
       $user  = $_REQUEST['acl_user'];
       $perm  = $_REQUEST['acl_perm'];
-    
+
       if(is_array($perm)){
         //use the maximum
         sort($perm);
@@ -69,30 +69,30 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
       }else{
         $perm = 0;
       }
-    
+
       //sanitize
-      $user  = cleanID($user);
+      $user  = auth_nameencode($user);
       if($type == '@') $user = '@'.$user;
       if($user == '@all') $user = '@ALL'; //special group! (now case insensitive)
       $perm  = (int) $perm;
       if($perm > AUTH_DELETE) $perm = AUTH_DELETE;
       //FIXME sanitize scope!!!
-    
+
       //nothing to do?
       if(empty($cmd) || empty($scope) || empty($user)) return;
-    
-    
+
+
       if($cmd == 'save'){
         $this->admin_acl_del($scope, $user);
-        $this->admin_acl_add($scope, $user, $perm);  
+        $this->admin_acl_add($scope, $user, $perm);
       }elseif($cmd == 'delete'){
         $this->admin_acl_del($scope, $user);
       }
-    
+
       // reload ACL config
       $AUTH_ACL = file(DOKU_CONF.'acl.auth.php');
     }
- 
+
     /**
      * ACL Output function
      *
@@ -104,26 +104,26 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
      */
     function html() {
       global $ID;
-    
+
       print $this->locale_xhtml('intro');
-    
+
       ptln('<div class="acladmin">');
       ptln('<table class="inline">');
-    
+
       //new
       $this->admin_acl_html_new();
-    
+
       //current config
       $acls = $this->get_acl_config($ID);
       foreach ($acls as $id => $acl){
-        $this->admin_acl_html_current($id,$acl); 
+        $this->admin_acl_html_current($id,$acl);
       }
-    
+
       ptln('</table>');
       ptln('</div>');
     }
-    
- 
+
+
     /**
      * Get matching ACL lines for a page
      *
@@ -138,9 +138,9 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
      */
     function get_acl_config($id){
       global $AUTH_ACL;
-      
+
       $acl_config=array();
-      
+
       // match exact name
       $matches = preg_grep('/^'.$id.'\s+.*/',$AUTH_ACL);
       if(count($matches)){
@@ -151,7 +151,7 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
           $acl_config[$acl[0]][] = array( 'name' => $acl[1], 'perm' => $acl[2]);
         }
       }
-      
+
       $specific_found=array();
       // match ns
       while(($id=getNS($id)) !== false){
@@ -166,7 +166,7 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
           }
         }
       }
-      
+
       //include *-config
       $matches = preg_grep('/^\*\s+.*/',$AUTH_ACL);
       if(count($matches)){
@@ -180,15 +180,15 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
           }
         }
       }
-      
+
       //sort
       //FIXME: better sort algo: first sort by key, then sort by first value
       krsort($acl_config, SORT_STRING);
-     
+
       return($acl_config);
     }
-    
-    
+
+
     /**
      * adds new acl-entry to conf/acl.auth.php
      *
@@ -196,19 +196,19 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
      */
     function admin_acl_add($acl_scope, $acl_user, $acl_level){
       $acl_config = join("",file(DOKU_CONF.'acl.auth.php'));
-      
+
       // max level for pagenames is edit
       if(strpos($acl_scope,'*') === false) {
         if($acl_level > AUTH_EDIT) $acl_level = AUTH_EDIT;
       }
-      
+
       $new_acl = "$acl_scope\t$acl_user\t$acl_level\n";
-      
+
       $new_config = $acl_config.$new_acl;
-      
+
       return io_saveFile(DOKU_CONF.'acl.auth.php', $new_config);
     }
-    
+
     /**
      * remove acl-entry from conf/acl.auth.php
      *
@@ -216,17 +216,17 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
      */
     function admin_acl_del($acl_scope, $acl_user){
       $acl_config = file(DOKU_CONF.'acl.auth.php');
-    
+
       $acl_pattern = '^'.preg_quote($acl_scope,'/').'\s+'.$acl_user.'\s+[0-8].*$';
-      
+
       // save all non!-matching #FIXME invert is available from 4.2.0 only!
       $new_config = preg_grep("/$acl_pattern/", $acl_config, PREG_GREP_INVERT);
-      
+
       return io_saveFile(DOKU_CONF.'acl.auth.php', join('',$new_config));
     }
-    
+
     // --- HTML OUTPUT FUNCTIONS BELOW --- //
-    
+
     /**
      * print tablerows with the current permissions for one id
      *
@@ -237,26 +237,26 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
       $cur = $id;
       $ret = '';
       $opt = array();
-    
+
       //prepare all options
-    
+
       // current page
       $opt[] = array('key'=> $id, 'val'=> $id.' ('.$this->lang['page'].')');
-    
+
       // additional namespaces
       while(($id=getNS($id)) !== false){
         $opt[] = array('key'=> $id.':*', 'val'=> $id.':* ('.$this->lang['namespace'].')');
       }
-    
+
       // the top namespace
       $opt[] = array('key'=> '*', 'val'=> '* ('.$this->lang['namespace'].')');
-    
+
       // set sel on second entry (current namespace)
       $opt[1]['sel'] = ' selected="selected"';
-    
+
       // flip options
       $opt = array_reverse($opt);
-    
+
       // create HTML
       $att = array( 'name'  => 'acl_scope',
                     'class' => 'edit',
@@ -266,10 +266,10 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
         $ret .= '<option value="'.$o['key'].'"'.$o['sel'].'>'.$o['val'].'</option>';
       }
       $ret .= '</select>';
-    
+
       return $ret;
     }
-    
+
     /**
      * print tablerows with the current permissions for one id
      *
@@ -278,26 +278,26 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
      */
     function admin_acl_html_new(){
       global $ID;
-			global $lang;
-    
+            global $lang;
+
       // table headers
       ptln('<tr>',2);
       ptln('  <th class="leftalign" colspan="3">'.$this->lang['acl_new'].'</th>',2);
       ptln('</tr>',2);
-    
+
       ptln('<tr>',2);
-    
+
       ptln('<td class="centeralign" colspan="3">',4);
-    
+
       ptln('  <form method="post" action="'.wl($ID).'">',4);
       ptln('    <input type="hidden" name="do"   value="admin" />',4);
       ptln('    <input type="hidden" name="page" value="acl" />',4);
       ptln('    <input type="hidden" name="acl_cmd" value="save" />',4);
-     
+
       //scope select
       ptln($this->lang['acl_perms'],4);
-      ptln($this->admin_acl_html_dropdown($ID),4); 
-    
+      ptln($this->admin_acl_html_dropdown($ID),4);
+
       $att = array( 'name'  => 'acl_type',
                     'class' => 'edit',
                     'title' => $this->lang['acl_user'].'/'.$this->lang['acl_group']);
@@ -305,22 +305,22 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
       ptln('      <option value="@">'.$this->lang['acl_group'].'</option>',4);
       ptln('      <option value="">'.$this->lang['acl_user'].'</option>',4);
       ptln('    </select>',4);
-    
+
       $att = array( 'name'  => 'acl_user',
                     'type'  => 'text',
                     'class' => 'edit',
                     'title' => $this->lang['acl_user'].'/'.$this->lang['acl_group']);
       ptln('    <input '.html_attbuild($att).' />',4);
       ptln('    <br />');
-    
+
       ptln(     $this->admin_acl_html_checkboxes(0,false),8);
-    
+
       ptln('    <input type="submit" class="edit" value="'.$lang['btn_save'].'" />',4);
       ptln('  </form>');
       ptln('</td>',4);
       ptln('</tr>',2);
     }
-    
+
     /**
      * print tablerows with the current permissions for one id
      *
@@ -330,14 +330,14 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
     function admin_acl_html_current($id,$permissions){
       global $lang;
       global $ID;
-    
+
       //is it a page?
       if(substr($id,-1) == '*'){
         $ispage = false;
       }else{
         $ispage = true;
       }
-    
+
       // table headers
       ptln('  <tr>');
       ptln('    <th class="leftalign" colspan="3">');
@@ -350,11 +350,12 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
       ptln('<em>'.$id.'</em>',6);
       ptln('    </th>');
       ptln('  </tr>');
-    
+
       sort($permissions);
-    
+
       foreach ($permissions as $conf){
         //userfriendly group/user display
+        $conf['name'] = rawurldecode($conf['name']);
         if(substr($conf['name'],0,1)=="@"){
           $group = $this->lang['acl_group'];
           $name  = substr($conf['name'],1);
@@ -364,10 +365,10 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
           $name  = $conf['name'];
           $type  = '';
         }
-    
+
         ptln('<tr>',2);
-        ptln('<td class="leftalign">'.$group.' '.$name.'</td>',4);
-    
+        ptln('<td class="leftalign">'.htmlspecialchars($group.' '.$name).'</td>',4);
+
         // update form
         ptln('<td class="centeralign">',4);
         ptln('  <form method="post" action="'.wl($ID).'">',4);
@@ -381,14 +382,14 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
         ptln('    <input type="submit" class="edit" value="'.$lang['btn_update'].'" />',4);
         ptln('  </form>');
         ptln('</td>',4);
-    
-    
+
+
         // deletion form
-    
+
         $ask  = $lang['del_confirm'].'\\n';
         $ask .= $id.'  '.$conf['name'].'  '.$conf['perm'];
         ptln('<td class="centeralign">',4);
-        ptln('  <form method="post" action="'.wl($ID).'" onsubmit="return confirm(\''.$ask.'\')">',4);
+        ptln('  <form method="post" action="'.wl($ID).'" onsubmit="return confirm(\''.str_replace('\\\\n','\\n',addslashes($ask)).'\')">',4);
         ptln('    <input type="hidden" name="do"        value="admin" />',4);
         ptln('    <input type="hidden" name="page"      value="acl" />',4);
         ptln('    <input type="hidden" name="acl_cmd"   value="delete" />',4);
@@ -398,13 +399,13 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
         ptln('    <input type="submit" class="edit" value="'.$lang['btn_delete'].'" />',4);
         ptln('  </form>',4);
         ptln('</td>',4);
-    
+
         ptln('</tr>',2);
       }
-    
+
     }
-    
-    
+
+
     /**
      * print the permission checkboxes
      *
@@ -413,13 +414,13 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
      */
     function admin_acl_html_checkboxes($setperm,$ispage){
       global $lang;
-    
+
       static $label = 0; //number labels
       $ret = '';
-    
+
       foreach(array(AUTH_READ,AUTH_EDIT,AUTH_CREATE,AUTH_UPLOAD,AUTH_DELETE) as $perm){
         $label += 1;
-    
+
         //general checkbox attributes
         $atts = array( 'type'  => 'checkbox',
                        'id'    => 'pbox'.$label,
@@ -429,7 +430,7 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
         if($setperm >= $perm) $atts['checked']  = 'checked';
     #        if($perm > AUTH_READ) $atts['onchange'] = #FIXME JS to autoadd lower perms
         if($ispage && $perm > AUTH_EDIT) $atts['disabled'] = 'disabled';
-    
+
         //build code
         $ret .= '<label for="pbox'.$label.'" title="'.$this->lang['acl_perm'.$perm].'">';
         $ret .= '<input '.html_attbuild($atts).' />';
@@ -438,5 +439,5 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
       }
       return $ret;
     }
-    
+
 }
-- 
GitLab