diff --git a/app/modules/api/Controllers/Account/CreateController.php b/app/modules/api/Controllers/Account/CreateController.php index aaee7487..6e350541 100644 --- a/app/modules/api/Controllers/Account/CreateController.php +++ b/app/modules/api/Controllers/Account/CreateController.php @@ -24,7 +24,6 @@ namespace SP\Modules\Api\Controllers\Account; - use Exception; use SP\Core\Events\Event; use SP\Core\Events\EventMessage; diff --git a/app/modules/web/Controllers/ConfigImport/ImportController.php b/app/modules/web/Controllers/ConfigImport/ImportController.php index 1faee48c..b7b9c53d 100644 --- a/app/modules/web/Controllers/ConfigImport/ImportController.php +++ b/app/modules/web/Controllers/ConfigImport/ImportController.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\Core\Events\EventMessage; use SP\Domain\Core\Acl\AclActionsInterface; use SP\Domain\Core\Acl\UnauthorizedPageException; use SP\Domain\Core\Exceptions\SessionTimeout; +use SP\Domain\Import\Dtos\ImportParamsDto; +use SP\Domain\Import\Ports\ImportParams; use SP\Domain\Import\Ports\ImportServiceInterface; use SP\Domain\Import\Services\FileImport; -use SP\Domain\Import\Services\ImportParams; use SP\Http\JsonMessage; use SP\Modules\Web\Controllers\SimpleControllerBase; use SP\Modules\Web\Controllers\Traits\JsonTrait; @@ -112,11 +113,11 @@ final class ImportController extends SimpleControllerBase } /** - * @return ImportParams + * @return \SP\Domain\Import\Dtos\ImportParams */ private function getImportParams(): ImportParams { - $importParams = new ImportParams(); + $importParams = new ImportParamsDto(); $importParams->setDefaultUser( $this->request->analyzeInt('import_defaultuser', $this->session->getUserData()->getId()) ); diff --git a/lib/SP/Domain/Common/Dtos/Dto.php b/lib/SP/Domain/Common/Dtos/Dto.php index a5c7b25a..d4d64511 100644 --- a/lib/SP/Domain/Common/Dtos/Dto.php +++ b/lib/SP/Domain/Common/Dtos/Dto.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. * @@ -34,7 +34,7 @@ abstract class Dto /** * Expose any property. This allows to get any property from dynamic calls. * - * @param string $property + * @param string $property * * @return mixed */ @@ -50,10 +50,10 @@ abstract class Dto /** * Set any property. This allows to set any property from dynamic calls. * - * @param string $property - * @param mixed $value + * @param string $property + * @param mixed $value * - * @return \SP\Domain\Common\Dtos\Dto|null Returns a new instance with the property set. + * @return Dto|null Returns a new instance with the property set. */ public function set(string $property, mixed $value): static|null { diff --git a/lib/SP/Domain/Import/Dtos/CsvImportParamsDto.php b/lib/SP/Domain/Import/Dtos/CsvImportParamsDto.php new file mode 100644 index 00000000..634a12bc --- /dev/null +++ b/lib/SP/Domain/Import/Dtos/CsvImportParamsDto.php @@ -0,0 +1,44 @@ +. + */ + +namespace SP\Domain\Import\Dtos; + +/** + * Class CsvImportParamsDto + */ +class CsvImportParamsDto extends ImportParamsDto +{ + public function __construct( + int $defaultUser, + int $defaultGroup, + private readonly string $delimiter = ';' + ) { + parent::__construct($defaultUser, $defaultGroup); + } + + public function getDelimiter(): string + { + return $this->delimiter; + } +} diff --git a/lib/SP/Domain/Import/Dtos/ImportParamsDto.php b/lib/SP/Domain/Import/Dtos/ImportParamsDto.php new file mode 100644 index 00000000..b6035561 --- /dev/null +++ b/lib/SP/Domain/Import/Dtos/ImportParamsDto.php @@ -0,0 +1,68 @@ +. + */ + +namespace SP\Domain\Import\Dtos; + +use SP\Domain\Import\Ports\ImportParams; + +/** + * Class ImportParamsDto + */ +class ImportParamsDto implements ImportParams +{ + /** + * @param int $defaultUser The default user to use as the owner of the imported items + * @param int $defaultGroup The default group to use as the owner of the imported items + * @param string|null $password The password used to encrypt the imported file + * @param string|null $masterPassword The master password used for the items encrypted + */ + public function __construct( + private readonly int $defaultUser, + private readonly int $defaultGroup, + private readonly ?string $password = null, + private readonly ?string $masterPassword = null + ) { + } + + + public function getPassword(): ?string + { + return $this->password; + } + + public function getDefaultGroup(): int + { + return $this->defaultGroup; + } + + public function getMasterPassword(): ?string + { + return $this->masterPassword; + } + + public function getDefaultUser(): int + { + return $this->defaultUser; + } +} diff --git a/lib/SP/Domain/Import/Ports/ImportParams.php b/lib/SP/Domain/Import/Ports/ImportParams.php new file mode 100644 index 00000000..97fbb4b7 --- /dev/null +++ b/lib/SP/Domain/Import/Ports/ImportParams.php @@ -0,0 +1,39 @@ +. + */ + +namespace SP\Domain\Import\Ports; + +/** + * Class ImportParamsDto + */ +interface ImportParams +{ + public function getPassword(): ?string; + + public function getDefaultGroup(): int; + + public function getMasterPassword(): ?string; + + public function getDefaultUser(): int; +} diff --git a/lib/SP/Domain/Import/Ports/ImportServiceInterface.php b/lib/SP/Domain/Import/Ports/ImportServiceInterface.php index d0092ef2..be1190fd 100644 --- a/lib/SP/Domain/Import/Ports/ImportServiceInterface.php +++ b/lib/SP/Domain/Import/Ports/ImportServiceInterface.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. * @@ -25,8 +25,7 @@ namespace SP\Domain\Import\Ports; use Exception; -use SP\Domain\Import\Services\FileImportInterface; -use SP\Domain\Import\Services\ImportParams; +use SP\Domain\Import\Services\FileImportService; /** * Esta clase es la encargada de importar cuentas. @@ -39,5 +38,5 @@ interface ImportServiceInterface * @return int Returns the total number of imported items * @throws Exception */ - public function doImport(ImportParams $importParams, FileImportInterface $fileImport): int; + public function doImport(ImportParams $importParams, FileImportService $fileImport): int; } diff --git a/lib/SP/Domain/Import/Services/CsvImport.php b/lib/SP/Domain/Import/Services/CsvImport.php index 59195b28..e9e96b6a 100644 --- a/lib/SP/Domain/Import/Services/CsvImport.php +++ b/lib/SP/Domain/Import/Services/CsvImport.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. * @@ -24,38 +24,139 @@ namespace SP\Domain\Import\Services; +use Exception; +use SP\Core\Application; use SP\Core\Events\Event; use SP\Core\Events\EventMessage; +use SP\Domain\Account\Dtos\AccountCreateDto; +use SP\Domain\Category\Models\Category; +use SP\Domain\Client\Models\Client; +use SP\Domain\Core\Crypt\CryptInterface; +use SP\Domain\Import\Dtos\CsvImportParamsDto; +use SP\Domain\Import\Ports\ImportParams; use SP\Infrastructure\File\FileException; -defined('APP_ROOT') || die(); +use function SP\__; +use function SP\__u; +use function SP\processException; /** - * Class CsvImport para importar cuentas desde archivos CSV - * - * @package SP + * Class CsvImport */ -final class CsvImport extends CsvImportBase implements ImportInterface +final class CsvImport extends ImportBase { + private const NUM_FIELDS = 7; + protected array $categories = []; + protected array $clients = []; + + public function __construct( + Application $application, + ImportHelper $importHelper, + CryptInterface $crypt, + private readonly FileImportService $fileImport + ) { + parent::__construct($application, $importHelper, $crypt); + } + /** - * Iniciar la importación desde CSV + * Import the data from a CSV file * - * @return $this|ImportInterface - * @throws ImportException + * @param CsvImportParamsDto|ImportParams $importParamsDto + * @return Import * @throws FileException + * @throws ImportException */ - public function doImport(): ImportInterface + public function doImport(CsvImportParamsDto|ImportParams $importParamsDto): Import { $this->eventDispatcher->notify( 'run.import.csv', new Event( $this, - EventMessage::factory()->addDescription(sprintf(__('Detected format: %s'), 'CSV')) + EventMessage::factory() + ->addDescription(sprintf(__('Detected format: %s'), 'CSV')) ) ); - $this->processAccounts(); + $this->processAccounts($importParamsDto); return $this; } + + /** + * @throws ImportException + * @throws FileException + */ + private function processAccounts(CsvImportParamsDto $importParamsDto): void + { + $line = 0; + + foreach ($this->fileImport->readFileToArrayFromCsv($importParamsDto->getDelimiter()) as $fields) { + $line++; + $numfields = count($fields); + + if ($numfields !== self::NUM_FIELDS) { + throw ImportException::error( + sprintf(__('Wrong number of fields (%d)'), $numfields), + sprintf(__('Please, check the CSV file format in line %s'), $line) + ); + } + + [ + $accountName, + $clientName, + $categoryName, + $url, + $login, + $password, + $notes, + ] = $fields; + + try { + if (empty($clientName) || empty($categoryName)) { + throw ImportException::error('Either client or category name not set'); + } + + $clientId = $this->addClient(new Client(['name' => $clientName])); + $categoryId = $this->addCategory(new Category(['name' => $categoryName])); + + $accountCreateDto = new AccountCreateDto( + name: $accountName, + login: $login, + clientId: $clientId, + categoryId: $categoryId, + pass: $password, + url: $url, + notes: $notes + ); + + $this->addAccount($accountCreateDto, $importParamsDto); + + $this->eventDispatcher->notify( + 'run.import.csv.process.account', + new Event( + $this, + EventMessage::factory() + ->addDetail(__u('Account imported'), $accountName) + ->addDetail(__u('Client'), $clientName) + ) + ); + } catch (Exception $e) { + processException($e); + + $this->eventDispatcher->notify( + 'exception', + new Event( + $e, + EventMessage::factory() + ->addDetail(__u('Error while importing the account'), $accountName) + ->addDetail(__u('Error while processing line'), $line) + ) + ); + } + } + + if ($line === 0) { + throw ImportException::error(__('No lines read from the file')); + } + } } diff --git a/lib/SP/Domain/Import/Services/CsvImportBase.php b/lib/SP/Domain/Import/Services/CsvImportBase.php deleted file mode 100644 index 84319ad6..00000000 --- a/lib/SP/Domain/Import/Services/CsvImportBase.php +++ /dev/null @@ -1,161 +0,0 @@ -. - */ - -namespace SP\Domain\Import\Services; - -use Exception; -use SP\Core\Application; -use SP\Core\Events\Event; -use SP\Core\Events\EventDispatcher; -use SP\Core\Events\EventMessage; -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\Infrastructure\File\FileException; - -defined('APP_ROOT') || die(); - -/** - * Clase CsvImportBase para base de clases de importación desde archivos CSV - * - * @package SP - */ -abstract class CsvImportBase -{ - use ImportTrait; - - protected int $numFields = 7; - protected FileImportInterface $fileImport; - protected EventDispatcher $eventDispatcher; - protected array $categories = []; - protected array $clients = []; - - public function __construct( - Application $application, - ImportHelper $importHelper, - FileImportInterface $fileImport, - ImportParams $importParams - ) { - $this->eventDispatcher = $application->getEventDispatcher(); - $this->accountService = $importHelper->getAccountService(); - $this->categoryService = $importHelper->getCategoryService(); - $this->clientService = $importHelper->getClientService(); - $this->tagService = $importHelper->getTagService(); - $this->fileImport = $fileImport; - $this->importParams = $importParams; - } - - /** - * Obtener los datos de las entradas de sysPass y crearlas - * - * @throws ImportException - * @throws FileException - */ - protected function processAccounts(): void - { - $line = 0; - - $handler = $this->fileImport->getFileHandler()->open(); - - while (($fields = fgetcsv($handler, 0, $this->importParams->getCsvDelimiter())) !== false) { - $line++; - $numfields = count($fields); - - // Comprobar el número de campos de la línea - if ($numfields !== $this->numFields) { - throw new ImportException( - sprintf(__('Wrong number of fields (%d)'), $numfields), - SPException::ERROR, - sprintf(__('Please, check the CSV file format in line %s'), $line) - ); - } - - // Asignar los valores del array a variables - [ - $accountName, - $clientName, - $categoryName, - $url, - $login, - $password, - $notes, - ] = $fields; - - try { - if (empty($clientName) || empty($categoryName)) { - throw new ImportException('Either client or category name not set'); - } - - // Obtener los ids de cliente y categoría - $clientId = $this->addClient(new Client(null, $clientName)); - $categoryId = $this->addCategory(new Category(null, $categoryName)); - - // Crear la nueva cuenta - $accountRequest = new AccountRequest(); - $accountRequest->name = $accountName; - $accountRequest->login = $login; - $accountRequest->clientId = $clientId; - $accountRequest->categoryId = $categoryId; - $accountRequest->notes = $notes; - $accountRequest->url = $url; - $accountRequest->pass = $password; - - $this->addAccount($accountRequest); - - $this->eventDispatcher->notify( - 'run.import.csv.process.account', - new Event( - $this, - EventMessage::factory() - ->addDetail(__u('Account imported'), $accountName) - ->addDetail(__u('Client'), $clientName) - ) - ); - } catch (Exception $e) { - processException($e); - - $this->eventDispatcher->notify( - 'exception', - new Event( - $e, - EventMessage::factory() - ->addDetail(__u('Error while importing the account'), $accountName) - ->addDetail(__u('Error while processing line'), $line) - ) - ); - } - } - - $this->fileImport->getFileHandler()->close(); - - if ($line === 0) { - throw new ImportException( - sprintf(__('Wrong number of fields (%d)'), 0), - SPException::ERROR, - sprintf(__('Please, check the CSV file format in line %s'), 0) - ); - } - } -} diff --git a/lib/SP/Domain/Import/Services/FileImport.php b/lib/SP/Domain/Import/Services/FileImport.php index 9fd3dd9f..249ec87e 100644 --- a/lib/SP/Domain/Import/Services/FileImport.php +++ b/lib/SP/Domain/Import/Services/FileImport.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. * @@ -31,21 +31,22 @@ use SP\Infrastructure\File\FileHandler; use SP\Infrastructure\File\FileHandlerInterface; use SP\Util\Util; -defined('APP_ROOT') || die(); +use function SP\__u; +use function SP\logger; /** * Clase FileImport encargada el leer archivos para su importación * * @package SP */ -final class FileImport implements FileImportInterface +final class FileImport implements FileImportService { private FileHandler $fileHandler; /** * FileImport constructor. * - * @param FileHandlerInterface $fileHandler Datos del archivo a importar + * @param FileHandlerInterface $fileHandler Datos del archivo a importar */ private function __construct(FileHandlerInterface $fileHandler) { @@ -56,7 +57,7 @@ final class FileImport implements FileImportInterface * @throws FileException * @throws SPException */ - public static function fromRequest(string $filename, RequestInterface $request): FileImportInterface + public static function fromRequest(string $filename, RequestInterface $request): FileImportService { return new self(self::checkFile($request->getFile($filename))); } @@ -64,7 +65,7 @@ final class FileImport implements FileImportInterface /** * Leer los datos del archivo. * - * @param array|null $file con los datos del archivo + * @param array|null $file con los datos del archivo * * @return FileHandlerInterface * @throws ImportException @@ -114,7 +115,7 @@ final class FileImport implements FileImportInterface return $this->fileHandler->getFileType(); } - public static function fromFilesystem(string $path): FileImportInterface + public static function fromFilesystem(string $path): FileImportService { return new self(new FileHandler($path)); } @@ -144,6 +145,24 @@ final class FileImport implements FileImportInterface ini_set('auto_detect_line_endings', true); } + /** + * Read a CSV file + * + * @throws FileException + */ + public function readFileToArrayFromCsv(string $delimiter): iterable + { + $this->autodetectEOL(); + + $handler = $this->fileHandler->open(); + + while (($fields = fgetcsv($handler, 0, $delimiter)) !== false) { + yield $fields; + } + + $this->fileHandler->close(); + } + /** * Leer los datos de un archivo subido a una cadena * diff --git a/lib/SP/Domain/Import/Services/FileImportInterface.php b/lib/SP/Domain/Import/Services/FileImportService.php similarity index 83% rename from lib/SP/Domain/Import/Services/FileImportInterface.php rename to lib/SP/Domain/Import/Services/FileImportService.php index c55ab5ed..8122a4a7 100644 --- a/lib/SP/Domain/Import/Services/FileImportInterface.php +++ b/lib/SP/Domain/Import/Services/FileImportService.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. * @@ -28,11 +28,9 @@ use SP\Infrastructure\File\FileException; use SP\Infrastructure\File\FileHandlerInterface; /** - * Clase FileImport encargada el leer archivos para su importación - * - * @package SP + * Interface FileImportService */ -interface FileImportInterface +interface FileImportService { public function getFilePath(): string; @@ -56,4 +54,11 @@ interface FileImportInterface public function readFileToString(): string; public function getFileHandler(): FileHandlerInterface; + + /** + * Read a CSV file + * + * @throws FileException + */ + public function readFileToArrayFromCsv(string $delimiter): iterable; } diff --git a/lib/SP/Domain/Import/Services/ImportInterface.php b/lib/SP/Domain/Import/Services/Import.php similarity index 79% rename from lib/SP/Domain/Import/Services/ImportInterface.php rename to lib/SP/Domain/Import/Services/Import.php index 9ef89983..227257d8 100644 --- a/lib/SP/Domain/Import/Services/ImportInterface.php +++ b/lib/SP/Domain/Import/Services/Import.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. * @@ -24,19 +24,20 @@ namespace SP\Domain\Import\Services; +use SP\Domain\Import\Ports\ImportParams; + /** * Interface ImportInterface * * @package Import */ -interface ImportInterface +interface Import { /** - * Iniciar la importación - * - * @return ImportInterface + * @param ImportParams $importParamsDto + * @return Import */ - public function doImport(): ImportInterface; + public function doImport(ImportParams $importParamsDto): Import; /** * Devolver el contador de objetos importados diff --git a/lib/SP/Domain/Import/Services/ImportTrait.php b/lib/SP/Domain/Import/Services/ImportBase.php similarity index 54% rename from lib/SP/Domain/Import/Services/ImportTrait.php rename to lib/SP/Domain/Import/Services/ImportBase.php index ad4dd467..1ebe2877 100644 --- a/lib/SP/Domain/Import/Services/ImportTrait.php +++ b/lib/SP/Domain/Import/Services/ImportBase.php @@ -24,41 +24,61 @@ namespace SP\Domain\Import\Services; -use Defuse\Crypto\Exception\CryptoException; -use SP\Core\Crypt\Crypt; -use SP\Domain\Account\Dtos\AccountRequest; +use SP\Core\Application; +use SP\Core\Crypt\Hash; +use SP\Domain\Account\Dtos\AccountCreateDto; use SP\Domain\Account\Ports\AccountService; use SP\Domain\Category\Models\Category; use SP\Domain\Category\Ports\CategoryService; use SP\Domain\Client\Models\Client; use SP\Domain\Client\Ports\ClientService; +use SP\Domain\Common\Services\Service; +use SP\Domain\Config\Ports\ConfigService; +use SP\Domain\Core\Crypt\CryptInterface; use SP\Domain\Core\Exceptions\ConstraintException; +use SP\Domain\Core\Exceptions\CryptException; use SP\Domain\Core\Exceptions\NoSuchPropertyException; use SP\Domain\Core\Exceptions\QueryException; use SP\Domain\Core\Exceptions\SPException; +use SP\Domain\Import\Ports\ImportParams; use SP\Domain\Tag\Models\Tag; use SP\Domain\Tag\Ports\TagServiceInterface; use SP\Infrastructure\Common\Repositories\DuplicatedItemException; +use function SP\__u; + /** - * Trait ImportTrait - * - * @package SP\Domain\Import\Services + * Class ImportBase */ -trait ImportTrait +abstract class ImportBase extends Service implements Import { protected int $version = 0; /** * @var bool Indica si el hash de la clave suministrada es igual a la actual */ - protected bool $mPassValidHash = false; - protected int $counter = 0; - protected ImportParams $importParams; - private AccountService $accountService; - private CategoryService $categoryService; - private ClientService $clientService; - private TagServiceInterface $tagService; - private array $items; + protected bool $mPassValidHash = false; + protected int $counter = 0; + protected readonly AccountService $accountService; + protected readonly CategoryService $categoryService; + protected readonly ClientService $clientService; + protected readonly TagServiceInterface $tagService; + protected readonly ConfigService $configService; + private array $items; + + public function __construct( + Application $application, + ImportHelper $importHelper, + private readonly CryptInterface $crypt + ) { + parent::__construct($application); + + $this->accountService = $importHelper->getAccountService(); + $this->categoryService = $importHelper->getCategoryService(); + $this->clientService = $importHelper->getClientService(); + $this->tagService = $importHelper->getTagService(); + $this->configService = $importHelper->getConfigService(); + } + /** * @return int @@ -71,66 +91,79 @@ trait ImportTrait /** * Añadir una cuenta desde un archivo importado. * - * @param AccountRequest $accountRequest - * - * @throws ImportException - * @throws SPException - * @throws CryptoException + * @param AccountCreateDto $accountCreateDto + * @param ImportParams $importParams * @throws ConstraintException + * @throws ImportException * @throws NoSuchPropertyException * @throws QueryException + * @throws SPException + * @throws CryptException */ - protected function addAccount(AccountRequest $accountRequest): void + protected function addAccount(AccountCreateDto $accountCreateDto, ImportParams $importParams): void { - if (empty($accountRequest->categoryId)) { + if (empty($accountCreateDto->getCategoryId())) { throw new ImportException(__u('Category Id not set. Unable to import account.')); } - if (empty($accountRequest->clientId)) { + if (empty($accountCreateDto->getClientId())) { throw new ImportException(__u('Client Id not set. Unable to import account.')); } - $accountRequest->userId = $this->importParams->getDefaultUser(); - $accountRequest->userGroupId = $this->importParams->getDefaultGroup(); + $hasValidHash = $this->validateHash($importParams); - if ($this->mPassValidHash === false - && !empty($this->importParams->getImportMasterPwd())) { + $dto = $accountCreateDto + ->set('userId', $importParams->getDefaultUser()) + ->set('userGroupId', $importParams->getDefaultGroup()); + + if ($hasValidHash === false && !empty($importParams->getMasterPassword())) { if ($this->version >= 210) { - $pass = Crypt::decrypt( - $accountRequest->pass, - $accountRequest->key, - $this->importParams->getImportMasterPwd() + $pass = $this->crypt->decrypt( + $accountCreateDto->getPass(), + $accountCreateDto->getKey(), + $importParams->getMasterPassword() ); - } else { - throw new ImportException(__u('The file was exported with an old sysPass version (<= 2.10).')); - } - $accountRequest->pass = $pass; - $accountRequest->key = ''; + $dto = $accountCreateDto->set('pass', $pass)->set('key', ''); + } else { + throw ImportException::error(__u('The file was exported with an old sysPass version (<= 2.10).')); + } } - $this->accountService->create($accountRequest); + $this->accountService->create($dto); $this->counter++; } + private function validateHash(ImportParams $importParams): bool + { + if (!empty($importParams->getMasterPassword())) { + return Hash::checkHashKey( + $importParams->getMasterPassword(), + $this->configService->getByParam('masterPwd') + ); + } + + return true; + } + /** * Añadir una categoría y devolver el Id * - * @param Category $categoryData + * @param Category $category * * @return int * @throws DuplicatedItemException * @throws SPException */ - protected function addCategory(Category $categoryData): int + protected function addCategory(Category $category): int { try { - $categoryId = $this->getWorkingItem('category', $categoryData->getName()); + $categoryId = $this->getWorkingItem('category', $category->getName()); - return $categoryId ?? $this->categoryService->create($categoryData); + return $categoryId ?? $this->categoryService->create($category); } catch (DuplicatedItemException $e) { - $itemData = $this->categoryService->getByName($categoryData->getName()); + $itemData = $this->categoryService->getByName($category->getName()); if ($itemData === null) { throw $e; @@ -141,8 +174,8 @@ trait ImportTrait } /** - * @param string $type - * @param string|int $value + * @param string $type + * @param string|int $value * * @return int|null */ @@ -152,9 +185,9 @@ trait ImportTrait } /** - * @param string $type - * @param string|int $value - * @param int $id + * @param string $type + * @param string|int $value + * @param int $id * * @return int */ @@ -172,20 +205,20 @@ trait ImportTrait /** * Añadir un cliente y devolver el Id * - * @param Client $clientData + * @param Client $client * * @return int * @throws DuplicatedItemException * @throws SPException */ - protected function addClient(Client $clientData): int + protected function addClient(Client $client): int { try { - $clientId = $this->getWorkingItem('client', $clientData->getName()); + $clientId = $this->getWorkingItem('client', $client->getName()); - return $clientId ?? $this->clientService->create($clientData); + return $clientId ?? $this->clientService->create($client); } catch (DuplicatedItemException $e) { - $itemData = $this->clientService->getByName($clientData->getName()); + $itemData = $this->clientService->getByName($client->getName()); if ($itemData === null) { throw $e; @@ -202,19 +235,19 @@ trait ImportTrait /** * Añadir una etiqueta y devolver el Id * - * @param Tag $tagData + * @param Tag $tag * * @return int * @throws SPException */ - protected function addTag(Tag $tagData): int + protected function addTag(Tag $tag): int { try { - $tagId = $this->getWorkingItem('tag', $tagData->getName()); + $tagId = $this->getWorkingItem('tag', $tag->getName()); - return $tagId ?? $this->tagService->create($tagData); + return $tagId ?? $this->tagService->create($tag); } catch (DuplicatedItemException $e) { - $itemData = $this->tagService->getByName($tagData->getName()); + $itemData = $this->tagService->getByName($tag->getName()); if ($itemData === null) { throw $e; diff --git a/lib/SP/Domain/Import/Services/ImportHelper.php b/lib/SP/Domain/Import/Services/ImportHelper.php index 71eadb40..788647d1 100644 --- a/lib/SP/Domain/Import/Services/ImportHelper.php +++ b/lib/SP/Domain/Import/Services/ImportHelper.php @@ -24,10 +24,10 @@ namespace SP\Domain\Import\Services; - use SP\Domain\Account\Ports\AccountService; use SP\Domain\Category\Ports\CategoryService; use SP\Domain\Client\Ports\ClientService; +use SP\Domain\Config\Ports\ConfigService; use SP\Domain\Tag\Ports\TagServiceInterface; /** @@ -35,21 +35,14 @@ use SP\Domain\Tag\Ports\TagServiceInterface; */ final class ImportHelper { - private AccountService $accountService; - private CategoryService $categoryService; - private ClientService $clientService; - private TagServiceInterface $tagService; public function __construct( - AccountService $accountService, - CategoryService $categoryService, - ClientService $clientService, - TagServiceInterface $tagService + private readonly AccountService $accountService, + private readonly CategoryService $categoryService, + private readonly ClientService $clientService, + private readonly TagServiceInterface $tagService, + private readonly ConfigService $configService ) { - $this->accountService = $accountService; - $this->categoryService = $categoryService; - $this->clientService = $clientService; - $this->tagService = $tagService; } public function getAccountService(): AccountService @@ -71,4 +64,9 @@ final class ImportHelper { return $this->tagService; } + + public function getConfigService(): ConfigService + { + return $this->configService; + } } diff --git a/lib/SP/Domain/Import/Services/ImportParams.php b/lib/SP/Domain/Import/Services/ImportParams.php deleted file mode 100644 index 3ca03d44..00000000 --- a/lib/SP/Domain/Import/Services/ImportParams.php +++ /dev/null @@ -1,90 +0,0 @@ -. - */ - -namespace SP\Domain\Import\Services; - - -/** - * Class ImportParams - * - * @package SP\Domain\Import\Services - */ -final class ImportParams -{ - protected ?string $importPwd = null; - protected ?string $importMasterPwd = null; - protected int $defaultUser = 0; - protected int $defaultGroup = 0; - protected string $csvDelimiter = ';'; - - public function getImportPwd(): ?string - { - return $this->importPwd; - } - - public function setImportPwd(string $importPwd): void - { - $this->importPwd = $importPwd; - } - - public function getDefaultGroup(): int - { - return $this->defaultGroup; - } - - public function setDefaultGroup(int $defaultGroup): void - { - $this->defaultGroup = $defaultGroup; - } - - public function getCsvDelimiter(): string - { - return $this->csvDelimiter; - } - - public function setCsvDelimiter(string $csvDelimiter): void - { - $this->csvDelimiter = $csvDelimiter; - } - - public function getImportMasterPwd(): ?string - { - return $this->importMasterPwd; - } - - public function setImportMasterPwd(string $importMasterPwd): void - { - $this->importMasterPwd = $importMasterPwd; - } - - public function getDefaultUser(): int - { - return $this->defaultUser; - } - - public function setDefaultUser(int $defaultUser): void - { - $this->defaultUser = $defaultUser; - } -} \ No newline at end of file diff --git a/lib/SP/Domain/Import/Services/ImportService.php b/lib/SP/Domain/Import/Services/ImportService.php index 87a084d3..a5c5bd16 100644 --- a/lib/SP/Domain/Import/Services/ImportService.php +++ b/lib/SP/Domain/Import/Services/ImportService.php @@ -30,6 +30,8 @@ use SP\Core\Application; use SP\Domain\Common\Services\Service; use SP\Domain\Config\Ports\ConfigService; use SP\Domain\Core\Exceptions\SPException; +use SP\Domain\Import\Dtos\ImportParamsDto; +use SP\Domain\Import\Ports\ImportParams; use SP\Domain\Import\Ports\ImportServiceInterface; use SP\Infrastructure\Database\DatabaseInterface; use SP\Infrastructure\File\FileException; @@ -51,9 +53,9 @@ final class ImportService extends Service implements ImportServiceInterface 'text/xml', ]; - private ?ImportParams $importParams = null; - private ?FileImportInterface $fileImport = null; - private Application $application; + private ?ImportParamsDto $importParams = null; + private ?FileImportService $fileImport = null; + private Application $application; private ImportHelper $importHelper; private ConfigService $configService; private DatabaseInterface $database; @@ -81,7 +83,7 @@ final class ImportService extends Service implements ImportServiceInterface * @return int Returns the total number of imported items * @throws Exception */ - public function doImport(ImportParams $importParams, FileImportInterface $fileImport): int + public function doImport(ImportParams $importParams, FileImportService $fileImport): int { $this->importParams = $importParams; $this->fileImport = $fileImport; @@ -96,7 +98,7 @@ final class ImportService extends Service implements ImportServiceInterface * @throws ImportException * @throws FileException */ - protected function selectImportType(): ImportInterface + protected function selectImportType(): Import { $fileType = $this->fileImport->getFileType(); diff --git a/lib/SP/Domain/Import/Services/KeepassImport.php b/lib/SP/Domain/Import/Services/KeepassImport.php index 1edf312b..eeecb01e 100644 --- a/lib/SP/Domain/Import/Services/KeepassImport.php +++ b/lib/SP/Domain/Import/Services/KeepassImport.php @@ -40,7 +40,7 @@ defined('APP_ROOT') || die(); /** * Esta clase es la encargada de importar cuentas desde KeePass */ -final class KeepassImport extends XmlImportBase implements ImportInterface +final class KeepassImport extends XmlImportBase implements Import { private array $items = []; @@ -49,7 +49,7 @@ final class KeepassImport extends XmlImportBase implements ImportInterface * * @throws SPException */ - public function doImport(): ImportInterface + public function doImport(): Import { $this->eventDispatcher->notify( 'run.import.keepass', diff --git a/lib/SP/Domain/Import/Services/SyspassImport.php b/lib/SP/Domain/Import/Services/SyspassImport.php index bce6929f..02579b60 100644 --- a/lib/SP/Domain/Import/Services/SyspassImport.php +++ b/lib/SP/Domain/Import/Services/SyspassImport.php @@ -47,14 +47,14 @@ defined('APP_ROOT') || die(); /** * Esta clase es la encargada de importar cuentas desde sysPass */ -final class SyspassImport extends XmlImportBase implements ImportInterface +final class SyspassImport extends XmlImportBase implements Import { /** * Iniciar la importación desde sysPass. * * @throws ImportException */ - public function doImport(): ImportInterface + public function doImport(): Import { try { $this->eventDispatcher->notify( @@ -62,9 +62,9 @@ final class SyspassImport extends XmlImportBase implements ImportInterface new Event($this, EventMessage::factory()->addDescription(__u('sysPass XML Import'))) ); - if (!empty($this->importParams->getImportMasterPwd())) { + if (!empty($this->importParams->getMasterPassword())) { $this->mPassValidHash = Hash::checkHashKey( - $this->importParams->getImportMasterPwd(), + $this->importParams->getMasterPassword(), $this->configService->getByParam('masterPwd') ); } @@ -72,7 +72,7 @@ final class SyspassImport extends XmlImportBase implements ImportInterface $this->version = $this->getXmlVersion(); if ($this->detectEncrypted()) { - if ($this->importParams->getImportPwd() === '') { + if ($this->importParams->getPassword() === '') { throw new ImportException(__u('Encryption password not set'), SPException::INFO); } @@ -135,7 +135,7 @@ final class SyspassImport extends XmlImportBase implements ImportInterface ->getAttribute('hash'); if (!empty($hash) - && !Hash::checkHashKey($this->importParams->getImportPwd(), $hash) + && !Hash::checkHashKey($this->importParams->getPassword(), $hash) ) { throw new ImportException(__u('Wrong encryption password')); } @@ -147,14 +147,14 @@ final class SyspassImport extends XmlImportBase implements ImportInterface $xmlDecrypted = Crypt::decrypt( base64_decode($node->nodeValue), $node->getAttribute('key'), - $this->importParams->getImportPwd() + $this->importParams->getPassword() ); } else { if ($this->version >= 320) { $xmlDecrypted = Crypt::decrypt( $node->nodeValue, $node->getAttribute('key'), - $this->importParams->getImportPwd() + $this->importParams->getPassword() ); } else { throw new ImportException(__u('The file was exported with an old sysPass version (<= 2.10).')); @@ -199,7 +199,7 @@ final class SyspassImport extends XmlImportBase implements ImportInterface */ protected function checkIntegrity(): void { - $key = $this->importParams->getImportPwd() ?: sha1($this->configData->getPasswordSalt()); + $key = $this->importParams->getPassword() ?: sha1($this->configData->getPasswordSalt()); if (!XmlVerify::checkXmlHash($this->xmlDOM, $key)) { $this->eventDispatcher->notify( diff --git a/lib/SP/Domain/Import/Services/XmlFileImport.php b/lib/SP/Domain/Import/Services/XmlFileImport.php index a5561341..db231f63 100644 --- a/lib/SP/Domain/Import/Services/XmlFileImport.php +++ b/lib/SP/Domain/Import/Services/XmlFileImport.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. * @@ -41,12 +41,12 @@ final class XmlFileImport implements XmlFileImportInterface /** * XmlFileImport constructor. * - * @param FileImportInterface $fileImport + * @param FileImportService $fileImport * * @throws ImportException * @throws FileException */ - public function __construct(FileImportInterface $fileImport) + public function __construct(FileImportService $fileImport) { $this->fileImport = $fileImport; diff --git a/lib/SP/Domain/Import/Services/XmlImport.php b/lib/SP/Domain/Import/Services/XmlImport.php index 079481e2..c628d360 100644 --- a/lib/SP/Domain/Import/Services/XmlImport.php +++ b/lib/SP/Domain/Import/Services/XmlImport.php @@ -27,6 +27,8 @@ namespace SP\Domain\Import\Services; use SP\Core\Application; use SP\Domain\Config\Ports\ConfigService; use SP\Domain\Core\Exceptions\SPException; +use SP\Domain\Import\Dtos\ImportParamsDto; +use SP\Domain\Import\Ports\ImportParams; defined('APP_ROOT') || die(); @@ -38,9 +40,9 @@ defined('APP_ROOT') || die(); */ final class XmlImport implements XmlImportInterface { - private XmlFileImport $xmlFileImport; - private ImportParams $importParams; - private ImportHelper $importHelper; + private XmlFileImport $xmlFileImport; + private ImportParamsDto $importParams; + private ImportHelper $importHelper; private ConfigService $configService; private Application $application; @@ -52,7 +54,7 @@ final class XmlImport implements XmlImportInterface ImportHelper $importHelper, ConfigService $configService, XmlFileImportInterface $xmlFileImport, - ImportParams $importParams + ImportParams $importParams ) { $this->application = $application; $this->importHelper = $importHelper; @@ -67,7 +69,7 @@ final class XmlImport implements XmlImportInterface * @throws ImportException * @throws SPException */ - public function doImport(): ImportInterface + public function doImport(): Import { $format = $this->xmlFileImport->detectXMLFormat(); diff --git a/lib/SP/Domain/Import/Services/XmlImportBase.php b/lib/SP/Domain/Import/Services/XmlImportBase.php index 4c6528f8..6d458eae 100644 --- a/lib/SP/Domain/Import/Services/XmlImportBase.php +++ b/lib/SP/Domain/Import/Services/XmlImportBase.php @@ -27,45 +27,36 @@ namespace SP\Domain\Import\Services; use DOMDocument; use DOMElement; use SP\Core\Application; -use SP\Core\Events\EventDispatcher; use SP\Domain\Config\Ports\ConfigDataInterface; -use SP\Domain\Config\Ports\ConfigService; +use SP\Domain\Core\Crypt\CryptInterface; use SP\Domain\Core\Exceptions\SPException; +use SP\Domain\Import\Ports\ImportParams; /** * Class XmlImportBase * * @package SP\Domain\Import\Services */ -abstract class XmlImportBase +abstract class XmlImportBase extends ImportBase { - use ImportTrait; - - protected XmlFileImportInterface $xmlFileImport; - protected DOMDocument $xmlDOM; - protected EventDispatcher $eventDispatcher; - protected ConfigService $configService; - protected ConfigDataInterface $configData; + protected readonly XmlFileImportInterface $xmlFileImport; + protected readonly DOMDocument $xmlDOM; + protected readonly ConfigDataInterface $configData; + protected readonly ImportParams $importParams; /** * ImportBase constructor. */ public function __construct( - Application $application, - ImportHelper $importHelper, - ConfigService $configService, + Application $application, + ImportHelper $importHelper, + CryptInterface $crypt, XmlFileImportInterface $xmlFileImport, - ImportParams $importParams + ImportParams $importParams ) { - $this->eventDispatcher = $application->getEventDispatcher(); + parent::__construct($application, $importHelper, $crypt); $this->xmlFileImport = $xmlFileImport; $this->importParams = $importParams; - $this->accountService = $importHelper->getAccountService(); - $this->categoryService = $importHelper->getCategoryService(); - $this->clientService = $importHelper->getClientService(); - $this->tagService = $importHelper->getTagService(); - $this->configService = $configService; - $this->configData = $application->getConfig()->getConfigData(); $this->xmlDOM = $xmlFileImport->getXmlDOM(); } @@ -73,10 +64,10 @@ abstract class XmlImportBase /** * Obtener los datos de los nodos * - * @param string $nodeName Nombre del nodo principal - * @param string $childNodeName Nombre de los nodos hijos - * @param callable $callback Método a ejecutar - * @param bool $required Indica si el nodo es requerido + * @param string $nodeName Nombre del nodo principal + * @param string $childNodeName Nombre de los nodos hijos + * @param callable $callback Método a ejecutar + * @param bool $required Indica si el nodo es requerido * * @throws ImportException */ @@ -84,7 +75,7 @@ abstract class XmlImportBase string $nodeName, string $childNodeName, callable $callback, - bool $required = true + bool $required = true ): void { $nodeList = $this->xmlDOM->getElementsByTagName($nodeName); diff --git a/lib/SP/Domain/Import/Services/XmlImportInterface.php b/lib/SP/Domain/Import/Services/XmlImportInterface.php index 9cbe4ce9..3ed48643 100644 --- a/lib/SP/Domain/Import/Services/XmlImportInterface.php +++ b/lib/SP/Domain/Import/Services/XmlImportInterface.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. * @@ -30,6 +30,6 @@ namespace SP\Domain\Import\Services; * * @package SP */ -interface XmlImportInterface extends ImportInterface +interface XmlImportInterface extends Import { } diff --git a/tests/SPT/Domain/Import/Services/CsvImportTest.php b/tests/SPT/Domain/Import/Services/CsvImportTest.php new file mode 100644 index 00000000..cfcbcfa3 --- /dev/null +++ b/tests/SPT/Domain/Import/Services/CsvImportTest.php @@ -0,0 +1,321 @@ +. + */ + +namespace SPT\Domain\Import\Services; + +use PHPUnit\Framework\Constraint\Callback; +use PHPUnit\Framework\MockObject\MockObject; +use SP\Domain\Account\Dtos\AccountCreateDto; +use SP\Domain\Account\Ports\AccountService; +use SP\Domain\Category\Models\Category; +use SP\Domain\Category\Ports\CategoryService; +use SP\Domain\Client\Models\Client; +use SP\Domain\Client\Ports\ClientService; +use SP\Domain\Config\Ports\ConfigService; +use SP\Domain\Core\Crypt\CryptInterface; +use SP\Domain\Import\Dtos\CsvImportParamsDto; +use SP\Domain\Import\Services\CsvImport; +use SP\Domain\Import\Services\FileImportService; +use SP\Domain\Import\Services\ImportException; +use SP\Domain\Import\Services\ImportHelper; +use SP\Domain\Tag\Ports\TagServiceInterface; +use SP\Infrastructure\File\FileException; +use SPT\UnitaryTestCase; + +/** + * Class CsvImportTest + * + * @group unitary + */ +class CsvImportTest extends UnitaryTestCase +{ + + private AccountService|MockObject $accountService; + private MockObject|CategoryService $categoryService; + private ClientService|MockObject $clientService; + private CryptInterface|MockObject $crypt; + private FileImportService|MockObject $fileImportService; + private CsvImport $csvImport; + + + /** + * @throws ImportException + * @throws FileException + */ + public function testDoImport() + { + $params = new CsvImportParamsDto(1, 1); + $accounts = static function () { + yield ['Account_name', 'Client_name', 'Category_name', 'a_url', 'a_login', 'a_password', 'a_note']; + yield ['Account_name', 'Client_name', 'Category_name', 'a_url', 'a_login', 'a_password', 'a_note']; + }; + + $this->fileImportService + ->expects(self::once()) + ->method('readFileToArrayFromCsv') + ->with($params->getDelimiter()) + ->willReturnCallback($accounts); + + $this->clientService + ->expects(self::exactly(2)) + ->method('create') + ->with( + new Callback(static function (Client $client) { + return $client->getName() === 'Client_name'; + }) + ) + ->willReturn(100); + + $this->categoryService + ->expects(self::exactly(2)) + ->method('create') + ->with( + new Callback(static function (Category $category) { + return $category->getName() === 'Category_name'; + }) + ) + ->willReturn(200); + + $this->accountService + ->expects(self::exactly(2)) + ->method('create') + ->with( + new Callback(static function (AccountCreateDto $dto) { + return $dto->getName() === 'Account_name' + && $dto->getLogin() === 'a_login' + && $dto->getClientId() === 100 + && $dto->getCategoryId() === 200 + && $dto->getPass() === 'a_password' + && $dto->getNotes() === 'a_note' + && $dto->getUrl() === 'a_url'; + }) + ); + + $this->csvImport->doImport($params); + } + + /** + * @throws ImportException + * @throws FileException + */ + public function testDoImportWithWrongFields() + { + $params = new CsvImportParamsDto(1, 1); + $accounts = static function () { + yield ['Account_name', 'Client_name', 'Category_name', 'a_url', 'a_login', 'a_password']; + yield ['Account_name', 'Client_name', 'Category_name', 'a_url', 'a_login', 'a_password', 'a_note']; + }; + + $this->fileImportService + ->expects(self::once()) + ->method('readFileToArrayFromCsv') + ->with($params->getDelimiter()) + ->willReturnCallback($accounts); + + $this->clientService + ->expects(self::never()) + ->method('create'); + + $this->categoryService + ->expects(self::never()) + ->method('create'); + + $this->accountService + ->expects(self::never()) + ->method('create'); + + $this->expectException(ImportException::class); + $this->expectExceptionMessage('Wrong number of fields (6)'); + + $this->csvImport->doImport($params); + } + + /** + * @throws ImportException + * @throws FileException + */ + public function testDoImportWithoutLines() + { + $params = new CsvImportParamsDto(1, 1); + + $this->fileImportService + ->expects(self::once()) + ->method('readFileToArrayFromCsv') + ->with($params->getDelimiter()) + ->willReturn([]); + + $this->clientService + ->expects(self::never()) + ->method('create'); + + $this->categoryService + ->expects(self::never()) + ->method('create'); + + $this->accountService + ->expects(self::never()) + ->method('create'); + + $this->expectException(ImportException::class); + $this->expectExceptionMessage('No lines read from the file'); + + $this->csvImport->doImport($params); + } + + + /** + * @throws ImportException + * @throws FileException + */ + public function testDoImportWithEmptyClient() + { + $params = new CsvImportParamsDto(1, 1); + $accounts = static function () { + yield ['Account_name', '', 'Category_name', 'a_url', 'a_login', 'a_password', 'a_note']; + yield ['Account_name', 'Client_name', 'Category_name', 'a_url', 'a_login', 'a_password', 'a_note']; + }; + + $this->fileImportService + ->expects(self::once()) + ->method('readFileToArrayFromCsv') + ->with($params->getDelimiter()) + ->willReturnCallback($accounts); + + $this->clientService + ->expects(self::once()) + ->method('create') + ->with( + new Callback(static function (Client $client) { + return $client->getName() === 'Client_name'; + }) + ) + ->willReturn(100); + + $this->categoryService + ->expects(self::once()) + ->method('create') + ->with( + new Callback(static function (Category $category) { + return $category->getName() === 'Category_name'; + }) + ) + ->willReturn(200); + + $this->accountService + ->expects(self::once()) + ->method('create') + ->with( + new Callback(static function (AccountCreateDto $dto) { + return $dto->getName() === 'Account_name' + && $dto->getLogin() === 'a_login' + && $dto->getClientId() === 100 + && $dto->getCategoryId() === 200 + && $dto->getPass() === 'a_password' + && $dto->getNotes() === 'a_note' + && $dto->getUrl() === 'a_url'; + }) + ); + + $this->csvImport->doImport($params); + } + + /** + * @throws ImportException + * @throws FileException + */ + public function testDoImportWithEmptyCategory() + { + $params = new CsvImportParamsDto(1, 1); + $accounts = static function () { + yield ['Account_name', 'Client_name', '', 'a_url', 'a_login', 'a_password', 'a_note']; + yield ['Account_name', 'Client_name', 'Category_name', 'a_url', 'a_login', 'a_password', 'a_note']; + }; + + $this->fileImportService + ->expects(self::once()) + ->method('readFileToArrayFromCsv') + ->with($params->getDelimiter()) + ->willReturnCallback($accounts); + + $this->clientService + ->expects(self::once()) + ->method('create') + ->with( + new Callback(static function (Client $client) { + return $client->getName() === 'Client_name'; + }) + ) + ->willReturn(100); + + $this->categoryService + ->expects(self::once()) + ->method('create') + ->with( + new Callback(static function (Category $category) { + return $category->getName() === 'Category_name'; + }) + ) + ->willReturn(200); + + $this->accountService + ->expects(self::once()) + ->method('create') + ->with( + new Callback(static function (AccountCreateDto $dto) { + return $dto->getName() === 'Account_name' + && $dto->getLogin() === 'a_login' + && $dto->getClientId() === 100 + && $dto->getCategoryId() === 200 + && $dto->getPass() === 'a_password' + && $dto->getNotes() === 'a_note' + && $dto->getUrl() === 'a_url'; + }) + ); + + $this->csvImport->doImport($params); + } + + protected function setUp(): void + { + parent::setUp(); + + $this->accountService = $this->createMock(AccountService::class); + $this->categoryService = $this->createMock(CategoryService::class); + $this->clientService = $this->createMock(ClientService::class); + $this->tagService = $this->createMock(TagServiceInterface::class); + + $importHelper = new ImportHelper( + $this->accountService, + $this->categoryService, + $this->clientService, + $this->createMock(TagServiceInterface::class), + $this->createMock(ConfigService::class) + ); + + $this->crypt = $this->createMock(CryptInterface::class); + $this->fileImportService = $this->createMock(FileImportService::class); + + $this->csvImport = new CsvImport($this->application, $importHelper, $this->crypt, $this->fileImportService); + } +} diff --git a/tests/SPT/PHPUnitHelper.php b/tests/SPT/PHPUnitHelper.php index f75f5212..9a3c47c9 100644 --- a/tests/SPT/PHPUnitHelper.php +++ b/tests/SPT/PHPUnitHelper.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. * @@ -39,8 +39,8 @@ use PHPUnit\Framework\Constraint\Constraint; trait PHPUnitHelper { /** - * @param array $firstCallArguments - * @param array ...$consecutiveCallsArguments + * @param array $firstCallArguments + * @param array ...$consecutiveCallsArguments * * @return iterable */ @@ -89,4 +89,19 @@ trait PHPUnitHelper ); } } + + /** + * Return a Callback that implements a generator function + * + * @param array $values + * @return Callback + */ + public static function withGenerator(array $values): Callback + { + return new Callback(function () use ($values) { + foreach ($values as $value) { + yield $value; + } + }); + } }