Updated services required to initialize API keys with roles

This commit is contained in:
Alejandro Celaya 2021-01-10 20:05:14 +01:00
parent 95e51665b1
commit c9ff2b3834
7 changed files with 70 additions and 9 deletions

View file

@ -30,7 +30,7 @@
"laminas/laminas-diactoros": "^2.1.3", "laminas/laminas-diactoros": "^2.1.3",
"laminas/laminas-inputfilter": "^2.10", "laminas/laminas-inputfilter": "^2.10",
"laminas/laminas-paginator": "^2.8", "laminas/laminas-paginator": "^2.8",
"laminas/laminas-servicemanager": "^3.4", "laminas/laminas-servicemanager": "^3.6",
"laminas/laminas-stdlib": "^3.2", "laminas/laminas-stdlib": "^3.2",
"lcobucci/jwt": "^4.0", "lcobucci/jwt": "^4.0",
"league/uri": "^6.2", "league/uri": "^6.2",

View file

@ -45,6 +45,9 @@ class DomainService implements DomainServiceInterface
]; ];
} }
/**
* @throws DomainNotFoundException
*/
public function getDomain(string $domainId): Domain public function getDomain(string $domainId): Domain
{ {
/** @var Domain|null $domain */ /** @var Domain|null $domain */
@ -55,4 +58,16 @@ class DomainService implements DomainServiceInterface
return $domain; return $domain;
} }
public function getOrCreate(string $authority): Domain
{
$repo = $this->em->getRepository(Domain::class);
/** @var Domain|null $domain */
$domain = $repo->findOneBy(['authority' => $authority]) ?? new Domain($authority);
$this->em->persist($domain);
$this->em->flush();
return $domain;
}
} }

View file

@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\Core\Domain;
use Shlinkio\Shlink\Core\Domain\Model\DomainItem; use Shlinkio\Shlink\Core\Domain\Model\DomainItem;
use Shlinkio\Shlink\Core\Entity\Domain; use Shlinkio\Shlink\Core\Entity\Domain;
use Shlinkio\Shlink\Core\Exception\DomainNotFoundException;
use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Entity\ApiKey;
interface DomainServiceInterface interface DomainServiceInterface
@ -15,5 +16,10 @@ interface DomainServiceInterface
*/ */
public function listDomains(?ApiKey $apiKey = null): array; public function listDomains(?ApiKey $apiKey = null): array;
/**
* @throws DomainNotFoundException
*/
public function getDomain(string $domainId): Domain; public function getDomain(string $domainId): Domain;
public function getOrCreate(string $authority): Domain;
} }

View file

@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\Core\Domain;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy; use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Core\Domain\DomainService; use Shlinkio\Shlink\Core\Domain\DomainService;
@ -111,4 +112,33 @@ class DomainServiceTest extends TestCase
self::assertSame($domain, $result); self::assertSame($domain, $result);
$find->shouldHaveBeenCalledOnce(); $find->shouldHaveBeenCalledOnce();
} }
/**
* @test
* @dataProvider provideFoundDomains
*/
public function getOrCreateAlwaysPersistsDomain(?Domain $foundDomain): void
{
$authority = 'example.com';
$repo = $this->prophesize(DomainRepositoryInterface::class);
$repo->findOneBy(['authority' => $authority])->willReturn($foundDomain);
$getRepo = $this->em->getRepository(Domain::class)->willReturn($repo->reveal());
$persist = $this->em->persist($foundDomain !== null ? $foundDomain : Argument::type(Domain::class));
$flush = $this->em->flush();
$result = $this->domainService->getOrCreate($authority);
if ($foundDomain !== null) {
self::assertSame($result, $foundDomain);
}
$getRepo->shouldHaveBeenCalledOnce();
$persist->shouldHaveBeenCalledOnce();
$flush->shouldHaveBeenCalledOnce();
}
public function provideFoundDomains(): iterable
{
yield 'domain not found' => [null];
yield 'domain found' => [new Domain('')];
}
} }

View file

@ -7,6 +7,7 @@ namespace Shlinkio\Shlink\Rest\Service;
use Cake\Chronos\Chronos; use Cake\Chronos\Chronos;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
use Shlinkio\Shlink\Rest\ApiKey\Model\RoleDefinition;
use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Entity\ApiKey;
use function sprintf; use function sprintf;
@ -20,9 +21,13 @@ class ApiKeyService implements ApiKeyServiceInterface
$this->em = $em; $this->em = $em;
} }
public function create(?Chronos $expirationDate = null): ApiKey public function create(?Chronos $expirationDate = null, RoleDefinition ...$roleDefinitions): ApiKey
{ {
$key = new ApiKey($expirationDate); $key = new ApiKey($expirationDate);
foreach ($roleDefinitions as $definition) {
$key->registerRole($definition);
}
$this->em->persist($key); $this->em->persist($key);
$this->em->flush(); $this->em->flush();
@ -31,7 +36,6 @@ class ApiKeyService implements ApiKeyServiceInterface
public function check(string $key): ApiKeyCheckResult public function check(string $key): ApiKeyCheckResult
{ {
/** @var ApiKey|null $apiKey */
$apiKey = $this->getByKey($key); $apiKey = $this->getByKey($key);
return new ApiKeyCheckResult($apiKey); return new ApiKeyCheckResult($apiKey);
} }
@ -41,7 +45,6 @@ class ApiKeyService implements ApiKeyServiceInterface
*/ */
public function disable(string $key): ApiKey public function disable(string $key): ApiKey
{ {
/** @var ApiKey|null $apiKey */
$apiKey = $this->getByKey($key); $apiKey = $this->getByKey($key);
if ($apiKey === null) { if ($apiKey === null) {
throw new InvalidArgumentException(sprintf('API key "%s" does not exist and can\'t be disabled', $key)); throw new InvalidArgumentException(sprintf('API key "%s" does not exist and can\'t be disabled', $key));

View file

@ -6,11 +6,12 @@ namespace Shlinkio\Shlink\Rest\Service;
use Cake\Chronos\Chronos; use Cake\Chronos\Chronos;
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
use Shlinkio\Shlink\Rest\ApiKey\Model\RoleDefinition;
use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Entity\ApiKey;
interface ApiKeyServiceInterface interface ApiKeyServiceInterface
{ {
public function create(?Chronos $expirationDate = null): ApiKey; public function create(?Chronos $expirationDate = null, RoleDefinition ...$roleDefinitions): ApiKey;
public function check(string $key): ApiKeyCheckResult; public function check(string $key): ApiKeyCheckResult;

View file

@ -12,6 +12,7 @@ use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy; use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
use Shlinkio\Shlink\Rest\ApiKey\Model\RoleDefinition;
use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Entity\ApiKey;
use Shlinkio\Shlink\Rest\Service\ApiKeyService; use Shlinkio\Shlink\Rest\Service\ApiKeyService;
@ -31,21 +32,26 @@ class ApiKeyServiceTest extends TestCase
/** /**
* @test * @test
* @dataProvider provideCreationDate * @dataProvider provideCreationDate
* @param RoleDefinition[] $roles
*/ */
public function apiKeyIsProperlyCreated(?Chronos $date): void public function apiKeyIsProperlyCreated(?Chronos $date, array $roles): void
{ {
$this->em->flush()->shouldBeCalledOnce(); $this->em->flush()->shouldBeCalledOnce();
$this->em->persist(Argument::type(ApiKey::class))->shouldBeCalledOnce(); $this->em->persist(Argument::type(ApiKey::class))->shouldBeCalledOnce();
$key = $this->service->create($date); $key = $this->service->create($date, ...$roles);
self::assertEquals($date, $key->getExpirationDate()); self::assertEquals($date, $key->getExpirationDate());
foreach ($roles as $roleDefinition) {
self::assertTrue($key->hasRole($roleDefinition->roleName()));
}
} }
public function provideCreationDate(): iterable public function provideCreationDate(): iterable
{ {
yield 'no expiration date' => [null]; yield 'no expiration date' => [null, []];
yield 'expiration date' => [Chronos::parse('2030-01-01')]; yield 'expiration date' => [Chronos::parse('2030-01-01'), []];
yield 'roles' => [null, [RoleDefinition::forDomain('123'), RoleDefinition::forAuthoredShortUrls()]];
} }
/** /**