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