From 534585439a3abebc4709e8cc2926abf2ed71f435 Mon Sep 17 00:00:00 2001 From: Benoit Wannepain Date: Wed, 21 Nov 2018 12:43:03 +0100 Subject: [PATCH] Add SSH password authentication method --- src/Infrastructure/Cli/Ssh.php | 14 +++- src/Infrastructure/Cli/SshConsole.php | 6 +- .../Common/config/parameters.yml | 1 + src/Infrastructure/Common/messages.en.yml | 2 + .../S010FromReadyToSourcePimConfigured.php | 16 +++- src/Infrastructure/Pim/SshConnection.php | 11 ++- src/Infrastructure/SshFileFetcher.php | 5 +- ...S010FromReadyToSourcePimConfiguredSpec.php | 81 +++++++++++++++++-- 8 files changed, 120 insertions(+), 16 deletions(-) diff --git a/src/Infrastructure/Cli/Ssh.php b/src/Infrastructure/Cli/Ssh.php index 70f3208..a2dfc94 100644 --- a/src/Infrastructure/Cli/Ssh.php +++ b/src/Infrastructure/Cli/Ssh.php @@ -23,9 +23,9 @@ public function __construct(string $host, int $port = 22) $this->port = $port; } - public function exec(string $command, string $username): ?string + public function exec(string $command, string $username, ?string $password = null): ?string { - $connection = $this->getAuthenticatedConnection($username); + $connection = $this->getAuthenticatedConnection($username, $password); $stream = ssh2_exec($connection, $command); if (!is_resource($stream)) { @@ -49,7 +49,7 @@ public function exec(string $command, string $username): ?string return $output; } - public function getAuthenticatedConnection(string $username) + public function getAuthenticatedConnection(string $username, ?$password = null) { $connection = ssh2_connect($this->host, $this->port); @@ -63,7 +63,13 @@ public function getAuthenticatedConnection(string $username) ); } - if (false === ssh2_auth_agent($connection, $username)) { + if (!empty($password)) { + $res = ssh2_auth_password($connection, $username, $password); + } else { + $res = ssh2_auth_agent($connection, $username); + } + + if (false === $res) { throw new ImpossibleConnectionException( sprintf( 'Impossible to login to %s@%s:%d using ssh local agent, try to run ssh-add before retry', diff --git a/src/Infrastructure/Cli/SshConsole.php b/src/Infrastructure/Cli/SshConsole.php index d53ce18..5f8eb2a 100644 --- a/src/Infrastructure/Cli/SshConsole.php +++ b/src/Infrastructure/Cli/SshConsole.php @@ -64,7 +64,7 @@ public function execute(Command $command, Pim $pim): CommandResult $this->logger->debug(sprintf('SshConsole: executing MySqlQueryCommand -> %s', $command->getCommand())); - $output = $ssh->exec($query, $connection->getUsername()); + $output = $ssh->exec($query, $connection->getUsername(), $connection->getPassword()); return new CommandResult($output !== false ? 0 : 1, ''); } @@ -74,7 +74,7 @@ public function execute(Command $command, Pim $pim): CommandResult $this->logger->debug(sprintf('SshConsole: executing MySqlQueryCommand -> %s', $command->getCommand())); - $output = $ssh->exec($query, $connection->getUsername()); + $output = $ssh->exec($query, $connection->getUsername(), $connection->getPassword()); $lines = array_filter(explode(PHP_EOL, $output), function ($element) { return !empty(trim($element)); @@ -96,7 +96,7 @@ public function execute(Command $command, Pim $pim): CommandResult $this->logger->debug(sprintf('SshConsole: executing %s command -> %s', get_class($command), $processedCommand)); - $output = $ssh->exec($processedCommand, $connection->getUsername()); + $output = $ssh->exec($processedCommand, $connection->getUsername(), $connection->getPassword()); return new CommandResult($output !== false ? 0 : 1, $output); } diff --git a/src/Infrastructure/Common/config/parameters.yml b/src/Infrastructure/Common/config/parameters.yml index d84236d..95686cd 100644 --- a/src/Infrastructure/Common/config/parameters.yml +++ b/src/Infrastructure/Common/config/parameters.yml @@ -9,6 +9,7 @@ parameters: ssh_hostname_source_pim: '' ssh_port_source_pim: '22' ssh_user_source_pim: '' + ssh_passwd_source_pim: '' # Base URI (with http://) to request the API api_base_uri_source_pim: '' diff --git a/src/Infrastructure/Common/messages.en.yml b/src/Infrastructure/Common/messages.en.yml index 35670a5..59143d1 100644 --- a/src/Infrastructure/Common/messages.en.yml +++ b/src/Infrastructure/Common/messages.en.yml @@ -16,6 +16,8 @@ from_ready_to_source_pim_configured: ssh_port_question: "What is the SSH port of the source PIM? " ssh_port_error: "The SSH port should be an int. " ssh_user_question: "What is the SSH user you want to connect with to the source PIM? " + ssh_passwd_question: "What is the SSH password you want to connect with to the source PIM?" + ssh_auth_mode_question: "How to you want to authenticate on the source PIM SSH server?" ssh_key_path_question: "What is the absolute path of the private SSH key able to connect to the source PIM? " ssh_key_path_error: "Your SSH key path should be an absolute one." ssh_key_protected: "Is your ssh key protected by a passphrase ? " diff --git a/src/Infrastructure/MigrationStep/S010FromReadyToSourcePimConfigured.php b/src/Infrastructure/MigrationStep/S010FromReadyToSourcePimConfigured.php index aa53f4d..47b977d 100644 --- a/src/Infrastructure/MigrationStep/S010FromReadyToSourcePimConfigured.php +++ b/src/Infrastructure/MigrationStep/S010FromReadyToSourcePimConfigured.php @@ -28,6 +28,9 @@ class S010FromReadyToSourcePimConfigured extends AbstractStateMachineSubscriber private const LOCAL_SOURCE_PIM = 'locally'; private const REMOTE_SOURCE_PIM = 'on a remote server'; + private const SSH_AUTH_KEYPAIR = "ssh keypair"; + private const SSH_AUTH_PASSWORD = "password"; + private const YES = 'yes'; private const NO = 'no'; @@ -161,8 +164,19 @@ function ($answer) use ($transPrefix) { $this->translator->trans($transPrefix.'ssh_user_question'), $stateMachine->getDefaultResponse('ssh_user_source_pim') ); + $sshAuthMode = $this->printerAndAsker->askChoiceQuestion( + $this->translator->trans($transPrefix.'ssh_auth_mode_question'), + [self::SSH_AUTH_KEYPAIR, self::SSH_AUTH_PASSWORD] + ); + $password = null; + if ($sshAuthMode === self::SSH_AUTH_PASSWORD) { + $password = $this->printerAndAsker->askSimpleQuestion( + $this->translator->trans($transPrefix . 'ssh_passwd_question'), + $stateMachine->getDefaultResponse('ssh_passwd_source_pim') + ); + } - $stateMachine->setSourcePimConnection(new SshConnection($host, $port, $user)); + $stateMachine->setSourcePimConnection(new SshConnection($host, $port, $user, $password)); $pimProjectPath = $this ->printerAndAsker diff --git a/src/Infrastructure/Pim/SshConnection.php b/src/Infrastructure/Pim/SshConnection.php index 03b247e..2bd9af6 100644 --- a/src/Infrastructure/Pim/SshConnection.php +++ b/src/Infrastructure/Pim/SshConnection.php @@ -24,11 +24,15 @@ class SshConnection implements PimConnection /** @var string */ private $username; - public function __construct(string $host, int $port, string $username) + /** @var string */ + private $password; + + public function __construct(string $host, int $port, string $username, ?string $password = null) { $this->host = $host; $this->port = $port; $this->username = $username; + $this->password = $password; } public static function fromString(string $serverInformation) @@ -56,4 +60,9 @@ public function getUsername(): string { return $this->username; } + + public function getPassword(): ?string + { + return $this->password; + } } diff --git a/src/Infrastructure/SshFileFetcher.php b/src/Infrastructure/SshFileFetcher.php index 30a68b7..ae238d6 100644 --- a/src/Infrastructure/SshFileFetcher.php +++ b/src/Infrastructure/SshFileFetcher.php @@ -30,7 +30,8 @@ public function fetch(PimConnection $connection, string $filePath, bool $withLoc $output = $ssh->exec( sprintf('test -f %s ; echo "$?"', $filePath), - $connection->getUsername() + $connection->getUsername(), + $connection->getPassword() ); if ("0" !== trim($output)) { @@ -40,7 +41,7 @@ public function fetch(PimConnection $connection, string $filePath, bool $withLoc $varDir = sprintf('%s/../../var', __DIR__); $localPath = realpath($varDir).DIRECTORY_SEPARATOR.$fileName; - $sshConnection = $ssh->getAuthenticatedConnection($connection->getUsername()); + $sshConnection = $ssh->getAuthenticatedConnection($connection->getUsername(), $connection->getPassword()); $result = ssh2_scp_recv($sshConnection, $filePath, $localPath); $ssh->disconnect($sshConnection); diff --git a/tests/spec/Infrastructure/MigrationStep/S010FromReadyToSourcePimConfiguredSpec.php b/tests/spec/Infrastructure/MigrationStep/S010FromReadyToSourcePimConfiguredSpec.php index 7839679..76f7cb6 100644 --- a/tests/spec/Infrastructure/MigrationStep/S010FromReadyToSourcePimConfiguredSpec.php +++ b/tests/spec/Infrastructure/MigrationStep/S010FromReadyToSourcePimConfiguredSpec.php @@ -102,7 +102,7 @@ public function it_guards_the_distant_source_pim_configuration( $this->guardDistantSourcePimConfiguration($guardEvent); } - public function it_configures_a_source_pim_from_a_server( + public function it_configures_a_source_pim_from_a_server_with_ssh_keypair( Event $event, TransporteoStateMachine $stateMachine, PimConfiguration $sourcePimConfiguration, @@ -117,6 +117,8 @@ public function it_configures_a_source_pim_from_a_server( $projectPathQuestion = 'What is the absolute path of the source PIM on the server? '; $sshKeyProtected = 'Is your ssh key protected by a passphrase ? '; $sshKeyPassphrase = 'Enter passphrase for %s '; + $sshPasswdQuestion = 'What is the SSH password you want to connect with to the source PIM?'; + $sshAuthMode = 'How to you want to authenticate on the source PIM SSH server?'; $transPrefix = 'from_ready_to_source_pim_configured.on_distant_configuration.'; $translations = [ @@ -127,6 +129,8 @@ public function it_configures_a_source_pim_from_a_server( $transPrefix . 'project_path_question' => $projectPathQuestion, $transPrefix . 'ssh_key_protected' => $sshKeyProtected, $transPrefix . 'ssh_key_passphrase' => $sshKeyPassphrase, + $transPrefix . 'ssh_passwd_question' => $sshPasswdQuestion, + $transPrefix . 'ssh_auth_mode_question' => $sshAuthMode, ]; foreach ($translations as $translationKey => $translation) { @@ -142,14 +146,76 @@ public function it_configures_a_source_pim_from_a_server( $printerAndAsker->askSimpleQuestion($sshUserQuestion, Argument::any(), Argument::any())->willReturn('akeneo'); $sshKeyPath = ResourcesFileLocator::getSshKeyPath(); + $printerAndAsker->askChoiceQuestion($sshAuthMode, ['ssh keypair', 'password'])->willReturn('key pair'); + $printerAndAsker->askSimpleQuestion($sshKeyPathQuestion, Argument::any(), Argument::any())->willReturn($sshKeyPath); $printerAndAsker->askChoiceQuestion($sshKeyProtected, ['yes' => 'yes', 'no' => 'no'])->willReturn('no'); - $sshKey = new SshKey($sshKeyPath); - $serverAccessInformation = new SshConnection('my-super-pim.akeneo.com', 22, 'akeneo', $sshKey); + $serverAccessInformation = new SshConnection('my-super-pim.akeneo.com', 22, 'akeneo'); $stateMachine->setSourcePimConnection($serverAccessInformation)->shouldBeCalled(); $stateMachine->getSourcePimConnection()->willReturn($serverAccessInformation); + $composerJsonPath = ResourcesFileLocator::getStepOneAbsoluteComposerJsonLocalPath(); + $projectPath = str_replace('composer.json', '', $composerJsonPath); + + $printerAndAsker->askSimpleQuestion($projectPathQuestion, Argument::any(), Argument::any())->willReturn($projectPath); + $sourcePimServerInformation = new PimServerInformation(ResourcesFileLocator::getStepOneAbsoluteComposerJsonLocalPath(), 'a-super-project'); + $stateMachine->setSourcePimServerInformation($sourcePimServerInformation)->shouldBeCalled(); + + $sourcePimConfigurator->configure($serverAccessInformation, $sourcePimServerInformation)->willReturn($sourcePimConfiguration); + $stateMachine->setSourcePimConfiguration($sourcePimConfiguration)->shouldBeCalled(); + + $this->onDistantConfiguration($event); + } + + public function it_configures_a_source_pim_from_a_server_with_ssh_passwd( + Event $event, + TransporteoStateMachine $stateMachine, + PimConfiguration $sourcePimConfiguration, + $sourcePimConfigurator, + $printerAndAsker, + $translator + ) { + $hostNameQuestion = 'What is the hostname of the source PIM server? '; + $portQuestion = 'What is the SSH port of the source PIM server? '; + $sshUserQuestion = 'What is the SSH user you want to connect with ? '; + $sshKeyPathQuestion = 'What is the absolute path of the private SSH key able to connect to the server? '; + $projectPathQuestion = 'What is the absolute path of the source PIM on the server? '; + $sshKeyProtected = 'Is your ssh key protected by a passphrase ? '; + $sshKeyPassphrase = 'Enter passphrase for %s '; + $sshPasswdQuestion = 'What is the SSH password you want to connect with to the source PIM?'; + $sshAuthMode = 'How to you want to authenticate on the source PIM SSH server?'; + $transPrefix = 'from_ready_to_source_pim_configured.on_distant_configuration.'; + $translations = [ + $transPrefix . 'hostname_question' => $hostNameQuestion, + $transPrefix . 'ssh_port_question' => $portQuestion, + $transPrefix . 'ssh_user_question' => $sshUserQuestion, + $transPrefix . 'ssh_key_path_question' => $sshKeyPathQuestion, + $transPrefix . 'project_path_question' => $projectPathQuestion, + $transPrefix . 'ssh_key_protected' => $sshKeyProtected, + $transPrefix . 'ssh_key_passphrase' => $sshKeyPassphrase, + $transPrefix . 'ssh_passwd_question' => $sshPasswdQuestion, + $transPrefix . 'ssh_auth_mode_question' => $sshAuthMode, + ]; + + foreach ($translations as $translationKey => $translation) { + $translator->trans($translationKey)->willReturn($translation); + } + + $event->getSubject()->willReturn($stateMachine); + $stateMachine->getProjectName()->willReturn('a-super-project'); + $stateMachine->getDefaultResponse(Argument::any())->willReturn(''); + + $printerAndAsker->askSimpleQuestion($hostNameQuestion, Argument::any(), Argument::any())->willReturn('my-super-pim.akeneo.com'); + $printerAndAsker->askSimpleQuestion($portQuestion, '', Argument::any())->willReturn('22'); + $printerAndAsker->askSimpleQuestion($sshUserQuestion, Argument::any(), Argument::any())->willReturn('akeneo'); + + $printerAndAsker->askChoiceQuestion($sshAuthMode, ['ssh keypair', 'password'])->willReturn('password'); + $printerAndAsker->askSimpleQuestion($sshPasswdQuestion, Argument::any(), Argument::any())->willReturn('akeneo-passwd'); + + $serverAccessInformation = new SshConnection('my-super-pim.akeneo.com', 22, 'akeneo', 'akeneo-passwd'); + $stateMachine->setSourcePimConnection($serverAccessInformation)->shouldBeCalled(); + $stateMachine->getSourcePimConnection()->willReturn($serverAccessInformation); $composerJsonPath = ResourcesFileLocator::getStepOneAbsoluteComposerJsonLocalPath(); $projectPath = str_replace('composer.json', '', $composerJsonPath); @@ -218,6 +284,8 @@ public function it_throws_business_exception_from_technical( $projectPathQuestion = 'What is the absolute path of the source PIM on the server? '; $sshKeyProtected = 'Is your ssh key protected by a passphrase ? '; $sshKeyPassphrase = 'Enter passphrase for %s '; + $sshPasswdQuestion = 'What is the SSH password you want to connect with to the source PIM?'; + $sshAuthMode = 'How to you want to authenticate on the source PIM SSH server?'; $transPrefix = 'from_ready_to_source_pim_configured.on_distant_configuration.'; $translations = [ @@ -228,6 +296,8 @@ public function it_throws_business_exception_from_technical( $transPrefix . 'project_path_question' => $projectPathQuestion, $transPrefix . 'ssh_key_protected' => $sshKeyProtected, $transPrefix . 'ssh_key_passphrase' => $sshKeyPassphrase, + $transPrefix . 'ssh_passwd_question' => $sshPasswdQuestion, + $transPrefix . 'ssh_auth_mode_question' => $sshAuthMode, ]; foreach ($translations as $translationKey => $translation) { @@ -238,13 +308,14 @@ public function it_throws_business_exception_from_technical( $printerAndAsker->askSimpleQuestion($portQuestion, '', Argument::any())->willReturn('22'); $printerAndAsker->askSimpleQuestion($sshUserQuestion, Argument::any(), Argument::any())->willReturn('akeneo'); + $printerAndAsker->askChoiceQuestion($sshAuthMode, ['ssh keypair', 'password'])->willReturn('key pair'); + $sshKeyPath = ResourcesFileLocator::getSshKeyPath(); $printerAndAsker->askSimpleQuestion($sshKeyPathQuestion, Argument::any(), Argument::any())->willReturn($sshKeyPath); $printerAndAsker->askChoiceQuestion($sshKeyProtected, ['yes' => 'yes', 'no' => 'no'])->willReturn('no'); - $sshKey = new SshKey($sshKeyPath); - $serverAccessInformation = new SshConnection('my-super-pim.akeneo.com', 22, 'akeneo', $sshKey); + $serverAccessInformation = new SshConnection('my-super-pim.akeneo.com', 22, 'akeneo'); $stateMachine->setSourcePimConnection($serverAccessInformation)->shouldBeCalled(); $stateMachine->getSourcePimConnection()->willReturn($serverAccessInformation);