mirror of
https://github.com/nuxsmin/sysPass.git
synced 2026-03-07 00:46:59 +01:00
@@ -59,10 +59,10 @@ final class SaveController extends SimpleControllerBase
|
||||
private ConfigService $configService;
|
||||
|
||||
public function __construct(
|
||||
Application $application,
|
||||
Application $application,
|
||||
SimpleControllerHelper $simpleControllerHelper,
|
||||
MasterPassService $masterPassService,
|
||||
ConfigService $configService
|
||||
ConfigService $configService
|
||||
) {
|
||||
parent::__construct($application, $simpleControllerHelper);
|
||||
|
||||
@@ -145,13 +145,10 @@ final class SaveController extends SimpleControllerBase
|
||||
|
||||
if (!$noAccountPassChange) {
|
||||
try {
|
||||
$task = $this->getTask();
|
||||
|
||||
$request = new UpdateMasterPassRequest(
|
||||
$currentMasterPass,
|
||||
$newMasterPass,
|
||||
$this->configService->getByParam(MasterPass::PARAM_MASTER_PASS_HASH),
|
||||
$task
|
||||
);
|
||||
|
||||
$this->eventDispatcher->notify('update.masterPassword.start', new Event($this));
|
||||
@@ -165,10 +162,6 @@ final class SaveController extends SimpleControllerBase
|
||||
$this->eventDispatcher->notify('exception', new Event($e));
|
||||
|
||||
return $this->returnJsonResponseException($e);
|
||||
} finally {
|
||||
if (isset($task)) {
|
||||
TaskFactory::end($task);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
@@ -194,18 +187,6 @@ final class SaveController extends SimpleControllerBase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FileException
|
||||
*/
|
||||
private function getTask(): ?TaskInterface
|
||||
{
|
||||
$taskId = $this->request->analyzeString('taskId');
|
||||
|
||||
return $taskId !== null
|
||||
? TaskFactory::register(new Task(__FUNCTION__, $taskId))
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws JsonException
|
||||
@@ -222,4 +203,16 @@ final class SaveController extends SimpleControllerBase
|
||||
$this->returnJsonResponseException($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FileException
|
||||
*/
|
||||
private function getTask(): ?TaskInterface
|
||||
{
|
||||
$taskId = $this->request->analyzeString('taskId');
|
||||
|
||||
return $taskId !== null
|
||||
? TaskFactory::register(new Task(__FUNCTION__, $taskId))
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* @author nuxsmin
|
||||
* @link https://syspass.org
|
||||
* @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org
|
||||
* @copyright 2012-2024, Rubén Domínguez nuxsmin@$syspass.org
|
||||
*
|
||||
* This file is part of sysPass.
|
||||
*
|
||||
@@ -24,8 +24,9 @@
|
||||
|
||||
namespace SP\Core\Messages;
|
||||
|
||||
use JsonException;
|
||||
use JsonSerializable;
|
||||
use SP\Domain\Common\Adapters\Serde;
|
||||
use SP\Domain\Core\Exceptions\SPException;
|
||||
use SP\Domain\Core\Messages\MessageInterface;
|
||||
|
||||
/**
|
||||
@@ -40,7 +41,9 @@ final class TaskMessage implements MessageInterface, JsonSerializable
|
||||
protected int $progress = 0;
|
||||
protected int $end = 0;
|
||||
|
||||
public function __construct(private string $taskId, private string $task) {}
|
||||
public function __construct(private string $taskId, private string $task)
|
||||
{
|
||||
}
|
||||
|
||||
public function getTask(): string
|
||||
{
|
||||
@@ -116,23 +119,23 @@ final class TaskMessage implements MessageInterface, JsonSerializable
|
||||
public function composeText(string $delimiter = ';'): string
|
||||
{
|
||||
return implode($delimiter, [
|
||||
'taskId' => $this->taskId,
|
||||
'task' => $this->task,
|
||||
'message' => $this->message,
|
||||
'time' => $this->time,
|
||||
'taskId' => $this->taskId,
|
||||
'task' => $this->task,
|
||||
'message' => $this->message,
|
||||
'time' => $this->time,
|
||||
'progress' => $this->progress,
|
||||
'end' => $this->end,
|
||||
'end' => $this->end,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Componer un mensaje en formato JSON
|
||||
*
|
||||
* @throws JsonException
|
||||
* @throws SPException
|
||||
*/
|
||||
public function composeJson(): bool|string
|
||||
{
|
||||
return json_encode($this, JSON_THROW_ON_ERROR);
|
||||
return Serde::serializeJson($this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,7 +39,6 @@ use SP\Domain\Core\Crypt\CryptInterface;
|
||||
use SP\Domain\Core\Exceptions\CryptException;
|
||||
use SP\Domain\Core\Exceptions\SPException;
|
||||
use SP\Domain\Crypt\Dtos\UpdateMasterPassRequest;
|
||||
use SP\Domain\Task\Services\TaskFactory;
|
||||
|
||||
use function SP\__;
|
||||
use function SP\__u;
|
||||
@@ -59,23 +58,6 @@ final class AccountCrypt extends Service implements AccountCryptService
|
||||
parent::__construct($application);
|
||||
}
|
||||
|
||||
/**
|
||||
* Devolver el tiempo aproximado en segundos de una operación
|
||||
*
|
||||
* @return array Con el tiempo estimado y los elementos por segundo
|
||||
*/
|
||||
public static function getETA(int $startTime, int $numItems, int $totalItems): array
|
||||
{
|
||||
if ($numItems > 0 && $totalItems > 0) {
|
||||
$runtime = time() - $startTime;
|
||||
$eta = (int)((($totalItems * $runtime) / $numItems) - $runtime);
|
||||
|
||||
return [$eta, $numItems / $runtime];
|
||||
}
|
||||
|
||||
return [0, 0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza las claves de todas las cuentas con la nueva clave maestra.
|
||||
*
|
||||
@@ -93,18 +75,6 @@ final class AccountCrypt extends Service implements AccountCryptService
|
||||
)
|
||||
);
|
||||
|
||||
$task = $updateMasterPassRequest->getTask();
|
||||
|
||||
if (null !== $task) {
|
||||
TaskFactory::update(
|
||||
$task,
|
||||
TaskFactory::createMessage(
|
||||
$task->getTaskId(),
|
||||
__u('Update Master Password')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$eventMessage = $this->processAccounts(
|
||||
$this->accountService->getAccountsPassData(),
|
||||
function (int $accountId, EncryptedPassword $encryptedPassword) {
|
||||
@@ -163,8 +133,6 @@ final class AccountCrypt extends Service implements AccountCryptService
|
||||
$configData = $this->config->getConfigData();
|
||||
$currentMasterPassHash = $updateMasterPassRequest->getCurrentHash();
|
||||
|
||||
$task = $updateMasterPassRequest->getTask();
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
// No realizar cambios si está en modo demo
|
||||
if ($configData->isDemoEnabled()) {
|
||||
@@ -175,29 +143,16 @@ final class AccountCrypt extends Service implements AccountCryptService
|
||||
if ($counter % 100 === 0) {
|
||||
$eta = self::getETA($startTime, $counter, $numAccounts);
|
||||
|
||||
if (null !== $task) {
|
||||
$taskMessage = TaskFactory::createMessage(
|
||||
$task->getTaskId(),
|
||||
__('Update Master Password')
|
||||
)->setMessage(
|
||||
sprintf(__('Accounts updated: %d / %d - ETA: %ds (%.2f/s)'), $counter, $numAccounts, ...$eta)
|
||||
)->setProgress(round(($counter * 100) / $numAccounts, 2));
|
||||
|
||||
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]
|
||||
)
|
||||
);
|
||||
}
|
||||
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) {
|
||||
@@ -237,6 +192,23 @@ final class AccountCrypt extends Service implements AccountCryptService
|
||||
return $eventMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Devolver el tiempo aproximado en segundos de una operación
|
||||
*
|
||||
* @return array Con el tiempo estimado y los elementos por segundo
|
||||
*/
|
||||
public static function getETA(int $startTime, int $numItems, int $totalItems): array
|
||||
{
|
||||
if ($numItems > 0 && $totalItems > 0) {
|
||||
$runtime = time() - $startTime;
|
||||
$eta = (int)((($totalItems * $runtime) / $numItems) - $runtime);
|
||||
|
||||
return [$eta, $numItems / $runtime];
|
||||
}
|
||||
|
||||
return [0, 0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Devolver los datos de la clave encriptados
|
||||
*
|
||||
@@ -288,18 +260,6 @@ final class AccountCrypt extends Service implements AccountCryptService
|
||||
)
|
||||
);
|
||||
|
||||
$task = $updateMasterPassRequest->getTask();
|
||||
|
||||
if (null !== $task) {
|
||||
TaskFactory::update(
|
||||
$task,
|
||||
TaskFactory::createMessage(
|
||||
$task->getTaskId(),
|
||||
__u('Update Master Password (H)')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$eventMessage = $this->processAccounts(
|
||||
$this->accountHistoryService->getAccountsPassData(),
|
||||
function (int $accountId, EncryptedPassword $encryptedPassword) {
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
namespace SP\Domain\Crypt\Dtos;
|
||||
|
||||
use SP\Core\Crypt\Hash;
|
||||
use SP\Domain\Task\Ports\TaskInterface;
|
||||
|
||||
/**
|
||||
* Class UpdateMasterPassRequest
|
||||
@@ -35,10 +34,9 @@ final class UpdateMasterPassRequest
|
||||
private string $hash;
|
||||
|
||||
public function __construct(
|
||||
private readonly string $currentMasterPass,
|
||||
private readonly string $newMasterPass,
|
||||
private readonly string $currentHash,
|
||||
private readonly ?TaskInterface $task = null
|
||||
private readonly string $currentMasterPass,
|
||||
private readonly string $newMasterPass,
|
||||
private readonly string $currentHash
|
||||
) {
|
||||
$this->hash = Hash::hashKey($newMasterPass);
|
||||
}
|
||||
@@ -53,16 +51,6 @@ final class UpdateMasterPassRequest
|
||||
return $this->newMasterPass;
|
||||
}
|
||||
|
||||
public function getTask(): ?TaskInterface
|
||||
{
|
||||
return $this->task;
|
||||
}
|
||||
|
||||
public function useTask(): bool
|
||||
{
|
||||
return $this->task !== null;
|
||||
}
|
||||
|
||||
public function getHash(): string
|
||||
{
|
||||
return $this->hash;
|
||||
|
||||
@@ -37,7 +37,6 @@ use SP\Domain\CustomField\Ports\CustomFieldCryptService;
|
||||
use SP\Domain\CustomField\Ports\CustomFieldDataService;
|
||||
use SP\Domain\Task\Services\TaskFactory;
|
||||
|
||||
use function SP\__;
|
||||
use function SP\__u;
|
||||
use function SP\processException;
|
||||
|
||||
@@ -117,18 +116,6 @@ final class CustomFieldCrypt extends Service implements CustomFieldCryptService
|
||||
)
|
||||
);
|
||||
|
||||
if ($request->useTask()) {
|
||||
$task = $request->getTask();
|
||||
|
||||
TaskFactory::update(
|
||||
$task,
|
||||
TaskFactory::createMessage(
|
||||
$task->getTaskId(),
|
||||
__('Update Master Password')
|
||||
)->setMessage(__('Updating encrypted data'))
|
||||
);
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
$success = [];
|
||||
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
* @link https://syspass.org
|
||||
* @copyright 2012-2024, Rubén Domínguez nuxsmin@$syspass.org
|
||||
*
|
||||
* This file is part of sysPass.
|
||||
*
|
||||
* sysPass is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* sysPass is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace SP\Domain\Task\Ports;
|
||||
|
||||
use SP\Core\Messages\TaskMessage;
|
||||
use SP\Domain\File\Ports\FileHandlerInterface;
|
||||
use SP\Infrastructure\File\FileException;
|
||||
|
||||
/**
|
||||
* Class Task
|
||||
*
|
||||
* @package SP\Core
|
||||
*/
|
||||
interface TaskInterface
|
||||
{
|
||||
public function genUid(): string;
|
||||
|
||||
/**
|
||||
* Escribir el tado de la tarea a un archivo
|
||||
*/
|
||||
public function writeStatusAndFlush(TaskMessage $message): bool;
|
||||
|
||||
/**
|
||||
* Escribir un mensaje en el archivo de la tarea en formato JSON
|
||||
*/
|
||||
public function writeJsonStatusAndFlush(TaskMessage $message): void;
|
||||
|
||||
/**
|
||||
* Iniciar la tarea
|
||||
*/
|
||||
public function end(): void;
|
||||
|
||||
/**
|
||||
* Desregistrar la tarea en la sesión
|
||||
*
|
||||
* @throws FileException
|
||||
*/
|
||||
public function unregister(): void;
|
||||
|
||||
public function getInterval(): int;
|
||||
|
||||
public function setInterval(int $interval): TaskInterface;
|
||||
|
||||
public function getTaskId(): string;
|
||||
|
||||
public function getFileOut(): ?FileHandlerInterface;
|
||||
|
||||
/**
|
||||
* Register a task
|
||||
*
|
||||
* @throws FileException
|
||||
*/
|
||||
public function register(): TaskInterface;
|
||||
|
||||
/**
|
||||
* Register a task
|
||||
*
|
||||
* Session is locked in order to allow other scripts execution
|
||||
*
|
||||
* @throws FileException
|
||||
*/
|
||||
public function registerSession(): TaskInterface;
|
||||
|
||||
public function getUid(): string;
|
||||
|
||||
public function getFileTask(): ?FileHandlerInterface;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
* @link https://syspass.org
|
||||
* @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org
|
||||
*
|
||||
* This file is part of sysPass.
|
||||
*
|
||||
* sysPass is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* sysPass is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace SP\Domain\Task\Ports;
|
||||
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* Class TaskService
|
||||
*
|
||||
* @package SP\Domain\Common\Services
|
||||
*/
|
||||
interface TaskServiceInterface
|
||||
{
|
||||
/**
|
||||
* Track task status
|
||||
*
|
||||
* @throws \JsonException
|
||||
* @throws \SP\Domain\Common\Services\ServiceException
|
||||
*/
|
||||
public function trackStatus(string $taskId, Closure $messagePusher): void;
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
* @link https://syspass.org
|
||||
* @copyright 2012-2024, Rubén Domínguez nuxsmin@$syspass.org
|
||||
*
|
||||
* This file is part of sysPass.
|
||||
*
|
||||
* sysPass is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* sysPass is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace SP\Domain\Task\Services;
|
||||
|
||||
use JsonException;
|
||||
use SP\Core\Context\Session;
|
||||
use SP\Core\Messages\TaskMessage;
|
||||
use SP\Domain\Common\Adapters\Serde;
|
||||
use SP\Domain\File\Ports\FileHandlerInterface;
|
||||
use SP\Domain\Task\Ports\TaskInterface;
|
||||
use SP\Infrastructure\File\FileException;
|
||||
use SP\Infrastructure\File\FileHandler;
|
||||
use SP\Infrastructure\File\FileSystem;
|
||||
|
||||
use function SP\logger;
|
||||
use function SP\processException;
|
||||
|
||||
/**
|
||||
* Class Task
|
||||
*
|
||||
* @package SP\Core
|
||||
*/
|
||||
final class Task implements TaskInterface
|
||||
{
|
||||
private ?FileHandler $fileOut = null;
|
||||
private ?FileHandler $fileTask = null;
|
||||
/**
|
||||
* @var int Intérvalo en segundos
|
||||
*/
|
||||
private int $interval = 5;
|
||||
/**
|
||||
* @var bool Si se ha inicializado para escribir en el archivo
|
||||
*/
|
||||
private bool $initialized;
|
||||
private string $uid;
|
||||
|
||||
/**
|
||||
* Task constructor.
|
||||
*
|
||||
* @param string $name Nombre de la tarea
|
||||
* @param string $taskId
|
||||
*/
|
||||
public function __construct(private string $name, private string $taskId)
|
||||
{
|
||||
$this->initialized = $this->checkFile();
|
||||
$this->uid = $this->genUid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Comprobar si se puede escribir en el archivo
|
||||
*/
|
||||
private function checkFile(): bool
|
||||
{
|
||||
$tempDir = FileSystem::getTempDir();
|
||||
|
||||
if ($tempDir !== false) {
|
||||
$this->fileOut = new FileHandler(
|
||||
$tempDir.
|
||||
DIRECTORY_SEPARATOR.
|
||||
$this->taskId.
|
||||
'.out'
|
||||
);
|
||||
$this->fileTask = new FileHandler(
|
||||
$tempDir.
|
||||
DIRECTORY_SEPARATOR.
|
||||
$this->taskId.
|
||||
'.task'
|
||||
);
|
||||
|
||||
$this->deleteTaskFiles();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eliminar los archivos de la tarea no usados
|
||||
*/
|
||||
private function deleteTaskFiles(): void
|
||||
{
|
||||
$filesOut =
|
||||
dirname($this->fileOut->getFile()).
|
||||
DIRECTORY_SEPARATOR.
|
||||
$this->taskId.
|
||||
'*.out';
|
||||
$filesTask =
|
||||
dirname($this->fileTask->getFile()).
|
||||
DIRECTORY_SEPARATOR.
|
||||
$this->taskId.
|
||||
'*.task';
|
||||
|
||||
array_map(
|
||||
'unlink',
|
||||
array_merge(glob($filesOut), glob($filesTask))
|
||||
);
|
||||
}
|
||||
|
||||
public function genUid(): string
|
||||
{
|
||||
return md5($this->name.$this->taskId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generar un ID de tarea
|
||||
*/
|
||||
public static function genTaskId(string $name): string
|
||||
{
|
||||
return uniqid($name, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escribir el tado de la tarea a un archivo
|
||||
*/
|
||||
public function writeStatusAndFlush(TaskMessage $message): bool
|
||||
{
|
||||
try {
|
||||
if ($this->initialized === true) {
|
||||
$this->fileOut->save($message->composeText());
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch (FileException $e) {
|
||||
processException($e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escribir un mensaje en el archivo de la tarea en formato JSON
|
||||
*/
|
||||
public function writeJsonStatusAndFlush(TaskMessage $message): void
|
||||
{
|
||||
try {
|
||||
if ($this->initialized === true) {
|
||||
$this->fileOut->save($message->composeJson());
|
||||
}
|
||||
} catch (FileException|JsonException $e) {
|
||||
processException($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iniciar la tarea
|
||||
*/
|
||||
public function end(): void
|
||||
{
|
||||
try {
|
||||
logger("End Task: {$this->name}");
|
||||
|
||||
$this->unregister();
|
||||
|
||||
$this->fileOut->delete();
|
||||
} catch (FileException $e) {
|
||||
processException($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Desregistrar la tarea en la sesión
|
||||
*
|
||||
* @throws FileException
|
||||
*/
|
||||
public function unregister(): void
|
||||
{
|
||||
logger("Unregister Task: $this->name");
|
||||
|
||||
$this->fileTask->delete();
|
||||
}
|
||||
|
||||
public function getInterval(): int
|
||||
{
|
||||
return $this->interval;
|
||||
}
|
||||
|
||||
public function setInterval(int $interval): TaskInterface
|
||||
{
|
||||
$this->interval = $interval;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTaskId(): string
|
||||
{
|
||||
return $this->taskId;
|
||||
}
|
||||
|
||||
public function getFileOut(): ?FileHandlerInterface
|
||||
{
|
||||
return $this->fileOut;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a task
|
||||
*
|
||||
* @throws FileException
|
||||
*/
|
||||
public function register(): TaskInterface
|
||||
{
|
||||
logger("Register Task: $this->name");
|
||||
|
||||
$this->fileTask->save(Serde::serialize($this));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a task
|
||||
*
|
||||
* Session is locked in order to allow other scripts execution
|
||||
*
|
||||
* @throws FileException
|
||||
*/
|
||||
public function registerSession(): TaskInterface
|
||||
{
|
||||
logger("Register Task (session): $this->name");
|
||||
|
||||
$this->fileTask->save(Serde::serialize($this));
|
||||
|
||||
Session::close();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUid(): string
|
||||
{
|
||||
return $this->uid;
|
||||
}
|
||||
|
||||
public function getFileTask(): ?FileHandlerInterface
|
||||
{
|
||||
return $this->fileTask;
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
* @link https://syspass.org
|
||||
* @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org
|
||||
*
|
||||
* This file is part of sysPass.
|
||||
*
|
||||
* sysPass is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* sysPass is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace SP\Domain\Task\Services;
|
||||
|
||||
use RuntimeException;
|
||||
use SP\Core\Messages\TaskMessage;
|
||||
use SP\Domain\Task\Ports\TaskInterface;
|
||||
use SP\Infrastructure\File\FileException;
|
||||
|
||||
/**
|
||||
* Class TaskFactory
|
||||
*
|
||||
* @package SP\Core
|
||||
*/
|
||||
final class TaskFactory
|
||||
{
|
||||
/**
|
||||
* @var TaskInterface[]
|
||||
*/
|
||||
private static array $tasks = [];
|
||||
|
||||
/**
|
||||
* Crear una tarea para la actualización de estado de la actualización
|
||||
*
|
||||
* @throws FileException
|
||||
*/
|
||||
public static function register(TaskInterface $task, bool $hasSession = true): TaskInterface
|
||||
{
|
||||
$task = self::add($task);
|
||||
|
||||
if ($hasSession) {
|
||||
return $task->registerSession();
|
||||
}
|
||||
|
||||
return $task->register();
|
||||
}
|
||||
|
||||
private static function add(TaskInterface $task): TaskInterface
|
||||
{
|
||||
if (!isset(self::$tasks[$task->getUid()])) {
|
||||
self::$tasks[$task->getUid()] = $task;
|
||||
|
||||
return $task;
|
||||
}
|
||||
|
||||
throw new RuntimeException('Task already registered');
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizar la tarea
|
||||
*/
|
||||
public static function end(TaskInterface $task): void
|
||||
{
|
||||
self::get($task->getUid())->end();
|
||||
|
||||
self::delete($task->getUid());
|
||||
}
|
||||
|
||||
private static function get(string $id): TaskInterface
|
||||
{
|
||||
if (isset(self::$tasks[$id])) {
|
||||
return self::$tasks[$id];
|
||||
}
|
||||
|
||||
throw new RuntimeException('Task not registered');
|
||||
}
|
||||
|
||||
private static function delete(string $id): void
|
||||
{
|
||||
if (isset(self::$tasks[$id])) {
|
||||
unset(self::$tasks[$id]);
|
||||
}
|
||||
}
|
||||
|
||||
public static function createMessage(string $taskId, string $task): TaskMessage
|
||||
{
|
||||
return new TaskMessage($taskId, $task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enviar un mensaje de actualización a la tarea
|
||||
*/
|
||||
public static function update(TaskInterface $task, TaskMessage $taskMessage): void
|
||||
{
|
||||
self::get($task->getUid())->writeJsonStatusAndFlush($taskMessage);
|
||||
}
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
* @link https://syspass.org
|
||||
* @copyright 2012-2024, Rubén Domínguez nuxsmin@$syspass.org
|
||||
*
|
||||
* This file is part of sysPass.
|
||||
*
|
||||
* sysPass is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* sysPass is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace SP\Domain\Task\Services;
|
||||
|
||||
use Closure;
|
||||
use JsonException;
|
||||
use SP\Domain\Common\Services\Service;
|
||||
use SP\Domain\Common\Services\ServiceException;
|
||||
use SP\Domain\Task\Ports\TaskServiceInterface;
|
||||
use SP\Infrastructure\File\FileException;
|
||||
use SP\Infrastructure\File\FileHandler;
|
||||
use SP\Infrastructure\File\FileSystem;
|
||||
|
||||
/**
|
||||
* Class TaskService
|
||||
*
|
||||
* @package SP\Domain\Common\Services
|
||||
*/
|
||||
final class TaskService extends Service implements TaskServiceInterface
|
||||
{
|
||||
/**
|
||||
* Time for waiting to initialization
|
||||
*/
|
||||
private const STARTUP_WAIT_TIME = 5;
|
||||
/**
|
||||
* Initialization attempts
|
||||
*/
|
||||
private const STARTUP_WAIT_COUNT = 30;
|
||||
|
||||
private ?Closure $messagePusher = null;
|
||||
private ?Task $task = null;
|
||||
private ?string $taskDirectory = null;
|
||||
private ?string $taskId = null;
|
||||
private ?FileHandler $taskFile = null;
|
||||
|
||||
/**
|
||||
* Track task status
|
||||
*
|
||||
* @throws JsonException
|
||||
* @throws ServiceException
|
||||
*/
|
||||
public function trackStatus(string $taskId, Closure $messagePusher): void
|
||||
{
|
||||
$this->taskId = $taskId;
|
||||
$this->taskDirectory = FileSystem::getTempDir();
|
||||
$this->messagePusher = $messagePusher;
|
||||
|
||||
if ($this->taskDirectory === false || !$this->getLock()) {
|
||||
throw new ServiceException(__('Unable to create the lock file'));
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
|
||||
while (!$this->checkTaskRegistered()
|
||||
|| !file_exists($this->task->getFileOut()->getFile())
|
||||
) {
|
||||
if ($count >= self::STARTUP_WAIT_COUNT) {
|
||||
throw new ServiceException(__('Task not set within wait time'));
|
||||
}
|
||||
|
||||
logger(
|
||||
sprintf(
|
||||
'Waiting for task "%s" (%ds) ...',
|
||||
$taskId,
|
||||
(self::STARTUP_WAIT_COUNT - $count) * self::STARTUP_WAIT_TIME
|
||||
)
|
||||
);
|
||||
|
||||
$count++;
|
||||
|
||||
sleep(self::STARTUP_WAIT_TIME);
|
||||
}
|
||||
|
||||
$this->readTaskStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a lock for task execution
|
||||
*/
|
||||
private function getLock(): bool
|
||||
{
|
||||
$lockFile = new FileHandler($this->taskDirectory.DIRECTORY_SEPARATOR.$this->taskId.'.lock');
|
||||
|
||||
try {
|
||||
if ($lockFile->getFileTime() + (self::STARTUP_WAIT_COUNT * self::STARTUP_WAIT_TIME) < time()) {
|
||||
$lockFile->delete();
|
||||
}
|
||||
} catch (FileException $e) {
|
||||
processException($e);
|
||||
}
|
||||
|
||||
try {
|
||||
$lockFile->write(time());
|
||||
|
||||
return true;
|
||||
} catch (FileException $e) {
|
||||
processException($e);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the task's file has been registered
|
||||
*/
|
||||
private function checkTaskRegistered(): bool
|
||||
{
|
||||
if (is_object($this->task)) {
|
||||
logger('Task detected: '.$this->task->getTaskId());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->taskFile = new FileHandler($this->taskDirectory.DIRECTORY_SEPARATOR.$this->taskId.'.task');
|
||||
$this->taskFile->checkFileExists();
|
||||
$this->task = unserialize($this->taskFile->readToString());
|
||||
|
||||
return is_object($this->task);
|
||||
} catch (FileException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a task status and send it back to the browser (messagePusher)
|
||||
*
|
||||
* @throws JsonException
|
||||
*/
|
||||
private function readTaskStatus(): void
|
||||
{
|
||||
logger('Tracking task status: '.$this->task->getTaskId());
|
||||
|
||||
$id = 0;
|
||||
$failCount = 0;
|
||||
$outputFile = $this->task->getFileOut();
|
||||
|
||||
while ($failCount <= self::STARTUP_WAIT_COUNT
|
||||
&& $this->checkTaskFile()
|
||||
) {
|
||||
try {
|
||||
$content = $outputFile->readToString();
|
||||
|
||||
if (!empty($content)) {
|
||||
$this->messagePusher->call($this, $id, $content);
|
||||
$id++;
|
||||
} else {
|
||||
$message = TaskFactory::createMessage(
|
||||
$this->task->getTaskId(),
|
||||
__('Waiting for progress updating ...')
|
||||
);
|
||||
|
||||
logger($message->getTask());
|
||||
|
||||
$this->messagePusher->call($this, $id, $message->composeJson());
|
||||
|
||||
$failCount++;
|
||||
}
|
||||
} catch (FileException $e) {
|
||||
processException($e);
|
||||
|
||||
$this->messagePusher->call(
|
||||
$this,
|
||||
$id,
|
||||
TaskFactory::createMessage(
|
||||
$this->task->getTaskId(),
|
||||
$e->getMessage()
|
||||
)->composeJson()
|
||||
);
|
||||
|
||||
$failCount++;
|
||||
}
|
||||
|
||||
sleep($this->task->getInterval());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the task's output file exists
|
||||
*/
|
||||
private function checkTaskFile(): bool
|
||||
{
|
||||
try {
|
||||
$this->taskFile->checkFileExists();
|
||||
|
||||
return true;
|
||||
} catch (FileException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,9 +109,9 @@ class FileSystem
|
||||
/**
|
||||
* Comprueba y devuelve un directorio temporal válido
|
||||
*
|
||||
* @return bool|string
|
||||
* @return false|string
|
||||
*/
|
||||
public static function getTempDir(): bool|string
|
||||
public static function getTempDir(): false|string
|
||||
{
|
||||
$sysTmp = sys_get_temp_dir();
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ use SP\Domain\Core\Exceptions\SPException;
|
||||
use SP\Domain\Crypt\Dtos\UpdateMasterPassRequest;
|
||||
use SP\Domain\Task\Ports\TaskInterface;
|
||||
use SP\Domain\Task\Services\TaskFactory;
|
||||
use SP\Infrastructure\File\FileException;
|
||||
use SPT\Generators\AccountDataGenerator;
|
||||
use SPT\UnitaryTestCase;
|
||||
|
||||
@@ -57,25 +56,14 @@ class AccountCryptTest extends UnitaryTestCase
|
||||
|
||||
/**
|
||||
* @throws ServiceException
|
||||
* @throws FileException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testUpdateMasterPassword(): void
|
||||
{
|
||||
$task = $this->createMock(TaskInterface::class);
|
||||
$task->method('getUid')
|
||||
->willReturn(self::$faker->uuid);
|
||||
$task->method('getTaskId')
|
||||
->willReturn((string)self::$faker->randomNumber());
|
||||
$task->method('registerSession')
|
||||
->willReturnSelf();
|
||||
|
||||
$request =
|
||||
new UpdateMasterPassRequest(
|
||||
self::$faker->password,
|
||||
self::$faker->password,
|
||||
self::$faker->sha1,
|
||||
TaskFactory::register($task)
|
||||
self::$faker->sha1
|
||||
);
|
||||
$accountData = array_map(static fn() => AccountDataGenerator::factory()->buildAccount(), range(0, 9));
|
||||
|
||||
@@ -92,8 +80,6 @@ class AccountCryptTest extends UnitaryTestCase
|
||||
$this->crypt->expects(self::exactly(10))
|
||||
->method('encrypt')
|
||||
->willReturn(self::$faker->password);
|
||||
$task->expects(self::exactly(2))
|
||||
->method('writeJsonStatusAndFlush');
|
||||
|
||||
$this->accountCrypt->updateMasterPassword($request);
|
||||
}
|
||||
@@ -161,24 +147,15 @@ class AccountCryptTest extends UnitaryTestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws ServiceException
|
||||
* @throws FileException
|
||||
*/
|
||||
public function testUpdateHistoryMasterPassword(): void
|
||||
{
|
||||
$task = $this->createMock(TaskInterface::class);
|
||||
$task->method('getUid')
|
||||
->willReturn(self::$faker->uuid);
|
||||
$task->method('getTaskId')
|
||||
->willReturn((string)self::$faker->randomNumber());
|
||||
$task->method('registerSession')
|
||||
->willReturnSelf();
|
||||
|
||||
$request = new UpdateMasterPassRequest(
|
||||
self::$faker->password,
|
||||
self::$faker->password,
|
||||
self::$faker->sha1,
|
||||
TaskFactory::register($task)
|
||||
self::$faker->sha1
|
||||
);
|
||||
$accountData = array_map(static fn() => AccountDataGenerator::factory()->buildAccount(), range(0, 9));
|
||||
|
||||
@@ -189,8 +166,6 @@ class AccountCryptTest extends UnitaryTestCase
|
||||
->method('updatePasswordMasterPass');
|
||||
$this->crypt->expects(self::exactly(10))
|
||||
->method('decrypt');
|
||||
$task->expects(self::exactly(2))
|
||||
->method('writeJsonStatusAndFlush');
|
||||
|
||||
$this->accountCrypt->updateHistoryMasterPassword($request);
|
||||
}
|
||||
|
||||
@@ -36,8 +36,6 @@ use SP\Domain\CustomField\Models\CustomFieldData as CustomFieldDataModel;
|
||||
use SP\Domain\CustomField\Ports\CustomFieldDataService;
|
||||
use SP\Domain\CustomField\Services\CustomFieldCrypt;
|
||||
use SP\Domain\Task\Ports\TaskInterface;
|
||||
use SP\Domain\Task\Services\TaskFactory;
|
||||
use SP\Infrastructure\File\FileException;
|
||||
use SPT\Generators\CustomFieldDataGenerator;
|
||||
use SPT\UnitaryTestCase;
|
||||
|
||||
@@ -51,7 +49,7 @@ class CustomFieldCryptTest extends UnitaryTestCase
|
||||
|
||||
private CustomFieldDataService|MockObject $customFieldService;
|
||||
private CryptInterface|MockObject $crypt;
|
||||
private CustomFieldCrypt $customFieldCrypt;
|
||||
private CustomFieldCrypt $customFieldCrypt;
|
||||
|
||||
/**
|
||||
* @throws ServiceException
|
||||
@@ -101,8 +99,6 @@ class CustomFieldCryptTest extends UnitaryTestCase
|
||||
->method('getAllEncrypted')
|
||||
->willReturn([]);
|
||||
|
||||
$data = self::$faker->text();
|
||||
|
||||
$this->crypt
|
||||
->expects(self::never())
|
||||
->method('decrypt');
|
||||
@@ -115,20 +111,13 @@ class CustomFieldCryptTest extends UnitaryTestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ServiceException
|
||||
* @throws Exception
|
||||
* @throws FileException
|
||||
* @throws ServiceException
|
||||
*/
|
||||
public function testUpdateMasterPasswordWithTask()
|
||||
{
|
||||
$task = $this->createStub(TaskInterface::class);
|
||||
$task->method('getTaskId')->willReturn(self::$faker->colorName());
|
||||
$task->method('getUid')->willReturn(self::$faker->uuid());
|
||||
|
||||
TaskFactory::register($task);
|
||||
|
||||
$hash = self::$faker->sha1;
|
||||
$request = new UpdateMasterPassRequest('secret', 'test_secret', $hash, $task);
|
||||
$request = new UpdateMasterPassRequest('secret', 'test_secret', $hash);
|
||||
$customFieldData = CustomFieldDataGenerator::factory()->buildCustomFieldData();
|
||||
|
||||
$this->customFieldService
|
||||
@@ -187,7 +176,6 @@ class CustomFieldCryptTest extends UnitaryTestCase
|
||||
{
|
||||
$hash = self::$faker->sha1;
|
||||
$request = new UpdateMasterPassRequest('secret', 'test_secret', $hash);
|
||||
$customFieldData = CustomFieldDataGenerator::factory()->buildCustomFieldData();
|
||||
|
||||
$this->customFieldService
|
||||
->expects(self::once())
|
||||
|
||||
Reference in New Issue
Block a user