]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiUser.php
code reformatting
[SourceForge/phpwiki.git] / lib / WikiUser.php
1 <?php //-*-php-*-
2 rcs_id('$Id: WikiUser.php,v 1.31 2003-01-15 05:37:20 carstenklapp Exp $');
3
4 // It is anticipated that when userid support is added to phpwiki,
5 // this object will hold much more information (e-mail,
6 // home(wiki)page, etc.) about the user.
7
8 // There seems to be no clean way to "log out" a user when using HTTP
9 // authentication. So we'll hack around this by storing the currently
10 // logged in username and other state information in a cookie.
11
12 // 2002-09-08 11:44:04 rurban
13 // Todo: Fix prefs cookie/session handling:
14 //       _userid and _homepage cookie/session vars still hold the
15 //       serialized string.
16 //       If no homepage, fallback to prefs in cookie as in 1.3.3.
17
18 define('WIKIAUTH_ANON', 0);
19 define('WIKIAUTH_BOGO', 1);     // any valid WikiWord is enough
20 define('WIKIAUTH_USER', 2);     // real auth from a database/file/server.
21
22 define('WIKIAUTH_ADMIN', 10);
23 define('WIKIAUTH_FORBIDDEN', 11); // Completely not allowed.
24
25 $UserPreferences = array(
26                          'userid'        => new _UserPreference(''), // really store this also?
27                          'passwd'        => new _UserPreference(''),
28                          'email'         => new _UserPreference(''),
29                          'emailVerified' => new _UserPreference_bool(),
30                          'notifyPages'   => new _UserPreference(''),
31                          'theme'         => new _UserPreference_theme(THEME),
32                          'lang'          => new _UserPreference_language(DEFAULT_LANGUAGE),
33                          'editWidth'     => new _UserPreference_int(80, 30, 150),
34                          'editHeight'    => new _UserPreference_int(22, 5, 80),
35                          'timeOffset'    => new _UserPreference_numeric(0, -26, 26),
36                          'relativeDates' => new _UserPreference_bool()
37                          );
38
39 class WikiUser {
40     var $_userid = false;
41     var $_level  = false;
42     var $_request, $_dbi, $_authdbi, $_homepage;
43     var $_authmethod = '', $_authhow = '';
44
45     /**
46      * Constructor.
47      */
48     function WikiUser ($userid = false, $authlevel = false) {
49         $this->_request = &$GLOBALS['request'];
50         $this->_dbi = &$this->_request->getDbh();
51
52         if (isa($userid, 'WikiUser')) {
53             $this->_userid   = $userid->_userid;
54             $this->_level    = $userid->_level;
55         }
56         else {
57             $this->_userid = $userid;
58             $this->_level = $authlevel;
59         }
60         if ($this->_userid)
61             $this->_homepage = $this->_dbi->getPage($this->_userid);
62         if (!$this->_ok()) {
63             // Paranoia: if state is at all inconsistent, log out...
64             $this->_userid = false;
65             $this->_level = false;
66             $this->_homepage = false;
67             $this->_authhow .= ' paranoia logout';
68         }
69     }
70
71     function auth_how() {
72         return $this->_authhow;
73     }
74
75     /**
76      * Invariant
77      */
78     function _ok () {
79         if (empty($this->_userid) || empty($this->_level)) {
80             // This is okay if truly logged out.
81             return $this->_userid === false && $this->_level === false;
82         }
83         // User is logged in...
84
85         // Check for valid authlevel.
86         if (!in_array($this->_level, array(WIKIAUTH_BOGO, WIKIAUTH_USER,
87                                            WIKIAUTH_ADMIN)))
88             return false;
89
90         // Check for valid userid.
91         if (!is_string($this->_userid))
92             return false;
93         return true;
94     }
95
96     function getId () {
97         return ( $this->isSignedIn()
98                  ? $this->_userid
99                  : $this->_request->get('REMOTE_ADDR') ); // FIXME: globals
100     }
101
102     function getAuthenticatedId() {
103         return ( $this->isAuthenticated()
104                  ? $this->_userid
105                  : $this->_request->get('REMOTE_ADDR') ); // FIXME: globals
106     }
107
108     function isSignedIn () {
109         return $this->_level >= WIKIAUTH_BOGO;
110     }
111
112     function isAuthenticated () {
113         return $this->_level >= WIKIAUTH_USER;
114     }
115
116     function isAdmin () {
117         return $this->_level == WIKIAUTH_ADMIN;
118     }
119
120     function hasAuthority ($require_level) {
121         return $this->_level >= $require_level;
122     }
123
124     function AuthCheck ($postargs) {
125         // Normalize args, and extract.
126         $keys = array('userid', 'passwd', 'require_level', 'login', 'logout',
127                       'cancel');
128         foreach ($keys as $key)
129             $args[$key] = isset($postargs[$key]) ? $postargs[$key] : false;
130         extract($args);
131         $require_level = max(0, min(WIKIAUTH_ADMIN, (int)$require_level));
132
133         if ($logout)
134             return new WikiUser; // Log out
135         elseif ($cancel)
136             return false;        // User hit cancel button.
137         elseif (!$login && !$userid)
138             return false;       // Nothing to do?
139
140         $authlevel = $this->_pwcheck($userid, $passwd);
141         if (!$authlevel)
142             return _("Invalid password or userid.");
143         elseif ($authlevel < $require_level)
144             return _("Insufficient permissions.");
145
146         // Successful login.
147         $user = new WikiUser;
148         $user->_userid = $userid;
149         $user->_level = $authlevel;
150         return $user;
151     }
152
153     function PrintLoginForm (&$request, $args, $fail_message = false,
154                              $seperate_page = true) {
155         include_once('lib/Template.php');
156
157         $userid = '';
158         $require_level = 0;
159         extract($args); // fixme
160
161         $require_level = max(0, min(WIKIAUTH_ADMIN, (int)$require_level));
162
163         $pagename = $request->getArg('pagename');
164         $login = new Template('login', $request,
165                               compact('pagename', 'userid', 'require_level',
166                                       'fail_message', 'pass_required'));
167         if ($seperate_page) {
168             $top = new Template('html', $request,
169                                 array('TITLE' => _("Sign In")));
170             return $top->printExpansion($login);
171         } else {
172             return $login;
173         }
174     }
175
176     /**
177      * Check password.
178      */
179     function _pwcheck ($userid, $passwd) {
180         global $WikiNameRegexp;
181
182         if (!empty($userid) && $userid == ADMIN_USER) {
183             // $this->_authmethod = 'pagedata';
184             if (defined('ENCRYPTED_PASSWD') && ENCRYPTED_PASSWD)
185                 if ( !empty($passwd)
186                      && crypt($passwd, ADMIN_PASSWD) == ADMIN_PASSWD )
187                     return WIKIAUTH_ADMIN;
188             if (!empty($passwd)) {
189                 if ($passwd == ADMIN_PASSWD)
190                   return WIKIAUTH_ADMIN;
191                 else {
192                     // maybe we forgot to enable ENCRYPTED_PASSWD?
193                     if ( function_exists('crypt')
194                          && crypt($passwd, ADMIN_PASSWD) == ADMIN_PASSWD ) {
195                         trigger_error(_("You forgot to set ENCRYPTED_PASSWD to true. Please update your /index.php"),
196                                       E_USER_WARNING);
197                         return WIKIAUTH_ADMIN;
198                     }
199                 }
200             }
201             return false;
202         }
203         // HTTP Authentication
204         elseif (ALLOW_HTTP_AUTH_LOGIN && !empty($PHP_AUTH_USER)) {
205             // if he ignored the password field, because he is already
206             // authenticated try the previously given password.
207             if (empty($passwd))
208                 $passwd = $PHP_AUTH_PW;
209         }
210
211         // WikiDB_User DB/File Authentication from $DBAuthParams
212         // Check if we have the user. If not try other methods.
213         if (ALLOW_USER_LOGIN) { // && !empty($passwd)) {
214             $request = $this->_request;
215             // first check if the user is known
216             if ($this->exists($userid)) {
217                 $this->_authmethod = 'pagedata';
218                 return ($this->checkPassword($passwd)) ? WIKIAUTH_USER : false;
219             } else {
220                 // else try others such as LDAP authentication:
221                 if (ALLOW_LDAP_LOGIN && !empty($passwd)) {
222                     if ($ldap = ldap_connect(LDAP_AUTH_HOST)) { // must be a valid LDAP server!
223                         $r = @ldap_bind($ldap); // this is an anonymous bind
224                         $st_search = "uid=$userid";
225                         // Need to set the right root search information. see ../index.php
226                         $sr = ldap_search($ldap, LDAP_AUTH_SEARCH,
227                                           "$st_search");
228                         $info = ldap_get_entries($ldap, $sr); // there may be more hits with this userid. try every
229                         for ($i = 0; $i < $info["count"]; $i++) {
230                             $dn = $info[$i]["dn"];
231                             // The password is still plain text.
232                             if ($r = @ldap_bind($ldap, $dn, $passwd)) {
233                                 // ldap_bind will return TRUE if everything matches
234                                 ldap_close($ldap);
235                                 $this->_authmethod = 'LDAP';
236                                 return WIKIAUTH_USER;
237                             }
238                         }
239                     } else {
240                         trigger_error("Unable to connect to LDAP server "
241                                       . LDAP_AUTH_HOST, E_USER_WARNING);
242                     }
243                 }
244                 // imap authentication. added by limako
245                 if (ALLOW_IMAP_LOGIN && !empty($passwd)) {
246                     $mbox = @imap_open( "{" . IMAP_AUTH_HOST . ":143}",
247                                         $userid, $passwd, OP_HALFOPEN );
248                     if($mbox) {
249                         imap_close($mbox);
250                         $this->_authmethod = 'IMAP';
251                         return WIKIAUTH_USER;
252                     }
253                 }
254             }
255         }
256         if ( ALLOW_BOGO_LOGIN
257              && preg_match('/\A' . $WikiNameRegexp . '\z/', $userid) ) {
258             $this->_authmethod = 'BOGO';
259             return WIKIAUTH_BOGO;
260         }
261         return false;
262     }
263
264     // Todo: try our WikiDB backends.
265     function getPreferences() {
266         // Restore saved preferences.
267
268         // I'd rather prefer only to store the UserId in the cookie or
269         // session, and get the preferences from the db or page.
270         if (!($prefs = $this->_request->getCookieVar('WIKI_PREFS2')))
271             $prefs = $this->_request->getSessionVar('wiki_prefs');
272
273         //if (!$this->_userid && !empty($GLOBALS['HTTP_COOKIE_VARS']['WIKI_ID'])) {
274         //    $this->_userid = $GLOBALS['HTTP_COOKIE_VARS']['WIKI_ID'];
275         //}
276
277         // before we get his prefs we should check if he is signed in
278         if (USE_PREFS_IN_PAGE && $this->homePage()) { // in page metadata
279             if ($pref = $this->_homepage->get('pref')) {
280                 //trigger_error("pref=".$pref);//debugging
281                 $prefs = unserialize($pref);
282             }
283         }
284         return new UserPreferences($prefs);
285     }
286
287     // No cookies anymore for all prefs, only the userid. PHP creates
288     // a session cookie in memory, which is much more efficient.
289     //
290     // Return the number of changed entries?
291     function setPreferences($prefs, $id_only = false) {
292         // update the id
293         $this->_request->setSessionVar('wiki_prefs', $prefs);
294         // $this->_request->setCookieVar('WIKI_PREFS2', $this->_prefs, 365);
295         // simple unpacked cookie
296         if ($this->_userid) setcookie('WIKI_ID', $this->_userid, 365, '/');
297
298         // We must ensure that any password is encrypted.
299         // We don't need any plaintext password.
300         if (! $id_only ) {
301             if ($this->isSignedIn()) {
302                 if ($this->isAdmin())
303                     $prefs->set('passwd', '');
304                 // already stored in index.php, and it might be
305                 // plaintext! well oh well
306                 if ($homepage = $this->homePage()) {
307                     $homepage->set('pref', serialize($prefs->_prefs));
308                     return sizeof($prefs->_prefs);
309                 } else {
310                     trigger_error("No homepage for user found. Creating one...",
311                                   E_USER_WARNING);
312                     $this->createHomepage($prefs);
313                     //$homepage->set('pref', serialize($prefs->_prefs));
314                     return sizeof($prefs->_prefs);
315                 }
316             } else {
317                 trigger_error("you must be signed in", E_USER_WARNING);
318             }
319         }
320         return 0;
321     }
322
323     // check for homepage with user flag.
324     // can be overriden from the auth backends
325     function exists() {
326         $homepage = $this->homePage();
327         return ($this->_userid && $homepage && $homepage->get('pref'));
328     }
329
330     // doesn't check for existance!!! hmm.
331     // how to store metadata in not existing pages? how about versions?
332     function homePage() {
333         if (!$this->_userid)
334             return false;
335         if (!empty($this->_homepage)) {
336             return $this->_homepage;
337         } else {
338             $this->_homepage = $this->_dbi->getPage($this->_userid);
339             return $this->_homepage;
340         }
341     }
342
343     // create user by checking his homepage
344     function createUser ($pref, $createDefaultHomepage = true) {
345         if ($this->exists())
346             return;
347         if ($createDefaultHomepage) {
348             $this->createHomepage($pref);
349         } else {
350             // empty page
351             include "lib/loadsave.php";
352             $pageinfo = array('pagedata' => array('pref' => serialize($pref->_pref)),
353                               'versiondata' => array('author' => $this->_userid),
354                               'pagename' => $this->_userid,
355                               'content' => _('CategoryHomepage'));
356             SavePage (&$this->_request, $pageinfo, false, false);
357         }
358         $this->setPreferences($pref);
359     }
360
361     // create user and default user homepage
362     function createHomepage ($pref) {
363         $pagename = $this->_userid;
364         include "lib/loadsave.php";
365
366         // create default homepage:
367         //  properly expanded template and the pref metadata
368         $template = Template('homepage.tmpl', $this->_request);
369         $text  = $template->getExpansion();
370         $pageinfo = array('pagedata' => array('pref' => serialize($pref->_pref)),
371                           'versiondata' => array('author' => $this->_userid),
372                           'pagename' => $pagename,
373                           'content' => $text);
374         SavePage (&$this->_request, $pageinfo, false, false);
375
376         // create Calender
377         $pagename = $this->_userid . SUBPAGE_SEPARATOR . _('Preferences');
378         if (! isWikiPage($pagename)) {
379             $pageinfo = array('pagedata' => array(),
380                               'versiondata' => array('author' => $this->_userid),
381                               'pagename' => $pagename,
382                               'content' => "<?plugin Calender ?>\n");
383             SavePage (&$this->_request, $pageinfo, false, false);
384         }
385
386         // create Preferences
387         $pagename = $this->_userid . SUBPAGE_SEPARATOR . _('Preferences');
388         if (! isWikiPage($pagename)) {
389             $pageinfo = array('pagedata' => array(),
390                               'versiondata' => array('author' => $this->_userid),
391                               'pagename' => $pagename,
392                               'content' => "<?plugin UserPreferences ?>\n");
393             SavePage (&$this->_request, $pageinfo, false, false);
394         }
395     }
396
397     function tryAuthBackends() {
398         return ''; // crypt('') will never be ''
399     }
400
401     // Auth backends must store the crypted password where?
402     // Not in the preferences.
403     function checkPassword($passwd) {
404         $prefs = $this->getPreferences();
405         $stored_passwd = $prefs->get('passwd'); // crypted
406         if (empty($prefs->_prefs['passwd']))    // not stored in the page
407             // allow empty passwords? At least store a '*' then.
408             // try other backend. hmm.
409             $stored_passwd = $this->tryAuthBackends($this->_userid);
410         if (empty($stored_passwd)) {
411             trigger_error(sprintf(_("Old UserPage %s without stored password updated with empty password. Set a password in your UserPreferences."),
412                                   $this->_userid), E_USER_NOTICE);
413             $prefs->set('passwd','*');
414             return true;
415         }
416         if ($stored_passwd == '*')
417             return true;
418         if ( !empty($passwd)
419              && crypt($passwd, $stored_passwd) == $stored_passwd )
420             return true;
421         else
422             return false;
423     }
424
425     function changePassword($newpasswd, $passwd2 = false) {
426         if (! $this->mayChangePassword() ) {
427             trigger_error(sprintf("Attempt to change an external password for '%s'. Not allowed!",
428                                   $this->_userid), E_USER_ERROR);
429             return;
430         }
431         if ($passwd2 && $passwd2 != $newpasswd) {
432             trigger_error("The second password must be the same as the first to change it",
433                           E_USER_ERROR);
434             return;
435         }
436         $prefs = $this->getPreferences();
437         //$oldpasswd = $prefs->get('passwd');
438         $prefs->set('passwd', crypt($newpasswd));
439         $this->setPreferences($prefs);
440     }
441
442     function mayChangePassword() {
443         // on external DBAuth maybe. on IMAP or LDAP not
444         // on internal DBAuth yes
445         if (in_array($this->_authmethod, array('IMAP', 'LDAP')))
446             return false;
447         if ($this->isAdmin())
448             return false;
449         if ($this->_authmethod == 'pagedata')
450             return true;
451         if ($this->_authmethod == 'authdb')
452             return true;
453     }
454                          }
455
456 // create user and default user homepage
457 function createUser ($userid, $pref) {
458     $user = new WikiUser ($userid);
459     $user->createUser($pref);
460 }
461
462 class _UserPreference
463 {
464     function _UserPreference ($default_value) {
465         $this->default_value = $default_value;
466     }
467
468     function sanify ($value) {
469         return (string)$value;
470     }
471
472     function update ($value) {
473     }
474 }
475
476 class _UserPreference_numeric
477 extends _UserPreference
478 {
479     function _UserPreference_numeric ($default, $minval = false,
480                                       $maxval = false) {
481         $this->_UserPreference((double)$default);
482         $this->_minval = (double)$minval;
483         $this->_maxval = (double)$maxval;
484     }
485
486     function sanify ($value) {
487         $value = (double)$value;
488         if ($this->_minval !== false && $value < $this->_minval)
489             $value = $this->_minval;
490         if ($this->_maxval !== false && $value > $this->_maxval)
491             $value = $this->_maxval;
492         return $value;
493     }
494 }
495
496 class _UserPreference_int
497 extends _UserPreference_numeric
498 {
499     function _UserPreference_int ($default, $minval = false, $maxval = false) {
500         $this->_UserPreference_numeric((int)$default, (int)$minval,
501                                        (int)$maxval);
502     }
503
504     function sanify ($value) {
505         return (int)parent::sanify((int)$value);
506     }
507 }
508
509 class _UserPreference_bool
510 extends _UserPreference
511 {
512     function _UserPreference_bool ($default = false) {
513         $this->_UserPreference((bool)$default);
514     }
515
516     function sanify ($value) {
517         if (is_array($value)) {
518             /* This allows for constructs like:
519              *
520              *   <input type="hidden" name="pref[boolPref][]" value="0" />
521              *   <input type="checkbox" name="pref[boolPref][]" value="1" />
522              *
523              * (If the checkbox is not checked, only the hidden input
524              * gets sent. If the checkbox is sent, both inputs get
525              * sent.)
526              */
527             foreach ($value as $val) {
528                 if ($val)
529                     return true;
530             }
531             return false;
532         }
533         return (bool) $value;
534     }
535 }
536
537 class _UserPreference_language
538 extends _UserPreference
539 {
540     function _UserPreference_language ($default = DEFAULT_LANGUAGE) {
541         $this->_UserPreference($default);
542     }
543
544     function sanify ($value) {
545         // FIXME: check for valid locale
546         return $value;
547     }
548 }
549
550 class _UserPreference_theme
551 extends _UserPreference
552 {
553     function _UserPreference_theme ($default = THEME) {
554         $this->_UserPreference($default);
555     }
556
557     function sanify ($value) {
558         if (file_exists($this->_themefile($value)))
559             return $value;
560         return $this->default_value;
561     }
562
563     function update ($newvalue) {
564         global $Theme;
565         include_once($this->_themefile($newvalue));
566         if (empty($Theme))
567             include_once($this->_themefile(THEME));
568     }
569
570     function _themefile ($theme) {
571         return "themes/$theme/themeinfo.php";
572     }
573 }
574
575 // don't save default preferences for efficiency.
576 class UserPreferences {
577     function UserPreferences ($saved_prefs = false) {
578         $this->_prefs = array();
579
580         if (isa($saved_prefs, 'UserPreferences') && $saved_prefs->_prefs) {
581             foreach ($saved_prefs->_prefs as $name => $value)
582                 $this->set($name, $value);
583         } elseif (is_array($saved_prefs)) {
584             foreach ($saved_prefs as $name => $value)
585                 $this->set($name, $value);
586         }
587     }
588
589     function _getPref ($name) {
590         global $UserPreferences;
591         if (!isset($UserPreferences[$name])) {
592             if ($name == 'passwd2') return false;
593             trigger_error("$name: unknown preference", E_USER_NOTICE);
594             return false;
595         }
596         return $UserPreferences[$name];
597     }
598
599     function get ($name) {
600         if (isset($this->_prefs[$name]))
601             return $this->_prefs[$name];
602         if (!($pref = $this->_getPref($name)))
603             return false;
604         return $pref->default_value;
605     }
606
607     function set ($name, $value) {
608         if (!($pref = $this->_getPref($name)))
609             return false;
610
611         $newvalue = $pref->sanify($value);
612         $oldvalue = $this->get($name);
613
614         // update on changes
615         if ($newvalue != $oldvalue)
616             $pref->update($newvalue);
617
618         // don't set default values to save space (in cookies, db and
619         // sesssion)
620         if ($value == $pref->default_value)
621             unset($this->_prefs[$name]);
622         else
623             $this->_prefs[$name] = $newvalue;
624     }
625 }
626
627 // $Log: not supported by cvs2svn $
628 // Revision 1.30  2003/01/15 04:59:27  carstenklapp
629 // Bugfix: Previously stored preferences were not loading when user
630 // signed in. (Fixed... I hope.)
631 //
632
633 // Local Variables:
634 // mode: php
635 // tab-width: 8
636 // c-basic-offset: 4
637 // c-hanging-comment-ender-p: nil
638 // indent-tabs-mode: nil
639 // End:
640 ?>