diff --git a/app/modules/api/Controllers/Config/ExportController.php b/app/modules/api/Controllers/Config/ExportController.php index 9a166207..21b2f534 100644 --- a/app/modules/api/Controllers/Config/ExportController.php +++ b/app/modules/api/Controllers/Config/ExportController.php @@ -35,7 +35,8 @@ use SP\Domain\Api\Ports\ApiService; use SP\Domain\Core\Acl\AclActionsInterface; use SP\Domain\Core\Acl\AclInterface; use SP\Domain\Core\Exceptions\InvalidClassException; -use SP\Domain\Export\Ports\XmlExportServiceInterface; +use SP\Domain\Export\Ports\XmlExportService; +use SP\Infrastructure\File\DirectoryHandler; use SP\Modules\Api\Controllers\ControllerBase; use SP\Modules\Api\Controllers\Help\ConfigHelp; @@ -44,17 +45,17 @@ use SP\Modules\Api\Controllers\Help\ConfigHelp; */ final class ExportController extends ControllerBase { - private XmlExportServiceInterface $xmlExportService; + private XmlExportService $xmlExportService; /** * @throws InvalidClassException */ public function __construct( - Application $application, - Klein $router, - ApiService $apiService, - AclInterface $acl, - XmlExportServiceInterface $xmlExportService + Application $application, + Klein $router, + ApiService $apiService, + AclInterface $acl, + XmlExportService $xmlExportService ) { parent::__construct($application, $router, $apiService, $acl); @@ -84,7 +85,7 @@ final class ExportController extends ControllerBase ) ); - $this->xmlExportService->doExport($path, $password); + $file = $this->xmlExportService->export(new DirectoryHandler($path), $password); $this->eventDispatcher->notify( @@ -92,7 +93,7 @@ final class ExportController extends ControllerBase new Event($this, EventMessage::factory()->addDescription(__u('Export process finished'))) ); - $exportFiles = ['files' => ['xml' => $this->xmlExportService->getExportFile()]]; + $exportFiles = ['files' => ['xml' => $file]]; $this->returnResponse(ApiResponse::makeSuccess($exportFiles, null, __('Export process finished'))); } catch (Exception $e) { diff --git a/app/modules/web/Controllers/ConfigBackup/DownloadExportController.php b/app/modules/web/Controllers/ConfigBackup/DownloadExportController.php index 25e27595..dd241b0d 100644 --- a/app/modules/web/Controllers/ConfigBackup/DownloadExportController.php +++ b/app/modules/web/Controllers/ConfigBackup/DownloadExportController.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2024, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -33,7 +33,7 @@ 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\Export\Services\XmlExportService; +use SP\Domain\Export\Services\XmlExport; use SP\Infrastructure\File\FileHandler; use SP\Modules\Web\Controllers\SimpleControllerBase; use SP\Modules\Web\Controllers\Traits\JsonTrait; @@ -53,7 +53,7 @@ final class DownloadExportController extends SimpleControllerBase try { SessionContext::close(); - $filePath = XmlExportService::getExportFilename( + $filePath = XmlExport::buildFilename( BACKUP_PATH, $this->configData->getExportHash(), true diff --git a/app/modules/web/Controllers/ConfigBackup/XmlExportController.php b/app/modules/web/Controllers/ConfigBackup/XmlExportController.php index 85d8098c..cf81d776 100644 --- a/app/modules/web/Controllers/ConfigBackup/XmlExportController.php +++ b/app/modules/web/Controllers/ConfigBackup/XmlExportController.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2024, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -33,9 +33,10 @@ 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\Export\Ports\XmlExportServiceInterface; +use SP\Domain\Export\Ports\XmlExportService; use SP\Domain\Export\Ports\XmlVerifyServiceInterface; use SP\Http\JsonMessage; +use SP\Infrastructure\File\DirectoryHandler; use SP\Modules\Web\Controllers\SimpleControllerBase; use SP\Modules\Web\Controllers\Traits\JsonTrait; use SP\Mvc\Controller\SimpleControllerHelper; @@ -47,13 +48,13 @@ final class XmlExportController extends SimpleControllerBase { use JsonTrait; - private XmlExportServiceInterface $xmlExportService; + private XmlExportService $xmlExportService; private XmlVerifyServiceInterface $xmlVerifyService; public function __construct( - Application $application, + Application $application, SimpleControllerHelper $simpleControllerHelper, - XmlExportServiceInterface $xmlExportService, + XmlExportService $xmlExportService, XmlVerifyServiceInterface $xmlVerifyService ) { parent::__construct($application, $simpleControllerHelper); @@ -83,21 +84,21 @@ final class XmlExportController extends SimpleControllerBase SessionContext::close(); - $this->xmlExportService->doExport(BACKUP_PATH, $exportPassword); + $file = $this->xmlExportService->export(new DirectoryHandler(BACKUP_PATH), $exportPassword); $this->eventDispatcher->notify( 'run.export.end', new Event($this, EventMessage::factory()->addDescription(__u('Export process finished'))) ); - if ($this->xmlExportService->isEncrypted()) { + if (!empty($exportPassword)) { $verifyResult = $this->xmlVerifyService->verifyEncrypted( - $this->xmlExportService->getExportFile(), + $file, $exportPassword ); } else { - $verifyResult = $this->xmlVerifyService->verify($this->xmlExportService->getExportFile()); + $verifyResult = $this->xmlVerifyService->verify($file); } $nodes = $verifyResult->getNodes(); @@ -118,7 +119,7 @@ final class XmlExportController extends SimpleControllerBase ); // Create the XML archive after verifying the export integrity - $this->xmlExportService->createArchive(); + $this->xmlExportService->createArchiveFor($file); return $this->returnJsonResponse(JsonMessage::JSON_SUCCESS, __u('Export process finished')); } catch (Exception $e) { diff --git a/app/modules/web/Controllers/ConfigManager/IndexController.php b/app/modules/web/Controllers/ConfigManager/IndexController.php index 8648d1ec..3222ea22 100644 --- a/app/modules/web/Controllers/ConfigManager/IndexController.php +++ b/app/modules/web/Controllers/ConfigManager/IndexController.php @@ -46,7 +46,7 @@ use SP\Domain\Core\File\MimeType; use SP\Domain\Core\File\MimeTypesService; use SP\Domain\Crypt\Services\TemporaryMasterPass; use SP\Domain\Export\Services\BackupFileHelper; -use SP\Domain\Export\Services\XmlExportService; +use SP\Domain\Export\Services\XmlExport; use SP\Domain\Task\Services\Task; use SP\Domain\User\Ports\UserGroupServiceInterface; use SP\Domain\User\Ports\UserProfileServiceInterface; @@ -479,7 +479,7 @@ final class IndexController extends ControllerBase ) ); $exportFile = new FileHandler( - XmlExportService::getExportFilename( + XmlExport::buildFilename( BACKUP_PATH, $this->configData->getExportHash() ?: '', true diff --git a/lib/SP/Domain/Export/Ports/XmlAccountExportService.php b/lib/SP/Domain/Export/Ports/XmlAccountExportService.php new file mode 100644 index 00000000..c292d48b --- /dev/null +++ b/lib/SP/Domain/Export/Ports/XmlAccountExportService.php @@ -0,0 +1,42 @@ +. + */ + +namespace SP\Domain\Export\Ports; + +use DOMDocument; +use DOMElement; +use SP\Domain\Common\Services\ServiceException; + +/** + * Interface XmlAccountExportService + */ +interface XmlAccountExportService +{ + /** + * Build the node with accounts + * + * @throws ServiceException + */ + public function export(DOMDocument $document): DOMElement; +} diff --git a/lib/SP/Domain/Export/Ports/XmlCategoryExportService.php b/lib/SP/Domain/Export/Ports/XmlCategoryExportService.php new file mode 100644 index 00000000..df6684a3 --- /dev/null +++ b/lib/SP/Domain/Export/Ports/XmlCategoryExportService.php @@ -0,0 +1,42 @@ +. + */ + +namespace SP\Domain\Export\Ports; + +use DOMDocument; +use DOMElement; +use SP\Domain\Common\Services\ServiceException; + +/** + * Interface XmlCategoryExportService + */ +interface XmlCategoryExportService +{ + /** + * Build the node with categories + * + * @throws ServiceException + */ + public function export(DOMDocument $document): DOMElement; +} diff --git a/lib/SP/Domain/Export/Ports/XmlClientExportService.php b/lib/SP/Domain/Export/Ports/XmlClientExportService.php new file mode 100644 index 00000000..568690e8 --- /dev/null +++ b/lib/SP/Domain/Export/Ports/XmlClientExportService.php @@ -0,0 +1,43 @@ +. + */ + +namespace SP\Domain\Export\Ports; + +use DOMDocument; +use DOMElement; +use SP\Domain\Common\Services\ServiceException; + +/** + * Interface XmlClientExportService + */ +interface XmlClientExportService +{ + /** + * Build the node with clients + * + * @throws ServiceException + * @throws ServiceException + */ + public function export(DOMDocument $document): DOMElement; +} diff --git a/lib/SP/Domain/Export/Ports/XmlExportServiceInterface.php b/lib/SP/Domain/Export/Ports/XmlExportService.php similarity index 62% rename from lib/SP/Domain/Export/Ports/XmlExportServiceInterface.php rename to lib/SP/Domain/Export/Ports/XmlExportService.php index fa789aa5..5aa50c78 100644 --- a/lib/SP/Domain/Export/Ports/XmlExportServiceInterface.php +++ b/lib/SP/Domain/Export/Ports/XmlExportService.php @@ -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. * @@ -26,33 +26,30 @@ namespace SP\Domain\Export\Ports; use SP\Domain\Common\Services\ServiceException; use SP\Domain\Core\Exceptions\CheckException; +use SP\Domain\File\Ports\DirectoryHandlerService; use SP\Infrastructure\File\FileException; /** - * Clase XmlExport para realizar la exportación de las cuentas de sysPass a formato XML - * - * @package SP + * Interface XmlExportServiceInterface */ -interface XmlExportServiceInterface +interface XmlExportService { /** - * Realiza la exportación de las cuentas a XML + * Export the accounts and related objects into a XML file * - * @param string $exportPath - * @param string|null $pass La clave de exportación + * @param DirectoryHandlerService $exportPath The path where to store the exported file + * @param string|null $password The password to encrypt the exported data * + * @return string The exported file * @throws ServiceException * @throws FileException */ - public function doExport(string $exportPath, ?string $pass = null): void; + public function export(DirectoryHandlerService $exportPath, ?string $password = null): string; /** + * @return string The path to the archive file * @throws CheckException * @throws FileException */ - public function createArchive(): void; - - public function getExportFile(): string; - - public function isEncrypted(): bool; + public function createArchiveFor(string $file): string; } diff --git a/lib/SP/Domain/Export/Ports/XmlTagExportService.php b/lib/SP/Domain/Export/Ports/XmlTagExportService.php new file mode 100644 index 00000000..5cc986f7 --- /dev/null +++ b/lib/SP/Domain/Export/Ports/XmlTagExportService.php @@ -0,0 +1,42 @@ +. + */ + +namespace SP\Domain\Export\Ports; + +use DOMDocument; +use DOMElement; +use SP\Domain\Common\Services\ServiceException; + +/** + * Interface XmlTagExportService + */ +interface XmlTagExportService +{ + /** + * Build the node with tags + * + * @throws ServiceException + */ + public function export(DOMDocument $document): DOMElement; +} diff --git a/lib/SP/Domain/Export/Services/XmlAccountExport.php b/lib/SP/Domain/Export/Services/XmlAccountExport.php new file mode 100644 index 00000000..3762f8ad --- /dev/null +++ b/lib/SP/Domain/Export/Services/XmlAccountExport.php @@ -0,0 +1,133 @@ +. + */ + +namespace SP\Domain\Export\Services; + +use DOMDocument; +use DOMElement; +use Exception; +use SP\Core\Application; +use SP\Core\Events\Event; +use SP\Core\Events\EventMessage; +use SP\Domain\Account\Ports\AccountService; +use SP\Domain\Account\Ports\AccountToTagService; +use SP\Domain\Common\Services\Service; +use SP\Domain\Common\Services\ServiceException; +use SP\Domain\Export\Ports\XmlAccountExportService; + +use function SP\__u; + +/** + * Class XmlAccountExport + */ +final class XmlAccountExport extends Service implements XmlAccountExportService +{ + public function __construct( + Application $application, + private readonly AccountService $accountService, + private readonly AccountToTagService $accountToTagService, + + ) { + parent::__construct($application); + } + + /** + * Crear el nodo con los datos + * + * @throws ServiceException + */ + public function export(DOMDocument $document): DOMElement + { + try { + $this->eventDispatcher->notify( + 'run.export.process.account', + new Event($this, EventMessage::factory()->addDescription(__u('Exporting accounts'))) + ); + + $accounts = $this->accountService->getAllBasic(); + + // Crear el nodo de cuentas + $nodeAccounts = $document->createElement('Accounts'); + + if ($nodeAccounts === false) { + throw ServiceException::error(__u('Unable to create node')); + } + + if (count($accounts) === 0) { + return $nodeAccounts; + } + + foreach ($accounts as $account) { + $accountName = $document->createElement( + 'name', + $document->createTextNode($account->getName())->nodeValue + ); + $accountCustomerId = $document->createElement('clientId', $account->getClientId()); + $accountCategoryId = $document->createElement('categoryId', $account->getCategoryId()); + $accountLogin = $document->createElement( + 'login', + $document->createTextNode($account->getLogin())->nodeValue + ); + $accountUrl = $document->createElement('url', $document->createTextNode($account->getUrl())->nodeValue); + $accountNotes = $document->createElement( + 'notes', + $document->createTextNode($account->getNotes())->nodeValue + ); + $accountPass = $document->createElement( + 'pass', + $document->createTextNode($account->getPass())->nodeValue + ); + $accountIV = $document->createElement('key', $document->createTextNode($account->getKey())->nodeValue); + $tags = $document->createElement('tags'); + + foreach ($this->accountToTagService->getTagsByAccountId($account->getId()) as $itemData) { + $tag = $document->createElement('tag'); + $tags->appendChild($tag); + + $tag->setAttribute('id', $itemData->getId()); + } + + // Crear el nodo de cuenta + $nodeAccount = $document->createElement('Account'); + $nodeAccount->setAttribute('id', $account->getId()); + $nodeAccount->appendChild($accountName); + $nodeAccount->appendChild($accountCustomerId); + $nodeAccount->appendChild($accountCategoryId); + $nodeAccount->appendChild($accountLogin); + $nodeAccount->appendChild($accountUrl); + $nodeAccount->appendChild($accountNotes); + $nodeAccount->appendChild($accountPass); + $nodeAccount->appendChild($accountIV); + $nodeAccount->appendChild($tags); + + // Añadir cuenta al nodo de cuentas + $nodeAccounts->appendChild($nodeAccount); + } + + return $nodeAccounts; + } catch (Exception $e) { + throw ServiceException::error($e->getMessage(), __FUNCTION__); + } + } +} diff --git a/lib/SP/Domain/Export/Services/XmlCategoryExport.php b/lib/SP/Domain/Export/Services/XmlCategoryExport.php new file mode 100644 index 00000000..5e4ff79e --- /dev/null +++ b/lib/SP/Domain/Export/Services/XmlCategoryExport.php @@ -0,0 +1,99 @@ +. + */ + +namespace SP\Domain\Export\Services; + +use DOMDocument; +use DOMElement; +use Exception; +use SP\Core\Application; +use SP\Core\Events\Event; +use SP\Core\Events\EventMessage; +use SP\Domain\Category\Ports\CategoryService; +use SP\Domain\Common\Services\Service; +use SP\Domain\Common\Services\ServiceException; +use SP\Domain\Export\Ports\XmlCategoryExportService; + +use function SP\__u; + +/** + * Class XmlCategoryExport + */ +final class XmlCategoryExport extends Service implements XmlCategoryExportService +{ + public function __construct( + Application $application, + private readonly CategoryService $categoryService, + + ) { + parent::__construct($application); + } + + /** + * Crear el nodo con los datos + * + * @throws ServiceException + */ + public function export(DOMDocument $document): DOMElement + { + try { + $this->eventDispatcher->notify( + 'run.export.process.category', + new Event($this, EventMessage::factory()->addDescription(__u('Exporting categories'))) + ); + + $categories = $this->categoryService->getAll(); + + $nodeCategories = $document->createElement('Categories'); + + if ($nodeCategories === false) { + throw ServiceException::error(__u('Unable to create node')); + } + + if (count($categories) === 0) { + return $nodeCategories; + } + + foreach ($categories as $category) { + $nodeCategory = $document->createElement('Category'); + $nodeCategory->setAttribute('id', $category->getId()); + $nodeCategory->appendChild( + $document->createElement('name', XmlUtil::escapeChars($category->getName())) + ); + $nodeCategory->appendChild( + $document->createElement( + 'description', + XmlUtil::escapeChars($category->getDescription()) + ) + ); + + $nodeCategories->appendChild($nodeCategory); + } + + return $nodeCategories; + } catch (Exception $e) { + throw ServiceException::error($e->getMessage(), __FUNCTION__); + } + } +} diff --git a/lib/SP/Domain/Export/Services/XmlClientExport.php b/lib/SP/Domain/Export/Services/XmlClientExport.php new file mode 100644 index 00000000..207c537e --- /dev/null +++ b/lib/SP/Domain/Export/Services/XmlClientExport.php @@ -0,0 +1,95 @@ +. + */ + +namespace SP\Domain\Export\Services; + +use DOMDocument; +use DOMElement; +use Exception; +use SP\Core\Application; +use SP\Core\Events\Event; +use SP\Core\Events\EventMessage; +use SP\Domain\Client\Ports\ClientService; +use SP\Domain\Common\Services\Service; +use SP\Domain\Common\Services\ServiceException; +use SP\Domain\Export\Ports\XmlClientExportService; + +use function SP\__u; + +/** + * Class XmlClientExport + */ +final class XmlClientExport extends Service implements XmlClientExportService +{ + public function __construct( + Application $application, + private readonly ClientService $clientService, + + ) { + parent::__construct($application); + } + + /** + * Crear el nodo con los datos + * + * @throws ServiceException + * @throws ServiceException + */ + public function export(DOMDocument $document): DOMElement + { + try { + $this->eventDispatcher->notify( + 'run.export.process.client', + new Event($this, EventMessage::factory()->addDescription(__u('Exporting clients'))) + ); + + $clients = $this->clientService->getAll(); + + $nodeClients = $document->createElement('Clients'); + + if ($nodeClients === false) { + throw ServiceException::error(__u('Unable to create node')); + } + + if (count($clients) === 0) { + return $nodeClients; + } + + foreach ($clients as $client) { + $nodeClient = $document->createElement('Client'); + $nodeClient->setAttribute('id', $client->getId()); + $nodeClient->appendChild($document->createElement('name', XmlUtil::escapeChars($client->getName()))); + $nodeClient->appendChild( + $document->createElement('description', XmlUtil::escapeChars($client->getDescription())) + ); + + $nodeClients->appendChild($nodeClient); + } + + return $nodeClients; + } catch (Exception $e) { + throw ServiceException::error($e->getMessage(), __FUNCTION__); + } + } +} diff --git a/lib/SP/Domain/Export/Services/XmlExport.php b/lib/SP/Domain/Export/Services/XmlExport.php new file mode 100644 index 00000000..46d1ad7a --- /dev/null +++ b/lib/SP/Domain/Export/Services/XmlExport.php @@ -0,0 +1,268 @@ +. + */ + +namespace SP\Domain\Export\Services; + +use DOMDocument; +use DOMElement; +use Exception; +use SP\Core\Application; +use SP\Core\Crypt\Hash; +use SP\Domain\Common\Services\Service; +use SP\Domain\Common\Services\ServiceException; +use SP\Domain\Config\Ports\ConfigDataInterface; +use SP\Domain\Core\AppInfoInterface; +use SP\Domain\Core\Crypt\CryptInterface; +use SP\Domain\Core\Exceptions\CheckException; +use SP\Domain\Core\PhpExtensionCheckerService; +use SP\Domain\Export\Ports\XmlAccountExportService; +use SP\Domain\Export\Ports\XmlCategoryExportService; +use SP\Domain\Export\Ports\XmlClientExportService; +use SP\Domain\Export\Ports\XmlExportService; +use SP\Domain\Export\Ports\XmlTagExportService; +use SP\Domain\File\Ports\DirectoryHandlerService; +use SP\Infrastructure\File\ArchiveHandler; +use SP\Infrastructure\File\FileException; +use SP\Util\FileUtil; +use SP\Util\VersionUtil; + +use function SP\__u; + +/** + * Class XmlExportService + */ +final class XmlExport extends Service implements XmlExportService +{ + use XmlTrait; + + private ConfigDataInterface $configData; + private DOMDocument $xml; + private DOMElement $root; + + /** + * @throws ServiceException + */ + public function __construct( + Application $application, + private readonly PhpExtensionCheckerService $extensionChecker, + private readonly XmlClientExportService $xmlClientExportService, + private readonly XmlAccountExportService $xmlAccountExportService, + private readonly XmlCategoryExportService $xmlCategoryExportService, + private readonly XmlTagExportService $xmlTagExportService, + private readonly CryptInterface $crypt + ) { + parent::__construct($application); + + $this->configData = $this->config->getConfigData(); + + $this->createRoot(); + } + + /** + * @throws ServiceException + */ + private function createRoot(): void + { + try { + $this->xml = new DOMDocument('1.0', 'UTF-8'); + $this->xml->formatOutput = true; + $this->xml->preserveWhiteSpace = false; + $this->root = $this->xml->appendChild($this->xml->createElement('Root')); + } catch (Exception $e) { + throw ServiceException::error($e->getMessage(), __FUNCTION__); + } + } + + /** + * @inheritDoc + * @throws CheckException + */ + public function export(DirectoryHandlerService $exportPath, ?string $password = null): string + { + set_time_limit(0); + + $exportPath->checkOrCreate(); + + self::deleteExportFiles($exportPath->getPath()); + + $file = self::buildFilename($exportPath->getPath(), $this->buildAndSaveHashForFile()); + $this->buildAndSaveXml($file, $password); + + return $file; + } + + private static function deleteExportFiles(string $path): void + { + $path = FileUtil::buildPath($path, AppInfoInterface::APP_NAME); + + array_map( + static fn($file) => @unlink($file), + array_merge(glob($path . '_export-*'), glob($path . '*.xml')) + ); + } + + public static function buildFilename(string $path, string $hash, bool $compressed = false): string + { + $file = sprintf('%s%s%s_export-%s', $path, DIRECTORY_SEPARATOR, AppInfoInterface::APP_NAME, $hash); + + if ($compressed) { + return $file . ArchiveHandler::COMPRESS_EXTENSION; + } + + return sprintf('%s.xml', $file); + } + + /** + * @throws FileException + */ + private function buildAndSaveHashForFile(): string + { + $hash = sha1(uniqid('sysPassExport', true)); + $this->configData->setExportHash($hash); + $this->config->save($this->configData); + + return $hash; + } + + /** + * @throws ServiceException + */ + private function buildAndSaveXml(string $file, ?string $password = null): void + { + try { + $this->appendMeta(); + $this->appendNode($this->xmlCategoryExportService->export($this->xml), $password); + $this->appendNode($this->xmlClientExportService->export($this->xml), $password); + $this->appendNode($this->xmlTagExportService->export($this->xml), $password); + $this->appendNode($this->xmlAccountExportService->export($this->xml), $password); + $this->appendHash($password); + + if (!$this->xml->save($file)) { + throw ServiceException::error(__u('Error while creating the XML file')); + } + } catch (ServiceException $e) { + throw $e; + } catch (Exception $e) { + throw ServiceException::error( + __u('Error while exporting'), + __u('Please check out the event log for more details'), + $e->getCode(), + $e + ); + } + } + + /** + * @throws ServiceException + */ + private function appendMeta(): void + { + try { + $userData = $this->context->getUserData(); + + $nodeMeta = $this->xml->createElement('Meta'); + + $nodeMeta->appendChild($this->xml->createElement('Generator', 'sysPass')); + $nodeMeta->appendChild($this->xml->createElement('Version', VersionUtil::getVersionStringNormalized())); + $nodeMeta->appendChild($this->xml->createElement('Time', time())); + $nodeMeta->appendChild($this->xml->createElement('User', $userData->getLogin())); + $nodeMeta->appendChild($this->xml->createElement('Group', $userData->getUserGroupName())); + + $this->root->appendChild($nodeMeta); + } catch (Exception $e) { + throw ServiceException::error($e->getMessage(), __FUNCTION__); + } + } + + /** + * @throws ServiceException + */ + private function appendNode(DOMElement $node, ?string $password = null): void + { + try { + if (!empty($password)) { + $securedKey = $this->crypt->makeSecuredKey($password, false); + $encrypted = $this->crypt->encrypt($this->xml->saveXML($node), $securedKey->unlockKey($password)); + + $encryptedData = $this->xml->createElement('Data', $encrypted); + + $encryptedDataKey = $this->xml->createAttribute('key'); + $encryptedDataKey->value = $securedKey->saveToAsciiSafeString(); + + $encryptedData->appendChild($encryptedDataKey); + + $encryptedNode = $this->root->getElementsByTagName('Encrypted'); + + if ($encryptedNode->length === 0) { + $newNode = $this->xml->createElement('Encrypted'); + $newNode->setAttribute('hash', Hash::hashKey($password)); + } else { + $newNode = $encryptedNode->item(0); + } + + $newNode->appendChild($encryptedData); + + // Añadir el nodo encriptado + $this->root->appendChild($newNode); + } else { + $this->root->appendChild($node); + } + } catch (Exception $e) { + throw ServiceException::error($e->getMessage(), __FUNCTION__); + } + } + + /** + * @throws ServiceException + */ + private function appendHash(?string $password = null): void + { + try { + $hash = self::generateHashFromNodes($this->xml); + + $hashNode = $this->xml->createElement('Hash', $hash); + $hashNode->appendChild($this->xml->createAttribute('sign')); + + $key = $password ?: sha1($this->configData->getPasswordSalt()); + + $hashNode->setAttribute('sign', Hash::signMessage($hash, $key)); + + $this->root + ->getElementsByTagName('Meta') + ->item(0) + ->appendChild($hashNode); + } catch (Exception $e) { + throw ServiceException::error($e->getMessage(), __FUNCTION__); + } + } + + /** + * @throws FileException + */ + public function createArchiveFor(string $file): string + { + $archive = new ArchiveHandler($file, $this->extensionChecker); + return $archive->compressFile($file); + } +} diff --git a/lib/SP/Domain/Export/Services/XmlExportService.php b/lib/SP/Domain/Export/Services/XmlExportService.php deleted file mode 100644 index 200815df..00000000 --- a/lib/SP/Domain/Export/Services/XmlExportService.php +++ /dev/null @@ -1,606 +0,0 @@ -. - */ - -namespace SP\Domain\Export\Services; - -use DOMDocument; -use DOMElement; -use DOMXPath; -use Exception; -use SP\Core\Application; -use SP\Core\Crypt\Crypt; -use SP\Core\Crypt\Hash; -use SP\Core\Events\Event; -use SP\Core\Events\EventMessage; -use SP\Core\PhpExtensionChecker; -use SP\Domain\Account\Ports\AccountService; -use SP\Domain\Account\Ports\AccountToTagService; -use SP\Domain\Category\Models\Category; -use SP\Domain\Category\Ports\CategoryService; -use SP\Domain\Client\Ports\ClientService; -use SP\Domain\Common\Services\Service; -use SP\Domain\Common\Services\ServiceException; -use SP\Domain\Config\Ports\ConfigDataInterface; -use SP\Domain\Core\AppInfoInterface; -use SP\Domain\Core\Exceptions\CheckException; -use SP\Domain\Core\Exceptions\SPException; -use SP\Domain\Export\Ports\XmlExportServiceInterface; -use SP\Domain\Tag\Ports\TagServiceInterface; -use SP\Infrastructure\File\ArchiveHandler; -use SP\Infrastructure\File\FileException; -use SP\Infrastructure\File\FileHandler; -use SP\Util\VersionUtil; - -defined('APP_ROOT') || die(); - -/** - * Clase XmlExport para realizar la exportación de las cuentas de sysPass a formato XML - * - * @package SP - */ -final class XmlExportService extends Service implements XmlExportServiceInterface -{ - private ConfigDataInterface $configData; - private PhpExtensionChecker $extensionChecker; - private ClientService $clientService; - private AccountService $accountService; - private AccountToTagService $accountToTagService; - private CategoryService $categoryService; - private TagServiceInterface $tagService; - private ?DOMDocument $xml = null; - private ?DOMElement $root = null; - private ?string $exportPass = null; - private bool $encrypted = false; - private ?string $exportPath = null; - private ?string $exportFile = null; - - public function __construct( - Application $application, - PhpExtensionChecker $extensionChecker, - ClientService $clientService, - AccountService $accountService, - AccountToTagService $accountToTagService, - CategoryService $categoryService, - TagServiceInterface $tagService - - ) { - parent::__construct($application); - - $this->extensionChecker = $extensionChecker; - $this->clientService = $clientService; - $this->accountService = $accountService; - $this->accountToTagService = $accountToTagService; - $this->categoryService = $categoryService; - $this->tagService = $tagService; - $this->configData = $this->config->getConfigData(); - } - - - /** - * Realiza la exportación de las cuentas a XML - * - * @param string $exportPath - * @param string|null $pass La clave de exportación - * - * @throws ServiceException - * @throws FileException - */ - public function doExport(string $exportPath, ?string $pass = null): void - { - set_time_limit(0); - - if (!empty($pass)) { - $this->exportPass = $pass; - $this->encrypted = true; - } - - $this->setExportPath($exportPath); - $this->exportFile = $this->generateExportFilename(); - $this->deleteOldExports(); - $this->makeXML(); - } - - /** - * @throws ServiceException - */ - private function setExportPath(string $exportPath): void - { - if (!is_dir($exportPath) - && !@mkdir($exportPath, 0700, true) - && !is_dir($exportPath) - ) { - throw new ServiceException(sprintf(__('Unable to create the directory (%s)'), $exportPath)); - } - - $this->exportPath = $exportPath; - } - - /** - * Genera el nombre del archivo usado para la exportación. - * - * @throws FileException - */ - private function generateExportFilename(): string - { - // Generar hash unico para evitar descargas no permitidas - $hash = sha1(uniqid('sysPassExport', true)); - $this->configData->setExportHash($hash); - $this->config->save($this->configData); - - return self::getExportFilename($this->exportPath, $hash); - } - - public static function getExportFilename(string $path, string $hash, bool $compressed = false): string - { - $file = sprintf('%s%s%s_export-%s', $path, DIRECTORY_SEPARATOR, AppInfoInterface::APP_NAME, $hash); - - if ($compressed) { - return $file.ArchiveHandler::COMPRESS_EXTENSION; - } - - return sprintf('%s.xml', $file); - } - - /** - * Eliminar los archivos de exportación anteriores - */ - private function deleteOldExports(): void - { - $path = $this->exportPath.DIRECTORY_SEPARATOR.AppInfoInterface::APP_NAME; - - array_map( - static fn($file) => @unlink($file), - array_merge(glob($path.'_export-*'), glob($path.'*.xml')) - ); - } - - /** - * Crear el documento XML y guardarlo - * - * @throws ServiceException - */ - private function makeXML(): void - { - try { - $this->createRoot(); - $this->createMeta(); - $this->createCategories(); - $this->createClients(); - $this->createTags(); - $this->createAccounts(); - $this->createHash(); - $this->writeXML(); - } catch (ServiceException $e) { - throw $e; - } catch (Exception $e) { - throw new ServiceException( - __u('Error while exporting'), - SPException::ERROR, - __u('Please check out the event log for more details'), - $e->getCode(), - $e - ); - } - } - - /** - * Crear el nodo raíz - * - * @throws ServiceException - */ - private function createRoot(): void - { - try { - $this->xml = new DOMDocument('1.0', 'UTF-8'); - $this->root = $this->xml->appendChild($this->xml->createElement('Root')); - } catch (Exception $e) { - throw new ServiceException($e->getMessage(), SPException::ERROR, __FUNCTION__); - } - } - - /** - * Crear el nodo con metainformación del archivo XML - * - * @throws ServiceException - */ - private function createMeta(): void - { - try { - $userData = $this->context->getUserData(); - - $nodeMeta = $this->xml->createElement('Meta'); - $metaGenerator = $this->xml->createElement('Generator', 'sysPass'); - $metaVersion = $this->xml->createElement('Version', VersionUtil::getVersionStringNormalized()); - $metaTime = $this->xml->createElement('Time', time()); - $metaUser = $this->xml->createElement('User', $userData->getLogin()); - $metaUser->setAttribute('id', $userData->getId()); - $metaGroup = $this->xml->createElement('Group', $userData->getUserGroupName()); - $metaGroup->setAttribute('id', $userData->getUserGroupId()); - - $nodeMeta->appendChild($metaGenerator); - $nodeMeta->appendChild($metaVersion); - $nodeMeta->appendChild($metaTime); - $nodeMeta->appendChild($metaUser); - $nodeMeta->appendChild($metaGroup); - - $this->root->appendChild($nodeMeta); - } catch (Exception $e) { - throw new ServiceException($e->getMessage(), SPException::ERROR, __FUNCTION__); - } - } - - /** - * Crear el nodo con los datos de las categorías - * - * @throws ServiceException - */ - private function createCategories(): void - { - try { - $this->eventDispatcher->notify( - 'run.export.process.category', - new Event($this, EventMessage::factory()->addDescription(__u('Exporting categories'))) - ); - - $categories = $this->categoryService->getAll(); - - // Crear el nodo de categorías - $nodeCategories = $this->xml->createElement('Categories'); - - if (count($categories) === 0) { - $this->appendNode($nodeCategories); - - return; - } - - foreach ($categories as $category) { - /** @var $category Category */ - $categoryName = $this->xml->createElement('name', $this->escapeChars($category->getName())); - $categoryDescription = $this->xml->createElement( - 'description', - $this->escapeChars($category->getDescription()) - ); - - // Crear el nodo de categoría - $nodeCategory = $this->xml->createElement('Category'); - $nodeCategory->setAttribute('id', $category->getId()); - $nodeCategory->appendChild($categoryName); - $nodeCategory->appendChild($categoryDescription); - - // Añadir categoría al nodo de categorías - $nodeCategories->appendChild($nodeCategory); - } - - $this->appendNode($nodeCategories); - } catch (Exception $e) { - throw new ServiceException($e->getMessage(), SPException::ERROR, __FUNCTION__); - } - } - - /** - * Añadir un nuevo nodo al árbol raíz - * - * @param DOMElement $node El nodo a añadir - * - * @throws ServiceException - */ - private function appendNode(DOMElement $node): void - { - try { - // Si se utiliza clave de encriptación los datos se encriptan en un nuevo nodo: - // Encrypted -> Data - if ($this->encrypted === true) { - // Obtener el nodo en formato XML - $nodeXML = $this->xml->saveXML($node); - - // Crear los datos encriptados con la información del nodo - $securedKey = Crypt::makeSecuredKey($this->exportPass, false); - $encrypted = Crypt::encrypt($nodeXML, $securedKey->unlockKey($this->exportPass)); - - // Crear el nodo hijo con los datos encriptados - $encryptedData = $this->xml->createElement('Data', $encrypted); - - $encryptedDataKey = $this->xml->createAttribute('key'); - $encryptedDataKey->value = $securedKey->saveToAsciiSafeString(); - - // Añadir nodos de datos - $encryptedData->appendChild($encryptedDataKey); - - // Buscar si existe ya un nodo para el conjunto de datos encriptados - $encryptedNode = $this->root->getElementsByTagName('Encrypted'); - - if ($encryptedNode->length === 0) { - $newNode = $this->xml->createElement('Encrypted'); - $newNode->setAttribute('hash', Hash::hashKey($this->exportPass)); - } else { - $newNode = $encryptedNode->item(0); - } - - $newNode->appendChild($encryptedData); - - // Añadir el nodo encriptado - $this->root->appendChild($newNode); - } else { - $this->root->appendChild($node); - } - } catch (Exception $e) { - throw new ServiceException($e->getMessage(), SPException::ERROR, __FUNCTION__); - } - } - - /** - * Escapar carácteres no válidos en XML - * - * @param $data string Los datos a escapar - * - * @return string - */ - private function escapeChars(string $data): string - { - $arrStrFrom = ['&', '<', '>', '"', '\'']; - $arrStrTo = ['&', '<', '>', '"', ''']; - - return str_replace($arrStrFrom, $arrStrTo, $data); - } - - /** - * Crear el nodo con los datos de los clientes - * - * @throws ServiceException - * @throws ServiceException - */ - private function createClients(): void - { - try { - $this->eventDispatcher->notify( - 'run.export.process.client', - new Event($this, EventMessage::factory()->addDescription(__u('Exporting clients'))) - ); - - $clients = $this->clientService->getAll(); - - // Crear el nodo de clientes - $nodeClients = $this->xml->createElement('Clients'); - - if (count($clients) === 0) { - $this->appendNode($nodeClients); - - return; - } - - foreach ($clients as $client) { - $clientName = $this->xml->createElement('name', $this->escapeChars($client->getName())); - $clientDescription = - $this->xml->createElement('description', $this->escapeChars($client->getDescription())); - - // Crear el nodo de clientes - $nodeClient = $this->xml->createElement('Client'); - $nodeClient->setAttribute('id', $client->getId()); - $nodeClient->appendChild($clientName); - $nodeClient->appendChild($clientDescription); - - // Añadir cliente al nodo de clientes - $nodeClients->appendChild($nodeClient); - } - - $this->appendNode($nodeClients); - } catch (Exception $e) { - throw new ServiceException($e->getMessage(), SPException::ERROR, __FUNCTION__); - } - } - - /** - * Crear el nodo con los datos de las etiquetas - * - * @throws ServiceException - */ - private function createTags(): void - { - try { - $this->eventDispatcher->notify( - 'run.export.process.tag', - new Event($this, EventMessage::factory()->addDescription(__u('Exporting tags'))) - ); - - $tags = $this->tagService->getAll(); - - // Crear el nodo de etiquetas - $nodeTags = $this->xml->createElement('Tags'); - - if (count($tags) === 0) { - $this->appendNode($nodeTags); - - return; - } - - foreach ($tags as $tag) { - $tagName = $this->xml->createElement('name', $this->escapeChars($tag->getName())); - - // Crear el nodo de etiquetas - $nodeTag = $this->xml->createElement('Tag'); - $nodeTag->setAttribute('id', $tag->getId()); - $nodeTag->appendChild($tagName); - - // Añadir etiqueta al nodo de etiquetas - $nodeTags->appendChild($nodeTag); - } - - $this->appendNode($nodeTags); - } catch (Exception $e) { - throw new ServiceException($e->getMessage(), SPException::ERROR, __FUNCTION__); - } - } - - /** - * Crear el nodo con los datos de las cuentas - * - * @throws ServiceException - */ - private function createAccounts(): void - { - try { - $this->eventDispatcher->notify( - 'run.export.process.account', - new Event($this, EventMessage::factory()->addDescription(__u('Exporting accounts'))) - ); - - $accounts = $this->accountService->getAllBasic(); - - // Crear el nodo de cuentas - $nodeAccounts = $this->xml->createElement('Accounts'); - - if (count($accounts) === 0) { - $this->appendNode($nodeAccounts); - - return; - } - - foreach ($accounts as $account) { - $accountName = $this->xml->createElement('name', $this->escapeChars($account->getName())); - $accountCustomerId = $this->xml->createElement('clientId', $account->getClientId()); - $accountCategoryId = $this->xml->createElement('categoryId', $account->getCategoryId()); - $accountLogin = $this->xml->createElement('login', $this->escapeChars($account->getLogin())); - $accountUrl = $this->xml->createElement('url', $this->escapeChars($account->getUrl())); - $accountNotes = $this->xml->createElement('notes', $this->escapeChars($account->getNotes())); - $accountPass = $this->xml->createElement('pass', $this->escapeChars($account->getPass())); - $accountIV = $this->xml->createElement('key', $this->escapeChars($account->getKey())); - $tags = $this->xml->createElement('tags'); - - foreach ($this->accountToTagService->getTagsByAccountId($account->getId()) as $itemData) { - $tag = $this->xml->createElement('tag'); - $tag->setAttribute('id', $itemData->getId()); - - $tags->appendChild($tag); - } - - // Crear el nodo de cuenta - $nodeAccount = $this->xml->createElement('Account'); - $nodeAccount->setAttribute('id', $account->getId()); - $nodeAccount->appendChild($accountName); - $nodeAccount->appendChild($accountCustomerId); - $nodeAccount->appendChild($accountCategoryId); - $nodeAccount->appendChild($accountLogin); - $nodeAccount->appendChild($accountUrl); - $nodeAccount->appendChild($accountNotes); - $nodeAccount->appendChild($accountPass); - $nodeAccount->appendChild($accountIV); - $nodeAccount->appendChild($tags); - - // Añadir cuenta al nodo de cuentas - $nodeAccounts->appendChild($nodeAccount); - } - - $this->appendNode($nodeAccounts); - } catch (Exception $e) { - throw new ServiceException($e->getMessage(), SPException::ERROR, __FUNCTION__); - } - } - - /** - * Crear el hash del archivo XML e insertarlo en el árbol DOM - * - * @throws ServiceException - */ - private function createHash(): void - { - try { - $hash = self::generateHashFromNodes($this->xml); - - $hashNode = $this->xml->createElement('Hash', $hash); - $hashNode->appendChild($this->xml->createAttribute('sign')); - - $key = $this->exportPass ?: sha1($this->configData->getPasswordSalt()); - - $hashNode->setAttribute('sign', Hash::signMessage($hash, $key)); - - $this->root - ->getElementsByTagName('Meta') - ->item(0) - ->appendChild($hashNode); - } catch (Exception $e) { - throw new ServiceException( - $e->getMessage(), - SPException::ERROR, - __FUNCTION__ - ); - } - } - - public static function generateHashFromNodes(DOMDocument $document): string - { - $data = ''; - - foreach ((new DOMXPath($document))->query('/Root/*[not(self::Meta)]') as $node) { - $data .= $document->saveXML($node); - } - - return sha1($data); - } - - /** - * Generar el archivo XML - * - * @throws ServiceException - */ - private function writeXML(): void - { - try { - $this->xml->formatOutput = true; - $this->xml->preserveWhiteSpace = false; - - if (!$this->xml->save($this->exportFile)) { - throw new ServiceException(__u('Error while creating the XML file')); - } - } catch (Exception $e) { - throw new ServiceException( - $e->getMessage(), - SPException::ERROR, - __FUNCTION__ - ); - } - } - - /** - * @throws CheckException - * @throws FileException - */ - public function createArchive(): void - { - $archive = new ArchiveHandler($this->exportFile, $this->extensionChecker); - $archive->compressFile($this->exportFile); - - $file = new FileHandler($this->exportFile); - $file->delete(); - } - - public function getExportFile(): string - { - return $this->exportFile; - } - - public function isEncrypted(): bool - { - return $this->encrypted; - } -} diff --git a/lib/SP/Domain/Export/Services/XmlTagExport.php b/lib/SP/Domain/Export/Services/XmlTagExport.php new file mode 100644 index 00000000..35e36a7c --- /dev/null +++ b/lib/SP/Domain/Export/Services/XmlTagExport.php @@ -0,0 +1,91 @@ +. + */ + +namespace SP\Domain\Export\Ports; + +use DOMDocument; +use DOMElement; +use Exception; +use SP\Core\Application; +use SP\Core\Events\Event; +use SP\Core\Events\EventMessage; +use SP\Domain\Common\Services\Service; +use SP\Domain\Common\Services\ServiceException; +use SP\Domain\Export\Services\XmlUtil; +use SP\Domain\Tag\Services\TagService; + +use function SP\__u; + +/** + * Class XmlTagExport + */ +final class XmlTagExport extends Service implements XmlTagExportService +{ + public function __construct( + Application $application, + private readonly TagService $tagService, + + ) { + parent::__construct($application); + } + + /** + * Crear el nodo con los datos + * + * @throws ServiceException + */ + public function export(DOMDocument $document): DOMElement + { + try { + $this->eventDispatcher->notify( + 'run.export.process.tag', + new Event($this, EventMessage::factory()->addDescription(__u('Exporting tags'))) + ); + + $tags = $this->tagService->getAll(); + + $nodeTags = $document->createElement('Tags'); + + if ($nodeTags === false) { + throw ServiceException::error(__u('Unable to create node')); + } + + if (count($tags) === 0) { + return $nodeTags; + } + + foreach ($tags as $tag) { + $nodeTag = $document->createElement('Tag'); + $nodeTag->setAttribute('id', $tag->getId()); + $nodeTag->appendChild($document->createElement('name', XmlUtil::escapeChars($tag->getName()))); + + $nodeTags->appendChild($nodeTag); + } + + return $nodeTags; + } catch (Exception $e) { + throw ServiceException::error($e->getMessage(), __FUNCTION__); + } + } +} diff --git a/lib/SP/Domain/Export/Services/XmlTrait.php b/lib/SP/Domain/Export/Services/XmlTrait.php new file mode 100644 index 00000000..a23f2525 --- /dev/null +++ b/lib/SP/Domain/Export/Services/XmlTrait.php @@ -0,0 +1,45 @@ +. + */ + +namespace SP\Domain\Export\Services; + +use DOMDocument; +use DOMNode; +use DOMXPath; + +/** + * Trait XmlTrait + */ +trait XmlTrait +{ + private static function generateHashFromNodes(DOMDocument $document): string + { + return sha1( + array_reduce( + (new DOMXPath($document))->query('/Root/*[not(self::Meta)]'), + static fn(string $carry, DOMNode $node) => $carry . $document->saveXML($node) + ) + ); + } +} diff --git a/lib/SP/Domain/Export/Services/XmlUtil.php b/lib/SP/Domain/Export/Services/XmlUtil.php new file mode 100644 index 00000000..98e758ff --- /dev/null +++ b/lib/SP/Domain/Export/Services/XmlUtil.php @@ -0,0 +1,46 @@ +. + */ + +namespace SP\Domain\Export\Services; + +/** + * Class XmlUtil + */ +final class XmlUtil +{ + /** + * Escapar carácteres no válidos en XML + * + * @param $data string Los datos a escapar + * + * @return string + */ + public static function escapeChars(string $data): string + { + $arrStrFrom = ['&', '<', '>', '"', '\'']; + $arrStrTo = ['&', '<', '>', '"', ''']; + + return str_replace($arrStrFrom, $arrStrTo, $data); + } +} diff --git a/lib/SP/Domain/Export/Services/XmlVerifyService.php b/lib/SP/Domain/Export/Services/XmlVerifyService.php index f4ea9bc5..8e9f54bf 100644 --- a/lib/SP/Domain/Export/Services/XmlVerifyService.php +++ b/lib/SP/Domain/Export/Services/XmlVerifyService.php @@ -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. * @@ -49,6 +49,8 @@ use SP\Util\VersionUtil; */ final class XmlVerifyService extends Service implements XmlVerifyServiceInterface { + use XmlTrait; + private const NODES = ['Category', 'Client', 'Tag', 'Account']; private const XML_MIN_VERSION = [2, 1, 0, 0]; private ?DOMDocument $xml = null; @@ -58,7 +60,7 @@ final class XmlVerifyService extends Service implements XmlVerifyServiceInterfac /** * @throws FileException * @throws ImportException - * @throws \SP\Domain\Common\Services\ServiceException + * @throws ServiceException */ public function verify(string $xmlFile): VerifyResult { @@ -91,7 +93,7 @@ final class XmlVerifyService extends Service implements XmlVerifyServiceInterfac } /** - * @throws \SP\Domain\Common\Services\ServiceException + * @throws ServiceException */ public static function validateSchema(DOMDocument $document): void { @@ -109,7 +111,7 @@ final class XmlVerifyService extends Service implements XmlVerifyServiceInterfac } /** - * @throws \SP\Domain\Common\Services\ServiceException + * @throws ServiceException */ public static function checkVersion(string $version): void { @@ -142,7 +144,7 @@ final class XmlVerifyService extends Service implements XmlVerifyServiceInterfac ); } - return (string)$hash === XmlExportService::generateHashFromNodes($document); + return (string)$hash === self::generateHashFromNodes($document); } /** @@ -191,7 +193,7 @@ final class XmlVerifyService extends Service implements XmlVerifyServiceInterfac } /** - * @throws \SP\Domain\Common\Services\ServiceException + * @throws ServiceException */ private function checkPassword(): void { @@ -216,7 +218,7 @@ final class XmlVerifyService extends Service implements XmlVerifyServiceInterfac /** * Process the encrypted data and then build the unencrypted DOM * - * @throws \SP\Domain\Common\Services\ServiceException + * @throws ServiceException */ private function processEncrypted(): DOMDocument { diff --git a/lib/SP/Infrastructure/File/ArchiveHandler.php b/lib/SP/Infrastructure/File/ArchiveHandler.php index 5954a906..b516177e 100644 --- a/lib/SP/Infrastructure/File/ArchiveHandler.php +++ b/lib/SP/Infrastructure/File/ArchiveHandler.php @@ -82,12 +82,14 @@ final class ArchiveHandler implements ArchiveHandlerInterface * * @throws FileException */ - public function compressFile(string $file): void + public function compressFile(string $file): string { $this->archive->addFile($file, basename($file)); $this->archive->compress(Phar::GZ); // Delete the non-compressed archive (new FileHandler($this->archive->getPath()))->delete(); + + return $this->archive->getFileInfo()->getPathname(); } } diff --git a/lib/SP/Infrastructure/File/ArchiveHandlerInterface.php b/lib/SP/Infrastructure/File/ArchiveHandlerInterface.php index c1a865b7..5ac80828 100644 --- a/lib/SP/Infrastructure/File/ArchiveHandlerInterface.php +++ b/lib/SP/Infrastructure/File/ArchiveHandlerInterface.php @@ -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. * @@ -42,7 +42,8 @@ interface ArchiveHandlerInterface /** * Realizar un backup de la aplicación y comprimirlo. * + * @return string The path to the file * @throws FileException */ - public function compressFile(string $file): void; -} \ No newline at end of file + public function compressFile(string $file): string; +}