2 rcs_id('$Id: PagePerm.php,v 1.4 2004-02-12 13:05:36 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 PagePermissions());
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 // Check the permissions for the current action.
96 // Walk down the inheritance tree. Collect all permissions until
97 // the minimum required level is gained, which is not
98 // overruled by more specific forbid rules.
99 // Todo: cache result per access and page in session?
100 function requiredAuthorityForPage ($action) {
101 if (_requiredAuthorityForPagename(action2access($action),
102 $GLOBALS['request']->getArg('pagename')))
103 return $GLOBALS['request']->_user->_level;
105 return WIKIAUTH_FORBIDDEN;
108 // Translate action or plugin to the simplier access types:
109 function action2access ($action) {
125 $page = $request->getPage();
126 $current = $page->getCurrentRevision();
127 if ($current->hasDefaultContents())
141 //Todo: Plugins should be able to override its access type
142 if (isWikiWord($action))
150 // Recursive helper to do the real work
151 function _requiredAuthorityForPagename($access, $pagename) {
153 $page = $request->getPage($pagename);
154 // Page not found; check against default permissions
155 if (! $page->exists() ) {
156 $perm = new PagePermission();
157 return ($perm->isAuthorized($access,$request->_user) === true);
159 // no ACL defined; check for special dotfile or walk down
160 if (! ($perm = getPagePermissions($page))) {
161 if ($pagename[0] == '.') {
162 $perm = new PagePermission(PagePermission::dotPerms());
163 return ($perm->isAuthorized($access,$request->_user) === true);
165 return _requiredAuthorityForPagename($access,getParentPage($pagename));
167 // ACL defined; check if isAuthorized returns true or false or undecided
168 $authorized = $perm->isAuthorized($access,$request->_user);
169 if ($authorized != -1) // -1 for undecided
172 return _requiredAuthorityForPagename($access,getParentPage($pagename));
176 * @param string $pagename page from which the parent page is searched.
177 * @return string parent pagename or the (possibly pseudo) dot-pagename.
179 function getParentPage($pagename) {
180 if (isSubPage($pagename)) {
181 return subPageSlice($pagename,0);
187 // Read the ACL from the page
188 // Done: Not existing pages should NOT be queried.
189 // Check the parent page instead and don't take the default ACL's
190 function getPagePermissions ($page) {
191 if ($hash = $page->get('perm')) // hash => object
192 return new PagePermission(unserialize($hash));
197 // Store the ACL in the page
198 function setPagePermissions ($page,$perm) {
202 function getAccessDescription($access) {
203 static $accessDescriptions;
204 if (! $accessDescriptions) {
205 $accessDescriptions = array(
206 'list' => _("List this page and all subpages"),
207 'view' => _("View this page and all subpages"),
208 'edit' => _("Edit this page and all subpages"),
209 'create' => _("Create a new (sub)page"),
210 'dump' => _("Download the page contents"),
211 'change' => _("Change page attributes"),
212 'remove' => _("Remove this page"),
215 if (in_array($action, array_keys($accessDescriptions)))
216 return $accessDescriptions[$access];
222 * The ACL object per page. It is stored in a page, but can also
223 * be merged with ACL's from other pages or taken from the master (pseudo) dot-file.
225 * A hash of "access" => "requires" pairs.
226 * "access" is a shortcut for common actions, which map to main.php actions
227 * "requires" required username or groupname or any special group => true or false
229 * Define any special rules here, like don't list dot-pages.
231 class PagePermission {
234 function PagePermission($hash = array()) {
235 if (is_array($hash) and !empty($hash)) {
236 $accessTypes = $this->accessTypes();
237 foreach ($hash as $access => $requires) {
238 if (in_array($access,$accessTypes))
239 $this->perm[$access] = $requires;
241 trigger_error(sprintf(_("Unsupported ACL access type %s ignored."),$access),
245 // set default permissions, the so called dot-file acl's
246 $this->perm = $this->defaultPerms();
252 * The workhorse to check the user against the current ACL pairs.
253 * Must translate the various special groups to the actual users settings
254 * (userid, group membership).
256 function isAuthorized($access,$user) {
257 if (!empty($this->perm{$access})) {
258 foreach ($this->perm[$access] as $group => $bool) {
259 if ($this->isMember($user,$group))
263 return -1; // undecided
267 * Translate the various special groups to the actual users settings
268 * (userid, group membership).
270 function isMember($user,$group) {
272 if ($group === ACL_EVERY) return true;
273 $member = &WikiGroup::getGroup($request);
274 //$user = & $request->_user;
275 if ($group === ACL_ADMIN) // WIKI_ADMIN or member of _("Administrators")
276 return $user->isAdmin() or
277 $member->isMember(GROUP_ADMIN);
278 if ($group === ACL_ANONYMOUS)
279 return ! $user->isSignedIn();
280 if ($group === ACL_BOGOUSERS)
281 if (ENABLE_USER_NEW) return isa($user,'_BogoUser');
282 else return isWikiWord($user->UserName());
283 if ($group === ACL_HASHOMEPAGE)
284 return $user->hasHomePage();
285 if ($group === ACL_SIGNED)
286 return $user->isSignedIn();
287 if ($group === ACL_AUTHENTICATED)
288 return $user->isAuthenticated();
289 if ($group === ACL_OWNER) {
290 $page = $request->getPage();
291 return $page->get('author') === $user->UserName();
293 if ($group === ACL_CREATOR) {
294 $page = $request->getPage();
295 $rev = $page->getRevision(1);
296 return $rev->get('author') === $user->UserName();
298 /* Or named groups or usernames.
299 Note: We don't seperate groups and users here.
300 Users overrides groups with the same name. */
301 return $user->UserName() === $group or
302 $member->isMember($group);
306 * returns hash of default permissions.
307 * check if the page '.' exists and returns this instead.
309 function defaultPerms() {
310 //Todo: check for the existance of '.' and take this instead.
311 //Todo: honor more index.php auth settings here
312 $perm = array('view' => array(ACL_EVERY => true),
313 'edit' => array(ACL_EVERY => true),
314 'create' => array(ACL_EVERY => true),
315 'list' => array(ACL_EVERY => true),
316 'remove' => array(ACL_ADMIN => true,
318 'change' => array(ACL_ADMIN => true,
320 if (defined('ZIPDUMP_AUTH') && ZIPDUMP_AUTH)
321 $perm['dump'] = array(ACL_ADMIN => true,
324 $perm['dump'] = array(ACL_EVERY => true);
325 if (defined('REQUIRE_SIGNIN_BEFORE_EDIT') && REQUIRE_SIGNIN_BEFORE_EDIT)
326 $perm['edit'] = array(ACL_SIGNED => true);
327 if (defined('ALLOW_ANON_USER') && ! ALLOW_ANON_USER) {
328 if (defined('ALLOW_BOGO_USER') && ALLOW_BOGO_USER) {
329 $perm['view'] = array(ACL_BOGOUSER => true);
330 $perm['edit'] = array(ACL_BOGOUSER => true);
331 } elseif (defined('ALLOW_USER_PASSWORDS') && ALLOW_USER_PASSWORDS) {
332 $perm['view'] = array(ACL_AUTHENTICATED => true);
333 $perm['edit'] = array(ACL_AUTHENTICATED => true);
335 $perm['view'] = array(ACL_SIGNED => true);
336 $perm['edit'] = array(ACL_SIGNED => true);
343 * returns list of all supported access types.
345 function accessTypes() {
346 return array_keys($this->defaultPerms());
350 * special permissions for dot-files, beginning with '.'
351 * maybe also for '_' files?
353 function dotPerms() {
354 $def = array(ACL_ADMIN => true,
357 foreach ($this->accessTypes as $access) {
358 $perm[$access] = $def;
364 * dead code. not needed inside the object. see getPagePermissions($page)
366 function retrieve($page) {
367 $hash = $page->get('perm');
368 if ($hash) // hash => object
369 return new PagePermission(unserialize($hash));
371 return new PagePermission();
374 function store($page) {
376 return $page->set('perm',serialize(obj2hash($this->perm)));
380 // $Log: not supported by cvs2svn $
381 // Revision 1.3 2004/02/09 03:58:12 rurban
382 // for now default DB_SESSION to false
384 // * not existing perms will now query the parent, and not
385 // return the default perm
386 // * added pagePermissions func which returns the object per page
387 // * added getAccessDescription
389 // * added global ->prepare (not yet used) with smart user/pref/member table prefixing.
390 // * force init of authdbh in the 2 db classes
392 // * fixed session handling (not triple auth request anymore)
393 // * don't store cookie prefs with sessions
394 // stdlib: global obj2hash helper from _AuthInfo, also needed for PagePerm
396 // Revision 1.2 2004/02/08 13:17:48 rurban
397 // This should be the functionality. Needs testing and some minor todos.
399 // Revision 1.1 2004/02/08 12:29:30 rurban
400 // initial version, not yet hooked into lib/main.php
408 // c-hanging-comment-ender-p: nil
409 // indent-tabs-mode: nil