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