From 19834f6715456bc063718f5b5ab381e3c98ef851 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 4 Jan 2021 15:55:59 +0100 Subject: [PATCH] Applied API role specs to domains list --- module/Core/src/Domain/DomainService.php | 13 ++++++++++--- module/Core/src/Domain/DomainServiceInterface.php | 3 ++- .../Core/src/Domain/Repository/DomainRepository.php | 11 ++++++++--- .../Domain/Repository/DomainRepositoryInterface.php | 6 ++++-- .../Domain/Repository/DomainRepositoryTest.php | 2 +- module/Core/test/Domain/DomainServiceTest.php | 2 +- module/Rest/src/Action/Domain/ListDomainsAction.php | 4 +++- module/Rest/src/Entity/ApiKey.php | 7 ++++++- .../test/Action/Domain/ListDomainsActionTest.php | 8 +++++--- 9 files changed, 40 insertions(+), 16 deletions(-) diff --git a/module/Core/src/Domain/DomainService.php b/module/Core/src/Domain/DomainService.php index 4e5e5da8..836ca1da 100644 --- a/module/Core/src/Domain/DomainService.php +++ b/module/Core/src/Domain/DomainService.php @@ -8,6 +8,8 @@ use Doctrine\ORM\EntityManagerInterface; use Shlinkio\Shlink\Core\Domain\Model\DomainItem; use Shlinkio\Shlink\Core\Domain\Repository\DomainRepositoryInterface; use Shlinkio\Shlink\Core\Entity\Domain; +use Shlinkio\Shlink\Rest\ApiKey\Role; +use Shlinkio\Shlink\Rest\Entity\ApiKey; use function Functional\map; @@ -25,15 +27,20 @@ class DomainService implements DomainServiceInterface /** * @return DomainItem[] */ - public function listDomains(): array + public function listDomains(?ApiKey $apiKey = null): array { /** @var DomainRepositoryInterface $repo */ $repo = $this->em->getRepository(Domain::class); - $domains = $repo->findDomainsWithout($this->defaultDomain); + $domains = $repo->findDomainsWithout($this->defaultDomain, $apiKey); + $mappedDomains = map($domains, fn (Domain $domain) => new DomainItem($domain->getAuthority(), false)); + + if ($apiKey !== null && $apiKey->hasRole(Role::DOMAIN_SPECIFIC)) { + return $mappedDomains; + } return [ new DomainItem($this->defaultDomain, true), - ...map($domains, fn (Domain $domain) => new DomainItem($domain->getAuthority(), false)), + ...$mappedDomains, ]; } } diff --git a/module/Core/src/Domain/DomainServiceInterface.php b/module/Core/src/Domain/DomainServiceInterface.php index ff89b7b1..681000c5 100644 --- a/module/Core/src/Domain/DomainServiceInterface.php +++ b/module/Core/src/Domain/DomainServiceInterface.php @@ -5,11 +5,12 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Domain; use Shlinkio\Shlink\Core\Domain\Model\DomainItem; +use Shlinkio\Shlink\Rest\Entity\ApiKey; interface DomainServiceInterface { /** * @return DomainItem[] */ - public function listDomains(): array; + public function listDomains(?ApiKey $apiKey = null): array; } diff --git a/module/Core/src/Domain/Repository/DomainRepository.php b/module/Core/src/Domain/Repository/DomainRepository.php index f02dd120..f2152fbe 100644 --- a/module/Core/src/Domain/Repository/DomainRepository.php +++ b/module/Core/src/Domain/Repository/DomainRepository.php @@ -4,17 +4,18 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Domain\Repository; -use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query\Expr\Join; +use Happyr\DoctrineSpecification\EntitySpecificationRepository; use Shlinkio\Shlink\Core\Entity\Domain; use Shlinkio\Shlink\Core\Entity\ShortUrl; +use Shlinkio\Shlink\Rest\Entity\ApiKey; -class DomainRepository extends EntityRepository implements DomainRepositoryInterface +class DomainRepository extends EntitySpecificationRepository implements DomainRepositoryInterface { /** * @return Domain[] */ - public function findDomainsWithout(?string $excludedAuthority = null): array + public function findDomainsWithout(?string $excludedAuthority, ?ApiKey $apiKey = null): array { $qb = $this->createQueryBuilder('d'); $qb->join(ShortUrl::class, 's', Join::WITH, 's.domain = d') @@ -25,6 +26,10 @@ class DomainRepository extends EntityRepository implements DomainRepositoryInter ->setParameter('excludedAuthority', $excludedAuthority); } + if ($apiKey !== null) { + $this->applySpecification($qb, $apiKey->spec(), 's'); + } + return $qb->getQuery()->getResult(); } } diff --git a/module/Core/src/Domain/Repository/DomainRepositoryInterface.php b/module/Core/src/Domain/Repository/DomainRepositoryInterface.php index 56a765ac..13917dc6 100644 --- a/module/Core/src/Domain/Repository/DomainRepositoryInterface.php +++ b/module/Core/src/Domain/Repository/DomainRepositoryInterface.php @@ -5,12 +5,14 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Domain\Repository; use Doctrine\Persistence\ObjectRepository; +use Happyr\DoctrineSpecification\EntitySpecificationRepositoryInterface; use Shlinkio\Shlink\Core\Entity\Domain; +use Shlinkio\Shlink\Rest\Entity\ApiKey; -interface DomainRepositoryInterface extends ObjectRepository +interface DomainRepositoryInterface extends ObjectRepository, EntitySpecificationRepositoryInterface { /** * @return Domain[] */ - public function findDomainsWithout(?string $excludedAuthority = null): array; + public function findDomainsWithout(?string $excludedAuthority, ?ApiKey $apiKey = null): array; } diff --git a/module/Core/test-db/Domain/Repository/DomainRepositoryTest.php b/module/Core/test-db/Domain/Repository/DomainRepositoryTest.php index 79f9caaf..c553821e 100644 --- a/module/Core/test-db/Domain/Repository/DomainRepositoryTest.php +++ b/module/Core/test-db/Domain/Repository/DomainRepositoryTest.php @@ -46,7 +46,7 @@ class DomainRepositoryTest extends DatabaseTestCase $this->getEntityManager()->flush(); - self::assertEquals([$barDomain, $bazDomain, $fooDomain], $this->repo->findDomainsWithout()); + self::assertEquals([$barDomain, $bazDomain, $fooDomain], $this->repo->findDomainsWithout(null)); self::assertEquals([$barDomain, $bazDomain], $this->repo->findDomainsWithout('foo.com')); self::assertEquals([$bazDomain, $fooDomain], $this->repo->findDomainsWithout('bar.com')); self::assertEquals([$barDomain, $fooDomain], $this->repo->findDomainsWithout('baz.com')); diff --git a/module/Core/test/Domain/DomainServiceTest.php b/module/Core/test/Domain/DomainServiceTest.php index ccdbf04c..55094b53 100644 --- a/module/Core/test/Domain/DomainServiceTest.php +++ b/module/Core/test/Domain/DomainServiceTest.php @@ -34,7 +34,7 @@ class DomainServiceTest extends TestCase { $repo = $this->prophesize(DomainRepositoryInterface::class); $getRepo = $this->em->getRepository(Domain::class)->willReturn($repo->reveal()); - $findDomains = $repo->findDomainsWithout('default.com')->willReturn($domains); + $findDomains = $repo->findDomainsWithout('default.com', null)->willReturn($domains); $result = $this->domainService->listDomains(); diff --git a/module/Rest/src/Action/Domain/ListDomainsAction.php b/module/Rest/src/Action/Domain/ListDomainsAction.php index c35bb074..35ce04f3 100644 --- a/module/Rest/src/Action/Domain/ListDomainsAction.php +++ b/module/Rest/src/Action/Domain/ListDomainsAction.php @@ -9,6 +9,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Shlinkio\Shlink\Core\Domain\DomainServiceInterface; use Shlinkio\Shlink\Rest\Action\AbstractRestAction; +use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware; class ListDomainsAction extends AbstractRestAction { @@ -24,7 +25,8 @@ class ListDomainsAction extends AbstractRestAction public function handle(ServerRequestInterface $request): ResponseInterface { - $domainItems = $this->domainService->listDomains(); + $apiKey = AuthenticationMiddleware::apiKeyFromRequest($request); + $domainItems = $this->domainService->listDomains($apiKey); return new JsonResponse([ 'domains' => [ diff --git a/module/Rest/src/Entity/ApiKey.php b/module/Rest/src/Entity/ApiKey.php index 81c22b25..28b236fc 100644 --- a/module/Rest/src/Entity/ApiKey.php +++ b/module/Rest/src/Entity/ApiKey.php @@ -76,6 +76,11 @@ class ApiKey extends AbstractEntity public function isAdmin(): bool { - return $this->roles->count() === 0; + return $this->roles->isEmpty(); + } + + public function hasRole(string $roleName): bool + { + return $this->roles->exists(fn ($key, ApiKeyRole $role) => $role->name() === $roleName); } } diff --git a/module/Rest/test/Action/Domain/ListDomainsActionTest.php b/module/Rest/test/Action/Domain/ListDomainsActionTest.php index 61d9a99f..d6dcc4a3 100644 --- a/module/Rest/test/Action/Domain/ListDomainsActionTest.php +++ b/module/Rest/test/Action/Domain/ListDomainsActionTest.php @@ -12,6 +12,7 @@ use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Domain\DomainServiceInterface; use Shlinkio\Shlink\Core\Domain\Model\DomainItem; use Shlinkio\Shlink\Rest\Action\Domain\ListDomainsAction; +use Shlinkio\Shlink\Rest\Entity\ApiKey; class ListDomainsActionTest extends TestCase { @@ -23,20 +24,21 @@ class ListDomainsActionTest extends TestCase public function setUp(): void { $this->domainService = $this->prophesize(DomainServiceInterface::class); - $this->action = new ListDomainsAction($this->domainService->reveal(), 'foo.com'); + $this->action = new ListDomainsAction($this->domainService->reveal()); } /** @test */ public function domainsAreProperlyListed(): void { + $apiKey = new ApiKey(); $domains = [ new DomainItem('bar.com', true), new DomainItem('baz.com', false), ]; - $listDomains = $this->domainService->listDomains()->willReturn($domains); + $listDomains = $this->domainService->listDomains($apiKey)->willReturn($domains); /** @var JsonResponse $resp */ - $resp = $this->action->handle(ServerRequestFactory::fromGlobals()); + $resp = $this->action->handle(ServerRequestFactory::fromGlobals()->withAttribute(ApiKey::class, $apiKey)); $payload = $resp->getPayload(); self::assertEquals([