Skip to content

Commit

Permalink
CLI-1212: [auth:logout] remove secrets (#1643)
Browse files Browse the repository at this point in the history
* CLI-1212: [auth:logout] remove secrets

* kill mutants

* refactor
  • Loading branch information
danepowell authored Dec 6, 2023
1 parent 6917e4a commit 4d08db3
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 129 deletions.
2 changes: 1 addition & 1 deletion src/Command/Auth/AuthAcsfLoginCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(name: 'auth:acsf-login', description: 'Register your Site Factory API key and secret to use API functionality')]
#[AsCommand(name: 'auth:acsf-login', description: 'Register Site Factory API credentials')]
final class AuthAcsfLoginCommand extends CommandBase {

protected function configure(): void {
Expand Down
2 changes: 1 addition & 1 deletion src/Command/Auth/AuthAcsfLogoutCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(name: 'auth:acsf-logout', description: 'Remove your Site Factory key and secret from your local machine.')]
#[AsCommand(name: 'auth:acsf-logout', description: 'Remove Site Factory API credentials')]
final class AuthAcsfLogoutCommand extends CommandBase {

protected function execute(InputInterface $input, OutputInterface $output): int {
Expand Down
28 changes: 16 additions & 12 deletions src/Command/Auth/AuthLoginCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,40 @@
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(name: 'auth:login', description: 'Register your Cloud API key and secret to use API functionality', aliases: ['login'])]
#[AsCommand(name: 'auth:login', description: 'Register Cloud Platform API credentials', aliases: ['login'])]
final class AuthLoginCommand extends CommandBase {

protected function configure(): void {
$this
->addOption('key', 'k', InputOption::VALUE_REQUIRED, 'Your Cloud API key')
->addOption('secret', 's', InputOption::VALUE_REQUIRED, 'Your Cloud API secret');
->addOption('key', 'k', InputOption::VALUE_REQUIRED, 'Your Cloud Platform API key')
->addOption('secret', 's', InputOption::VALUE_REQUIRED, 'Your Cloud Platform API secret')
->setHelp('Acquia CLI can store multiple sets of credentials in case you have multiple Cloud Platform accounts. However, only a single account can be active at a time. This command allows you to activate a new or existing set of credentials.');
}

protected function execute(InputInterface $input, OutputInterface $output): int {
if ($this->cloudApiClientService->isMachineAuthenticated()) {
$answer = $this->io->confirm('Your machine has already been authenticated with the Cloud Platform API, would you like to re-authenticate?');
if (!$answer) {
return Command::SUCCESS;
}
$keys = $this->datastoreCloud->get('keys');
$activeKey = $this->datastoreCloud->get('acli_key');
if ($activeKey) {
$activeKeyLabel = $keys[$activeKey]['label'];
$output->write("The following Cloud Platform API key is active: <options=bold>$activeKeyLabel</>");
}
else {
$output->write('No Cloud Platform API key is active');
}

// If keys already are saved locally, prompt to select.
if ($input->isInteractive() && $keys = $this->datastoreCloud->get('keys')) {
if ($keys && $input->isInteractive()) {
foreach ($keys as $uuid => $key) {
$keys[$uuid]['uuid'] = $uuid;
}
$keys['create_new'] = [
'label' => 'Enter a new API key',
'uuid' => 'create_new',
];
$selectedKey = $this->promptChooseFromObjectsOrArrays($keys, 'uuid', 'label', 'Choose which API key to use');
$selectedKey = $this->promptChooseFromObjectsOrArrays($keys, 'uuid', 'label', 'Activate a Cloud Platform API key');
if ($selectedKey['uuid'] !== 'create_new') {
$this->datastoreCloud->set('acli_key', $selectedKey['uuid']);
$output->writeln("<info>Acquia CLI will use the API Key <options=bold>{$selectedKey['label']}</></info>");
$output->writeln("<info>Acquia CLI will use the API key <options=bold>{$selectedKey['label']}</></info>");
$this->reAuthenticate($this->cloudCredentials->getCloudKey(), $this->cloudCredentials->getCloudSecret(), $this->cloudCredentials->getBaseUri(), $this->cloudCredentials->getAccountsUri());
return Command::SUCCESS;
}
Expand All @@ -57,7 +61,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}

private function writeApiCredentialsToDisk(string $apiKey, string $apiSecret): void {
$tokenInfo = $this->cloudApiClientService->getClient()->request('get', "/account/tokens/{$apiKey}");
$tokenInfo = $this->cloudApiClientService->getClient()->request('get', "/account/tokens/$apiKey");
$keys = $this->datastoreCloud->get('keys');
$keys[$apiKey] = [
'label' => $tokenInfo->label,
Expand Down
29 changes: 21 additions & 8 deletions src/Command/Auth/AuthLogoutCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,37 @@
namespace Acquia\Cli\Command\Auth;

use Acquia\Cli\Command\CommandBase;
use Acquia\Cli\Exception\AcquiaCliException;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(name: 'auth:logout', description: 'Remove Cloud API key and secret from local machine.', aliases: ['logout'])]
#[AsCommand(name: 'auth:logout', description: 'Remove Cloud Platform API credentials', aliases: ['logout'])]
final class AuthLogoutCommand extends CommandBase {

protected function configure(): void {
$this->addOption('delete', NULL, InputOption::VALUE_NEGATABLE, 'Delete the active Cloud Platform API credentials');
}

protected function execute(InputInterface $input, OutputInterface $output): int {
if ($this->cloudApiClientService->isMachineAuthenticated()) {
$answer = $this->io->confirm('Are you sure you\'d like to unset the Acquia Cloud API key for Acquia CLI?');
if (!$answer) {
return Command::SUCCESS;
}
$keys = $this->datastoreCloud->get('keys');
$activeKey = $this->datastoreCloud->get('acli_key');
if (!$activeKey) {
throw new AcquiaCliException('There is no active Cloud Platform API key');
}
$activeKeyLabel = $keys[$activeKey]['label'];
$output->writeln("<info>The key <options=bold>$activeKeyLabel</> will be deactivated on this machine. However, the credentials will remain on disk and can be reactivated by running <options=bold>acli auth:login</> unless you also choose to delete them.</info>");
$delete = $this->determineOption('delete', FALSE, NULL, NULL, FALSE);
$this->datastoreCloud->remove('acli_key');

$output->writeln("Unset the Acquia Cloud API key for Acquia CLI</info>");
$action = 'deactivated';
if ($delete) {
$this->datastoreCloud->remove("keys.$activeKey");
$action = 'deleted';
}
$output->writeln("<info>The active Cloud Platform API credentials were $action</info>");
$output->writeln('<info>No Cloud Platform API key is active. Run <options=bold>acli auth:login</> to continue using the Cloud Platform API.</info>');

return Command::SUCCESS;
}
Expand Down
23 changes: 19 additions & 4 deletions src/Command/CommandBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Terminal;
Expand Down Expand Up @@ -1349,7 +1350,7 @@ protected function determineApiSecret(): string {
* explicitly or by default. In other words, we can't prompt for the value of
* an option that already has a default value.
*/
protected function determineOption(string $optionName, bool $hidden = FALSE, ?Closure $validator = NULL, ?Closure $normalizer = NULL, ?string $default = NULL): string|int|null {
protected function determineOption(string $optionName, bool $hidden = FALSE, ?Closure $validator = NULL, ?Closure $normalizer = NULL, string|bool|null $default = NULL): string|int|bool|null {
if ($optionValue = $this->input->getOption($optionName)) {
if (isset($normalizer)) {
$optionValue = $normalizer($optionValue);
Expand All @@ -1360,18 +1361,32 @@ protected function determineOption(string $optionName, bool $hidden = FALSE, ?Cl
return $optionValue;
}
$option = $this->getDefinition()->getOption($optionName);
if ($option->isNegatable() && $this->input->getOption("no-$optionName")) {
return FALSE;
}
$optionShortcut = $option->getShortcut();
$description = lcfirst($option->getDescription());
if ($optionShortcut) {
$message = "Enter $description (option <options=bold>-$optionShortcut</>, <options=bold>--$optionName</>)";
$optionString = "option <options=bold>-$optionShortcut</>, <options=bold>--$optionName</>";
}
else {
$optionString = "option <options=bold>--$optionName</>";
}
if ($option->acceptValue()) {
$message = "Enter $description ($optionString)";
}
else {
$message = "Enter $description (option <options=bold>--$optionName</>)";
$message = "Do you want to $description ($optionString)?";
}
$optional = $option->isValueOptional();
$message .= $optional ? ' (optional)' : '';
$message .= $hidden ? ' (input will be hidden)' : '';
$question = new Question($message, $default);
if ($option->acceptValue()) {
$question = new Question($message, $default);
}
else {
$question = new ConfirmationQuestion($message, $default);
}
$question->setHidden($this->localMachineHelper->useTty() && $hidden);
$question->setHiddenFallback($hidden);
if (isset($normalizer)) {
Expand Down
8 changes: 4 additions & 4 deletions tests/phpunit/src/Application/KernelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ private function getEnd(): string {
archive
archive:export Export an archive of the Drupal application including code, files, and database
auth
auth:acsf-login Register your Site Factory API key and secret to use API functionality
auth:acsf-logout Remove your Site Factory key and secret from your local machine.
auth:login [login] Register your Cloud API key and secret to use API functionality
auth:logout [logout] Remove Cloud API key and secret from local machine.
auth:acsf-login Register Site Factory API credentials
auth:acsf-logout Remove Site Factory API credentials
auth:login [login] Register Cloud Platform API credentials
auth:logout [logout] Remove Cloud Platform API credentials
codestudio
codestudio:php-version Change the PHP version in Code Studio
codestudio:wizard [cs:wizard] Create and/or configure a new Code Studio project for a given Acquia Cloud application
Expand Down
73 changes: 0 additions & 73 deletions tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,79 +22,6 @@ protected function createCommand(): Command {
}

public function providerTestAuthLoginCommand(): Generator {
yield 'Interactive keys' => [
// $machineIsAuthenticated
FALSE,
// $assertCloudPrompts
TRUE,
[
// Would you like to share anonymous performance usage and data? (yes/no) [yes]
'yes',
// Do you want to open this page to generate a token now?
'no',
// Enter your API Key:
$this->key,
// Enter your API Secret:
$this->secret,
],
// No arguments, all interactive.
[],
// Output to assert.
'Saved credentials',
];
yield 'Already authenticated, enter new key' => [
// $machineIsAuthenticated
TRUE,
// $assertCloudPrompts
TRUE,
[
// Your machine has already been authenticated with the Cloud Platform API, would you like to re-authenticate?
'yes',
// Choose which API key to use:
"Enter a new API key",
// Do you want to open this page to generate a token now?
'no',
// Enter your API Key:
$this->key,
// Enter your API Secret:
$this->secret,
],
// No arguments, all interactive.
[],
// Output to assert.
'Saved credentials',
];
yield 'Already authenticated, use existing key' => [
// $machineIsAuthenticated
TRUE,
// $assertCloudPrompts
FALSE,
[
// Your machine has already been authenticated with the Cloud Platform API, would you like to re-authenticate?
'yes',
// Choose which API key to use:
'Test Key',
// @todo Make sure this key has the right value to assert.
],
// No arguments, all interactive.
[],
// Output to assert.
'Acquia CLI will use the API Key',
];
yield 'Already authenticated, abort' => [
// $machineIsAuthenticated
TRUE,
// $assertCloudPrompts
FALSE,
[
// Your machine has already been authenticated with the Cloud Platform API, would you like to re-authenticate?
'no',
],
// No arguments, all interactive.
[],
// Output to assert.
'Your machine has already been authenticated',
];
yield 'Keys as args' => [
// $machineIsAuthenticated
FALSE,
Expand Down
32 changes: 6 additions & 26 deletions tests/phpunit/src/Commands/Auth/AuthLogoutCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,16 @@ protected function createCommand(): Command {
return $this->injectCommand(AuthLogoutCommand::class);
}

/**
* @return array<mixed>
*/
public function providerTestAuthLogoutCommand(): array {
return [
[FALSE, []],
[
TRUE,
// Are you sure you'd like to remove your Cloud API login credentials from this machine?
['y'],
],
];
}

/**
* @dataProvider providerTestAuthLogoutCommand
* @param array $inputs
*/
public function testAuthLogoutCommand(bool $machineIsAuthenticated, array $inputs): void {
if (!$machineIsAuthenticated) {
$this->clientServiceProphecy->isMachineAuthenticated()->willReturn(FALSE);
$this->removeMockCloudConfigFile();
}

$this->executeCommand([], $inputs);
public function testAuthLogoutCommand(): void {
$this->executeCommand();
$output = $this->getDisplay();
// Assert creds are removed locally.
$this->assertFileExists($this->cloudConfigFilepath);
$config = new CloudDataStore($this->localMachineHelper, new CloudDataConfig(), $this->cloudConfigFilepath);
$this->assertFalse($config->exists('acli_key'));
$this->assertNotEmpty($config->get('keys'));
$this->assertStringContainsString('The key Test Key will be deactivated on this machine.', $output);
$this->assertStringContainsString('Do you want to delete the active Cloud Platform API credentials (option --delete)? (yes/no) [no]:', $output);
$this->assertStringContainsString('The active Cloud Platform API credentials were deactivated', $output);
}

}

0 comments on commit 4d08db3

Please sign in to comment.