mirror of
https://github.com/nuxsmin/sysPass.git
synced 2026-03-06 08:34:16 +01:00
543 lines
18 KiB
PHP
543 lines
18 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
/*
|
|
* sysPass
|
|
*
|
|
* @author nuxsmin
|
|
* @link https://syspass.org
|
|
* @copyright 2012-2024, Rubén Domínguez nuxsmin@$syspass.org
|
|
*
|
|
* This file is part of sysPass.
|
|
*
|
|
* sysPass is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* sysPass is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
namespace SP\Tests\Domain\Export\Services;
|
|
|
|
use Defuse\Crypto\Exception\EnvironmentIsBrokenException;
|
|
use Defuse\Crypto\Key;
|
|
use Defuse\Crypto\KeyProtectedByPassword;
|
|
use DOMDocument;
|
|
use DOMElement;
|
|
use DOMException;
|
|
use PHPUnit\Framework\Attributes\Group;
|
|
use PHPUnit\Framework\Constraint\Callback;
|
|
use PHPUnit\Framework\MockObject\Exception;
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
|
use RuntimeException;
|
|
use SP\Core\Context\ContextException;
|
|
use SP\Domain\Common\Providers\Version;
|
|
use SP\Domain\Common\Services\ServiceException;
|
|
use SP\Domain\Core\Crypt\CryptInterface;
|
|
use SP\Domain\Core\Exceptions\CheckException;
|
|
use SP\Domain\Core\Exceptions\CryptException;
|
|
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\XmlTagExportService;
|
|
use SP\Domain\Export\Services\XmlExport;
|
|
use SP\Domain\File\Ports\DirectoryHandlerService;
|
|
use SP\Domain\User\Dtos\UserDataDto;
|
|
use SP\Infrastructure\File\FileException;
|
|
use SP\Tests\Generators\UserDataGenerator;
|
|
use SP\Tests\UnitaryTestCase;
|
|
|
|
/**
|
|
* Class XmlExportTest
|
|
*
|
|
*/
|
|
#[Group('unitary')]
|
|
class XmlExportTest extends UnitaryTestCase
|
|
{
|
|
use XmlTrait;
|
|
|
|
private PhpExtensionCheckerService|MockObject $phpExtensionCheckerService;
|
|
private MockObject|XmlClientExportService $xmlClientExportService;
|
|
private XmlAccountExportService|MockObject $xmlAccountExportService;
|
|
private XmlCategoryExportService|MockObject $xmlCategoryExportService;
|
|
private XmlTagExportService|MockObject $xmlTagExportService;
|
|
private CryptInterface|MockObject $crypt;
|
|
private XmlExport $xmlExport;
|
|
|
|
/**
|
|
* @throws ServiceException
|
|
* @throws Exception
|
|
* @throws FileException
|
|
* @throws CheckException
|
|
* @throws EnvironmentIsBrokenException
|
|
* @throws DOMException
|
|
*/
|
|
public function testExport()
|
|
{
|
|
$this->context->setUserData(
|
|
new UserDataDto(
|
|
UserDataGenerator::factory()
|
|
->buildUserData()
|
|
->mutate(
|
|
[
|
|
'login' => 'test_user',
|
|
'userGroup.name' => 'test_group',
|
|
]
|
|
)
|
|
)
|
|
);
|
|
|
|
$exportPath = $this->createMock(DirectoryHandlerService::class);
|
|
$exportPath->expects(self::once())
|
|
->method('checkOrCreate');
|
|
$exportPath->method('getPath')
|
|
->willReturn(TMP_PATH);
|
|
|
|
$password = self::$faker->password();
|
|
|
|
$this->xmlCategoryExportService
|
|
->expects(self::once())
|
|
->method('export')
|
|
->willReturn($this->createNode('TestCategories'));
|
|
|
|
$this->xmlClientExportService
|
|
->expects(self::once())
|
|
->method('export')
|
|
->willReturn($this->createNode('TestClients'));
|
|
|
|
$this->xmlTagExportService
|
|
->expects(self::once())
|
|
->method('export')
|
|
->willReturn($this->createNode('TestTags'));
|
|
|
|
$this->xmlAccountExportService
|
|
->expects(self::once())
|
|
->method('export')
|
|
->willReturn($this->createNode('TestAccounts'));
|
|
|
|
$this->checkCrypt($password);
|
|
|
|
$out = $this->xmlExport->export($exportPath, $password);
|
|
|
|
$this->assertNotEmpty($this->config->getConfigData()->getExportHash());
|
|
$this->assertFileExists($out);
|
|
|
|
$xml = new DOMDocument();
|
|
$xml->load($out, LIBXML_NOBLANKS);
|
|
|
|
$meta = $xml->documentElement->getElementsByTagName('Meta')->item(0)->childNodes;
|
|
|
|
$this->assertEquals(6, $meta->count());
|
|
|
|
$this->checkNodes(
|
|
$meta,
|
|
[
|
|
'Generator' => 'sysPass',
|
|
'Version' => Version::getVersionStringNormalized(),
|
|
'Time' => static fn(string $value) => self::assertTrue($value > 0),
|
|
'User' => 'test_user',
|
|
'Group' => 'test_group',
|
|
'Hash' => static fn(string $value) => self::assertNotEmpty($value),
|
|
]
|
|
);
|
|
|
|
$this->assertNotEmpty($meta->item(5)->attributes->getNamedItem('sign')->nodeValue);
|
|
|
|
$encrypted = $xml->documentElement->getElementsByTagName('Encrypted')->item(0);
|
|
|
|
$this->assertStringStartsWith('$2y$10$', $encrypted->attributes->getNamedItem('hash')->nodeValue);
|
|
|
|
$this->assertEquals(4, $encrypted->childNodes->count());
|
|
|
|
$this->assertEquals(
|
|
'encrypted_data_categories',
|
|
$encrypted->childNodes->item(0)->childNodes->item(0)->nodeValue
|
|
);
|
|
$this->assertNotEmpty($encrypted->childNodes->item(0)->attributes->getNamedItem('key')->nodeValue);
|
|
$this->assertEquals(
|
|
'encrypted_data_clients',
|
|
$encrypted->childNodes->item(1)->childNodes->item(0)->nodeValue
|
|
);
|
|
$this->assertNotEmpty($encrypted->childNodes->item(1)->attributes->getNamedItem('key')->nodeValue);
|
|
$this->assertEquals(
|
|
'encrypted_data_tags',
|
|
$encrypted->childNodes->item(2)->childNodes->item(0)->nodeValue
|
|
);
|
|
$this->assertNotEmpty($encrypted->childNodes->item(2)->attributes->getNamedItem('key')->nodeValue);
|
|
$this->assertEquals(
|
|
'encrypted_data_accounts',
|
|
$encrypted->childNodes->item(3)->childNodes->item(0)->nodeValue
|
|
);
|
|
$this->assertNotEmpty($encrypted->childNodes->item(3)->attributes->getNamedItem('key')->nodeValue);
|
|
}
|
|
|
|
/**
|
|
* @throws DOMException
|
|
*/
|
|
private function createNode(string $nodeName): DOMElement
|
|
{
|
|
return new DOMElement($nodeName, self::$faker->text());
|
|
}
|
|
|
|
/**
|
|
* @param string $password
|
|
* @param int $times
|
|
* @return void
|
|
* @throws EnvironmentIsBrokenException
|
|
*/
|
|
private function checkCrypt(string $password, int $times = 4): void
|
|
{
|
|
$securedKey = KeyProtectedByPassword::createRandomPasswordProtectedKey($password);
|
|
|
|
$this->crypt
|
|
->expects(self::exactly($times))
|
|
->method('makeSecuredKey')
|
|
->with($password, false)
|
|
->willReturn($securedKey);
|
|
|
|
$this->crypt
|
|
->expects(self::exactly($times))
|
|
->method('encrypt')
|
|
->with(
|
|
self::anything(),
|
|
new Callback(static function (Key $key) use ($password, $securedKey) {
|
|
return $key->saveToAsciiSafeString() === $securedKey->unlockKey($password)->saveToAsciiSafeString();
|
|
})
|
|
)
|
|
->willReturn(
|
|
'encrypted_data_categories',
|
|
'encrypted_data_clients',
|
|
'encrypted_data_tags',
|
|
'encrypted_data_accounts'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @throws CheckException
|
|
* @throws Exception
|
|
* @throws FileException
|
|
* @throws ServiceException
|
|
*/
|
|
public function testExportWithCheckDirectoryException()
|
|
{
|
|
$this->context->setUserData(
|
|
new UserDataDto(
|
|
UserDataGenerator::factory()
|
|
->buildUserData()
|
|
->mutate(
|
|
[
|
|
'login' => 'test_user',
|
|
'userGroup.name' => 'test_group',
|
|
]
|
|
)
|
|
)
|
|
);
|
|
|
|
$exportPath = $this->createMock(DirectoryHandlerService::class);
|
|
$exportPath->expects(self::once())
|
|
->method('checkOrCreate')
|
|
->willThrowException(CheckException::error('test'));
|
|
|
|
$this->expectException(CheckException::class);
|
|
$this->expectExceptionMessage('test');
|
|
|
|
$this->xmlExport->export($exportPath);
|
|
}
|
|
|
|
/**
|
|
* @throws CheckException
|
|
* @throws Exception
|
|
* @throws FileException
|
|
* @throws ServiceException
|
|
*/
|
|
public function testExportWithExportCategoryException()
|
|
{
|
|
$this->context->setUserData(
|
|
new UserDataDto(
|
|
UserDataGenerator::factory()
|
|
->buildUserData()
|
|
->mutate(
|
|
[
|
|
'login' => 'test_user',
|
|
'userGroup.name' => 'test_group',
|
|
]
|
|
)
|
|
)
|
|
);
|
|
|
|
$exportPath = $this->createMock(DirectoryHandlerService::class);
|
|
$exportPath->expects(self::once())
|
|
->method('checkOrCreate');
|
|
$exportPath->method('getPath')
|
|
->willReturn(TMP_PATH);
|
|
|
|
$password = self::$faker->password();
|
|
|
|
$this->xmlCategoryExportService
|
|
->expects(self::once())
|
|
->method('export')
|
|
->willThrowException(new RuntimeException('test'));
|
|
|
|
$this->expectException(ServiceException::class);
|
|
$this->expectExceptionMessage('Error while exporting');
|
|
|
|
$this->xmlExport->export($exportPath, $password);
|
|
}
|
|
|
|
/**
|
|
* @throws CheckException
|
|
* @throws Exception
|
|
* @throws FileException
|
|
* @throws ServiceException
|
|
* @throws DOMException
|
|
* @throws EnvironmentIsBrokenException
|
|
*/
|
|
public function testExportWithExportClientException()
|
|
{
|
|
$this->context->setUserData(
|
|
new UserDataDto(
|
|
UserDataGenerator::factory()
|
|
->buildUserData()
|
|
->mutate(
|
|
[
|
|
'login' => 'test_user',
|
|
'userGroup.name' => 'test_group',
|
|
]
|
|
)
|
|
)
|
|
);
|
|
|
|
$exportPath = $this->createMock(DirectoryHandlerService::class);
|
|
$exportPath->expects(self::once())
|
|
->method('checkOrCreate');
|
|
$exportPath->method('getPath')
|
|
->willReturn(TMP_PATH);
|
|
|
|
$this->xmlCategoryExportService
|
|
->expects(self::once())
|
|
->method('export')
|
|
->willReturn($this->createNode('TestCategories'));
|
|
|
|
$this->xmlClientExportService
|
|
->expects(self::once())
|
|
->method('export')
|
|
->willThrowException(new RuntimeException('test'));
|
|
|
|
$password = self::$faker->password();
|
|
|
|
$this->checkCrypt($password, 1);
|
|
|
|
$this->expectException(ServiceException::class);
|
|
$this->expectExceptionMessage('Error while exporting');
|
|
|
|
$this->xmlExport->export($exportPath, $password);
|
|
}
|
|
|
|
/**
|
|
* @throws CheckException
|
|
* @throws Exception
|
|
* @throws FileException
|
|
* @throws ServiceException
|
|
* @throws DOMException
|
|
* @throws EnvironmentIsBrokenException
|
|
*/
|
|
public function testExportWithExportTagException()
|
|
{
|
|
$this->context->setUserData(
|
|
new UserDataDto(
|
|
UserDataGenerator::factory()
|
|
->buildUserData()
|
|
->mutate(
|
|
[
|
|
'login' => 'test_user',
|
|
'userGroup.name' => 'test_group',
|
|
]
|
|
)
|
|
)
|
|
);
|
|
|
|
$exportPath = $this->createMock(DirectoryHandlerService::class);
|
|
$exportPath->expects(self::once())
|
|
->method('checkOrCreate');
|
|
$exportPath->method('getPath')
|
|
->willReturn(TMP_PATH);
|
|
|
|
$this->xmlCategoryExportService
|
|
->expects(self::once())
|
|
->method('export')
|
|
->willReturn($this->createNode('TestCategories'));
|
|
|
|
$this->xmlClientExportService
|
|
->expects(self::once())
|
|
->method('export')
|
|
->willReturn($this->createNode('TestClients'));
|
|
|
|
$this->xmlTagExportService
|
|
->expects(self::once())
|
|
->method('export')
|
|
->willThrowException(new RuntimeException('test'));
|
|
|
|
$password = self::$faker->password();
|
|
|
|
$this->checkCrypt($password, 2);
|
|
|
|
$this->expectException(ServiceException::class);
|
|
$this->expectExceptionMessage('Error while exporting');
|
|
|
|
$this->xmlExport->export($exportPath, $password);
|
|
}
|
|
|
|
/**
|
|
* @throws CheckException
|
|
* @throws Exception
|
|
* @throws FileException
|
|
* @throws ServiceException
|
|
* @throws DOMException
|
|
* @throws EnvironmentIsBrokenException
|
|
*/
|
|
public function testExportWithExportAccountException()
|
|
{
|
|
$this->context->setUserData(
|
|
new UserDataDto(
|
|
UserDataGenerator::factory()
|
|
->buildUserData()
|
|
->mutate(
|
|
[
|
|
'login' => 'test_user',
|
|
'userGroup.name' => 'test_group',
|
|
]
|
|
)
|
|
)
|
|
);
|
|
|
|
$exportPath = $this->createMock(DirectoryHandlerService::class);
|
|
$exportPath->expects(self::once())
|
|
->method('checkOrCreate');
|
|
$exportPath->method('getPath')
|
|
->willReturn(TMP_PATH);
|
|
|
|
$this->xmlCategoryExportService
|
|
->expects(self::once())
|
|
->method('export')
|
|
->willReturn($this->createNode('TestCategories'));
|
|
|
|
$this->xmlClientExportService
|
|
->expects(self::once())
|
|
->method('export')
|
|
->willReturn($this->createNode('TestClients'));
|
|
|
|
$this->xmlTagExportService
|
|
->expects(self::once())
|
|
->method('export')
|
|
->willReturn($this->createNode('TestTags'));
|
|
|
|
$this->xmlAccountExportService
|
|
->expects(self::once())
|
|
->method('export')
|
|
->willThrowException(new RuntimeException('test'));
|
|
|
|
$password = self::$faker->password();
|
|
|
|
$this->checkCrypt($password, 3);
|
|
|
|
$this->expectException(ServiceException::class);
|
|
$this->expectExceptionMessage('Error while exporting');
|
|
|
|
$this->xmlExport->export($exportPath, $password);
|
|
}
|
|
|
|
/**
|
|
* @throws ServiceException
|
|
* @throws Exception
|
|
* @throws FileException
|
|
* @throws CheckException
|
|
* @throws DOMException
|
|
*/
|
|
public function testExportWithCryptException()
|
|
{
|
|
$this->context->setUserData(
|
|
new UserDataDto(
|
|
UserDataGenerator::factory()
|
|
->buildUserData()
|
|
->mutate(
|
|
[
|
|
'login' => 'test_user',
|
|
'userGroup.name' => 'test_group',
|
|
]
|
|
)
|
|
)
|
|
);
|
|
|
|
$exportPath = $this->createMock(DirectoryHandlerService::class);
|
|
$exportPath->expects(self::once())
|
|
->method('checkOrCreate');
|
|
$exportPath->method('getPath')
|
|
->willReturn(TMP_PATH);
|
|
|
|
$password = self::$faker->password();
|
|
|
|
$this->xmlCategoryExportService
|
|
->expects(self::once())
|
|
->method('export')
|
|
->willReturn($this->createNode('TestCategories'));
|
|
|
|
$this->crypt
|
|
->expects(self::once())
|
|
->method('makeSecuredKey')
|
|
->willThrowException(CryptException::error('test'));
|
|
|
|
$this->expectException(ServiceException::class);
|
|
$this->expectExceptionMessage('test');
|
|
|
|
$this->xmlExport->export($exportPath, $password);
|
|
}
|
|
|
|
public function testBuildFilename()
|
|
{
|
|
$out = XmlExport::buildFilename('test', 'a_hash');
|
|
|
|
$this->assertEquals('test/sysPass_export-a_hash.xml', $out);
|
|
}
|
|
|
|
public function testBuildFilenameCompressed()
|
|
{
|
|
$out = XmlExport::buildFilename('test', 'a_hash', true);
|
|
|
|
$this->assertEquals('test/sysPass_export-a_hash.tar.gz', $out);
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
* @throws ServiceException
|
|
* @throws ContextException
|
|
*/
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->phpExtensionCheckerService = $this->createMock(PhpExtensionCheckerService::class);
|
|
$this->xmlClientExportService = $this->createMock(XmlClientExportService::class);
|
|
$this->xmlAccountExportService = $this->createMock(XmlAccountExportService::class);
|
|
$this->xmlCategoryExportService = $this->createMock(XmlCategoryExportService::class);
|
|
$this->xmlTagExportService = $this->createMock(XmlTagExportService::class);
|
|
$this->crypt = $this->createMock(CryptInterface::class);
|
|
|
|
$this->xmlExport = new XmlExport(
|
|
$this->application,
|
|
$this->phpExtensionCheckerService,
|
|
$this->xmlClientExportService,
|
|
$this->xmlAccountExportService,
|
|
$this->xmlCategoryExportService,
|
|
$this->xmlTagExportService,
|
|
$this->crypt
|
|
);
|
|
}
|
|
}
|