. */ namespace SP\Services\Account; use Defuse\Crypto\Exception\CryptoException; use Exception; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; use SP\Core\Crypt\Crypt; use SP\Core\Events\Event; use SP\Core\Events\EventMessage; use SP\Core\Exceptions\SPException; use SP\Services\Crypt\UpdateMasterPassRequest; use SP\Services\Service; use SP\Services\ServiceException; use SP\Services\Task\TaskFactory; use SP\Util\Util; /** * Class AccountCryptService * * @package SP\Services\Account */ final class AccountCryptService extends Service { /** * @var AccountService */ private $accountService; /** * @var AccountHistoryService */ private $accountHistoryService; /** * @var UpdateMasterPassRequest */ private $request; /** * Actualiza las claves de todas las cuentas con la nueva clave maestra. * * @param UpdateMasterPassRequest $updateMasterPassRequest * * @throws ServiceException */ public function updateMasterPassword(UpdateMasterPassRequest $updateMasterPassRequest) { $this->request = $updateMasterPassRequest; try { $this->eventDispatcher->notifyEvent( 'update.masterPassword.accounts.start', new Event($this, EventMessage::factory() ->addDescription(__u('Update Master Password')) ) ); if ($this->request->useTask()) { $task = $this->request->getTask(); TaskFactory::update($task, TaskFactory::createMessage($task->getTaskId(), __u('Update Master Password')) ); } $eventMessage = $this->processAccounts( $this->accountService->getAccountsPassData(), function (AccountPasswordRequest $request) { $this->accountService->updatePasswordMasterPass($request); } ); $this->eventDispatcher->notifyEvent( 'update.masterPassword.accounts.end', new Event($this, $eventMessage) ); } catch (Exception $e) { $this->eventDispatcher->notifyEvent('exception', new Event($e)); throw new ServiceException( __u('Error while updating the accounts\' passwords'), ServiceException::ERROR, null, $e->getCode(), $e); } } /** * @param array $accounts * @param callable $passUpdater * * @return EventMessage */ private function processAccounts(array $accounts, callable $passUpdater) { set_time_limit(0); $accountsOk = []; $errorCount = 0; $counter = 0; $startTime = time(); $numAccounts = count($accounts); $eventMessage = EventMessage::factory(); if ($numAccounts === 0) { $eventMessage->addDescription(__u('There are no accounts for processing')); $eventMessage->addDetail(__u('Accounts updated'), __u('N/A')); $eventMessage->addDetail(__u('Errors'), 0); return $eventMessage; } $configData = $this->config->getConfigData(); $currentMasterPassHash = $this->request->getCurrentHash(); if ($this->request->useTask()) { $task = $this->request->getTask(); } foreach ($accounts as $account) { // No realizar cambios si está en modo demo if ($configData->isDemoEnabled()) { $accountsOk[] = $account->id; continue; } if ($counter % 100 === 0) { $eta = Util::getETA($startTime, $counter, $numAccounts); if (isset($task)) { $taskMessage = TaskFactory::createMessage( $task->getTaskId(), __('Update Master Password') )->setMessage(sprintf(__('Accounts updated: %d / %d'), $counter, $numAccounts)) ->setProgress(round(($counter * 100) / $numAccounts, 2)) ->setTime(sprintf('ETA: %ds (%.2f/s)', $eta[0], $eta[1])); TaskFactory::update($task, $taskMessage); logger($taskMessage->composeText()); } else { logger( sprintf(__('Updated accounts: %d / %d - %d%% - ETA: %ds (%.2f/s)'), $counter, $numAccounts, round(($counter * 100) / $numAccounts, 2), $eta[0], $eta[1]) ); } } if (isset($account->mPassHash) && $account->mPassHash !== $currentMasterPassHash) { $eventMessage->addDescription(__u('The record\'s master password does not match')); $eventMessage->addDetail($account->name, $account->id); continue; } $request = new AccountPasswordRequest(); $request->id = $account->id; try { $passData = $this->accountService->getPasswordEncrypted( Crypt::decrypt($account->pass, $account->key, $this->request->getCurrentMasterPass()), $this->request->getNewMasterPass() ); $request->key = $passData['key']; $request->pass = $passData['pass']; // Call the specific updater $passUpdater($request); $accountsOk[] = $account->id; $counter++; } catch (SPException | CryptoException $e) { $errorCount++; $eventMessage->addDescription(__u('Error while updating the account\'s password')); $eventMessage->addDetail($account->name, $account->id); } } $eventMessage->addDetail(__u('Accounts updated'), implode(',', $accountsOk)); $eventMessage->addDetail(__u('Errors'), $errorCount); return $eventMessage; } /** * Actualiza las claves de todas las cuentas con la nueva clave maestra. * * @param UpdateMasterPassRequest $updateMasterPassRequest * * @throws ServiceException */ public function updateHistoryMasterPassword(UpdateMasterPassRequest $updateMasterPassRequest) { $this->request = $updateMasterPassRequest; try { $this->eventDispatcher->notifyEvent( 'update.masterPassword.accountsHistory.start', new Event($this, EventMessage::factory() ->addDescription(__u('Update Master Password (H)')) ) ); if ($this->request->useTask()) { $task = $this->request->getTask(); TaskFactory::update($task, TaskFactory::createMessage($task->getTaskId(), __u('Update Master Password (H)')) ); } $eventMessage = $this->processAccounts( $this->accountHistoryService->getAccountsPassData(), function (AccountPasswordRequest $request) { $request->hash = $this->request->getHash(); $this->accountHistoryService->updatePasswordMasterPass($request); }); $this->eventDispatcher->notifyEvent( 'update.masterPassword.accountsHistory.end', new Event($this, $eventMessage) ); } catch (Exception $e) { $this->eventDispatcher->notifyEvent('exception', new Event($e)); throw new ServiceException( __u('Error while updating the accounts\' passwords in history'), SPException::ERROR, null, $e->getCode(), $e); } } /** * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ protected function initialize() { $this->accountService = $this->dic->get(AccountService::class); $this->accountHistoryService = $this->dic->get(AccountHistoryService::class); } }