user->isAdmin()) { throw new Forbidden(); } /** @var ?User $user */ $user = $this->entityManager->getEntityById(User::ENTITY_TYPE, $id); if (!$user) { throw new NotFound(); } if (!$user->isActive()) { throw new Forbidden("User is not active."); } if ( !$user->isRegular() && !$user->isAdmin() && !$user->isPortal() ) { throw new Forbidden(); } $this->recovery->createAndSendRequestForExistingUser($user); } /** * Change a password by a recovery request. * * @param string $requestId A request ID. * @param string $password A new password. * * @return ?string A URL to suggest to a user. * @throws Error * @throws Forbidden * @throws NotFound */ public function changePasswordByRecovery(string $requestId, string $password): ?string { $request = $this->recovery->getRequest($requestId); $this->changePassword($request->getUserId(), $password); $this->recovery->removeRequest($requestId); return $request->getUrl(); } /** * Change a password with a current password check. * * @throws Forbidden * @throws NotFound * @throws Error */ public function changePasswordWithCheck(string $userId, string $password, string $currentPassword): void { $this->changePasswordInternal($userId, $password, true, $currentPassword); } /** * Change a password. * * @throws Forbidden * @throws NotFound * @throws Error */ private function changePassword(string $userId, string $password): void { $this->changePasswordInternal($userId, $password); } /** * @throws Forbidden * @throws Error * @throws NotFound */ private function changePasswordInternal( string $userId, string $password, bool $checkCurrentPassword = false, ?string $currentPassword = null ): void { /** @var ?User $user */ $user = $this->entityManager->getEntityById(User::ENTITY_TYPE, $userId); if (!$user) { throw new NotFound(); } if ( $user->isSuperAdmin() && !$this->user->isSuperAdmin() ) { throw new Forbidden(); } $authenticationMethod = $this->authenticationMethodProvider->get(); if (!$user->isAdmin() && $authenticationMethod !== Espo::NAME) { throw new Forbidden("Authentication method is not `Espo`."); } if (empty($password)) { throw new Error("Password can't be empty."); } if ($checkCurrentPassword) { $u = $this->entityManager ->getRDBRepository(User::ENTITY_TYPE) ->where([ 'id' => $user->getId(), 'password' => $this->passwordHash->hash($currentPassword ?? ''), ]) ->findOne(); if (!$u) { throw new Forbidden("Wrong password."); } } if (!$this->checker->checkStrength($password)) { throw new Forbidden("Password is weak."); } $validLength = $this->fieldValidationManager->check( $user, 'password', 'maxLength', (object) ['password' => $password] ); if (!$validLength) { throw new Forbidden("Password exceeds max length."); } $user->set('password', $this->passwordHash->hash($password)); $this->entityManager->saveEntity($user); } /** * Send access info for a new user. * * @throws Error * @throws SendingError */ public function sendAccessInfoForNewUser(User $user): void { $emailAddress = $user->getEmailAddress(); if ($emailAddress === null) { throw new Error("Can't send access info for user '{$user->getId()}' w/o email address."); } if (!$this->isSmtpConfigured()) { throw new Error("Can't send access info. SMTP is not configured."); } $stubPassword = $this->generator->generate(); $this->savePasswordSilent($user, $stubPassword); $request = $this->recovery->createRequestForNewUser($user); $this->sender->sendAccessInfo($user, $request); } /** * Generate a new password and send it in an email. Only for admin. * * @throws Forbidden * @throws NotFound * @throws Error */ public function generateAndSendNewPasswordForUser(string $id): void { if (!$this->user->isAdmin()) { throw new Forbidden(); } /** @var ?User $user */ $user = $this->serviceContainer ->get(User::ENTITY_TYPE) ->getEntity($id); if (!$user) { throw new NotFound(); } if ($user->isApi()) { throw new Forbidden(); } if ($user->isSuperAdmin()) { throw new Forbidden(); } if ($user->isSystem()) { throw new Forbidden(); } if (!$user->getEmailAddress()) { throw new Forbidden("Generate new password: Can't process because user doesn't have email address."); } if (!$this->isSmtpConfigured()) { throw new Forbidden("Generate new password: Can't process because SMTP is not configured."); } $password = $this->generator->generate(); try { $this->sender->sendPassword($user, $password); } catch (SendingError) { throw new Error("Email sending error."); } $this->savePassword($user, $password); } private function savePassword(User $user, string $password): void { $user->set('password', $this->passwordHash->hash($password)); $this->entityManager->saveEntity($user); } private function savePasswordSilent(User $user, string $password): void { $user->set('password', $this->passwordHash->hash($password)); $this->entityManager->saveEntity($user, [SaveOption::SILENT => true]); } private function isSmtpConfigured(): bool { return $this->emailSender->hasSystemSmtp() || $this->config->get('internalSmtpServer'); } }