]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/PagePerm.php
fixed minor warnings: unchecked args, POST => Get urls for sortby e.g.
[SourceForge/phpwiki.git] / lib / PagePerm.php
1 <?php // -*-php-*-
2 rcs_id('$Id: PagePerm.php,v 1.6 2004-02-24 15:20:05 rurban Exp $');
3 /*
4  Copyright 2004 $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 maybe inherited its parent pages, and ultimativly the 
30    optional master page (".")
31    Pagenames starting with "." have special default permissions.
32    For Authentification see WikiUserNew.php, WikiGroup.php and main.php
33    Page Permssions are in PhpWiki since v1.3.9 and enabled since v1.4.0
34
35    This file might replace the following functions from main.php:
36      Request::_notAuthorized($require_level)
37        display the denied message and optionally a login form 
38        to gain higher privileges
39      Request::getActionDescription($action)
40        helper to localize the _notAuthorized message per action, 
41        when login is tried.
42      Request::getDisallowedActionDescription($action)
43        helper to localize the _notAuthorized message per action, 
44        when it aborts
45      Request::requiredAuthority($action)
46        returns the needed user level
47        has a hook for plugins on POST
48      Request::requiredAuthorityForAction($action)
49        just returns the level per action, will be replaced with the 
50        action + page pair
51
52      The defined main.php actions map to simplier access types:
53        browse => view
54        edit   => edit
55        create => edit or create
56        remove => remove
57        rename => change
58        store prefs => change
59        list in PageList => list
60 */
61
62 /* Symbolic special ACL groups. Untranslated to be stored in page metadata*/
63 define('ACL_EVERY',        '_EVERY');
64 define('ACL_ANONYMOUS',    '_ANONYMOUS');
65 define('ACL_BOGOUSERS',    '_BOGOUSERS');
66 define('ACL_HASHOMEPAGE',  '_HASHOMEPAGE');
67 define('ACL_SIGNED',       '_SIGNED');
68 define('ACL_AUTHENTICATED','_AUTHENTICATED');
69 define('ACL_ADMIN',        '_ADMIN');
70 define('ACL_OWNER',        '_OWNER');
71 define('ACL_CREATOR',      '_CREATOR');
72
73 // Return an page permissions array for this page.
74 // To provide ui helpers to view and change page permissions:
75 //   <tr><th>Group</th><th>Access</th><th>Allow or Forbid</th></tr>
76 //   <tr><td>$group</td><td>_($access)</td><td> [ ] </td></tr>
77 function pagePermissions($pagename) {
78     global $request;
79     $page = $request->getPage($pagename);
80     // Page not found (new page); returned inherited permissions, to be displayed in gray
81     if (! $page->exists() ) {
82         if ($pagename == '.') // stop recursion
83             return array('default',new PagePermission());
84         else {
85             return array('inherited',pagePermissions(getParentPage($pagename)));
86         }
87     } elseif ($perm = getPagePermissions($page)) {
88         return array('page',$perm);
89     // or no permissions defined; returned inherited permissions, to be displayed in gray
90     } else {
91         return array('inherited',pagePermissions(getParentPage($pagename)));
92     }
93 }
94
95 function pagePermissionsSimpleFormat($perm_tree,$owner,$group=false) {
96     list($type,$perm) = pagePermissionsAcl($perm_tree[0], $perm_tree);
97     /*
98     $type = $perm_tree[0];
99     $perm = pagePermissionsAcl($perm_tree);
100     if (is_object($perm_tree[1]))
101         $perm = $perm_tree[1];
102     elseif (is_array($perm_tree[1])) {
103         $perm_tree = pagePermissionsSimpleFormat($perm_tree[1],$owner,$group);
104         if (isa($perm_tree[1],'pagepermission'))
105             $perm = $perm_tree[1];
106         elseif (isa($perm_tree,'htmlelement'))
107             return $perm_tree;
108     }
109     */
110     if ($type == 'page')
111         return HTML::tt(HTML::bold($perm->asRwxString($owner,$group).'+'));
112     elseif ($type == 'default')
113         return HTML::tt($perm->asRwxString($owner,$group));
114     elseif ($type == 'inherited') {
115         return HTML::tt(array('class'=>'inherited','style'=>'color:#aaa;'),
116                         $perm->asRwxString($owner,$group));
117     }
118 }
119
120 function pagePermissionsAcl($type,$perm_tree) {
121     $perm = $perm_tree[1];
122     while (!is_object($perm)) {
123         $perm_tree = pagePermissionsAcl($type, $perm);
124         $perm = $perm_tree[1];
125     }
126     return array($type,$perm);
127 }
128
129 // view => who
130 // edit => who
131 function pagePermissionsAclFormat($perm_tree,$editable=false) {
132     list($type,$perm) = pagePermissionsAcl($perm_tree[0], $perm_tree);
133     if ($editable)
134         return $perm->asEditableTable($type);
135     else
136         return $perm->asTable($type);
137 }
138
139
140 // Check the permissions for the current action.
141 // Walk down the inheritance tree. Collect all permissions until 
142 // the minimum required level is gained, which is not 
143 // overruled by more specific forbid rules.
144 // Todo: cache result per access and page in session?
145 function requiredAuthorityForPage ($action) {
146     if (_requiredAuthorityForPagename(action2access($action),
147                                       $GLOBALS['request']->getArg('pagename')))
148         return $GLOBALS['request']->_user->_level;
149     else
150         return WIKIAUTH_FORBIDDEN;
151 }
152
153 // Translate action or plugin to the simplier access types:
154 function action2access ($action) {
155     global $request;
156     switch ($action) {
157     case 'browse':
158     case 'viewsource':
159     case 'diff':
160     case 'select':
161     case 'xmlrpc':
162     case 'search':
163         return 'view';
164     case 'zip':
165     case 'ziphtml':
166     case 'dumpserial':
167     case 'dumphtml':
168         return 'dump';
169     case 'edit':
170         return 'edit';
171     case 'create':
172         $page = $request->getPage();
173         $current = $page->getCurrentRevision();
174         if ($current->hasDefaultContents())
175             return 'edit';
176         else
177             return 'view'; 
178         break;
179     case 'upload':
180     case 'loadfile': 
181         // probably create/edit but we cannot check all page permissions, can we?
182     case 'remove':
183     case 'lock':
184     case 'unlock':
185             return 'change';
186     default:
187         //Todo: Plugins should be able to override its access type
188         if (isWikiWord($action))
189             return 'view';
190         else
191             return 'change';
192         break;
193     }
194 }
195
196 // Recursive helper to do the real work
197 function _requiredAuthorityForPagename($access, $pagename) {
198     global $request;
199     $page = $request->getPage($pagename);
200     // Page not found; check against default permissions
201     if (! $page->exists() ) {
202         $perm = new PagePermission();
203         return ($perm->isAuthorized($access,$request->_user) === true);
204     }
205     // no ACL defined; check for special dotfile or walk down
206     if (! ($perm = getPagePermissions($page))) { 
207         if ($pagename[0] == '.') {
208             $perm = new PagePermission(PagePermission::dotPerms());
209             return ($perm->isAuthorized($access,$request->_user) === true);
210         }
211         return _requiredAuthorityForPagename($access,getParentPage($pagename));
212     }
213     // ACL defined; check if isAuthorized returns true or false or undecided
214     $authorized = $perm->isAuthorized($access,$request->_user);
215     if ($authorized != -1) // -1 for undecided
216         return $authorized;
217     else
218         return _requiredAuthorityForPagename($access,getParentPage($pagename));
219 }
220
221 /**
222  * @param  string $pagename   page from which the parent page is searched.
223  * @return string parent      pagename or the (possibly pseudo) dot-pagename.
224  */
225 function getParentPage($pagename) {
226     if (isSubPage($pagename)) {
227         return subPageSlice($pagename,0);
228     } else {
229         return '.';
230     }
231 }
232
233 // Read the ACL from the page
234 // Done: Not existing pages should NOT be queried. 
235 // Check the parent page instead and don't take the default ACL's
236 function getPagePermissions ($page) {
237     if ($hash = $page->get('perm'))  // hash => object
238         return new PagePermission(unserialize($hash));
239     else 
240         return false;
241 }
242
243 // Store the ACL in the page
244 function setPagePermissions ($page,$perm) {
245     $perm->store($page);
246 }
247
248 function getAccessDescription($access) {
249     static $accessDescriptions;
250     if (! $accessDescriptions) {
251         $accessDescriptions = array(
252                                     'list'     => _("List this page and all subpages"),
253                                     'view'     => _("View this page and all subpages"),
254                                     'edit'     => _("Edit this page and all subpages"),
255                                     'create'   => _("Create a new (sub)page"),
256                                     'dump'     => _("Download the page contents"),
257                                     'change'   => _("Change page attributes"),
258                                     'remove'   => _("Remove this page"),
259                                     );
260     }
261     if (in_array($access, array_keys($accessDescriptions)))
262         return $accessDescriptions[$access];
263     else
264         return $access;
265 }
266
267 /**
268  * The ACL object per page. It is stored in a page, but can also 
269  * be merged with ACL's from other pages or taken from the master (pseudo) dot-file.
270  *
271  * A hash of "access" => "requires" pairs.
272  *   "access"   is a shortcut for common actions, which map to main.php actions
273  *   "requires" required username or groupname or any special group => true or false
274  *
275  * Define any special rules here, like don't list dot-pages.
276  */ 
277 class PagePermission {
278     var $perm;
279
280     function PagePermission($hash = array()) {
281         if (is_array($hash) and !empty($hash)) {
282             $accessTypes = $this->accessTypes();
283             foreach ($hash as $access => $requires) {
284                 if (in_array($access,$accessTypes))
285                     $this->perm[$access] = $requires;
286                 else
287                     trigger_error(sprintf(_("Unsupported ACL access type %s ignored."),$access),
288                                   E_USER_WARNING);
289             }
290         } else {
291             // set default permissions, the so called dot-file acl's
292             $this->perm = $this->defaultPerms();
293         }
294         return $this;
295     }
296
297     /**
298      * The workhorse to check the user against the current ACL pairs.
299      * Must translate the various special groups to the actual users settings 
300      * (userid, group membership).
301      */
302     function isAuthorized($access,$user) {
303         if (!empty($this->perm{$access})) {
304             foreach ($this->perm[$access] as $group => $bool) {
305                 if ($this->isMember($user,$group))
306                     return $bool;
307             }
308         }
309         return -1; // undecided
310     }
311
312     /**
313      * Translate the various special groups to the actual users settings 
314      * (userid, group membership).
315      */
316     function isMember($user,$group) {
317         global $request;
318         if ($group === ACL_EVERY) return true;
319         $member = &WikiGroup::getGroup($request);
320         //$user = & $request->_user;
321         if ($group === ACL_ADMIN)   // WIKI_ADMIN or member of _("Administrators")
322             return $user->isAdmin() or
323                    $member->isMember(GROUP_ADMIN);
324         if ($group === ACL_ANONYMOUS) 
325             return ! $user->isSignedIn();
326         if ($group === ACL_BOGOUSERS)
327             if (ENABLE_USER_NEW) return isa($user,'_BogoUser');
328             else return isWikiWord($user->UserName());
329         if ($group === ACL_HASHOMEPAGE)
330             return $user->hasHomePage();
331         if ($group === ACL_SIGNED)
332             return $user->isSignedIn();
333         if ($group === ACL_AUTHENTICATED)
334             return $user->isAuthenticated();
335         if ($group === ACL_OWNER) {
336             $page = $request->getPage();
337             return $page->get('author') === $user->UserName();
338         }
339         if ($group === ACL_CREATOR) {
340             $page = $request->getPage();
341             $rev = $page->getRevision(1);
342             return $rev->get('author') === $user->UserName();
343         }
344         /* Or named groups or usernames.
345          Note: We don't seperate groups and users here. 
346          Users overrides groups with the same name. */
347         return $user->UserName() === $group or
348                $member->isMember($group);
349     }
350
351     /**
352      * returns hash of default permissions.
353      * check if the page '.' exists and returns this instead.
354      */
355     function defaultPerms() {
356         //Todo: check for the existance of '.' and take this instead.
357         //Todo: honor more index.php auth settings here
358         $perm = array('view'   => array(ACL_EVERY => true),
359                       'edit'   => array(ACL_EVERY => true),
360                       'create' => array(ACL_EVERY => true),
361                       'list'   => array(ACL_EVERY => true),
362                       'remove' => array(ACL_ADMIN => true,
363                                         ACL_OWNER => true),
364                       'change' => array(ACL_ADMIN => true,
365                                         ACL_OWNER => true));
366         if (defined('ZIPDUMP_AUTH') && ZIPDUMP_AUTH)
367             $perm['dump'] = array(ACL_ADMIN => true,
368                                   ACL_OWNER => true);
369         else
370             $perm['dump'] = array(ACL_EVERY => true);
371         if (defined('REQUIRE_SIGNIN_BEFORE_EDIT') && REQUIRE_SIGNIN_BEFORE_EDIT)
372             $perm['edit'] = array(ACL_SIGNED => true);
373         if (defined('ALLOW_ANON_USER') && ! ALLOW_ANON_USER) {
374             if (defined('ALLOW_BOGO_USER') && ALLOW_BOGO_USER) {
375                 $perm['view'] = array(ACL_BOGOUSER => true);
376                 $perm['edit'] = array(ACL_BOGOUSER => true);
377             } elseif (defined('ALLOW_USER_PASSWORDS') && ALLOW_USER_PASSWORDS) {
378                 $perm['view'] = array(ACL_AUTHENTICATED => true);
379                 $perm['edit'] = array(ACL_AUTHENTICATED => true);
380             } else {
381                 $perm['view'] = array(ACL_SIGNED => true);
382                 $perm['edit'] = array(ACL_SIGNED => true);
383             }
384         }
385         return $perm;
386     }
387
388     /**
389      * returns list of all supported access types.
390      */
391     function accessTypes() {
392         return array_keys($this->defaultPerms());
393     }
394
395     /**
396      * special permissions for dot-files, beginning with '.'
397      * maybe also for '_' files?
398      */
399     function dotPerms() {
400         $def = array(ACL_ADMIN => true,
401                      ACL_OWNER => true);
402         $perm = array();
403         foreach ($this->accessTypes as $access) {
404             $perm[$access] = $def;
405         }
406         return $perm;
407     }
408
409     /**
410      *  dead code. not needed inside the object. see getPagePermissions($page)
411      */
412     function retrieve($page) {
413         $hash = $page->get('perm');
414         if ($hash)  // hash => object
415             return new PagePermission(unserialize($hash));
416         else 
417             return new PagePermission();
418     }
419
420     function store($page) {
421         // object => hash
422         return $page->set('perm',serialize(obj2hash($this->perm)));
423     }
424
425     /* type: page, default, inherited */
426     function asTable($type) {
427         $table = HTML::table();
428         foreach ($this->perm as $access => $perms) {
429             $td = HTML::table(array('class' => 'cal','valign' => 'top'));
430             foreach ($perms as $group => $bool) {
431                 $td->pushContent(HTML::tr(HTML::td(array('align'=>'right'),$group),
432                                                    HTML::td($bool ? '[X]' : '[ ]')));
433             }
434             $table->pushContent(HTML::tr(array('valign' => 'top'),
435                                          HTML::td($access),HTML::td($td)));
436         }
437         if ($type == 'default')
438             $table->setAttr('style','border: dotted thin black; background-color:#eee;');
439         elseif ($type == 'inherited')
440             $table->setAttr('style','border: dotted thin black; background-color:#ddd;');
441         elseif ($type == 'page')
442             $table->setAttr('style','border: solid thin black; font-weight: bold;');
443         return $table;
444     }
445
446     /* type: page, default, inherited */
447     function asEditableTable($type) {
448         $table = HTML::table();
449         $table->pushContent(HTML::tr(array('valign' => 'top'),
450                                      HTML::th(array('align' => 'left'),'access'),
451                                      HTML::th(array('align'=>'right'),
452                                               'Group/User'),
453                                      HTML::th(),
454                                      HTML::th('Description')));
455         foreach ($this->perm as $access => $perms) {
456             //$permlist = HTML::table(array('class' => 'cal','valign' => 'top'));
457             $first_only = true;
458             foreach ($perms as $group => $bool) {
459                 $checkbox = HTML::input(array('type' => 'checkbox',
460                                               'name' => "acl[$access][$group]",
461                                               'value' => true));
462                 if ($bool) $checkbox->setAttr('checked','checked');
463                 if ($first_only) {
464                     $table->pushContent(HTML::tr(array('valign' => 'top'),
465                                                  HTML::td(HTML::strong($access.":")),
466                                                  HTML::td(array('class' => 'cal-today','align'=>'right'),
467                                                           constant("GROUP".$group)),
468                                                  HTML::td($checkbox),
469                                                  HTML::td(HTML::em(getAccessDescription($access)))));
470                     $first_only = false;
471                 } else {
472                     $table->pushContent(HTML::tr(array('valign' => 'top'),
473                                                  HTML::td(),
474                                                  HTML::td(array('class' => 'cal-today','align'=>'right'),
475                                                           constant("GROUP".$group)),
476                                                  HTML::td($checkbox),
477                                                  HTML::td()));
478                 }
479             }
480             /*
481             $table->pushContent(HTML::tr(array('valign' => 'top'),
482                                          HTML::td(HTML::strong($access)),
483                                          HTML::td($permlist),
484                                          HTML::td(HTML::em(getAccessDescription($access)))));
485             */
486         }
487         if ($type == 'default')
488             $table->setAttr('style','border: dotted thin black; background-color:#eee;');
489         elseif ($type == 'inherited')
490             $table->setAttr('style','border: dotted thin black; background-color:#ddd;');
491         elseif ($type == 'page')
492             $table->setAttr('style','border: solid thin black; font-weight: bold;');
493         return $table;
494     }
495
496     // this is just a bad hack for testing
497     // simplify the ACL to a unix-like "rwx------" string
498     function asRwxString($owner,$group=false) {
499         global $request;
500         // simplify object => rwxrw---x+ string as in cygwin (+ denotes additional ACLs)
501         $perm =& $this->perm;
502         // get effective user and group
503         $s = '---------';
504         if (isset($perm['view'][$owner]) or 
505             (isset($perm['view'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
506             $s[0] = 'r';
507         if (isset($perm['edit'][$owner]) or 
508             (isset($perm['edit'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
509             $s[1] = 'w';
510         if (isset($perm['change'][$owner]) or 
511             (isset($perm['change'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
512             $s[2] = 'x';
513         if (!empty($group)) {
514             if (isset($perm['view'][$group]) or 
515                 (isset($perm['view'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
516                 $s[3] = 'r';
517             if (isset($perm['edit'][$group]) or 
518                 (isset($perm['edit'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
519                 $s[4] = 'w';
520             if (isset($perm['change'][$group]) or 
521                 (isset($perm['change'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
522                 $s[5] = 'x';
523         }
524         if (isset($perm['view'][ACL_EVERY]) or 
525             (isset($perm['view'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
526             $s[6] = 'r';
527         if (isset($perm['edit'][ACL_EVERY]) or 
528             (isset($perm['edit'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
529             $s[7] = 'w';
530         if (isset($perm['change'][ACL_EVERY]) or 
531             (isset($perm['change'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
532             $s[8] = 'x';
533         return $s;
534     }
535 }
536
537 // $Log: not supported by cvs2svn $
538 // Revision 1.5  2004/02/23 21:30:25  rurban
539 // more PagePerm stuff: (working against 1.4.0)
540 //   ACL editing and simplification of ACL's to simple rwx------ string
541 //   not yet working.
542 //
543 // Revision 1.4  2004/02/12 13:05:36  rurban
544 // Rename functional for PearDB backend
545 // some other minor changes
546 // SiteMap comes with a not yet functional feature request: includepages (tbd)
547 //
548 // Revision 1.3  2004/02/09 03:58:12  rurban
549 // for now default DB_SESSION to false
550 // PagePerm:
551 //   * not existing perms will now query the parent, and not
552 //     return the default perm
553 //   * added pagePermissions func which returns the object per page
554 //   * added getAccessDescription
555 // WikiUserNew:
556 //   * added global ->prepare (not yet used) with smart user/pref/member table prefixing.
557 //   * force init of authdbh in the 2 db classes
558 // main:
559 //   * fixed session handling (not triple auth request anymore)
560 //   * don't store cookie prefs with sessions
561 // stdlib: global obj2hash helper from _AuthInfo, also needed for PagePerm
562 //
563 // Revision 1.2  2004/02/08 13:17:48  rurban
564 // This should be the functionality. Needs testing and some minor todos.
565 //
566 // Revision 1.1  2004/02/08 12:29:30  rurban
567 // initial version, not yet hooked into lib/main.php
568 //
569 //
570
571 // Local Variables:
572 // mode: php
573 // tab-width: 8
574 // c-basic-offset: 4
575 // c-hanging-comment-ender-p: nil
576 // indent-tabs-mode: nil
577 // End:
578 ?>