2 rcs_id('$Id: PagePerm.php,v 1.11 2004-05-02 21:26:38 rurban Exp $');
4 Copyright 2004 $ThePhpWikiProgrammingTeam
6 This file is part of PhpWiki.
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.
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.
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
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)
29 Permissions maybe inherited its parent pages, and ultimativly the
30 optional master page (".")
31 Pagenames starting with "." have special default permissions.
32 For Authentication see WikiUserNew.php, WikiGroup.php and main.php
33 Page Permssions are in PhpWiki since v1.3.9 and enabled since v1.4.0
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,
42 Request::getDisallowedActionDescription($action)
43 helper to localize the _notAuthorized message per action,
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
52 The defined main.php actions map to simplier access types:
55 create => edit or create
59 list in PageList => list
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');
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) {
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());
85 return array('inherited',pagePermissions(getParentPage($pagename)));
87 } elseif ($perm = getPagePermissions($page)) {
88 return array('page',$perm);
89 // or no permissions defined; returned inherited permissions, to be displayed in gray
91 return array('inherited',pagePermissions(getParentPage($pagename)));
95 function pagePermissionsSimpleFormat($perm_tree,$owner,$group=false) {
96 list($type,$perm) = pagePermissionsAcl($perm_tree[0], $perm_tree);
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'))
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));
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];
126 return array($type,$perm);
131 function pagePermissionsAclFormat($perm_tree,$editable=false) {
132 list($type,$perm) = pagePermissionsAcl($perm_tree[0], $perm_tree);
134 return $perm->asEditableTable($type);
136 return $perm->asTable($type);
140 * Check permission per page.
141 * Returns true or false.
143 function mayAccessPage ($access,$pagename) {
144 return _requiredAuthorityForPagename($access,$pagename);
147 /** Check the permissions for the current action.
148 * Walk down the inheritance tree. Collect all permissions until
149 * the minimum required level is gained, which is not
150 * overruled by more specific forbid rules.
151 * Todo: cache result per access and page in session?
153 function requiredAuthorityForPage ($action) {
154 if (_requiredAuthorityForPagename(action2access($action),
155 $GLOBALS['request']->getArg('pagename')))
156 return $GLOBALS['request']->_user->_level;
158 return WIKIAUTH_UNOBTAINABLE;
161 // Translate action or plugin to the simplier access types:
162 function action2access ($action) {
180 $page = $request->getPage();
181 $current = $page->getCurrentRevision();
182 if ($current->hasDefaultContents())
189 // probably create/edit but we cannot check all page permissions, can we?
196 //Todo: Plugins should be able to override its access type
197 if (isWikiWord($action))
205 // Recursive helper to do the real work
206 function _requiredAuthorityForPagename($access, $pagename) {
208 $page = $request->getPage($pagename);
209 // Page not found; check against default permissions
210 if (! $page->exists() ) {
211 $perm = new PagePermission();
212 return ($perm->isAuthorized($access,$request->_user) === true);
214 // no ACL defined; check for special dotfile or walk down
215 if (! ($perm = getPagePermissions($page))) {
216 if ($pagename[0] == '.') {
217 $perm = new PagePermission(PagePermission::dotPerms());
218 return ($perm->isAuthorized($access,$request->_user) === true);
220 return _requiredAuthorityForPagename($access,getParentPage($pagename));
222 // ACL defined; check if isAuthorized returns true or false or undecided
223 $authorized = $perm->isAuthorized($access,$request->_user);
224 if ($authorized != -1) // -1 for undecided
227 return _requiredAuthorityForPagename($access,getParentPage($pagename));
231 * @param string $pagename page from which the parent page is searched.
232 * @return string parent pagename or the (possibly pseudo) dot-pagename.
234 function getParentPage($pagename) {
235 if (isSubPage($pagename)) {
236 return subPageSlice($pagename,0);
242 // Read the ACL from the page
243 // Done: Not existing pages should NOT be queried.
244 // Check the parent page instead and don't take the default ACL's
245 function getPagePermissions ($page) {
246 if ($hash = $page->get('perm')) // hash => object
247 return new PagePermission(unserialize($hash));
252 // Store the ACL in the page
253 function setPagePermissions ($page,$perm) {
257 function getAccessDescription($access) {
258 static $accessDescriptions;
259 if (! $accessDescriptions) {
260 $accessDescriptions = array(
261 'list' => _("List this page and all subpages"),
262 'view' => _("View this page and all subpages"),
263 'edit' => _("Edit this page and all subpages"),
264 'create' => _("Create a new (sub)page"),
265 'dump' => _("Download the page contents"),
266 'change' => _("Change page attributes"),
267 'remove' => _("Remove this page"),
270 if (in_array($access, array_keys($accessDescriptions)))
271 return $accessDescriptions[$access];
277 * The ACL object per page. It is stored in a page, but can also
278 * be merged with ACL's from other pages or taken from the master (pseudo) dot-file.
280 * A hash of "access" => "requires" pairs.
281 * "access" is a shortcut for common actions, which map to main.php actions
282 * "requires" required username or groupname or any special group => true or false
284 * Define any special rules here, like don't list dot-pages.
286 class PagePermission {
289 function PagePermission($hash = array()) {
290 if (is_array($hash) and !empty($hash)) {
291 $accessTypes = $this->accessTypes();
292 foreach ($hash as $access => $requires) {
293 if (in_array($access,$accessTypes))
294 $this->perm[$access] = $requires;
296 trigger_error(sprintf(_("Unsupported ACL access type %s ignored."),$access),
300 // set default permissions, the so called dot-file acl's
301 $this->perm = $this->defaultPerms();
307 * The workhorse to check the user against the current ACL pairs.
308 * Must translate the various special groups to the actual users settings
309 * (userid, group membership).
311 function isAuthorized($access,$user) {
312 if (!empty($this->perm{$access})) {
313 foreach ($this->perm[$access] as $group => $bool) {
314 if ($this->isMember($user,$group)) {
319 return -1; // undecided
323 * Translate the various special groups to the actual users settings
324 * (userid, group membership).
326 function isMember($user,$group) {
328 if ($group === ACL_EVERY) return true;
329 $member = &WikiGroup::getGroup($request);
330 //$user = & $request->_user;
331 if ($group === ACL_ADMIN) // WIKI_ADMIN or member of _("Administrators")
332 return $user->isAdmin() or
333 $member->isMember(GROUP_ADMIN);
334 if ($group === ACL_ANONYMOUS)
335 return ! $user->isSignedIn();
336 if ($group === ACL_BOGOUSERS)
337 if (ENABLE_USER_NEW) return isa($user,'_BogoUser');
338 else return isWikiWord($user->UserName());
339 if ($group === ACL_HASHOMEPAGE)
340 return $user->hasHomePage();
341 if ($group === ACL_SIGNED)
342 return $user->isSignedIn();
343 if ($group === ACL_AUTHENTICATED)
344 return $user->isAuthenticated();
345 if ($group === ACL_OWNER) {
346 $page = $request->getPage();
347 return $page->get('author') === $user->UserName();
349 if ($group === ACL_CREATOR) {
350 $page = $request->getPage();
351 $rev = $page->getRevision(1);
352 return $rev->get('author') === $user->UserName();
354 /* Or named groups or usernames.
355 Note: We don't seperate groups and users here.
356 Users overrides groups with the same name. */
357 return $user->UserName() === $group or
358 $member->isMember($group);
362 * returns hash of default permissions.
363 * check if the page '.' exists and returns this instead.
365 function defaultPerms() {
366 //Todo: check for the existance of '.' and take this instead.
367 //Todo: honor more index.php auth settings here
368 $perm = array('view' => array(ACL_EVERY => true),
369 'edit' => array(ACL_EVERY => true),
370 'create' => array(ACL_EVERY => true),
371 'list' => array(ACL_EVERY => true),
372 'remove' => array(ACL_ADMIN => true,
374 'change' => array(ACL_ADMIN => true,
376 if (defined('ZIPDUMP_AUTH') && ZIPDUMP_AUTH)
377 $perm['dump'] = array(ACL_ADMIN => true,
380 $perm['dump'] = array(ACL_EVERY => true);
381 if (defined('REQUIRE_SIGNIN_BEFORE_EDIT') && REQUIRE_SIGNIN_BEFORE_EDIT)
382 $perm['edit'] = array(ACL_SIGNED => true);
383 if (defined('ALLOW_ANON_USER') && ! ALLOW_ANON_USER) {
384 if (defined('ALLOW_BOGO_USER') && ALLOW_BOGO_USER) {
385 $perm['view'] = array(ACL_BOGOUSER => true);
386 $perm['edit'] = array(ACL_BOGOUSER => true);
387 } elseif (defined('ALLOW_USER_PASSWORDS') && ALLOW_USER_PASSWORDS) {
388 $perm['view'] = array(ACL_AUTHENTICATED => true);
389 $perm['edit'] = array(ACL_AUTHENTICATED => true);
391 $perm['view'] = array(ACL_SIGNED => true);
392 $perm['edit'] = array(ACL_SIGNED => true);
399 * returns list of all supported access types.
401 function accessTypes() {
402 return array_keys($this->defaultPerms());
406 * special permissions for dot-files, beginning with '.'
407 * maybe also for '_' files?
409 function dotPerms() {
410 $def = array(ACL_ADMIN => true,
413 foreach ($this->accessTypes() as $access) {
414 $perm[$access] = $def;
420 * dead code. not needed inside the object. see getPagePermissions($page)
422 function retrieve($page) {
423 $hash = $page->get('perm');
424 if ($hash) // hash => object
425 return new PagePermission(unserialize($hash));
427 return new PagePermission();
430 function store($page) {
432 return $page->set('perm',serialize(obj2hash($this->perm)));
435 /* type: page, default, inherited */
436 function asTable($type) {
437 $table = HTML::table();
438 foreach ($this->perm as $access => $perms) {
439 $td = HTML::table(array('class' => 'cal','valign' => 'top'));
440 foreach ($perms as $group => $bool) {
441 $td->pushContent(HTML::tr(HTML::td(array('align'=>'right'),$group),
442 HTML::td($bool ? '[X]' : '[ ]')));
444 $table->pushContent(HTML::tr(array('valign' => 'top'),
445 HTML::td($access),HTML::td($td)));
447 if ($type == 'default')
448 $table->setAttr('style','border: dotted thin black; background-color:#eee;');
449 elseif ($type == 'inherited')
450 $table->setAttr('style','border: dotted thin black; background-color:#ddd;');
451 elseif ($type == 'page')
452 $table->setAttr('style','border: solid thin black; font-weight: bold;');
456 /* type: page, default, inherited */
457 function asEditableTable($type) {
458 $table = HTML::table();
459 $table->pushContent(HTML::tr(array('valign' => 'top'),
460 HTML::th(array('align' => 'left'),'access'),
461 HTML::th(array('align'=>'right'),
464 HTML::th('Description')));
465 foreach ($this->perm as $access => $perms) {
466 //$permlist = HTML::table(array('class' => 'cal','valign' => 'top'));
468 foreach ($perms as $group => $bool) {
469 $checkbox = HTML::input(array('type' => 'checkbox',
470 'name' => "acl[$access][$group]",
472 if ($bool) $checkbox->setAttr('checked','checked');
474 $table->pushContent(HTML::tr(array('valign' => 'top'),
475 HTML::td(HTML::strong($access.":")),
476 HTML::td(array('class' => 'cal-today','align'=>'right'),
477 constant("GROUP".$group)),
479 HTML::td(HTML::em(getAccessDescription($access)))));
482 $table->pushContent(HTML::tr(array('valign' => 'top'),
484 HTML::td(array('class' => 'cal-today','align'=>'right'),
485 constant("GROUP".$group)),
491 $table->pushContent(HTML::tr(array('valign' => 'top'),
492 HTML::td(HTML::strong($access)),
494 HTML::td(HTML::em(getAccessDescription($access)))));
497 if ($type == 'default')
498 $table->setAttr('style','border: dotted thin black; background-color:#eee;');
499 elseif ($type == 'inherited')
500 $table->setAttr('style','border: dotted thin black; background-color:#ddd;');
501 elseif ($type == 'page')
502 $table->setAttr('style','border: solid thin black; font-weight: bold;');
506 // this is just a bad hack for testing
507 // simplify the ACL to a unix-like "rwx------" string
508 function asRwxString($owner,$group=false) {
510 // simplify object => rwxrw---x+ string as in cygwin (+ denotes additional ACLs)
511 $perm =& $this->perm;
512 // get effective user and group
514 if (isset($perm['view'][$owner]) or
515 (isset($perm['view'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
517 if (isset($perm['edit'][$owner]) or
518 (isset($perm['edit'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
520 if (isset($perm['change'][$owner]) or
521 (isset($perm['change'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
523 if (!empty($group)) {
524 if (isset($perm['view'][$group]) or
525 (isset($perm['view'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
527 if (isset($perm['edit'][$group]) or
528 (isset($perm['edit'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
530 if (isset($perm['change'][$group]) or
531 (isset($perm['change'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
534 if (isset($perm['view'][ACL_EVERY]) or
535 (isset($perm['view'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
537 if (isset($perm['edit'][ACL_EVERY]) or
538 (isset($perm['edit'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
540 if (isset($perm['change'][ACL_EVERY]) or
541 (isset($perm['change'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
547 // $Log: not supported by cvs2svn $
548 // Revision 1.10 2004/04/29 22:32:56 zorloc
549 // Slightly more elegant fix. Instead of WIKIAUTH_FORBIDDEN, the current user's level + 1 is returned on a false.
551 // Revision 1.9 2004/04/29 17:18:19 zorloc
552 // 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.
554 // Revision 1.8 2004/03/14 16:24:35 rurban
555 // authenti(fi)cation spelling
557 // Revision 1.7 2004/02/28 22:25:07 rurban
558 // First PagePerm implementation:
560 // $Theme->setAnonEditUnknownLinks(false);
562 // Layout improvement with dangling links for mostly closed wiki's:
563 // If false, only users with edit permissions will be presented the
564 // special wikiunknown class with "?" and Tooltip.
565 // If true (default), any user will see the ?, but will be presented
566 // the PrintLoginForm on a click.
568 // Revision 1.6 2004/02/24 15:20:05 rurban
569 // fixed minor warnings: unchecked args, POST => Get urls for sortby e.g.
571 // Revision 1.5 2004/02/23 21:30:25 rurban
572 // more PagePerm stuff: (working against 1.4.0)
573 // ACL editing and simplification of ACL's to simple rwx------ string
576 // Revision 1.4 2004/02/12 13:05:36 rurban
577 // Rename functional for PearDB backend
578 // some other minor changes
579 // SiteMap comes with a not yet functional feature request: includepages (tbd)
581 // Revision 1.3 2004/02/09 03:58:12 rurban
582 // for now default DB_SESSION to false
584 // * not existing perms will now query the parent, and not
585 // return the default perm
586 // * added pagePermissions func which returns the object per page
587 // * added getAccessDescription
589 // * added global ->prepare (not yet used) with smart user/pref/member table prefixing.
590 // * force init of authdbh in the 2 db classes
592 // * fixed session handling (not triple auth request anymore)
593 // * don't store cookie prefs with sessions
594 // stdlib: global obj2hash helper from _AuthInfo, also needed for PagePerm
596 // Revision 1.2 2004/02/08 13:17:48 rurban
597 // This should be the functionality. Needs testing and some minor todos.
599 // Revision 1.1 2004/02/08 12:29:30 rurban
600 // initial version, not yet hooked into lib/main.php
608 // c-hanging-comment-ender-p: nil
609 // indent-tabs-mode: nil