From 7447044115ad2256243d60d1b090a75bcffaded5 Mon Sep 17 00:00:00 2001 From: Christian Hartmann Date: Sat, 26 Oct 2024 00:49:10 +0200 Subject: [PATCH] make share endpoints openapi compatible Signed-off-by: Christian Hartmann --- lib/Controller/ShareApiController.php | 76 +++++++++++------ lib/ResponseDefinitions.php | 13 ++- openapi.json | 118 ++++++++++++++++++++++++-- 3 files changed, 171 insertions(+), 36 deletions(-) diff --git a/lib/Controller/ShareApiController.php b/lib/Controller/ShareApiController.php index 088685f5b..fc22afec1 100644 --- a/lib/Controller/ShareApiController.php +++ b/lib/Controller/ShareApiController.php @@ -30,19 +30,21 @@ use OCA\Forms\Db\FormMapper; use OCA\Forms\Db\Share; use OCA\Forms\Db\ShareMapper; +use OCA\Forms\ResponseDefinitions; use OCA\Forms\Service\CirclesService; use OCA\Forms\Service\ConfigService; use OCA\Forms\Service\FormsService; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\IMapperException; +use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\ApiRoute; use OCP\AppFramework\Http\Attribute\CORS; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCS\OCSBadRequestException; -use OCP\AppFramework\OCS\OCSException; use OCP\AppFramework\OCS\OCSForbiddenException; +use OCP\AppFramework\OCS\OCSNotFoundException; use OCP\AppFramework\OCSController; use OCP\Files\IRootFolder; use OCP\IGroup; @@ -56,6 +58,9 @@ use OCP\Share\IShare; use Psr\Log\LoggerInterface; +/** + * @psalm-import-type FormsShare from ResponseDefinitions + */ class ShareApiController extends OCSController { private IUser $currentUser; @@ -85,9 +90,23 @@ public function __construct( * @param int $formId The form to share * @param int $shareType Nextcloud-ShareType * @param string $shareWith ID of user/group/... to share with. For Empty shareWith and shareType Link, this will be set as RandomID. - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @param array $permissions the permissions granted on the share, defaults to `submit` + * Possible values: + * - `submit` user can submit + * - `results` user can see the results + * - `results_delete` user can see and delete results + * @return DataResponse, Http::STATUS_CREATED, array<>> + * @throws OCSBadRequestException Invalid shareType + * @throws OCSBadRequestException Invalid permission given + * @throws OCSBadRequestException Invalid user to share with + * @throws OCSBadRequestException Invalid group to share with + * @throws OCSBadRequestException Invalid team to share with + * @throws OCSBadRequestException Unknown shareType + * @throws OCSBadRequestException Share hash exists, please try again + * @throws OCSBadRequestException Teams app is disabled + * @throws OCSForbiddenException Link share not allowed + * @throws OCSForbiddenException This form is not owned by the current user + * @throws OCSNotFoundException Could not find form */ #[CORS()] #[NoAdminRequired()] @@ -109,20 +128,20 @@ public function newShare(int $formId, int $shareType, string $shareWith = '', ar // Block LinkShares if not allowed if ($shareType === IShare::TYPE_LINK && !$this->configService->getAllowPublicLink()) { $this->logger->debug('Link Share not allowed.'); - throw new OCSForbiddenException('Link Share not allowed.'); + throw new OCSForbiddenException('Link share not allowed.'); } try { $form = $this->formMapper->findById($formId); } catch (IMapperException $e) { $this->logger->debug('Could not find form', ['exception' => $e]); - throw new OCSBadRequestException('Could not find form'); + throw new OCSNotFoundException('Could not find form'); } // Check for permission to share form if ($form->getOwnerId() !== $this->currentUser->getUID()) { $this->logger->debug('This form is not owned by the current user'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('This form is not owned by the current user'); } if (!$this->validatePermissions($permissions, $shareType)) { @@ -157,11 +176,11 @@ public function newShare(int $formId, int $shareType, string $shareWith = '', ar // Check if hash already exists. (Unfortunately not possible here by unique index on db.) try { // Try loading a share to the hash. - $nonex = $this->shareMapper->findPublicShareByHash($shareWith); + $this->shareMapper->findPublicShareByHash($shareWith); // If we come here, a share has been found --> The share hash already exists, thus aborting. - $this->logger->debug('Share Hash already exists.'); - throw new OCSException('Share Hash exists. Please retry.'); + $this->logger->debug('Share hash already exists.'); + throw new OCSBadRequestException('Share hash exists, please retry.'); } catch (DoesNotExistException $e) { // Just continue, this is what we expect to happen (share hash not existing yet). } @@ -170,7 +189,7 @@ public function newShare(int $formId, int $shareType, string $shareWith = '', ar case IShare::TYPE_CIRCLE: if (!$this->circlesService->isCirclesEnabled()) { $this->logger->debug('Teams app is disabled, sharing to teams not possible.'); - throw new OCSException('Teams app is disabled.'); + throw new OCSBadRequestException('Teams app is disabled.'); } $circle = $this->circlesService->getCircle($shareWith); if (is_null($circle)) { @@ -182,7 +201,7 @@ public function newShare(int $formId, int $shareType, string $shareWith = '', ar default: // This passed the check for used shareTypes, but has not been found here. $this->logger->warning('Unknown, but used shareType: {shareType}. Please file an issue on GitHub.', [ 'shareType' => $shareType ]); - throw new OCSException('Unknown shareType.'); + throw new OCSBadRequestException('Unknown shareType.'); } $share = new Share(); @@ -202,7 +221,7 @@ public function newShare(int $formId, int $shareType, string $shareWith = '', ar $shareData = $share->read(); $shareData['displayName'] = $this->formsService->getShareDisplayName($shareData); - return new DataResponse($shareData); + return new DataResponse($shareData, Http::STATUS_CREATED); } /** @@ -210,10 +229,14 @@ public function newShare(int $formId, int $shareType, string $shareWith = '', ar * * @param int $formId of the form * @param int $shareId of the share to update - * @param array $keyValuePairs Array of key=>value pairs to update. - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @param array{key: string, values: mixed} $keyValuePairs Array of key=>value pairs to update. + * @return DataResponse, Http::STATUS_OK, array<>> + * @throws OCSBadRequestException Share doesn't belong to given Form + * @throws OCSBadRequestException Invalid permission given + * @throws OCSForbiddenException This form is not owned by the current user + * @throws OCSForbiddenException Empty keyValuePairs, will not update + * @throws OCSForbiddenException Not allowed to update other properties than permissions + * @throws OCSNotFoundException Could not find share */ #[CORS()] #[NoAdminRequired()] @@ -230,7 +253,7 @@ public function updateShare(int $formId, int $shareId, array $keyValuePairs): Da $form = $this->formMapper->findById($formId); } catch (IMapperException $e) { $this->logger->debug('Could not find share', ['exception' => $e]); - throw new OCSBadRequestException('Could not find share'); + throw new OCSNotFoundException('Could not find share'); } if ($formId !== $formShare->getFormId()) { @@ -240,19 +263,19 @@ public function updateShare(int $formId, int $shareId, array $keyValuePairs): Da if ($form->getOwnerId() !== $this->currentUser->getUID()) { $this->logger->debug('This form is not owned by the current user'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('This form is not owned by the current user'); } // Don't allow empty array if (sizeof($keyValuePairs) === 0) { $this->logger->info('Empty keyValuePairs, will not update.'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('Empty keyValuePairs, will not update'); } //Don't allow to change other properties than permissions if (count($keyValuePairs) > 1 || !key_exists('permissions', $keyValuePairs)) { $this->logger->debug('Not allowed to update other properties than permissions'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('Not allowed to update other properties than permissions'); } if (!$this->validatePermissions($keyValuePairs['permissions'], $formShare->getShareType())) { @@ -302,9 +325,10 @@ public function updateShare(int $formId, int $shareId, array $keyValuePairs): Da * * @param int $formId of the form * @param int $shareId of the share to delete - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException + * @return DataResponse, Http::STATUS_OK, array<>> + * @throws OCSBadRequestException Share doesn't belong to given Form + * @throws OCSForbiddenException This form is not owned by the current user + * @throws OCSNotFoundException Could not find share */ #[CORS()] #[NoAdminRequired()] @@ -320,7 +344,7 @@ public function deleteShare(int $formId, int $shareId): DataResponse { $form = $this->formMapper->findById($formId); } catch (IMapperException $e) { $this->logger->debug('Could not find share', ['exception' => $e]); - throw new OCSBadRequestException('Could not find share'); + throw new OCSNotFoundException('Could not find share'); } if ($formId !== $share->getFormId()) { @@ -330,7 +354,7 @@ public function deleteShare(int $formId, int $shareId): DataResponse { if ($form->getOwnerId() !== $this->currentUser->getUID()) { $this->logger->debug('This form is not owned by the current user'); - throw new OCSForbiddenException(); + throw new OCSForbiddenException('This form is not owned by the current user'); } $this->shareMapper->delete($share); diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index 12ca9f496..2f4a5e679 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -63,7 +63,7 @@ * "text": string, * "name": string, * "options": array, - * "accept": array, + * "accept": array, * "extraSettings": stdClass * } * @@ -99,8 +99,15 @@ * "uploadedFileId": int, * "fileName": string * } - * - * @psalm-type FormsQuestionFileType = string + * + * @psalm-type FormsShare = array{ + * "id": int, + * "formId": int, + * "shareType": int, + * "shareWith": string, + * "permissions": array, + * "displayName": string + * } */ class ResponseDefinitions { } diff --git a/openapi.json b/openapi.json index 75110c59c..e679d2389 100644 --- a/openapi.json +++ b/openapi.json @@ -2733,6 +2733,14 @@ "type": "string", "default": "", "description": "ID of user/group/... to share with. For Empty shareWith and shareType Link, this will be set as RandomID." + }, + "permissions": { + "type": "array", + "default": [], + "description": "the permissions granted on the share, defaults to `submit`\n Possible values:\n - `submit` user can submit\n - `results` user can see the results\n - `results_delete` user can see and delete results", + "items": { + "type": "string" + } } } } @@ -2763,7 +2771,7 @@ ], "responses": { "400": { - "description": "", + "description": "Teams app is disabled", "content": { "application/json": { "schema": { @@ -2791,7 +2799,35 @@ } }, "403": { - "description": "", + "description": "This form is not owned by the current user", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Could not find form", "content": { "application/json": { "schema": { @@ -2845,7 +2881,19 @@ "properties": { "keyValuePairs": { "type": "object", - "description": "Array of key=>value pairs to update." + "description": "Array of key=>value pairs to update.", + "required": [ + "key", + "values" + ], + "properties": { + "key": { + "type": "string" + }, + "values": { + "type": "object" + } + } } } } @@ -2886,7 +2934,7 @@ ], "responses": { "400": { - "description": "", + "description": "Invalid permission given", "content": { "application/json": { "schema": { @@ -2914,7 +2962,35 @@ } }, "403": { - "description": "", + "description": "Not allowed to update other properties than permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Could not find share", "content": { "application/json": { "schema": { @@ -2988,7 +3064,7 @@ ], "responses": { "400": { - "description": "", + "description": "Share doesn't belong to given Form", "content": { "application/json": { "schema": { @@ -3016,7 +3092,35 @@ } }, "403": { - "description": "", + "description": "This form is not owned by the current user", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": {} + } + } + } + } + } + } + }, + "404": { + "description": "Could not find share", "content": { "application/json": { "schema": {