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