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