diff --git a/app/Ownership/OwnershipRequestHandlerServiceProvider.php b/app/Ownership/OwnershipRequestHandlerServiceProvider.php index f7aba4a07e..dbed2e4757 100644 --- a/app/Ownership/OwnershipRequestHandlerServiceProvider.php +++ b/app/Ownership/OwnershipRequestHandlerServiceProvider.php @@ -65,7 +65,9 @@ public function register(): void $container->addShared( GetOwnershipRequestHandler::class, fn () => new GetOwnershipRequestHandler( - $container->get(OwnershipServiceProvider::OWNERSHIP_JSONLD_REPOSITORY) + $container->get(OwnershipServiceProvider::OWNERSHIP_JSONLD_REPOSITORY), + $container->get(CurrentUser::class), + $container->get(OwnershipStatusGuard::class) ) ); diff --git a/features/ownership/get.feature b/features/ownership/get.feature new file mode 100644 index 0000000000..831ca3d4a4 --- /dev/null +++ b/features/ownership/get.feature @@ -0,0 +1,62 @@ +Feature: Test getting a single ownership by ID + Background: + Given I am using the UDB3 base URL + And I am using an UiTID v1 API key of consumer "uitdatabank" + And I am authorized as JWT provider v1 user "centraal_beheerder" + And I send and accept "application/json" + + Scenario: Get the ownership as an admin + Given I create a minimal organizer and save the "id" as "organizerId" + And I request ownership for "auth0|64089494e980aedd96740212" on the organizer with organizerId "%{organizerId}" and save the "id" as "ownershipId" + When I send a GET request to '/ownerships/%{ownershipId}' + Then the response status should be 200 + And the JSON response at id should be "%{ownershipId}" + And the JSON response at ownerId should be "auth0|64089494e980aedd96740212" + And the JSON response at itemId should be "%{organizerId}" + And the JSON response at state should be "requested" + And the JSON response at itemType should be "organizer" + And the JSON response at requesterId should be "7a583ed3-cbc1-481d-93b1-d80fff0174dd" + And the JSON response at ownerEmail should be "dev+e2etest@publiq.be" + + Scenario: Get the ownership as owner + Given I create a minimal organizer and save the "id" as "organizerId" + And I request ownership for "auth0|64089494e980aedd96740212" on the organizer with organizerId "%{organizerId}" and save the "id" as "ownershipId" + When I am authorized as JWT provider v2 user "dev_e2e_test" + And I send a GET request to '/ownerships/%{ownershipId}' + Then the response status should be 200 + And the JSON response at id should be "%{ownershipId}" + And the JSON response at ownerId should be "auth0|64089494e980aedd96740212" + And the JSON response at itemId should be "%{organizerId}" + And the JSON response at state should be "requested" + And the JSON response at itemType should be "organizer" + And the JSON response at requesterId should be "7a583ed3-cbc1-481d-93b1-d80fff0174dd" + And the JSON response at ownerEmail should be "dev+e2etest@publiq.be" + + Scenario: Not allowed to get the ownership as an unrelated user + Given I create a minimal organizer and save the "id" as "organizerId" + And I request ownership for "auth0|64089494e980aedd96740212" on the organizer with organizerId "%{organizerId}" and save the "id" as "ownershipId" + When I am authorized as JWT provider v2 user "invoerder" + And I send a GET request to '/ownerships/%{ownershipId}' + Then the response status should be 403 + And the JSON response should be: + """ + { + "type": "https://api.publiq.be/probs/auth/forbidden", + "title": "Forbidden", + "status": 403, + "detail": "You are not allowed to get this ownership" + } + """ + + Scenario: Get an unexisting ownership + When I send a GET request to '/ownerships/a0a9f9c0-84b5-471b-869e-c2c692217cc0' + Then the response status should be 404 + And the JSON response should be: + """ + { + "type": "https://api.publiq.be/probs/url/not-found", + "title": "Not Found", + "status": 404, + "detail": "The Ownership with id \"a0a9f9c0-84b5-471b-869e-c2c692217cc0\" was not found." + } + """ diff --git a/src/Http/Ownership/GetOwnershipRequestHandler.php b/src/Http/Ownership/GetOwnershipRequestHandler.php index ab66566ecb..f95e503152 100644 --- a/src/Http/Ownership/GetOwnershipRequestHandler.php +++ b/src/Http/Ownership/GetOwnershipRequestHandler.php @@ -9,6 +9,7 @@ use CultuurNet\UDB3\Http\Response\JsonLdResponse; use CultuurNet\UDB3\ReadModel\DocumentDoesNotExist; use CultuurNet\UDB3\ReadModel\DocumentRepository; +use CultuurNet\UDB3\User\CurrentUser; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; @@ -16,10 +17,17 @@ final class GetOwnershipRequestHandler implements RequestHandlerInterface { private DocumentRepository $ownershipRepository; + private CurrentUser $currentUser; + private OwnershipStatusGuard $ownershipStatusGuard; - public function __construct(DocumentRepository $ownershipRepository) - { + public function __construct( + DocumentRepository $ownershipRepository, + CurrentUser $currentUser, + OwnershipStatusGuard $ownershipStatusGuard + ) { $this->ownershipRepository = $ownershipRepository; + $this->currentUser = $currentUser; + $this->ownershipStatusGuard = $ownershipStatusGuard; } public function handle(ServerRequestInterface $request): ResponseInterface @@ -27,6 +35,8 @@ public function handle(ServerRequestInterface $request): ResponseInterface $routeParameters = new RouteParameters($request); $ownershipId = $routeParameters->getOwnershipId(); + $this->ownershipStatusGuard->isAllowedToGet($ownershipId, $this->currentUser); + try { return new JsonLdResponse( $this->ownershipRepository->fetch($ownershipId)->getRawBody() diff --git a/src/Http/Ownership/OwnershipStatusGuard.php b/src/Http/Ownership/OwnershipStatusGuard.php index 71e725bb10..27ef066936 100644 --- a/src/Http/Ownership/OwnershipStatusGuard.php +++ b/src/Http/Ownership/OwnershipStatusGuard.php @@ -25,6 +25,34 @@ public function __construct( $this->permissionVoter = $permissionVoter; } + public function isAllowedToGet(string $ownershipId, CurrentUser $currentUser): void + { + try { + $ownership = $this->ownershipSearchRepository->getById($ownershipId); + } catch (OwnershipItemNotFound $exception) { + throw ApiProblem::ownershipNotFound($ownershipId); + } + + if ($currentUser->isGodUser()) { + return; + } + + $isOwner = $this->permissionVoter->isAllowed( + Permission::organisatiesBewerken(), + $ownership->getItemId(), + $currentUser->getId() + ); + if ($isOwner) { + return; + } + + if ($ownership->getOwnerId() === $currentUser->getId()) { + return; + } + + throw ApiProblem::forbidden('You are not allowed to get this ownership'); + } + public function isAllowedToRequest(string $itemId, string $requesterId, CurrentUser $currentUser): void { $isOwner = $this->permissionVoter->isAllowed( @@ -73,7 +101,7 @@ private function isAllowedToUpdateOwnership(OwnershipItem $ownership, CurrentUse } return $this->permissionVoter->isAllowed( - Permission::organisatiesBeheren(), + Permission::organisatiesBewerken(), $ownership->getItemId(), $currentUser->getId() ); diff --git a/tests/Http/Ownership/GetOwnershipRequestHandlerTest.php b/tests/Http/Ownership/GetOwnershipRequestHandlerTest.php index f44d69afec..631766c245 100644 --- a/tests/Http/Ownership/GetOwnershipRequestHandlerTest.php +++ b/tests/Http/Ownership/GetOwnershipRequestHandlerTest.php @@ -8,8 +8,15 @@ use CultuurNet\UDB3\Http\ApiProblem\AssertApiProblemTrait; use CultuurNet\UDB3\Http\Request\Psr7RequestBuilder; use CultuurNet\UDB3\Json; +use CultuurNet\UDB3\Ownership\OwnershipState; +use CultuurNet\UDB3\Ownership\Repositories\OwnershipItem; +use CultuurNet\UDB3\Ownership\Repositories\OwnershipItemNotFound; +use CultuurNet\UDB3\Ownership\Repositories\Search\OwnershipSearchRepository; use CultuurNet\UDB3\ReadModel\InMemoryDocumentRepository; use CultuurNet\UDB3\ReadModel\JsonDocument; +use CultuurNet\UDB3\Security\Permission\PermissionVoter; +use CultuurNet\UDB3\User\CurrentUser; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class GetOwnershipRequestHandlerTest extends TestCase @@ -18,13 +25,30 @@ class GetOwnershipRequestHandlerTest extends TestCase private InMemoryDocumentRepository $ownershipRepository; + /** @var OwnershipSearchRepository&MockObject */ + private $ownerShipSearchRepository; + + /** @var PermissionVoter&MockObject */ + private $permissionVoter; + private GetOwnershipRequestHandler $getOwnershipRequestHandler; protected function setUp(): void { $this->ownershipRepository = new InMemoryDocumentRepository(); - $this->getOwnershipRequestHandler = new GetOwnershipRequestHandler($this->ownershipRepository); + $this->ownerShipSearchRepository = $this->createMock(OwnershipSearchRepository::class); + + $this->permissionVoter = $this->createMock(PermissionVoter::class); + + $this->getOwnershipRequestHandler = new GetOwnershipRequestHandler( + $this->ownershipRepository, + new CurrentUser('auth0|63e22626e39a8ca1264bd29b'), + new OwnershipStatusGuard( + $this->ownerShipSearchRepository, + $this->permissionVoter + ) + ); parent::setUp(); } @@ -32,21 +56,46 @@ protected function setUp(): void /** * @test */ - public function it_handles_getting_an_ownership(): void + public function it_handles_getting_an_ownership_as_owner(): void { + CurrentUser::configureGodUserIds([]); + $ownershipId = 'e6e1f3a0-3e5e-4b3e-8e3e-3f3e3e3e3e3e'; + $this->givenItFindsAnOwnershipForUser($ownershipId, 'auth0|63e22626e39a8ca1264bd29b'); + + $body = $this->givenThereIsAnOwnershipDocument($ownershipId); + + $this->permissionVoter->expects($this->once()) + ->method('isAllowed') + ->willReturn(false); $getOwnershipRequest = (new Psr7RequestBuilder()) ->withRouteParameter('ownershipId', $ownershipId) ->build('GET'); + $response = $this->getOwnershipRequestHandler->handle($getOwnershipRequest); - $body = Json::encode([ - 'id' => 'e6e1f3a0-3e5e-4b3e-8e3e-3f3e3e3e3e3e', - 'itemId' => '9e68dafc-01d8-4c1c-9612-599c918b981d', - ]); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals($body, $response->getBody()->getContents()); + } - $this->ownershipRepository->save(new JsonDocument($ownershipId, $body)); + /** + * @test + */ + public function it_handles_getting_an_ownership_as_admin(): void + { + CurrentUser::configureGodUserIds(['auth0|63e22626e39a8ca1264bd29b']); + + $ownershipId = 'e6e1f3a0-3e5e-4b3e-8e3e-3f3e3e3e3e3e'; + $this->givenItFindsAnOwnershipForUser($ownershipId, 'auth0|63e22626e39a8ca1264bd29b'); + + $body = $this->givenThereIsAnOwnershipDocument($ownershipId); + + $this->permissionVoter->expects($this->never()) + ->method('isAllowed'); + $getOwnershipRequest = (new Psr7RequestBuilder()) + ->withRouteParameter('ownershipId', $ownershipId) + ->build('GET'); $response = $this->getOwnershipRequestHandler->handle($getOwnershipRequest); $this->assertEquals(200, $response->getStatusCode()); @@ -56,17 +105,95 @@ public function it_handles_getting_an_ownership(): void /** * @test */ - public function it_throws_an_api_problem_when_ownership_is_not_found(): void + public function it_handles_getting_an_ownership_with_permission(): void + { + CurrentUser::configureGodUserIds([]); + + $ownershipId = 'e6e1f3a0-3e5e-4b3e-8e3e-3f3e3e3e3e3e'; + $this->givenItFindsAnOwnershipForUser($ownershipId, 'auth0|for_another_user'); + + $body = $this->givenThereIsAnOwnershipDocument($ownershipId); + + $this->permissionVoter->expects($this->once()) + ->method('isAllowed') + ->willReturn(true); + + $getOwnershipRequest = (new Psr7RequestBuilder()) + ->withRouteParameter('ownershipId', $ownershipId) + ->build('GET'); + $response = $this->getOwnershipRequestHandler->handle($getOwnershipRequest); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals($body, $response->getBody()->getContents()); + } + + /** + * @test + */ + public function it_forbids_getting_an_ownership_without_permission(): void { + CurrentUser::configureGodUserIds([]); + $ownershipId = 'e6e1f3a0-3e5e-4b3e-8e3e-3f3e3e3e3e3e'; + $this->givenItFindsAnOwnershipForUser($ownershipId, 'auth0|for_another_user'); + + $this->permissionVoter->expects($this->once()) + ->method('isAllowed') + ->willReturn(false); + + $this->givenThereIsAnOwnershipDocument($ownershipId); + $getOwnershipRequest = (new Psr7RequestBuilder()) ->withRouteParameter('ownershipId', $ownershipId) ->build('GET'); + $this->assertCallableThrowsApiProblem( + ApiProblem::forbidden('You are not allowed to get this ownership'), + fn () => $this->getOwnershipRequestHandler->handle($getOwnershipRequest) + ); + } + + /** + * @test + */ + public function it_throws_an_api_problem_when_ownership_is_not_found(): void + { + $ownershipId = 'e6e1f3a0-3e5e-4b3e-8e3e-3f3e3e3e3e3e'; + + $this->ownerShipSearchRepository->expects($this->once()) + ->method('getById') + ->willThrowException(OwnershipItemNotFound::byId($ownershipId)); + $getOwnershipRequest = (new Psr7RequestBuilder()) + ->withRouteParameter('ownershipId', $ownershipId) + ->build('GET'); $this->assertCallableThrowsApiProblem( ApiProblem::ownershipNotFound($ownershipId), fn () => $this->getOwnershipRequestHandler->handle($getOwnershipRequest) ); } + + private function givenItFindsAnOwnershipForUser(string $ownershipId, string $userId): void + { + $this->ownerShipSearchRepository->expects($this->once()) + ->method('getById') + ->willReturn(new OwnershipItem( + $ownershipId, + '9e68dafc-01d8-4c1c-9612-599c918b981d', + 'organizer', + $userId, + OwnershipState::requested()->toString() + )); + } + + private function givenThereIsAnOwnershipDocument(string $ownershipId): string + { + $body = Json::encode([ + 'id' => $ownershipId, + 'itemId' => '9e68dafc-01d8-4c1c-9612-599c918b981d', + ]); + $this->ownershipRepository->save(new JsonDocument($ownershipId, $body)); + + return $body; + } }