From d020963eaa7b62d19186bcd5f14247f4c8e0ec98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D?= Date: Sat, 17 Feb 2024 09:16:46 +0100 Subject: [PATCH] chore(tests): UT for XmlVerify service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rubén D --- .../ConfigBackup/XmlExportController.php | 11 +- ...viceInterface.php => XmlVerifyService.php} | 13 +- lib/SP/Domain/Export/Services/XmlTrait.php | 5 +- lib/SP/Domain/Export/Services/XmlVerify.php | 233 +++++++++++++++ .../Export/Services/XmlVerifyService.php | 266 ------------------ .../Domain/Import/Services/SyspassImport.php | 4 +- lib/SP/Util/VersionUtil.php | 2 +- .../Domain/Export/Services/XmlVerifyTest.php | 251 +++++++++++++++++ tests/SPT/UnitaryTestCase.php | 26 +- tests/res/import/data_syspass.xml | 27 +- .../res/import/data_syspass_invalid_hash.xml | 186 ++++++++++++ .../import/data_syspass_invalid_version.xml | 188 +++++++++++++ tests/res/import/data_syspass_valid_hash.xml | 91 ------ 13 files changed, 914 insertions(+), 389 deletions(-) rename lib/SP/Domain/Export/Ports/{XmlVerifyServiceInterface.php => XmlVerifyService.php} (75%) create mode 100644 lib/SP/Domain/Export/Services/XmlVerify.php delete mode 100644 lib/SP/Domain/Export/Services/XmlVerifyService.php create mode 100644 tests/SPT/Domain/Export/Services/XmlVerifyTest.php create mode 100644 tests/res/import/data_syspass_invalid_hash.xml create mode 100644 tests/res/import/data_syspass_invalid_version.xml delete mode 100644 tests/res/import/data_syspass_valid_hash.xml 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 - - - - - -