]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiUserNew.php
Code Housecleaning: fixed syntax errors. (php -l *.php)
[SourceForge/phpwiki.git] / lib / WikiUserNew.php
1 <?php //-*-php-*-
2 rcs_id('$Id: WikiUserNew.php,v 1.4 2003-12-07 19:29:48 carstenklapp Exp $');
3
4 // This is a complete rewrite of the old WikiUser code but it is not
5 // implemented yet. Much of the existing UserPreferences class should
6 // work fine with this but a few other parts of PhpWiki need to be
7 // refitted: main.php, config.php, index.php. --Carsten
8
9
10 // Returns a user object, which contains the user's preferences.
11 //
12 // Given no name, returns an _AnonUser (anonymous user) object, who
13 // may or may not have a cookie. Given a user name, returns a
14 // _BogoUser object, who may or may not have a cookie and/or
15 // NamesakePage, a _PassUser object or an _AdminUser object.
16 //
17 // Takes care of passwords, all preference loading/storing in the
18 // user's page and any cookies. main.php will query the user object to
19 // verify the password as appropriate.
20
21
22 define('WIKIAUTH_ANON', 0);       // Not signed in.
23 define('WIKIAUTH_BOGO', 1);       // Any valid WikiWord is enough.
24 define('WIKIAUTH_USER', 2);       // Bogo user with a password.
25 define('WIKIAUTH_ADMIN', 10);     // UserName == ADMIN_USER.
26 define('WIKIAUTH_FORBIDDEN', -1); // Completely not allowed.
27
28 if (!defined('COOKIE_EXPIRATION_DAYS')) define('COOKIE_EXPIRATION_DAYS', 365);
29 if (!defined('COOKIE_DOMAIN'))          define('COOKIE_DOMAIN', '/');
30
31 if (!defined('EDITWIDTH_MIN_COLS'))     define('EDITWIDTH_MIN_COLS',     30);
32 if (!defined('EDITWIDTH_MAX_COLS'))     define('EDITWIDTH_MAX_COLS',    150);
33 if (!defined('EDITWIDTH_DEFAULT_COLS')) define('EDITWIDTH_DEFAULT_COLS', 80);
34
35 if (!defined('EDITHEIGHT_MIN_ROWS'))     define('EDITHEIGHT_MIN_ROWS',      5);
36 if (!defined('EDITHEIGHT_MAX_ROWS'))     define('EDITHEIGHT_MAX_ROWS',     80);
37 if (!defined('EDITHEIGHT_DEFAULT_ROWS')) define('EDITHEIGHT_DEFAULT_ROWS', 22);
38
39 define('TIMEOFFSET_MIN_HOURS', -26);
40 define('TIMEOFFSET_MAX_HOURS',  26);
41 if (!defined('TIMEOFFSET_DEFAULT_HOURS')) define('TIMEOFFSET_DEFAULT_HOURS', 0);
42
43 /**
44  * There are/will be four constants in index.php to establish login
45  * parameters:
46  *
47  * ALLOW_ANON_USER         default true
48  * ALLOW_BOGO_LOGIN        default true
49  * ALLOW_USER_PASSWORDS    default true
50  * PASSWORD_LENGTH_MINIMUM default 6?
51  *
52  *
53  * To require user passwords:
54  * ALLOW_BOGO_LOGIN = false,
55  * ALLOW_USER_PASSWORDS = true.
56  *
57  * To establish a COMPLETELY private wiki, such as an internal
58  * corporate one:
59  * ALLOW_ANON_USER = false,
60  * (and probably require user passwords as described above). In this
61  * case the user will be prompted to login immediately upon accessing
62  * any page.
63  *
64  * There are other possible combinations, but the typical wiki (such
65  * as PhpWiki.sf.net) would usually just leave all three enabled.
66  */
67
68 // Local convenience functions.
69 function _isAnonUserAllowed() {
70     return (defined('ALLOW_ANON_USER') && ALLOW_ANON_USER);
71 }
72 function _isBogoUserAllowed() {
73     return (defined('ALLOW_BOGO_LOGIN') && ALLOW_BOGO_LOGIN);
74 }
75 function _isUserPasswordsAllowed() {
76     return (defined('ALLOW_USER_PASSWORDS') && ALLOW_USER_PASSWORDS);
77 }
78
79
80 // Possibly upgrade userobject functions.
81 function _determineAdminUserOrOtherUser($UserName) {
82     // Sanity check. User name is a condition of the definition of the
83     // _AdminUser, _BogoUser and _PassUser.
84     if (!$UserName)
85         return false;
86
87     if ($UserName == ADMIN_USER)
88         return new _AdminUser($UserName);
89     else
90         return _determineBogoUserOrPassUser($UserName);
91 }
92
93 function _determineBogoUserOrPassUser($UserName) {
94     // Sanity check. User name is a condition of the definition of
95     // _BogoUser and _PassUser.
96     if (!$UserName)
97         return false;
98
99     // Check for password and possibly upgrade user object.
100     $_BogoUser = new _BogoUser($UserName);
101     if (_isUserPasswordsAllowed()) {
102         if (/*$has_password =*/ $_BogoUser->_prefs->get('passwd'))
103             return new _PassUser($UserName);
104     }
105     // User has no password.
106     if (_isBogoUserAllowed())
107         return $_BogoUser;
108
109     // Passwords are not allowed, and Bogo is disallowed too. (Only
110     // the admin can sign in).
111     return false;
112 }
113
114 /**
115  * Primary WikiUser function, called by main.php.
116  * 
117  * This determines the user's type and returns an appropriate user
118  * object. main.php then querys the resultant object for password
119  * validity as necessary.
120  *
121  * If an _AnonUser object is returned, the user may only browse pages
122  * (and save prefs in a cookie).
123  *
124  * When this function returns false instead of any user object, the
125  * user has been denied access to the wiki (possibly even reading
126  * pages) and must therefore sign in to continue.
127  */
128 function WikiUser ($UserName = '') {
129     //TODO: Check sessionvar for username & save username into
130     //sessionvar (may be more appropriate to do this in main.php).
131     if ($UserName) {
132         // Found a user name.
133         return _determineAdminUserOrOtherUser($UserName);
134     }
135     else {
136         // Check for autologin pref in cookie and possibly upgrade
137         // user object to another type.
138         $_AnonUser = new _AnonUser();
139         if ($UserName = $_AnonUser->UserName && $_AnonUser->_prefs->get('autologin')) {
140             // Found a user name.
141             return _determineAdminUserOrOtherUser($UserName);
142         }
143         else {
144             if (_isAnonUserAllowed())
145                 return $_AnonUser;
146             return false; // User must sign in to browse pages.
147         }
148         return false; // User must sign in with a password.
149     }
150     trigger_error("DEBUG: Note: End of function reached in WikiUser." . " "
151                   . "Unexpectedly, an appropriate user class could not be determined.");
152     return false; // Failsafe.
153 }
154
155 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
156
157 // Base _WikiUser class.
158 class _WikiUser
159 {
160     var $_level = WIKIAUTH_FORBIDDEN;
161     var $_prefs = false;
162     var $_HomePagehandle = false;
163
164     var $UserName = '';
165
166     // constructor
167     function _WikiUser($UserName = '') {
168         if ($UserName) {
169             $this->UserName = $UserName;
170             $this->_HomePagehandle = $this->hasHomePage();
171         }
172         $this->loadPreferences();
173     }
174
175     function loadPreferences() {
176         trigger_error("DEBUG: Note: undefined _WikiUser class trying to load prefs." . " "
177                       . "New subclasses of _WikiUser must override this function.");
178         return false;
179     }
180
181     function savePreferences() {
182         trigger_error("DEBUG: Note: undefined _WikiUser class trying to save prefs." . " "
183                       . "New subclasses of _WikiUser must override this function.");
184         return false;
185     }
186
187     // returns page_handle to user's home page or false if none
188     function hasHomePage() {
189         if ($this->UserName) {
190             if ($this->_HomePagehandle) {
191                 return $this->_HomePagehandle;
192             }
193             else {
194                 // check db again (maybe someone else created it since
195                 // we logged in.)
196                 global $request;
197                 $this->_HomePagehandle = $request->getPage($this->UserName);
198                 return $this->_HomePagehandle;
199             }
200         }
201         // nope
202         return false;
203     }
204
205     function checkPass($submitted_password) {
206         // By definition, an undefined user class cannot sign in.
207         trigger_error("DEBUG: Warning: undefined _WikiUser class trying to sign in." . " "
208                       . "New subclasses of _WikiUser must override this function.");
209         return false;
210     }
211
212 }
213
214 class _AnonUser
215 extends _WikiUser
216 {
217     var $_level = WIKIAUTH_ANON;
218
219     // Anon only gets to load and save prefs in a cookie, that's it.
220     function loadPreferences() {
221         global $request;
222         if ($cookie = $request->getCookieVar(WIKI_NAME)) {
223             if (! $unboxedcookie = $this->_prefs->unpack($cookie)) {
224                 trigger_error(_("Format of UserPreferences cookie not recognised.") . " "
225                               . _("Default preferences will be used."),
226                               E_USER_WARNING);
227             }
228             // TODO: try reading userid from old PhpWiki cookie
229             // formats, then delete old cookie from browser!
230             //
231             //else {
232                 // try old cookie format.
233                 //$cookie = $request->getCookieVar('WIKI_ID');
234             //}
235
236             /**
237              * Only keep the cookie if it matches the UserName who is
238              * signing in or if this really is an Anon login (no
239              * username). (Remember, _BogoUser and higher inherit this
240              * function too!).
241              */
242             if (! $this->UserName || $this->UserName == $unboxedcookie['userid']) {
243                 $this->_prefs = new UserPreferences($unboxedcookie);
244                 $this->UserName = $unboxedcookie['userid'];
245             }
246         }
247     }
248     function savePreferences() {
249         // Allow for multiple wikis in same domain. Encode only the
250         // _prefs array of the UserPreference object. Ideally the
251         // prefs array should just be imploded into a single string or
252         // something so it is completely human readable by the end
253         // user. In that case stricter error checking will be needed
254         // when loading the cookie.
255         setcookie(WIKI_NAME, $this->_prefs->pack($this->_prefs->getAll()),
256                   COOKIE_EXPIRATION_DAYS, COOKIE_DOMAIN);
257     }
258
259     function checkPass($submitted_password) {
260         // By definition, the _AnonUser does not HAVE a password
261         // (compared to _BogoUser, who has an EMPTY password).
262         trigger_error("DEBUG: Warning: _AnonUser unexpectedly asked to checkPass()." . " "
263                       . "Check isa($user, '_PassUser'), or: isa($user, '_AdminUser') etc. first." . " "
264                       . "New subclasses of _WikiUser must override this function.");
265         return false;
266     }
267
268 }
269
270 /**
271  * Do NOT extend _BogoUser to other classes, for checkPass()
272  * security. (In case of defects in code logic of the new class!)
273  */
274 class _BogoUser
275 extends _AnonUser
276 {
277     var $_level = WIKIAUTH_BOGO;
278
279     function checkPass($submitted_password) {
280         // By definition, BogoUser has an empty password.
281         return true;
282     }
283 }
284
285 class _PassUser
286 extends _AnonUser
287 /**
288  * New classes for externally authenticated users should extend from
289  * this class.
290  * 
291  * For now, the prefs $restored_from_page stuff is in here, but that
292  * will soon be moved into a new PersonalPage PassUser class or
293  * something, thus leaving this as a more generic passuser class from
294  * which other new authentication classes (and preference storage
295  * types) can extend.
296  */
297 {
298     var $_level = WIKIAUTH_USER;
299
300     //TODO: password changing
301     //TODO: email verification
302
303     function loadPreferences() {
304         // We don't necessarily have to read the cookie first. Since
305         // the user has a password, the prefs stored in the homepage
306         // cannot be arbitrarily altered by other Bogo users.
307         _AnonUser::loadPreferences();
308         // User may have deleted cookie, retrieve from his
309         // NamesakePage if there is one.
310         if ((! $this->_prefs) && $this->_HomePagehandle) {
311             if ($restored_from_page = $this->_prefs->unpack($this->_HomePagehandle->get('_prefs'))) {
312                 $this->_prefs = new UserPreferences($restored_from_page);
313             }
314         }
315     }
316     function savePreferences() {
317         _AnonUser::savePreferences();
318         // Encode only the _prefs array of the UserPreference object
319         $serialized = $this->_prefs->pack($this->_prefs->getAll());
320         $this->_HomePagehandle->set('_prefs', $serialized);
321     }
322
323     //TODO: alternatively obtain $stored_password from external auth
324     function checkPass($submitted_password) {
325         $stored_password = $this->_prefs->get('passwd');
326         return $this->_checkPass($submitted_password, $stored_password);
327     }
328
329     //TODO: remove crypt() function check from config.php:396
330     function _checkPass($submitted_password, $stored_password) {
331         if(!empty($submitted_password)) {
332             if (defined('ENCRYPTED_PASSWD') && ENCRYPTED_PASSWD) {
333                 // Verify against encrypted password.
334                 if (function_exists('crypt')) {
335                     if (crypt($submitted_password, $stored_password) == $stored_password )
336                         return true; // matches encrypted password
337                     else
338                         return false;
339                 }
340                 else {
341                     trigger_error(_("The crypt function is not available in this version of PHP.") . " "
342                                   . _("Please set ENCRYPTED_PASSWD to false in index.php and change ADMIN_PASSWD."),
343                                   E_USER_WARNING);
344                     return false;
345                 }
346             }
347             else {
348                 // Verify against cleartext password.
349                 if ($submitted_password == $stored_password)
350                     return true;
351                 else {
352                     // Check whether we forgot to enable ENCRYPTED_PASSWD
353                     if (function_exists('crypt')) {
354                         if (crypt($submitted_password, $stored_password) == $stored_password) {
355                             trigger_error(_("Please set ENCRYPTED_PASSWD to true in index.php."),
356                                           E_USER_WARNING);
357                             return true;
358                         }
359                     }
360                 }
361             }
362         }
363         return false;
364     }
365 }
366
367 /**
368  * For security, this class should not be extended. Instead, extend
369  * from _PassUser (think of this as unix "root").
370  */
371 class _AdminUser
372 extends _PassUser
373 {
374     var $_level = WIKIAUTH_ADMIN;
375
376     function checkPass($submitted_password) {
377         $stored_password = ADMIN_PASSWD;
378         return $this->_checkPass($submitted_password, $stored_password);
379     }
380 }
381
382 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
383
384 class _UserPreference
385 {
386     function _UserPreference ($default_value) {
387         $this->default_value = $default_value;
388     }
389
390     function sanify ($value) {
391         return (string)$value;
392     }
393
394     function update ($value) {
395     }
396 }
397
398 class _UserPreference_numeric
399 extends _UserPreference
400 {
401     function _UserPreference_numeric ($default, $minval = false,
402                                       $maxval = false) {
403         $this->_UserPreference((double)$default);
404         $this->_minval = (double)$minval;
405         $this->_maxval = (double)$maxval;
406     }
407
408     function sanify ($value) {
409         $value = (double)$value;
410         if ($this->_minval !== false && $value < $this->_minval)
411             $value = $this->_minval;
412         if ($this->_maxval !== false && $value > $this->_maxval)
413             $value = $this->_maxval;
414         return $value;
415     }
416 }
417
418 class _UserPreference_int
419 extends _UserPreference_numeric
420 {
421     function _UserPreference_int ($default, $minval = false, $maxval = false) {
422         $this->_UserPreference_numeric((int)$default, (int)$minval,
423                                        (int)$maxval);
424     }
425
426     function sanify ($value) {
427         return (int)parent::sanify((int)$value);
428     }
429 }
430
431 class _UserPreference_bool
432 extends _UserPreference
433 {
434     function _UserPreference_bool ($default = false) {
435         $this->_UserPreference((bool)$default);
436     }
437
438     function sanify ($value) {
439         if (is_array($value)) {
440             /* This allows for constructs like:
441              *
442              *   <input type="hidden" name="pref[boolPref][]" value="0" />
443              *   <input type="checkbox" name="pref[boolPref][]" value="1" />
444              *
445              * (If the checkbox is not checked, only the hidden input
446              * gets sent. If the checkbox is sent, both inputs get
447              * sent.)
448              */
449             foreach ($value as $val) {
450                 if ($val)
451                     return true;
452             }
453             return false;
454         }
455         return (bool) $value;
456     }
457 }
458
459 class _UserPreference_language
460 extends _UserPreference
461 {
462     function _UserPreference_language ($default = DEFAULT_LANGUAGE) {
463         $this->_UserPreference($default);
464     }
465
466     // FIXME: check for valid locale
467     function sanify ($value) {
468         // Revert to DEFAULT_LANGUAGE if user does not specify
469         // language in UserPreferences or chooses <system language>.
470         if ($value == '' or empty($value))
471             $value = DEFAULT_LANGUAGE;
472
473         return (string) $value;
474     }
475 }
476
477 class _UserPreference_theme
478 extends _UserPreference
479 {
480     function _UserPreference_theme ($default = THEME) {
481         $this->_UserPreference($default);
482     }
483
484     function sanify ($value) {
485         if (file_exists($this->_themefile($value)))
486             return $value;
487         return $this->default_value;
488     }
489
490     function update ($newvalue) {
491         global $Theme;
492         include_once($this->_themefile($newvalue));
493         if (empty($Theme))
494             include_once($this->_themefile(THEME));
495     }
496
497     function _themefile ($theme) {
498         return "themes/$theme/themeinfo.php";
499     }
500 }
501
502 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
503
504 // don't save default preferences for efficiency.
505 class UserPreferences
506 {
507     function UserPreferences ($saved_prefs = false) {
508         // userid stored too, to ensure the prefs are being loaded for
509         // the correct (currently signing in) userid if stored in a
510         // cookie.
511         $this->_prefs
512             = array(
513                     'userid'        => new _UserPreference(''),
514                     'passwd'        => new _UserPreference(''),
515                     'autologin'     => new _UserPreference_bool(),
516                     'email'         => new _UserPreference(''),
517                     'emailVerified' => new _UserPreference_bool(),
518                     'notifyPages'   => new _UserPreference(''),
519                     'theme'         => new _UserPreference_theme(THEME),
520                     'lang'          => new _UserPreference_language(DEFAULT_LANGUAGE),
521                     'editWidth'     => new _UserPreference_int(EDITWIDTH_DEFAULT_COLS,
522                                                                EDITWIDTH_MIN_COLS,
523                                                                EDITWIDTH_MAX_COLS),
524                     'noLinkIcons'   => new _UserPreference_bool(),
525                     'editHeight'    => new _UserPreference_int(EDITHEIGHT_DEFAULT_ROWS,
526                                                                EDITHEIGHT_MIN_ROWS,
527                                                                EDITHEIGHT_DEFAULT_ROWS),
528                     'timeOffset'    => new _UserPreference_numeric(TIMEOFFSET_DEFAULT_HOURS,
529                                                                    TIMEOFFSET_MIN_HOURS,
530                                                                    TIMEOFFSET_MAX_HOURS),
531                     'relativeDates' => new _UserPreference_bool()
532                     );
533
534         if (is_array($saved_prefs)) {
535             foreach ($saved_prefs as $name => $value)
536                 $this->set($name, $value);
537         }
538     }
539
540     function _getPref ($name) {
541         if (!isset($this->_prefs[$name])) {
542             if ($name == 'passwd2') return false;
543             trigger_error("$name: unknown preference", E_USER_NOTICE);
544             return false;
545         }
546         return $this->_prefs[$name];
547     }
548
549     function get ($name) {
550         if (isset($this->_prefs[$name]))
551             return $this->_prefs[$name];
552         if (!($pref = $this->_getPref($name)))
553             return false;
554         return $pref->default_value;
555     }
556
557     function set ($name, $value) {
558         if (!($pref = $this->_getPref($name)))
559             return false;
560
561         $newvalue = $pref->sanify($value);
562         $oldvalue = $this->get($name);
563
564         // update on changes
565         if ($newvalue != $oldvalue)
566             $pref->update($newvalue);
567
568         // don't set default values to save space (in cookies, db and
569         // sesssion)
570         if ($value == $pref->default_value)
571             unset($this->_prefs[$name]);
572         else
573             $this->_prefs[$name] = $newvalue;
574     }
575
576     function getAll() {
577         return $this->_prefs;
578     }
579
580     function pack($nonpacked) {
581         return serialize($nonpacked);
582     }
583     function unpack($packed) {
584         if (!$packed)
585             return false;
586         if (substr($packed, 0, 2) == "O:") {
587             // Looks like a serialized object
588             return unserialize($packed);
589         }
590         //trigger_error("DEBUG: Can't unpack bad UserPreferences",
591         //E_USER_WARNING);
592         return false;
593     }
594
595     function hash () {
596         return hash($this->_prefs);
597     }
598 }
599
600
601 // $Log: not supported by cvs2svn $
602 // Revision 1.3  2003/12/06 19:10:46  carstenklapp
603 // Finished off logic for determining user class, including
604 // PassUser. Removed ability of BogoUser to save prefs into a page.
605 //
606 // Revision 1.2  2003/12/03 21:45:48  carstenklapp
607 // Added admin user, password user, and preference classes. Added
608 // password checking functions for users and the admin. (Now the easy
609 // parts are nearly done).
610 //
611 // Revision 1.1  2003/12/02 05:46:36  carstenklapp
612 // Complete rewrite of WikiUser.php.
613 //
614 // This should make it easier to hook in user permission groups etc. some
615 // time in the future. Most importantly, to finally get UserPreferences
616 // fully working properly for all classes of users: AnonUser, BogoUser,
617 // AdminUser; whether they have a NamesakePage (PersonalHomePage) or not,
618 // want a cookie or not, and to bring back optional AutoLogin with the
619 // UserName stored in a cookie--something that was lost after PhpWiki had
620 // dropped the default http auth login method.
621 //
622 // Added WikiUser classes which will (almost) work together with existing
623 // UserPreferences class. Other parts of PhpWiki need to be updated yet
624 // before this code can be hooked up.
625 //
626
627 // Local Variables:
628 // mode: php
629 // tab-width: 8
630 // c-basic-offset: 4
631 // c-hanging-comment-ender-p: nil
632 // indent-tabs-mode: nil
633 // End:
634 ?>