From 4b160476327ecb893c08ce4deb29ce1180f6e0ef Mon Sep 17 00:00:00 2001 From: Elmar Ludwig <elmar.ludwig@uni-osnabrueck.de> Date: Mon, 6 Nov 2023 15:15:21 +0000 Subject: [PATCH] add implementation for /institutes/{id}/memberships route, fixes #3429 Closes #3429 Merge request studip/studip!2334 --- lib/classes/JsonApi/RouteMap.php | 1 + .../InstituteMembershipsShow.php | 2 +- .../JsonApi/Routes/Institutes/Authority.php | 9 +++ .../Institutes/InstituteMembershipsIndex.php | 80 +++++++++++++++++++ lib/classes/JsonApi/Schemas/Institute.php | 7 ++ .../jsonapi/InstituteMembershipsIndexTest.php | 41 ++++++++++ 6 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 lib/classes/JsonApi/Routes/Institutes/InstituteMembershipsIndex.php create mode 100644 tests/jsonapi/InstituteMembershipsIndexTest.php diff --git a/lib/classes/JsonApi/RouteMap.php b/lib/classes/JsonApi/RouteMap.php index e779e8b25ae..2eb33a815e2 100644 --- a/lib/classes/JsonApi/RouteMap.php +++ b/lib/classes/JsonApi/RouteMap.php @@ -253,6 +253,7 @@ class RouteMap $group->get('/institutes/{id}', Routes\Institutes\InstitutesShow::class); $group->get('/institutes', Routes\Institutes\InstitutesIndex::class); + $group->get('/institutes/{id}/memberships', Routes\Institutes\InstituteMembershipsIndex::class); $group->get('/institutes/{id}/status-groups', Routes\Institutes\StatusGroupsOfInstitutes::class); } diff --git a/lib/classes/JsonApi/Routes/InstituteMemberships/InstituteMembershipsShow.php b/lib/classes/JsonApi/Routes/InstituteMemberships/InstituteMembershipsShow.php index 7662af34693..13e0dd4c41b 100644 --- a/lib/classes/JsonApi/Routes/InstituteMemberships/InstituteMembershipsShow.php +++ b/lib/classes/JsonApi/Routes/InstituteMemberships/InstituteMembershipsShow.php @@ -22,7 +22,7 @@ class InstituteMembershipsShow extends JsonApiController } $user = $this->getUser($request); - if ($user->id !== $membership->user_id) { + if ($user->id !== $membership->user_id && !get_visibility_by_id($membership->user_id)) { throw new AuthorizationFailedException(); } diff --git a/lib/classes/JsonApi/Routes/Institutes/Authority.php b/lib/classes/JsonApi/Routes/Institutes/Authority.php index b3fe9b13b87..c6ee43bee90 100644 --- a/lib/classes/JsonApi/Routes/Institutes/Authority.php +++ b/lib/classes/JsonApi/Routes/Institutes/Authority.php @@ -2,10 +2,19 @@ namespace JsonApi\Routes\Institutes; +use Institute; use User; class Authority { + /** + * @SuppressWarnings(PHPMD.Superglobals) + */ + public static function canEditInstitute(User $user, Institute $institute) + { + return $GLOBALS['perm']->have_studip_perm('admin', $institute->id, $user->id); + } + /** * @SuppressWarnings(PHPMD.Superglobals) */ diff --git a/lib/classes/JsonApi/Routes/Institutes/InstituteMembershipsIndex.php b/lib/classes/JsonApi/Routes/Institutes/InstituteMembershipsIndex.php new file mode 100644 index 00000000000..9184fd94d99 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Institutes/InstituteMembershipsIndex.php @@ -0,0 +1,80 @@ +<?php + +namespace JsonApi\Routes\Institutes; + +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use JsonApi\Errors\AuthorizationFailedException; +use JsonApi\Errors\BadRequestException; +use JsonApi\Errors\RecordNotFoundException; +use JsonApi\JsonApiController; +use JsonApi\Schemas\InstituteMember; + +/** + * Returns all institute-memberships of the institute. + */ +class InstituteMembershipsIndex extends JsonApiController +{ + protected $allowedFilteringParameters = ['permission']; + + protected $allowedIncludePaths = [InstituteMember::REL_INSTITUTE, InstituteMember::REL_USER]; + + protected $allowedPagingParameters = ['offset', 'limit']; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameters) + */ + public function __invoke(Request $request, Response $response, $args) + { + $institute = \Institute::find($args['id']); + if (!$institute) { + throw new RecordNotFoundException(); + } + + $this->validateFilters(); + + $user = $this->getUser($request); + $memberships = $this->getMemberships($institute, $user, $this->getFilters()); + + list($offset, $limit) = $this->getOffsetAndLimit(); + + return $this->getPaginatedContentResponse($memberships->limit($offset, $limit), count($memberships)); + } + + private function getMemberships(\Institute $institute, \User $user, array $filters) + { + $memberships = $institute->members; + + $visibleMemberships = Authority::canEditInstitute($user, $institute) + ? $memberships + : $memberships->filter(function ($membership) use ($user) { + return $membership->user_id === $user->id || get_visibility_by_id($membership->user_id); + }); + + return isset($filters['permission']) + ? $visibleMemberships->filter(function ($membership) use ($filters) { + return $membership->inst_perms === $filters['permission']; + }) + : $visibleMemberships; + } + + private function validateFilters() + { + $filtering = $this->getQueryParameters()->getFilteringParameters() ?? []; + + if (array_key_exists('permission', $filtering)) { + if (!in_array($filtering['permission'], ['user', 'autor', 'tutor', 'dozent', 'admin'])) { + throw new BadRequestException('Filter `permission` must be one of `user`, `autor`, `tutor`, `dozent`, `admin`.'); + } + } + } + + private function getFilters() + { + $filtering = $this->getQueryParameters()->getFilteringParameters() ?? []; + + $filters['permission'] = $filtering['permission'] ?? null; + + return $filters; + } +} diff --git a/lib/classes/JsonApi/Schemas/Institute.php b/lib/classes/JsonApi/Schemas/Institute.php index d3eda4ac133..c1775b1646d 100644 --- a/lib/classes/JsonApi/Schemas/Institute.php +++ b/lib/classes/JsonApi/Schemas/Institute.php @@ -12,6 +12,7 @@ class Institute extends SchemaProvider const REL_BLUBBER = 'blubber-threads'; const REL_FILES = 'file-refs'; const REL_FOLDERS = 'folders'; + const REL_MEMBERSHIPS = 'memberships'; const REL_STATUS_GROUPS = 'status-groups'; public function getId($institute): ?string @@ -60,6 +61,12 @@ class Institute extends SchemaProvider ], ]; + $relationships[self::REL_MEMBERSHIPS] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_MEMBERSHIPS), + ], + ]; + $relationships = $this->addStatusGroupsRelationship( $relationships, $resource, diff --git a/tests/jsonapi/InstituteMembershipsIndexTest.php b/tests/jsonapi/InstituteMembershipsIndexTest.php new file mode 100644 index 00000000000..f9ddf9defec --- /dev/null +++ b/tests/jsonapi/InstituteMembershipsIndexTest.php @@ -0,0 +1,41 @@ +<?php + +use JsonApi\Routes\Institutes\InstituteMembershipsIndex; + +class InstituteMembershipsIndexTest extends \Codeception\Test\Unit +{ + /** + * @var \UnitTester + */ + protected $tester; + + protected function _before() + { + \DBManager::getInstance()->setConnection('studip', $this->getModule('\\Helper\\StudipDb')->dbh); + } + + protected function _after() + { + } + + public function testIndexMemberships() + { + $credentials = $this->tester->getCredentialsForTestAutor(); + $instituteId = '2560f7c7674942a7dce8eeb238e15d93'; + + $institute = \Institute::find($instituteId); + + $app = $this->tester->createApp($credentials, 'get', '/institutes/{id}/memberships', InstituteMembershipsIndex::class); + + $requestBuilder = $this->tester->createRequestBuilder($credentials); + $requestBuilder->setUri('/institutes/'.$instituteId.'/memberships')->fetch(); + + $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest()); + + $this->tester->assertTrue($response->isSuccessfulDocument([200])); + $document = $response->document(); + $this->tester->assertTrue($document->isResourceCollectionDocument()); + $resources = $document->primaryResources(); + $this->tester->assertCount(count($institute->members), $resources); + } +} -- GitLab