1<?php 2/** 3 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 4 * @author Etienne MELEARD <etienne.meleard@free.fr> 5 */ 6 7// must be run within Dokuwiki 8if(!defined('DOKU_INC')) die(); 9 10if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC.'lib/plugins/'); 11 12require_once(DOKU_PLUGIN.'admin.php'); 13 14define('ACLAUDITOR_SCENARIODIR', DOKU_CONF.'aclauditor_scenarios'); 15 16class admin_plugin_aclauditor extends DokuWiki_Admin_Plugin { 17 var $mode = 'single'; 18 19 var $data = null; 20 21 var $error = ''; 22 23 var $test = null; 24 var $who = null; 25 var $what = null; 26 27 var $scenario = ''; 28 var $scenariofn = ''; 29 var $scenarios = array(); 30 31 function getInfo() { 32 return array( 33 'author' => 'Etienne MELEARD', 34 'email' => 'etienne.meleard@free.fr', 35 'date' => @file_get_contents(DOKU_PLUGIN.'aclauditor/VERSION'), 36 'name' => 'ACL Auditor Admin Plugin', 37 'desc' => 'Functions to get info about user /group ACLs of a wiki page', 38 'url' => 'http://dokuwiki.org/plugin:aclauditor', 39 ); 40 } 41 42 function getMenuSort() { return 200; } 43 function getMenuText($language) { return $this->getLang('menu'); } 44 function forAdminOnly() { return false; } 45 46 function handle() { 47 global $ID; 48 49 $helper = $this->loadHelper('aclauditor', true); 50 if(!$helper) return null; 51 52 $this->mode = (isset($_GET['aclaudit_mode']) && in_array($_GET['aclaudit_mode'], array('single', 'scenario'))) ? $_GET['aclaudit_mode'] : 'single'; 53 54 if($this->mode != 'scenario') { 55 $what = (isset($_REQUEST['aclaudit_what']) && !empty($_REQUEST['aclaudit_what'])) ? $_REQUEST['aclaudit_what'] : null; 56 $this->what = preg_match('`^(.*[^:])(\:\*)$`', $what, $m) ? cleanID($m[1]).$m[2] : $what; 57 if(substr($this->what, -1) == ':') $this->what .= '*'; 58 $this->who = (isset($_REQUEST['aclaudit_who']) && !empty($_REQUEST['aclaudit_who'])) ? $_REQUEST['aclaudit_who'] : null; 59 60 if(isset($_REQUEST['aclaudit_testwho'])) { 61 $this->test = 'who'; 62 $this->data = $helper->getACL($this->who ? $this->who : $_SERVER['REMOTE_USER']); 63 }elseif(isset($_REQUEST['aclaudit_testwhat'])) { 64 $this->test = 'what'; 65 $this->data = $helper->resourceACLs($this->what ? $this->what : $ID); 66 }elseif(isset($_REQUEST['aclaudit_testboth'])) { 67 $this->test = 'both'; 68 $this->data = $helper->getACLon($this->who ? $this->who : $_SERVER['REMOTE_USER'], $this->what ? $this->what : $ID); 69 } 70 }else{ 71 if(!@is_dir(ACLAUDITOR_SCENARIODIR)) mkdir(ACLAUDITOR_SCENARIODIR); 72 foreach(scandir(ACLAUDITOR_SCENARIODIR) as $i) { 73 if(!preg_match('`^[^\.].*\.csv$`', $i)) continue; 74 $desc = ''; 75 if($fp = fopen(ACLAUDITOR_SCENARIODIR.'/'.$i, 'r')) { 76 $l = fgets($fp); 77 if(preg_match('`^\s*"?\s*#\s*(.+)\s*"?,,,\s*$`', $l, $m)) $desc = str_replace('""', '"', $m[1]); 78 fclose($fp); 79 } 80 $this->scenarios[$i] = trim($desc, '"'); 81 } 82 83 $this->scenario = isset($_POST['aclaudit_scenario_content']) ? trim($_POST['aclaudit_scenario_content']) : null; 84 85 $fn = isset($_POST['aclaudit_scenario_save_filename']) ? trim($_POST['aclaudit_scenario_save_filename']) : null; 86 if(preg_match('`^[^/\.]+(\.csv)?$`', $fn, $m)) $this->scenariofn = $fn.($m[1] ? '' : '.csv'); 87 88 if(isset($_POST['aclaudit_scenario_load'])) { 89 $fn = isset($_POST['aclaudit_scenario_load_filename']) ? trim($_POST['aclaudit_scenario_load_filename']) : null; 90 if($fn && array_key_exists($fn, $this->scenarios)) $this->scenariofn = $fn; 91 92 if($this->scenariofn && @file_exists(ACLAUDITOR_SCENARIODIR.'/'.$this->scenariofn)) { 93 $this->scenario = @file_get_contents(ACLAUDITOR_SCENARIODIR.'/'.$this->scenariofn); 94 msg($this->getLang('scenario_load_success'), 1); 95 }else msg($this->getLang('scenario_load_badfile_error'), -1); 96 }elseif(isset($_POST['aclaudit_scenario_upload'])) { 97 if(isset($_FILES['aclaudit_scenario_upload_file']) && @is_uploaded_file($_FILES['aclaudit_scenario_upload_file']['tmp_name'])) { 98 $this->scenariofn = ''; 99 $this->scenario = @file_get_contents($_FILES['aclaudit_scenario_upload_file']['tmp_name']); 100 msg($this->getLang('scenario_upload_success'), 1); 101 }else msg($this->getLang('scenario_upload_error'), -1); 102 }elseif(isset($_POST['aclaudit_scenario_save'])) { 103 if($this->scenariofn) { 104 if($this->scenario) { 105 if($fp = fopen(ACLAUDITOR_SCENARIODIR.'/'.$this->scenariofn, 'w')) { 106 fwrite($fp, $this->scenario); 107 fclose($fp); 108 msg($this->getLang('scenario_save_success'), 1); 109 }else msg($this->getLang('scenario_save_failed_error'), -1); 110 }elseif(@file_exists(ACLAUDITOR_SCENARIODIR.'/'.$this->scenariofn)) { 111 if(unlink(ACLAUDITOR_SCENARIODIR.'/'.$this->scenariofn)) { 112 msg($this->getLang('scenario_rm_success'), 1); 113 $this->scenario = ''; 114 $this->scenariofn = ''; 115 }else msg($this->getLang('scenario_save_rmfailed_error'), -1); 116 } 117 }else msg($this->getLang('scenario_save_badfile_error'), -1); 118 }elseif(isset($_POST['aclaudit_scenario_download'])) { 119 if($this->scenario) { 120 header('Content-type: application/force-download'); 121 header('Content-Disposition: attachment; filename="'.($this->scenariofn ? $this->scenariofn : 'scenario_'.date('Ymd_His').'.csv').'"'); 122 header('Content-Length: '.strlen($this->scenario)); 123 echo $this->scenario; 124 exit(); 125 }else msg($this->getLang('scenario_download_nodata_error'), -1); 126 } 127 128 if(isset($_POST['aclaudit_scenario_run'])) { 129 if($this->scenario) { 130 $this->test = true; 131 $this->data = array(); 132 foreach(preg_split('`\s*\n\s*`', $this->scenario) as $l) { 133 if(preg_match('`^\s*"?\s*#`', $l)) continue; 134 135 list($id, $user, $group, $reqlvl) = array_map(create_function('$i', 'return trim(preg_match("`^\"(.+)\"$`", $i, $m) ? $m[1] : $i);'), preg_split('`\s*[,;]\s*`', $l)); 136 137 $this->data[$l] = array('valid' => false); 138 if(!$user && !$group) continue; 139 140 if(!$id) $id = '*'; 141 $this->data[$l]['id'] = $id; 142 143 $u = $user ? $user : 'dummy_aclauditor_user_'.uniqid().'_'.time(); 144 $this->data[$l]['user'] = $user; 145 146 $grps = array_filter(explode('|', (string)$group), create_function('$g', 'return (bool)strlen($g);')); 147 if($grps[0] == '*') { 148 global $auth; 149 if($auth && ($u = reset($auth->retrieveUsers(0, 1, array('user' => $u))))) { 150 $grps = $u['grps']; 151 }else $grps = array(); 152 } 153 if(!count($grps)) $grps[] = 'ALL'; 154 $this->data[$l]['group'] = $grps; 155 156 $law = '=='; 157 if(preg_match('`^(\=\=?|\<\=?|\>\=?|\!\=?)\s*([0-9]+)$`', $reqlvl, $m)) { 158 $law = $m[1]; 159 $reqlvl = (int)$m[2]; 160 }else $reqlvl = (int)$reqlvl; 161 if(!preg_match('`^[0-9]+$`', $reqlvl)) continue; 162 $this->data[$l]['law'] = $law; 163 $this->data[$l]['reqlvl'] = $reqlvl; 164 $this->data[$l]['required'] = ((($law != '==') && ($law != '=')) ? $law : '').$reqlvl; 165 166 $syslvl = auth_aclcheck($id, $u, $grps); 167 if(auth_isadmin($u, $grps)) $syslvl = 255; 168 $this->data[$l]['found'] = $syslvl; 169 170 $ok = false; 171 switch($law) { 172 case '<' : if($syslvl < $reqlvl) $ok = true; break; 173 case '<=' : if($syslvl <= $reqlvl) $ok = true; break; 174 case '>' : if($syslvl > $reqlvl) $ok = true; break; 175 case '>=' : if($syslvl >= $reqlvl) $ok = true; break; 176 case '!' : 177 case '!=' : if($syslvl != $reqlvl) $ok = true; break; 178 case '=' : 179 case '==' : 180 default : if($syslvl == $reqlvl) $ok = true; $law = ''; 181 } 182 $this->data[$l]['valid'] = true; 183 $this->data[$l]['pass'] = $ok; 184 $this->data[$l]['explanations'] = $ok ? null : $helper->resourceACLs($id); 185 186 if(!$ok) $this->test = false; 187 } 188 }else msg($this->getLang('scenario_download_nodata_error'), -1); 189 } 190 } 191 } 192 193 function html() { 194 global $ID; 195 196 if($this->mode != 'scenario') { 197 ptln('<h1>'.$this->getLang('listform').'</h1>'); 198 ptln('<a href="'.wl($ID, array('do' => 'admin', 'page' => 'aclauditor', 'aclaudit_mode' => 'scenario')).'">'.$this->getLang('toscenario').'</a><br /><br />'); 199 ptln('<form id="aclauditor_form" method="post" action="'.wl($ID, array('do' => 'admin', 'page' => 'aclauditor', 'aclaudit_mode' => 'single')).'">'); 200 ptln(' '.$this->getLang('who').' : <input type="text" name="aclaudit_who" value="'.($this->test ? $this->who : '').'" /> <input type="submit" name="aclaudit_testwho" value="'.$this->getLang('testwho').'" /><br />'); 201 ptln(' '.$this->getLang('what').' : <input type="text" name="aclaudit_what" value="'.($this->test ? $this->what : '').'" /> <input type="submit" name="aclaudit_testwhat" value="'.$this->getLang('testwhat').'" /><br />'); 202 ptln(' <input type="submit" name="aclaudit_testboth" value="'.$this->getLang('testboth').'" />'); 203 ptln('</form>'); 204 205 if($this->test) { 206 ptln('<h1>'.$this->getLang('list').'</h1>'); 207 ptln('<div id="aclauditor_list">'); 208 switch($this->test) { 209 case 'who' : 210 ptln('<div class="listtitle">'.$this->getLang('listwho').' : "'.($this->who ? $this->who : $_SERVER['REMOTE_USER']).'"'.'</div>'); 211 foreach($this->data as $id => $list) { 212 ptln('<table class="permset">'); 213 ptln(' <caption>'.$this->getLang('permwhat').' "'.$id.'" :</caption>'); 214 ptln(' <tr><th>'.$this->getLang('who').'</th><th>'.$this->getLang('what').'</th><th>'.$this->getLang('perm').'</th></tr>'); 215 foreach($list as $rule => $dfn) { 216 if($rule == '_lvl') continue; 217 ptln(' <tr><td>'.$dfn['who'].'</td><td>'.$dfn['what'].'</td><td>'.$this->getLang('perm'.$dfn['lvl']).'</td></tr>'); 218 } 219 ptln(' <tr><td colspan="2" class="permset_final_perm">'.$this->getLang('resource_final_perm').' :</td><td>'.$this->getLang('perm'.$list['_lvl']).'</td>'); 220 ptln('</table>'); 221 } 222 break; 223 case 'what' : 224 ptln('<div class="listtitle">'.$this->getLang('listwhat').' : "'.($this->what ? $this->what : $ID).'"'.'</div>'); 225 foreach($this->data as $who => $list) { 226 ptln('<table class="permset">'); 227 ptln(' <caption>'.$this->getLang('permwho').' "'.$who.'" :</caption>'); 228 ptln(' <tr><th>'.$this->getLang('who').'</th><th>'.$this->getLang('what').'</th><th>'.$this->getLang('perm').'</th></tr>'); 229 foreach($list as $rule => $dfn) { 230 if($rule == '_lvl') continue; 231 ptln(' <tr><td>'.$dfn['who'].'</td><td>'.$dfn['what'].'</td><td>'.$this->getLang('perm'.$dfn['lvl']).'</td></tr>'); 232 } 233 ptln(' <tr><td colspan="2" class="permset_final_perm">'.$this->getLang('resource_final_perm').' :</td><td>'.$this->getLang('perm'.$list['_lvl']).'</td>'); 234 ptln('</table>'); 235 } 236 break; 237 case 'both' : 238 ptln('<div class="listtitle">'.$this->getLang('listbothwho').' "'.($this->who ? $this->who : $_SERVER['REMOTE_USER']).'" '.$this->getLang('listbothwhat').' "'.($this->what ? $this->what : $ID).'"'.'</div>'); 239 foreach($this->data as $id => $list) { 240 if($id == '_lvl') continue; 241 if($id == '_specificlvl') continue; 242 ptln('<div class="pathpermset">'); 243 ptln(' <div class="pathpermsettitle">'.$this->getLang('permbothwhat').' "'.$id.'" :</div>'); 244 ptln(' <table class="permset">'); 245 ptln(' <tr><th>'.$this->getLang('who').'</th><th>'.$this->getLang('what').'</th><th>'.$this->getLang('perm').'</th></tr>'); 246 $data = $this->data; 247 foreach($data as $tid => $tlist) { 248 foreach($tlist as $who => $slist) { 249 if($who == '_lvl') continue; 250 if($who == '_specificlvl') continue; 251 foreach($slist as $rule => $dfn) { 252 if($rule == '_lvl') continue; 253 if($rule == '_specificlvl') continue; 254 ptln(' <tr><td>'.$dfn['who'].'</td><td>'.$dfn['what'].'</td><td>'.$this->getLang('perm'.$dfn['lvl']).'</td></tr>'); 255 } 256 } 257 if($tid == $id) break; 258 } 259 if(count($list) <= 2) ptln('<br />'); 260 $perm = ($list['_specificlvl'] >= 0) ? $list['_specificlvl'] : $list['_lvl']; 261 ptln(' <tr><td colspan="2" class="permset_final_perm">'.$this->getLang('bothresource_final_perm').' '.((($list['_specificlvl'] >= 0) && ($id != '*')) ? '<span class="specific">('.$this->getLang('specific').')</span> ' : '').':</td><td>'.$this->getLang('perm'.$perm).'</td>'); 262 ptln(' </table>'); 263 ptln('</div>'); 264 } 265 $perm = ($this->data['_specificlvl'] >= 0) ? $this->data['_specificlvl'] : $this->data['_lvl']; 266 ptln(' <div class="final_perm">'.$this->getLang('resource_final_perm').' '.(($this->data['_specificlvl'] >= 0) ? '<span class="specific">('.$this->getLang('specific').')</span> ' : '').': '.$this->getLang('perm'.$perm).'</div>'); 267 break; 268 } 269 ptln('</div>'); 270 } 271 }else{ 272 ptln('<h1>'.$this->getLang('scenarioform').'</h1>'); 273 ptln('<a href="'.wl($ID, array('do' => 'admin', 'page' => 'aclauditor', 'aclaudit_mode' => 'single')).'">'.$this->getLang('tosingle').'</a><br /><br />'); 274 ptln('<form id="aclauditor_scnform" method="post" action="'.wl($ID, array('do' => 'admin', 'page' => 'aclauditor', 'aclaudit_mode' => 'scenario')).'" enctype="multipart/form-data">'); 275 ptln(' '.$this->getLang('scenario_load').' : <select name="aclaudit_scenario_load_filename">'); 276 ptln(' <option value="">'.$this->getLang('choose').'</option>'); 277 foreach($this->scenarios as $fn => $d) { 278 ptln(' <option '.(($this->scenariofn && ($this->scenariofn == $fn)) ? 'selected="selected"' : '').' value="'.$fn.'">'.($d ? $d.' ('.$fn.')' : $fn).'</option>'); 279 } 280 ptln(' </select> <input type="submit" name="aclaudit_scenario_load" value="'.$this->getLang('load').'" /><br />'); 281 ptln(' '.$this->getLang('scenario_upload').' : <input type="file" name="aclaudit_scenario_upload_file" /> <input type="submit" name="aclaudit_scenario_upload" value="'.$this->getLang('upload').'" /><br />'); 282 ptln(' <textarea name="aclaudit_scenario_content">'.$this->scenario.'</textarea><br />'); 283 ptln(' '.$this->getLang('scenario_save').' : <input type="text" name="aclaudit_scenario_save_filename" value="'.$this->scenariofn.'" /> <input type="submit" name="aclaudit_scenario_save" value="'.$this->getLang('save').'" /><br />'); 284 ptln(' '.$this->getLang('scenario_download').' : <input type="submit" name="aclaudit_scenario_download" value="'.$this->getLang('download').'" /><br />'); 285 ptln(' <input class="runscn" type="submit" name="aclaudit_scenario_run" value="'.$this->getLang('run').'" />'); 286 ptln('</form>'); 287 288 if(!is_null($this->data)) { 289 ptln('<h1>'.$this->getLang('scenario_results').'</h1>'); 290 ptln('<div id="aclauditor_scenario_main">'.$this->getLang($this->test ? 'scenario_passed' : 'scenario_failed').'</div>'); 291 if(!$this->test) { 292 ptln('<table id="aclauditor_scenario_details">'); 293 ptln(' <tr>'); 294 ptln(' <th>'.$this->getLang('scenario_rule').'</th>'); 295 ptln(' <th>'.$this->getLang('scenario_testid').'</th>'); 296 ptln(' <th>'.$this->getLang('scenario_teston').'</th>'); 297 ptln(' <th>'.$this->getLang('scenario_required').'</th>'); 298 ptln(' <th>'.$this->getLang('scenario_found').'</th>'); 299 ptln(' </tr>'); 300 foreach($this->data as $rule => $r) { 301 if($r['pass']) continue; 302 if(!$r['valid']) { 303 ptln(' <tr>'); 304 ptln(' <td class="rule">'.$rule.'</td>'); 305 ptln(' <td colspan="4">'.$this->getLang('invalid_rule').'</td>'); 306 ptln(' </tr>'); 307 continue; 308 } 309 $on = $r['user'] ? $r['user'] : ''; 310 if($on && $r['group']) $on .= '<br />'; 311 $on .= $r['group'] ? implode(', ', array_map(create_function('$g', 'return "@".$g;'), $r['group'])) : ''; 312 ptln(' <tr>'); 313 ptln(' <td class="rule">'.$rule.'</td>'); 314 ptln(' <td>'.$r['id'].'</td>'); 315 ptln(' <td>'.$on.'</td>'); 316 $lvlstr = $this->getLang('perm'.$r['reqlvl']); 317 switch($r['law']) { 318 case '<' : $l = 'lt'; break; 319 case '<=' : $l = 'lte'; break; 320 case '>' : $l = 'gt'; break; 321 case '>=' : $l = 'gte'; break; 322 case '!' : 323 case '!=' : $l = 'ne'; break; 324 case '=' : 325 case '==' : 326 default : $l = 'eq'; break; 327 } 328 ptln(' <td class="required">'.$r['required'].($lvlstr ? ' ('.$this->getLang('req_'.$l).' '.$lvlstr.')' : '').'</td>'); 329 ptln(' <td class="found">'.$r['found'].' ('.$this->getLang('perm'.$r['found']).')</td>'); 330 ptln(' </tr>'); 331 332 ptln(' <tr class="explanations">'); 333 ptln(' <td colspan="5"><pre>'); 334 foreach($r['explanations'] as $who => $list) { 335 if(substr($who, 0, 1) == '_') continue; 336 $rules = array(); 337 foreach($list as $crule => $parts) { 338 if(substr($id, 0, 1) == '_') continue; 339 if(($r['user'] && $parts['who'] == $r['user']) || ($r['group'] && in_array($parts['who'], array_map(create_function('$g', 'return "@".$g;'), $r['group'])))) { 340 $rules[] = ' '.$crule.' => '.$parts['lvl']; 341 } 342 } 343 if(count($rules)) { 344 ptln($this->getLang('permwho').' : '.$who); 345 foreach($rules as $d) ptln($d); 346 } 347 } 348 ptln('</pre><a target="_blank" href="'.wl($ID, array('do' => 'admin', 'page' => 'aclauditor', 'aclaudit_mode' => 'single', 'aclaudit_who' => $r['user'] ? $r['user'] : '@'.reset($r['group']), 'aclaudit_what' => $r['id'], 'aclaudit_testboth' => 1)).'">'.$this->getLang('more_details').'</a></td>'); 349 ptln(' </tr>'); 350 } 351 ptln('</table>'); 352 } 353 } 354 } 355 } 356} 357