gluon-web-remote

Web remote Administration for big size of gluon routers
git clone git://archive.git.mtrnord.blog/MTRNord/gluon-web-remote.git
Log | Files | Refs | README

PasswordResetModel.php (12947B)


      1 <?php
      2 
      3 /**
      4  * Class PasswordResetModel
      5  *
      6  * Handles all the stuff that is related to the password-reset process
      7  */
      8 class PasswordResetModel
      9 {
     10 	/**
     11 	 * Perform the necessary actions to send a password reset mail
     12 	 *
     13 	 * @param $user_name_or_email string Username or user's email
     14 	 *
     15 	 * @return bool success status
     16 	 */
     17 	public static function requestPasswordReset($user_name_or_email)
     18 	{
     19 		if (empty($user_name_or_email)) {
     20 			Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_EMAIL_FIELD_EMPTY'));
     21 			return false;
     22 		}
     23 
     24 		// check if that username exists
     25 		$result = UserModel::getUserDataByUserNameOrEmail($user_name_or_email);
     26 		if (!$result) {
     27 			Session::add('feedback_negative', Text::get('FEEDBACK_USER_DOES_NOT_EXIST'));
     28 			return false;
     29 		}
     30 
     31 		// generate integer-timestamp (to see when exactly the user (or an attacker) requested the password reset mail)
     32 		// generate random hash for email password reset verification (40 char string)
     33 		$temporary_timestamp = time();
     34 		$user_password_reset_hash = sha1(uniqid(mt_rand(), true));
     35 
     36 		// set token (= a random hash string and a timestamp) into database ...
     37 		$token_set = self::setPasswordResetDatabaseToken($result->user_name, $user_password_reset_hash, $temporary_timestamp);
     38 		if (!$token_set) {
     39 			return false;
     40 		}
     41 
     42 		// ... and send a mail to the user, containing a link with username and token hash string
     43 		$mail_sent = self::sendPasswordResetMail($result->user_name, $user_password_reset_hash, $result->user_email);
     44 		if ($mail_sent) {
     45 			return true;
     46 		}
     47 
     48 		// default return
     49 		return false;
     50 	}
     51 
     52 	/**
     53 	 * Set password reset token in database (for DEFAULT user accounts)
     54 	 *
     55 	 * @param string $user_name username
     56 	 * @param string $user_password_reset_hash password reset hash
     57 	 * @param int $temporary_timestamp timestamp
     58 	 *
     59 	 * @return bool success status
     60 	 */
     61 	public static function setPasswordResetDatabaseToken($user_name, $user_password_reset_hash, $temporary_timestamp)
     62 	{
     63 		$database = DatabaseFactory::getFactory()->getConnection();
     64 
     65 		$sql = "UPDATE users
     66                 SET user_password_reset_hash = :user_password_reset_hash, user_password_reset_timestamp = :user_password_reset_timestamp
     67                 WHERE user_name = :user_name AND user_provider_type = :provider_type LIMIT 1";
     68 		$query = $database->prepare($sql);
     69 		$query->execute(array(
     70 			':user_password_reset_hash' => $user_password_reset_hash, ':user_name' => $user_name,
     71 			':user_password_reset_timestamp' => $temporary_timestamp, ':provider_type' => 'DEFAULT'
     72 		));
     73 
     74 		// check if exactly one row was successfully changed
     75 		if ($query->rowCount() == 1) {
     76 			return true;
     77 		}
     78 
     79 		// fallback
     80 		Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_RESET_TOKEN_FAIL'));
     81 		return false;
     82 	}
     83 
     84 	/**
     85 	 * Send the password reset mail
     86 	 *
     87 	 * @param string $user_name username
     88 	 * @param string $user_password_reset_hash password reset hash
     89 	 * @param string $user_email user email
     90 	 *
     91 	 * @return bool success status
     92 	 */
     93 	public static function sendPasswordResetMail($user_name, $user_password_reset_hash, $user_email)
     94 	{
     95 		// create email body
     96 		$body = Config::get('EMAIL_PASSWORD_RESET_CONTENT') . ' ' . Config::get('URL') .
     97 		        Config::get('EMAIL_PASSWORD_RESET_URL') . '/' . urlencode($user_name) . '/' . urlencode($user_password_reset_hash);
     98 
     99 		// create instance of Mail class, try sending and check
    100 		$mail = new Mail;
    101 		$mail_sent = $mail->sendMail($user_email, Config::get('EMAIL_PASSWORD_RESET_FROM_EMAIL'), 
    102             Config::get('EMAIL_PASSWORD_RESET_FROM_NAME'), Config::get('EMAIL_PASSWORD_RESET_SUBJECT'), $body
    103 		);
    104 
    105 		if ($mail_sent) {
    106 			Session::add('feedback_positive', Text::get('FEEDBACK_PASSWORD_RESET_MAIL_SENDING_SUCCESSFUL'));
    107 			return true;
    108 		}
    109 
    110 		Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_RESET_MAIL_SENDING_ERROR') . $mail->getError() );
    111 		return false;
    112 	}
    113 
    114 	/**
    115 	 * Verifies the password reset request via the verification hash token (that's only valid for one hour)
    116 	 * @param string $user_name Username
    117 	 * @param string $verification_code Hash token
    118 	 * @return bool Success status
    119 	 */
    120 	public static function verifyPasswordReset($user_name, $verification_code)
    121 	{
    122 		$database = DatabaseFactory::getFactory()->getConnection();
    123 
    124 		// check if user-provided username + verification code combination exists
    125 		$sql = "SELECT user_id, user_password_reset_timestamp
    126                   FROM users
    127                  WHERE user_name = :user_name
    128                        AND user_password_reset_hash = :user_password_reset_hash
    129                        AND user_provider_type = :user_provider_type
    130                  LIMIT 1";
    131 		$query = $database->prepare($sql);
    132 		$query->execute(array(
    133 			':user_password_reset_hash' => $verification_code, ':user_name' => $user_name,
    134 			':user_provider_type' => 'DEFAULT'
    135 		));
    136 
    137 		// if this user with exactly this verification hash code does NOT exist
    138 		if ($query->rowCount() != 1) {
    139 			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_RESET_COMBINATION_DOES_NOT_EXIST'));
    140 			return false;
    141 		}
    142 
    143 		// get result row (as an object)
    144 		$result_user_row = $query->fetch();
    145 
    146 		// 3600 seconds are 1 hour
    147 		$timestamp_one_hour_ago = time() - 3600;
    148 
    149 		// if password reset request was sent within the last hour (this timeout is for security reasons)
    150 		if ($result_user_row->user_password_reset_timestamp > $timestamp_one_hour_ago) {
    151 			// verification was successful
    152 			Session::add('feedback_positive', Text::get('FEEDBACK_PASSWORD_RESET_LINK_VALID'));
    153 			return true;
    154 		} else {
    155 			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_RESET_LINK_EXPIRED'));
    156 			return false;
    157 		}
    158 	}
    159 
    160 	/**
    161 	 * Writes the new password to the database
    162 	 *
    163 	 * @param string $user_name username
    164 	 * @param string $user_password_hash
    165 	 * @param string $user_password_reset_hash
    166 	 *
    167 	 * @return bool
    168 	 */
    169 	public static function saveNewUserPassword($user_name, $user_password_hash, $user_password_reset_hash)
    170 	{
    171 		$database = DatabaseFactory::getFactory()->getConnection();
    172 
    173 		$sql = "UPDATE users SET user_password_hash = :user_password_hash, user_password_reset_hash = NULL,
    174                        user_password_reset_timestamp = NULL
    175                  WHERE user_name = :user_name AND user_password_reset_hash = :user_password_reset_hash
    176                        AND user_provider_type = :user_provider_type LIMIT 1";
    177 		$query = $database->prepare($sql);
    178 		$query->execute(array(
    179 			':user_password_hash' => $user_password_hash, ':user_name' => $user_name,
    180 			':user_password_reset_hash' => $user_password_reset_hash, ':user_provider_type' => 'DEFAULT'
    181 		));
    182 
    183 		// if one result exists, return true, else false. Could be written even shorter btw.
    184 		return ($query->rowCount() == 1 ? true : false);
    185 	}
    186 
    187 	/**
    188 	 * Set the new password (for DEFAULT user, FACEBOOK-users don't have a password)
    189 	 * Please note: At this point the user has already pre-verified via verifyPasswordReset() (within one hour),
    190 	 * so we don't need to check again for the 60min-limit here. In this method we authenticate
    191 	 * via username & password-reset-hash from (hidden) form fields.
    192 	 *
    193 	 * @param string $user_name
    194 	 * @param string $user_password_reset_hash
    195 	 * @param string $user_password_new
    196 	 * @param string $user_password_repeat
    197 	 *
    198 	 * @return bool success state of the password reset
    199 	 */
    200 	public static function setNewPassword($user_name, $user_password_reset_hash, $user_password_new, $user_password_repeat)
    201 	{
    202 		// validate the password
    203 		if (!self::validateResetPassword($user_name, $user_password_reset_hash, $user_password_new, $user_password_repeat)) {
    204 			return false;
    205 		}
    206 
    207 		// crypt the password (with the PHP 5.5+'s password_hash() function, result is a 60 character hash string)
    208 		$user_password_hash = password_hash($user_password_new, PASSWORD_DEFAULT);
    209 
    210 		// write the password to database (as hashed and salted string), reset user_password_reset_hash
    211 		if (self::saveNewUserPassword($user_name, $user_password_hash, $user_password_reset_hash)) {
    212 			Session::add('feedback_positive', Text::get('FEEDBACK_PASSWORD_CHANGE_SUCCESSFUL'));
    213 			return true;
    214 		} else {
    215 			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_CHANGE_FAILED'));
    216 			return false;
    217 		}
    218 	}
    219 
    220 	/**
    221 	 * Validate the password submission
    222 	 *
    223 	 * @param $user_name
    224 	 * @param $user_password_reset_hash
    225 	 * @param $user_password_new
    226 	 * @param $user_password_repeat
    227 	 *
    228 	 * @return bool
    229 	 */
    230 	public static function validateResetPassword($user_name, $user_password_reset_hash, $user_password_new, $user_password_repeat)
    231 	{
    232 		if (empty($user_name)) {
    233 			Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_FIELD_EMPTY'));
    234 			return false;
    235 		} else if (empty($user_password_reset_hash)) {
    236 			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_RESET_TOKEN_MISSING'));
    237 			return false;
    238 		} else if (empty($user_password_new) || empty($user_password_repeat)) {
    239 			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_FIELD_EMPTY'));
    240 			return false;
    241 		} else if ($user_password_new !== $user_password_repeat) {
    242 			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_REPEAT_WRONG'));
    243 			return false;
    244 		} else if (strlen($user_password_new) < 6) {
    245 			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_TOO_SHORT'));
    246 			return false;
    247 		}
    248 
    249 		return true;
    250 	}
    251 
    252 
    253 	/**
    254 	 * Writes the new password to the database
    255 	 *
    256 	 * @param string $user_name
    257 	 * @param string $user_password_hash
    258 	 *
    259 	 * @return bool
    260 	 */
    261 	public static function saveChangedPassword($user_name, $user_password_hash)
    262 	{
    263 		$database = DatabaseFactory::getFactory()->getConnection();
    264 
    265 		$sql = "UPDATE users SET user_password_hash = :user_password_hash
    266                  WHERE user_name = :user_name
    267                  AND user_provider_type = :user_provider_type LIMIT 1";
    268 		$query = $database->prepare($sql);
    269 		$query->execute(array(
    270 			':user_password_hash' => $user_password_hash, ':user_name' => $user_name,
    271 			':user_provider_type' => 'DEFAULT'
    272 		));
    273 
    274 		// if one result exists, return true, else false. Could be written even shorter btw.
    275 		return ($query->rowCount() == 1 ? true : false);
    276 	}
    277 
    278 
    279 	/**
    280 	 * Validates fields, hashes new password, saves new password
    281 	 *
    282 	 * @param string $user_name
    283 	 * @param string $user_password_current
    284 	 * @param string $user_password_new
    285 	 * @param string $user_password_repeat
    286 	 *
    287 	 * @return bool
    288 	 */
    289 	public static function changePassword($user_name, $user_password_current, $user_password_new, $user_password_repeat)
    290 	{
    291 		// validate the passwords
    292 		if (!self::validatePasswordChange($user_name, $user_password_current, $user_password_new, $user_password_repeat)) {
    293 			return false;
    294 		}
    295 
    296 		// crypt the password (with the PHP 5.5+'s password_hash() function, result is a 60 character hash string)
    297 		$user_password_hash = password_hash($user_password_new, PASSWORD_DEFAULT);
    298 
    299 		// write the password to database (as hashed and salted string)
    300 		if (self::saveChangedPassword($user_name, $user_password_hash)) {
    301 			Session::add('feedback_positive', Text::get('FEEDBACK_PASSWORD_CHANGE_SUCCESSFUL'));
    302 			return true;
    303 		} else {
    304 			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_CHANGE_FAILED'));
    305 			return false;
    306 		}
    307 	}
    308 
    309 
    310 	/**
    311 	 * Validates current and new passwords
    312 	 *
    313 	 * @param string $user_name
    314 	 * @param string $user_password_current
    315 	 * @param string $user_password_new
    316 	 * @param string $user_password_repeat
    317 	 *
    318 	 * @return bool
    319 	 */
    320 	public static function validatePasswordChange($user_name, $user_password_current, $user_password_new, $user_password_repeat)
    321 	{
    322 		$database = DatabaseFactory::getFactory()->getConnection();
    323 
    324 		$sql = "SELECT user_password_hash, user_failed_logins FROM users WHERE user_name = :user_name LIMIT 1;";
    325 		$query = $database->prepare($sql);
    326 		$query->execute(array(
    327 			':user_name' => $user_name
    328 		));
    329 
    330 		$user = $query->fetch();
    331 
    332         if ($query->rowCount() == 1) {
    333             $user_password_hash = $user->user_password_hash;
    334         } else {
    335             Session::add('feedback_negative', Text::get('FEEDBACK_USER_DOES_NOT_EXIST'));
    336             return false;
    337         }
    338 
    339 		if (!password_verify($user_password_current, $user_password_hash)) {
    340 			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_CURRENT_INCORRECT'));
    341 			return false;
    342 		} else if (empty($user_password_new) || empty($user_password_repeat)) {
    343 			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_FIELD_EMPTY'));
    344 			return false;
    345 		} else if ($user_password_new !== $user_password_repeat) {
    346 			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_REPEAT_WRONG'));
    347 			return false;
    348 		} else if (strlen($user_password_new) < 6) {
    349 			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_TOO_SHORT'));
    350 			return false;
    351 		} else if ($user_password_current == $user_password_new){
    352 			Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_NEW_SAME_AS_CURRENT'));
    353 			return false;
    354 		}
    355 
    356 		return true;
    357 	}
    358 }