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