2 rcs_id('$Id: WikiUserNew.php,v 1.4 2003-12-07 19:29:48 carstenklapp Exp $');
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
10 // Returns a user object, which contains the user's preferences.
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.
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.
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.
28 if (!defined('COOKIE_EXPIRATION_DAYS')) define('COOKIE_EXPIRATION_DAYS', 365);
29 if (!defined('COOKIE_DOMAIN')) define('COOKIE_DOMAIN', '/');
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);
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);
39 define('TIMEOFFSET_MIN_HOURS', -26);
40 define('TIMEOFFSET_MAX_HOURS', 26);
41 if (!defined('TIMEOFFSET_DEFAULT_HOURS')) define('TIMEOFFSET_DEFAULT_HOURS', 0);
44 * There are/will be four constants in index.php to establish login
47 * ALLOW_ANON_USER default true
48 * ALLOW_BOGO_LOGIN default true
49 * ALLOW_USER_PASSWORDS default true
50 * PASSWORD_LENGTH_MINIMUM default 6?
53 * To require user passwords:
54 * ALLOW_BOGO_LOGIN = false,
55 * ALLOW_USER_PASSWORDS = true.
57 * To establish a COMPLETELY private wiki, such as an internal
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
64 * There are other possible combinations, but the typical wiki (such
65 * as PhpWiki.sf.net) would usually just leave all three enabled.
68 // Local convenience functions.
69 function _isAnonUserAllowed() {
70 return (defined('ALLOW_ANON_USER') && ALLOW_ANON_USER);
72 function _isBogoUserAllowed() {
73 return (defined('ALLOW_BOGO_LOGIN') && ALLOW_BOGO_LOGIN);
75 function _isUserPasswordsAllowed() {
76 return (defined('ALLOW_USER_PASSWORDS') && ALLOW_USER_PASSWORDS);
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.
87 if ($UserName == ADMIN_USER)
88 return new _AdminUser($UserName);
90 return _determineBogoUserOrPassUser($UserName);
93 function _determineBogoUserOrPassUser($UserName) {
94 // Sanity check. User name is a condition of the definition of
95 // _BogoUser and _PassUser.
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);
105 // User has no password.
106 if (_isBogoUserAllowed())
109 // Passwords are not allowed, and Bogo is disallowed too. (Only
110 // the admin can sign in).
115 * Primary WikiUser function, called by main.php.
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.
121 * If an _AnonUser object is returned, the user may only browse pages
122 * (and save prefs in a cookie).
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.
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).
132 // Found a user name.
133 return _determineAdminUserOrOtherUser($UserName);
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);
144 if (_isAnonUserAllowed())
146 return false; // User must sign in to browse pages.
148 return false; // User must sign in with a password.
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.
155 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
157 // Base _WikiUser class.
160 var $_level = WIKIAUTH_FORBIDDEN;
162 var $_HomePagehandle = false;
167 function _WikiUser($UserName = '') {
169 $this->UserName = $UserName;
170 $this->_HomePagehandle = $this->hasHomePage();
172 $this->loadPreferences();
175 function loadPreferences() {
176 trigger_error("DEBUG: Note: undefined _WikiUser class trying to load prefs." . " "
177 . "New subclasses of _WikiUser must override this function.");
181 function savePreferences() {
182 trigger_error("DEBUG: Note: undefined _WikiUser class trying to save prefs." . " "
183 . "New subclasses of _WikiUser must override this function.");
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;
194 // check db again (maybe someone else created it since
197 $this->_HomePagehandle = $request->getPage($this->UserName);
198 return $this->_HomePagehandle;
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.");
217 var $_level = WIKIAUTH_ANON;
219 // Anon only gets to load and save prefs in a cookie, that's it.
220 function loadPreferences() {
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."),
228 // TODO: try reading userid from old PhpWiki cookie
229 // formats, then delete old cookie from browser!
232 // try old cookie format.
233 //$cookie = $request->getCookieVar('WIKI_ID');
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
242 if (! $this->UserName || $this->UserName == $unboxedcookie['userid']) {
243 $this->_prefs = new UserPreferences($unboxedcookie);
244 $this->UserName = $unboxedcookie['userid'];
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);
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.");
271 * Do NOT extend _BogoUser to other classes, for checkPass()
272 * security. (In case of defects in code logic of the new class!)
277 var $_level = WIKIAUTH_BOGO;
279 function checkPass($submitted_password) {
280 // By definition, BogoUser has an empty password.
288 * New classes for externally authenticated users should extend from
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
298 var $_level = WIKIAUTH_USER;
300 //TODO: password changing
301 //TODO: email verification
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);
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);
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);
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
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."),
348 // Verify against cleartext password.
349 if ($submitted_password == $stored_password)
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."),
368 * For security, this class should not be extended. Instead, extend
369 * from _PassUser (think of this as unix "root").
374 var $_level = WIKIAUTH_ADMIN;
376 function checkPass($submitted_password) {
377 $stored_password = ADMIN_PASSWD;
378 return $this->_checkPass($submitted_password, $stored_password);
382 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
384 class _UserPreference
386 function _UserPreference ($default_value) {
387 $this->default_value = $default_value;
390 function sanify ($value) {
391 return (string)$value;
394 function update ($value) {
398 class _UserPreference_numeric
399 extends _UserPreference
401 function _UserPreference_numeric ($default, $minval = false,
403 $this->_UserPreference((double)$default);
404 $this->_minval = (double)$minval;
405 $this->_maxval = (double)$maxval;
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;
418 class _UserPreference_int
419 extends _UserPreference_numeric
421 function _UserPreference_int ($default, $minval = false, $maxval = false) {
422 $this->_UserPreference_numeric((int)$default, (int)$minval,
426 function sanify ($value) {
427 return (int)parent::sanify((int)$value);
431 class _UserPreference_bool
432 extends _UserPreference
434 function _UserPreference_bool ($default = false) {
435 $this->_UserPreference((bool)$default);
438 function sanify ($value) {
439 if (is_array($value)) {
440 /* This allows for constructs like:
442 * <input type="hidden" name="pref[boolPref][]" value="0" />
443 * <input type="checkbox" name="pref[boolPref][]" value="1" />
445 * (If the checkbox is not checked, only the hidden input
446 * gets sent. If the checkbox is sent, both inputs get
449 foreach ($value as $val) {
455 return (bool) $value;
459 class _UserPreference_language
460 extends _UserPreference
462 function _UserPreference_language ($default = DEFAULT_LANGUAGE) {
463 $this->_UserPreference($default);
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;
473 return (string) $value;
477 class _UserPreference_theme
478 extends _UserPreference
480 function _UserPreference_theme ($default = THEME) {
481 $this->_UserPreference($default);
484 function sanify ($value) {
485 if (file_exists($this->_themefile($value)))
487 return $this->default_value;
490 function update ($newvalue) {
492 include_once($this->_themefile($newvalue));
494 include_once($this->_themefile(THEME));
497 function _themefile ($theme) {
498 return "themes/$theme/themeinfo.php";
502 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
504 // don't save default preferences for efficiency.
505 class UserPreferences
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
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,
524 'noLinkIcons' => new _UserPreference_bool(),
525 'editHeight' => new _UserPreference_int(EDITHEIGHT_DEFAULT_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()
534 if (is_array($saved_prefs)) {
535 foreach ($saved_prefs as $name => $value)
536 $this->set($name, $value);
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);
546 return $this->_prefs[$name];
549 function get ($name) {
550 if (isset($this->_prefs[$name]))
551 return $this->_prefs[$name];
552 if (!($pref = $this->_getPref($name)))
554 return $pref->default_value;
557 function set ($name, $value) {
558 if (!($pref = $this->_getPref($name)))
561 $newvalue = $pref->sanify($value);
562 $oldvalue = $this->get($name);
565 if ($newvalue != $oldvalue)
566 $pref->update($newvalue);
568 // don't set default values to save space (in cookies, db and
570 if ($value == $pref->default_value)
571 unset($this->_prefs[$name]);
573 $this->_prefs[$name] = $newvalue;
577 return $this->_prefs;
580 function pack($nonpacked) {
581 return serialize($nonpacked);
583 function unpack($packed) {
586 if (substr($packed, 0, 2) == "O:") {
587 // Looks like a serialized object
588 return unserialize($packed);
590 //trigger_error("DEBUG: Can't unpack bad UserPreferences",
596 return hash($this->_prefs);
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.
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).
611 // Revision 1.1 2003/12/02 05:46:36 carstenklapp
612 // Complete rewrite of WikiUser.php.
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.
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.
631 // c-hanging-comment-ender-p: nil
632 // indent-tabs-mode: nil