mirror of
https://github.com/nuxsmin/sysPass.git
synced 2026-03-21 15:56:51 +01:00
* [ADD] Backup CLI command and tests.
* [MOD] Code refactoring. Signed-off-by: Rubén D <nuxsmin@syspass.org>
This commit is contained in:
140
app/modules/cli/Commands/BackupCommand.php
Normal file
140
app/modules/cli/Commands/BackupCommand.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?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;
|
||||
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use RuntimeException;
|
||||
use SP\Config\Config;
|
||||
use SP\Services\Backup\FileBackupService;
|
||||
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 BackupCommand
|
||||
*
|
||||
* @package SP\Modules\Cli\Commands
|
||||
*/
|
||||
final class BackupCommand extends CommandBase
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public static array $envVarsMapping = [
|
||||
'path' => 'BACKUP_PATH'
|
||||
];
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $defaultName = 'sp:backup';
|
||||
/**
|
||||
* @var FileBackupService
|
||||
*/
|
||||
private FileBackupService $fileBackupService;
|
||||
|
||||
public function __construct(FileBackupService $fileBackupService,
|
||||
LoggerInterface $logger,
|
||||
Config $config)
|
||||
{
|
||||
$this->fileBackupService = $fileBackupService;
|
||||
|
||||
parent::__construct($logger, $config);
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription(__('Backup actions'))
|
||||
->setHelp(__('This command performs a file based backup from sysPass database and application'))
|
||||
->addOption('path',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
__('Path where to store the backup files'),
|
||||
BACKUP_PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$style = new SymfonyStyle($input, $output);
|
||||
|
||||
try {
|
||||
$this->checkInstalled();
|
||||
|
||||
$path = $this->getPath($input, $style);
|
||||
|
||||
$this->logger->info(sprintf(__u('Backup path set to: %s'), $path));
|
||||
|
||||
$this->fileBackupService->doBackup($path);
|
||||
|
||||
$this->logger->info(__u('Application and database backup completed successfully'));
|
||||
|
||||
$style->success(__('Application and database backup completed successfully'));
|
||||
|
||||
return self::SUCCESS;
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error($e->getTraceAsString());
|
||||
$this->logger->error($e->getMessage());
|
||||
|
||||
$style->error(__($e->getMessage()));
|
||||
}
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
private function checkInstalled(): void
|
||||
{
|
||||
if (!defined('TEST_ROOT')
|
||||
&& !$this->configData->isInstalled()) {
|
||||
throw new RuntimeException(__u('sysPass is not installed'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param StyleInterface $style
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getPath(InputInterface $input, StyleInterface $style): string
|
||||
{
|
||||
$path = self::getEnvVarOrOption('path', $input);
|
||||
|
||||
if (empty($path)) {
|
||||
$this->logger->debug(__u('Ask for path'));
|
||||
|
||||
return $style->ask(__('Please enter the path where to store the backup files'), BACKUP_PATH);
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ use Psr\Log\LoggerInterface;
|
||||
use SP\Config\Config;
|
||||
use SP\Config\ConfigData;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
|
||||
/**
|
||||
* Class CommandBase
|
||||
@@ -36,19 +37,22 @@ use Symfony\Component\Console\Command\Command;
|
||||
*/
|
||||
abstract class CommandBase extends Command
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public static array $envVarsMapping = [];
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
protected LoggerInterface $logger;
|
||||
/**
|
||||
* @var Config
|
||||
*/
|
||||
protected $config;
|
||||
protected Config $config;
|
||||
/**
|
||||
* @var ConfigData
|
||||
*/
|
||||
protected $configData;
|
||||
protected ConfigData $configData;
|
||||
|
||||
/**
|
||||
* CommandBase constructor.
|
||||
@@ -67,4 +71,44 @@ abstract class CommandBase extends Command
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $option
|
||||
* @param InputInterface $input
|
||||
*
|
||||
* @return array|false|mixed|string
|
||||
*/
|
||||
protected static function getEnvVarOrOption(
|
||||
string $option,
|
||||
InputInterface $input
|
||||
)
|
||||
{
|
||||
return static::getEnvVarForOption($option)
|
||||
?: $input->getOption($option);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $option
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
protected static function getEnvVarForOption(string $option)
|
||||
{
|
||||
return getenv(static::$envVarsMapping[$option]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $argument
|
||||
* @param InputInterface $input
|
||||
*
|
||||
* @return array|false|mixed|string
|
||||
*/
|
||||
protected static function getEnvVarOrArgument(
|
||||
string $argument,
|
||||
InputInterface $input
|
||||
)
|
||||
{
|
||||
return static::getEnvVarForOption($argument)
|
||||
?: $input->getArgument($argument);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,6 @@ namespace SP\Modules\Cli\Commands;
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use RuntimeException;
|
||||
use SP\Config\Config;
|
||||
use SP\Core\Exceptions\InstallError;
|
||||
use SP\Core\Exceptions\InvalidArgumentException;
|
||||
@@ -52,7 +51,7 @@ final class InstallCommand extends CommandBase
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public static $envVarsMapping = [
|
||||
public static array $envVarsMapping = [
|
||||
'adminLogin' => 'ADMIN_LOGIN',
|
||||
'adminPassword' => 'ADMIN_PASSWORD',
|
||||
'databaseHost' => 'DATABASE_HOST',
|
||||
@@ -72,7 +71,7 @@ final class InstallCommand extends CommandBase
|
||||
/**
|
||||
* @var Installer
|
||||
*/
|
||||
private $installer;
|
||||
private Installer $installer;
|
||||
|
||||
public function __construct(LoggerInterface $logger,
|
||||
Config $config,
|
||||
@@ -185,16 +184,8 @@ final class InstallCommand extends CommandBase
|
||||
*/
|
||||
private function getInstallData(InputInterface $input, StyleInterface $style): InstallData
|
||||
{
|
||||
$passNotEmptyValidator = function ($value) {
|
||||
if (empty($value)) {
|
||||
throw new RuntimeException(__('Password cannot be blank'));
|
||||
}
|
||||
|
||||
return $value;
|
||||
};
|
||||
|
||||
$adminPassword = $this->getAdminPassword($input, $style, $passNotEmptyValidator);
|
||||
$masterPassword = $this->getMasterPassword($input, $style, $passNotEmptyValidator);
|
||||
$adminPassword = $this->getAdminPassword($input, $style);
|
||||
$masterPassword = $this->getMasterPassword($input, $style);
|
||||
$databasePassword = $this->getDatabasePassword($input, $style);
|
||||
$language = $this->getLanguage($input, $style);
|
||||
$hostingMode = $this->isHostingMode($input);
|
||||
@@ -220,15 +211,13 @@ final class InstallCommand extends CommandBase
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param StyleInterface $style
|
||||
* @param Closure $passNotEmptyValidator
|
||||
*
|
||||
* @return array|false|mixed|string
|
||||
* @throws InstallError
|
||||
*/
|
||||
private function getAdminPassword(
|
||||
InputInterface $input,
|
||||
StyleInterface $style,
|
||||
Closure $passNotEmptyValidator
|
||||
StyleInterface $style
|
||||
)
|
||||
{
|
||||
$option = 'adminPassword';
|
||||
@@ -242,12 +231,18 @@ final class InstallCommand extends CommandBase
|
||||
|
||||
$password = $style->askHidden(
|
||||
__('Please provide sysPass admin\'s password'),
|
||||
$passNotEmptyValidator
|
||||
fn($value) => Validators::valueNotEmpty(
|
||||
$value,
|
||||
sprintf(__u('%s cannot be blank'), 'Admin password')
|
||||
)
|
||||
);
|
||||
|
||||
$passwordRepeat = $style->askHidden(
|
||||
__('Please provide sysPass admin\'s password again'),
|
||||
$passNotEmptyValidator
|
||||
fn($value) => Validators::valueNotEmpty(
|
||||
$value,
|
||||
sprintf(__u('%s cannot be blank'), 'Admin password')
|
||||
)
|
||||
);
|
||||
|
||||
if ($password !== $passwordRepeat) {
|
||||
@@ -260,18 +255,17 @@ final class InstallCommand extends CommandBase
|
||||
return $password;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param StyleInterface $style
|
||||
* @param Closure $passNotEmptyValidator
|
||||
*
|
||||
* @return array|false|mixed|string
|
||||
* @throws InstallError
|
||||
*/
|
||||
private function getMasterPassword(
|
||||
InputInterface $input,
|
||||
StyleInterface $style,
|
||||
Closure $passNotEmptyValidator
|
||||
StyleInterface $style
|
||||
)
|
||||
{
|
||||
$password = self::getEnvVarOrOption('masterPassword', $input);
|
||||
@@ -281,11 +275,17 @@ final class InstallCommand extends CommandBase
|
||||
|
||||
$password = $style->askHidden(
|
||||
__('Please provide sysPass master password'),
|
||||
$passNotEmptyValidator
|
||||
fn($value) => Validators::valueNotEmpty(
|
||||
$value,
|
||||
sprintf(__u('%s cannot be blank'), 'Master password')
|
||||
)
|
||||
);
|
||||
$passwordRepeat = $style->askHidden(
|
||||
__('Please provide sysPass master password again'),
|
||||
$passNotEmptyValidator
|
||||
fn($value) => Validators::valueNotEmpty(
|
||||
$value,
|
||||
sprintf(__u('%s cannot be blank'), 'Master password')
|
||||
)
|
||||
);
|
||||
|
||||
if ($password !== $passwordRepeat) {
|
||||
@@ -298,31 +298,6 @@ final class InstallCommand extends CommandBase
|
||||
return $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $option
|
||||
* @param InputInterface $input
|
||||
*
|
||||
* @return array|false|mixed|string
|
||||
*/
|
||||
private static function getEnvVarOrOption(
|
||||
string $option,
|
||||
InputInterface $input
|
||||
)
|
||||
{
|
||||
return self::getEnvVarForOption($option)
|
||||
?: $input->getOption($option);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $option
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
public static function getEnvVarForOption(string $option)
|
||||
{
|
||||
return getenv(self::$envVarsMapping[$option]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param StyleInterface $style
|
||||
@@ -387,21 +362,6 @@ final class InstallCommand extends CommandBase
|
||||
: $input->getOption($option);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $argument
|
||||
* @param InputInterface $input
|
||||
*
|
||||
* @return array|false|mixed|string
|
||||
*/
|
||||
private static function getEnvVarOrArgument(
|
||||
string $argument,
|
||||
InputInterface $input
|
||||
)
|
||||
{
|
||||
return self::getEnvVarForOption($argument)
|
||||
?: $input->getArgument($argument);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
*
|
||||
|
||||
48
app/modules/cli/Commands/Validators.php
Normal file
48
app/modules/cli/Commands/Validators.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?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;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
final class Validators
|
||||
{
|
||||
/**
|
||||
* @param string|null $value
|
||||
* @param string|null $message
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function valueNotEmpty(?string $value, ?string $message): string
|
||||
{
|
||||
if (empty($value)) {
|
||||
throw new RuntimeException($message ?? __u('Value cannot be blank'));
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* @author nuxsmin
|
||||
* @link https://syspass.org
|
||||
* @copyright 2012-2020, Rubén Domínguez nuxsmin@$syspass.org
|
||||
* @copyright 2012-2021, Rubén Domínguez nuxsmin@$syspass.org
|
||||
*
|
||||
* This file is part of sysPass.
|
||||
*
|
||||
@@ -19,7 +19,7 @@
|
||||
* 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/>.
|
||||
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace SP\Modules\Cli;
|
||||
@@ -32,6 +32,7 @@ use SP\Core\Context\ContextException;
|
||||
use SP\Core\Context\StatelessContext;
|
||||
use SP\Core\Language;
|
||||
use SP\Core\ModuleBase;
|
||||
use SP\Modules\Cli\Commands\BackupCommand;
|
||||
use SP\Modules\Cli\Commands\InstallCommand;
|
||||
use SP\Util\VersionUtil;
|
||||
use Symfony\Component\Console\Application;
|
||||
@@ -46,7 +47,8 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
final class Init extends ModuleBase
|
||||
{
|
||||
private const CLI_COMMANDS = [
|
||||
InstallCommand::class
|
||||
InstallCommand::class,
|
||||
BackupCommand::class
|
||||
];
|
||||
/**
|
||||
* @var StatelessContext
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* @author nuxsmin
|
||||
* @link https://syspass.org
|
||||
* @copyright 2012-2020, Rubén Domínguez nuxsmin@$syspass.org
|
||||
* @copyright 2012-2021, Rubén Domínguez nuxsmin@$syspass.org
|
||||
*
|
||||
* This file is part of sysPass.
|
||||
*
|
||||
@@ -19,8 +19,8 @@
|
||||
* 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/>.
|
||||
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
define('MODULE_PATH', __DIR__);
|
||||
define('PLUGINS_PATH', MODULE_PATH . DIRECTORY_SEPARATOR . 'plugins');
|
||||
const MODULE_PATH = __DIR__;
|
||||
const PLUGINS_PATH = MODULE_PATH . DIRECTORY_SEPARATOR . 'plugins';
|
||||
|
||||
@@ -202,15 +202,18 @@ final class Config
|
||||
* @return Config
|
||||
* @throws FileException
|
||||
*/
|
||||
public function saveConfig(ConfigData $configData, $backup = true): Config
|
||||
public function saveConfig(ConfigData $configData, ?bool $backup = true): Config
|
||||
{
|
||||
if ($backup) {
|
||||
$this->dic->get(ConfigBackupService::class)
|
||||
->backup($configData);
|
||||
}
|
||||
|
||||
$configSaver = $this->context->getUserData()->getLogin()
|
||||
?: AppInfoInterface::APP_NAME;
|
||||
|
||||
$configData->setConfigDate(time());
|
||||
$configData->setConfigSaver($this->context->getUserData()->getLogin() ?: AppInfoInterface::APP_NAME);
|
||||
$configData->setConfigSaver($configSaver);
|
||||
$configData->setConfigHash();
|
||||
|
||||
// Save only attributes to avoid a parent attributes node within the XML
|
||||
@@ -258,7 +261,7 @@ final class Config
|
||||
*
|
||||
* @return ConfigData
|
||||
*/
|
||||
public function loadConfig($reload = false): ConfigData
|
||||
public function loadConfig(?bool $reload = false): ConfigData
|
||||
{
|
||||
try {
|
||||
$configData = $this->fileCache->load();
|
||||
|
||||
@@ -85,9 +85,9 @@ interface ContextInterface
|
||||
/**
|
||||
* Devuelve los datos del usuario en la sesión.
|
||||
*
|
||||
* @return UserLoginResponse|null
|
||||
* @return UserLoginResponse
|
||||
*/
|
||||
public function getUserData(): ?UserLoginResponse;
|
||||
public function getUserData(): UserLoginResponse;
|
||||
|
||||
/**
|
||||
* Establecer el lenguaje de la sesión
|
||||
|
||||
@@ -119,7 +119,7 @@ final class StatelessContext extends ContextBase
|
||||
*
|
||||
* @return UserLoginResponse
|
||||
*/
|
||||
public function getUserData(): ?UserLoginResponse
|
||||
public function getUserData(): UserLoginResponse
|
||||
{
|
||||
return $this->getContextKey('userData', new UserLoginResponse());
|
||||
}
|
||||
|
||||
@@ -203,22 +203,6 @@ final class LdapConnection implements LdapConnectionInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getServerUri(): string
|
||||
{
|
||||
$server = $this->getServer();
|
||||
$port = $this->ldapParams->getPort();
|
||||
|
||||
if (strpos($server, '://') !== false) {
|
||||
return $server . ':' . $port;
|
||||
} elseif ($port === 389 || $port === null) {
|
||||
return 'ldap://' . $server;
|
||||
} elseif ($port === 636) {
|
||||
return 'ldaps://' . $server;
|
||||
}
|
||||
|
||||
return 'ldap://' . $server . ':' . $port;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@@ -229,9 +213,13 @@ final class LdapConnection implements LdapConnectionInterface
|
||||
|
||||
if (strpos($server, '://') !== false) {
|
||||
return $server . ':' . $port;
|
||||
} elseif ($port === 389 || $port === null) {
|
||||
}
|
||||
|
||||
if ($port === 389 || $port === null) {
|
||||
return 'ldap://' . $server;
|
||||
} elseif ($port === 636) {
|
||||
}
|
||||
|
||||
if ($port === 636) {
|
||||
return 'ldaps://' . $server;
|
||||
}
|
||||
|
||||
|
||||
@@ -137,13 +137,14 @@ final class FileBackupService extends Service
|
||||
/**
|
||||
* Comprobar y crear el directorio de backups.
|
||||
*
|
||||
* @return bool
|
||||
* @return void
|
||||
* @throws ServiceException
|
||||
*/
|
||||
private function checkBackupDir()
|
||||
private function checkBackupDir(): void
|
||||
{
|
||||
if (is_dir($this->path) === false
|
||||
&& @mkdir($this->path, 0750) === false
|
||||
&& !@mkdir($concurrentDirectory = $this->path, 0750)
|
||||
&& !is_dir($concurrentDirectory)
|
||||
) {
|
||||
throw new ServiceException(
|
||||
sprintf(__('Unable to create the backups directory ("%s")'), $this->path));
|
||||
@@ -154,7 +155,6 @@ final class FileBackupService extends Service
|
||||
__u('Please, check the backup directory permissions'));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -54,11 +54,19 @@ final class FileUtil
|
||||
throw new FileNotFoundException('Directory does not exist');
|
||||
}
|
||||
|
||||
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST);
|
||||
$it = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator(
|
||||
$dir,
|
||||
FilesystemIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
|
||||
foreach ($it as $file) {
|
||||
if ($file->isDir()) rmdir($file->getPathname());
|
||||
else unlink($file->getPathname());
|
||||
if ($file->isDir()) {
|
||||
rmdir($file->getPathname());
|
||||
} else {
|
||||
unlink($file->getPathname());
|
||||
}
|
||||
}
|
||||
|
||||
return rmdir($dir);
|
||||
|
||||
@@ -25,9 +25,15 @@
|
||||
namespace SP\Tests\Modules\Cli;
|
||||
|
||||
use DI\ContainerBuilder;
|
||||
use DI\DependencyException;
|
||||
use DI\NotFoundException;
|
||||
use Exception;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use SP\Core\Context\ContextInterface;
|
||||
use SP\Storage\Database\DBStorageInterface;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use function SP\Tests\getDbHandler;
|
||||
|
||||
/**
|
||||
* Class CliTestCase
|
||||
@@ -40,6 +46,10 @@ abstract class CliTestCase extends TestCase
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
protected static $dic;
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected static array $commandInputData = [];
|
||||
|
||||
/**
|
||||
* This method is called before the first test of this test class is run.
|
||||
@@ -57,5 +67,43 @@ abstract class CliTestCase extends TestCase
|
||||
);
|
||||
|
||||
self::$dic = $builder->build();
|
||||
|
||||
$context = self::$dic->get(ContextInterface::class);
|
||||
$context->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $commandClass
|
||||
* @param array|null $inputData
|
||||
* @param bool $useInputData
|
||||
*
|
||||
* @return CommandTester
|
||||
* @throws DependencyException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
protected function executeCommandTest(
|
||||
string $commandClass,
|
||||
?array $inputData = null,
|
||||
bool $useInputData = true
|
||||
): CommandTester
|
||||
{
|
||||
$installCommand = self::$dic->get($commandClass);
|
||||
|
||||
if (null === $inputData && $useInputData) {
|
||||
$inputData = static::$commandInputData;
|
||||
}
|
||||
|
||||
$commandTester = new CommandTester($installCommand);
|
||||
$commandTester->execute(
|
||||
$inputData ?? [],
|
||||
['interactive' => false]
|
||||
);
|
||||
|
||||
return $commandTester;
|
||||
}
|
||||
|
||||
protected function setupDatabase(): void
|
||||
{
|
||||
self::$dic->set(DBStorageInterface::class, getDbHandler());
|
||||
}
|
||||
}
|
||||
168
tests/SP/Modules/Cli/Commands/BackupCommandTest.php
Normal file
168
tests/SP/Modules/Cli/Commands/BackupCommandTest.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?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\Config\Config;
|
||||
use SP\Core\Exceptions\FileNotFoundException;
|
||||
use SP\Modules\Cli\Commands\BackupCommand;
|
||||
use SP\Services\Backup\FileBackupService;
|
||||
use SP\Tests\Modules\Cli\CliTestCase;
|
||||
use function SP\Tests\recreateDir;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class BackupCommandTest extends CliTestCase
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static string $currentConfig;
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected static array $commandInputData = [
|
||||
'--path' => TMP_PATH
|
||||
];
|
||||
|
||||
/**
|
||||
* @throws DependencyException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function testBackupFails(): void
|
||||
{
|
||||
$inputData = ['--path' => '/non/existant/path'];
|
||||
|
||||
$commandTester = $this->executeCommandTest(
|
||||
BackupCommand::class,
|
||||
$inputData
|
||||
);
|
||||
|
||||
// the output of the command in the console
|
||||
$output = $commandTester->getDisplay();
|
||||
$this->assertStringContainsString('Unable to create the backups directory', $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DependencyException
|
||||
* @throws FileNotFoundException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function testBackupIsSuccessful(): void
|
||||
{
|
||||
$this->setupDatabase();
|
||||
|
||||
$commandTester = $this->executeCommandTest(
|
||||
BackupCommand::class,
|
||||
self::$commandInputData
|
||||
);
|
||||
|
||||
// the output of the command in the console
|
||||
$output = $commandTester->getDisplay();
|
||||
$this->assertStringContainsString('Application and database backup completed successfully', $output);
|
||||
|
||||
$this->checkBackupFilesAreCreated();
|
||||
|
||||
// Recreate cache directory to avoid unwanted behavior
|
||||
recreateDir(CACHE_PATH);
|
||||
}
|
||||
|
||||
private function checkBackupFilesAreCreated(): void
|
||||
{
|
||||
$configData = self::$dic->get(Config::class)->getConfigData();
|
||||
|
||||
$this->assertFileExists(
|
||||
FileBackupService::getAppBackupFilename(
|
||||
TMP_PATH,
|
||||
$configData->getBackupHash(),
|
||||
true
|
||||
)
|
||||
);
|
||||
$this->assertFileExists(
|
||||
FileBackupService::getDbBackupFilename(
|
||||
TMP_PATH,
|
||||
$configData->getBackupHash(),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DependencyException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function testBackupFromEnvironmentVarIsAbort(): void
|
||||
{
|
||||
putenv(sprintf('%s=%s',
|
||||
BackupCommand::$envVarsMapping['path'],
|
||||
'/non/existant/path')
|
||||
);
|
||||
|
||||
$commandTester = $this->executeCommandTest(
|
||||
BackupCommand::class,
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
// the output of the command in the console
|
||||
$output = $commandTester->getDisplay();
|
||||
$this->assertStringContainsString('Unable to create the backups directory', $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DependencyException
|
||||
* @throws NotFoundException
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
public function testBackupFromEnvironmentVarIsSuccessful(): void
|
||||
{
|
||||
$this->setEnvironmentVariables();
|
||||
|
||||
$commandTester = $this->executeCommandTest(
|
||||
BackupCommand::class,
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
// the output of the command in the console
|
||||
$output = $commandTester->getDisplay();
|
||||
$this->assertStringContainsString('Application and database backup completed successfully', $output);
|
||||
|
||||
$this->checkBackupFilesAreCreated();
|
||||
|
||||
// Recreate cache directory to avoid unwanted behavior
|
||||
recreateDir(CACHE_PATH);
|
||||
}
|
||||
|
||||
private function setEnvironmentVariables(): void
|
||||
{
|
||||
putenv(sprintf('%s=%s',
|
||||
BackupCommand::$envVarsMapping['path'],
|
||||
TMP_PATH)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,6 @@ use SP\Modules\Cli\Commands\InstallCommand;
|
||||
use SP\Storage\Database\DatabaseException;
|
||||
use SP\Tests\DatabaseUtil;
|
||||
use SP\Tests\Modules\Cli\CliTestCase;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use function SP\Tests\getResource;
|
||||
use function SP\Tests\recreateDir;
|
||||
use function SP\Tests\saveResource;
|
||||
@@ -45,11 +44,11 @@ class InstallCommandTest extends CliTestCase
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $currentConfig;
|
||||
protected static string $currentConfig;
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private static $commandInputData = [
|
||||
protected static array $commandInputData = [
|
||||
'adminLogin' => 'Admin',
|
||||
'databaseHost' => 'localhost',
|
||||
'databaseName' => 'syspass-test-install',
|
||||
@@ -92,41 +91,13 @@ class InstallCommandTest extends CliTestCase
|
||||
*/
|
||||
public function testInstallationIsAborted(): void
|
||||
{
|
||||
$commandTester = $this->executeCommandTest();
|
||||
$commandTester = $this->executeCommandTest(InstallCommand::class);
|
||||
|
||||
// the output of the command in the console
|
||||
$output = $commandTester->getDisplay();
|
||||
$this->assertStringContainsString('Installation aborted', $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $inputData
|
||||
* @param bool $useInputData
|
||||
*
|
||||
* @return CommandTester
|
||||
* @throws DependencyException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
private function executeCommandTest(
|
||||
?array $inputData = null,
|
||||
bool $useInputData = true
|
||||
): CommandTester
|
||||
{
|
||||
$installCommand = self::$dic->get(InstallCommand::class);
|
||||
|
||||
if (null === $inputData && $useInputData) {
|
||||
$inputData = self::$commandInputData;
|
||||
}
|
||||
|
||||
$commandTester = new CommandTester($installCommand);
|
||||
$commandTester->execute(
|
||||
$inputData ?? [],
|
||||
['interactive' => false]
|
||||
);
|
||||
|
||||
return $commandTester;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DependencyException
|
||||
* @throws NotFoundException
|
||||
@@ -138,7 +109,10 @@ class InstallCommandTest extends CliTestCase
|
||||
['--forceInstall' => null]
|
||||
);
|
||||
|
||||
$commandTester = $this->executeCommandTest($inputData);
|
||||
$commandTester = $this->executeCommandTest(
|
||||
InstallCommand::class,
|
||||
$inputData
|
||||
);
|
||||
|
||||
// the output of the command in the console
|
||||
$output = $commandTester->getDisplay();
|
||||
@@ -156,7 +130,10 @@ class InstallCommandTest extends CliTestCase
|
||||
['--adminPassword' => '']
|
||||
);
|
||||
|
||||
$commandTester = $this->executeCommandTest($inputData);
|
||||
$commandTester = $this->executeCommandTest(
|
||||
InstallCommand::class,
|
||||
$inputData
|
||||
);
|
||||
|
||||
// the output of the command in the console
|
||||
$output = $commandTester->getDisplay();
|
||||
@@ -174,7 +151,10 @@ class InstallCommandTest extends CliTestCase
|
||||
['--masterPassword' => '']
|
||||
);
|
||||
|
||||
$commandTester = $this->executeCommandTest($inputData);
|
||||
$commandTester = $this->executeCommandTest(
|
||||
InstallCommand::class,
|
||||
$inputData
|
||||
);
|
||||
|
||||
// the output of the command in the console
|
||||
$output = $commandTester->getDisplay();
|
||||
@@ -198,7 +178,10 @@ class InstallCommandTest extends CliTestCase
|
||||
]
|
||||
);
|
||||
|
||||
$commandTester = $this->executeCommandTest($inputData);
|
||||
$commandTester = $this->executeCommandTest(
|
||||
InstallCommand::class,
|
||||
$inputData
|
||||
);
|
||||
|
||||
// the output of the command in the console
|
||||
$output = $commandTester->getDisplay();
|
||||
@@ -229,7 +212,10 @@ class InstallCommandTest extends CliTestCase
|
||||
]
|
||||
);
|
||||
|
||||
$commandTester = $this->executeCommandTest($inputData);
|
||||
$commandTester = $this->executeCommandTest(
|
||||
InstallCommand::class,
|
||||
$inputData
|
||||
);
|
||||
|
||||
// the output of the command in the console
|
||||
$output = $commandTester->getDisplay();
|
||||
@@ -273,7 +259,10 @@ class InstallCommandTest extends CliTestCase
|
||||
]
|
||||
);
|
||||
|
||||
$commandTester = $this->executeCommandTest($inputData);
|
||||
$commandTester = $this->executeCommandTest(
|
||||
InstallCommand::class,
|
||||
$inputData
|
||||
);
|
||||
|
||||
// the output of the command in the console
|
||||
$output = $commandTester->getDisplay();
|
||||
@@ -296,7 +285,11 @@ class InstallCommandTest extends CliTestCase
|
||||
{
|
||||
$this->setEnvironmentVariables();
|
||||
|
||||
$commandTester = $this->executeCommandTest(null, false);
|
||||
$commandTester = $this->executeCommandTest(
|
||||
InstallCommand::class,
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
// the output of the command in the console
|
||||
$output = $commandTester->getDisplay();
|
||||
@@ -347,7 +340,11 @@ class InstallCommandTest extends CliTestCase
|
||||
|
||||
$this->setEnvironmentVariables();
|
||||
|
||||
$commandTester = $this->executeCommandTest(null, false);
|
||||
$commandTester = $this->executeCommandTest(
|
||||
InstallCommand::class,
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
// the output of the command in the console
|
||||
$output = $commandTester->getDisplay();
|
||||
@@ -370,7 +367,11 @@ class InstallCommandTest extends CliTestCase
|
||||
|
||||
$this->setEnvironmentVariables();
|
||||
|
||||
$commandTester = $this->executeCommandTest(null, false);
|
||||
$commandTester = $this->executeCommandTest(
|
||||
InstallCommand::class,
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
// the output of the command in the console
|
||||
$output = $commandTester->getDisplay();
|
||||
|
||||
@@ -27,13 +27,16 @@ namespace SP\Tests;
|
||||
use DI\Container;
|
||||
use DI\ContainerBuilder;
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
use SP\Core\Context\ContextException;
|
||||
use SP\Core\Context\ContextInterface;
|
||||
use SP\Core\Exceptions\FileNotFoundException;
|
||||
use SP\DataModel\ProfileData;
|
||||
use SP\Services\User\UserLoginResponse;
|
||||
use SP\Storage\Database\DatabaseConnectionData;
|
||||
use SP\Storage\Database\DBStorageInterface;
|
||||
use SP\Storage\Database\MySQLHandler;
|
||||
use SP\Util\FileUtil;
|
||||
|
||||
define('DEBUG', true);
|
||||
|
||||
@@ -46,11 +49,11 @@ define('CONFIG_PATH', RESOURCE_PATH . DIRECTORY_SEPARATOR . 'config');
|
||||
define('CONFIG_FILE', CONFIG_PATH . DIRECTORY_SEPARATOR . 'config.xml');
|
||||
define('ACTIONS_FILE', CONFIG_PATH . DIRECTORY_SEPARATOR . 'actions.xml');
|
||||
|
||||
define('MODULES_PATH', APP_ROOT. DIRECTORY_SEPARATOR. 'app' . DIRECTORY_SEPARATOR . 'modules');
|
||||
define('MODULES_PATH', APP_ROOT . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'modules');
|
||||
define('SQL_PATH', APP_ROOT . DIRECTORY_SEPARATOR . 'schemas');
|
||||
define('CACHE_PATH', RESOURCE_PATH . DIRECTORY_SEPARATOR . 'cache');
|
||||
define('TMP_PATH', TEST_ROOT . DIRECTORY_SEPARATOR . 'tmp');
|
||||
|
||||
define('BACKUP_PATH', TMP_PATH);
|
||||
|
||||
define('XML_SCHEMA', APP_ROOT . DIRECTORY_SEPARATOR . 'schemas' . DIRECTORY_SEPARATOR . 'syspass.xsd');
|
||||
|
||||
@@ -189,19 +192,20 @@ function saveResource($dir, $file, $data): string
|
||||
|
||||
/**
|
||||
* @param $dir
|
||||
*
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
function recreateDir($dir)
|
||||
{
|
||||
if (!is_dir($dir)) {
|
||||
print 'Creating ' . $dir . PHP_EOL;
|
||||
|
||||
if (!mkdir($dir) && !is_dir($dir)) {
|
||||
throw new \RuntimeException(sprintf('Directory "%s" was not created', $dir));
|
||||
}
|
||||
} else {
|
||||
if (is_dir($dir)) {
|
||||
print 'Deleting ' . $dir . PHP_EOL;
|
||||
|
||||
// Delete tmp dir ...
|
||||
array_map('unlink', glob($dir . DIRECTORY_SEPARATOR . '*'));
|
||||
FileUtil::rmdir_recursive($dir);
|
||||
}
|
||||
|
||||
print 'Creating ' . $dir . PHP_EOL;
|
||||
|
||||
if (!mkdir($dir) && !is_dir($dir)) {
|
||||
throw new RuntimeException(sprintf('Directory "%s" was not created', $dir));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user