2 rcs_id('$Id: PagePerm.php,v 1.6 2004-02-24 15:20:05 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 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
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 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;
150 return WIKIAUTH_FORBIDDEN;
153 // Translate action or plugin to the simplier access types:
154 function action2access ($action) {
172 $page = $request->getPage();
173 $current = $page->getCurrentRevision();
174 if ($current->hasDefaultContents())
181 // probably create/edit but we cannot check all page permissions, can we?
187 //Todo: Plugins should be able to override its access type
188 if (isWikiWord($action))
196 // Recursive helper to do the real work
197 function _requiredAuthorityForPagename($access, $pagename) {
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);
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);
211 return _requiredAuthorityForPagename($access,getParentPage($pagename));
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
218 return _requiredAuthorityForPagename($access,getParentPage($pagename));
222 * @param string $pagename page from which the parent page is searched.
223 * @return string parent pagename or the (possibly pseudo) dot-pagename.
225 function getParentPage($pagename) {
226 if (isSubPage($pagename)) {
227 return subPageSlice($pagename,0);
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));
243 // Store the ACL in the page
244 function setPagePermissions ($page,$perm) {
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"),
261 if (in_array($access, array_keys($accessDescriptions)))
262 return $accessDescriptions[$access];
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.
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
275 * Define any special rules here, like don't list dot-pages.
277 class PagePermission {
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;
287 trigger_error(sprintf(_("Unsupported ACL access type %s ignored."),$access),
291 // set default permissions, the so called dot-file acl's
292 $this->perm = $this->defaultPerms();
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).
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))
309 return -1; // undecided
313 * Translate the various special groups to the actual users settings
314 * (userid, group membership).
316 function isMember($user,$group) {
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();
339 if ($group === ACL_CREATOR) {
340 $page = $request->getPage();
341 $rev = $page->getRevision(1);
342 return $rev->get('author') === $user->UserName();
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);
352 * returns hash of default permissions.
353 * check if the page '.' exists and returns this instead.
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,
364 'change' => array(ACL_ADMIN => true,
366 if (defined('ZIPDUMP_AUTH') && ZIPDUMP_AUTH)
367 $perm['dump'] = array(ACL_ADMIN => true,
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);
381 $perm['view'] = array(ACL_SIGNED => true);
382 $perm['edit'] = array(ACL_SIGNED => true);
389 * returns list of all supported access types.
391 function accessTypes() {
392 return array_keys($this->defaultPerms());
396 * special permissions for dot-files, beginning with '.'
397 * maybe also for '_' files?
399 function dotPerms() {
400 $def = array(ACL_ADMIN => true,
403 foreach ($this->accessTypes as $access) {
404 $perm[$access] = $def;
410 * dead code. not needed inside the object. see getPagePermissions($page)
412 function retrieve($page) {
413 $hash = $page->get('perm');
414 if ($hash) // hash => object
415 return new PagePermission(unserialize($hash));
417 return new PagePermission();
420 function store($page) {
422 return $page->set('perm',serialize(obj2hash($this->perm)));
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]' : '[ ]')));
434 $table->pushContent(HTML::tr(array('valign' => 'top'),
435 HTML::td($access),HTML::td($td)));
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;');
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'),
454 HTML::th('Description')));
455 foreach ($this->perm as $access => $perms) {
456 //$permlist = HTML::table(array('class' => 'cal','valign' => 'top'));
458 foreach ($perms as $group => $bool) {
459 $checkbox = HTML::input(array('type' => 'checkbox',
460 'name' => "acl[$access][$group]",
462 if ($bool) $checkbox->setAttr('checked','checked');
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)),
469 HTML::td(HTML::em(getAccessDescription($access)))));
472 $table->pushContent(HTML::tr(array('valign' => 'top'),
474 HTML::td(array('class' => 'cal-today','align'=>'right'),
475 constant("GROUP".$group)),
481 $table->pushContent(HTML::tr(array('valign' => 'top'),
482 HTML::td(HTML::strong($access)),
484 HTML::td(HTML::em(getAccessDescription($access)))));
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;');
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) {
500 // simplify object => rwxrw---x+ string as in cygwin (+ denotes additional ACLs)
501 $perm =& $this->perm;
502 // get effective user and group
504 if (isset($perm['view'][$owner]) or
505 (isset($perm['view'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
507 if (isset($perm['edit'][$owner]) or
508 (isset($perm['edit'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
510 if (isset($perm['change'][$owner]) or
511 (isset($perm['change'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
513 if (!empty($group)) {
514 if (isset($perm['view'][$group]) or
515 (isset($perm['view'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
517 if (isset($perm['edit'][$group]) or
518 (isset($perm['edit'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
520 if (isset($perm['change'][$group]) or
521 (isset($perm['change'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
524 if (isset($perm['view'][ACL_EVERY]) or
525 (isset($perm['view'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
527 if (isset($perm['edit'][ACL_EVERY]) or
528 (isset($perm['edit'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
530 if (isset($perm['change'][ACL_EVERY]) or
531 (isset($perm['change'][ACL_AUTHENTICATED]) and $request->_user->isAuthenticated()))
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
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)
548 // Revision 1.3 2004/02/09 03:58:12 rurban
549 // for now default DB_SESSION to false
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
556 // * added global ->prepare (not yet used) with smart user/pref/member table prefixing.
557 // * force init of authdbh in the 2 db classes
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
563 // Revision 1.2 2004/02/08 13:17:48 rurban
564 // This should be the functionality. Needs testing and some minor todos.
566 // Revision 1.1 2004/02/08 12:29:30 rurban
567 // initial version, not yet hooked into lib/main.php
575 // c-hanging-comment-ender-p: nil
576 // indent-tabs-mode: nil