diff --git a/apps/files_sharing/src/components/SharingEntryLink.vue b/apps/files_sharing/src/components/SharingEntryLink.vue
index ab5e049e187de..de2551518628c 100644
--- a/apps/files_sharing/src/components/SharingEntryLink.vue
+++ b/apps/files_sharing/src/components/SharingEntryLink.vue
@@ -87,7 +87,7 @@
:disabled="pendingEnforcedExpirationDate || saving"
class="share-link-expiration-date-checkbox"
@change="onDefaultExpirationDateEnabledChange">
- {{ config.enforcePasswordForPublicLink ? t('files_sharing', 'Enable link expiration (enforced)') : t('files_sharing', 'Enable link expiration') }}
+ {{ config.isDefaultExpireDateEnforced ? t('files_sharing', 'Enable link expiration (enforced)') : t('files_sharing', 'Enable link expiration') }}
diff --git a/cypress/e2e/files_sharing/ShareOptionsType.ts b/cypress/e2e/files_sharing/ShareOptionsType.ts
new file mode 100644
index 0000000000000..a6ce69222995d
--- /dev/null
+++ b/cypress/e2e/files_sharing/ShareOptionsType.ts
@@ -0,0 +1,18 @@
+/*!
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+export type ShareOptions = {
+ enforcePassword?: boolean
+ enforceExpirationDate?: boolean
+ alwaysAskForPassword?: boolean
+ defaultExpirationDateSet?: boolean
+}
+
+export const defaultShareOptions: ShareOptions = {
+ enforcePassword: false,
+ enforceExpirationDate: false,
+ alwaysAskForPassword: false,
+ defaultExpirationDateSet: false,
+}
diff --git a/cypress/e2e/files_sharing/public-share/required-before-create.cy.ts b/cypress/e2e/files_sharing/public-share/required-before-create.cy.ts
new file mode 100644
index 0000000000000..d0222f087e1d4
--- /dev/null
+++ b/cypress/e2e/files_sharing/public-share/required-before-create.cy.ts
@@ -0,0 +1,187 @@
+/*!
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import type { ShareContext } from './setup-public-share.ts'
+import type { ShareOptions } from '../ShareOptionsType.ts'
+import { setupData, createShare } from './setup-public-share.ts'
+
+describe('files_sharing: Before create checks', () => {
+
+ let shareContext: ShareContext
+
+ before(() => {
+ // Setup data for the shared folder once before all tests
+ cy.createRandomUser().then((randomUser) => {
+ shareContext = {
+ user: randomUser,
+ }
+ })
+ })
+
+ afterEach(() => {
+ cy.runOccCommand('config:app:delete core shareapi_enable_link_password_by_default')
+ cy.runOccCommand('config:app:delete core shareapi_enforce_links_password')
+ cy.runOccCommand('config:app:delete core shareapi_default_expire_date')
+ cy.runOccCommand('config:app:delete core shareapi_enforce_expire_date')
+ cy.runOccCommand('config:app:delete core shareapi_expire_after_n_days')
+ })
+
+ const applyShareOptions = (options: ShareOptions): void => {
+ cy.runOccCommand(`config:app:set --value ${options.alwaysAskForPassword ? 'yes' : 'no'} core shareapi_enable_link_password_by_default`)
+ cy.runOccCommand(`config:app:set --value ${options.enforcePassword ? 'yes' : 'no'} core shareapi_enforce_links_password`)
+ cy.runOccCommand(`config:app:set --value ${options.enforceExpirationDate ? 'yes' : 'no'} core shareapi_enforce_expire_date`)
+ cy.runOccCommand(`config:app:set --value ${options.defaultExpirationDateSet ? 'yes' : 'no'} core shareapi_default_expire_date`)
+ if (options.defaultExpirationDateSet) {
+ cy.runOccCommand('config:app:set --value 2 core shareapi_expire_after_n_days')
+ }
+ }
+
+ it('Checks if user can create share when both password and expiration date are enforced', () => {
+ const shareOptions : ShareOptions = {
+ alwaysAskForPassword: true,
+ enforcePassword: true,
+ enforceExpirationDate: true,
+ defaultExpirationDateSet: true,
+ }
+ applyShareOptions(shareOptions)
+ const shareName = 'passwordAndExpireEnforced'
+ setupData(shareContext, shareName)
+ createShare(shareContext, shareName, shareOptions).then((shareUrl) => {
+ shareContext.url = shareUrl
+ cy.log(`Created share with URL: ${shareUrl}`)
+ })
+ })
+
+ it('Checks if user can create share when password is enforced and expiration date has a default set', () => {
+ const shareOptions : ShareOptions = {
+ alwaysAskForPassword: true,
+ enforcePassword: true,
+ defaultExpirationDateSet: true,
+ }
+ applyShareOptions(shareOptions)
+ const shareName = 'passwordEnforcedDefaultExpire'
+ setupData(shareContext, shareName)
+ createShare(shareContext, shareName).then((shareUrl) => {
+ shareContext.url = shareUrl
+ cy.log(`Created share with URL: ${shareUrl}`)
+ })
+ })
+
+ it('Checks if user can create share when password is optionally requested and expiration date is enforced', () => {
+ const shareOptions : ShareOptions = {
+ alwaysAskForPassword: true,
+ enforceExpirationDate: true,
+ }
+ applyShareOptions(shareOptions)
+ const shareName = 'defaultPasswordExpireEnforced'
+ setupData(shareContext, shareName)
+ createShare(shareContext, shareName, shareOptions).then((shareUrl) => {
+ shareContext.url = shareUrl
+ cy.log(`Created share with URL: ${shareUrl}`)
+ })
+ })
+
+ it('Checks if user can create share when password is optionally requested and expiration date have defaults set', () => {
+ const shareOptions : ShareOptions = {
+ alwaysAskForPassword: true,
+ defaultExpirationDateSet: true,
+ }
+ applyShareOptions(shareOptions)
+ const shareName = 'defaultPasswordAndExpire'
+ setupData(shareContext, shareName)
+ createShare(shareContext, shareName).then((shareUrl) => {
+ shareContext.url = shareUrl
+ cy.log(`Created share with URL: ${shareUrl}`)
+ })
+ })
+
+ it('Checks if user can create share with password enforced and expiration date set but not enforced', () => {
+ const shareOptions : ShareOptions = {
+ alwaysAskForPassword: true,
+ enforcePassword: true,
+ defaultExpirationDateSet: true,
+ enforceExpirationDate: false,
+ }
+ applyShareOptions(shareOptions)
+ const shareName = 'passwordEnforcedExpireSetNotEnforced'
+ setupData(shareContext, shareName)
+ createShare(shareContext, shareName, shareOptions).then((shareUrl) => {
+ shareContext.url = shareUrl
+ cy.log(`Created share with URL: ${shareUrl}`)
+ })
+ })
+
+ it('Checks if user can create share with both password and expiration date not enforced, but defaults set', () => {
+ const shareOptions : ShareOptions = {
+ enforcePassword: false,
+ enforceExpirationDate: false,
+ defaultExpirationDateSet: true,
+ alwaysAskForPassword: true,
+ }
+ const shareName = 'defaultPasswordExpireNotEnforced'
+ setupData(shareContext, shareName)
+ createShare(shareContext, shareName, shareOptions).then((shareUrl) => {
+ shareContext.url = shareUrl
+ cy.log(`Created share with URL: ${shareUrl}`)
+ })
+ })
+
+ it('Checks if user can create share with password not enforced but expiration date enforced', () => {
+ const shareOptions : ShareOptions = {
+ alwaysAskForPassword: true,
+ enforcePassword: false,
+ defaultExpirationDateSet: true,
+ enforceExpirationDate: true,
+ }
+ applyShareOptions(shareOptions)
+ const shareName = 'noPasswordExpireEnforced'
+ setupData(shareContext, shareName)
+ createShare(shareContext, shareName, shareOptions).then((shareUrl) => {
+ shareContext.url = shareUrl
+ cy.log(`Created share with URL: ${shareUrl}`)
+ })
+ })
+
+ it('Checks if user can create share with password not enforced and expiration date has a default set', () => {
+ const shareOptions : ShareOptions = {
+ enforcePassword: false,
+ defaultExpirationDateSet: true,
+ }
+ applyShareOptions(shareOptions)
+ const shareName = 'defaultExpireNoPasswordEnforced'
+ setupData(shareContext, shareName)
+ createShare(shareContext, shareName, shareOptions).then((shareUrl) => {
+ shareContext.url = shareUrl
+ cy.log(`Created share with URL: ${shareUrl}`)
+ })
+ })
+
+ it('Checks if user can create share with expiration date set and password not enforced', () => {
+ const shareOptions : ShareOptions = {
+ alwaysAskForPassword: true,
+ enforcePassword: false,
+ defaultExpirationDateSet: true,
+ }
+ applyShareOptions(shareOptions)
+
+ const shareName = 'noPasswordExpireDefault'
+ setupData(shareContext, shareName)
+ createShare(shareContext, shareName, shareOptions).then((shareUrl) => {
+ shareContext.url = shareUrl
+ cy.log(`Created share with URL: ${shareUrl}`)
+ })
+ })
+
+ it('Checks if user can create share with password not enforced, expiration date not enforced, and no defaults set', () => {
+ applyShareOptions()
+ const shareName = 'noPasswordNoExpireNoDefaults'
+ setupData(shareContext, shareName)
+ createShare(shareContext, shareName, null).then((shareUrl) => {
+ shareContext.url = shareUrl
+ cy.log(`Created share with URL: ${shareUrl}`)
+ })
+ })
+
+})
diff --git a/cypress/e2e/files_sharing/public-share/setup-public-share.ts b/cypress/e2e/files_sharing/public-share/setup-public-share.ts
index 5e23357a8215c..8f3bc303208e4 100644
--- a/cypress/e2e/files_sharing/public-share/setup-public-share.ts
+++ b/cypress/e2e/files_sharing/public-share/setup-public-share.ts
@@ -3,91 +3,141 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { User } from '@nextcloud/cypress'
+import type { ShareOptions } from '../ShareOptionsType.ts'
import { openSharingPanel } from '../FilesSharingUtils.ts'
-let user: User
-let url: string
+export interface ShareContext {
+ user: User
+ url?: string
+}
+
+const defaultShareContext: ShareContext = {
+ user: {} as User,
+ url: undefined,
+}
/**
- * URL of the share
+ * Retrieves the URL of the share.
+ * Throws an error if the share context is not initialized properly.
+ *
+ * @param context The current share context (defaults to `defaultShareContext` if not provided).
+ * @return The share URL.
+ * @throws Error if the share context has no URL.
*/
-export function getShareUrl() {
- if (url === undefined) {
+export function getShareUrl(context: ShareContext = defaultShareContext): string {
+ if (!context.url) {
throw new Error('You need to setup the share first!')
}
- return url
+ return context.url
}
/**
* Setup the available data
+ * @param user The current share context
* @param shareName The name of the shared folder
*/
-function setupData(shareName: string) {
+export function setupData(user: User, shareName: string): void {
cy.mkdir(user, `/${shareName}`)
cy.mkdir(user, `/${shareName}/subfolder`)
cy.uploadContent(user, new Blob(['foo']), 'text/plain', `/${shareName}/foo.txt`)
cy.uploadContent(user, new Blob(['bar']), 'text/plain', `/${shareName}/subfolder/bar.txt`)
}
+/**
+ * Check the password state based on enforcement and default presence.
+ *
+ * @param enforced Whether the password is enforced.
+ * @param alwaysAskForPassword Wether the password should always be asked for.
+ */
+function checkPasswordState(enforced: boolean, alwaysAskForPassword: boolean) {
+ if (enforced) {
+ cy.contains('Password protection (enforced)').should('exist')
+ cy.contains('Enter a password')
+ .should('exist')
+ .and('not.be.disabled')
+ } else if (alwaysAskForPassword) {
+ cy.contains('Password protection').should('exist')
+ cy.contains('Enter a password')
+ .should('exist')
+ .and('not.be.disabled')
+ }
+}
+
+/**
+ * Check the expiration date state based on enforcement and default presence.
+ *
+ * @param enforced Whether the expiration date is enforced.
+ * @param hasDefault Whether a default expiration date is set.
+ */
+function checkExpirationDateState(enforced: boolean, hasDefault: boolean) {
+ if (enforced) {
+ cy.contains('Enable link expiration (enforced)').should('exist')
+ } else if (hasDefault) {
+ cy.contains('Enable link expiration').should('exist')
+ }
+ cy.contains('Enter expiration date')
+ .should('exist')
+ .and('not.be.disabled')
+}
+
/**
* Create a public link share
+ * @param context The current share context
* @param shareName The name of the shared folder
+ * @param options The share options
*/
-function createShare(shareName: string) {
- cy.login(user)
- // open the files app
+export function createShare(context: ShareContext, shareName: string, options: ShareOptions | null = null) {
+ cy.login(context.user)
cy.visit('/apps/files')
- // open the sidebar
openSharingPanel(shareName)
- // create the share
+
cy.intercept('POST', '**/ocs/v2.php/apps/files_sharing/api/v1/shares').as('createShare')
- cy.findByRole('button', { name: 'Create a new share link' })
- .click()
+ cy.findByRole('button', { name: 'Create a new share link' }).click()
+ // Conduct optional checks based on the provided options
+ if (options) {
+ cy.get('.sharing-entry__actions').should('be.visible') // Wait for the dialog to open
+ checkPasswordState(options.enforcePassword ?? false, options.alwaysAskForPassword ?? false)
+ checkExpirationDateState(options.enforceExpirationDate ?? false, options.defaultExpirationDateSet ?? false)
+ cy.findByRole('button', { name: 'Create share' }).click()
+ }
- // extract the link
return cy.wait('@createShare')
.should(({ response }) => {
- const { ocs } = response!.body
- url = ocs?.data.url
- expect(url).to.match(/^http:\/\//)
+ expect(response?.statusCode).to.eq(200)
+ const url = response?.body?.ocs?.data?.url
+ expect(url).to.match(/^https?:\/\//)
+ context.url = url
})
- .then(() => cy.wrap(url))
+ .then(() => cy.wrap(context.url))
}
/**
* Adjust share permissions to be editable
*/
-function adjustSharePermission() {
- // Update the share to be a file drop
+function adjustSharePermission(): void {
cy.findByRole('list', { name: 'Link shares' })
.findAllByRole('listitem')
.first()
.findByRole('button', { name: /Actions/i })
.click()
- cy.findByRole('menuitem', { name: /Customize link/i })
- .should('be.visible')
- .click()
+ cy.findByRole('menuitem', { name: /Customize link/i }).click()
+
+ cy.get('[data-cy-files-sharing-share-permissions-bundle]').should('be.visible')
+ cy.get('[data-cy-files-sharing-share-permissions-bundle="upload-edit"]').click()
- // Enable upload-edit
- cy.get('[data-cy-files-sharing-share-permissions-bundle]')
- .should('be.visible')
- cy.get('[data-cy-files-sharing-share-permissions-bundle="upload-edit"]')
- .click()
- // save changes
cy.intercept('PUT', '**/ocs/v2.php/apps/files_sharing/api/v1/shares/*').as('updateShare')
- cy.findByRole('button', { name: 'Update share' })
- .click()
- cy.wait('@updateShare')
+ cy.findByRole('button', { name: 'Update share' }).click()
+ cy.wait('@updateShare').its('response.statusCode').should('eq', 200)
}
/**
* Setup a public share and backup the state.
* If the setup was already done in another run, the state will be restored.
*
+ * @param shareName The name of the shared folder
* @return The URL of the share
*/
-export function setupPublicShare(): Cypress.Chainable {
- const shareName = 'shared'
+export function setupPublicShare(shareName = 'shared'): Cypress.Chainable {
return cy.task('getVariable', { key: 'public-share-data' })
.then((data) => {
@@ -95,20 +145,28 @@ export function setupPublicShare(): Cypress.Chainable {
const { dataSnapshot, shareUrl } = data as any || {}
if (dataSnapshot) {
cy.restoreState(dataSnapshot)
- url = shareUrl
+ defaultShareContext.url = shareUrl
return cy.wrap(shareUrl as string)
} else {
const shareData: Record = {}
return cy.createRandomUser()
- .then(($user) => { user = $user })
- .then(() => setupData(shareName))
- .then(() => createShare(shareName))
- .then((value) => { shareData.shareUrl = value })
+ .then((user) => {
+ defaultShareContext.user = user
+ })
+ .then(() => setupData(defaultShareContext.user, shareName))
+ .then(() => createShare(defaultShareContext, shareName))
+ .then((url) => {
+ shareData.shareUrl = url
+ })
.then(() => adjustSharePermission())
- .then(() => cy.saveState().then((value) => { shareData.dataSnapshot = value }))
+ .then(() =>
+ cy.saveState().then((snapshot) => {
+ shareData.dataSnapshot = snapshot
+ }),
+ )
.then(() => cy.task('setVariable', { key: 'public-share-data', value: shareData }))
.then(() => cy.log(`Public share setup, URL: ${shareData.shareUrl}`))
- .then(() => cy.wrap(url))
+ .then(() => cy.wrap(defaultShareContext.url))
}
})
}