mirror of
https://github.com/nuxsmin/sysPass.git
synced 2026-02-20 01:41:27 +01:00
* [ADD] Master password update CLI command and tests.
* [MOD] Code refactoring. Signed-off-by: Rubén D <nuxsmin@syspass.org>
This commit is contained in:
280
app/modules/cli/Commands/Crypt/UpdateMasterPasswordCommand.php
Normal file
280
app/modules/cli/Commands/Crypt/UpdateMasterPasswordCommand.php
Normal file
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
/*
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
* @link https://syspass.org
|
||||
* @copyright 2012-2021, 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\Modules\Cli\Commands\Crypt;
|
||||
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use RuntimeException;
|
||||
use SP\Config\Config;
|
||||
use SP\Modules\Cli\Commands\CommandBase;
|
||||
use SP\Modules\Cli\Commands\Validators;
|
||||
use SP\Services\Config\ConfigService;
|
||||
use SP\Services\Crypt\MasterPassService;
|
||||
use SP\Services\Crypt\UpdateMasterPassRequest;
|
||||
use SP\Util\Util;
|
||||
use Symfony\Component\Console\Command\LockableTrait;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\StyleInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
/**
|
||||
* Class CryptCommand
|
||||
*
|
||||
* @package SP\Modules\Cli\Commands\Crypt
|
||||
*/
|
||||
final class UpdateMasterPasswordCommand extends CommandBase
|
||||
{
|
||||
use LockableTrait;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public static array $envVarsMapping = [
|
||||
'currentMasterPassword' => 'CURRENT_MASTER_PASSWORD',
|
||||
'masterPassword' => 'MASTER_PASSWORD',
|
||||
'update' => 'UPDATE',
|
||||
];
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $defaultName = 'sp:crypt:update-master-password';
|
||||
/**
|
||||
* @var MasterPassService
|
||||
*/
|
||||
private MasterPassService $masterPassService;
|
||||
/**
|
||||
* @var ConfigService
|
||||
*/
|
||||
private ConfigService $configService;
|
||||
|
||||
public function __construct(MasterPassService $masterPassService,
|
||||
ConfigService $configService,
|
||||
LoggerInterface $logger,
|
||||
Config $config)
|
||||
{
|
||||
$this->masterPassService = $masterPassService;
|
||||
$this->configService = $configService;
|
||||
|
||||
parent::__construct($logger, $config);
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription(__('Update sysPass master password'))
|
||||
->setHelp(__('This command updates sysPass master password for all the encrypted data'))
|
||||
->addOption('masterPassword',
|
||||
null,
|
||||
InputOption::VALUE_REQUIRED,
|
||||
__('The new master password to encrypt the data'))
|
||||
->addOption('currentMasterPassword',
|
||||
null,
|
||||
InputOption::VALUE_REQUIRED,
|
||||
__('The current master password'))
|
||||
->addOption('update',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
__('Skip asking to confirm the update'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$style = new SymfonyStyle($input, $output);
|
||||
|
||||
if (!$this->lock()) {
|
||||
$style->warning(__('The command is already running in another process'));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->checkInstalled();
|
||||
$this->checkMaintenance();
|
||||
|
||||
$request = new UpdateMasterPassRequest(
|
||||
$this->getCurrentMasterPassword($input, $style),
|
||||
$this->getMasterPassword($input, $style),
|
||||
$this->configService->getByParam(MasterPassService::PARAM_MASTER_PASS_HASH)
|
||||
);
|
||||
|
||||
if (!$this->getUpdate($input, $style)) {
|
||||
$this->logger->debug(__u('Master password update aborted'));
|
||||
$style->info(__('Master password update aborted'));
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$style->caution(__('This is a critical process, please do not cancel/close this CLI'));
|
||||
|
||||
$style->ask(__('Please, press any key to continue'));
|
||||
|
||||
$this->masterPassService->changeMasterPassword($request);
|
||||
|
||||
$this->logger->info(__u('Master password updated'));
|
||||
|
||||
$style->success(__('Master password updated'));
|
||||
$style->info(__('Please, restart any browser session to update it'));
|
||||
|
||||
return self::SUCCESS;
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error($e->getTraceAsString());
|
||||
$this->logger->error($e->getMessage());
|
||||
|
||||
$style->error(__($e->getMessage()));
|
||||
} finally {
|
||||
$this->release();
|
||||
}
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
private function checkInstalled(): void
|
||||
{
|
||||
if (!defined('TEST_ROOT')
|
||||
&& !$this->configData->isInstalled()) {
|
||||
throw new RuntimeException(__u('sysPass is not installed'));
|
||||
}
|
||||
}
|
||||
|
||||
private function checkMaintenance(): void
|
||||
{
|
||||
if (!defined('TEST_ROOT')
|
||||
&& !$this->configData->isMaintenance()) {
|
||||
throw new RuntimeException(__u('Maintenance mode not enabled'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param StyleInterface $style
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function getUpdate(InputInterface $input, StyleInterface $style): bool
|
||||
{
|
||||
$option = 'update';
|
||||
|
||||
$envUpdate = self::getEnvVarForOption($option);
|
||||
|
||||
$value = $envUpdate !== false
|
||||
? Util::boolval($envUpdate)
|
||||
: $input->getOption($option);
|
||||
|
||||
if ($value === false) {
|
||||
return $style->confirm(__('Update master password? (This process cannot be undone)'), false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param StyleInterface $style
|
||||
*
|
||||
* @return array|false|mixed|string
|
||||
*/
|
||||
private function getCurrentMasterPassword(
|
||||
InputInterface $input,
|
||||
StyleInterface $style
|
||||
)
|
||||
{
|
||||
$password = self::getEnvVarOrOption('currentMasterPassword', $input);
|
||||
|
||||
if (empty($password)) {
|
||||
$this->logger->debug(__u('Ask for current master password'));
|
||||
|
||||
$password = $style->askHidden(
|
||||
__('Please provide the current master password'),
|
||||
fn($value) => Validators::valueNotEmpty(
|
||||
$value,
|
||||
sprintf(__u('%s cannot be blank'), 'Master password')
|
||||
)
|
||||
);
|
||||
$passwordRepeat = $style->askHidden(
|
||||
__('Please provide the current master password again'),
|
||||
fn($value) => Validators::valueNotEmpty(
|
||||
$value,
|
||||
sprintf(__u('%s cannot be blank'), 'Master password')
|
||||
)
|
||||
);
|
||||
|
||||
if ($password !== $passwordRepeat) {
|
||||
throw new RuntimeException(__u('Passwords do not match'));
|
||||
} elseif (null === $password || null === $passwordRepeat) {
|
||||
throw new RuntimeException(sprintf(__u('%s cannot be blank'), 'Master password'));
|
||||
}
|
||||
}
|
||||
|
||||
return $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param StyleInterface $style
|
||||
*
|
||||
* @return array|false|mixed|string
|
||||
*/
|
||||
private function getMasterPassword(
|
||||
InputInterface $input,
|
||||
StyleInterface $style
|
||||
)
|
||||
{
|
||||
$password = self::getEnvVarOrOption('masterPassword', $input);
|
||||
|
||||
if (empty($password)) {
|
||||
$this->logger->debug(__u('Ask for master password'));
|
||||
|
||||
$password = $style->askHidden(
|
||||
__('Please provide the new master password'),
|
||||
fn($value) => Validators::valueNotEmpty(
|
||||
$value,
|
||||
sprintf(__u('%s cannot be blank'), 'Master password')
|
||||
)
|
||||
);
|
||||
$passwordRepeat = $style->askHidden(
|
||||
__('Please provide the new master password again'),
|
||||
fn($value) => Validators::valueNotEmpty(
|
||||
$value,
|
||||
sprintf(__u('%s cannot be blank'), 'Master password')
|
||||
)
|
||||
);
|
||||
|
||||
if ($password !== $passwordRepeat) {
|
||||
throw new RuntimeException(__u('Passwords do not match'));
|
||||
} elseif (null === $password || null === $passwordRepeat) {
|
||||
throw new RuntimeException(sprintf(__u('%s cannot be blank'), 'Master password'));
|
||||
}
|
||||
}
|
||||
|
||||
return $password;
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
namespace SP\Modules\Cli\Commands;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use SP\Config\Config;
|
||||
@@ -109,7 +108,7 @@ final class InstallCommand extends CommandBase
|
||||
->addOption('masterPassword',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
__('Master password to encrypt the passwords'))
|
||||
__('Master password to encrypt the data'))
|
||||
->addOption('hostingMode',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
|
||||
@@ -33,6 +33,7 @@ use SP\Core\Context\StatelessContext;
|
||||
use SP\Core\Language;
|
||||
use SP\Core\ModuleBase;
|
||||
use SP\Modules\Cli\Commands\BackupCommand;
|
||||
use SP\Modules\Cli\Commands\Crypt\UpdateMasterPasswordCommand;
|
||||
use SP\Modules\Cli\Commands\InstallCommand;
|
||||
use SP\Util\VersionUtil;
|
||||
use Symfony\Component\Console\Application;
|
||||
@@ -48,7 +49,8 @@ final class Init extends ModuleBase
|
||||
{
|
||||
private const CLI_COMMANDS = [
|
||||
InstallCommand::class,
|
||||
BackupCommand::class
|
||||
BackupCommand::class,
|
||||
UpdateMasterPasswordCommand::class
|
||||
];
|
||||
/**
|
||||
* @var StatelessContext
|
||||
|
||||
@@ -26,6 +26,8 @@ use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use SP\Modules\Cli\Commands\BackupCommand;
|
||||
use SP\Modules\Cli\Commands\Crypt\UpdateMasterPasswordCommand;
|
||||
use SP\Modules\Cli\Commands\InstallCommand;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
@@ -46,5 +48,7 @@ return [
|
||||
OutputInterface::class => create(ConsoleOutput::class)
|
||||
->constructor(OutputInterface::VERBOSITY_NORMAL, true),
|
||||
InputInterface::class => create(ArgvInput::class),
|
||||
InstallCommand::class => autowire()
|
||||
InstallCommand::class => autowire(),
|
||||
BackupCommand::class => autowire(),
|
||||
UpdateMasterPasswordCommand::class => autowire()
|
||||
];
|
||||
|
||||
@@ -28,6 +28,7 @@ use DI\DependencyException;
|
||||
use DI\NotFoundException;
|
||||
use Exception;
|
||||
use SP\Core\Acl\Acl;
|
||||
use SP\Core\Acl\ActionsInterface;
|
||||
use SP\Core\Acl\UnauthorizedPageException;
|
||||
use SP\Core\Crypt\Hash;
|
||||
use SP\Core\Crypt\Session as CryptSession;
|
||||
@@ -135,7 +136,9 @@ final class ConfigEncryptionController extends SimpleControllerBase
|
||||
|
||||
if (!$noAccountPassChange) {
|
||||
try {
|
||||
$task = $taskId !== null ? TaskFactory::create(__FUNCTION__, $taskId) : null;
|
||||
$task = $taskId !== null
|
||||
? TaskFactory::create(__FUNCTION__, $taskId)
|
||||
: null;
|
||||
|
||||
$request = new UpdateMasterPassRequest(
|
||||
$currentMasterPass,
|
||||
@@ -177,7 +180,7 @@ final class ConfigEncryptionController extends SimpleControllerBase
|
||||
return $this->returnJsonResponse(
|
||||
JsonResponse::JSON_SUCCESS_STICKY,
|
||||
__u('Master password updated'),
|
||||
[__u('Please, restart the session for update it')]
|
||||
[__u('Please, restart the session to update it')]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -275,7 +278,7 @@ final class ConfigEncryptionController extends SimpleControllerBase
|
||||
{
|
||||
try {
|
||||
$this->checks();
|
||||
$this->checkAccess(Acl::CONFIG_CRYPT);
|
||||
$this->checkAccess(ActionsInterface::CONFIG_CRYPT);
|
||||
} catch (UnauthorizedPageException $e) {
|
||||
$this->eventDispatcher->notifyEvent('exception', new Event($e));
|
||||
|
||||
|
||||
@@ -41,7 +41,8 @@
|
||||
"ext-libxml": "*",
|
||||
"ext-mbstring": "*",
|
||||
"league/fractal": "^0.19.2",
|
||||
"symfony/console": "^v5.1.2"
|
||||
"symfony/console": "^v5.1.2",
|
||||
"symfony/lock": "^v5.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9",
|
||||
|
||||
82
composer.lock
generated
82
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "64273b1e13feb822664429ef9e4c698d",
|
||||
"content-hash": "e123614aaac6578d9025c736e63dcebb",
|
||||
"packages": [
|
||||
{
|
||||
"name": "ademarre/binary-to-text-php",
|
||||
@@ -2756,6 +2756,86 @@
|
||||
],
|
||||
"time": "2021-03-23T23:28:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/lock",
|
||||
"version": "v5.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/lock.git",
|
||||
"reference": "a78fda52b1b6f74d60e642e91d0e0133b08a8546"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/lock/zipball/a78fda52b1b6f74d60e642e91d0e0133b08a8546",
|
||||
"reference": "a78fda52b1b6f74d60e642e91d0e0133b08a8546",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"psr/log": "^1|^2|^3",
|
||||
"symfony/deprecation-contracts": "^2.1",
|
||||
"symfony/polyfill-php80": "^1.16"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/dbal": "<2.10"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/dbal": "^2.10|^3.0",
|
||||
"mongodb/mongodb": "~1.1",
|
||||
"predis/predis": "~1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Lock\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jérémy Derussé",
|
||||
"email": "jeremy@derusse.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Creates and manages locks, a mechanism to provide exclusive access to a shared resource",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"cas",
|
||||
"flock",
|
||||
"locking",
|
||||
"mutex",
|
||||
"redlock",
|
||||
"semaphore"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/lock/tree/v5.3.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-07-23T15:55:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.23.0",
|
||||
|
||||
@@ -198,12 +198,7 @@ final class AccountCryptService extends Service
|
||||
|
||||
$accountsOk[] = $account->id;
|
||||
$counter++;
|
||||
} catch (SPException $e) {
|
||||
$errorCount++;
|
||||
|
||||
$eventMessage->addDescription(__u('Error while updating the account\'s password'));
|
||||
$eventMessage->addDetail($account->name, $account->id);
|
||||
} catch (CryptoException $e) {
|
||||
} catch (SPException | CryptoException $e) {
|
||||
$errorCount++;
|
||||
|
||||
$eventMessage->addDescription(__u('Error while updating the account\'s password'));
|
||||
@@ -263,7 +258,7 @@ final class AccountCryptService extends Service
|
||||
|
||||
throw new ServiceException(
|
||||
__u('Error while updating the accounts\' passwords in history'),
|
||||
ServiceException::ERROR,
|
||||
SPException::ERROR,
|
||||
null,
|
||||
$e->getCode(),
|
||||
$e);
|
||||
|
||||
@@ -24,9 +24,7 @@
|
||||
|
||||
namespace SP\Tests;
|
||||
|
||||
use PDO;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SP\Storage\Database\DatabaseException;
|
||||
|
||||
/**
|
||||
* Class DatabaseBaseTest
|
||||
@@ -37,30 +35,7 @@ use SP\Storage\Database\DatabaseException;
|
||||
*/
|
||||
abstract class DatabaseTestCase extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected static $loadFixtures = false;
|
||||
/**
|
||||
* @var PDO
|
||||
*/
|
||||
private static $conn;
|
||||
|
||||
/**
|
||||
* @param string $table
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected static function getRowCount(string $table): int
|
||||
{
|
||||
if (!self::$conn) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$sql = sprintf('SELECT count(*) FROM `%s`', $table);
|
||||
|
||||
return (int)self::$conn->query($sql)->fetchColumn();
|
||||
}
|
||||
use DatabaseTrait;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -70,57 +45,4 @@ abstract class DatabaseTestCase extends TestCase
|
||||
self::loadFixtures();
|
||||
}
|
||||
}
|
||||
|
||||
protected static function loadFixtures()
|
||||
{
|
||||
$dbServer = getenv('DB_SERVER');
|
||||
$dbUser = getenv('DB_USER');
|
||||
$dbPass = getenv('DB_PASS');
|
||||
$dbName = getenv('DB_NAME');
|
||||
|
||||
foreach (FIXTURE_FILES as $file) {
|
||||
if (!empty($dbPass)) {
|
||||
$cmd = sprintf(
|
||||
'mysql -h %s -u %s -p%s %s < %s',
|
||||
$dbServer,
|
||||
$dbUser,
|
||||
$dbPass,
|
||||
$dbName,
|
||||
$file
|
||||
);
|
||||
} else {
|
||||
$cmd = sprintf(
|
||||
'mysql -h %s -u %s %s < %s',
|
||||
$dbServer,
|
||||
$dbUser,
|
||||
$dbName,
|
||||
$file
|
||||
);
|
||||
}
|
||||
|
||||
exec($cmd, $output, $res);
|
||||
|
||||
if ($res !== 0) {
|
||||
error_log(sprintf('Cannot load fixtures from: %s', $file));
|
||||
error_log(sprintf('CMD: %s', $cmd));
|
||||
error_log(print_r($output, true));
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
printf('Fixtures loaded from: %s' . PHP_EOL, $file);
|
||||
}
|
||||
|
||||
if (!self::$conn) {
|
||||
try {
|
||||
self::$conn = getDbHandler()->getConnection();
|
||||
} catch (DatabaseException $e) {
|
||||
processException($e);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
113
tests/SP/DatabaseTrait.php
Normal file
113
tests/SP/DatabaseTrait.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/*
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
* @link https://syspass.org
|
||||
* @copyright 2012-2021, 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;
|
||||
|
||||
use PDO;
|
||||
use SP\Storage\Database\DatabaseException;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
trait DatabaseTrait
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected static bool $loadFixtures = false;
|
||||
/**
|
||||
* @var PDO|null
|
||||
*/
|
||||
private static ?PDO $conn = null;
|
||||
|
||||
/**
|
||||
* @param string $table
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected static function getRowCount(string $table): int
|
||||
{
|
||||
if (!self::$conn) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$sql = sprintf('SELECT count(*) FROM `%s`', $table);
|
||||
|
||||
return (int)self::$conn->query($sql)->fetchColumn();
|
||||
}
|
||||
|
||||
protected static function loadFixtures(): void
|
||||
{
|
||||
$dbServer = getenv('DB_SERVER');
|
||||
$dbUser = getenv('DB_USER');
|
||||
$dbPass = getenv('DB_PASS');
|
||||
$dbName = getenv('DB_NAME');
|
||||
|
||||
foreach (FIXTURE_FILES as $file) {
|
||||
if (!empty($dbPass)) {
|
||||
$cmd = sprintf(
|
||||
'mysql -h %s -u %s -p%s %s < %s',
|
||||
$dbServer,
|
||||
$dbUser,
|
||||
$dbPass,
|
||||
$dbName,
|
||||
$file
|
||||
);
|
||||
} else {
|
||||
$cmd = sprintf(
|
||||
'mysql -h %s -u %s %s < %s',
|
||||
$dbServer,
|
||||
$dbUser,
|
||||
$dbName,
|
||||
$file
|
||||
);
|
||||
}
|
||||
|
||||
exec($cmd, $output, $res);
|
||||
|
||||
if ($res !== 0) {
|
||||
/** @noinspection ForgottenDebugOutputInspection */
|
||||
error_log(sprintf('Cannot load fixtures from: %s', $file));
|
||||
/** @noinspection ForgottenDebugOutputInspection */
|
||||
error_log(sprintf('CMD: %s', $cmd));
|
||||
/** @noinspection ForgottenDebugOutputInspection */
|
||||
error_log(print_r($output, true));
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
printf('Fixtures loaded from: %s' . PHP_EOL, $file);
|
||||
}
|
||||
|
||||
if (!self::$conn) {
|
||||
try {
|
||||
self::$conn = getDbHandler()->getConnection();
|
||||
} catch (DatabaseException $e) {
|
||||
processException($e);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
/*
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
* @link https://syspass.org
|
||||
* @copyright 2012-2021, 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\Modules\Cli\Commands;
|
||||
|
||||
use DI\DependencyException;
|
||||
use DI\NotFoundException;
|
||||
use SP\Core\Exceptions\FileNotFoundException;
|
||||
use SP\Modules\Cli\Commands\Crypt\UpdateMasterPasswordCommand;
|
||||
use SP\Tests\DatabaseTrait;
|
||||
use SP\Tests\Modules\Cli\CliTestCase;
|
||||
use SP\Tests\Services\Account\AccountCryptServiceTest;
|
||||
use function SP\Tests\recreateDir;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class UpdateMasterPasswordCommandTest extends CliTestCase
|
||||
{
|
||||
|
||||
use DatabaseTrait;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static string $currentConfig;
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected static array $commandInputData = [
|
||||
'--currentMasterPassword' => AccountCryptServiceTest::CURRENT_MASTERPASS,
|
||||
'--masterPassword' => AccountCryptServiceTest::NEW_MASTERPASS
|
||||
];
|
||||
|
||||
/**
|
||||
* @throws DependencyException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function testUpdateAborted(): void
|
||||
{
|
||||
$commandTester = $this->executeCommandTest(
|
||||
UpdateMasterPasswordCommand::class
|
||||
);
|
||||
|
||||
// the output of the command in the console
|
||||
$output = $commandTester->getDisplay();
|
||||
$this->assertStringContainsString('Master password update aborted', $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DependencyException
|
||||
* @throws FileNotFoundException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function testUpdateIsSuccessful(): void
|
||||
{
|
||||
$inputData = array_merge(
|
||||
self::$commandInputData,
|
||||
[
|
||||
'--update' => null
|
||||
]
|
||||
);
|
||||
|
||||
$commandTester = $this->executeCommandTest(
|
||||
UpdateMasterPasswordCommand::class,
|
||||
$inputData
|
||||
);
|
||||
|
||||
// the output of the command in the console
|
||||
$output = $commandTester->getDisplay();
|
||||
$this->assertStringContainsString('Master password updated', $output);
|
||||
|
||||
// Recreate cache directory to avoid unwanted behavior
|
||||
recreateDir(CACHE_PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DependencyException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function testUpdateFromEnvironmentVarIsAbort(): void
|
||||
{
|
||||
$this->setEnvironmentVariables();
|
||||
|
||||
$commandTester = $this->executeCommandTest(
|
||||
UpdateMasterPasswordCommand::class,
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
// the output of the command in the console
|
||||
$output = $commandTester->getDisplay();
|
||||
$this->assertStringContainsString('Master password update aborted', $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DependencyException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function testUpdateFromEnvironmentVarBlankCurrentMasterPassword(): void
|
||||
{
|
||||
putenv(sprintf('%s=',
|
||||
UpdateMasterPasswordCommand::$envVarsMapping['masterPassword'])
|
||||
);
|
||||
|
||||
$commandTester = $this->executeCommandTest(
|
||||
UpdateMasterPasswordCommand::class,
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
// the output of the command in the console
|
||||
$output = $commandTester->getDisplay();
|
||||
$this->assertStringContainsString('Master password cannot be blank', $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DependencyException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function testUpdateFromEnvironmentVarBlankMasterPassword(): void
|
||||
{
|
||||
putenv(sprintf('%s=',
|
||||
UpdateMasterPasswordCommand::$envVarsMapping['currentMasterPassword'])
|
||||
);
|
||||
|
||||
$commandTester = $this->executeCommandTest(
|
||||
UpdateMasterPasswordCommand::class,
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
// the output of the command in the console
|
||||
$output = $commandTester->getDisplay();
|
||||
$this->assertStringContainsString('Master password cannot be blank', $output);
|
||||
}
|
||||
|
||||
private function setEnvironmentVariables(): void
|
||||
{
|
||||
putenv(sprintf('%s=%s',
|
||||
UpdateMasterPasswordCommand::$envVarsMapping['currentMasterPassword'],
|
||||
AccountCryptServiceTest::CURRENT_MASTERPASS)
|
||||
);
|
||||
putenv(sprintf('%s=%s',
|
||||
UpdateMasterPasswordCommand::$envVarsMapping['masterPassword'],
|
||||
AccountCryptServiceTest::NEW_MASTERPASS)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DependencyException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function testUpdateFromEnvironmentVarIsSuccessful(): void
|
||||
{
|
||||
putenv(sprintf('%s=true',
|
||||
UpdateMasterPasswordCommand::$envVarsMapping['update'])
|
||||
);
|
||||
|
||||
$this->setEnvironmentVariables();
|
||||
|
||||
$commandTester = $this->executeCommandTest(
|
||||
UpdateMasterPasswordCommand::class,
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
// the output of the command in the console
|
||||
$output = $commandTester->getDisplay();
|
||||
$this->assertStringContainsString('Master password updated', $output);
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->setupDatabase();
|
||||
|
||||
self::loadFixtures();
|
||||
|
||||
parent::setUp();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user