1<?php 2 3/** 4 * OrangeHRM is a comprehensive Human Resource Management (HRM) System that captures 5 * all the essential functionalities required for any enterprise. 6 * Copyright (C) 2006 OrangeHRM Inc., http://www.orangehrm.com 7 * 8 * OrangeHRM is free software; you can redistribute it and/or modify it under the terms of 9 * the GNU General Public License as published by the Free Software Foundation; either 10 * version 2 of the License, or (at your option) any later version. 11 * 12 * OrangeHRM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 13 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 * See the GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License along with this program; 17 * if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 * Boston, MA 02110-1301, USA 19 */ 20class PasswordResetService extends BaseService { 21 22 const RESET_PASSWORD_TOKEN_RANDOM_BYTES_LENGTH = 16; 23 24 private $passwordResetDao = null; 25 private $securityAuthenticationConfigService = null; 26 private $authenticationService = null; 27 private $emailService = null; 28 private $systemUserService = null; 29 private $employeeService = null; 30 31 /** 32 * 33 * @return SecurityAuthenticationConfigService 34 */ 35 public function getSecurityAuthenticationConfigService() { 36 if (empty($this->securityAuthenticationConfigService)) { 37 $this->securityAuthenticationConfigService = new SecurityAuthenticationConfigService(); 38 } 39 return $this->securityAuthenticationConfigService; 40 } 41 42 /** 43 * 44 * @param SecurityAuthenticationConfigService $securityAuthConfigService 45 */ 46 public function setSecurityAuthenticationConfigService($securityAuthConfigService) { 47 $this->securityAuthenticationConfigService = $securityAuthConfigService; 48 } 49 50 /** 51 * 52 * @return AuthenticationService 53 */ 54 public function getAuthenticationService() { 55 if (empty($this->authenticationService)) { 56 $this->authenticationService = new AuthenticationService(); 57 } 58 return $this->authenticationService; 59 } 60 61 /** 62 * 63 * @param AuthenticationService $authService 64 */ 65 public function setAuthenticationService($authService) { 66 $this->authenticationService = $authService; 67 } 68 69 /** 70 * @param $username 71 * @return array 72 * @throws ServiceException 73 */ 74 public function searchForUserRecord($username) { 75 if (empty($username)){ 76 throw new ServiceException(__('Could not find a user with given details')); 77 } 78 $userService = $this->getSystemUserService(); 79 $users = $userService->searchSystemUsers(array('userName' => $username)); 80 if ($users->count() > 0) { 81 $user = $users->get(0); 82 $associatedEmployee = $user->getEmployee(); 83 if (!($associatedEmployee instanceof Employee)) { 84 throw new ServiceException(__('User account is not associated with an employee')); 85 } else { 86 $emailConfiguration=new EmailConfigurationService(); 87 $companyEmail=$emailConfiguration->getEmailConfiguration(); 88 if(!empty($companyEmail['sentAs'])) { 89 if (empty($associatedEmployee['termination_id'])) { 90 $workEmail = trim($associatedEmployee->getEmpWorkEmail()); 91 if (!empty($workEmail)) { 92 $passResetCode=$this->hasPasswordResetRequest($workEmail); 93 if (!$passResetCode) { 94 return $user; 95 } else { 96 throw new ServiceException(__('There is a password reset request already in the system.')); 97 } 98 99 } else { 100 throw new ServiceException(__('Work email is not set. Please contact HR admin in order to reset the password')); 101 } 102 } else { 103 throw new ServiceException(__('Please contact HR admin in order to reset the password')); 104 } 105 } else { 106 throw new ServiceException(__('Password reset email could not be sent')); 107 } 108 } 109 110 } else { 111 throw new ServiceException(__('Please contact HR admin in order to reset the password')); 112 } 113 return null; 114 } 115 116 public function getEmployeeService() { 117 if (is_null($this->employeeService)) { 118 $this->employeeService = new EmployeeService(); 119 } 120 return $this->employeeService; 121 } 122 123 /** 124 * @param $employeeService 125 */ 126 public function setEmployeeService($employeeService) { 127 $this->employeeService = $employeeService; 128 } 129 130 /** 131 * @param $email 132 * @return bool 133 */ 134 public function hasPasswordResetRequest($email) { 135 136 $resetPassword = $this->getResetPasswordLogByEmail($email); 137 if ($resetPassword instanceof ResetPassword) { 138 return $this->hasPasswordResetRequestNotExpired($resetPassword); 139 } 140 141 return false; 142 } 143 144 /** 145 * @param $email 146 * @return mixed 147 * @throws ServiceException 148 */ 149 public function getResetPasswordLogByEmail($email) { 150 151 try { 152 return $this->getPasswordResetDao()->getResetPasswordLogByEmail($email); 153 } catch (Exception $e) { 154 throw new ServiceException($e->getMessage()); 155 } 156 } 157 158 /** 159 * @return PasswordResetDao 160 */ 161 public function getPasswordResetDao() { 162 return $this->passwordResetDao = new PasswordResetDao(); 163 } 164 165 /** 166 * @param $passwordResetDao 167 */ 168 public function setPasswordResetDao($passwordResetDao) { 169 $this->passwordResetDao = $passwordResetDao; 170 } 171 172 /** 173 * @param ResetPassword $resetPassword 174 * @return bool 175 */ 176 public function hasPasswordResetRequestNotExpired(ResetPassword $resetPassword) { 177 $strExpireTime = strtotime("+1 days " . $resetPassword->getResetRequestDate()); 178 $strCurrentTime = strtotime(date("Y-m-d H:i:s")); 179 return ($strExpireTime > $strCurrentTime); 180 } 181 182 /** 183 * @param $user 184 * @return bool 185 * @throws ServiceException 186 */ 187 public function logPasswordResetRequest($user) { 188 $identifier = $user->getUserName(); 189 $resetCode = $this->generatePasswordResetCode($identifier); 190 191 $resetPassword = new ResetPassword(); 192 $resetPassword->setResetEmail($user->getEmployee()->getEmpWorkEmail()); 193 $resetPassword->setResetRequestDate(date('Y-m-d H:i:s')); 194 $resetPassword->setResetCode($resetCode); 195 196 $emailSent = $this->sendPasswordResetCodeEmail($user->getEmployee(), $resetCode); 197 if (!$emailSent) { 198 throw new ServiceException(__('Password reset email could not be sent.')); 199 } 200 $this->saveResetPasswordLog($resetPassword); 201 202 return true; 203 } 204 205 /** 206 * 207 * @return string 208 */ 209 public function generatePasswordResetCode($identfier) { 210 return Base64Url::encode("{$identfier}#SEPARATOR#" . 211 random_bytes(static::RESET_PASSWORD_TOKEN_RANDOM_BYTES_LENGTH)); 212 } 213 214 /** 215 * @param Employee $receiver 216 * @param $resetCode 217 * @return bool 218 */ 219 public function sendPasswordResetCodeEmail(Employee $receiver, $resetCode) { 220 $this->getEmailService()->setMessageTo(array($receiver->getEmpWorkEmail())); 221 $this->getEmailService()->setMessageFrom( 222 array($this->getEmailService()->getEmailConfig()->getSentAs() => 'OrangeHRM System')); 223 $this->getEmailService()->setMessageSubject('OrangeHRM Password Reset'); 224 $this->getEmailService()->setMessageBody($this->generatePasswordResetEmailBody($receiver, $resetCode)); 225 return $this->getEmailService()->sendEmail(); 226 } 227 228 /** 229 * 230 * @return EmailService 231 */ 232 public function getEmailService() { 233 if (!($this->emailService instanceof EmailService)) { 234 $this->emailService = new EmailService(); 235 } 236 return $this->emailService; 237 } 238 239 /** 240 * 241 * @param EmailService $emailService 242 */ 243 public function setEmailService(EmailService $emailService) { 244 $this->emailService = $emailService; 245 } 246 247 /** 248 * @param Employee $receiver 249 * @param $resetCode 250 * @return string 251 */ 252 protected function generatePasswordResetEmailBody(Employee $receiver, $resetCode) { 253 $resetLink = public_path('index.php/auth/resetPassword', true); 254 255 $placeholders = array('firstName', 'lastName', 'passwordResetLink', 'code', 'passwordResetCodeLink'); 256 $replacements = array( 257 $receiver->getFirstName(), 258 $receiver->getLastName(), 259 $resetLink, 260 $resetCode, 261 ); 262 263 return $this->generateEmailBody('password-reset-request.txt', $placeholders, $replacements); 264 } 265 266 /** 267 * 268 * @param string $templateFile 269 * @param array $placeholders 270 * @param array $replacements 271 * @return string 272 */ 273 protected function generateEmailBody($templateFile, array $placeholders, array $replacements) { 274 $body = file_get_contents(sfConfig::get('sf_plugins_dir') . 275 DIRECTORY_SEPARATOR.'orangehrmSecurityAuthenticationPlugin'.DIRECTORY_SEPARATOR. 276 'config'.DIRECTORY_SEPARATOR.'data'.DIRECTORY_SEPARATOR . $templateFile); 277 278 foreach ($placeholders as $key => $value) { 279 $placeholders[$key] = "/\{{$value}\}/"; 280 } 281 282 $body = preg_replace($placeholders, $replacements, $body); 283 return $body; 284 } 285 286 /** 287 * @param ResetPassword $resetPassword 288 * @return mixed 289 * @throws ServiceException 290 */ 291 public function saveResetPasswordLog(ResetPassword $resetPassword) { 292 try { 293 return $this->getPasswordResetDao()->saveResetPasswordLog($resetPassword); 294 } catch (Exception $e) { 295 throw new ServiceException($e->getMessage()); 296 } 297 } 298 299 /** 300 * @param $passwordResetData 301 * @param $resetCode 302 * @return bool 303 * @throws ServiceException 304 */ 305 public function saveNewPassword($passwordResetData, $resetCode) { 306 307 $userNameMetaData= $this->extractPasswordResetMetaData($resetCode); 308 $username=$userNameMetaData[0]; 309 $newPrimaryPassword = $passwordResetData['newPrimaryPassword']; 310 $primaryPasswordConfirmation = $passwordResetData['primaryPasswordConfirmation']; 311 312 $user = $this->getSystemUserService()->searchSystemUsers(array('userName' => $username, 'limit' => 1))->get(0); 313 $email = $user->getEmployee()->getEmpWorkEmail(); 314 $resetPasswordLogByEmail = $this->getPasswordResetDao()->getResetPasswordLogByEmail($email); 315 316 $expireDate = $this->hasPasswordResetRequest($email); 317 318 if ($newPrimaryPassword !== $primaryPasswordConfirmation) { 319 throw new ServiceException(__('New primary password and the confirmation does not match')); 320 } else if(!$expireDate) { 321 $this->getPasswordResetDao()->deletePasswordResetRequestsByEmail($email); 322 throw new ServiceException(__('This link is expired, Please request again')); 323 }elseif($resetPasswordLogByEmail['reset_code']!==$resetCode) { 324 throw new ServiceException(__('Key does not match')); 325 }else { 326 try { 327 $primaryHash = $this->getSystemUserService()->hashPassword($newPrimaryPassword); 328 $success = (bool)$this->getPasswordResetDao()->saveNewPrimaryPassword($username, $primaryHash); 329 $this->getPasswordResetDao()->deletePasswordResetRequestsByEmail($email); 330 return $success; 331 } catch (Exception $e) { 332 throw new ServiceException($e->getMessage()); 333 } 334 } 335 } 336 337 /** 338 * 339 * @param string $resetCode 340 * @return array 341 */ 342 public function extractPasswordResetMetaData($resetCode) { 343 $code = Base64Url::decode($resetCode); 344 345 $metaData = explode('#SEPARATOR#', $code); 346 347 array_pop($metaData); 348 349 return $metaData; 350 } 351 352 /** 353 * 354 * @return SystemUserService 355 */ 356 public function getSystemUserService() { 357 if (!($this->systemUserService instanceof SystemUserService)) { 358 $this->systemUserService = new SystemUserService(); 359 } 360 return $this->systemUserService; 361 } 362 363 /** 364 * 365 * @param SystemUserService $systemUserService 366 */ 367 public function setSystemUserService($systemUserService) { 368 $this->systemUserService = $systemUserService; 369 } 370 371} 372