1<?php 2/** 3 * ACL administration functions 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Frank Schubert <frank@schokilade.de> 7 */ 8// must be run within Dokuwiki 9if(!defined('DOKU_INC')) die(); 10 11if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 12require_once(DOKU_PLUGIN.'admin.php'); 13 14/** 15 * All DokuWiki plugins to extend the admin function 16 * need to inherit from this class 17 */ 18class admin_plugin_acl extends DokuWiki_Admin_Plugin { 19 20 21 function admin_plugin_acl(){ 22 $this->setupLocale(); 23 } 24 25 26 27 /** 28 * return some info 29 */ 30 function getInfo(){ 31 return array( 32 'author' => 'Frank Schubert', 33 'email' => 'frank@schokilade.de', 34 'date' => '2005-08-08', 35 'name' => 'ACL', 36 'desc' => 'Manage Page Access Control Lists', 37 'url' => 'http://wiki.splitbrain.org/wiki:acl', 38 ); 39 } 40 41 /** 42 * return prompt for admin menu 43 */ 44 function getMenuText($language) { 45 return $this->lang['admin_acl']; 46 } 47 48 /** 49 * return sort order for position in admin menu 50 */ 51 function getMenuSort() { 52 return 1; 53 } 54 55 /** 56 * handle user request 57 */ 58 function handle() { 59 global $AUTH_ACL; 60 61 $cmd = $_REQUEST['acl_cmd']; 62 $scope = $_REQUEST['acl_scope']; 63 $type = $_REQUEST['acl_type']; 64 $user = $_REQUEST['acl_user']; 65 $perm = $_REQUEST['acl_perm']; 66 67 if(is_array($perm)){ 68 //use the maximum 69 sort($perm); 70 $perm = array_pop($perm); 71 }else{ 72 $perm = 0; 73 } 74 75 //sanitize 76 $user = auth_nameencode($user); 77 if($type == '@') $user = '@'.$user; 78 if($user == '@all') $user = '@ALL'; //special group! (now case insensitive) 79 $perm = (int) $perm; 80 if($perm > AUTH_DELETE) $perm = AUTH_DELETE; 81 82 // check token 83 if(!checkSecurityToken()) return; 84 85 //nothing to do? 86 if(empty($cmd) || empty($scope) || empty($user)) return; 87 88 89 if($cmd == 'save'){ 90 $this->admin_acl_del($scope, $user); 91 $this->admin_acl_add($scope, $user, $perm); 92 }elseif($cmd == 'delete'){ 93 $this->admin_acl_del($scope, $user); 94 } 95 96 // reload ACL config 97 $AUTH_ACL = file(DOKU_CONF.'acl.auth.php'); 98 } 99 100 /** 101 * ACL Output function 102 * 103 * print a table with all significant permissions for the 104 * current id 105 * 106 * @author Frank Schubert <frank@schokilade.de> 107 * @author Andreas Gohr <andi@splitbrain.org> 108 */ 109 function html() { 110 global $ID; 111 112 print $this->locale_xhtml('intro'); 113 114 ptln('<div id="acl__manager">'); 115 ptln('<table class="inline">'); 116 117 //new 118 $this->admin_acl_html_new(); 119 120 //current config 121 $acls = $this->get_acl_config($ID); 122 foreach ($acls as $id => $acl){ 123 $this->admin_acl_html_current($id,$acl); 124 } 125 126 ptln('</table>'); 127 ptln('</div>'); 128 } 129 130 131 /** 132 * Get matching ACL lines for a page 133 * 134 * $ID is pagename, reads matching lines from $AUTH_ACL, 135 * also reads acls from namespace 136 * returns multi-array with key=pagename and value=array(user, acl) 137 * 138 * @todo Fix comment to make sense 139 * @todo should this moved to auth.php? 140 * @todo can this be combined with auth_aclcheck to avoid duplicate code? 141 * @author Frank Schubert <frank@schokilade.de> 142 */ 143 function get_acl_config($id){ 144 global $AUTH_ACL; 145 146 $acl_config=array(); 147 148 // match exact name 149 $matches = preg_grep('/^'.$id.'\s+.*/',$AUTH_ACL); 150 if(count($matches)){ 151 foreach($matches as $match){ 152 $match = preg_replace('/#.*$/','',$match); //ignore comments 153 $acl = preg_split('/\s+/',$match); 154 //0 is pagename, 1 is user, 2 is acl 155 $acl_config[$acl[0]][] = array( 'name' => $acl[1], 'perm' => $acl[2]); 156 } 157 } 158 159 $specific_found=array(); 160 // match ns 161 while(($id=getNS($id)) !== false){ 162 $matches = preg_grep('/^'.$id.':\*\s+.*/',$AUTH_ACL); 163 if(count($matches)){ 164 foreach($matches as $match){ 165 $match = preg_replace('/#.*$/','',$match); //ignore comments 166 $acl = preg_split('/\s+/',$match); 167 //0 is pagename, 1 is user, 2 is acl 168 $acl_config[$acl[0]][] = array( 'name' => $acl[1], 'perm' => $acl[2]); 169 $specific_found[]=$acl[1]; 170 } 171 } 172 } 173 174 //include *-config 175 $matches = preg_grep('/^\*\s+.*/',$AUTH_ACL); 176 if(count($matches)){ 177 foreach($matches as $match){ 178 $match = preg_replace('/#.*$/','',$match); //ignore comments 179 $acl = preg_split('/\s+/',$match); 180 // only include * for this user if not already found in ns 181 if(!in_array($acl[1], $specific_found)){ 182 //0 is pagename, 1 is user, 2 is acl 183 $acl_config[$acl[0]][] = array( 'name' => $acl[1], 'perm' => $acl[2]); 184 } 185 } 186 } 187 188 //sort 189 //FIXME: better sort algo: first sort by key, then sort by first value 190 krsort($acl_config, SORT_STRING); 191 192 return($acl_config); 193 } 194 195 196 /** 197 * adds new acl-entry to conf/acl.auth.php 198 * 199 * @author Frank Schubert <frank@schokilade.de> 200 */ 201 function admin_acl_add($acl_scope, $acl_user, $acl_level){ 202 $acl_config = join("",file(DOKU_CONF.'acl.auth.php')); 203 204 // max level for pagenames is edit 205 if(strpos($acl_scope,'*') === false) { 206 if($acl_level > AUTH_EDIT) $acl_level = AUTH_EDIT; 207 } 208 209 $new_acl = "$acl_scope\t$acl_user\t$acl_level\n"; 210 211 $new_config = $acl_config.$new_acl; 212 213 return io_saveFile(DOKU_CONF.'acl.auth.php', $new_config); 214 } 215 216 /** 217 * remove acl-entry from conf/acl.auth.php 218 * 219 * @author Frank Schubert <frank@schokilade.de> 220 */ 221 function admin_acl_del($acl_scope, $acl_user){ 222 $acl_config = file(DOKU_CONF.'acl.auth.php'); 223 224 $acl_pattern = '^'.preg_quote($acl_scope,'/').'\s+'.$acl_user.'\s+[0-8].*$'; 225 226 // save all non!-matching #FIXME invert is available from 4.2.0 only! 227 $new_config = preg_grep("/$acl_pattern/", $acl_config, PREG_GREP_INVERT); 228 229 return io_saveFile(DOKU_CONF.'acl.auth.php', join('',$new_config)); 230 } 231 232 // --- HTML OUTPUT FUNCTIONS BELOW --- // 233 234 /** 235 * print tablerows with the current permissions for one id 236 * 237 * @author Frank Schubert <frank@schokilade.de> 238 * @author Andreas Gohr <andi@splitbrain.org> 239 */ 240 function admin_acl_html_dropdown($id){ 241 $cur = $id; 242 $ret = ''; 243 $opt = array(); 244 245 //prepare all options 246 247 // current page 248 $opt[] = array('key'=> $id, 'val'=> $id.' ('.$this->lang['page'].')'); 249 250 // additional namespaces 251 while(($id=getNS($id)) !== false){ 252 $opt[] = array('key'=> $id.':*', 'val'=> $id.':* ('.$this->lang['namespace'].')'); 253 } 254 255 // the top namespace 256 $opt[] = array('key'=> '*', 'val'=> '* ('.$this->lang['namespace'].')'); 257 258 // set sel on second entry (current namespace) 259 $opt[1]['sel'] = ' selected="selected"'; 260 261 // flip options 262 $opt = array_reverse($opt); 263 264 // create HTML 265 $att = array( 'name' => 'acl_scope', 266 'class' => 'edit', 267 'title' => $this->lang['page'].'/'.$this->lang['namespace']); 268 $ret .= '<select '.html_attbuild($att).'>'; 269 foreach($opt as $o){ 270 $ret .= '<option value="'.$o['key'].'"'.$o['sel'].'>'.$o['val'].'</option>'; 271 } 272 $ret .= '</select>'; 273 274 return $ret; 275 } 276 277 /** 278 * print tablerows with the current permissions for one id 279 * 280 * @author Frank Schubert <frank@schokilade.de> 281 * @author Andreas Gohr <andi@splitbrain.org> 282 */ 283 function admin_acl_html_new(){ 284 global $ID; 285 global $lang; 286 287 // table headers 288 ptln('<tr>',2); 289 ptln(' <th class="leftalign" colspan="3">'.$this->lang['acl_new'].'</th>',2); 290 ptln('</tr>',2); 291 292 ptln('<tr>',2); 293 294 ptln('<td class="centeralign" colspan="3">',4); 295 296 ptln(' <form method="post" action="'.wl($ID).'"><div class="no">',4); 297 ptln(' <input type="hidden" name="do" value="admin" />',4); 298 ptln(' <input type="hidden" name="page" value="acl" />',4); 299 ptln(' <input type="hidden" name="acl_cmd" value="save" />',4); 300 formSecurityToken(); 301 302 //scope select 303 ptln($this->lang['acl_perms'],4); 304 ptln($this->admin_acl_html_dropdown($ID),4); 305 306 $att = array( 'name' => 'acl_type', 307 'class' => 'edit', 308 'title' => $this->lang['acl_user'].'/'.$this->lang['acl_group']); 309 ptln(' <select '.html_attbuild($att).'>',4); 310 ptln(' <option value="@">'.$this->lang['acl_group'].'</option>',4); 311 ptln(' <option value="">'.$this->lang['acl_user'].'</option>',4); 312 ptln(' </select>',4); 313 314 $att = array( 'name' => 'acl_user', 315 'type' => 'text', 316 'class' => 'edit', 317 'title' => $this->lang['acl_user'].'/'.$this->lang['acl_group']); 318 ptln(' <input '.html_attbuild($att).' />',4); 319 ptln(' <br />'); 320 321 ptln( $this->admin_acl_html_checkboxes(0,false),8); 322 323 ptln(' <input type="submit" class="button" value="'.$lang['btn_save'].'" />',4); 324 ptln(' </div></form>'); 325 ptln('</td>',4); 326 ptln('</tr>',2); 327 } 328 329 /** 330 * print tablerows with the current permissions for one id 331 * 332 * @author Frank Schubert <frank@schokilade.de> 333 * @author Andreas Gohr <andi@splitbrain.org> 334 */ 335 function admin_acl_html_current($id,$permissions){ 336 global $lang; 337 global $ID; 338 339 //is it a page? 340 if(substr($id,-1) == '*'){ 341 $ispage = false; 342 }else{ 343 $ispage = true; 344 } 345 346 // table headers 347 ptln(' <tr>'); 348 ptln(' <th class="leftalign" colspan="3">'); 349 ptln($this->lang['acl_perms'],6); 350 if($ispage){ 351 ptln($this->lang['page'],6); 352 }else{ 353 ptln($this->lang['namespace'],6); 354 } 355 ptln('<em>'.$id.'</em>',6); 356 ptln(' </th>'); 357 ptln(' </tr>'); 358 359 sort($permissions); 360 361 foreach ($permissions as $conf){ 362 //userfriendly group/user display 363 $conf['name'] = rawurldecode($conf['name']); 364 if(substr($conf['name'],0,1)=="@"){ 365 $group = $this->lang['acl_group']; 366 $name = substr($conf['name'],1); 367 $type = '@'; 368 }else{ 369 $group = $this->lang['acl_user']; 370 $name = $conf['name']; 371 $type = ''; 372 } 373 374 ptln('<tr>',2); 375 ptln('<td class="leftalign">'.htmlspecialchars($group.' '.$name).'</td>',4); 376 377 // update form 378 ptln('<td class="centeralign">',4); 379 ptln(' <form method="post" action="'.wl($ID).'"><div class="no">',4); 380 formSecurityToken(); 381 ptln(' <input type="hidden" name="do" value="admin" />',4); 382 ptln(' <input type="hidden" name="page" value="acl" />',4); 383 ptln(' <input type="hidden" name="acl_cmd" value="save" />',4); 384 ptln(' <input type="hidden" name="acl_scope" value="'.formtext($id).'" />',4); 385 ptln(' <input type="hidden" name="acl_type" value="'.$type.'" />',4); 386 ptln(' <input type="hidden" name="acl_user" value="'.formtext($name).'" />',4); 387 ptln( $this->admin_acl_html_checkboxes($conf['perm'],$ispage),8); 388 ptln(' <input type="submit" class="button" value="'.$lang['btn_update'].'" />',4); 389 ptln(' </div></form>'); 390 ptln('</td>',4); 391 392 393 // deletion form 394 395 $ask = $lang['del_confirm'].'\\n'; 396 $ask .= $id.' '.$conf['name'].' '.$conf['perm']; 397 ptln('<td class="centeralign">',4); 398 ptln(' <form method="post" action="'.wl($ID).'" onsubmit="return confirm(\''.str_replace('\\\\n','\\n',addslashes($ask)).'\')"><div class="no">',4); 399 formSecurityToken(); 400 ptln(' <input type="hidden" name="do" value="admin" />',4); 401 ptln(' <input type="hidden" name="page" value="acl" />',4); 402 ptln(' <input type="hidden" name="acl_cmd" value="delete" />',4); 403 ptln(' <input type="hidden" name="acl_scope" value="'.formtext($id).'" />',4); 404 ptln(' <input type="hidden" name="acl_type" value="'.$type.'" />',4); 405 ptln(' <input type="hidden" name="acl_user" value="'.formtext($name).'" />',4); 406 ptln(' <input type="submit" class="button" value="'.$lang['btn_delete'].'" />',4); 407 ptln(' </div></form>',4); 408 ptln('</td>',4); 409 410 ptln('</tr>',2); 411 } 412 413 } 414 415 416 /** 417 * print the permission checkboxes 418 * 419 * @author Frank Schubert <frank@schokilade.de> 420 * @author Andreas Gohr <andi@splitbrain.org> 421 */ 422 function admin_acl_html_checkboxes($setperm,$ispage){ 423 global $lang; 424 425 static $label = 0; //number labels 426 $ret = ''; 427 428 foreach(array(AUTH_READ,AUTH_EDIT,AUTH_CREATE,AUTH_UPLOAD,AUTH_DELETE) as $perm){ 429 $label += 1; 430 431 //general checkbox attributes 432 $atts = array( 'type' => 'checkbox', 433 'id' => 'pbox'.$label, 434 'name' => 'acl_perm[]', 435 'value' => $perm ); 436 //dynamic attributes 437 if($setperm >= $perm) $atts['checked'] = 'checked'; 438 # if($perm > AUTH_READ) $atts['onchange'] = #FIXME JS to autoadd lower perms 439 if($ispage && $perm > AUTH_EDIT) $atts['disabled'] = 'disabled'; 440 441 //build code 442 $ret .= '<label for="pbox'.$label.'" title="'.$this->lang['acl_perm'.$perm].'">'; 443 $ret .= '<input '.html_attbuild($atts).' />'; 444 $ret .= $this->lang['acl_perm'.$perm]; 445 $ret .= "</label>\n"; 446 } 447 return $ret; 448 } 449 450} 451