]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/PagePerm.php
Rename functional for PearDB backend
[SourceForge/phpwiki.git] / lib / PagePerm.php
1 <?php // -*-php-*-
2 rcs_id('$Id: PagePerm.php,v 1.4 2004-02-12 13:05:36 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 PagePermissions());
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 // 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;
104     else
105         return WIKIAUTH_FORBIDDEN;
106 }
107
108 // Translate action or plugin to the simplier access types:
109 function action2access ($action) {
110     global $request;
111     switch ($action) {
112     case 'browse':
113     case 'viewsource':
114     case 'diff':
115     case 'select':
116     case 'xmlrpc':
117     case 'search':
118         return 'view';
119     case 'zip':
120     case 'ziphtml':
121         return 'dump';
122     case 'edit':
123         return 'edit';
124     case 'create':
125         $page = $request->getPage();
126         $current = $page->getCurrentRevision();
127         if ($current->hasDefaultContents())
128             return 'edit';
129         else
130             return 'view'; 
131         break;
132     case 'upload':
133     case 'dumpserial':
134     case 'dumphtml':
135     case 'loadfile':
136     case 'remove':
137     case 'lock':
138     case 'unlock':
139             return 'change';
140     default:
141         //Todo: Plugins should be able to override its access type
142         if (isWikiWord($action))
143             return 'view';
144         else
145             return 'change';
146         break;
147     }
148 }
149
150 // Recursive helper to do the real work
151 function _requiredAuthorityForPagename($access, $pagename) {
152     global $request;
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);
158     }
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);
164         }
165         return _requiredAuthorityForPagename($access,getParentPage($pagename));
166     }
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
170         return $authorized;
171     else
172         return _requiredAuthorityForPagename($access,getParentPage($pagename));
173 }
174
175 /**
176  * @param  string $pagename   page from which the parent page is searched.
177  * @return string parent      pagename or the (possibly pseudo) dot-pagename.
178  */
179 function getParentPage($pagename) {
180     if (isSubPage($pagename)) {
181         return subPageSlice($pagename,0);
182     } else {
183         return '.';
184     }
185 }
186
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));
193     else 
194         return false;
195 }
196
197 // Store the ACL in the page
198 function setPagePermissions ($page,$perm) {
199     $perm->store($page);
200 }
201
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"),
213                                     );
214     }
215     if (in_array($action, array_keys($accessDescriptions)))
216         return $accessDescriptions[$access];
217     else
218         return $access;
219 }
220
221 /**
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.
224  *
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
228  *
229  * Define any special rules here, like don't list dot-pages.
230  */ 
231 class PagePermission {
232     var $perm;
233
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;
240                 else
241                     trigger_error(sprintf(_("Unsupported ACL access type %s ignored."),$access),
242                                   E_USER_WARNING);
243             }
244         } else {
245             // set default permissions, the so called dot-file acl's
246             $this->perm = $this->defaultPerms();
247         }
248         return $this;
249     }
250
251     /**
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).
255      */
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))
260                     return $bool;
261             }
262         }
263         return -1; // undecided
264     }
265
266     /**
267      * Translate the various special groups to the actual users settings 
268      * (userid, group membership).
269      */
270     function isMember($user,$group) {
271         global $request;
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();
292         }
293         if ($group === ACL_CREATOR) {
294             $page = $request->getPage();
295             $rev = $page->getRevision(1);
296             return $rev->get('author') === $user->UserName();
297         }
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);
303     }
304
305     /**
306      * returns hash of default permissions.
307      * check if the page '.' exists and returns this instead.
308      */
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,
317                                         ACL_OWNER => true),
318                       'change' => array(ACL_ADMIN => true,
319                                         ACL_OWNER => true));
320         if (defined('ZIPDUMP_AUTH') && ZIPDUMP_AUTH)
321             $perm['dump'] = array(ACL_ADMIN => true,
322                                   ACL_OWNER => true);
323         else
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);
334             } else {
335                 $perm['view'] = array(ACL_SIGNED => true);
336                 $perm['edit'] = array(ACL_SIGNED => true);
337             }
338         }
339         return $perm;
340     }
341
342     /**
343      * returns list of all supported access types.
344      */
345     function accessTypes() {
346         return array_keys($this->defaultPerms());
347     }
348
349     /**
350      * special permissions for dot-files, beginning with '.'
351      * maybe also for '_' files?
352      */
353     function dotPerms() {
354         $def = array(ACL_ADMIN => true,
355                      ACL_OWNER => true);
356         $perm = array();
357         foreach ($this->accessTypes as $access) {
358             $perm[$access] = $def;
359         }
360         return $perm;
361     }
362
363     /**
364      *  dead code. not needed inside the object. see getPagePermissions($page)
365      */
366     function retrieve($page) {
367         $hash = $page->get('perm');
368         if ($hash)  // hash => object
369             return new PagePermission(unserialize($hash));
370         else 
371             return new PagePermission();
372     }
373
374     function store($page) {
375         // object => hash
376         return $page->set('perm',serialize(obj2hash($this->perm)));
377     }
378 }
379
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
383 // PagePerm:
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
388 // WikiUserNew:
389 //   * added global ->prepare (not yet used) with smart user/pref/member table prefixing.
390 //   * force init of authdbh in the 2 db classes
391 // main:
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
395 //
396 // Revision 1.2  2004/02/08 13:17:48  rurban
397 // This should be the functionality. Needs testing and some minor todos.
398 //
399 // Revision 1.1  2004/02/08 12:29:30  rurban
400 // initial version, not yet hooked into lib/main.php
401 //
402 //
403
404 // Local Variables:
405 // mode: php
406 // tab-width: 8
407 // c-basic-offset: 4
408 // c-hanging-comment-ender-p: nil
409 // indent-tabs-mode: nil
410 // End:
411 ?>