]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/PagePerm.php
Activated Id substitution for Subversion
[SourceForge/phpwiki.git] / lib / PagePerm.php
1 <?php // -*-php-*-
2 rcs_id('$Id$');
3 /*
4  Copyright 2004,2007 $ThePhpWikiProgrammingTeam
5
6  This file is part of PhpWiki.
7
8  PhpWiki is free software; you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation; either version 2 of the License, or
11  (at your option) any later version.
12
13  PhpWiki is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  GNU General Public License for more details.
17
18  You should have received a copy of the GNU General Public License
19  along with PhpWiki; if not, write to the Free Software
20  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23 /**
24    Permissions per page and action based on current user, 
25    ownership and group membership implemented with ACL's (Access Control Lists),
26    opposed to the simplier unix-like ugo:rwx system.
27    The previous system was only based on action and current user. (lib/main.php)
28
29    Permissions may be inherited from its parent pages, a optional the 
30    optional master page ("."), and predefined default permissions, if "." 
31    is not defined.
32    Pagenames starting with "." have special default permissions.
33    For Authentication see WikiUserNew.php, WikiGroup.php and main.php
34    Page Permissions are in PhpWiki since v1.3.9 and enabled since v1.4.0
35
36    This file might replace the following functions from main.php:
37      Request::_notAuthorized($require_level)
38        display the denied message and optionally a login form 
39        to gain higher privileges
40      Request::getActionDescription($action)
41        helper to localize the _notAuthorized message per action, 
42        when login is tried.
43      Request::getDisallowedActionDescription($action)
44        helper to localize the _notAuthorized message per action, 
45        when it aborts
46      Request::requiredAuthority($action)
47        returns the needed user level
48        has a hook for plugins on POST
49      Request::requiredAuthorityForAction($action)
50        just returns the level per action, will be replaced with the 
51        action + page pair
52
53      The defined main.php actions map to simplier access types:
54        browse => view
55        edit   => edit
56        create => edit or create
57        remove => remove
58        rename => change
59        store prefs => change
60        list in PageList => list
61 */
62
63 /* Symbolic special ACL groups. Untranslated to be stored in page metadata*/
64 define('ACL_EVERY',        '_EVERY');
65 define('ACL_ANONYMOUS',    '_ANONYMOUS');
66 define('ACL_BOGOUSER',     '_BOGOUSER');
67 define('ACL_HASHOMEPAGE',  '_HASHOMEPAGE');
68 define('ACL_SIGNED',       '_SIGNED');
69 define('ACL_AUTHENTICATED','_AUTHENTICATED');
70 define('ACL_ADMIN',        '_ADMIN');
71 define('ACL_OWNER',        '_OWNER');
72 define('ACL_CREATOR',      '_CREATOR');
73
74 // Return an page permissions array for this page.
75 // To provide ui helpers to view and change page permissions:
76 //   <tr><th>Group</th><th>Access</th><th>Allow or Forbid</th></tr>
77 //   <tr><td>$group</td><td>_($access)</td><td> [ ] </td></tr>
78 function pagePermissions($pagename) {
79     global $request;
80     $page = $request->getPage($pagename);
81     // Page not found (new page); returned inherited permissions, to be displayed in gray
82     if (! $page->exists() ) {
83         if ($pagename == '.') // stop recursion
84             return array('default', new PagePermission());
85         else {
86             return array('inherited', pagePermissions(getParentPage($pagename)));
87         }
88     } elseif ($perm = getPagePermissions($page)) {
89         return array('page', $perm);
90     // or no permissions defined; returned inherited permissions, to be displayed in gray
91     } elseif ($pagename == '.') { // stop recursion in pathological case. 
92         // "." defined, without any acl
93         return array('default', new PagePermission());
94     } else {
95         return array('inherited', pagePermissions(getParentPage($pagename)));
96     }
97 }
98
99 function pagePermissionsSimpleFormat($perm_tree, $owner, $group=false) {
100     list($type,$perm) = pagePermissionsAcl($perm_tree[0], $perm_tree);
101     /*
102     $type = $perm_tree[0];
103     $perm = pagePermissionsAcl($perm_tree);
104     if (is_object($perm_tree[1]))
105         $perm = $perm_tree[1];
106     elseif (is_array($perm_tree[1])) {
107         $perm_tree = pagePermissionsSimpleFormat($perm_tree[1],$owner,$group);
108         if (isa($perm_tree[1],'pagepermission'))
109             $perm = $perm_tree[1];
110         elseif (isa($perm_tree,'htmlelement'))
111             return $perm_tree;
112     }
113     */
114     if ($type == 'page')
115         return HTML::tt(HTML::strong($perm->asRwxString($owner, $group)));
116     elseif ($type == 'default')
117         return HTML::tt($perm->asRwxString($owner, $group));
118     elseif ($type == 'inherited') {
119         return HTML::tt(array('class'=>'inherited', 'style'=>'color:#aaa;'),
120                         $perm->asRwxString($owner, $group));
121     }
122 }
123
124 function pagePermissionsAcl($type,$perm_tree) {
125     $perm = $perm_tree[1];
126     while (!is_object($perm)) {
127         $perm_tree = pagePermissionsAcl($type, $perm);
128         $perm = $perm_tree[1];
129     }
130     return array($type,$perm);
131 }
132
133 // view => who
134 // edit => who
135 function pagePermissionsAclFormat($perm_tree, $editable=false) {
136     list($type,$perm) = pagePermissionsAcl($perm_tree[0], $perm_tree);
137     if ($editable)
138         return $perm->asEditableTable($type);
139     else
140         return $perm->asTable($type);
141 }
142
143 /** 
144  * Check the permissions for the current action.
145  * Walk down the inheritance tree. Collect all permissions until 
146  * the minimum required level is gained, which is not 
147  * overruled by more specific forbid rules.
148  * Todo: cache result per access and page in session?
149  */
150 function requiredAuthorityForPage ($action) {
151     global $request;
152     $auth = _requiredAuthorityForPagename(action2access($action),
153                                           $request->getArg('pagename'));
154     assert($auth !== -1);
155     if ($auth)
156         return $request->_user->_level;
157     else
158         return WIKIAUTH_UNOBTAINABLE;
159 }
160
161 // Translate action or plugin to the simplier access types:
162 function action2access ($action) {
163     global $request;
164     switch ($action) {
165     case 'browse':
166     case 'viewsource':
167     case 'diff':
168     case 'select':
169     case 'search':
170     case 'pdf':
171     case 'captcha':
172     case 'zip':
173         return 'view';
174
175     case 'dumpserial':
176         if (INSECURE_ACTIONS_LOCALHOST_ONLY and is_localhost())
177             return 'dump';
178         else
179             return 'view';
180
181     // performance and security relevant
182     case 'xmlrpc':
183     case 'soap':
184     case 'ziphtml':
185     case 'dumphtml':
186         return 'dump';
187
188     // invent a new access-perm massedit? or switch back to change, or keep it at edit?
189     case _("PhpWikiAdministration")."/"._("Rename"):
190     case _("PhpWikiAdministration")."/"._("Replace"):
191     case 'replace':
192     case 'rename':
193     case 'revert':
194     case 'edit':
195         return 'edit';
196     case 'create':
197         $page = $request->getPage();
198         if (!$page->exists())
199             return 'create';
200         else
201             return 'view'; 
202         break;
203     case 'upload':
204     case 'loadfile': 
205         // probably create/edit but we cannot check all page permissions, can we?
206     case 'remove':
207     case 'lock':
208     case 'unlock':
209     case 'upgrade':
210     case 'chown':
211     case 'setacl':
212         return 'change';
213     default:
214         //Todo: Plugins should be able to override its access type
215         if (isWikiWord($action))
216             return 'view';
217         else
218             return 'change';
219         break;
220     }
221 }
222
223 // Recursive helper to do the real work.
224 // Using a simple perm cache for page-access pairs.
225 // Maybe page-(current+edit+change?)action pairs will help
226 function _requiredAuthorityForPagename($access, $pagename) {
227     static $permcache = array();
228     
229     if (array_key_exists($pagename, $permcache)
230         and array_key_exists($access, $permcache[$pagename]))
231         return $permcache[$pagename][$access];
232         
233     global $request;
234     $page = $request->getPage($pagename);
235     // Page not found; check against default permissions
236     if (! $page->exists() ) {
237         $perm = new PagePermission();
238         $result = ($perm->isAuthorized($access, $request->_user) === true);
239         $permcache[$pagename][$access] = $result;
240         return $result;
241     }
242     // no ACL defined; check for special dotfile or walk down
243     if (! ($perm = getPagePermissions($page))) { 
244         if ($pagename == '.') {
245             $perm = new PagePermission();
246             if ($perm->isAuthorized('change', $request->_user)) {
247                 // warn the user to set ACL of ".", if he has permissions to do so.
248                 trigger_error(". (dotpage == rootpage for inheriting pageperm ACLs) exists without any ACL!\n".
249                               "Please do ?action=setacl&pagename=.", E_USER_WARNING);
250             }
251             $result = ($perm->isAuthorized($access, $request->_user) === true);
252             $permcache[$pagename][$access] = $result;
253             return $result;
254         } elseif ($pagename[0] == '.') {
255             $perm = new PagePermission(PagePermission::dotPerms());
256             $result = ($perm->isAuthorized($access, $request->_user) === true);
257             $permcache[$pagename][$access] = $result;
258             return $result;
259         }
260         return _requiredAuthorityForPagename($access, getParentPage($pagename));
261     }
262     // ACL defined; check if isAuthorized returns true or false or undecided
263     $authorized = $perm->isAuthorized($access, $request->_user);
264     if ($authorized !== -1) { // interestingly true is also -1
265         $permcache[$pagename][$access] = $authorized;
266         return $authorized;
267     } elseif ($pagename == '.') {
268         return false;
269     } else {    
270         return _requiredAuthorityForPagename($access, getParentPage($pagename));
271     }
272 }
273
274 /**
275  * @param  string $pagename   page from which the parent page is searched.
276  * @return string parent      pagename or the (possibly pseudo) dot-pagename.
277  */
278 function getParentPage($pagename) {
279     if (isSubPage($pagename)) {
280         return subPageSlice($pagename, 0);
281     } else {
282         return '.';
283     }
284 }
285
286 // Read the ACL from the page
287 // Done: Not existing pages should NOT be queried. 
288 // Check the parent page instead and don't take the default ACL's
289 function getPagePermissions ($page) {
290     if ($hash = $page->get('perm'))  // hash => object
291         return new PagePermission(unserialize($hash));
292     else 
293         return false;
294 }
295
296 // Store the ACL in the page
297 function setPagePermissions ($page,$perm) {
298     $perm->store($page);
299 }
300
301 function getAccessDescription($access) {
302     static $accessDescriptions;
303     if (! $accessDescriptions) {
304         $accessDescriptions = array(
305                                     'list'     => _("List this page and all subpages"),
306                                     'view'     => _("View this page and all subpages"),
307                                     'edit'     => _("Edit this page and all subpages"),
308                                     'create'   => _("Create a new (sub)page"),
309                                     'dump'     => _("Download page contents"),
310                                     'change'   => _("Change page attributes"),
311                                     'remove'   => _("Remove this page"),
312                                     );
313     }
314     if (in_array($access, array_keys($accessDescriptions)))
315         return $accessDescriptions[$access];
316     else
317         return $access;
318 }
319
320 // from php.net docs
321 function array_diff_assoc_recursive($array1, $array2) {
322     foreach ($array1 as $key => $value) {
323          if (is_array($value)) {
324              if (!is_array($array2[$key])) {
325                  $difference[$key] = $value;
326              } else {
327                  $new_diff = array_diff_assoc_recursive($value, $array2[$key]);
328                  if ($new_diff != false) {
329                      $difference[$key] = $new_diff;
330                  } 
331              }
332          } elseif(!isset($array2[$key]) || $array2[$key] != $value) {
333              $difference[$key] = $value;
334          }
335     }
336     return !isset($difference) ? 0 : $difference;
337 }
338
339 /**
340  * The ACL object per page. It is stored in a page, but can also 
341  * be merged with ACL's from other pages or taken from the master (pseudo) dot-file.
342  *
343  * A hash of "access" => "requires" pairs.
344  *   "access"   is a shortcut for common actions, which map to main.php actions
345  *   "requires" required username or groupname or any special group => true or false
346  *
347  * Define any special rules here, like don't list dot-pages.
348  */ 
349 class PagePermission {
350     var $perm;
351
352     function PagePermission($hash = array()) {
353         $this->_group = &$GLOBALS['request']->getGroup();
354         if (is_array($hash) and !empty($hash)) {
355             $accessTypes = $this->accessTypes();
356             foreach ($hash as $access => $requires) {
357                 if (in_array($access, $accessTypes))
358                     $this->perm[$access] = $requires;
359                 else
360                     trigger_error(sprintf(_("Unsupported ACL access type %s ignored."), $access),
361                                   E_USER_WARNING);
362             }
363         } else {
364             // set default permissions, the so called dot-file acl's
365             $this->perm = $this->defaultPerms();
366         }
367         return $this;
368     }
369
370     /**
371      * The workhorse to check the user against the current ACL pairs.
372      * Must translate the various special groups to the actual users settings 
373      * (userid, group membership).
374      */
375     function isAuthorized($access, $user) {
376         if (!empty($this->perm{$access})) {
377             $allow = -1;
378             foreach ($this->perm[$access] as $group => $bool) {
379                 if ($this->isMember($user, $group)) {
380                     return $bool;
381                 } elseif ($allow == -1) { // not a member and undecided: check other groups
382                     $allow = !$bool;
383                 }
384             }
385         }
386         return $allow; // undecided
387     }
388
389     /**
390      * Translate the various special groups to the actual users settings 
391      * (userid, group membership).
392      */
393     function isMember($user, $group) {
394         global $request;
395         if ($group === ACL_EVERY) return true;
396         if (!isset($this->_group)) $member =& $request->getGroup();
397         else $member =& $this->_group;
398         //$user = & $request->_user;
399         if ($group === ACL_ADMIN)   // WIKI_ADMIN or member of _("Administrators")
400             return $user->isAdmin() or 
401                    ($user->isAuthenticated() and 
402                    $member->isMember(GROUP_ADMIN));
403         if ($group === ACL_ANONYMOUS) 
404             return ! $user->isSignedIn();
405         if ($group === ACL_BOGOUSER)
406             if (ENABLE_USER_NEW)
407                 return isa($user,'_BogoUser') or 
408                       (isWikiWord($user->_userid) and $user->_level >= WIKIAUTH_BOGO);
409             else return isWikiWord($user->UserName());
410         if ($group === ACL_HASHOMEPAGE)
411             return $user->hasHomePage();
412         if ($group === ACL_SIGNED)
413             return $user->isSignedIn();
414         if ($group === ACL_AUTHENTICATED)
415             return $user->isAuthenticated();
416         if ($group === ACL_OWNER) {
417             if (!$user->isAuthenticated()) return false;
418             $page = $request->getPage();
419             $owner = $page->getOwner();
420             return ($owner === $user->UserName() 
421                     or $member->isMember($owner));
422         }
423         if ($group === ACL_CREATOR) {
424             if (!$user->isAuthenticated()) return false;
425             $page = $request->getPage();
426             $creator = $page->getCreator();
427             return ($creator === $user->UserName() 
428                     or $member->isMember($creator));
429         }
430         /* Or named groups or usernames.
431            Note: We don't seperate groups and users here. 
432            Users overrides groups with the same name. 
433         */
434         return $user->UserName() === $group or
435                $member->isMember($group);
436     }
437
438     /**
439      * returns hash of default permissions.
440      * check if the page '.' exists and returns this instead.
441      */
442     function defaultPerms() {
443         //Todo: check for the existance of '.' and take this instead.
444         //Todo: honor more config.ini auth settings here
445         $perm = array('view'   => array(ACL_EVERY => true),
446                       'edit'   => array(ACL_EVERY => true),
447                       'create' => array(ACL_EVERY => true),
448                       'list'   => array(ACL_EVERY => true),
449                       'remove' => array(ACL_ADMIN => true,
450                                         ACL_OWNER => true),
451                       'dump'   => array(ACL_ADMIN => true,
452                                         ACL_OWNER => true),
453                       'change' => array(ACL_ADMIN => true,
454                                         ACL_OWNER => true));
455         if (ZIPDUMP_AUTH)
456             $perm['dump'] = array(ACL_ADMIN => true,
457                                   ACL_OWNER => true);
458         elseif (INSECURE_ACTIONS_LOCALHOST_ONLY) {
459             if (is_localhost())
460                 $perm['dump'] = array(ACL_EVERY => true);
461             else
462                 $perm['dump'] = array(ACL_ADMIN => true);
463         }
464         else
465             $perm['dump'] = array(ACL_EVERY => true);
466         if (defined('REQUIRE_SIGNIN_BEFORE_EDIT') && REQUIRE_SIGNIN_BEFORE_EDIT)
467             $perm['edit'] = array(ACL_SIGNED => true);
468         // view:
469         if (!ALLOW_ANON_USER) {
470             if (!ALLOW_USER_PASSWORDS) 
471                 $perm['view'] = array(ACL_SIGNED => true);
472             else                
473                 $perm['view'] = array(ACL_AUTHENTICATED => true);
474             $perm['view'][ACL_BOGOUSER] = ALLOW_BOGO_LOGIN ? true : false;
475         }
476         // edit:
477         if (!ALLOW_ANON_EDIT) {
478             if (!ALLOW_USER_PASSWORDS) 
479                 $perm['edit'] = array(ACL_SIGNED => true);
480             else                
481                 $perm['edit'] = array(ACL_AUTHENTICATED => true);
482             $perm['edit'][ACL_BOGOUSER] = ALLOW_BOGO_LOGIN ? true : false;
483             $perm['create'] = $perm['edit'];
484         }
485         return $perm;
486     }
487
488     /**
489      * FIXME: check valid groups and access
490      */
491     function sanify() {
492         foreach ($this->perm as $access => $groups) {
493             foreach ($groups as $group => $bool) {
494                 $this->perm[$access][$group] = (boolean) $bool;
495             }
496         }
497     }
498
499     /**
500      * do a recursive comparison
501      */
502     function equal($otherperm) {
503         $diff = array_diff_assoc_recursive($this->perm, $otherperm);
504         return empty($diff);
505     }
506     
507     /**
508      * returns list of all supported access types.
509      */
510     function accessTypes() {
511         return array_keys(PagePermission::defaultPerms());
512     }
513
514     /**
515      * special permissions for dot-files, beginning with '.'
516      * maybe also for '_' files?
517      */
518     function dotPerms() {
519         $def = array(ACL_ADMIN => true,
520                      ACL_OWNER => true);
521         $perm = array();
522         foreach (PagePermission::accessTypes() as $access) {
523             $perm[$access] = $def;
524         }
525         return $perm;
526     }
527
528     /**
529      *  dead code. not needed inside the object. see getPagePermissions($page)
530      */
531     function retrieve($page) {
532         $hash = $page->get('perm');
533         if ($hash)  // hash => object
534             $perm = new PagePermission(unserialize($hash));
535         else 
536             $perm = new PagePermission();
537         $perm->sanify();
538         return $perm;
539     }
540
541     function store($page) {
542         // object => hash
543         $this->sanify();
544         return $page->set('perm',serialize($this->perm));
545     }
546
547     function groupName ($group) {
548         if ($group[0] == '_') return constant("GROUP".$group);
549         else return $group;
550     }
551     
552     /* type: page, default, inherited */
553     function asTable($type) {
554         $table = HTML::table();
555         foreach ($this->perm as $access => $perms) {
556             $td = HTML::table(array('class' => 'cal','valign' => 'top'));
557             foreach ($perms as $group => $bool) {
558                 $td->pushContent(HTML::tr(HTML::td(array('align'=>'right'),$group),
559                                                    HTML::td($bool ? '[X]' : '[ ]')));
560             }
561             $table->pushContent(HTML::tr(array('valign' => 'top'),
562                                          HTML::td($access),HTML::td($td)));
563         }
564         if ($type == 'default')
565             $table->setAttr('style','border: dotted thin black; background-color:#eee;');
566         elseif ($type == 'inherited')
567             $table->setAttr('style','border: dotted thin black; background-color:#ddd;');
568         elseif ($type == 'page')
569             $table->setAttr('style','border: solid thin black; font-weight: bold;');
570         return $table;
571     }
572     
573     /* type: page, default, inherited */
574     function asEditableTable($type) {
575         global $WikiTheme;
576         if (!isset($this->_group)) { 
577             $this->_group =& $GLOBALS['request']->getGroup();
578         }
579         $table = HTML::table();
580         $table->pushContent(HTML::tr(
581                                      HTML::th(array('align' => 'left'),
582                                               _("Access")),
583                                      HTML::th(array('align'=>'right'),
584                                               _("Group/User")),
585                                      HTML::th(_("Grant")),
586                                      HTML::th(_("Del/+")),
587                                      HTML::th(_("Description"))));
588         
589         $allGroups = $this->_group->_specialGroups();
590         foreach ($this->_group->getAllGroupsIn() as $group) {
591             if (!in_array($group,$this->_group->specialGroups()))
592                 $allGroups[] = $group;
593         }
594         //array_unique(array_merge($this->_group->getAllGroupsIn(),
595         $deletesrc = $WikiTheme->_findData('images/delete.png');
596         $addsrc = $WikiTheme->_findData('images/add.png');
597         $nbsp = HTML::raw('&nbsp;');
598         foreach ($this->perm as $access => $groups) {
599             //$permlist = HTML::table(array('class' => 'cal','valign' => 'top'));
600             $first_only = true;
601             $newperm = HTML::input(array('type' => 'checkbox',
602                                          'name' => "acl[_new_perm][$access]",
603                                          'value' => 1));
604             $addbutton = HTML::input(array('type' => 'checkbox',
605                                            'name' => "acl[_add_group][$access]",
606                                            //'src'  => $addsrc,
607                                            //'alt'   => "Add",
608                                            'title' => _("Add this ACL"),
609                                            'value' => 1));
610             $newgroup = HTML::select(array('name' => "acl[_new_group][$access]",
611                                            'style'=> 'text-align: right;',
612                                            'size' => 1));
613             foreach ($allGroups as $groupname) {
614                 if (!isset($groups[$groupname]))
615                     $newgroup->pushContent(HTML::option(array('value' => $groupname),
616                                                         $this->groupName($groupname)));
617             }
618             if (empty($groups)) {
619                 $addbutton->setAttr('checked','checked');
620                 $newperm->setAttr('checked','checked');
621                 $table->pushContent(
622                     HTML::tr(array('valign' => 'top'),
623                              HTML::td(HTML::strong($access.":")),
624                              HTML::td($newgroup),
625                              HTML::td($nbsp,$newperm),
626                              HTML::td($nbsp,$addbutton),
627                              HTML::td(HTML::em(getAccessDescription($access)))));
628             }
629             foreach ($groups as $group => $bool) {
630                 $checkbox = HTML::input(array('type' => 'checkbox',
631                                               'name' => "acl[$access][$group]",
632                                               'title' => _("Allow / Deny"),
633                                               'value' => 1));
634                 if ($bool) $checkbox->setAttr('checked','checked');
635                 $checkbox = HTML(HTML::input(array('type' => 'hidden',
636                                                    'name' => "acl[$access][$group]",
637                                                    'value' => 0)),
638                                  $checkbox);
639                 $deletebutton = HTML::input(array('type' => 'checkbox',
640                                                   'name' => "acl[_del_group][$access][$group]",
641                                                   'style' => 'background: #aaa url('.$deletesrc.')',
642                                                   //'src'  => $deletesrc,
643                                                   //'alt'   => "Del",
644                                                   'title' => _("Delete this ACL"),
645                                                   'value' => 1));
646                 if ($first_only) {
647                     $table->pushContent(
648                         HTML::tr(
649                                  HTML::td(HTML::strong($access.":")),
650                                  HTML::td(array('class' => 'cal-today','align'=>'right'),
651                                           HTML::strong($this->groupName($group))),
652                                  HTML::td(array('align'=>'center'),$nbsp,$checkbox),
653                                  HTML::td(array('align'=>'right','style' => 'background: #aaa url('.$deletesrc.') no-repeat'),$deletebutton),
654                                  HTML::td(HTML::em(getAccessDescription($access)))));
655                     $first_only = false;
656                 } else {
657                     $table->pushContent(
658                         HTML::tr(
659                                  HTML::td(),
660                                  HTML::td(array('class' => 'cal-today','align'=>'right'),
661                                           HTML::strong($this->groupName($group))),
662                                  HTML::td(array('align'=>'center'),$nbsp,$checkbox),
663                                  HTML::td(array('align'=>'right','style' => 'background: #aaa url('.$deletesrc.') no-repeat'),$deletebutton),
664                                  HTML::td()));
665                 }
666             }
667             if (!empty($groups))
668                 $table->pushContent(
669                     HTML::tr(array('valign' => 'top'),
670                              HTML::td(array('align'=>'right'),_("add ")),
671                              HTML::td($newgroup),
672                              HTML::td(array('align'=>'center'),$nbsp,$newperm),
673                              HTML::td(array('align'=>'right','style' => 'background: #ccc url('.$addsrc.') no-repeat'),$addbutton),
674                              HTML::td(HTML::small(_("Check to add this ACL")))));
675         }
676         if ($type == 'default')
677             $table->setAttr('style','border: dotted thin black; background-color:#eee;');
678         elseif ($type == 'inherited')
679             $table->setAttr('style','border: dotted thin black; background-color:#ddd;');
680         elseif ($type == 'page')
681             $table->setAttr('style','border: solid thin black; font-weight: bold;');
682         return $table;
683     }
684
685     // Print ACL as lines of [+-]user[,group,...]:access[,access...]
686     // Seperate acl's by "; " or whitespace
687     // See http://opag.ca/wiki/HelpOnAccessControlLists
688     // As used by WikiAdminSetAclSimple
689     function asAclLines() {
690         $s = ''; $line = '';
691         $this->sanify();
692         foreach ($this->perm as $access => $groups) {
693             // unify groups for same access+bool
694             //    view:CREATOR,-OWNER,
695             $line = $access.':';
696             foreach ($groups as $group => $bool) {
697                 $line .= ($bool?'':'-').$group.",";
698             }
699             if (substr($line,-1) == ',')
700                 $s .= substr($line,0,-1)."; ";
701         }
702         if (substr($s,-2) == '; ')
703             $s = substr($s,0,-2);
704         return $s;
705     }
706
707
708     // This is just a bad hack for testing.
709     // Simplify the ACL to a unix-like "rwx------+" string
710     // See getfacl(8)
711     function asRwxString($owner,$group=false) {
712         global $request;
713         // simplify object => rwxrw---x+ string as in cygwin (+ denotes additional ACLs)
714         $perm =& $this->perm;
715         // get effective user and group
716         $s = '---------+';
717         if (isset($perm['view'][$owner]) or 
718             (isset($perm['view'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
719             $s[0] = 'r';
720         if (isset($perm['edit'][$owner]) or 
721             (isset($perm['edit'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
722             $s[1] = 'w';
723         if (isset($perm['change'][$owner]) or 
724             (isset($perm['change'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
725             $s[2] = 'x';
726         if (!empty($group)) {
727             if (isset($perm['view'][$group]) or 
728                 (isset($perm['view'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
729                 $s[3] = 'r';
730             if (isset($perm['edit'][$group]) or 
731                 (isset($perm['edit'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
732                 $s[4] = 'w';
733             if (isset($perm['change'][$group]) or 
734                 (isset($perm['change'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
735                 $s[5] = 'x';
736         }
737         if (isset($perm['view'][ACL_EVERY]) or 
738             (isset($perm['view'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
739             $s[6] = 'r';
740         if (isset($perm['edit'][ACL_EVERY]) or 
741             (isset($perm['edit'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
742             $s[7] = 'w';
743         if (isset($perm['change'][ACL_EVERY]) or 
744             (isset($perm['change'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
745             $s[8] = 'x';
746         return $s;
747     }
748 }
749
750 // $Log: not supported by cvs2svn $
751 // Revision 1.44  2007/09/12 19:34:31  rurban
752 // revise INSECURE_ACTIONS_LOCALHOST_ONLY actions
753 //
754 // Revision 1.43  2007/09/01 13:24:23  rurban
755 // add INSECURE_ACTIONS_LOCALHOST_ONLY. advanced security settings
756 //
757 // Revision 1.42  2007/08/25 18:03:34  rurban
758 // change rename action from access perm change to edit: allow the signed in user to rename.
759 //
760 // Revision 1.41  2007/07/14 12:03:25  rurban
761 // fix for mult. group membership: not a member and undecided: check other groups
762 //
763 // Revision 1.40  2005/10/29 14:16:58  rurban
764 // unify message
765 //
766 // Revision 1.39  2005/05/06 16:57:54  rurban
767 // support captcha
768 //
769 // Revision 1.38  2004/11/30 17:48:38  rurban
770 // just comments
771 //
772 // Revision 1.37  2004/11/23 13:06:30  rurban
773 // several fixes and suggestions by Charles Corrigan:
774 // * fix GROUP_BOGO_USER check
775 // * allow group pages to have the link to the user page in [ ] brackets
776 // * fix up the implementation of GroupWikiPage::getMembersOf and allow the
777 //   user page to be linked in [ ] brackets
778 // * added _OWNER and _CREATOR to special wikigroups
779 // * check against those two for group membership also, not only the user.
780 //
781 // Revision 1.36  2004/11/21 11:59:16  rurban
782 // remove final \n to be ob_cache independent
783 //
784 // Revision 1.35  2004/11/15 15:56:40  rurban
785 // don't load PagePerm on ENABLE_PAGEPERM = false to save memory. Move mayAccessPage() to main.php
786 //
787 // Revision 1.34  2004/11/01 10:43:55  rurban
788 // seperate PassUser methods into seperate dir (memory usage)
789 // fix WikiUser (old) overlarge data session
790 // remove wikidb arg from various page class methods, use global ->_dbi instead
791 // ...
792 //
793 // Revision 1.33  2004/09/26 11:47:52  rurban
794 // fix another reecursion loop when . exists: deny if ACL not defined; implement pageperm cache
795 //
796 // Revision 1.32  2004/09/25 18:56:09  rurban
797 // avoid recursion bug on setacl for "."
798 //
799 // Revision 1.31  2004/09/25 18:34:45  rurban
800 // fix and warn on too restrictive ACL handling without ACL in existing . (dotpage)
801 //
802 // Revision 1.30  2004/09/25 16:24:02  rurban
803 // fix interesting PagePerm problem: -1 == true
804 //
805 // Revision 1.29  2004/07/03 08:04:19  rurban
806 // fixed implicit PersonalPage login (e.g. on edit), fixed to check against create ACL on create, not edit
807 //
808 // Revision 1.28  2004/06/25 14:29:17  rurban
809 // WikiGroup refactoring:
810 //   global group attached to user, code for not_current user.
811 //   improved helpers for special groups (avoid double invocations)
812 // new experimental config option ENABLE_XHTML_XML (fails with IE, and document.write())
813 // fixed a XHTML validation error on userprefs.tmpl
814 //
815 // Revision 1.27  2004/06/16 10:38:58  rurban
816 // Disallow refernces in calls if the declaration is a reference
817 // ("allow_call_time_pass_reference clean").
818 //   PhpWiki is now allow_call_time_pass_reference = Off clean,
819 //   but several external libraries may not.
820 //   In detail these libs look to be affected (not tested):
821 //   * Pear_DB odbc
822 //   * adodb oracle
823 //
824 // Revision 1.26  2004/06/14 11:31:36  rurban
825 // renamed global $Theme to $WikiTheme (gforge nameclash)
826 // inherit PageList default options from PageList
827 //   default sortby=pagename
828 // use options in PageList_Selectable (limit, sortby, ...)
829 // added action revert, with button at action=diff
830 // added option regex to WikiAdminSearchReplace
831 //
832 // Revision 1.25  2004/06/08 13:51:57  rurban
833 // some comments only
834 //
835 // Revision 1.24  2004/06/08 10:54:46  rurban
836 // better acl dump representation, read back acl and owner
837 //
838 // Revision 1.23  2004/06/08 10:05:11  rurban
839 // simplified admin action shortcuts
840 //
841 // Revision 1.22  2004/06/07 22:44:14  rurban
842 // added simplified chown, setacl actions
843 //
844 // Revision 1.21  2004/06/07 22:28:03  rurban
845 // add acl field to mimified dump
846 //
847 // Revision 1.20  2004/06/07 18:39:03  rurban
848 // support for SetAclSimple
849 //
850 // Revision 1.19  2004/06/06 17:12:28  rurban
851 // fixed PagePerm non-object problem (mayAccessPage), also bug #967150
852 //
853 // Revision 1.18  2004/05/27 17:49:05  rurban
854 // renamed DB_Session to DbSession (in CVS also)
855 // added WikiDB->getParam and WikiDB->getAuthParam method to get rid of globals
856 // remove leading slash in error message
857 // added force_unlock parameter to File_Passwd (no return on stale locks)
858 // fixed adodb session AffectedRows
859 // added FileFinder helpers to unify local filenames and DATA_PATH names
860 // editpage.php: new edit toolbar javascript on ENABLE_EDIT_TOOLBAR
861 //
862 // Revision 1.17  2004/05/16 23:10:44  rurban
863 // update_locale wrongly resetted LANG, which broke japanese.
864 // japanese now correctly uses EUC_JP, not utf-8.
865 // more charset and lang headers to help the browser.
866 //
867 // Revision 1.16  2004/05/16 22:32:53  rurban
868 // setacl icons
869 //
870 // Revision 1.15  2004/05/16 22:07:35  rurban
871 // check more config-default and predefined constants
872 // various PagePerm fixes:
873 //   fix default PagePerms, esp. edit and view for Bogo and Password users
874 //   implemented Creator and Owner
875 //   BOGOUSERS renamed to BOGOUSER
876 // fixed syntax errors in signin.tmpl
877 //
878 // Revision 1.14  2004/05/15 22:54:49  rurban
879 // fixed important WikiDB bug with DEBUG > 0: wrong assertion
880 // improved SetAcl (works) and PagePerms, some WikiGroup helpers.
881 //
882 // Revision 1.13  2004/05/15 19:48:33  rurban
883 // fix some too loose PagePerms for signed, but not authenticated users
884 //  (admin, owner, creator)
885 // no double login page header, better login msg.
886 // moved action_pdf to lib/pdf.php
887 //
888 // Revision 1.12  2004/05/04 22:34:25  rurban
889 // more pdf support
890 //
891 // Revision 1.11  2004/05/02 21:26:38  rurban
892 // limit user session data (HomePageHandle and auth_dbi have to invalidated anyway)
893 //   because they will not survive db sessions, if too large.
894 // extended action=upgrade
895 // some WikiTranslation button work
896 // revert WIKIAUTH_UNOBTAINABLE (need it for main.php)
897 // some temp. session debug statements
898 //
899 // Revision 1.10  2004/04/29 22:32:56  zorloc
900 // Slightly more elegant fix.  Instead of WIKIAUTH_FORBIDDEN, the current user's level + 1 is returned on a false.
901 //
902 // Revision 1.9  2004/04/29 17:18:19  zorloc
903 // Fixes permission failure issues.  With PagePermissions and Disabled Actions when user did not have permission WIKIAUTH_FORBIDDEN was returned.  In WikiUser this was ok because WIKIAUTH_FORBIDDEN had a value of 11 -- thus no user could perform that action.  But WikiUserNew has a WIKIAUTH_FORBIDDEN value of -1 -- thus a user without sufficent permission to do anything.  The solution is a new high value permission level (WIKIAUTH_UNOBTAINABLE) to be the default level for access failure.
904 //
905 // Revision 1.8  2004/03/14 16:24:35  rurban
906 // authenti(fi)cation spelling
907 //
908 // Revision 1.7  2004/02/28 22:25:07  rurban
909 // First PagePerm implementation:
910 //
911 // $WikiTheme->setAnonEditUnknownLinks(false);
912 //
913 // Layout improvement with dangling links for mostly closed wiki's:
914 // If false, only users with edit permissions will be presented the
915 // special wikiunknown class with "?" and Tooltip.
916 // If true (default), any user will see the ?, but will be presented
917 // the PrintLoginForm on a click.
918 //
919 // Revision 1.6  2004/02/24 15:20:05  rurban
920 // fixed minor warnings: unchecked args, POST => Get urls for sortby e.g.
921 //
922 // Revision 1.5  2004/02/23 21:30:25  rurban
923 // more PagePerm stuff: (working against 1.4.0)
924 //   ACL editing and simplification of ACL's to simple rwx------ string
925 //   not yet working.
926 //
927 // Revision 1.4  2004/02/12 13:05:36  rurban
928 // Rename functional for PearDB backend
929 // some other minor changes
930 // SiteMap comes with a not yet functional feature request: includepages (tbd)
931 //
932 // Revision 1.3  2004/02/09 03:58:12  rurban
933 // for now default DB_SESSION to false
934 // PagePerm:
935 //   * not existing perms will now query the parent, and not
936 //     return the default perm
937 //   * added pagePermissions func which returns the object per page
938 //   * added getAccessDescription
939 // WikiUserNew:
940 //   * added global ->prepare (not yet used) with smart user/pref/member table prefixing.
941 //   * force init of authdbh in the 2 db classes
942 // main:
943 //   * fixed session handling (not triple auth request anymore)
944 //   * don't store cookie prefs with sessions
945 // stdlib: global obj2hash helper from _AuthInfo, also needed for PagePerm
946 //
947 // Revision 1.2  2004/02/08 13:17:48  rurban
948 // This should be the functionality. Needs testing and some minor todos.
949 //
950 // Revision 1.1  2004/02/08 12:29:30  rurban
951 // initial version, not yet hooked into lib/main.php
952 //
953 //
954
955 // Local Variables:
956 // mode: php
957 // tab-width: 8
958 // c-basic-offset: 4
959 // c-hanging-comment-ender-p: nil
960 // indent-tabs-mode: nil
961 // End:
962 ?>