diff --git a/module/CLI/config/dependencies.config.php b/module/CLI/config/dependencies.config.php index 313d0022..3c9d74ce 100644 --- a/module/CLI/config/dependencies.config.php +++ b/module/CLI/config/dependencies.config.php @@ -8,7 +8,6 @@ use Doctrine\DBAL\Connection; use GeoIp2\Database\Reader; use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory; use Laminas\ServiceManager\Factory\InvokableFactory; -use Shlinkio\Shlink\CLI\Util\GeolocationDbUpdater; use Shlinkio\Shlink\Common\Doctrine\NoDbNameConnectionFactory; use Shlinkio\Shlink\Core\Domain\DomainService; use Shlinkio\Shlink\Core\Service; @@ -32,7 +31,8 @@ return [ SymfonyCli\Helper\ProcessHelper::class => ProcessHelperFactory::class, PhpExecutableFinder::class => InvokableFactory::class, - GeolocationDbUpdater::class => ConfigAbstractFactory::class, + Util\GeolocationDbUpdater::class => ConfigAbstractFactory::class, + ApiKey\RoleResolver::class => ConfigAbstractFactory::class, Command\ShortUrl\GenerateShortUrlCommand::class => ConfigAbstractFactory::class, Command\ShortUrl\ResolveUrlCommand::class => ConfigAbstractFactory::class, @@ -59,7 +59,8 @@ return [ ], ConfigAbstractFactory::class => [ - GeolocationDbUpdater::class => [DbUpdater::class, Reader::class, LOCAL_LOCK_FACTORY], + Util\GeolocationDbUpdater::class => [DbUpdater::class, Reader::class, LOCAL_LOCK_FACTORY], + ApiKey\RoleResolver::class => [DomainService::class], Command\ShortUrl\GenerateShortUrlCommand::class => [ Service\UrlShortener::class, @@ -75,10 +76,10 @@ return [ Visit\VisitLocator::class, IpLocationResolverInterface::class, LockFactory::class, - GeolocationDbUpdater::class, + Util\GeolocationDbUpdater::class, ], - Command\Api\GenerateKeyCommand::class => [ApiKeyService::class], + Command\Api\GenerateKeyCommand::class => [ApiKeyService::class, ApiKey\RoleResolver::class], Command\Api\DisableKeyCommand::class => [ApiKeyService::class], Command\Api\ListKeysCommand::class => [ApiKeyService::class], diff --git a/module/CLI/src/ApiKey/RoleResolver.php b/module/CLI/src/ApiKey/RoleResolver.php new file mode 100644 index 00000000..d007697a --- /dev/null +++ b/module/CLI/src/ApiKey/RoleResolver.php @@ -0,0 +1,36 @@ +domainService = $domainService; + } + + public function determineRoles(InputInterface $input): array + { + $domainAuthority = $input->getOption('domain-only'); + $author = $input->getOption('author-only'); + + $roleDefinitions = []; + if ($author) { + $roleDefinitions[] = RoleDefinition::forAuthoredShortUrls(); + } + if ($domainAuthority !== null) { + $domain = $this->domainService->getOrCreate($domainAuthority); + $roleDefinitions[] = RoleDefinition::forDomain($domain->getId()); + } + + return $roleDefinitions; + } +} diff --git a/module/CLI/src/ApiKey/RoleResolverInterface.php b/module/CLI/src/ApiKey/RoleResolverInterface.php new file mode 100644 index 00000000..98d50483 --- /dev/null +++ b/module/CLI/src/ApiKey/RoleResolverInterface.php @@ -0,0 +1,19 @@ +%command.name% generates a new valid API key. + + %command.full_name% + + You can optionally set its expiration date with --expirationDate or -e: + + %command.full_name% --expirationDate 2020-01-01 + + You can also set roles to the API key: + + * Can interact with short URLs created with this API key: %command.full_name% --author-only + * Can interact with short URLs for one domain only: %command.full_name% --domain-only=example.com + * Both: %command.full_name% --author-only --domain-only=example.com + HELP; private ApiKeyServiceInterface $apiKeyService; + private RoleResolverInterface $roleResolver; - public function __construct(ApiKeyServiceInterface $apiKeyService) + public function __construct(ApiKeyServiceInterface $apiKeyService, RoleResolverInterface $roleResolver) { - $this->apiKeyService = $apiKeyService; parent::__construct(); + $this->apiKeyService = $apiKeyService; + $this->roleResolver = $roleResolver; } protected function configure(): void @@ -37,15 +56,33 @@ class GenerateKeyCommand extends Command 'e', InputOption::VALUE_REQUIRED, 'The date in which the API key should expire. Use any valid PHP format.', - ); + ) + ->addOption( + RoleResolverInterface::AUTHOR_ONLY_PARAM, + 'a', + InputOption::VALUE_NONE, + sprintf('Adds the "%s" role to the new API key.', Role::AUTHORED_SHORT_URLS), + ) + ->addOption( + RoleResolverInterface::DOMAIN_ONLY_PARAM, + 'd', + InputOption::VALUE_REQUIRED, + sprintf('Adds the "%s" role to the new API key, with the domain provided.', Role::DOMAIN_SPECIFIC), + ) + ->setHelp(self::HELP); } protected function execute(InputInterface $input, OutputInterface $output): ?int { $expirationDate = $input->getOption('expirationDate'); - $apiKey = $this->apiKeyService->create(isset($expirationDate) ? Chronos::parse($expirationDate) : null); + $apiKey = $this->apiKeyService->create( + isset($expirationDate) ? Chronos::parse($expirationDate) : null, + ...$this->roleResolver->determineRoles($input), + ); + + // TODO Print permissions that have been set + (new SymfonyStyle($input, $output))->success(sprintf('Generated API key: "%s"', $apiKey->toString())); - (new SymfonyStyle($input, $output))->success(sprintf('Generated API key: "%s"', $apiKey)); return ExitCodes::EXIT_SUCCESS; } } diff --git a/module/CLI/test/Command/Api/GenerateKeyCommandTest.php b/module/CLI/test/Command/Api/GenerateKeyCommandTest.php index 7ff87a3f..744fb482 100644 --- a/module/CLI/test/Command/Api/GenerateKeyCommandTest.php +++ b/module/CLI/test/Command/Api/GenerateKeyCommandTest.php @@ -9,10 +9,12 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; +use Shlinkio\Shlink\CLI\ApiKey\RoleResolverInterface; use Shlinkio\Shlink\CLI\Command\Api\GenerateKeyCommand; use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Tester\CommandTester; class GenerateKeyCommandTest extends TestCase @@ -21,11 +23,15 @@ class GenerateKeyCommandTest extends TestCase private CommandTester $commandTester; private ObjectProphecy $apiKeyService; + private ObjectProphecy $roleResolver; public function setUp(): void { $this->apiKeyService = $this->prophesize(ApiKeyServiceInterface::class); - $command = new GenerateKeyCommand($this->apiKeyService->reveal()); + $this->roleResolver = $this->prophesize(RoleResolverInterface::class); + $this->roleResolver->determineRoles(Argument::type(InputInterface::class))->willReturn([]); + + $command = new GenerateKeyCommand($this->apiKeyService->reveal(), $this->roleResolver->reveal()); $app = new Application(); $app->add($command); $this->commandTester = new CommandTester($command);