From 9e9d213f208f5f7c728a92c45b9b50870bcd6777 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 11 Jan 2021 16:32:59 +0100 Subject: [PATCH] Added roles info to api key generation and api key list --- module/CLI/src/ApiKey/RoleResolver.php | 2 +- module/CLI/src/Command/Api/ListKeysCommand.php | 18 +++++++++++++----- module/CLI/test/ApiKey/RoleResolverTest.php | 5 +++-- .../Domain/Repository/DomainRepositoryTest.php | 6 +++--- .../Repository/ShortUrlRepositoryTest.php | 4 ++-- .../test-db/Repository/TagRepositoryTest.php | 2 +- .../test-db/Repository/VisitRepositoryTest.php | 2 +- module/Core/test/Domain/DomainServiceTest.php | 2 +- .../Rest/src/ApiKey/Model/RoleDefinition.php | 8 ++++++-- module/Rest/src/ApiKey/Role.php | 14 ++++++++++++++ .../Rest/test-api/Fixtures/ApiKeyFixture.php | 2 +- module/Rest/test/Service/ApiKeyServiceTest.php | 6 +++++- 12 files changed, 51 insertions(+), 20 deletions(-) diff --git a/module/CLI/src/ApiKey/RoleResolver.php b/module/CLI/src/ApiKey/RoleResolver.php index d007697a..67747983 100644 --- a/module/CLI/src/ApiKey/RoleResolver.php +++ b/module/CLI/src/ApiKey/RoleResolver.php @@ -28,7 +28,7 @@ class RoleResolver implements RoleResolverInterface } if ($domainAuthority !== null) { $domain = $this->domainService->getOrCreate($domainAuthority); - $roleDefinitions[] = RoleDefinition::forDomain($domain->getId()); + $roleDefinitions[] = RoleDefinition::forDomain($domain); } return $roleDefinitions; diff --git a/module/CLI/src/Command/Api/ListKeysCommand.php b/module/CLI/src/Command/Api/ListKeysCommand.php index f54ad8dd..17054f95 100644 --- a/module/CLI/src/Command/Api/ListKeysCommand.php +++ b/module/CLI/src/Command/Api/ListKeysCommand.php @@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\CLI\Command\Api; use Shlinkio\Shlink\CLI\Util\ExitCodes; use Shlinkio\Shlink\CLI\Util\ShlinkTable; +use Shlinkio\Shlink\Rest\ApiKey\Role; use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; use Symfony\Component\Console\Command\Command; @@ -14,7 +15,8 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use function array_filter; -use function array_map; +use function Functional\map; +use function implode; use function sprintf; class ListKeysCommand extends Command @@ -50,7 +52,7 @@ class ListKeysCommand extends Command { $enabledOnly = $input->getOption('enabledOnly'); - $rows = array_map(function (ApiKey $apiKey) use ($enabledOnly) { + $rows = map($this->apiKeyService->listKeys($enabledOnly), function (ApiKey $apiKey) use ($enabledOnly) { $expiration = $apiKey->getExpirationDate(); $messagePattern = $this->determineMessagePattern($apiKey); @@ -60,13 +62,21 @@ class ListKeysCommand extends Command $rowData[] = sprintf($messagePattern, $this->getEnabledSymbol($apiKey)); } $rowData[] = $expiration !== null ? $expiration->toAtomString() : '-'; + $rowData[] = $apiKey->isAdmin() ? '-' : implode("\n", $apiKey->mapRoles( + fn (string $roleName, array $meta) => + empty($meta) + ? Role::toFriendlyName($roleName) + : sprintf('%s: %s', Role::toFriendlyName($roleName), Role::domainAuthorityFromMeta($meta)), + )); + return $rowData; - }, $this->apiKeyService->listKeys($enabledOnly)); + }); ShlinkTable::fromOutput($output)->render(array_filter([ 'Key', ! $enabledOnly ? 'Is enabled' : null, 'Expiration date', + 'Roles', ]), $rows); return ExitCodes::EXIT_SUCCESS; } @@ -80,8 +90,6 @@ class ListKeysCommand extends Command return $apiKey->isExpired() ? self::WARNING_STRING_PATTERN : self::SUCCESS_STRING_PATTERN; } - /** - */ private function getEnabledSymbol(ApiKey $apiKey): string { return ! $apiKey->isEnabled() || $apiKey->isExpired() ? '---' : '+++'; diff --git a/module/CLI/test/ApiKey/RoleResolverTest.php b/module/CLI/test/ApiKey/RoleResolverTest.php index 4e498761..a50c2b12 100644 --- a/module/CLI/test/ApiKey/RoleResolverTest.php +++ b/module/CLI/test/ApiKey/RoleResolverTest.php @@ -47,6 +47,7 @@ class RoleResolverTest extends TestCase public function provideRoles(): iterable { + $domain = (new Domain('example.com'))->setId('1'); $buildInput = function (array $definition): InputInterface { $input = $this->prophesize(InputInterface::class); @@ -64,7 +65,7 @@ class RoleResolverTest extends TestCase ]; yield 'domain role only' => [ $buildInput([RoleResolver::DOMAIN_ONLY_PARAM => 'example.com', RoleResolver::AUTHOR_ONLY_PARAM => false]), - [RoleDefinition::forDomain('1')], + [RoleDefinition::forDomain($domain)], 1, ]; yield 'author role only' => [ @@ -74,7 +75,7 @@ class RoleResolverTest extends TestCase ]; yield 'both roles' => [ $buildInput([RoleResolver::DOMAIN_ONLY_PARAM => 'example.com', RoleResolver::AUTHOR_ONLY_PARAM => true]), - [RoleDefinition::forAuthoredShortUrls(), RoleDefinition::forDomain('1')], + [RoleDefinition::forAuthoredShortUrls(), RoleDefinition::forDomain($domain)], 1, ]; } diff --git a/module/Core/test-db/Domain/Repository/DomainRepositoryTest.php b/module/Core/test-db/Domain/Repository/DomainRepositoryTest.php index 74d5297e..b39f3a87 100644 --- a/module/Core/test-db/Domain/Repository/DomainRepositoryTest.php +++ b/module/Core/test-db/Domain/Repository/DomainRepositoryTest.php @@ -72,12 +72,12 @@ class DomainRepositoryTest extends DatabaseTestCase $this->getEntityManager()->flush(); - $authorAndDomainApiKey->registerRole(RoleDefinition::forDomain($fooDomain->getId())); + $authorAndDomainApiKey->registerRole(RoleDefinition::forDomain($fooDomain)); - $fooDomainApiKey = ApiKey::withRoles(RoleDefinition::forDomain($fooDomain->getId())); + $fooDomainApiKey = ApiKey::withRoles(RoleDefinition::forDomain($fooDomain)); $this->getEntityManager()->persist($fooDomainApiKey); - $barDomainApiKey = ApiKey::withRoles(RoleDefinition::forDomain($barDomain->getId())); + $barDomainApiKey = ApiKey::withRoles(RoleDefinition::forDomain($barDomain)); $this->getEntityManager()->persist($fooDomainApiKey); $this->getEntityManager()->flush(); diff --git a/module/Core/test-db/Repository/ShortUrlRepositoryTest.php b/module/Core/test-db/Repository/ShortUrlRepositoryTest.php index a95308ff..c806a243 100644 --- a/module/Core/test-db/Repository/ShortUrlRepositoryTest.php +++ b/module/Core/test-db/Repository/ShortUrlRepositoryTest.php @@ -335,9 +335,9 @@ class ShortUrlRepositoryTest extends DatabaseTestCase $this->getEntityManager()->persist($apiKey); $otherApiKey = ApiKey::withRoles(RoleDefinition::forAuthoredShortUrls()); $this->getEntityManager()->persist($otherApiKey); - $wrongDomainApiKey = ApiKey::withRoles(RoleDefinition::forDomain($wrongDomain->getId())); + $wrongDomainApiKey = ApiKey::withRoles(RoleDefinition::forDomain($wrongDomain)); $this->getEntityManager()->persist($wrongDomainApiKey); - $rightDomainApiKey = ApiKey::withRoles(RoleDefinition::forDomain($rightDomain->getId())); + $rightDomainApiKey = ApiKey::withRoles(RoleDefinition::forDomain($rightDomain)); $this->getEntityManager()->persist($rightDomainApiKey); $shortUrl = new ShortUrl('foo', ShortUrlMeta::fromRawData( diff --git a/module/Core/test-db/Repository/TagRepositoryTest.php b/module/Core/test-db/Repository/TagRepositoryTest.php index 8f9894cd..fce6e61d 100644 --- a/module/Core/test-db/Repository/TagRepositoryTest.php +++ b/module/Core/test-db/Repository/TagRepositoryTest.php @@ -114,7 +114,7 @@ class TagRepositoryTest extends DatabaseTestCase $authorApiKey = ApiKey::withRoles(RoleDefinition::forAuthoredShortUrls()); $this->getEntityManager()->persist($authorApiKey); - $domainApiKey = ApiKey::withRoles(RoleDefinition::forDomain($domain->getId())); + $domainApiKey = ApiKey::withRoles(RoleDefinition::forDomain($domain)); $this->getEntityManager()->persist($domainApiKey); $names = ['foo', 'bar', 'baz', 'another']; diff --git a/module/Core/test-db/Repository/VisitRepositoryTest.php b/module/Core/test-db/Repository/VisitRepositoryTest.php index 516b1dd3..b1c6e4bb 100644 --- a/module/Core/test-db/Repository/VisitRepositoryTest.php +++ b/module/Core/test-db/Repository/VisitRepositoryTest.php @@ -221,7 +221,7 @@ class VisitRepositoryTest extends DatabaseTestCase $this->getEntityManager()->persist($shortUrl3); $this->createVisitsForShortUrl($shortUrl3, 7); - $domainApiKey = ApiKey::withRoles(RoleDefinition::forDomain($domain->getId())); + $domainApiKey = ApiKey::withRoles(RoleDefinition::forDomain($domain)); $this->getEntityManager()->persist($domainApiKey); $this->getEntityManager()->flush(); diff --git a/module/Core/test/Domain/DomainServiceTest.php b/module/Core/test/Domain/DomainServiceTest.php index 6a1ccef8..46e39c5a 100644 --- a/module/Core/test/Domain/DomainServiceTest.php +++ b/module/Core/test/Domain/DomainServiceTest.php @@ -51,7 +51,7 @@ class DomainServiceTest extends TestCase { $default = new DomainItem('default.com', true); $adminApiKey = new ApiKey(); - $domainSpecificApiKey = ApiKey::withRoles(RoleDefinition::forDomain('123')); + $domainSpecificApiKey = ApiKey::withRoles(RoleDefinition::forDomain((new Domain(''))->setId('123'))); yield 'empty list without API key' => [[], [$default], null]; yield 'one item without API key' => [ diff --git a/module/Rest/src/ApiKey/Model/RoleDefinition.php b/module/Rest/src/ApiKey/Model/RoleDefinition.php index bb9165e8..569044dc 100644 --- a/module/Rest/src/ApiKey/Model/RoleDefinition.php +++ b/module/Rest/src/ApiKey/Model/RoleDefinition.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\ApiKey\Model; +use Shlinkio\Shlink\Core\Entity\Domain; use Shlinkio\Shlink\Rest\ApiKey\Role; final class RoleDefinition @@ -22,9 +23,12 @@ final class RoleDefinition return new self(Role::AUTHORED_SHORT_URLS, []); } - public static function forDomain(string $domainId): self + public static function forDomain(Domain $domain): self { - return new self(Role::DOMAIN_SPECIFIC, ['domain_id' => $domainId]); + return new self( + Role::DOMAIN_SPECIFIC, + ['domain_id' => $domain->getId(), 'authority' => $domain->getAuthority()], + ); } public function roleName(): string diff --git a/module/Rest/src/ApiKey/Role.php b/module/Rest/src/ApiKey/Role.php index 87bad5fc..ff3211ba 100644 --- a/module/Rest/src/ApiKey/Role.php +++ b/module/Rest/src/ApiKey/Role.php @@ -16,6 +16,10 @@ class Role { public const AUTHORED_SHORT_URLS = 'AUTHORED_SHORT_URLS'; public const DOMAIN_SPECIFIC = 'DOMAIN_SPECIFIC'; + private const ROLE_FRIENDLY_NAMES = [ + self::AUTHORED_SHORT_URLS => 'Author only', + self::DOMAIN_SPECIFIC => 'Domain only', + ]; public static function toSpec(ApiKeyRole $role, bool $inlined): Specification { @@ -35,4 +39,14 @@ class Role { return $meta['domain_id'] ?? '-1'; } + + public static function domainAuthorityFromMeta(array $meta): string + { + return $meta['authority'] ?? ''; + } + + public static function toFriendlyName(string $roleName): string + { + return self::ROLE_FRIENDLY_NAMES[$roleName] ?? ''; + } } diff --git a/module/Rest/test-api/Fixtures/ApiKeyFixture.php b/module/Rest/test-api/Fixtures/ApiKeyFixture.php index d0a1f802..c6383968 100644 --- a/module/Rest/test-api/Fixtures/ApiKeyFixture.php +++ b/module/Rest/test-api/Fixtures/ApiKeyFixture.php @@ -33,7 +33,7 @@ class ApiKeyFixture extends AbstractFixture implements DependentFixtureInterface /** @var Domain $exampleDomain */ $exampleDomain = $this->getReference('example_domain'); $domainApiKey = $this->buildApiKey('domain_api_key', true); - $domainApiKey->registerRole(RoleDefinition::forDomain($exampleDomain->getId())); + $domainApiKey->registerRole(RoleDefinition::forDomain($exampleDomain)); $manager->persist($domainApiKey); $manager->flush(); diff --git a/module/Rest/test/Service/ApiKeyServiceTest.php b/module/Rest/test/Service/ApiKeyServiceTest.php index 3bbdbf64..6879d492 100644 --- a/module/Rest/test/Service/ApiKeyServiceTest.php +++ b/module/Rest/test/Service/ApiKeyServiceTest.php @@ -12,6 +12,7 @@ use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; +use Shlinkio\Shlink\Core\Entity\Domain; use Shlinkio\Shlink\Rest\ApiKey\Model\RoleDefinition; use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Service\ApiKeyService; @@ -51,7 +52,10 @@ class ApiKeyServiceTest extends TestCase { yield 'no expiration date' => [null, []]; yield 'expiration date' => [Chronos::parse('2030-01-01'), []]; - yield 'roles' => [null, [RoleDefinition::forDomain('123'), RoleDefinition::forAuthoredShortUrls()]]; + yield 'roles' => [null, [ + RoleDefinition::forDomain((new Domain(''))->setId('123')), + RoleDefinition::forAuthoredShortUrls(), + ]]; } /**