From 3e25a62dba3ae330da3ec0a000c97220c95b2512 Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Fri, 15 Nov 2024 15:42:18 -0800 Subject: [PATCH 1/2] [wip] feat: Allow updating share token --- apps/files_sharing/lib/Controller/ShareAPIController.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php index 71f73f777a5d9..8bcf2d56da111 100644 --- a/apps/files_sharing/lib/Controller/ShareAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareAPIController.php @@ -1163,6 +1163,7 @@ private function hasPermission(int $permissionsSet, int $permissionsToCheck): bo * Considering the share already exists, no mail will be send after the share is updated. * You will have to use the sendMail action to send the mail. * @param string|null $shareWith New recipient for email shares + * @param string|null $token New token * @return DataResponse * @throws OCSBadRequestException Share could not be updated because the requested changes are invalid * @throws OCSForbiddenException Missing permissions to update the share @@ -1183,6 +1184,7 @@ public function updateShare( ?string $hideDownload = null, ?string $attributes = null, ?string $sendMail = null, + ?string $token = null, ): DataResponse { try { $share = $this->getShareById($id); @@ -1210,7 +1212,8 @@ public function updateShare( $label === null && $hideDownload === null && $attributes === null && - $sendMail === null + $sendMail === null && + $token === null ) { throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given')); } @@ -1335,6 +1338,10 @@ public function updateShare( } elseif ($sendPasswordByTalk !== null) { $share->setSendPasswordByTalk(false); } + + if ($token !== null) { + $share->setToken($token); + } } // NOT A LINK SHARE From 948ea9339debd2b7e923ccfe83b9069513617dbb Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Fri, 15 Nov 2024 15:42:18 -0800 Subject: [PATCH 2/2] [wip] feat: Edit share token --- .../lib/Controller/ShareAPIController.php | 17 +++++ apps/files_sharing/src/models/Share.ts | 7 ++ .../src/services/TokenService.ts | 31 ++++++++ .../src/views/SharingDetailsTab.vue | 31 ++++++++ .../Share20/Exception/ShareTokenException.php | 13 ++++ lib/private/Share20/Manager.php | 76 ++++++++++--------- lib/public/Share/IManager.php | 9 +++ 7 files changed, 149 insertions(+), 35 deletions(-) create mode 100644 apps/files_sharing/src/services/TokenService.ts create mode 100644 lib/private/Share20/Exception/ShareTokenException.php diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php index 8bcf2d56da111..f1dbcb8309744 100644 --- a/apps/files_sharing/lib/Controller/ShareAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareAPIController.php @@ -21,6 +21,7 @@ use OCA\GlobalSiteSelector\Service\SlaveService; use OCP\App\IAppManager; use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\ApiRoute; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\UserRateLimit; use OCP\AppFramework\Http\DataResponse; @@ -2158,4 +2159,20 @@ public function sendShareEmail(string $id, $password = ''): DataResponse { throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist')); } } + + /** + * Get a unique share token + * + * @return DataResponse + * + * 200: Token generated successfully + */ + #[ApiRoute('GET', '/api/v1/token')] + #[NoAdminRequired] + public function getToken(): DataResponse { + $token = $this->shareManager->generateToken(); + return new DataResponse([ + 'token' => $token, + ]); + } } diff --git a/apps/files_sharing/src/models/Share.ts b/apps/files_sharing/src/models/Share.ts index bfc6357240d2f..28631dc7288cb 100644 --- a/apps/files_sharing/src/models/Share.ts +++ b/apps/files_sharing/src/models/Share.ts @@ -192,6 +192,13 @@ export default class Share { return this._share.token } + /** + * Set the public share token + */ + set token(token: string) { + this._share.token = token + } + /** * Get the share note if any */ diff --git a/apps/files_sharing/src/services/TokenService.ts b/apps/files_sharing/src/services/TokenService.ts new file mode 100644 index 0000000000000..0ec3b27f67a24 --- /dev/null +++ b/apps/files_sharing/src/services/TokenService.ts @@ -0,0 +1,31 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import axios from '@nextcloud/axios' +import { generateUrl } from '@nextcloud/router' +import logger from './logger.ts' + +interface TokenData { + token: string, +} + +export const generateToken = async (): Promise => { + try { + const { data } = await axios.get(generateUrl('/api/v1/token')) + return data.token + } catch (error) { + logger.error('Failed to get token from server, falling back to client-side generation', { error }) + + const chars = 'abcdefgijkmnopqrstwxyzABCDEFGHJKLMNPQRSTWXYZ23456789' + const array = new Uint8Array(10) + const ratio = chars.length / 255 + window.crypto.getRandomValues(array) + let token = '' + for (let i = 0; i < array.length; i++) { + token += chars.charAt(array[i] * ratio) + } + return token + } +} diff --git a/apps/files_sharing/src/views/SharingDetailsTab.vue b/apps/files_sharing/src/views/SharingDetailsTab.vue index 0878c5d289d54..05be81d7a649f 100644 --- a/apps/files_sharing/src/views/SharingDetailsTab.vue +++ b/apps/files_sharing/src/views/SharingDetailsTab.vue @@ -105,9 +105,22 @@ role="region">
+ + +