1<?php
2/**
3 * Permissioninfo: Displays group and user information and the permissions of users and groups
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     Gabriel Birke <gb@birke-software.de>
7 */
8
9if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
10if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
11require_once(DOKU_PLUGIN.'admin.php');
12
13/**
14 * All DokuWiki plugins to extend the admin function
15 * need to inherit from this class
16 */
17class admin_plugin_permissioninfo extends DokuWiki_Admin_Plugin {
18
19    /**
20     * If information about which user is in which group is displayed
21     */
22    var $show_group_info = true;
23
24    /**
25     * return sort order for position in admin menu
26     */
27    function getMenuSort() {
28      return 145;
29    }
30
31    /**
32     * handle user request
33     */
34    function handle() {
35        global $conf;
36        global $auth;
37        global $INPUT;
38        $this->auth = $auth;
39
40        // If the auth class can't list users or groups, retrieve user and group information from ACL
41        if($this->auth->canDo('getUsers'))
42        {
43            $getUserFunc  = array($this->auth, 'retrieveUsers');
44        }
45        else
46        {
47            // Can't determine user/group association from ACL
48            $this->show_group_info = false;
49            $getUserFunc = array($this, "_getUsersFromACL");
50        }
51        if($this->auth->canDo('getGroups'))
52            $getGroupFunc = array($this->auth, 'retrieveGroups');
53        else
54            $getGroupFunc = array($this, '_getGroupsFromACL');
55        // Collect user and group names
56        $this->users = call_user_func($getUserFunc);
57        $this->groups = call_user_func($getGroupFunc);
58        ksort($this->groups);
59
60        // Get permissions for each group and set the data in $this->aclGroupPermissions
61        $this->_aclGroupPermissions();
62
63        // Get explicit user permissions from ACL and set the data in $this->aclUserPermissions
64        $this->_aclUserPermissions();
65
66        // Associate groups with users and set the data in $this->group2user
67        $this->_group2user();
68
69        // If we show permissions for an individual user, collect its permissions
70        if($INPUT->has('show') && $INPUT->has('user'))
71        {
72            $this->_userPermissions($INPUT->str('user'));
73        }
74    }
75
76    /**
77     * output Overview page with groups or permissionpage for individual user, all
78     * depending on $_REQUEST['show']
79     */
80    function html() {
81        global $INPUT;
82        switch($INPUT->str('show','overview'))
83        {
84            case 'userpermissions':
85                $this->_showUserPermissions();
86                break;
87            case 'overview':
88            default:
89                $this->_groupOverview();
90        }
91    }
92
93    /**
94     * Shows an overview for users in groups and permissions assigned to groups
95     */
96    function _groupOverview()
97    {
98        $id = cleanID($this->getLang('menu'));
99        ptln('<h1><a name="'.$id.'" id="'.$id.'">'.$this->getLang('menu')."</a></h1>");
100        echo $this->locale_xhtml('help');
101        foreach($this->groups as $gname => $g)
102        {
103            // container for group information
104            ptln('<section class="piContainer">');
105            // print group header
106            ptln('<header>', 2);
107            ptln("<h2>".hsc($gname)."</h2>", 4);
108            ptln('</header>', 2);
109
110            ptln('<div class="content">', 2);
111
112            // print acl settings for this group
113            ptln('<header>'.$this->getLang('permissions').'</header>', 4);
114            $this->_permissionTable($this->aclGroupPermissions[$gname], "permissions".$gname);
115
116            // print users in group
117            if(!empty($this->group2user[$gname])) {
118                ptln('<header>'.$this->getLang('users').'</header>', 4);
119                ptln('<div class="users">', 4);
120                foreach($this->group2user[$gname] as $u)
121                {
122                    $url = wl($ID, array(
123                        'do' => "admin",
124                        'page' => $this->getPluginName(),
125                        'show' => 'userpermissions',
126                        'user' => $u
127                    ));
128                    $u_enc = auth_nameencode($u);
129                    $lnk = '<a href="'.$url.'"  title="'.hsc($u).'" '.(!empty($this->aclUserPermissions[$u_enc])?'class="special"':"").'>';
130                    ptln($lnk. hsc($this->users[$u]['name']) .'</a>', 6);
131                }
132                ptln('   </div>');
133            }
134
135            // close content div
136            ptln('</div>', 2);
137
138            //end container
139            ptln('</section>');
140        }
141    }
142
143    /**
144     * Show permissions for individual user, highlight permissions that were
145     * assigned explicitly to this user.
146     */
147    function _showUserPermissions()
148    {
149        $head = sprintf($this->getLang('pi_permissionfor'), hsc($this->username));
150        $id = cleanID($head);
151        ptln('<h1><a name="'.$id.'" id="'.$id.'">'.$head."</a></h1>");
152        echo $this->locale_xhtml('help_userpermissions');
153
154        // Link to Overview
155        $url =wl($ID, array(
156            'do' => "admin",
157            'page' => $this->getPluginName(),
158            'show' => 'overview'
159        ));
160        ptln('<p class="piToOverview"><a href="'.$url.'">'.$this->getLang('pi_to_overview')."</a></p>");
161
162        ptln('<div class="piContainer">');
163        $this->_permissionTable($this->userPermissions, 'Userpermissions');
164        ptln('</div>');
165    }
166
167    /**
168     * Print permissions for a user or group
169     * @param array $acldata namespace/page_name=>permission pairs
170     * @param string $id ID for the div that surrounds the table
171     */
172    function _permissionTable($acldata)
173    {
174        $displayed_permissions = array(
175            AUTH_READ,
176            AUTH_EDIT,
177            AUTH_CREATE,
178            AUTH_UPLOAD,
179            AUTH_DELETE
180        );
181        ptln("   <div class='permissions'>");
182        if(empty($acldata))
183        {
184            ptln("    <p>".$this->getLang('pi_no_permissions_found').'</p>');
185            ptln("    </div>");
186            return;
187        }
188        ptln("   <table>");
189        $s = "<tr><th>".$this->getLang('pi_resource')."</th>";
190        foreach($displayed_permissions as $p)
191            $s .= "<th>".$this->getLang('acl_perm'.$p)."</td>";
192        ptln($s."</tr>",6);
193
194
195        $even = false;
196        foreach($acldata as $item => $perm)
197        {
198            $additional_class = empty($this->explicitUserPermissions[$item]) ? "" : " explicitUserPermission";
199            ptln('<tr class="'.($even?"even":"odd").$additional_class.'">', 6);
200            if(preg_match('/\*\s*$/', $item))
201                ptln('<td class="piItemNS">'.$item.'</td>', 9);
202            else
203                ptln('<td class="piItemPage">'.$item.'</td>',9);
204            foreach($displayed_permissions as $p)
205            {
206                if($p & $perm)
207                    ptln('<td class="piAllowed">X</td>', 9);
208                else
209                    ptln('<td class="piDenied">-</td>', 9);
210            }
211            $even = !$even;
212        }
213        ptln("   </table>");
214        ptln("   </div>");
215    }
216
217
218    /**
219     * This just gets a very rudimentary user and not very useful user list -
220     * only users who have special permissions in the ACL are listed.
221     * @return array This array is structured similar to the array returned by an auth class.
222     */
223    function _getUsersFromACL()
224    {
225        global $AUTH_ACL;
226        $users = array();
227        foreach($AUTH_ACL as $a)
228        {
229            // Don't parse comments
230            if(preg_match('/^#/', $a))
231                continue;
232            if(preg_match('/^[^\s]+\s([^@\s]+)/', $a, $matches))
233            {
234                $usr_arr = array('name' => $matches[1], 'grps' => array());
235                $users[$matches[1]] = $usr_arr;
236            }
237        }
238        return $users;
239    }
240
241    /**
242     * This function retrieves group names from the acl file.
243     * Since none of the existing auth classes supports groups, I don't know
244     * what output to expect from them. I assume a two-dimensional hash similar
245     * to that from Auth->retrieveUsers.
246     * @return array
247     */
248    function _getGroupsFromACL()
249    {
250        global $AUTH_ACL;
251        $groups = array();
252        foreach($AUTH_ACL as $a)
253        {
254            // Don't parse comments
255            if(preg_match('/^#/', $a))
256                continue;
257            if(preg_match('/^[^\s]+\s@([^\s]+)/', $a, $matches))
258            {
259                $grp_arr = array('name' => $matches[1]);
260                $groups[urldecode($matches[1])] = $grp_arr;
261            }
262        }
263        return $groups;
264    }
265
266    /**
267     * sets $this->aclGroupPermissions in the form of a[groupname][namespace/page_name]=permission
268     */
269    function _aclGroupPermissions()
270    {
271        $AUTH_ACL = $this->_auth_loadACL(); //without %USER% replacement
272        $gp = array();
273        foreach($AUTH_ACL as $a)
274        {
275            // Don't parse comments
276            if(preg_match('/^#/', $a))
277                continue;
278            if(preg_match('/^([^\s]+)\s@([^\s]+)\s(\d+)/', $a, $matches))
279            {
280                $gp[$matches[2]][$matches[1]] = $matches[3];
281            }
282        }
283        $this->aclGroupPermissions = array();
284        foreach($gp as $grpname => $permissions)
285        {
286            ksort($permissions);
287            $this->aclGroupPermissions[urldecode($grpname)] = $permissions;
288        }
289    }
290
291    /**
292     * sets $this->aclUserPermissions in the form of a[username][namespace/page_name]=permission
293     */
294    function _aclUserPermissions()
295    {
296        global $AUTH_ACL;
297        $up = array();
298        foreach($AUTH_ACL as $a)
299        {
300            // Don't parse comments
301            if(preg_match('/^#/', $a))
302                continue;
303            if(preg_match('/^([^\s]+)\s([^@\s]+)\s(\d+)/', $a, $matches))
304            {
305                $up[$matches[2]][$matches[1]] = $matches[3];
306            }
307        }
308        $this->aclUserPermissions = array();
309        foreach($up as $usrname => $permissions)
310        {
311            ksort($permissions);
312            $this->aclUserPermissions[$usrname] = $permissions;
313        }
314    }
315
316    /**
317     * Build an Array in $this->group2user that associates user names with users
318     * The users are sorted by last name
319     */
320    function _group2user()
321    {
322        $g2u = array();
323        foreach(array_keys($this->groups) as $g)
324            $g2u[$g] = array();
325        foreach($this->users as $username => $properties)
326        {
327            foreach($properties['grps'] as $grpname)
328                $g2u[$grpname][$username] = array_pop(explode(' ', $properties['name'])); // Store last name of user here for Sorting
329        }
330        $this->group2user = array();
331        foreach($g2u as $grpname => $users)
332        {
333            // Sort users in each group by last name
334            asort($users);
335            $this->group2user[$grpname] = array_keys($users);
336        }
337    }
338
339    /**
340     * Collects permission data for an individual user from the ACL. It
341     * collects permission data from the groups of the user and from the
342     * explicitly assigned permissions for the user.
343     * The data is stored in the form of two arrays:
344     * $this->userPermissions Namespace/Page => Permission pairs
345     * $this->explicitUserPermissions Namespace/Page => Permission pairs
346     */
347    function _userPermissions($username)
348    {
349        // Build regular expression for the username an its groups
350        $userdata = $this->auth->getUserData($username);
351        $this->username = $userdata['name'];
352        $search_string = preg_quote(auth_nameencode($username), '/');
353        foreach($userdata['grps'] as $g)
354            $search_string .= '|@'.preg_quote(auth_nameencode($g), '/');
355        $perm_regex = '/^([^\s]+)\s('.$search_string.')\s(\d+)/';
356        // Search through permissions
357        $AUTH_ACL = $this->_auth_loadACL(); //without user replacement
358        $up = array();
359        // $for_user holds permissions that are assigned explicitly to the user
360        $for_user = array();
361        foreach($AUTH_ACL as $a)
362        {
363            // Don't parse comments
364            if(preg_match('/^#/', $a))
365                continue;
366            if(preg_match($perm_regex, $a, $matches))
367            {
368                $ns = str_replace('%USER%',auth_nameencode($username),$matches[1]); //replace %USER% with username
369                $up[$ns] = (empty($up[$matches[1]])?0:$up[$matches[1]]) | $matches[3];
370                if(substr($matches[2], 0, 1) != "@")
371                    $for_user[$ns] = $matches[3];
372            }
373        }
374        ksort($up);
375        ksort($for_user);
376        $this->userPermissions = $up;
377        $this->explicitUserPermissions = $for_user;
378    }
379     /**
380     * Loads the ACL setup
381     *
382     * copyed from inc/auth -> auth_loadACL()
383     *  - removed substitute of user wildcard
384     */
385    function _auth_loadACL() {
386        global $config_cascade;
387        global $USERINFO;
388
389        if(!is_readable($config_cascade['acl']['default'])) return array();
390
391        $acl = file($config_cascade['acl']['default']);
392
393        $out = array();
394        foreach($acl as $line) {
395            $line = trim($line);
396            if(empty($line) || ($line{0} == '#')) continue; // skip blank lines & comments
397            list($id,$rest) = preg_split('/[ \t]+/',$line,2);
398
399            // substitute group wildcard (its 1:m)
400            if(strstr($line, '%GROUP%')){
401                // if user is not logged in, grps is empty, no output will be added (i.e. skipped)
402                foreach((array) $USERINFO['grps'] as $grp){
403                    $nid   = str_replace('%GROUP%',cleanID($grp),$id);
404                    $nrest = str_replace('%GROUP%','@'.auth_nameencode($grp),$rest);
405                    $out[] = "$nid\t$nrest";
406                }
407            } else {
408                $out[] = "$id\t$rest";
409            }
410        }
411
412        return $out;
413    }
414}
415
416