diff --git a/app/modules/web/Controllers/ConfigBackup/XmlExportController.php b/app/modules/web/Controllers/ConfigBackup/XmlExportController.php
index 91d954b4..db32908b 100644
--- a/app/modules/web/Controllers/ConfigBackup/XmlExportController.php
+++ b/app/modules/web/Controllers/ConfigBackup/XmlExportController.php
@@ -34,7 +34,7 @@ use SP\Domain\Core\Acl\UnauthorizedPageException;
use SP\Domain\Core\Exceptions\SessionTimeout;
use SP\Domain\Core\Exceptions\SPException;
use SP\Domain\Export\Ports\XmlExportService;
-use SP\Domain\Export\Ports\XmlVerifyServiceInterface;
+use SP\Domain\Export\Ports\XmlVerifyService;
use SP\Http\JsonMessage;
use SP\Infrastructure\File\ArchiveHandler;
use SP\Infrastructure\File\DirectoryHandler;
@@ -50,13 +50,13 @@ final class XmlExportController extends SimpleControllerBase
use JsonTrait;
private XmlExportService $xmlExportService;
- private XmlVerifyServiceInterface $xmlVerifyService;
+ private XmlVerifyService $xmlVerifyService;
public function __construct(
Application $application,
SimpleControllerHelper $simpleControllerHelper,
XmlExportService $xmlExportService,
- XmlVerifyServiceInterface $xmlVerifyService
+ XmlVerifyService $xmlVerifyService
) {
parent::__construct($application, $simpleControllerHelper);
@@ -94,10 +94,7 @@ final class XmlExportController extends SimpleControllerBase
if (!empty($exportPassword)) {
$verifyResult =
- $this->xmlVerifyService->verifyEncrypted(
- $file,
- $exportPassword
- );
+ $this->xmlVerifyService->verify($file, $exportPassword);
} else {
$verifyResult = $this->xmlVerifyService->verify($file);
}
diff --git a/lib/SP/Domain/Export/Ports/XmlVerifyServiceInterface.php b/lib/SP/Domain/Export/Ports/XmlVerifyService.php
similarity index 75%
rename from lib/SP/Domain/Export/Ports/XmlVerifyServiceInterface.php
rename to lib/SP/Domain/Export/Ports/XmlVerifyService.php
index 62157537..9bce9a19 100644
--- a/lib/SP/Domain/Export/Ports/XmlVerifyServiceInterface.php
+++ b/lib/SP/Domain/Export/Ports/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.
*
@@ -36,19 +36,12 @@ use SP\Infrastructure\File\FileException;
*
* @package SP\Domain\Export\Services
*/
-interface XmlVerifyServiceInterface
+interface XmlVerifyService
{
/**
* @throws FileException
* @throws ImportException
* @throws ServiceException
*/
- public function verify(string $xmlFile): VerifyResult;
-
- /**
- * @throws FileException
- * @throws ImportException
- * @throws \SP\Domain\Common\Services\ServiceException
- */
- public function verifyEncrypted(string $xmlFile, string $password): VerifyResult;
+ public function verify(string $xmlFile, ?string $password = null): VerifyResult;
}
diff --git a/lib/SP/Domain/Export/Services/XmlTrait.php b/lib/SP/Domain/Export/Services/XmlTrait.php
index 28392757..9b7ecbce 100644
--- a/lib/SP/Domain/Export/Services/XmlTrait.php
+++ b/lib/SP/Domain/Export/Services/XmlTrait.php
@@ -37,8 +37,9 @@ trait XmlTrait
{
return sha1(
array_reduce(
- (array)(new DOMXPath($document))->query('/Root/*[not(self::Meta)]'),
- static fn(string $carry, DOMNode $node) => $carry . $document->saveXML($node)
+ iterator_to_array((new DOMXPath($document))->query('/Root/*[not(self::Meta)]')->getIterator()),
+ static fn(string $carry, DOMNode $node) => $carry . $document->saveXML($node),
+ ''
)
);
}
diff --git a/lib/SP/Domain/Export/Services/XmlVerify.php b/lib/SP/Domain/Export/Services/XmlVerify.php
new file mode 100644
index 00000000..a0c8d219
--- /dev/null
+++ b/lib/SP/Domain/Export/Services/XmlVerify.php
@@ -0,0 +1,233 @@
+.
+ */
+
+namespace SP\Domain\Export\Services;
+
+use DOMDocument;
+use DOMElement;
+use DOMXPath;
+use SP\Core\Application;
+use SP\Core\Crypt\Hash;
+use SP\Domain\Common\Services\Service;
+use SP\Domain\Common\Services\ServiceException;
+use SP\Domain\Core\Crypt\CryptInterface;
+use SP\Domain\Core\Exceptions\CryptException;
+use SP\Domain\Export\Ports\XmlVerifyService;
+use SP\Util\VersionUtil;
+
+use function SP\__u;
+
+/**
+ * Class XmlVerify
+ *
+ * Verify a sysPass exported file format
+ */
+final class XmlVerify extends Service implements XmlVerifyService
+{
+ use XmlTrait;
+
+ private const NODES = ['Category', 'Client', 'Tag', 'Account'];
+ private const XML_MIN_VERSION = [2, 1, 0, 0];
+ private readonly DOMDocument $document;
+
+ public function __construct(
+ Application $application,
+ private readonly CryptInterface $crypt,
+ private readonly string $schema = XML_SCHEMA
+ ) {
+ parent::__construct($application);
+
+ $this->document = new DOMDocument('1.0', 'UTF-8');
+ }
+
+ /**
+ * @param string $xmlFile
+ * @param string|null $password
+ * @return VerifyResult
+ * @throws ServiceException
+ */
+ public function verify(string $xmlFile, ?string $password = null): VerifyResult
+ {
+ $self = clone $this;
+
+ $self->setup($xmlFile);
+ $self->validateSchema();
+
+ $version = $self->getXmlVersion();
+
+ self::checkVersion($version);
+
+ if (!self::checkXmlHash($self->document, $password ?? $self->config->getConfigData()->getPasswordSalt())) {
+ throw ServiceException::error(__u('Error while checking integrity hash'));
+ }
+
+ if (!empty($password)) {
+ $self->checkPassword($password);
+ $self->processEncrypted($password);
+ }
+
+ return new VerifyResult($version, $self->detectEncrypted(), $self->countItemNodes());
+ }
+
+ /**
+ * @throws ServiceException
+ */
+ private function setup(string $file): void
+ {
+ if (!$this->document->load($file, LIBXML_NOBLANKS)) {
+ $error = libxml_get_last_error();
+ throw ServiceException::error('Unable to load XML file', $error->message);
+ }
+ }
+
+ /**
+ * @throws ServiceException
+ */
+ private function validateSchema(): void
+ {
+ if (!$this->document->schemaValidate($this->schema)) {
+ $error = libxml_get_last_error();
+ throw ServiceException::error('Invalid XML schema', $error->message);
+ }
+ }
+
+ /**
+ * Obtener la versión del XML
+ */
+ private function getXmlVersion(): string
+ {
+ return (new DOMXPath($this->document))->query('/Root/Meta/Version')->item(0)->nodeValue;
+ }
+
+ /**
+ * @throws ServiceException
+ */
+ private static function checkVersion(string $version): void
+ {
+ if (VersionUtil::checkVersion($version, [self::XML_MIN_VERSION])) {
+ throw ServiceException::error(
+ sprintf(
+ 'Sorry, this XML version is not compatible. Please use >= %s',
+ implode('.', array_slice(self::XML_MIN_VERSION, 0, 2))
+ )
+ );
+ }
+ }
+
+ /**
+ * Obtener la versión del XML
+ */
+ public static function checkXmlHash(DOMDocument $document, string $key): bool
+ {
+ $xpath = new DOMXPath($document);
+ $hash = $xpath->query('/Root/Meta/Hash')->item(0)?->nodeValue;
+ $sign = $xpath->query('/Root/Meta/Hash/@sign')->item(0)?->nodeValue;
+
+ if (!empty($hash) && !empty($sign)) {
+ return Hash::checkMessage($hash, $key, $sign);
+ }
+
+ return $hash !== null && $hash === self::generateHashFromNodes($document);
+ }
+
+ /**
+ * @throws ServiceException
+ */
+ private function checkPassword(string $password): void
+ {
+ $hash = $this->document
+ ->getElementsByTagName('Encrypted')
+ ->item(0)
+ ->attributes
+ ?->getNamedItem('hash')
+ ->nodeValue;
+
+ if (empty($hash) || !Hash::checkHashKey($password, $hash)) {
+ throw ServiceException::error(__u('Wrong encryption password'));
+ }
+ }
+
+ /**
+ * Process the encrypted data and then build the unencrypted DOM
+ *
+ * @throws ServiceException
+ */
+ private function processEncrypted(string $password): DOMDocument
+ {
+ $dataNodes = (new DOMXPath($this->document))->query('/Root/Encrypted/Data');
+
+ $decode = VersionUtil::checkVersion($this->getXmlVersion(), '320.0');
+
+ /** @var $node DOMElement */
+ foreach ($dataNodes as $node) {
+ $data = $decode ? base64_decode($node->nodeValue) : $node->nodeValue;
+
+ try {
+ $xmlDecrypted = $this->crypt->decrypt($data, $node->getAttribute('key'), $password);
+ } catch (CryptException $e) {
+ throw ServiceException::error(__u('Wrong encryption password'), null, $e->getCode(), $e);
+ }
+
+ $newXmlData = new DOMDocument('1.0', 'UTF-8');
+
+ if (!$newXmlData->loadXML($xmlDecrypted)) {
+ throw ServiceException::error(__u('Error loading XML data'));
+ }
+
+ $this->document
+ ->documentElement
+ ->appendChild($this->document->importNode($newXmlData->documentElement, true));
+ }
+
+ // Remove the encrypted data after processing
+ $this->document->documentElement->removeChild($dataNodes->item(0)->parentNode);
+
+ // Validate XML schema again after processing the encrypted data
+ $this->validateSchema();
+
+ return $this->document;
+ }
+
+ /**
+ * Verificar si existen datos encriptados
+ */
+ private function detectEncrypted(): bool
+ {
+ return $this->document->getElementsByTagName('Encrypted')->length > 0;
+ }
+
+ /**
+ * @return int[]
+ */
+ private function countItemNodes(): array
+ {
+ $result = [];
+
+ foreach (self::NODES as $node) {
+ $result[$node] = $this->document->getElementsByTagName($node)->length;
+ }
+
+ return $result;
+ }
+}
diff --git a/lib/SP/Domain/Export/Services/XmlVerifyService.php b/lib/SP/Domain/Export/Services/XmlVerifyService.php
deleted file mode 100644
index 8e9f54bf..00000000
--- a/lib/SP/Domain/Export/Services/XmlVerifyService.php
+++ /dev/null
@@ -1,266 +0,0 @@
-.
- */
-
-namespace SP\Domain\Export\Services;
-
-
-use Defuse\Crypto\Exception\CryptoException;
-use DOMDocument;
-use DOMElement;
-use DOMXPath;
-use SP\Core\Crypt\Crypt;
-use SP\Core\Crypt\Hash;
-use SP\Domain\Common\Services\Service;
-use SP\Domain\Common\Services\ServiceException;
-use SP\Domain\Export\Ports\XmlVerifyServiceInterface;
-use SP\Domain\Import\Services\FileImport;
-use SP\Domain\Import\Services\ImportException;
-use SP\Domain\Import\Services\XmlFileImport;
-use SP\Infrastructure\File\FileException;
-use SP\Util\VersionUtil;
-
-/**
- * Class XmlVerifyService
- *
- * Verifies a sysPass exported file format
- *
- * @package SP\Domain\Export\Services
- */
-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;
- private ?string $xmlFile = null;
- private ?string $password = null;
-
- /**
- * @throws FileException
- * @throws ImportException
- * @throws ServiceException
- */
- public function verify(string $xmlFile): VerifyResult
- {
- $this->xmlFile = $xmlFile;
-
- $this->setup();
-
- self::validateSchema($this->xml);
-
- $version = $this->getXmlVersion();
-
- self::checkVersion($version);
-
- self::checkXmlHash($this->xml, $this->config->getConfigData()->getPasswordSalt());
-
- return new VerifyResult(
- $version,
- false,
- $this->countItemNodes($this->xml)
- );
- }
-
- /**
- * @throws FileException
- * @throws ImportException
- */
- private function setup(): void
- {
- $this->xml = (new XmlFileImport(FileImport::fromFilesystem($this->xmlFile)))->getXmlDOM();
- }
-
- /**
- * @throws ServiceException
- */
- public static function validateSchema(DOMDocument $document): void
- {
- if (!$document->schemaValidate(XML_SCHEMA)) {
- throw new ServiceException('Invalid XML schema');
- }
- }
-
- /**
- * Obtener la versión del XML
- */
- private function getXmlVersion(): string
- {
- return (new DOMXPath($this->xml))->query('/Root/Meta/Version')->item(0)->nodeValue;
- }
-
- /**
- * @throws ServiceException
- */
- public static function checkVersion(string $version): void
- {
- if (VersionUtil::checkVersion($version, self::XML_MIN_VERSION)) {
- throw new ServiceException(
- sprintf(
- 'Sorry, this XML version is not compatible. Please use >= %s',
- VersionUtil::normalizeVersionForCompare(self::XML_MIN_VERSION)
- )
- );
- }
- }
-
- /**
- * Obtener la versión del XML
- */
- public static function checkXmlHash(
- DOMDocument $document,
- string $key
- ): bool {
- $DOMXPath = new DOMXPath($document);
- $hash = $DOMXPath->query('/Root/Meta/Hash');
- $sign = $DOMXPath->query('/Root/Meta/Hash/@sign');
-
- if ($hash->length === 1 && $sign->length === 1) {
- return Hash::checkMessage(
- $hash->item(0)->nodeValue,
- $key,
- $sign->item(0)->nodeValue
- );
- }
-
- return (string)$hash === self::generateHashFromNodes($document);
- }
-
- /**
- * @return int[]
- */
- private function countItemNodes(DOMDocument $document): array
- {
- $result = [];
-
- foreach (self::NODES as $node) {
- $result[$node] = $document->getElementsByTagName($node)->length;
- }
-
- return $result;
- }
-
- /**
- * @throws FileException
- * @throws ImportException
- * @throws ServiceException
- */
- public function verifyEncrypted(string $xmlFile, string $password): VerifyResult
- {
- $this->xmlFile = $xmlFile;
- $this->password = $password;
-
- $this->setup();
-
- self::validateSchema($this->xml);
-
- self::checkVersion($this->getXmlVersion());
-
- $this->checkPassword();
-
- $key = $password !== '' ? $password : $this->config->getConfigData()->getPasswordSalt();
-
- if (!self::checkXmlHash($this->xml, $key)) {
- throw new ServiceException(__u('Error while checking integrity hash'));
- }
-
- return new VerifyResult(
- $this->getXmlVersion(),
- $this->detectEncrypted(),
- $this->countItemNodes($this->processEncrypted())
- );
- }
-
- /**
- * @throws ServiceException
- */
- private function checkPassword(): void
- {
- $hash = $this->xml
- ->getElementsByTagName('Encrypted')
- ->item(0)
- ->getAttribute('hash');
-
- if (empty($hash) || !Hash::checkHashKey($this->password, $hash)) {
- throw new ServiceException(__u('Wrong encryption password'));
- }
- }
-
- /**
- * Verificar si existen datos encriptados
- */
- private function detectEncrypted(): bool
- {
- return $this->xml->getElementsByTagName('Encrypted')->length > 0;
- }
-
- /**
- * Process the encrypted data and then build the unencrypted DOM
- *
- * @throws ServiceException
- */
- private function processEncrypted(): DOMDocument
- {
- $xpath = new DOMXPath($this->xml);
- $dataNodes = $xpath->query('/Root/Encrypted/Data');
-
- $decode = VersionUtil::checkVersion(
- $this->getXmlVersion(),
- '320.0'
- );
-
- /** @var $node DOMElement */
- foreach ($dataNodes as $node) {
- $data = $decode ? base64_decode($node->nodeValue) : $node->nodeValue;
-
- try {
- $xmlDecrypted = Crypt::decrypt(
- $data,
- $node->getAttribute('key'),
- $this->password
- );
- } catch (CryptoException $e) {
- throw new ServiceException(__u('Wrong encryption password'));
- }
-
- $newXmlData = new DOMDocument();
-
- if ($newXmlData->loadXML($xmlDecrypted) === false) {
- throw new ServiceException(__u('Error loading XML data'));
- }
-
- $this->xml->documentElement->appendChild(
- $this->xml->importNode($newXmlData->documentElement, true)
- );
- }
-
- // Remove the encrypted data after processing
- $this->xml->documentElement->removeChild($dataNodes->item(0)->parentNode);
-
- // Validate XML schema again after processing the encrypted data
- self::validateSchema($this->xml);
-
- return $this->xml;
- }
-}
diff --git a/lib/SP/Domain/Import/Services/SyspassImport.php b/lib/SP/Domain/Import/Services/SyspassImport.php
index 4736b869..bce6929f 100644
--- a/lib/SP/Domain/Import/Services/SyspassImport.php
+++ b/lib/SP/Domain/Import/Services/SyspassImport.php
@@ -38,7 +38,7 @@ use SP\Domain\Account\Dtos\AccountRequest;
use SP\Domain\Category\Models\Category;
use SP\Domain\Client\Models\Client;
use SP\Domain\Core\Exceptions\SPException;
-use SP\Domain\Export\Services\XmlVerifyService;
+use SP\Domain\Export\Services\XmlVerify;
use SP\Domain\Tag\Models\Tag;
use SP\Util\VersionUtil;
@@ -201,7 +201,7 @@ final class SyspassImport extends XmlImportBase implements ImportInterface
{
$key = $this->importParams->getImportPwd() ?: sha1($this->configData->getPasswordSalt());
- if (!XmlVerifyService::checkXmlHash($this->xmlDOM, $key)) {
+ if (!XmlVerify::checkXmlHash($this->xmlDOM, $key)) {
$this->eventDispatcher->notify(
'run.import.syspass.process.verify',
new Event(
diff --git a/lib/SP/Util/VersionUtil.php b/lib/SP/Util/VersionUtil.php
index 4374455c..dec87e4b 100644
--- a/lib/SP/Util/VersionUtil.php
+++ b/lib/SP/Util/VersionUtil.php
@@ -45,7 +45,7 @@ final class VersionUtil
* Compare versions
*
* @param string $currentVersion
- * @param array|string $upgradeableVersion
+ * @param array|string $upgradeableVersion A list of upgradeable versions
*
* @return bool True if $currentVersion is lower than $upgradeableVersion
*/
diff --git a/tests/SPT/Domain/Export/Services/XmlVerifyTest.php b/tests/SPT/Domain/Export/Services/XmlVerifyTest.php
new file mode 100644
index 00000000..ce7cf821
--- /dev/null
+++ b/tests/SPT/Domain/Export/Services/XmlVerifyTest.php
@@ -0,0 +1,251 @@
+.
+ */
+
+namespace SPT\Domain\Export\Services;
+
+use DOMDocument;
+use PHPUnit\Framework\MockObject\MockObject;
+use SP\Core\Crypt\Hash;
+use SP\Domain\Common\Services\ServiceException;
+use SP\Domain\Config\Adapters\ConfigData;
+use SP\Domain\Config\Ports\ConfigDataInterface;
+use SP\Domain\Config\Ports\ConfigFileService;
+use SP\Domain\Core\Crypt\CryptInterface;
+use SP\Domain\Core\Exceptions\CryptException;
+use SP\Domain\Export\Services\XmlVerify;
+use SPT\UnitaryTestCase;
+
+/**
+ * Class XmlVerifyTest
+ *
+ * @group unitary
+ */
+class XmlVerifyTest extends UnitaryTestCase
+{
+ private const VALID_ENCRYPTED_FILE = RESOURCE_PATH . DIRECTORY_SEPARATOR . 'import' . DIRECTORY_SEPARATOR .
+ 'data_syspass_encrypted.xml';
+ private const VALID_FILE = RESOURCE_PATH . DIRECTORY_SEPARATOR . 'import' . DIRECTORY_SEPARATOR .
+ 'data_syspass.xml';
+ private const NO_VALID_FILE = RESOURCE_PATH . DIRECTORY_SEPARATOR . 'import' . DIRECTORY_SEPARATOR .
+ 'data_syspass_invalid.xml';
+ private const NO_VALID_VERSION_FILE = RESOURCE_PATH . DIRECTORY_SEPARATOR . 'import' . DIRECTORY_SEPARATOR .
+ 'data_syspass_invalid_version.xml';
+ private const NO_VALID_HASH_FILE = RESOURCE_PATH . DIRECTORY_SEPARATOR . 'import' . DIRECTORY_SEPARATOR .
+ 'data_syspass_invalid_hash.xml';
+
+ private CryptInterface|MockObject $crypt;
+ private XmlVerify $xmlVerify;
+
+ public function testCheckXmlHashWithNoHash()
+ {
+ $xml = '';
+
+ $document = new DOMDocument();
+ $document->loadXML($xml);
+
+ $out = XmlVerify::checkXmlHash($document, 'test');
+
+ $this->assertFalse($out);
+ }
+
+ public function testCheckXmlHashWithNoSign()
+ {
+ $xml = 'a_hash';
+
+ $document = new DOMDocument();
+ $document->loadXML($xml);
+
+ $out = XmlVerify::checkXmlHash($document, 'test');
+
+ $this->assertFalse($out);
+ }
+
+ public function testCheckXmlHashWithWrongHash()
+ {
+ $xml = 'a_hash';
+
+ $document = new DOMDocument();
+ $document->loadXML($xml);
+
+ $out = XmlVerify::checkXmlHash($document, 'test');
+
+ $this->assertFalse($out);
+ }
+
+ public function testCheckXmlHash()
+ {
+ $sign = Hash::signMessage('test_data', 'test_key');
+ $xml = sprintf('test_data', $sign);
+
+ $document = new DOMDocument();
+ $document->loadXML($xml);
+
+ $out = XmlVerify::checkXmlHash($document, 'test_key');
+
+ $this->assertTrue($out);
+ }
+
+ public function testCheckXmlHashWithNodesHash()
+ {
+ $xml = sprintf(
+ '%stest_data',
+ sha1('test_data')
+ );
+ $document = new DOMDocument();
+ $document->loadXML($xml);
+
+ $out = XmlVerify::checkXmlHash($document, 'test_key');
+
+ $this->assertTrue($out);
+ }
+
+ /**
+ * @throws ServiceException
+ */
+ public function testVerify()
+ {
+ $out = $this->xmlVerify->verify(self::VALID_FILE);
+
+ $this->assertEquals('300.18071701', $out->getVersion());
+ $nodes = $out->getNodes();
+ $this->assertCount(4, $nodes);
+ $this->assertEquals(5, $nodes['Category']);
+ $this->assertEquals(4, $nodes['Client']);
+ $this->assertEquals(7, $nodes['Tag']);
+ $this->assertEquals(5, $nodes['Account']);
+ $this->assertFalse($out->isEncrypted());
+ }
+
+ /**
+ * @throws ServiceException
+ */
+ public function testVerifyWithInvalidFile()
+ {
+ $this->expectException(ServiceException::class);
+ $this->expectExceptionMessage('Unable to load XML file');
+
+ $this->xmlVerify->verify('a_file');
+ }
+
+ /**
+ * @throws ServiceException
+ */
+ public function testVerifyWithInvalidFileSchema()
+ {
+ $this->expectException(ServiceException::class);
+ $this->expectExceptionMessage('Invalid XML schema');
+
+ $this->xmlVerify->verify(self::NO_VALID_FILE);
+ }
+
+ /**
+ * @throws ServiceException
+ */
+ public function testVerifyWithInvalidVersion()
+ {
+ $this->expectException(ServiceException::class);
+ $this->expectExceptionMessage('Sorry, this XML version is not compatible. Please use >= 2.1');
+
+ $this->xmlVerify->verify(self::NO_VALID_VERSION_FILE);
+ }
+
+ /**
+ * @throws ServiceException
+ */
+ public function testVerifyWithInvalidHash()
+ {
+ $this->expectException(ServiceException::class);
+ $this->expectExceptionMessage('Error while checking integrity hash');
+
+ $this->xmlVerify->verify(self::NO_VALID_HASH_FILE);
+ }
+
+ /**
+ * @throws ServiceException
+ */
+ public function testVerifyEncrypted()
+ {
+ $categories = 'CSV Category 1';
+ $clients = 'Apple';
+ $tags = 'Apache';
+ $accounts = 'Google11adminhttps://google.comatest';
+
+ $this->crypt
+ ->expects(self::exactly(4))
+ ->method('decrypt')
+ ->with(
+ self::stringStartsWith('def50200'),
+ self::stringStartsWith('def10000def5020'),
+ 'test_encrypt'
+ )
+ ->willReturn($categories, $clients, $tags, $accounts);
+
+ $out = $this->xmlVerify->verify(self::VALID_ENCRYPTED_FILE, 'test_encrypt');
+
+ $this->assertEquals('300.18082201', $out->getVersion());
+ $nodes = $out->getNodes();
+ $this->assertCount(4, $nodes);
+ $this->assertEquals(1, $nodes['Category']);
+ $this->assertEquals(1, $nodes['Client']);
+ $this->assertEquals(1, $nodes['Tag']);
+ $this->assertEquals(1, $nodes['Account']);
+ $this->assertFalse($out->isEncrypted());
+ }
+
+ /**
+ * @throws ServiceException
+ */
+ public function testVerifyEncryptedWithCryptException()
+ {
+ $this->crypt
+ ->expects(self::once())
+ ->method('decrypt')
+ ->willThrowException(CryptException::error('test'));
+
+ $this->expectException(ServiceException::class);
+ $this->expectExceptionMessage('Wrong encryption password');
+
+ $this->xmlVerify->verify(self::VALID_ENCRYPTED_FILE, 'test_encrypt');
+ }
+
+ protected function getConfig(): ConfigFileService
+ {
+ $configData = new ConfigData([ConfigDataInterface::PASSWORD_SALT => 'a_salt']);
+
+ $config = $this->createStub(ConfigFileService::class);
+ $config->method('getConfigData')->willReturn($configData);
+
+ return $config;
+ }
+
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->crypt = $this->createMock(CryptInterface::class);
+
+ $this->xmlVerify = new XmlVerify($this->application, $this->crypt);
+ }
+}
diff --git a/tests/SPT/UnitaryTestCase.php b/tests/SPT/UnitaryTestCase.php
index 8feae6ce..8862ea9e 100644
--- a/tests/SPT/UnitaryTestCase.php
+++ b/tests/SPT/UnitaryTestCase.php
@@ -45,10 +45,10 @@ abstract class UnitaryTestCase extends TestCase
{
use PHPUnitHelper;
- protected static Generator $faker;
- protected ConfigFileService $config;
- protected Application $application;
- protected ContextInterface $context;
+ protected static Generator $faker;
+ protected readonly ConfigFileService $config;
+ protected readonly Application $application;
+ protected readonly ContextInterface $context;
public static function setUpBeforeClass(): void
{
@@ -92,15 +92,23 @@ abstract class UnitaryTestCase extends TestCase
$this->context->setUserData($userLogin);
$this->context->setUserProfile(new ProfileData());
+ return new Application(
+ $this->getConfig(),
+ $this->createStub(EventDispatcherInterface::class),
+ $this->context
+ );
+ }
+
+ /**
+ * @throws Exception
+ */
+ protected function getConfig(): ConfigFileService
+ {
$configData = ConfigDataGenerator::factory()->buildConfigData();
$config = $this->createStub(ConfigFileService::class);
$config->method('getConfigData')->willReturn($configData);
- return new Application(
- $config,
- $this->createStub(EventDispatcherInterface::class),
- $this->context
- );
+ return $config;
}
}
diff --git a/tests/res/import/data_syspass.xml b/tests/res/import/data_syspass.xml
index 39977c99..0b14cd2f 100644
--- a/tests/res/import/data_syspass.xml
+++ b/tests/res/import/data_syspass.xml
@@ -1,4 +1,27 @@
+
+
sysPass
@@ -6,7 +29,9 @@
admin
admin
- e633ecaaa0a76d06ab220d8eaed0784138916ba5
+
+ da39a3ee5e6b4b0d3255bfef95601890afd80709
+
diff --git a/tests/res/import/data_syspass_invalid_hash.xml b/tests/res/import/data_syspass_invalid_hash.xml
new file mode 100644
index 00000000..7c0bea0e
--- /dev/null
+++ b/tests/res/import/data_syspass_invalid_hash.xml
@@ -0,0 +1,186 @@
+
+
+
+
+
+ sysPass
+ 300.18071701
+
+ admin
+ admin
+ a_hash
+
+
+
+ CSV Category 1
+
+
+
+ Linux
+
+
+
+ SSH
+
+
+
+ Test
+
+
+
+ Web
+
+
+
+
+
+ Apple
+
+
+
+ CSV Client 1
+
+
+
+ Google
+
+
+
+ KK
+
+
+
+
+
+ Apache
+
+
+ Debian
+
+
+ JBoss
+
+
+ MySQL
+
+
+ server
+
+
+ SSH
+
+
+ www
+
+
+
+
+ Google
+ 1
+ 1
+ admin
+ https://google.com
+
+
+ def502008d12c14486b3cc5986ada5dd5c94e5fd518665d0bbcba98526dc69fb3836e7665991536e2b2e33c92229fccc671996b1f1ad22892e9232463ead8bb2b4603fee0f0d6f23d83766a63c5195c4330c19cf4b6d4119cb5a68b2cc2e4eab295a
+
+
+ def10000def5020092a48432287c2ca6bae1716581fa1fc9825cde731cab3a1fa482de6bc6f0fda8b1323f9a7bffd878d4b8bc24e3d671ba6a6a961478c47e8dbcceee3164464bbb25b5cc3bf95600e044f62ffcb706543b330aea6063c284380c091853e46ad09a1954e0d923d48192952df1591b013665da737dd5e56b46e699c2a1e648e7ab76e7f872e5f7e5b13088ff6f1314a7c659d75a8ab2554f5e17ba9bbf9b660ba750da177bc906b1f6c7f47684b4f85605faf8c23edd797f39a003fa7e3a6fe861717a4e3076f793d5a52e6db6602f0a7947908653b8ad7389dc8e78890024c5ab3e3d17869babf06242fa0aef189ac7a8af616620719037bfee
+
+
+
+
+
+
+
+
+ Google
+ 1
+ 1
+ admin
+ https://google.com
+ blablacar
+
+ def502006b4eba70d6527f4e3288ec35ae2f4a686b22bfa5dc5355417da61dc25e4f461a56d83c9461ad6ee72029d5f94f8cca4a5f0907a60fedd32cf0dc77aea9341be24c284f534edc4d96c2004454de1a22f953436bb1d61a54ec552ba478
+
+
+ def10000def502007548ee1ed2267a46dcf504cd7ac9e7306f20d6045a47a844b14618095dfe2c30913d6553d04de88d37b949b61c84979dffa35f102677f930a954c01c840e26159af20eca93f04eed277e919a69ff2e69a8c441990a0fe700e8b6af47a6094d47cb52df94ef12c59dd700f02f9ae2f0ca76361ce4fb149d3cde45f54b2223d0adf2be0d94f06d92cf6e1e9d4de2032ef4550f01e425a73848f350d5f0d0b4d820ab9fa7c5875c71a7d65d793a886a7626077c22247d66e1b07518f562f75b248478a6915342953f2caf876a4577d428a707c7cb52889a5b849eec0c0e89cd5b103cd477ce91347a181fcaafd9f77344674315ee1cbe208038
+
+
+
+
+
+
+
+
+ Test CSV 1
+ 4
+ 4
+ csv_login1
+ http://test.me
+ CSV Notes
+
+ def502009758bc1ad3e55a57d8bbd7fbe698b9f0bfa9b592607dc7806fc197fab96f2402be8ad6d1e5fe8639ff0e6860f0f761d1204df6be6b2c98a2a6c841e60bf3f0a7c719f26597240388432231b042f30acc01d8b504c5cb51ce50
+
+
+ def10000def50200a01a063b4244219d1d2e534abbb65f77534d3c29c321d4eb977daac11d3196741ba3b2973c46e1281857e433c0d3e85a21cb24e1646e6a8bd2f991afec881c9c230d844c0324ecd21729fb7ad596b5687d797b74c2b989333836a71f5cac6cba8a50cdd2165fe3627301c87516e822421539957a5ebaec39f478511f6140b044e3df8723f8b50b242238cf9ecc9b66488c4d1ee90aec1042176ede6481a9e57bd69c8dd309b992f0598b2193e7172a3223e45f7d4c87b63f4b95f6fa8f1723db7a6bcdbb1a5d2f85b59c29839380012a17c671aee627107c1b54af17d7d0e1da2582d1bb79e0060ba5e997beb6e44e219201c4bb643f1ded
+
+
+
+
+ Test CSV 2
+ 1
+ 5
+ csv_login2
+ http://linux.org
+ CSV Notes 2
+ bla
+ bla
+ car
+
+
+ def50200d96ee2feda40fc05246d2170399a88c6d84039dd6f043b99cf71af45eec61def13c331b0dcc22db9a78f3c6ae42aeca2f304bcb5b3266b60f14044ba2bd9c4d6d6530ee04ec70af7a02af19266bc2e94df0b01dc956a0990f1
+
+
+ def10000def50200db49c60711ecd68ba7c5e638e609f4f259f71ba0ea3b55679779b75e65047fb9f8fad9f679f41ef7486abd7f0f3b106dd5e73b689a337bc4ec65cb540e783e0012309208f510a543d99ee1e7c7075cdf4c08f12cfba8923da543b09b08844361c556b3e771b41b2219c74f35bbe2c3382167c7f4a4bf57acd69364dbb09b916b15c15509dd9eb6edf828ab93750323772936bee4e09d330421c90f8747aaf2da88ec2616534272d258ea105433fd52b268bb65417d18adb875fb075060497ee3ffa7bba808d0a7cba61794716f53f92e04174d7ac0fd867a7132d9918a4b29ba61e1dabef37b32145f9ca5c99974018a6f9313a41b06866d
+
+
+
+
+ Test CSV 3
+ 5
+ 6
+ csv_login2
+ http://apple.com
+ CSV Notes 3
+
+ def502005f79a3efb835d6c8e2c9156e18a90664ec5deae37a7fa905f53a424ba2e855a4b5aedcfe0e5926aa1c70deb736a10b039a67eee96d67806fe3e8ea025705c3eb99c954dc55b7a7cc227116edeef4f23a7d1bae12ded3cd255d
+
+
+ def10000def50200bd6d13fa15111969fdcdcddb54a83dc76b4be2f25ee7b16650f94505cb433f7530a53ad337a9e044a0c3f7d2789ecac9e3bef6299294812e21a3e6bab0cb4fb4ec186b723820a8e5c9dae22e59dc2439225b56777328daa6744b3801c57dfe4cd717c81cc7352b319f111b80b1764e52cddc25fe6fd509003560832e53686d4747e55024be63fdd1a0d5d1001cdf43d7aa3a5e2e5835730626aacb8f9864c3959d03bc6935f0dc5cf510d5660190cc409df728fd57c273619afe18abb00c8c37137a00379e5100ca386ef098171333b3ff99c937d5f6ac998595b36580d5a6be428dce9f801d156de4903a9383d488f8f7fd2f76c4b4a4ee
+
+
+
+
+
diff --git a/tests/res/import/data_syspass_invalid_version.xml b/tests/res/import/data_syspass_invalid_version.xml
new file mode 100644
index 00000000..75a14083
--- /dev/null
+++ b/tests/res/import/data_syspass_invalid_version.xml
@@ -0,0 +1,188 @@
+
+
+
+
+
+ sysPass
+ 200.00000000
+
+ admin
+ admin
+
+ e633ecaaa0a76d06ab220d8eaed0784138916ba5
+
+
+
+
+ CSV Category 1
+
+
+
+ Linux
+
+
+
+ SSH
+
+
+
+ Test
+
+
+
+ Web
+
+
+
+
+
+ Apple
+
+
+
+ CSV Client 1
+
+
+
+ Google
+
+
+
+ KK
+
+
+
+
+
+ Apache
+
+
+ Debian
+
+
+ JBoss
+
+
+ MySQL
+
+
+ server
+
+
+ SSH
+
+
+ www
+
+
+
+
+ Google
+ 1
+ 1
+ admin
+ https://google.com
+
+
+ def502008d12c14486b3cc5986ada5dd5c94e5fd518665d0bbcba98526dc69fb3836e7665991536e2b2e33c92229fccc671996b1f1ad22892e9232463ead8bb2b4603fee0f0d6f23d83766a63c5195c4330c19cf4b6d4119cb5a68b2cc2e4eab295a
+
+
+ def10000def5020092a48432287c2ca6bae1716581fa1fc9825cde731cab3a1fa482de6bc6f0fda8b1323f9a7bffd878d4b8bc24e3d671ba6a6a961478c47e8dbcceee3164464bbb25b5cc3bf95600e044f62ffcb706543b330aea6063c284380c091853e46ad09a1954e0d923d48192952df1591b013665da737dd5e56b46e699c2a1e648e7ab76e7f872e5f7e5b13088ff6f1314a7c659d75a8ab2554f5e17ba9bbf9b660ba750da177bc906b1f6c7f47684b4f85605faf8c23edd797f39a003fa7e3a6fe861717a4e3076f793d5a52e6db6602f0a7947908653b8ad7389dc8e78890024c5ab3e3d17869babf06242fa0aef189ac7a8af616620719037bfee
+
+
+
+
+
+
+
+
+ Google
+ 1
+ 1
+ admin
+ https://google.com
+ blablacar
+
+ def502006b4eba70d6527f4e3288ec35ae2f4a686b22bfa5dc5355417da61dc25e4f461a56d83c9461ad6ee72029d5f94f8cca4a5f0907a60fedd32cf0dc77aea9341be24c284f534edc4d96c2004454de1a22f953436bb1d61a54ec552ba478
+
+
+ def10000def502007548ee1ed2267a46dcf504cd7ac9e7306f20d6045a47a844b14618095dfe2c30913d6553d04de88d37b949b61c84979dffa35f102677f930a954c01c840e26159af20eca93f04eed277e919a69ff2e69a8c441990a0fe700e8b6af47a6094d47cb52df94ef12c59dd700f02f9ae2f0ca76361ce4fb149d3cde45f54b2223d0adf2be0d94f06d92cf6e1e9d4de2032ef4550f01e425a73848f350d5f0d0b4d820ab9fa7c5875c71a7d65d793a886a7626077c22247d66e1b07518f562f75b248478a6915342953f2caf876a4577d428a707c7cb52889a5b849eec0c0e89cd5b103cd477ce91347a181fcaafd9f77344674315ee1cbe208038
+
+
+
+
+
+
+
+
+ Test CSV 1
+ 4
+ 4
+ csv_login1
+ http://test.me
+ CSV Notes
+
+ def502009758bc1ad3e55a57d8bbd7fbe698b9f0bfa9b592607dc7806fc197fab96f2402be8ad6d1e5fe8639ff0e6860f0f761d1204df6be6b2c98a2a6c841e60bf3f0a7c719f26597240388432231b042f30acc01d8b504c5cb51ce50
+
+
+ def10000def50200a01a063b4244219d1d2e534abbb65f77534d3c29c321d4eb977daac11d3196741ba3b2973c46e1281857e433c0d3e85a21cb24e1646e6a8bd2f991afec881c9c230d844c0324ecd21729fb7ad596b5687d797b74c2b989333836a71f5cac6cba8a50cdd2165fe3627301c87516e822421539957a5ebaec39f478511f6140b044e3df8723f8b50b242238cf9ecc9b66488c4d1ee90aec1042176ede6481a9e57bd69c8dd309b992f0598b2193e7172a3223e45f7d4c87b63f4b95f6fa8f1723db7a6bcdbb1a5d2f85b59c29839380012a17c671aee627107c1b54af17d7d0e1da2582d1bb79e0060ba5e997beb6e44e219201c4bb643f1ded
+
+
+
+
+ Test CSV 2
+ 1
+ 5
+ csv_login2
+ http://linux.org
+ CSV Notes 2
+ bla
+ bla
+ car
+
+
+ def50200d96ee2feda40fc05246d2170399a88c6d84039dd6f043b99cf71af45eec61def13c331b0dcc22db9a78f3c6ae42aeca2f304bcb5b3266b60f14044ba2bd9c4d6d6530ee04ec70af7a02af19266bc2e94df0b01dc956a0990f1
+
+
+ def10000def50200db49c60711ecd68ba7c5e638e609f4f259f71ba0ea3b55679779b75e65047fb9f8fad9f679f41ef7486abd7f0f3b106dd5e73b689a337bc4ec65cb540e783e0012309208f510a543d99ee1e7c7075cdf4c08f12cfba8923da543b09b08844361c556b3e771b41b2219c74f35bbe2c3382167c7f4a4bf57acd69364dbb09b916b15c15509dd9eb6edf828ab93750323772936bee4e09d330421c90f8747aaf2da88ec2616534272d258ea105433fd52b268bb65417d18adb875fb075060497ee3ffa7bba808d0a7cba61794716f53f92e04174d7ac0fd867a7132d9918a4b29ba61e1dabef37b32145f9ca5c99974018a6f9313a41b06866d
+
+
+
+
+ Test CSV 3
+ 5
+ 6
+ csv_login2
+ http://apple.com
+ CSV Notes 3
+
+ def502005f79a3efb835d6c8e2c9156e18a90664ec5deae37a7fa905f53a424ba2e855a4b5aedcfe0e5926aa1c70deb736a10b039a67eee96d67806fe3e8ea025705c3eb99c954dc55b7a7cc227116edeef4f23a7d1bae12ded3cd255d
+
+
+ def10000def50200bd6d13fa15111969fdcdcddb54a83dc76b4be2f25ee7b16650f94505cb433f7530a53ad337a9e044a0c3f7d2789ecac9e3bef6299294812e21a3e6bab0cb4fb4ec186b723820a8e5c9dae22e59dc2439225b56777328daa6744b3801c57dfe4cd717c81cc7352b319f111b80b1764e52cddc25fe6fd509003560832e53686d4747e55024be63fdd1a0d5d1001cdf43d7aa3a5e2e5835730626aacb8f9864c3959d03bc6935f0dc5cf510d5660190cc409df728fd57c273619afe18abb00c8c37137a00379e5100ca386ef098171333b3ff99c937d5f6ac998595b36580d5a6be428dce9f801d156de4903a9383d488f8f7fd2f76c4b4a4ee
+
+
+
+
+
diff --git a/tests/res/import/data_syspass_valid_hash.xml b/tests/res/import/data_syspass_valid_hash.xml
deleted file mode 100644
index 8b326db9..00000000
--- a/tests/res/import/data_syspass_valid_hash.xml
+++ /dev/null
@@ -1,91 +0,0 @@
-
-
-
- sysPass
- 300.18082201
-
- admin
-
- aa38d7292f6a00f3c16a19f99dd2940f04a2026a
-
-
-
- AWS
-
-
-
- GCP
-
-
-
- SSH
-
-
-
- Web
-
-
-
-
-
- Amazon
-
-
-
- Apple
-
-
-
- Google
-
-
-
-
-
- Apache
-
-
- Email
-
-
- JBoss
-
-
- SaaS
-
-
- SSH
-
-
- Tomcat
-
-
-
-
- Amazon SES
- 3
- 2
- admin
- https://aws.amazon.com/
- Simple Email Service
- def502008fde3a27bb1a6023f1b4ec524ba147c80ae78f90f9b3d0637af6809ad9263afaa36d34237ded5e8803b53f17121bd5e2a66919c0ce21d27c9360dfbc39d8151df9cbb4aadd1a0daa83ff59181ce1f1535c4b93d2701fa72c662115e919
- def10000def502002937bbb81177ff4f769aeae126108783a141170588f3482fcbab44ae3390e80f85ab38521bafa8ec19648d267c9d46062294f5068a16706dcdb9042ddbb5b23ef54b3a12e7c5723b15d32ab9d8f5559e1fbc658c0b547a2a88a34b2498ef934d55395f857f28c9e3314493f319ebd4140d4a1b968e678cc12b58b5d2d057530be80f5f58c592cedcd85a90529d0895b11d6c04768bfbf4f1527babf5ee563798d35886f1c1017d1a3221cfd317cb84b13302d7d71c33e6753bcdb913dacd8909e195387f47b136a2789f85c48c3600f4ac6433a26e2bd0cae74fa2c461e4495108d340d7120febc65cfe79a4e2acc046f9bac29a0ce35cda
-
-
-
-
-
- Google GCP
- 1
- 1
- admin
- https://cloud.google.com/
- Google Cloud
- def50200894e22b540a4975a3efe7dae669760aa6337195c690ac79ebff28f03393ae1070c5c7a635fa5a9aee5059df912c1948c41d8198f24f9f892a728d400d12d200c184a793d03a3f13eebfa45614efd0004ad7ea83338d1a04e20e6846078
- def10000def50200f3d572936f4496e90369f6cc1feadce57c803b01a78c64b5987efd6369f7ecf526494a148d0e056feafe60d903bbb65ea9f79ad9b4f7ad42c7fe3c9c586f1807d252444c42667129da3727cf6f702a5aadf5db2391ba4a581f950df65262ae04b314b88d69d3174e6f226cb1f939f04d102799e58e0b6ed839fe2282056c58069e6298865c386e3d2114635621ed14eb199b1dad6dfcabc9b364ea2ae147c38352cd72bc0c79761a9df0f58690d5da1d1e3cc5e17261d740ca6863383a869b0253790d46ba2df032e741e8bb788033d8eb7b97d124d58b3c4310d15df7a4fd4d373dcc0ae111d46f6d623bac7ccf330520439736b223ae81
-
-
-
-
-
-