<?php
/**
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Etienne MELEARD <etienne.meleard@free.fr>
 */

// must be run within Dokuwiki
if(!defined('DOKU_INC')) die();

if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC.'lib/plugins/');

require_once(DOKU_PLUGIN.'admin.php');

define('ACLAUDITOR_SCENARIODIR', DOKU_CONF.'aclauditor_scenarios');

class admin_plugin_aclauditor extends DokuWiki_Admin_Plugin {
	var $mode = 'single';
	
	var $data = null;
	
	var $error = '';
	
	var $test = null;
	var $who = null;
	var $what = null;
	
	var $scenario = '';
	var $scenariofn = '';
	var $scenarios = array();
	
	function getInfo() {
		return array(
			'author' => 'Etienne MELEARD',
			'email'  => 'etienne.meleard@free.fr',
			'date'   => @file_get_contents(DOKU_PLUGIN.'aclauditor/VERSION'),
			'name'   => 'ACL Auditor Admin Plugin',
			'desc'   => 'Functions to get info about user /group ACLs of a wiki page',
			'url'    => 'http://dokuwiki.org/plugin:aclauditor',
		);
	}
	
	function getMenuSort() { return 200; }
	function getMenuText($language) { return $this->getLang('menu'); }
	function forAdminOnly() { return false; }
	
	function handle() {
		global $ID;
		
		$helper = $this->loadHelper('aclauditor', true);
		if(!$helper) return null;
		
		$this->mode = (isset($_GET['aclaudit_mode']) && in_array($_GET['aclaudit_mode'], array('single', 'scenario'))) ? $_GET['aclaudit_mode'] : 'single';
		
		if($this->mode != 'scenario') {
			$what = (isset($_REQUEST['aclaudit_what']) && !empty($_REQUEST['aclaudit_what'])) ? $_REQUEST['aclaudit_what'] : null;
			$this->what = preg_match('`^(.*[^:])(\:\*)$`', $what, $m) ? cleanID($m[1]).$m[2] : $what;
			if(substr($this->what, -1) == ':') $this->what .= '*';
			$this->who = (isset($_REQUEST['aclaudit_who']) && !empty($_REQUEST['aclaudit_who'])) ? $_REQUEST['aclaudit_who'] : null;
			
			if(isset($_REQUEST['aclaudit_testwho'])) {
				$this->test = 'who';
				$this->data = $helper->getACL($this->who ? $this->who : $_SERVER['REMOTE_USER']);
			}elseif(isset($_REQUEST['aclaudit_testwhat'])) {
				$this->test = 'what';
				$this->data = $helper->resourceACLs($this->what ? $this->what : $ID);
			}elseif(isset($_REQUEST['aclaudit_testboth'])) {
				$this->test = 'both';
				$this->data = $helper->getACLon($this->who ? $this->who : $_SERVER['REMOTE_USER'], $this->what ? $this->what : $ID);
			}
		}else{
			if(!@is_dir(ACLAUDITOR_SCENARIODIR)) mkdir(ACLAUDITOR_SCENARIODIR);
			foreach(scandir(ACLAUDITOR_SCENARIODIR) as $i) {
				if(!preg_match('`^[^\.].*\.csv$`', $i)) continue;
				$desc = '';
				if($fp = fopen(ACLAUDITOR_SCENARIODIR.'/'.$i, 'r')) {
					$l = fgets($fp);
					if(preg_match('`^\s*"?\s*#\s*(.+)\s*"?,,,\s*$`', $l, $m)) $desc = str_replace('""', '"', $m[1]);
					fclose($fp);
				}
				$this->scenarios[$i] = trim($desc, '"');
			}
			
			$this->scenario = isset($_POST['aclaudit_scenario_content']) ? trim($_POST['aclaudit_scenario_content']) : null;
			
			$fn = isset($_POST['aclaudit_scenario_save_filename']) ? trim($_POST['aclaudit_scenario_save_filename']) : null;
			if(preg_match('`^[^/\.]+(\.csv)?$`', $fn, $m)) $this->scenariofn = $fn.($m[1] ? '' : '.csv');
							
			if(isset($_POST['aclaudit_scenario_load'])) {
				$fn = isset($_POST['aclaudit_scenario_load_filename']) ? trim($_POST['aclaudit_scenario_load_filename']) : null;
				if($fn && array_key_exists($fn, $this->scenarios)) $this->scenariofn = $fn;
				
				if($this->scenariofn && @file_exists(ACLAUDITOR_SCENARIODIR.'/'.$this->scenariofn)) {
					$this->scenario = @file_get_contents(ACLAUDITOR_SCENARIODIR.'/'.$this->scenariofn);
					msg($this->getLang('scenario_load_success'), 1);
				}else msg($this->getLang('scenario_load_badfile_error'), -1);
			}elseif(isset($_POST['aclaudit_scenario_upload'])) {
				if(isset($_FILES['aclaudit_scenario_upload_file']) && @is_uploaded_file($_FILES['aclaudit_scenario_upload_file']['tmp_name'])) {
					$this->scenariofn = '';
					$this->scenario = @file_get_contents($_FILES['aclaudit_scenario_upload_file']['tmp_name']);
					msg($this->getLang('scenario_upload_success'), 1);
				}else msg($this->getLang('scenario_upload_error'), -1);
			}elseif(isset($_POST['aclaudit_scenario_save'])) {
				if($this->scenariofn) {
					if($this->scenario) {
						if($fp = fopen(ACLAUDITOR_SCENARIODIR.'/'.$this->scenariofn, 'w')) {
							fwrite($fp, $this->scenario);
							fclose($fp);
							msg($this->getLang('scenario_save_success'), 1);
						}else msg($this->getLang('scenario_save_failed_error'), -1);
					}elseif(@file_exists(ACLAUDITOR_SCENARIODIR.'/'.$this->scenariofn)) {
						if(unlink(ACLAUDITOR_SCENARIODIR.'/'.$this->scenariofn)) {
							msg($this->getLang('scenario_rm_success'), 1);
							$this->scenario = '';
							$this->scenariofn = '';
						}else msg($this->getLang('scenario_save_rmfailed_error'), -1);
					}
				}else msg($this->getLang('scenario_save_badfile_error'), -1);
			}elseif(isset($_POST['aclaudit_scenario_download'])) {
				if($this->scenario) {
					header('Content-type: application/force-download');
					header('Content-Disposition: attachment; filename="'.($this->scenariofn ? $this->scenariofn : 'scenario_'.date('Ymd_His').'.csv').'"');
					header('Content-Length: '.strlen($this->scenario));
					echo $this->scenario;
					exit();
				}else msg($this->getLang('scenario_download_nodata_error'), -1);
			}
			
			if(isset($_POST['aclaudit_scenario_run'])) {
				if($this->scenario) {
					$this->test = true;
					$this->data = array();
					foreach(preg_split('`\s*\n\s*`', $this->scenario) as $l) {
						if(preg_match('`^\s*"?\s*#`', $l)) continue;
						
						list($id, $user, $group, $reqlvl) = array_map(create_function('$i', 'return trim(preg_match("`^\"(.+)\"$`", $i, $m) ? $m[1] : $i);'), preg_split('`\s*[,;]\s*`', $l));
						
						$this->data[$l] = array('valid' => false);
						if(!$user && !$group) continue;
						
						if(!$id) $id = '*';
						$this->data[$l]['id'] = $id;
						
						$u = $user ? $user : 'dummy_aclauditor_user_'.uniqid().'_'.time();
						$this->data[$l]['user'] = $user;
						
						$grps = array_filter(explode('|', (string)$group), create_function('$g', 'return (bool)strlen($g);'));
						if($grps[0] == '*') {
							global $auth;
							if($auth && ($u = reset($auth->retrieveUsers(0, 1, array('user' => $u))))) {
								$grps = $u['grps'];
							}else $grps = array();
						}
						if(!count($grps)) $grps[] = 'ALL';
						$this->data[$l]['group'] = $grps;
						
						$law = '==';
						if(preg_match('`^(\=\=?|\<\=?|\>\=?|\!\=?)\s*([0-9]+)$`', $reqlvl, $m)) {
							$law = $m[1];
							$reqlvl = (int)$m[2];
						}else $reqlvl = (int)$reqlvl;
						if(!preg_match('`^[0-9]+$`', $reqlvl)) continue;
						$this->data[$l]['law'] = $law;
						$this->data[$l]['reqlvl'] = $reqlvl;
						$this->data[$l]['required'] = ((($law != '==') && ($law != '=')) ? $law : '').$reqlvl;
						
						$syslvl = auth_aclcheck($id, $u, $grps);
						if(auth_isadmin($u, $grps)) $syslvl = 255;
						$this->data[$l]['found'] = $syslvl;
						
						$ok = false;
						switch($law) {
							case '<' : if($syslvl < $reqlvl) $ok = true; break;
							case '<=' : if($syslvl <= $reqlvl) $ok = true; break;
							case '>' : if($syslvl > $reqlvl) $ok = true; break;
							case '>=' : if($syslvl >= $reqlvl) $ok = true; break;
							case '!' :
							case '!=' : if($syslvl != $reqlvl) $ok = true; break;
							case '=' :
							case '==' :
							default : if($syslvl == $reqlvl) $ok = true; $law = '';
						}
						$this->data[$l]['valid'] = true;
						$this->data[$l]['pass'] = $ok;
						$this->data[$l]['explanations'] = $ok ? null : $helper->resourceACLs($id);
						
						if(!$ok) $this->test = false;
					}
				}else msg($this->getLang('scenario_download_nodata_error'), -1);
			}
		}
	}
	
	function html() {
		global $ID;
		
		if($this->mode != 'scenario') {
			ptln('<h1>'.$this->getLang('listform').'</h1>');
			ptln('<a href="'.wl($ID, array('do' => 'admin', 'page' => 'aclauditor', 'aclaudit_mode' => 'scenario')).'">'.$this->getLang('toscenario').'</a><br /><br />');
			ptln('<form id="aclauditor_form" method="post" action="'.wl($ID, array('do' => 'admin', 'page' => 'aclauditor', 'aclaudit_mode' => 'single')).'">');
			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 />');
			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 />');
			ptln('	<input type="submit" name="aclaudit_testboth" value="'.$this->getLang('testboth').'" />');
			ptln('</form>');
			
			if($this->test) {
				ptln('<h1>'.$this->getLang('list').'</h1>');
				ptln('<div id="aclauditor_list">');
				switch($this->test) {
					case 'who' :
						ptln('<div class="listtitle">'.$this->getLang('listwho').' : "'.($this->who ? $this->who : $_SERVER['REMOTE_USER']).'"'.'</div>');
						foreach($this->data as $id => $list) {
							ptln('<table class="permset">');
							ptln('	<caption>'.$this->getLang('permwhat').' "'.$id.'" :</caption>');
							ptln('	<tr><th>'.$this->getLang('who').'</th><th>'.$this->getLang('what').'</th><th>'.$this->getLang('perm').'</th></tr>');
							foreach($list as $rule => $dfn) {
								if($rule == '_lvl') continue;
								ptln('	<tr><td>'.$dfn['who'].'</td><td>'.$dfn['what'].'</td><td>'.$this->getLang('perm'.$dfn['lvl']).'</td></tr>');
							}
							ptln('	<tr><td colspan="2" class="permset_final_perm">'.$this->getLang('resource_final_perm').' :</td><td>'.$this->getLang('perm'.$list['_lvl']).'</td>');
							ptln('</table>');
						}
						break;
					case 'what' :
						ptln('<div class="listtitle">'.$this->getLang('listwhat').' : "'.($this->what ? $this->what : $ID).'"'.'</div>');
						foreach($this->data as $who => $list) {
							ptln('<table class="permset">');
							ptln('	<caption>'.$this->getLang('permwho').' "'.$who.'" :</caption>');
							ptln('	<tr><th>'.$this->getLang('who').'</th><th>'.$this->getLang('what').'</th><th>'.$this->getLang('perm').'</th></tr>');
							foreach($list as $rule => $dfn) {
								if($rule == '_lvl') continue;
								ptln('	<tr><td>'.$dfn['who'].'</td><td>'.$dfn['what'].'</td><td>'.$this->getLang('perm'.$dfn['lvl']).'</td></tr>');
							}
							ptln('	<tr><td colspan="2" class="permset_final_perm">'.$this->getLang('resource_final_perm').' :</td><td>'.$this->getLang('perm'.$list['_lvl']).'</td>');
							ptln('</table>');
						}
						break;
					case 'both' :
						ptln('<div class="listtitle">'.$this->getLang('listbothwho').' "'.($this->who ? $this->who : $_SERVER['REMOTE_USER']).'" '.$this->getLang('listbothwhat').' "'.($this->what ? $this->what : $ID).'"'.'</div>');
						foreach($this->data as $id => $list) {
							if($id == '_lvl') continue;
							if($id == '_specificlvl') continue;
							ptln('<div class="pathpermset">');
							ptln('	<div class="pathpermsettitle">'.$this->getLang('permbothwhat').' "'.$id.'" :</div>');
							ptln('	<table class="permset">');
							ptln('		<tr><th>'.$this->getLang('who').'</th><th>'.$this->getLang('what').'</th><th>'.$this->getLang('perm').'</th></tr>');
							$data = $this->data;
							foreach($data as $tid => $tlist) {
								foreach($tlist as $who => $slist) {
									if($who == '_lvl') continue;
									if($who == '_specificlvl') continue;
									foreach($slist as $rule => $dfn) {
										if($rule == '_lvl') continue;
										if($rule == '_specificlvl') continue;
										ptln('		<tr><td>'.$dfn['who'].'</td><td>'.$dfn['what'].'</td><td>'.$this->getLang('perm'.$dfn['lvl']).'</td></tr>');
									}
								}
								if($tid == $id) break;
							}
							if(count($list) <= 2) ptln('<br />');
							$perm = ($list['_specificlvl'] >= 0) ? $list['_specificlvl'] : $list['_lvl'];
							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>');
							ptln('	</table>');
							ptln('</div>');
						}
						$perm = ($this->data['_specificlvl'] >= 0) ? $this->data['_specificlvl'] : $this->data['_lvl'];
						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>');
						break;
				}
				ptln('</div>');
			}
		}else{
			ptln('<h1>'.$this->getLang('scenarioform').'</h1>');
			ptln('<a href="'.wl($ID, array('do' => 'admin', 'page' => 'aclauditor', 'aclaudit_mode' => 'single')).'">'.$this->getLang('tosingle').'</a><br /><br />');
			ptln('<form id="aclauditor_scnform" method="post" action="'.wl($ID, array('do' => 'admin', 'page' => 'aclauditor', 'aclaudit_mode' => 'scenario')).'" enctype="multipart/form-data">');
			ptln('	'.$this->getLang('scenario_load').' : <select name="aclaudit_scenario_load_filename">');
			ptln('		<option value="">'.$this->getLang('choose').'</option>');
			foreach($this->scenarios as $fn => $d) {
				ptln('		<option '.(($this->scenariofn && ($this->scenariofn == $fn)) ? 'selected="selected"' : '').' value="'.$fn.'">'.($d ? $d.' ('.$fn.')' : $fn).'</option>');
			}
			ptln('	</select> <input type="submit" name="aclaudit_scenario_load" value="'.$this->getLang('load').'" /><br />');
			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 />');
			ptln('	<textarea name="aclaudit_scenario_content">'.$this->scenario.'</textarea><br />');
			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 />');
			ptln('	'.$this->getLang('scenario_download').' : <input type="submit" name="aclaudit_scenario_download" value="'.$this->getLang('download').'" /><br />');
			ptln('	<input class="runscn" type="submit" name="aclaudit_scenario_run" value="'.$this->getLang('run').'" />');
			ptln('</form>');
			
			if(!is_null($this->data)) {
				ptln('<h1>'.$this->getLang('scenario_results').'</h1>');
				ptln('<div id="aclauditor_scenario_main">'.$this->getLang($this->test ? 'scenario_passed' : 'scenario_failed').'</div>');
				if(!$this->test) {
					ptln('<table id="aclauditor_scenario_details">');
					ptln('	<tr>');
					ptln('		<th>'.$this->getLang('scenario_rule').'</th>');
					ptln('		<th>'.$this->getLang('scenario_testid').'</th>');
					ptln('		<th>'.$this->getLang('scenario_teston').'</th>');
					ptln('		<th>'.$this->getLang('scenario_required').'</th>');
					ptln('		<th>'.$this->getLang('scenario_found').'</th>');
					ptln('	</tr>');
					foreach($this->data as $rule => $r) {
						if($r['pass']) continue;
						if(!$r['valid']) {
							ptln('	<tr>');
							ptln('		<td class="rule">'.$rule.'</td>');
							ptln('		<td colspan="4">'.$this->getLang('invalid_rule').'</td>');
							ptln('	</tr>');
							continue;
						}
						$on = $r['user'] ? $r['user'] : '';
						if($on && $r['group']) $on .= '<br />';
						$on .= $r['group'] ? implode(', ', array_map(create_function('$g', 'return "@".$g;'), $r['group'])) : '';
						ptln('	<tr>');
						ptln('		<td class="rule">'.$rule.'</td>');
						ptln('		<td>'.$r['id'].'</td>');
						ptln('		<td>'.$on.'</td>');
						$lvlstr = $this->getLang('perm'.$r['reqlvl']);
						switch($r['law']) {
							case '<' : $l = 'lt'; break;
							case '<=' : $l = 'lte'; break;
							case '>' : $l = 'gt'; break;
							case '>=' : $l = 'gte'; break;
							case '!' :
							case '!=' : $l = 'ne'; break;
							case '=' :
							case '==' :
							default : $l = 'eq'; break;
						}
						ptln('		<td class="required">'.$r['required'].($lvlstr ? ' ('.$this->getLang('req_'.$l).' '.$lvlstr.')' : '').'</td>');
						ptln('		<td class="found">'.$r['found'].' ('.$this->getLang('perm'.$r['found']).')</td>');
						ptln('	</tr>');
						
						ptln('	<tr class="explanations">');
						ptln('		<td colspan="5"><pre>');
						foreach($r['explanations'] as $who => $list) {
							if(substr($who, 0, 1) == '_') continue;
							$rules = array();
							foreach($list as $crule => $parts) {
								if(substr($id, 0, 1) == '_') continue;
								if(($r['user'] && $parts['who'] == $r['user']) || ($r['group'] && in_array($parts['who'], array_map(create_function('$g', 'return "@".$g;'), $r['group'])))) {
									$rules[] = '	'.$crule.' => '.$parts['lvl'];
								}
							}
							if(count($rules)) {
								ptln($this->getLang('permwho').' : '.$who);
								foreach($rules as $d) ptln($d);
							}
						}
						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>');
						ptln('	</tr>');
					}
					ptln('</table>');
				}
			}
		}
	}
}
