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
10class helper_plugin_aclauditor extends DokuWiki_Plugin {
11	var $acls = null;
12	var $usersgroups = null;
13
14	function getInfo() {
15		return array(
16			'author' => 'Etienne MELEARD',
17			'email'  => 'etienne.meleard@free.fr',
18			'date'   => @file_get_contents(DOKU_PLUGIN.'aclauditor/VERSION'),
19			'name'   => 'ACL Auditor Plugin (helper class)',
20			'desc'   => 'Functions to get info about user /group ACLs of a wiki page',
21			'url'    => 'http://dokuwiki.org/plugin:aclauditor',
22		);
23	}
24
25	function getMethods() {
26		return array(
27			array(
28				'name'   => 'resourceACLs',
29				'desc'   => 'returns the list of ACLs that are applied on a resource (page/media)',
30				'params' => array(
31					'resource' => 'string'
32				),
33				'return' => array(
34					'list' => 'array'
35				)
36			),
37			array(
38				'name'   => 'getACL',
39				'desc'   => 'returns the list of ACLs for a user/group',
40				'params' => array(
41					'of' => 'string'
42				),
43				'return' => array(
44					'list' => 'array'
45				)
46			),
47			array(
48				'name'   => 'getACLon',
49				'desc'   => 'returns the list of ACLs that are applied on a resource (page/media) for a user or a group',
50				'params' => array(
51					'of' => 'string',
52					'resource' => 'string'
53				),
54				'return' => array(
55					'list' => 'array'
56				)
57			)
58		);
59	}
60
61	/**
62	 * Gets ACLs applied on a resource
63	 * @param $resource string or array of page id or media id to get acl list about
64	 * @retrun array
65	 */
66	function resourceACLs($resource, $locale = false) {
67		$this->init();
68
69		$acls = array();
70
71		foreach($this->usersgroups as $u => $grps) if(auth_isadmin($u, $grps)) {
72			$acls[$u] = array();
73			$acls[$u]['ADMIN'] = array('what' => '*', 'who' => $u, 'rule' => 'ADMIN', 'lvl' => AUTH_ADMIN);
74		}
75		$r = $this->IDPathTree($resource);
76		foreach($this->acls as $w => $parts) {
77			if(!in_array($parts['what'], $r)) continue;
78			if(!isset($acls[$parts['who']])) $acls[$parts['who']] = array();
79			$acls[$parts['who']][$w] = array('what' => $parts['what'], 'who' => $parts['who'], 'rule' => $w, 'lvl' => $parts['lvl']);
80		}
81
82		uksort($acls, create_function('$a, $b', '
83			$agrp = preg_match("`^@.+`", $a);
84			$bgrp = preg_match("`^@.+`", $b);
85			if($agrp && !$bgrp) return -1;
86			if(!$agrp && $bgrp) return 1;
87			return strcmp($a, $b);
88		'));
89
90		foreach($acls as $who => $list) {
91			uasort($list, create_function('$a, $b', '
92				if($a["what"] == $b["what"]) return $a["lvl"] - $b["lvl"];
93				$a["what"] = str_replace("*", "", $a["what"]);
94				$b["what"] = str_replace("*", "", $b["what"]);
95				if(strpos($a["what"], $b["what"]) === 0) return 1;
96				if(strpos($b["what"], $a["what"]) === 0) return -1;
97				return 0;
98			'));
99			$lvl = max(array_map(create_function('$p', 'return $p["lvl"];'), $list));
100			$slvl = -1;
101			foreach($list as $l) if($l['what'] == $resource) $slvl = $l['lvl'];
102			$list['_lvl'] = ($slvl >= 0) ? $slvl : $lvl;
103			$acls[$who] = $list;
104		}
105
106		if($locale) foreach($acls as $who => $list) foreach($list as $rule => $d) if($d['what'] != $resource) unset($acls[$who][$rule]);
107
108		return $acls;
109	}
110
111	/**
112	 * Get acls of a user/group for a resource
113	 * @param $of user or group to get acl list from
114	 * @param $resource page id or media id to get acl list about
115	 * @return array
116	 */
117	function getACLon($of, $resource) {
118		$this->init();
119
120		$flt = array($of);
121		if(!preg_match('`^@.+`', $of)) {
122			$flt[] = '@ALL';
123			if(isset($this->usersgroups[$of])) $flt = array_merge($flt, $this->usersgroups[$of]);
124		}
125
126		$acls = array();
127
128		$glvl = 0;
129		$gspecificlvl = -1;
130
131		foreach($this->IDPathTree($resource) as $id) {
132			$acls[$id] = $this->resourceACLs($id, true);
133			$lvl = 0;
134			$specificlvl = -1;
135			foreach($acls[$id] as $who => $d) {
136				if(!in_array($who, $flt)) {
137					unset($acls[$id][$who]);
138					continue;
139				}
140				foreach($d as $r => $rd) {
141					if($rd['rule'] == 'ADMIN') {
142						$lvl = AUTH_ADMIN;
143					}elseif($rd['lvl'] > $lvl) $lvl = $rd['lvl'];
144
145					if(($rd['what'] == $id) && ($rd['lvl'] > $specificlvl))  $specificlvl = $rd['lvl'];
146				}
147			}
148
149			$acls[$id]['_lvl'] = $lvl;
150			$acls[$id]['_specificlvl'] = $specificlvl;
151
152			if($lvl > $glvl) $glvl = $lvl;
153			if($specificlvl >= 0)  $gspecificlvl = $specificlvl;
154		}
155
156		$acls['_lvl'] = $glvl;
157		$acls['_specificlvl'] = $gspecificlvl;
158
159		return $acls;
160	}
161
162	/**
163	 * Gets the acl list of an user/group
164	 * @param $of string what to get acls of
165	 * @return array
166	 */
167	function getACL($of) {
168		$this->init();
169
170		$acls = array();
171
172		$of = array($of);
173		if(!preg_match('`^@.+`', $of[0]) && isset($this->usersgroups[$of[0]])) {
174			if(auth_isadmin($of[0], $this->usersgroups[$of[0]])) $acls['*'] = array('ADMIN' => array('what' => '*', 'who' => null, 'rule' => 'ADMIN', 'lvl' => AUTH_ADMIN));
175			$of = array_merge($of, $this->usersgroups[$of[0]]);
176		}
177		foreach($this->acls as $w => $parts) {
178			if(!in_array($parts['who'], $of)) continue;
179			if(!isset($acls[$parts['what']])) $acls[$parts['what']] = array();
180			$acls[$parts['what']][$w] = array('what' => $parts['what'], 'who' => $parts['who'], 'rule' => $w, 'lvl' => $parts['lvl']);
181		}
182
183		uksort($acls, create_function('$a, $b', '
184			if($a == $b) return 0;
185			$a = str_replace("*", "", $a);
186			$b = str_replace("*", "", $b);
187			if(($b == "") || strpos($a, $b) === 0) return 1;
188			if(($a == "") || strpos($b, $a) === 0) return -1;
189			return 0;
190		'));
191		foreach($acls as $what => $list) {
192			uasort($list, create_function('$a, $b', 'return $a["lvl"] - $b["lvl"];'));
193			$list['_lvl'] = max(array_map(create_function('$p', 'return $p["lvl"];'), $list));
194			$acls[$what] = $list;
195		}
196
197		return $acls;
198	}
199
200	/**
201	 * Parses the acl file into an array and loads users-groups hash
202	 */
203	function init() {
204		if(is_null($this->acls)) {
205			global $AUTH_ACL;
206			foreach($AUTH_ACL as $l) {
207				if(preg_match('`^\s*(#.*)$`', $l)) continue;
208				list($what, $who, $lvl) = preg_split('`\s+`', $l);
209				if(!$what || !$who || !$lvl) continue;
210				$this->acls[$what.' '.$who.' '.$lvl] = array(
211					'what' => $what,
212					'who' => $who,
213					'lvl' => (int)$lvl
214				);
215			}
216		}
217
218		if(is_null($this->usersgroups)) {
219			global $auth;
220			$this->usersgroups = array();
221			foreach($auth ? $auth->retrieveUsers() : array() as $u => $info) {
222				$this->usersgroups[$u] = array_map(create_function('$g', 'return "@".$g;'), $info['grps']);
223				$this->usersgroups[$u][] = '@ALL';
224			}
225		}
226	}
227
228	/**
229	 * Decomposes an ID into ACLs compliant namespace tree
230	 * @param $id string id to decompose
231	 * @return array
232	 */
233	function IDPathTree($id) {
234		$id = cleanID($id);
235		if(!@file_exists(wikiFN($id)) && !@file_exists(mediaFN($id))) $id .= ':*';
236		$tree = $id ? array($id) : array();
237		while($id = getNS($id)) $tree[] = $id.':*';
238		$tree[] = '*';
239		return array_reverse(array_unique($tree));
240	}
241}
242