source: branches/1.2/libs/UserAuth.php @ 1333

Revision 1333, 21.1 KB checked in by nick_ramsay, 3 years ago (diff)

[Branch 1.2] Add block list checks for username and email when updating an account.

Line 
1<?php
2/**
3 * Functions for authnticating, logging in and registering users
4 *
5 * PHP version 5
6 *
7 * LICENSE: Hotaru CMS is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of
10 * the License, or (at your option) any later version.
11 *
12 * Hotaru CMS is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with Hotaru CMS. If not, see http://www.gnu.org/licenses/.
18 *
19 * @category  Content Management System
20 * @package   HotaruCMS
21 * @author    Nick Ramsay <admin@hotarucms.org>
22 * @copyright Copyright (c) 2009, Hotaru CMS
23 * @license   http://www.gnu.org/copyleft/gpl.html GNU General Public License
24 * @link      http://www.hotarucms.org/
25 */
26class UserAuth extends UserBase
27{
28    /**
29     * check cookie and log in
30     *
31     * @return bool
32     */
33    public function checkCookie($h)
34    {
35        // Check for a cookie. If present then the user is logged in.
36        $h_user = $h->cage->cookie->testUsername('hotaru_user');
37       
38        if((!$h_user) || (!$h->cage->cookie->keyExists('hotaru_key'))) {
39            $this->setLoggedOutUser($h);
40            return false;
41        }
42       
43        $user_info=explode(":", base64_decode($h->cage->cookie->getRaw('hotaru_key')));
44       
45        if (($h_user != $user_info[0]) || (crypt($user_info[0], 22) != $user_info[1])) {
46            $this->setLoggedOutUser($h);
47            return false;
48        }
49
50        $this->name = $h_user;
51        if ($h_user) {
52            $valid = $this->getUserBasic($h, 0, $this->name);
53
54            if ($valid) {
55                $this->loggedIn = true;
56                if (!session_id()) { $this->updateUserLastVisit($h); } // update user_lastvisit field when a new session is created
57                $h->pluginHook('userauth_checkcookie_success'); // user_signin throws out killspammed, banned and suspended users
58               
59                // SUCCESS!!!
60                return true;
61            } else {
62                $h->currentUser->destroyCookieAndSession(); // removes cookie and session for physically deleted users
63            }
64        }
65       
66        // otherwise, give them "logged out" permissions
67        $this->setLoggedOutUser($h);
68        return false;
69    }
70   
71   
72    /**
73     * Log a user in if their username and password are valid
74     *
75     * @param string $username
76     * @param string $password
77     * @return bool
78     */
79    public function loginCheck($h, $username = '', $password = '')
80    {
81        // Read the current user's basic details
82        $userX = $this->getUserBasic($h, 0, $username);
83        if (!$userX) { return false; }
84       
85        // destroy the cookie for the following usergroups:
86        $no_cookie = array('killspammed', 'banned', 'suspended');
87        if (in_array($userX->user_role, $no_cookie)) {
88            $this->destroyCookieAndSession();
89            return false;
90        }
91       
92        $salt_length = 9;
93        $result = '';
94       
95        // Allow plugin to bypass the password check with their own methods, e.g. RPX
96        $plugin_result = $h->pluginHook('userbase_logincheck', '', array($username, $password));
97       
98        if (!$plugin_result)
99        {
100            // nothing or (false) was returned from the plugins, so confirm the username and password match:
101            $password = $this->generateHash($password, substr($userX->user_password, 0, $salt_length));
102            $sql = "SELECT user_username, user_password FROM " . TABLE_USERS . " WHERE user_username = %s AND user_password = %s";
103            $result = $h->db->get_row($h->db->prepare($sql, $username, $password));
104        }
105        elseif ($plugin_result)
106        {
107            // a positive result was returned from the plugin(s)
108            // let's hope the plugin did its own authentication because we've skipped the usual username/passowrd check!
109            $result = true;
110        }
111       
112        if ($result) { return true; } else { return false; }
113    }
114   
115   
116    /**
117     * Generate a hash for the password
118     *
119     * @param string $plainText - the password
120     * @param mixed $salt
121     *
122     * Note: Adapted from SocialWebCMS
123     */
124    public function generateHash($plainText, $salt = null)
125    {
126        $salt_length = 9;
127        if ($salt === null) {
128            $salt = substr(md5(uniqid(rand(), true)), 0, $salt_length); }
129        else {
130            $salt = substr($salt, 0, $salt_length);
131            }
132        return $salt . sha1($salt . $plainText);
133    }
134
135
136    /**
137     * Give logged out user default permissions
138     */   
139    public function setLoggedOutUser($h)
140    {
141        $default_perms = $this->getDefaultPermissions($h);
142        unset($default_perms['options']);  // don't need this for individual users
143        $this->setAllPermissions($default_perms);
144    }
145   
146   
147    /**
148     * Update last login
149     *
150     * @return bool
151     */
152    public function updateUserLastLogin($h)
153    {
154        if ($this->id != 0) {
155            $sql = "UPDATE " . TABLE_USERS . " SET user_lastlogin = CURRENT_TIMESTAMP WHERE user_id = %d";
156            $h->db->query($h->db->prepare($sql, $this->id));
157            return true;
158        } else {
159            return false;
160        }
161    }
162   
163
164    /**
165     * Update last visit (new session started)
166     *
167     * @return bool
168     */
169    public function updateUserLastVisit($h)
170    {
171        if ($this->id != 0) {
172            $sql = "UPDATE " . TABLE_USERS . " SET user_lastvisit = CURRENT_TIMESTAMP WHERE user_id = %d";
173            $h->db->query($h->db->prepare($sql, $this->id));
174            return true;
175        } else {
176            return false;
177        }
178    }
179   
180   
181    /**
182     * Set a 30-day cookie
183     *
184     * @param string $remember checkbox with value "checked" or empty
185     * @return bool
186     */
187    public function setCookie($h, $remember)
188    {
189        if (!$this->name)
190        {
191            echo $h->lang['main_userbase_cookie_error'];
192            return false;
193        } else {
194            $strCookie=base64_encode(
195                join(':', array($this->name, crypt($this->name, 22)))
196            );
197           
198            if ($remember) {
199                // 2592000 = 60 seconds * 60 mins * 24 hours * 30 days
200                $month = 2592000 + time();
201            } else {
202                $month = 0;
203            }
204           
205            if (strpos(BASEURL, "localhost") !== false) {
206                setcookie("hotaru_user", $this->name, $month, "/");
207                setcookie("hotaru_key", $strCookie, $month, "/");
208            } else {
209                $parsed = parse_url(BASEURL);
210
211                // now we need a dot in front of that so cookies work across subdomains:
212                setcookie("hotaru_user", $this->name, $month, "/", "." . $parsed['host']);
213                setcookie("hotaru_key", $strCookie, $month, "/", "." . $parsed['host']);
214            }
215            return true;
216        }
217    }
218   
219   
220    /**
221     * Delete cookie and destroy session
222     */
223    public function destroyCookieAndSession()
224    {
225        // setting a cookie with a negative time expires it
226       
227        if (strpos(BASEURL, "localhost") !== false) {
228            setcookie("hotaru_user", "", time()-3600, "/");
229            setcookie("hotaru_key", "", time()-3600, "/");
230        } else {
231            $parsed = parse_url(BASEURL);
232           
233            // now we need a dot in front of that so cookies are cleared across subdomains:
234            setcookie("hotaru_user", "", time()-3600, "/", "." . $parsed['host']);
235            setcookie("hotaru_key", "", time()-3600, "/", "." . $parsed['host']);
236        }
237       
238        session_destroy(); // sessions are used in CSRF
239       
240        $this->loggedIn = false;
241    }
242   
243   
244     /**
245     * Change username or email
246     *
247     * @param int $userid
248     * @return bool
249     */
250    public function updateAccount($h, $userid = 0)
251    {
252        // $viewee is the person whose account is being modified
253       
254        $viewee = new UserBase($h);
255       
256        // Get the details of the account to show.
257        // If no account is specified, assume it's your own.
258       
259        if (!$userid) {
260            $userid = $this->id;
261        }
262       
263        $viewee->getUserBasic($h, $userid);
264
265        $error = 0;
266       
267        // fill checks
268        $checks['userid_check'] = '';
269        $checks['username_check'] = '';
270        $checks['email_check'] = '';
271        $checks['role_check'] = '';
272        $checks['password_check_old'] = '';
273        $checks['password_check_new'] = '';
274        $checks['password_check_new2'] = '';
275       
276        // Updating account info (username and email address)
277        if ($h->cage->post->testAlnumLines('update_type') == 'update_general') {
278       
279            // check CSRF key
280            if (!$h->csrf()) {
281                $h->messages[$h->lang['error_csrf']] = 'red';
282                $error = 1;
283            }
284       
285            $username_check = $h->cage->post->testUsername('username'); // alphanumeric, dashes and underscores okay, case insensitive
286            if (!$username_check) {
287                $h->messages[$h->lang['main_user_account_update_username_error']] = 'red';
288                $error = 1;
289            } elseif($h->nameExists($username_check, '', $viewee->id) || $h->isBlocked('user', $username_check)) {
290                $h->messages[$h->lang['main_user_account_update_username_exists']] = 'red';
291                $error = 1;
292            } else {
293                //success
294                $viewee->name = $username_check;
295            }
296                               
297            $email_check = $h->cage->post->testEmail('email');
298            if (!$email_check) {
299                $h->messages[$h->lang['main_user_account_update_email_error']] = 'red';
300                $error = 1;
301            } elseif($h->emailExists($email_check, '', $viewee->id) || $h->isBlocked('email', $email_check)) {
302                $h->messages[$h->lang['main_user_account_update_email_exists']] = 'red';
303                $error = 1;
304            } else {
305                //success
306                $viewee->email = $email_check;
307            }
308           
309            $role_check = $h->cage->post->testAlnumLines('user_role'); // from Users plugin account page
310            // compare with current role and update if different
311            if (!$error && $role_check && ($role_check != $viewee->role)) {
312                $viewee->role = $role_check;
313                $new_perms = $viewee->getDefaultPermissions($h, $role_check);
314                $viewee->setAllPermissions($new_perms);
315                $viewee->updatePermissions($h);
316                if ($role_check == 'killspammed' || $role_check == 'deleted') {
317                    $h->deleteComments($viewee->id); // includes child comments from *other* users
318                    $h->deletePosts($viewee->id); // includes tags and votes for self-submitted posts
319                   
320                    $h->pluginHook('userbase_killspam', '', array('target_user' => $viewee->id));
321                   
322                    if ($role_check == 'deleted') {
323                        $h->deleteUser($viewee->id);
324                        $checks['username_check'] = 'deleted';
325                        $h->message = $h->lang["users_account_deleted"];
326                        $h->messageType = 'red';
327                        return $checks; // This will then show a red "deleted" notice
328                    }
329                }
330            }
331           
332            // If we've just edited our own account, let's refresh the cookie so it uses our latest username:
333            if ($h->currentUser->id == $h->cage->post->testInt('userid')) {
334                $h->currentUser->setCookie($h, false);           // delete the cookie
335                $h->currentUser->getUserBasic($h, $h->currentUser->id, '', true);    // re-read the database record to get updated info
336                $h->currentUser->setCookie($h, true);            // create a new, updated cookie
337            }
338        }
339       
340        if (!isset($username_check) && !isset($email_check)) {
341            $username_check = $viewee->name;
342            $email_check = $viewee->email;
343            $role_check = $viewee->role;
344            // do nothing
345        } elseif ($error == 0) {
346            $exists = $h->userExists(0, $username_check, $email_check);
347            if (($exists != 'no') && ($exists != 'error')) { // user exists
348                //success
349                $viewee->updateUserBasic($h, $userid);
350                // only update the cookie if it's your own account:
351                if ($userid == $this->id) {
352                $h->currentUser->setCookie($h, false);           // delete the cookie
353                $h->currentUser->getUserBasic($h, $h->currentUser->id, '', true);    // re-read the database record to get updated info
354                $h->currentUser->setCookie($h, true);            // create a new, updated cookie
355                }
356                $h->messages[$h->lang['main_user_account_update_success']] = 'green';
357            } else {
358                //fail
359                $h->messages[$h->lang["main_user_account_update_unexpected_error"]] = 'red';
360            }
361        } else {
362            // error must = 1 so fall through and display the form again
363        }
364       
365        //update checks
366        $this->updatePassword($h, $userid);
367        $userid_check = $viewee->id;
368        $checks['userid_check'] = $userid_check;
369        $checks['username_check'] = $username_check;
370        $checks['email_check'] = $email_check;
371        $checks['role_check'] = $role_check;
372               
373        return $checks;
374    }
375   
376   
377     /**
378     * Enable a user to change their password
379     *
380     * @return bool
381     */
382    public function updatePassword($h, $userid)
383    {
384        // we don't want to edit the password if this isn't our own account.
385        if ($userid != $this->id) { return false; }
386       
387        $error = 0;
388       
389        // Updating password
390        if ($h->cage->post->testAlnumLines('update_type') == 'update_password') {
391       
392            // check CSRF key
393            if (!$h->csrf()) {
394                $h->messages[$h->lang['error_csrf']] = 'red';
395                $error = 1;
396            }
397           
398           
399            $password_check_old = $h->cage->post->noTags('password_old');
400           
401            if ($this->loginCheck($h, $this->name, $password_check_old)) {
402                // safe, the old password matches the password for this user.
403            } else {
404                $h->messages[$h->lang['main_user_account_update_password_error_old']] = 'red';
405                $error = 1;
406            }
407       
408            $password_check_new = $h->cage->post->testPassword('password_new');   
409            if ($password_check_new) {
410                $password_check_new2 = $h->cage->post->testPassword('password_new2');   
411                if ($password_check_new2) {
412                    if ($password_check_new == $password_check_new2) {
413                        // safe, the two new password fields match
414                    } else {
415                        $h->messages[$h->lang['main_user_account_update_password_error_match']] = 'red';
416                        $error = 1;
417                    }
418                } else {
419                    $h->messages[$h->lang['main_user_account_update_password_error_new']] = 'red';
420                    $error = 1;
421                }
422            } else {
423                $h->messages[$h->lang['main_user_account_update_password_error_not_provided']] = 'red';
424                $error = 1;
425            }
426                       
427        }
428               
429        if (!isset($password_check_old) && !isset($password_check_new) && !isset($password_check_new2)) {
430            $password_check_old = "";
431            $password_check_new = "";
432            $password_check_new2 = "";
433            // do nothing
434        } elseif ($error == 0) {
435            $exists = $h->userExists(0, $this->name, $this->email);
436            if (($exists != 'no') && ($exists != 'error')) { // user exists
437                //success
438                $this->password = $this->generateHash($password_check_new);
439                $this->updateUserBasic($h, $this->id); // update the database record for this user
440                $this->setCookie($h, false);           // delete the cookie
441                $this->getUserBasic($h, $this->id, '', true);    // re-read the database record to get updated info
442                $this->setCookie($h, true);            // create a new, updated cookie
443                $h->messages[$h->lang['main_user_account_update_password_success']] = 'green';
444            } else {
445                //fail
446                $h->messages[$h->lang["main_user_account_update_unexpected_error"]] = 'red';
447            }
448        } else {
449            // error must = 1 so fall through and display the form again
450        }
451    }
452   
453   
454     /**
455     * Send a confirmation code to a user who has forgotten his/her password
456     *
457     * @param string $email - already validated above
458     */
459    public function sendPasswordConf($h, $userid, $email)
460    {
461        // generate the email confirmation code
462        $pass_conf = md5(crypt(md5($email),md5($email)));
463       
464        // store the hash in the user table
465        $sql = "UPDATE " . TABLE_USERS . " SET user_password_conf = %s WHERE user_id = %d";
466        $h->db->query($h->db->prepare($sql, $pass_conf, $userid));
467       
468        $line_break = "\r\n\r\n";
469        $next_line = "\r\n";
470
471        if ($h->isActive('signin')) {
472            $url = BASEURL . 'index.php?page=login&plugin=user_signin&userid=' . $userid . '&passconf=' . $pass_conf;
473        } else {
474            $url = BASEURL . 'admin_index.php?page=admin_login&userid=' . $userid . '&passconf=' . $pass_conf;
475        }
476       
477        // send email
478        $subject = $h->lang['main_user_email_password_conf_subject'];
479        $body = $h->lang['main_user_email_password_conf_body_hello'] . " " . $h->getUserNameFromId($userid);
480        $body .= $line_break;
481        $body .= $h->lang['main_user_email_password_conf_body_welcome'];
482        $body .= $h->lang['main_user_email_password_conf_body_click'];
483        $body .= $line_break;
484        $body .= $url;
485        $body .= $line_break;
486        $body .= $h->lang['main_user_email_password_conf_body_no_request'];
487        $body .= $line_break;
488        $body .= $h->lang['main_user_email_password_conf_body_regards'];
489        $body .= $next_line;
490        $body .= $h->lang['main_user_email_password_conf_body_sign'];
491        $to = $email;
492   
493        $h->email($to, $subject, $body);   
494       
495        return true;
496    }
497   
498   
499     /**
500     * Reset the user's password to soemthing random and email it.
501     *
502     * @param string $passconf - confirmation code clicked in email
503     */
504    public function newRandomPassword($h, $userid, $passconf)
505    {
506        $email = $h->getEmailFromId($userid);
507       
508        // check the email and confirmation code are a pair
509        $pass_conf_check = md5(crypt(md5($email),md5($email)));
510        if ($pass_conf_check != $passconf) {
511            return false;
512        }
513       
514        // update the password to something random
515        $temp_pass = random_string(10);
516        $sql = "UPDATE " . TABLE_USERS . " SET user_password = %s WHERE user_id = %d";
517        $h->db->query($h->db->prepare($sql, $this->generateHash($temp_pass), $userid));
518        $line_break = "\r\n\r\n";
519        $next_line = "\r\n";
520       
521        if ($h->isActive('signin')) {
522            $url = BASEURL . 'index.php?page=login&plugin=user_signin';
523        } else {
524            $url = BASEURL . 'admin_index.php?page=admin_login';
525        }
526       
527        // send email
528        $subject = $h->lang['main_user_email_new_password_subject'];
529        $body = $h->lang['main_user_email_password_conf_body_hello'] . " " . $h->getUserNameFromId($userid);
530        $body .= $line_break;
531        $body .= $h->lang['main_user_email_password_conf_body_requested'];
532        $body .= $line_break;
533        $body .= $temp_pass;
534        $body .= $line_break;
535        $body .= $h->lang['main_user_email_password_conf_body_remember'];
536        $body .= $line_break;
537        $body .= $h->lang['main_user_email_password_conf_body_pass_change'];
538        $body .= $line_break;
539        $body .= $url;
540        $body .= $line_break;
541        $body .= $h->lang['main_user_email_password_conf_body_regards'];
542        $body .= $next_line;
543        $body .= $h->lang['main_user_email_password_conf_body_sign'];
544        $to = $email;
545   
546        $h->email($to, $subject, $body);   
547       
548        return true;
549    }
550}
Note: See TracBrowser for help on using the repository browser.