xref: /dokuwiki/lib/plugins/acl/admin.php (revision aea87c78e17f8e8f817852532e3498577f97f405)
1<?php
2/**
3 * ACL administration functions
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     Frank Schubert <frank@schokilade.de>
7 */
8// must be run within Dokuwiki
9if(!defined('DOKU_INC')) die();
10
11if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
12require_once(DOKU_PLUGIN.'admin.php');
13
14/**
15 * All DokuWiki plugins to extend the admin function
16 * need to inherit from this class
17 */
18class admin_plugin_acl extends DokuWiki_Admin_Plugin {
19
20
21        function admin_plugin_acl(){
22            $this->setupLocale();
23        }
24
25
26
27    /**
28     * return some info
29     */
30    function getInfo(){
31      return array(
32        'author' => 'Frank Schubert',
33        'email'  => 'frank@schokilade.de',
34        'date'   => '2005-08-08',
35        'name'   => 'ACL',
36        'desc'   => 'Manage Page Access Control Lists',
37        'url'    => 'http://wiki.splitbrain.org/wiki:acl',
38      );
39    }
40
41    /**
42     * return prompt for admin menu
43     */
44    function getMenuText($language) {
45        return $this->lang['admin_acl'];
46    }
47
48    /**
49     * return sort order for position in admin menu
50     */
51    function getMenuSort() {
52      return 1;
53    }
54
55    /**
56     * handle user request
57     */
58    function handle() {
59      global $AUTH_ACL;
60
61      $cmd   = $_REQUEST['acl_cmd'];
62      $scope = $_REQUEST['acl_scope'];
63      $type  = $_REQUEST['acl_type'];
64      $user  = $_REQUEST['acl_user'];
65      $perm  = $_REQUEST['acl_perm'];
66
67      if(is_array($perm)){
68        //use the maximum
69        sort($perm);
70        $perm = array_pop($perm);
71      }else{
72        $perm = 0;
73      }
74
75      //sanitize
76      $user  = auth_nameencode($user);
77      if($type == '@') $user = '@'.$user;
78      if($user == '@all') $user = '@ALL'; //special group! (now case insensitive)
79      $perm  = (int) $perm;
80      if($perm > AUTH_DELETE) $perm = AUTH_DELETE;
81
82      //nothing to do?
83      if(empty($cmd) || empty($scope) || empty($user)) return;
84
85      // check token
86      if(!checkSecurityToken()) return;
87
88
89      if($cmd == 'save'){
90        $this->admin_acl_del($scope, $user);
91        $this->admin_acl_add($scope, $user, $perm);
92      }elseif($cmd == 'delete'){
93        $this->admin_acl_del($scope, $user);
94      }
95
96      // reload ACL config
97      $AUTH_ACL = file(DOKU_CONF.'acl.auth.php');
98    }
99
100    /**
101     * ACL Output function
102     *
103     * print a table with all significant permissions for the
104     * current id
105     *
106     * @author  Frank Schubert <frank@schokilade.de>
107     * @author  Andreas Gohr <andi@splitbrain.org>
108     */
109    function html() {
110      global $ID;
111
112      print $this->locale_xhtml('intro');
113
114      ptln('<div id="acl__manager">');
115      ptln('<table class="inline">');
116
117      //new
118      $this->admin_acl_html_new();
119
120      //current config
121      $acls = $this->get_acl_config($ID);
122      foreach ($acls as $id => $acl){
123        $this->admin_acl_html_current($id,$acl);
124      }
125
126      ptln('</table>');
127      ptln('</div>');
128    }
129
130
131    /**
132     * Get matching ACL lines for a page
133     *
134     * $ID is pagename, reads matching lines from $AUTH_ACL,
135     * also reads acls from namespace
136     * returns multi-array with key=pagename and value=array(user, acl)
137     *
138     * @todo    Fix comment to make sense
139     * @todo    should this moved to auth.php?
140     * @todo    can this be combined with auth_aclcheck to avoid duplicate code?
141     * @author  Frank Schubert <frank@schokilade.de>
142     */
143    function get_acl_config($id){
144      global $AUTH_ACL;
145
146      $acl_config=array();
147
148      // match exact name
149      $matches = preg_grep('/^'.$id.'\s+.*/',$AUTH_ACL);
150      if(count($matches)){
151        foreach($matches as $match){
152          $match = preg_replace('/#.*$/','',$match); //ignore comments
153          $acl   = preg_split('/\s+/',$match);
154          //0 is pagename, 1 is user, 2 is acl
155          $acl_config[$acl[0]][] = array( 'name' => $acl[1], 'perm' => $acl[2]);
156        }
157      }
158
159      $specific_found=array();
160      // match ns
161      while(($id=getNS($id)) !== false){
162        $matches = preg_grep('/^'.$id.':\*\s+.*/',$AUTH_ACL);
163        if(count($matches)){
164          foreach($matches as $match){
165            $match = preg_replace('/#.*$/','',$match); //ignore comments
166            $acl   = preg_split('/\s+/',$match);
167            //0 is pagename, 1 is user, 2 is acl
168            $acl_config[$acl[0]][] = array( 'name' => $acl[1], 'perm' => $acl[2]);
169            $specific_found[]=$acl[1];
170          }
171        }
172      }
173
174      //include *-config
175      $matches = preg_grep('/^\*\s+.*/',$AUTH_ACL);
176      if(count($matches)){
177        foreach($matches as $match){
178          $match = preg_replace('/#.*$/','',$match); //ignore comments
179          $acl   = preg_split('/\s+/',$match);
180          // only include * for this user if not already found in ns
181          if(!in_array($acl[1], $specific_found)){
182            //0 is pagename, 1 is user, 2 is acl
183            $acl_config[$acl[0]][] = array( 'name' => $acl[1], 'perm' => $acl[2]);
184          }
185        }
186      }
187
188      //sort
189      //FIXME: better sort algo: first sort by key, then sort by first value
190      krsort($acl_config, SORT_STRING);
191
192      return($acl_config);
193    }
194
195
196    /**
197     * adds new acl-entry to conf/acl.auth.php
198     *
199     * @author  Frank Schubert <frank@schokilade.de>
200     */
201    function admin_acl_add($acl_scope, $acl_user, $acl_level){
202      $acl_config = join("",file(DOKU_CONF.'acl.auth.php'));
203
204      // max level for pagenames is edit
205      if(strpos($acl_scope,'*') === false) {
206        if($acl_level > AUTH_EDIT) $acl_level = AUTH_EDIT;
207      }
208
209      $new_acl = "$acl_scope\t$acl_user\t$acl_level\n";
210
211      $new_config = $acl_config.$new_acl;
212
213      return io_saveFile(DOKU_CONF.'acl.auth.php', $new_config);
214    }
215
216    /**
217     * remove acl-entry from conf/acl.auth.php
218     *
219     * @author  Frank Schubert <frank@schokilade.de>
220     */
221    function admin_acl_del($acl_scope, $acl_user){
222      $acl_config = file(DOKU_CONF.'acl.auth.php');
223
224      $acl_pattern = '^'.preg_quote($acl_scope,'/').'\s+'.$acl_user.'\s+[0-8].*$';
225
226      // save all non!-matching #FIXME invert is available from 4.2.0 only!
227      $new_config = preg_grep("/$acl_pattern/", $acl_config, PREG_GREP_INVERT);
228
229      return io_saveFile(DOKU_CONF.'acl.auth.php', join('',$new_config));
230    }
231
232    // --- HTML OUTPUT FUNCTIONS BELOW --- //
233
234    /**
235     * print tablerows with the current permissions for one id
236     *
237     * @author  Frank Schubert <frank@schokilade.de>
238     * @author  Andreas Gohr <andi@splitbrain.org>
239     */
240    function admin_acl_html_dropdown($id){
241      $cur = $id;
242      $ret = '';
243      $opt = array();
244
245      //prepare all options
246
247      // current page
248      $opt[] = array('key'=> $id, 'val'=> $id.' ('.$this->lang['page'].')');
249
250      // additional namespaces
251      while(($id=getNS($id)) !== false){
252        $opt[] = array('key'=> $id.':*', 'val'=> $id.':* ('.$this->lang['namespace'].')');
253      }
254
255      // the top namespace
256      $opt[] = array('key'=> '*', 'val'=> '* ('.$this->lang['namespace'].')');
257
258      // set sel on second entry (current namespace)
259      $opt[1]['sel'] = ' selected="selected"';
260
261      // flip options
262      $opt = array_reverse($opt);
263
264      // create HTML
265      $att = array( 'name'  => 'acl_scope',
266                    'class' => 'edit',
267                    'title' => $this->lang['page'].'/'.$this->lang['namespace']);
268      $ret .= '<select '.html_attbuild($att).'>';
269      foreach($opt as $o){
270        $ret .= '<option value="'.$o['key'].'"'.$o['sel'].'>'.$o['val'].'</option>';
271      }
272      $ret .= '</select>';
273
274      return $ret;
275    }
276
277    /**
278     * print tablerows with the current permissions for one id
279     *
280     * @author  Frank Schubert <frank@schokilade.de>
281     * @author  Andreas Gohr <andi@splitbrain.org>
282     */
283    function admin_acl_html_new(){
284      global $ID;
285            global $lang;
286
287      // table headers
288      ptln('<tr>',2);
289      ptln('  <th class="leftalign" colspan="3">'.$this->lang['acl_new'].'</th>',2);
290      ptln('</tr>',2);
291
292      ptln('<tr>',2);
293
294      ptln('<td class="centeralign" colspan="3">',4);
295
296      ptln('  <form method="post" action="'.wl($ID).'"><div class="no">',4);
297      ptln('    <input type="hidden" name="do"   value="admin" />',4);
298      ptln('    <input type="hidden" name="page" value="acl" />',4);
299      ptln('    <input type="hidden" name="acl_cmd" value="save" />',4);
300      formSecurityToken();
301
302      //scope select
303      ptln($this->lang['acl_perms'],4);
304      ptln($this->admin_acl_html_dropdown($ID),4);
305
306      $att = array( 'name'  => 'acl_type',
307                    'class' => 'edit',
308                    'title' => $this->lang['acl_user'].'/'.$this->lang['acl_group']);
309      ptln('    <select '.html_attbuild($att).'>',4);
310      ptln('      <option value="@">'.$this->lang['acl_group'].'</option>',4);
311      ptln('      <option value="">'.$this->lang['acl_user'].'</option>',4);
312      ptln('    </select>',4);
313
314      $att = array( 'name'  => 'acl_user',
315                    'type'  => 'text',
316                    'class' => 'edit',
317                    'title' => $this->lang['acl_user'].'/'.$this->lang['acl_group']);
318      ptln('    <input '.html_attbuild($att).' />',4);
319      ptln('    <br />');
320
321      ptln(     $this->admin_acl_html_checkboxes(0,false),8);
322
323      ptln('    <input type="submit" class="button" value="'.$lang['btn_save'].'" />',4);
324      ptln('  </div></form>');
325      ptln('</td>',4);
326      ptln('</tr>',2);
327    }
328
329    /**
330     * print tablerows with the current permissions for one id
331     *
332     * @author  Frank Schubert <frank@schokilade.de>
333     * @author  Andreas Gohr <andi@splitbrain.org>
334     */
335    function admin_acl_html_current($id,$permissions){
336      global $lang;
337      global $ID;
338
339      //is it a page?
340      if(substr($id,-1) == '*'){
341        $ispage = false;
342      }else{
343        $ispage = true;
344      }
345
346      // table headers
347      ptln('  <tr>');
348      ptln('    <th class="leftalign" colspan="3">');
349      ptln($this->lang['acl_perms'],6);
350      if($ispage){
351        ptln($this->lang['page'],6);
352      }else{
353        ptln($this->lang['namespace'],6);
354      }
355      ptln('<em>'.$id.'</em>',6);
356      ptln('    </th>');
357      ptln('  </tr>');
358
359      sort($permissions);
360
361      foreach ($permissions as $conf){
362        //userfriendly group/user display
363        $conf['name'] = rawurldecode($conf['name']);
364        if(substr($conf['name'],0,1)=="@"){
365          $group = $this->lang['acl_group'];
366          $name  = substr($conf['name'],1);
367          $type  = '@';
368        }else{
369          $group = $this->lang['acl_user'];
370          $name  = $conf['name'];
371          $type  = '';
372        }
373
374        ptln('<tr>',2);
375        ptln('<td class="leftalign">'.htmlspecialchars($group.' '.$name).'</td>',4);
376
377        // update form
378        ptln('<td class="centeralign">',4);
379        ptln('  <form method="post" action="'.wl($ID).'"><div class="no">',4);
380        formSecurityToken();
381        ptln('    <input type="hidden" name="do"   value="admin" />',4);
382        ptln('    <input type="hidden" name="page" value="acl" />',4);
383        ptln('    <input type="hidden" name="acl_cmd"   value="save" />',4);
384        ptln('    <input type="hidden" name="acl_scope" value="'.formtext($id).'" />',4);
385        ptln('    <input type="hidden" name="acl_type" value="'.$type.'" />',4);
386        ptln('    <input type="hidden" name="acl_user"  value="'.formtext($name).'" />',4);
387        ptln(     $this->admin_acl_html_checkboxes($conf['perm'],$ispage),8);
388        ptln('    <input type="submit" class="button" value="'.$lang['btn_update'].'" />',4);
389        ptln('  </div></form>');
390        ptln('</td>',4);
391
392
393        // deletion form
394
395        $ask  = $lang['del_confirm'].'\\n';
396        $ask .= $id.'  '.$conf['name'].'  '.$conf['perm'];
397        ptln('<td class="centeralign">',4);
398        ptln('  <form method="post" action="'.wl($ID).'" onsubmit="return confirm(\''.str_replace('\\\\n','\\n',addslashes($ask)).'\')"><div class="no">',4);
399        formSecurityToken();
400        ptln('    <input type="hidden" name="do"        value="admin" />',4);
401        ptln('    <input type="hidden" name="page"      value="acl" />',4);
402        ptln('    <input type="hidden" name="acl_cmd"   value="delete" />',4);
403        ptln('    <input type="hidden" name="acl_scope" value="'.formtext($id).'" />',4);
404        ptln('    <input type="hidden" name="acl_type" value="'.$type.'" />',4);
405        ptln('    <input type="hidden" name="acl_user"  value="'.formtext($name).'" />',4);
406        ptln('    <input type="submit" class="button" value="'.$lang['btn_delete'].'" />',4);
407        ptln('  </div></form>',4);
408        ptln('</td>',4);
409
410        ptln('</tr>',2);
411      }
412
413    }
414
415
416    /**
417     * print the permission checkboxes
418     *
419     * @author  Frank Schubert <frank@schokilade.de>
420     * @author  Andreas Gohr <andi@splitbrain.org>
421     */
422    function admin_acl_html_checkboxes($setperm,$ispage){
423      global $lang;
424
425      static $label = 0; //number labels
426      $ret = '';
427
428      foreach(array(AUTH_READ,AUTH_EDIT,AUTH_CREATE,AUTH_UPLOAD,AUTH_DELETE) as $perm){
429        $label += 1;
430
431        //general checkbox attributes
432        $atts = array( 'type'  => 'checkbox',
433                       'id'    => 'pbox'.$label,
434                       'name'  => 'acl_perm[]',
435                       'value' => $perm );
436        //dynamic attributes
437        if($setperm >= $perm) $atts['checked']  = 'checked';
438    #        if($perm > AUTH_READ) $atts['onchange'] = #FIXME JS to autoadd lower perms
439        if($ispage && $perm > AUTH_EDIT) $atts['disabled'] = 'disabled';
440
441        //build code
442        $ret .= '<label for="pbox'.$label.'" title="'.$this->lang['acl_perm'.$perm].'">';
443        $ret .= '<input '.html_attbuild($atts).' />';
444        $ret .= $this->lang['acl_perm'.$perm];
445        $ret .= "</label>\n";
446      }
447      return $ret;
448    }
449
450}
451