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