ldc-arcadia-api/src/Presentation/Console/Commands/RegenerateClientSecretCommand.php

179 lines
5.7 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
namespace Presentation\Console\Commands;
use Application\Security\Exceptions\ClientNotFoundException;
use Application\Security\Models\Entities\Client;
use Application\Security\Models\ValueObjects\ClientIdentifier;
use Application\Security\Ports\Repositories\ClientRepository;
use Application\Security\Usecases\RegenerateClientSecret\Input;
use Application\Security\Usecases\RegenerateClientSecret\RegenerateClientSecret;
use Illuminate\Console\Command;
use Presentation\Console\Presenters\Clients\RegenerateClientSecretPresenter;
use Presentation\Console\Presenters\Clients\RegenerateClientSecretView;
use Shared\DDD\Usecase\Builder\UsecaseBuilder;
use Shared\DDD\Usecase\Builder\UsecaseExecutor;
use Shared\Pagination;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
final class RegenerateClientSecretCommand extends Command
{
/**
* @var string
*/
protected $signature = 'client:regenerate-secret
{identifier? : The identifier of the client}';
/**
* @var string
*/
protected $description = 'Regenerate the secret of an API client';
public function __construct(
private readonly ClientRepository $clientRepository,
) {
parent::__construct();
}
#[\Override]
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
parent::complete($input, $suggestions);
if ($input->mustSuggestArgumentValuesFor('identifier')) {
$clients = $this->clientRepository->findAll(Pagination::ofOffsetAndLimit(0, 100));
/** @var list<Suggestion> $items */
$items = array_map(
static fn (Client $client): Suggestion => new Suggestion(
value: $client->identifier->value,
description: sprintf('%s (%s)', $client->name->value, $client->isActive ? 'Active' : 'Inactive'),
),
$clients->getItems(),
);
$suggestions->suggestValues($items);
}
}
public function handle(
RegenerateClientSecret $usecase,
UsecaseExecutor $executor,
): int {
$identifierString = $this->resolveIdentifier();
if ($identifierString === null) {
return self::FAILURE;
}
try {
$clientIdentifier = $this->parseClientIdentifier($identifierString);
$client = $this->clientRepository->findByIdentifier($clientIdentifier);
if ($client === null) {
$this->error(sprintf("Client '%s' not found.", $clientIdentifier->value));
return self::FAILURE;
}
if (! $this->confirmRegeneration($client)) {
$this->warn('Secret regeneration cancelled.');
return self::SUCCESS;
}
$input = new Input($client->id);
/** @var RegenerateClientSecretView $view */
$view = UsecaseBuilder::using($executor)
->do($usecase)
->from($input)
->with(new RegenerateClientSecretPresenter())
->run();
$this->displayResult($client, $view);
return self::SUCCESS;
} catch (ClientNotFoundException | \InvalidArgumentException $e) {
$this->error($e->getMessage());
return self::FAILURE;
}
}
private function resolveIdentifier(): ?string
{
$raw = $this->argument('identifier');
if ($raw !== null) {
return (string) $raw;
}
$clients = $this->clientRepository->findAll(Pagination::ofOffsetAndLimit(0, 50));
$items = $clients->getItems();
if ($items === []) {
$this->error('No clients found in the database.');
return null;
}
$choices = [];
foreach ($items as $client) {
$choices[$client->identifier->value] = sprintf(
'%s (%s) - %s',
$client->name->value,
$client->isActive ? 'Active' : 'Inactive',
$client->identifier->value,
);
}
$selectedDisplay = $this->choice('Which client secret would you like to regenerate?', array_values($choices));
return (string) array_search($selectedDisplay, $choices, true);
}
/**
* @throws \InvalidArgumentException
*/
private function parseClientIdentifier(string $value): ClientIdentifier
{
try {
return new ClientIdentifier($value);
} catch (\InvalidArgumentException) {
throw new \InvalidArgumentException("Invalid client identifier '{$value}'. Expected a valid client identifier.");
}
}
private function confirmRegeneration(Client $client): bool
{
$this->newLine();
$this->warn(sprintf("Regenerating the secret will immediately invalidate the existing secret for client '%s'.", $client->name->value));
return $this->confirm('Do you want to proceed?', false);
}
private function displayResult(Client $client, RegenerateClientSecretView $view): void
{
$this->newLine();
$this->info($view->message);
$this->newLine();
$this->table(
['Field', 'Value'],
[
['ID', $client->id->value],
['Name', $client->name->value],
['Identifier', $view->identifier],
['New Secret', $view->secret],
],
);
$this->newLine();
$this->warn('WARNING: Store the new secret securely, it cannot be retrieved later.');
}
}