diff --git a/app/modules/web/Controllers/ConfigGeneral/DownloadConfigBackup.php b/app/modules/web/Controllers/ConfigGeneral/DownloadConfigBackup.php deleted file mode 100644 index d09d7743..00000000 --- a/app/modules/web/Controllers/ConfigGeneral/DownloadConfigBackup.php +++ /dev/null @@ -1,121 +0,0 @@ -. - */ - -namespace SP\Modules\Web\Controllers\ConfigGeneral; - -use Exception; -use JsonException; -use RuntimeException; -use SP\Core\Application; -use SP\Core\Events\Event; -use SP\Core\Events\EventMessage; -use SP\Domain\Config\Ports\ConfigBackupService; -use SP\Domain\Config\Services\ConfigBackup; -use SP\Domain\Core\Acl\AclActionsInterface; -use SP\Domain\Core\Acl\UnauthorizedPageException; -use SP\Domain\Core\Exceptions\SessionTimeout; -use SP\Modules\Web\Controllers\SimpleControllerBase; -use SP\Modules\Web\Controllers\Traits\JsonTrait; -use SP\Mvc\Controller\SimpleControllerHelper; - -/** - * Class DownloadConfigBackup - */ -final class DownloadConfigBackup extends SimpleControllerBase -{ - use JsonTrait; - - private ConfigBackupService $configBackupService; - - public function __construct( - Application $application, - SimpleControllerHelper $simpleControllerHelper, - ConfigBackupService $configBackupService - ) { - parent::__construct($application, $simpleControllerHelper); - - $this->configBackupService = $configBackupService; - } - - public function downloadConfigBackupAction(string $type): string - { - if ($this->configData->isDemoEnabled()) { - return __('Ey, this is a DEMO!!'); - } - - try { - $this->eventDispatcher->notify( - 'download.configBackupFile', - new Event( - $this, - EventMessage::build() - ->addDescription(__u('File downloaded')) - ->addDetail(__u('File'), 'config.json') - ) - ); - - if ($type === 'json') { - $data = ConfigBackup::configToJson($this->configBackupService->getBackup()); - } else { - throw new RuntimeException('Not implemented'); - } - - $response = $this->router->response(); - $response->header('Cache-Control', 'max-age=60, must-revalidate'); - $response->header('Content-length', strlen($data)); - $response->header('Content-type', 'application/json'); - $response->header('Content-Description', ' sysPass file'); - $response->header('Content-transfer-encoding', 'chunked'); - $response->header('Content-Disposition', 'attachment; filename="config.json"'); - $response->header('Set-Cookie', 'fileDownload=true; path=/'); - $response->header('Content-transfer-encoding', 'binary'); - $response->header('Set-Cookie', 'fileDownload=true; path=/'); - - $response->body($data); - $response->send(true); - } catch (Exception $e) { - processException($e); - - $this->eventDispatcher->notify('exception', new Event($e)); - } - - return ''; - } - - /** - * @throws JsonException - * @throws SessionTimeout - */ - protected function initialize(): void - { - try { - $this->checks(); - $this->checkAccess(AclActionsInterface::CONFIG_GENERAL); - } catch (UnauthorizedPageException $e) { - $this->eventDispatcher->notify('exception', new Event($e)); - - $this->returnJsonResponseException($e); - } - } -} diff --git a/app/modules/web/Controllers/ConfigGeneral/DownloadConfigBackupController.php b/app/modules/web/Controllers/ConfigGeneral/DownloadConfigBackupController.php new file mode 100644 index 00000000..c70ac444 --- /dev/null +++ b/app/modules/web/Controllers/ConfigGeneral/DownloadConfigBackupController.php @@ -0,0 +1,112 @@ +. + */ + +namespace SP\Modules\Web\Controllers\ConfigGeneral; + +use Klein\Response; +use RuntimeException; +use SP\Core\Application; +use SP\Core\Events\Event; +use SP\Core\Events\EventMessage; +use SP\Domain\Common\Attributes\Action; +use SP\Domain\Common\Dtos\ActionResponse; +use SP\Domain\Common\Enums\ResponseStatus; +use SP\Domain\Common\Enums\ResponseType; +use SP\Domain\Common\Services\ServiceException; +use SP\Domain\Config\Ports\ConfigBackupService; +use SP\Domain\Config\Services\ConfigBackup; +use SP\Domain\Core\Acl\AclActionsInterface; +use SP\Domain\Core\Acl\UnauthorizedPageException; +use SP\Domain\Core\Exceptions\SessionTimeout; +use SP\Domain\Core\Exceptions\SPException; +use SP\Modules\Web\Controllers\SimpleControllerBase; +use SP\Mvc\Controller\SimpleControllerHelper; + +use function SP\__; +use function SP\__u; + +/** + * Class DownloadConfigBackupController + */ +final class DownloadConfigBackupController extends SimpleControllerBase +{ + public function __construct( + Application $application, + SimpleControllerHelper $simpleControllerHelper, + protected readonly ConfigBackupService $configBackupService + ) { + parent::__construct($application, $simpleControllerHelper); + } + + /** + * @throws ServiceException + * @throws SPException + */ + #[Action(ResponseType::CALLBACK)] + public function downloadConfigBackupAction(string $type): ActionResponse + { + if ($this->configData->isDemoEnabled()) { + return ActionResponse::warning(__('Ey, this is a DEMO!!')); + } + + $this->eventDispatcher->notify( + 'download.configBackupFile', + new Event( + $this, + EventMessage::build(__u('File downloaded'))->addDetail(__u('File'), 'config.json') + ) + ); + + if ($type !== 'json') { + throw new RuntimeException('Not implemented'); + } + + $data = ConfigBackup::configToJson($this->configBackupService->getBackup()); + + return new ActionResponse( + ResponseStatus::OK, + function (Response $response) use ($data) { + $response->header('Cache-Control', 'max-age=60, must-revalidate') + ->header('Content-length', strlen($data)) + ->header('Content-type', 'application/json') + ->header('Content-Description', ' sysPass file') + ->header('Content-transfer-encoding', 'binary') + ->header('Content-Disposition', 'attachment; filename="config.json"') + ->header('Set-Cookie', 'fileDownload=true; path=/') + ->body($data); + } + ); + } + + /** + * @throws SPException + * @throws SessionTimeout + * @throws UnauthorizedPageException + */ + protected function initialize(): void + { + $this->checks(); + $this->checkAccess(AclActionsInterface::CONFIG_GENERAL); + } +} diff --git a/app/modules/web/Controllers/ConfigGeneral/DownloadLogController.php b/app/modules/web/Controllers/ConfigGeneral/DownloadLogController.php index f2bf19fb..f7c00c8f 100644 --- a/app/modules/web/Controllers/ConfigGeneral/DownloadLogController.php +++ b/app/modules/web/Controllers/ConfigGeneral/DownloadLogController.php @@ -25,15 +25,18 @@ namespace SP\Modules\Web\Controllers\ConfigGeneral; -use Exception; +use Klein\Response; use SP\Core\Application; use SP\Core\Bootstrap\Path; use SP\Core\Bootstrap\PathsContext; use SP\Core\Context\Session; use SP\Core\Events\Event; use SP\Core\Events\EventMessage; +use SP\Domain\Common\Attributes\Action; +use SP\Domain\Common\Dtos\ActionResponse; +use SP\Domain\Common\Enums\ResponseStatus; +use SP\Domain\Common\Enums\ResponseType; use SP\Domain\Core\Acl\AclActionsInterface; -use SP\Domain\Core\Acl\UnauthorizedPageException; use SP\Domain\Core\Exceptions\SessionTimeout; use SP\Domain\Core\Exceptions\SPException; use SP\Infrastructure\File\FileHandler; @@ -41,8 +44,8 @@ use SP\Modules\Web\Controllers\SimpleControllerBase; use SP\Modules\Web\Controllers\Traits\JsonTrait; use SP\Mvc\Controller\SimpleControllerHelper; +use function SP\__; use function SP\__u; -use function SP\processException; /** * Class DownloadLogController @@ -59,53 +62,44 @@ final class DownloadLogController extends SimpleControllerBase parent::__construct($application, $simpleControllerHelper); } - public function downloadLogAction(): string + #[Action(ResponseType::CALLBACK)] + public function downloadLogAction(): ActionResponse { if ($this->configData->isDemoEnabled()) { - return __('Ey, this is a DEMO!!'); + return ActionResponse::warning(__('Ey, this is a DEMO!!')); } - try { - Session::close(); + Session::close(); - $file = new FileHandler($this->pathsContext[Path::LOG_FILE]); - $file->checkFileExists(); + $file = new FileHandler($this->pathsContext[Path::LOG_FILE]); - $this->eventDispatcher->notify( - 'download.logFile', - new Event( - $this, - EventMessage::build() - ->addDescription(__u('File downloaded')) - ->addDetail( - __u('File'), - str_replace( - $this->pathsContext[Path::APP], - '', - $file->getFile() - ) - ) - ) - ); + $this->eventDispatcher->notify( + 'download.logFile', + new Event( + $this, + EventMessage::build(__u('File downloaded')) + ->addDetail(__u('File'), $file->getName()) + ) + ); - $response = $this->router->response(); - $response->header('Cache-Control', 'max-age=60, must-revalidate'); - $response->header('Content-length', $file->getFileSize()); - $response->header('Content-type', $file->getFileType()); - $response->header('Content-Description', ' sysPass file'); - $response->header('Content-transfer-encoding', 'chunked'); - $response->header('Content-Disposition', 'attachment; filename="' . basename($file->getFile()) . '"'); - $response->header('Set-Cookie', 'fileDownload=true; path=/'); - $response->send(); + return new ActionResponse( + ResponseStatus::OK, + function (Response $response) use ($file) { + $response->header('Cache-Control', 'max-age=60, must-revalidate') + ->header('Content-length', $file->getFileSize()) + ->header('Content-type', $file->getFileType()) + ->header('Content-Description', ' sysPass file') + ->header('Content-transfer-encoding', 'chunked') + ->header( + 'Content-Disposition', + sprintf("attachment; filename=\"%s\"", basename($file->getName())) + ) + ->header('Set-Cookie', 'fileDownload=true; path=/') + ->send(); - $file->readChunked(); - } catch (Exception $e) { - processException($e); - - $this->eventDispatcher->notify('exception', new Event($e)); - } - - return ''; + $file->readChunked(); + } + ); } /** @@ -114,13 +108,7 @@ final class DownloadLogController extends SimpleControllerBase */ protected function initialize(): void { - try { - $this->checks(); - $this->checkAccess(AclActionsInterface::CONFIG_GENERAL); - } catch (UnauthorizedPageException $e) { - $this->eventDispatcher->notify('exception', new Event($e)); - - $this->returnJsonResponseException($e); - } + $this->checks(); + $this->checkAccess(AclActionsInterface::CONFIG_GENERAL); } } diff --git a/app/modules/web/Controllers/ConfigGeneral/SaveController.php b/app/modules/web/Controllers/ConfigGeneral/SaveController.php index 97e7444d..0ad4bfd3 100644 --- a/app/modules/web/Controllers/ConfigGeneral/SaveController.php +++ b/app/modules/web/Controllers/ConfigGeneral/SaveController.php @@ -24,15 +24,15 @@ namespace SP\Modules\Web\Controllers\ConfigGeneral; -use JsonException; use SP\Core\Application; -use SP\Core\Bootstrap\BootstrapBase; use SP\Core\Events\Event; use SP\Core\Events\EventMessage; +use SP\Domain\Common\Attributes\Action; +use SP\Domain\Common\Dtos\ActionResponse; +use SP\Domain\Common\Enums\ResponseType; use SP\Domain\Config\Ports\ConfigDataInterface; use SP\Domain\Config\Services\ConfigUtil; use SP\Domain\Core\Acl\AclActionsInterface; -use SP\Domain\Core\Acl\UnauthorizedPageException; use SP\Domain\Core\Exceptions\SessionTimeout; use SP\Domain\Core\Exceptions\SPException; use SP\Domain\Core\Exceptions\ValidationException; @@ -42,10 +42,9 @@ use SP\Modules\Web\Controllers\Traits\ConfigTrait; use SP\Mvc\Controller\SimpleControllerHelper; use function SP\__u; -use function SP\logger; /** - * Class ConfigGeneral + * Class SaveController * * @package SP\Modules\Web\Controllers */ @@ -54,8 +53,8 @@ final class SaveController extends SimpleControllerBase use ConfigTrait; public function __construct( - Application $application, - SimpleControllerHelper $simpleControllerHelper, + Application $application, + SimpleControllerHelper $simpleControllerHelper, private readonly AppLockHandler $appLock ) { parent::__construct($application, $simpleControllerHelper); @@ -63,34 +62,29 @@ final class SaveController extends SimpleControllerBase /** - * @throws JsonException + * @return ActionResponse * @throws SPException + * @throws ValidationException */ - public function saveAction(): bool + #[Action(ResponseType::JSON)] + public function saveAction(): ActionResponse { $configData = $this->config->getConfigData(); $eventMessage = EventMessage::build(); - try { - $this->handleGeneralConfig($configData); - $this->handleEventsConfig($configData, $eventMessage); - $this->handleProxyConfig($configData, $eventMessage); - $this->handleAuthConfig($configData, $eventMessage); - } catch (ValidationException $e) { - return $this->returnJsonResponseException($e); - } + $this->handleGeneralConfig($configData); + $this->handleEventsConfig($configData, $eventMessage); + $this->handleProxyConfig($configData, $eventMessage); + $this->handleAuthConfig($configData, $eventMessage); return $this->saveConfig( $configData, $this->config, function () use ($eventMessage, $configData) { if ($configData->isMaintenance()) { - $this->appLock->lock($this->session->getUserData()->getId(), 'config'); - } - - if (BootstrapBase::$LOCK !== false && $configData->isMaintenance() === false) { + $this->appLock->lock($this->session->getUserData()->id, 'config'); + } elseif ($this->appLock->getLock() !== false) { $this->appLock->unlock(); - logger('Application unlocked'); } $this->eventDispatcher->notify('save.config.general', new Event($this, $eventMessage)); @@ -250,13 +244,7 @@ final class SaveController extends SimpleControllerBase */ protected function initialize(): void { - try { - $this->checks(); - $this->checkAccess(AclActionsInterface::CONFIG_GENERAL); - } catch (UnauthorizedPageException $e) { - $this->eventDispatcher->notify('exception', new Event($e)); - - $this->returnJsonResponseException($e); - } + $this->checks(); + $this->checkAccess(AclActionsInterface::CONFIG_GENERAL); } } diff --git a/lib/SP/Domain/Config/Services/ConfigBackup.php b/lib/SP/Domain/Config/Services/ConfigBackup.php index aec52f16..2c589a8e 100644 --- a/lib/SP/Domain/Config/Services/ConfigBackup.php +++ b/lib/SP/Domain/Config/Services/ConfigBackup.php @@ -56,7 +56,7 @@ readonly class ConfigBackup implements ConfigBackupService */ public static function configToJson(string $configData): string { - return Serde::serializeJson(Serde::deserialize($configData, ConfigData::class), JSON_PRETTY_PRINT); + return Serde::serializeJson(Serde::deserialize($configData), JSON_PRETTY_PRINT); } /** @@ -95,15 +95,15 @@ readonly class ConfigBackup implements ConfigBackupService try { $data = $this->configService->getByParam('config_backup'); - if ($data === null) { - throw new ServiceException(__u('Unable to restore the configuration')); + if ($data === null || ($config = gzuncompress(hex2bin($data))) === false) { + throw new ServiceException(__u('Unable to retrieve the configuration')); } - return gzuncompress(hex2bin($data)); + return $config; } catch (NoSuchItemException $e) { processException($e); - throw new ServiceException(__u('Unable to restore the configuration')); + throw new ServiceException(__u('Unable to retrieve the configuration')); } } } diff --git a/tests/SP/Modules/Web/Controllers/ConfigGeneral/ConfigGeneralTest.php b/tests/SP/Modules/Web/Controllers/ConfigGeneral/ConfigGeneralTest.php new file mode 100644 index 00000000..74f046f0 --- /dev/null +++ b/tests/SP/Modules/Web/Controllers/ConfigGeneral/ConfigGeneralTest.php @@ -0,0 +1,62 @@ +. + */ + +declare(strict_types=1); + +namespace SP\Tests\Modules\Web\Controllers\ConfigGeneral; + +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\MockObject\Exception; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; +use SP\Tests\InjectConfigParam; +use SP\Tests\IntegrationTestCase; + +/** + * Class ConfigGeneralTest + */ +#[Group('integration')] +class ConfigGeneralTest extends IntegrationTestCase +{ + private const CONFIG_BACKUP = '789ca558db6edb3810edb718c5be6c6b88ba4b45814de3a497a48d374e832cb0404191944d589654916a9216f9f71d5292633ba1a4621f628bf11cce68e6cc85bc886d2b9e2ce6ff1e1779ca97edd70c4b3c89c328fe2562c78e272f9e13785197cb0a5376c6ee276f440cdba82f273089d3625ddff2353fc97192313a79c3634b019c21c0d72afb2d0520ff0e0bb68b7107318255bf059863217601d110e00bde305162b2675768426514973396e23a93efaba22e95b710205c6b04625e1529cf588731bf4c591577f7a3e3a1a517acfab1ef2ad42b3f2f2aa9b60eadd01a963e0cc3c0de0731307ab3ac938ce76bf119df5d73762b94414e2f977610577ca35de95bd628c88e3ffbbd8f0929ea5c1eab0fada0d7fbadf439a818de9aac18597f2d29964c6c03db2f9d1792931d6963da13fdf8018bd5a42524b6701a440125a1635386b01721124436b27c9fb1d0775ddb4d9dc8f394bc6d0c124d3e14422a1904968a7b4816215ed3e435653f74788dd4a7c9a2206b2677b8d0a74665a29209b65a86101dd1901f4f2e681cf0facbf787e4e5d9fd59fc6b08db515a6145f9cd4b506061d7717c8cc9a0de36791cc752e4b0cda58f25f5724445659be230dd8dea550911475956dc327a7227c101384661fcabc5c593f9ec74cbc478f269fe5eadec66f5fee3e936c940f28bfecd6d5617b32bb5f2bad5624bfe7832bb3856ab40314baf6e74f1687ebc39d7a251bb5a9c6bf5ad35d78b995eb6e65cdd6825a8b5e77871ad97ad41ef8ecef4b2b5e87236d7cbd6a4b3937ff4b2b5e9f8b2d92a68f57e06bd0f633db7e03f9b426cd96e6f126ad0e8dab1cc8a04670b862bb2da86d2582d792e24ce7637ee6d235df7b481b294bc55ac41afe021ad18e325568f45b5ecef937a1f9ed38efe1e70b7e6f42da61b9ebf22f9db1a7e10eaa12d6ce2d5ff55d56529b4e205231593c87634d2eb43ce9feb3bc60c5288830cb28dad5f091f51311c1f25d8b6f9471b8c0558493fb6611b76054f4db5eba6addfa6a39ca6de1a4b598dd35a2c0fd9697e1f90fdc1f2a666d85dc900064054a0254dbb1d2030ca8e69934fc6eab6c13c3baae58a8d9d5414e0d05823e395f069556cba220d2d53fed53686ad1f7bd1dbf600cefbe3d3cbdbb5fc13fdbd621723906d81b755e5718dfe54a297ec7b0d9689c3fa6d8cb002411ed41597f7a322dc003a5e2957a8ff4c7fcb15bbddee5947fafdef4878c93be2a08e38cf6ff630f42e8f14dceeb44fb83efee412d8961336ec6765d16d51d105cef404e28383528a2c2f24d84a129c467e40ad10852ea3b6cd984d42eaf98107bf92089e228252db09e02f85f19da6fd6eaa9880095f1c89635ced9416a3bc6042f02257436c51cba6253683ac318e824b768e731d2f7030cbfb4771257eb562cd4c851cc51ac92a8eb3d74956b3fe02dccc92d7d009c0c62e851ccb9aa2d0f21c64a181131f7c24d0ac86363006fab9f3a891534af894677264ab684e894b56ef1f5f8dbe50f24d433f400c4ce2f0d84c18f0be8ee7f8be1a795c23a83ba67cdc806d8745d2f8320926ebbafcb6da99fa49845c3b0ac324a211c28e2274e83861905a5ee49000239be1d4c376bf97d85d0935707f633742294e08248b150528f1121b411e3976e0fbd8713d0b0784fa76d2dfe05652964f8aa5d1f95059f61b9cd55b901bf14b68b9928d2ec80de8e901da18aa06d0f5084f4fabe60b80768e52cde8aad0d11d6e940d8316b8b508dca3e7b3fe12c47252dd9772d15496edcdc4d039f7b4ce323de41c1138678a2de78c633486960fa32827072c758df5608b8069a1382f963c3f24f8307456a8ea3feac24988e2b93b1af35dd023e0e08ac69caeadf34eee4a5e3d4957a3a23d547781812cc70f2d28fe0fff0152be6bea'; + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws Exception + */ + #[Test] + #[InjectConfigParam(['config_backup' => self::CONFIG_BACKUP])] + public function downloadConfigBackup() + { + $container = $this->buildContainer( + IntegrationTestCase::buildRequest('get', 'index.php', ['r' => 'configGeneral/downloadConfigBackup/json']) + ); + + $this->expectOutputRegex('/^\s+(?:"[a-z]+":)?\s(?:".*"|\d+|\[|\]),?$/mi'); + + IntegrationTestCase::runApp($container); + } +}