Skip to content

Commit c0482b8

Browse files
alexander-rebelloChartman123
authored andcommitted
Add Share API, sharing sidebar UI, docs, and tests
1 parent a1d1afb commit c0482b8

6 files changed

Lines changed: 628 additions & 20 deletions

File tree

docs/API_v3.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,12 @@ Update a single or all properties of an option-object
647647
| Parameter | Type | Description |
648648
|------------------|----------|-------------|
649649
| _keyValuePairs_ | Array | Array of key-value pairs to update |
650-
- Restrictions: Currently only the _permissions_ can be updated.
650+
- Restrictions:
651+
- Allowed keys are _permissions_ and _token_.
652+
- _token_ updates are only available when the admin setting _allowCustomPublicShareTokens_ is enabled.
653+
- _token_ can only be updated on link shares.
654+
- _token_ must be unique among link shares and only contain alphanumeric characters.
655+
- _token_ length must be between 8 and 256 characters.
651656
- Response: **Status-Code OK**, as well as the id of the share object.
652657

653658
```

lib/Controller/ShareApiController.php

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -204,17 +204,21 @@ public function newShare(int $formId, int $shareType, string $shareWith = '', ar
204204
}
205205

206206
/**
207-
* Update permissions of a share
207+
* Update properties of a share
208208
*
209209
* @param int $formId of the form
210210
* @param int $shareId of the share to update
211211
* @param array<string, mixed> $keyValuePairs Array of key=>value pairs to update.
212212
* @return DataResponse<Http::STATUS_OK, int, array{}>
213213
* @throws OCSBadRequestException Share doesn't belong to given Form
214214
* @throws OCSBadRequestException Invalid permission given
215+
* @throws OCSBadRequestException Invalid share token
216+
* @throws OCSBadRequestException Share hash exists, please retry
217+
* @throws OCSForbiddenException Custom public share tokens are not allowed
215218
* @throws OCSForbiddenException This form is not owned by the current user
216219
* @throws OCSForbiddenException Empty keyValuePairs, will not update
217-
* @throws OCSForbiddenException Not allowed to update other properties than permissions
220+
* @throws OCSForbiddenException Not allowed to update token on non-link share
221+
* @throws OCSForbiddenException Not allowed to update unknown properties
218222
* @throws OCSNotFoundException Could not find share
219223
*
220224
* 200: the id of the updated share
@@ -223,7 +227,7 @@ public function newShare(int $formId, int $shareType, string $shareWith = '', ar
223227
#[NoAdminRequired()]
224228
#[ApiRoute(verb: 'PATCH', url: '/api/v3/forms/{formId}/shares/{shareId}')]
225229
public function updateShare(int $formId, int $shareId, array $keyValuePairs): DataResponse {
226-
$this->logger->debug('Updating share: {shareId} of form {formId}, permissions: {permissions}', [
230+
$this->logger->debug('Updating share: {shareId} of form {formId}, values: {keyValuePairs}', [
227231
'formId' => $formId,
228232
'shareId' => $shareId,
229233
'keyValuePairs' => $keyValuePairs
@@ -253,22 +257,64 @@ public function updateShare(int $formId, int $shareId, array $keyValuePairs): Da
253257
throw new OCSForbiddenException('Empty keyValuePairs, will not update');
254258
}
255259

256-
//Don't allow to change other properties than permissions
257-
if (count($keyValuePairs) > 1 || !array_key_exists('permissions', $keyValuePairs)) {
258-
$this->logger->debug('Not allowed to update other properties than permissions');
259-
throw new OCSForbiddenException('Not allowed to update other properties than permissions');
260+
$allowedKeys = ['permissions', 'token'];
261+
foreach (array_keys($keyValuePairs) as $key) {
262+
if (!in_array($key, $allowedKeys, true)) {
263+
$this->logger->debug('Not allowed to update other properties than permissions or token');
264+
throw new OCSForbiddenException('Not allowed to update other properties than permissions or token');
265+
}
260266
}
261267

262-
if (!$this->validatePermissions($keyValuePairs['permissions'], $formShare->getShareType())) {
268+
if (array_key_exists('permissions', $keyValuePairs) && !$this->validatePermissions($keyValuePairs['permissions'], $formShare->getShareType())) {
263269
throw new OCSBadRequestException('Invalid permission given');
264270
}
265271

272+
if (array_key_exists('token', $keyValuePairs)) {
273+
if (!$this->configService->getAllowCustomPublicToken()) {
274+
$this->logger->debug('Custom public share tokens are not allowed.');
275+
throw new OCSForbiddenException('Custom public share tokens are not allowed.');
276+
}
277+
278+
if ($formShare->getShareType() !== IShare::TYPE_LINK) {
279+
$this->logger->debug('Not allowed to update token on non-link share');
280+
throw new OCSForbiddenException('Not allowed to update token on non-link share');
281+
}
282+
283+
if (!is_string($keyValuePairs['token'])) {
284+
throw new OCSBadRequestException('Invalid share token');
285+
}
286+
287+
$token = $keyValuePairs['token'];
288+
if (!array_key_exists('permissions', $keyValuePairs) && $token === $formShare->getShareWith()) {
289+
return new DataResponse($formShare->getId());
290+
}
291+
292+
if ($token !== $formShare->getShareWith()) {
293+
$this->validatePublicShareToken($token);
294+
295+
try {
296+
$existingShare = $this->shareMapper->findPublicShareByHash($token);
297+
if ($existingShare->getId() !== $formShare->getId()) {
298+
$this->logger->debug('Share hash already exists.');
299+
throw new OCSBadRequestException('Share hash exists, please retry.');
300+
}
301+
} catch (DoesNotExistException $e) {
302+
// Just continue, this is what we expect to happen (share hash not existing yet).
303+
}
304+
305+
$formShare->setShareWith($token);
306+
}
307+
}
308+
266309
$this->formsService->obtainFormLock($form);
267310

268-
$formShare->setPermissions($keyValuePairs['permissions']);
311+
if (array_key_exists('permissions', $keyValuePairs)) {
312+
$formShare->setPermissions($keyValuePairs['permissions']);
313+
}
269314
$formShare = $this->shareMapper->update($formShare);
270315

271-
if (in_array($formShare->getShareType(), [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_USERGROUP, IShare::TYPE_CIRCLE], true)) {
316+
if (array_key_exists('permissions', $keyValuePairs)
317+
&& in_array($formShare->getShareType(), [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_USERGROUP, IShare::TYPE_CIRCLE], true)) {
272318
if (in_array(Constants::PERMISSION_RESULTS, $keyValuePairs['permissions'], true)) {
273319
$userFolder = $this->rootFolder->getUserFolder($form->getOwnerId());
274320
$uploadedFilesFolderPath = $this->filePathHelper->getFormUploadedFilesFolderPath($form);
@@ -418,4 +464,22 @@ private function validatePermissions(array $permissions, int $shareType): bool {
418464
}
419465
return true;
420466
}
467+
468+
/**
469+
* @throws OCSBadRequestException If token does not satisfy basic safety checks
470+
*/
471+
private function validatePublicShareToken(string $token): void {
472+
if ($token !== trim($token)) {
473+
throw new OCSBadRequestException('Invalid share token');
474+
}
475+
476+
$tokenLength = strlen($token);
477+
if ($tokenLength < Constants::PUBLIC_SHARE_TOKEN_MIN_LENGTH || $tokenLength > Constants::PUBLIC_SHARE_TOKEN_MAX_LENGTH) {
478+
throw new OCSBadRequestException('Invalid share token');
479+
}
480+
481+
if (preg_match('/^[a-zA-Z0-9]+$/', $token) !== 1) {
482+
throw new OCSBadRequestException('Invalid share token');
483+
}
484+
}
421485
}

openapi.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5196,7 +5196,7 @@
51965196
"/ocs/v2.php/apps/forms/api/v3/forms/{formId}/shares/{shareId}": {
51975197
"patch": {
51985198
"operationId": "share_api-update-share",
5199-
"summary": "Update permissions of a share",
5199+
"summary": "Update properties of a share",
52005200
"description": "This endpoint allows CORS requests",
52015201
"tags": [
52025202
"share_api"
@@ -5293,7 +5293,7 @@
52935293
}
52945294
},
52955295
"400": {
5296-
"description": "Invalid permission given",
5296+
"description": "Share hash exists, please retry",
52975297
"content": {
52985298
"application/json": {
52995299
"schema": {
@@ -5321,7 +5321,7 @@
53215321
}
53225322
},
53235323
"403": {
5324-
"description": "Not allowed to update other properties than permissions",
5324+
"description": "Not allowed to update unknown properties",
53255325
"content": {
53265326
"application/json": {
53275327
"schema": {

src/FormsSettings.vue

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@
7373
{{ t('forms', 'Allow sharing by link') }}
7474
</NcCheckboxRadioSwitch>
7575
<NcCheckboxRadioSwitch
76+
ref="switchAllowCustomPublicShareTokens"
77+
v-model="appConfig.allowCustomPublicShareTokens"
78+
type="switch"
79+
@update:modelValue="onAllowCustomPublicShareTokensChange">
80+
{{ t('forms', 'Allow custom public share tokens') }}
81+
</NcCheckboxRadioSwitch>
82+
<NcCheckboxRadioSwitch
83+
ref="switchAllowPermitAll"
7684
v-model="appConfig.allowPermitAll"
7785
:loading="loading.allowPermitAll"
7886
type="switch"
@@ -168,7 +176,14 @@ export default {
168176
async onAllowPublicLinkChange(newVal) {
169177
this.loading.allowPublicLink = true
170178
await this.saveAppConfig('allowPublicLink', newVal)
171-
this.loading.allowPublicLink = false
179+
el.loading = false
180+
},
181+
182+
async onAllowCustomPublicShareTokensChange(newVal) {
183+
const el = this.$refs.switchAllowCustomPublicShareTokens
184+
el.loading = true
185+
await this.saveAppConfig('allowCustomPublicShareTokens', newVal)
186+
el.loading = false
172187
},
173188
174189
async onAllowPermitAllChange(newVal) {

0 commit comments

Comments
 (0)