feat(sharing): Allow custom share tokens for public links#3348
feat(sharing): Allow custom share tokens for public links#3348Chartman123 wants to merge 3 commits into
Conversation
| $existingShare = $this->shareMapper->findPublicShareByHash($token); | ||
| if ($existingShare->getId() !== $formShare->getId()) { | ||
| $this->logger->debug('Share hash already exists.'); | ||
| throw new OCSBadRequestException('Share hash exists, please retry.'); |
There was a problem hiding this comment.
I think we should not leak that information
| throw new OCSBadRequestException('Share hash exists, please retry.'); | |
| throw new OCSBadRequestException('Invalid share token'); |
Chartman123
left a comment
There was a problem hiding this comment.
@alexander-rebello I added some comments/suggestions. Most of them, especially the suggestions need the rebase on current main first.
| self::CONFIG_KEY_RESTRICTCREATION | ||
| ]; | ||
|
|
||
| public const PUBLIC_SHARE_TOKEN_MIN_LENGTH = 8; |
There was a problem hiding this comment.
To be consistens with file sharing we should use 1 here, but we need to add bruteforce protection on the PageController
| public function getAllowCustomPublicToken(): bool { | ||
| return json_decode($this->config->getAppValue($this->appName, Constants::CONFIG_KEY_ALLOWCUSTOMPUBLICTOKEN, 'false')); | ||
| } |
There was a problem hiding this comment.
please use IAppConfig and getAppValueBool() here.
| public function getAllowCustomPublicToken(): bool { | |
| return json_decode($this->config->getAppValue($this->appName, Constants::CONFIG_KEY_ALLOWCUSTOMPUBLICTOKEN, 'false')); | |
| } | |
| public function getAllowCustomPublicToken(): bool { | |
| return json_decode($this->appConfig->getAppValueBool(Constants::CONFIG_KEY_ALLOWCUSTOMPUBLICTOKEN, false)); | |
| } |
| <NcActionButton | ||
| v-if="appConfig.allowCustomPublicShareTokens" | ||
| :disabled=" | ||
| locked | ||
| || !isCurrentUserOwner | ||
| || isShareTokenSaving(share) | ||
| || !isShareTokenDirty(share) | ||
| " | ||
| @click="updateShareToken(share)"> | ||
| <template #icon> | ||
| <IconCheck :size="20" /> | ||
| </template> | ||
| {{ t('forms', 'Save token') }} | ||
| </NcActionButton> |
| import NcNoteCard from '@nextcloud/vue/components/NcNoteCard' | ||
| import NcTextField from '@nextcloud/vue/components/NcTextField' | ||
| import IconAccountMultiple from 'vue-material-design-icons/AccountMultipleOutline.vue' | ||
| import IconCheck from 'vue-material-design-icons/Check.vue' |
There was a problem hiding this comment.
Use Material Symbol here
| import IconCheck from 'vue-material-design-icons/Check.vue' | |
| import IconCheck from '@material-symbols/svg-400/outlined/check.svg?raw' |
| NcIconSvgWrapper, | ||
| FormsIcon, | ||
| IconAccountMultiple, | ||
| IconCheck, |
There was a problem hiding this comment.
| IconCheck, |
| emits: ['addShare', 'updateShare', 'removeShare', 'update:formProp'], | ||
|
|
||
| setup() { | ||
| return { | ||
| FormsIcon, | ||
| IconCopyAll, | ||
| IconPlus, | ||
| IconCodeBrackets, | ||
| IconDelete, | ||
| IconLinkVariant, | ||
| IconLinkBoxVariantOutline, | ||
| IconAccountMultiple, | ||
| IconQr, | ||
| } | ||
| }, | ||
|
|
||
| data() { |
There was a problem hiding this comment.
The icons need to be added to the setup().
| emits: ['addShare', 'updateShare', 'removeShare', 'update:formProp'], | |
| setup() { | |
| return { | |
| FormsIcon, | |
| IconCopyAll, | |
| IconPlus, | |
| IconCodeBrackets, | |
| IconDelete, | |
| IconLinkVariant, | |
| IconLinkBoxVariantOutline, | |
| IconAccountMultiple, | |
| IconQr, | |
| } | |
| }, | |
| data() { | |
| emits: ['addShare', 'updateShare', 'removeShare', 'update:formProp'], | |
| setup() { | |
| return { | |
| IconCheck, | |
| } | |
| }, | |
| data() { |
| <NcCheckboxRadioSwitch | ||
| ref="switchAllowCustomPublicShareTokens" | ||
| v-model="appConfig.allowCustomPublicShareTokens" | ||
| type="switch" | ||
| @update:modelValue="onAllowCustomPublicShareTokensChange"> | ||
| {{ t('forms', 'Allow custom public share tokens') }} | ||
| </NcCheckboxRadioSwitch> |
There was a problem hiding this comment.
| <NcCheckboxRadioSwitch | |
| ref="switchAllowCustomPublicShareTokens" | |
| v-model="appConfig.allowCustomPublicShareTokens" | |
| type="switch" | |
| @update:modelValue="onAllowCustomPublicShareTokensChange"> | |
| {{ t('forms', 'Allow custom public share tokens') }} | |
| </NcCheckboxRadioSwitch> | |
| <NcCheckboxRadioSwitch | |
| v-model="appConfig.allowCustomPublicShareTokens" | |
| :loading="loading.allowCustomPublicShareTokens" | |
| type="switch" | |
| @update:modelValue="onAllowCustomPublicShareTokensChange"> | |
| {{ t('forms', 'Allow custom public share tokens') }} | |
| </NcCheckboxRadioSwitch> |
| async onAllowCustomPublicShareTokensChange(newVal) { | ||
| const el = this.$refs.switchAllowCustomPublicShareTokens | ||
| el.loading = true | ||
| await this.saveAppConfig('allowCustomPublicShareTokens', newVal) | ||
| el.loading = false | ||
| }, | ||
|
|
There was a problem hiding this comment.
| async onAllowCustomPublicShareTokensChange(newVal) { | |
| const el = this.$refs.switchAllowCustomPublicShareTokens | |
| el.loading = true | |
| await this.saveAppConfig('allowCustomPublicShareTokens', newVal) | |
| el.loading = false | |
| }, | |
| async onAllowCustomPublicShareTokensChange(newVal) { | |
| this.loading.allowCustomPublicShareTokens= true | |
| await this.saveAppConfig('allowCustomPublicShareTokens', newVal) | |
| this.loading.allowCustomPublicShareTokens= false | |
| }, | |

Closes #2815
This adds admin-gated custom tokens for public Forms share links. By default the feature is disabled, so existing instances keep the current random-token behavior. When enabled by an admin, form owners can edit the token of an existing public link directly in the sharing sidebar, save it explicitly, and the old URL becomes invalid immediately.
It also adds the necessary backend support for token updates, keeps public-link routing compatible with custom tokens, and includes tests plus API documentation updates.
New PR replacing #3311