diff --git a/src/Command/Auth/AuthAcsfLoginCommand.php b/src/Command/Auth/AuthAcsfLoginCommand.php index 861a180fd..2fc7518ac 100644 --- a/src/Command/Auth/AuthAcsfLoginCommand.php +++ b/src/Command/Auth/AuthAcsfLoginCommand.php @@ -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 { diff --git a/src/Command/Auth/AuthAcsfLogoutCommand.php b/src/Command/Auth/AuthAcsfLogoutCommand.php index 171272e27..910018958 100644 --- a/src/Command/Auth/AuthAcsfLogoutCommand.php +++ b/src/Command/Auth/AuthAcsfLogoutCommand.php @@ -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 { diff --git a/src/Command/Auth/AuthLoginCommand.php b/src/Command/Auth/AuthLoginCommand.php index 321665e83..a9effd9c0 100644 --- a/src/Command/Auth/AuthLoginCommand.php +++ b/src/Command/Auth/AuthLoginCommand.php @@ -11,25 +11,29 @@ 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: $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; } @@ -37,10 +41,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int '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("Acquia CLI will use the API Key {$selectedKey['label']}"); + $output->writeln("Acquia CLI will use the API key {$selectedKey['label']}"); $this->reAuthenticate($this->cloudCredentials->getCloudKey(), $this->cloudCredentials->getCloudSecret(), $this->cloudCredentials->getBaseUri(), $this->cloudCredentials->getAccountsUri()); return Command::SUCCESS; } @@ -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, diff --git a/src/Command/Auth/AuthLogoutCommand.php b/src/Command/Auth/AuthLogoutCommand.php index 9edda39f7..e498709fc 100644 --- a/src/Command/Auth/AuthLogoutCommand.php +++ b/src/Command/Auth/AuthLogoutCommand.php @@ -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("The key $activeKeyLabel will be deactivated on this machine. However, the credentials will remain on disk and can be reactivated by running acli auth:login unless you also choose to delete them."); + $delete = $this->determineOption('delete', FALSE, NULL, NULL, FALSE); $this->datastoreCloud->remove('acli_key'); - - $output->writeln("Unset the Acquia Cloud API key for Acquia CLI"); + $action = 'deactivated'; + if ($delete) { + $this->datastoreCloud->remove("keys.$activeKey"); + $action = 'deleted'; + } + $output->writeln("The active Cloud Platform API credentials were $action"); + $output->writeln('No Cloud Platform API key is active. Run acli auth:login to continue using the Cloud Platform API.'); return Command::SUCCESS; } diff --git a/src/Command/CommandBase.php b/src/Command/CommandBase.php index 73be130d9..316e702ab 100644 --- a/src/Command/CommandBase.php +++ b/src/Command/CommandBase.php @@ -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; @@ -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); @@ -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 -$optionShortcut, --$optionName)"; + $optionString = "option -$optionShortcut, --$optionName"; + } + else { + $optionString = "option --$optionName"; + } + if ($option->acceptValue()) { + $message = "Enter $description ($optionString)"; } else { - $message = "Enter $description (option --$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)) { diff --git a/tests/phpunit/src/Application/KernelTest.php b/tests/phpunit/src/Application/KernelTest.php index fc858a044..367097507 100644 --- a/tests/phpunit/src/Application/KernelTest.php +++ b/tests/phpunit/src/Application/KernelTest.php @@ -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 diff --git a/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php b/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php index 82e25d10d..1bc57d406 100644 --- a/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php +++ b/tests/phpunit/src/Commands/Auth/AuthLoginCommandTest.php @@ -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, diff --git a/tests/phpunit/src/Commands/Auth/AuthLogoutCommandTest.php b/tests/phpunit/src/Commands/Auth/AuthLogoutCommandTest.php index 4f7f241a2..9a0aec271 100644 --- a/tests/phpunit/src/Commands/Auth/AuthLogoutCommandTest.php +++ b/tests/phpunit/src/Commands/Auth/AuthLogoutCommandTest.php @@ -19,36 +19,16 @@ protected function createCommand(): Command { return $this->injectCommand(AuthLogoutCommand::class); } - /** - * @return array - */ - 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); } }