Skip to content

Commit eac3c5f

Browse files
Merge pull request #50202 from nextcloud/backport/50192/stable28
[stable28] fix(files_sharing): Stop overwriting the share expiration date with the default expiration date
2 parents 02a3053 + 8686524 commit eac3c5f

14 files changed

Lines changed: 165 additions & 26 deletions

apps/files_sharing/src/components/SharingEntryLink.vue

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -534,16 +534,7 @@ export default {
534534
},
535535
},
536536
mounted() {
537-
if (this.share) {
538-
this.defaultExpirationDateEnabled = this.config.defaultExpirationDate instanceof Date
539-
this.share.expireDate = this.defaultExpirationDateEnabled ? this.formatDateToString(this.config.defaultExpirationDate) : ''
540-
}
541-
},
542-
mounted() {
543-
if (this.share) {
544-
this.defaultExpirationDateEnabled = this.config.defaultExpirationDate instanceof Date
545-
this.share.expireDate = this.defaultExpirationDateEnabled ? this.formatDateToString(this.config.defaultExpirationDate) : ''
546-
}
537+
this.defaultExpirationDateEnabled = this.config.defaultExpirationDate instanceof Date
547538
},
548539
549540
methods: {

apps/files_sharing/src/mixins/SharesMixin.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ export default {
149149
return this.config.isDefaultExpireDateEnforced
150150
}
151151
if (this.isRemoteShare) {
152-
return this.config.isDefaultRemoteExpireDateEnforced
152+
return this.config.isDefaultRemoteExpireDateEnforced
153153
}
154154
return this.config.isDefaultInternalExpireDateEnforced
155155
},
@@ -230,9 +230,10 @@ export default {
230230
*
231231
* @param {Date} date
232232
*/
233-
onExpirationChange: debounce(function(date) {
233+
onExpirationChange(date) {
234234
this.share.expireDate = this.formatDateToString(new Date(date))
235-
}, 500),
235+
},
236+
236237
/**
237238
* Uncheck expire date
238239
* We need this method because @update:checked

apps/files_sharing/src/views/SharingDetailsTab.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@
140140
:value="new Date(share.expireDate ?? dateTomorrow)"
141141
:min="dateTomorrow"
142142
:max="maxExpirationDateEnforced"
143-
:hide-label="true"
143+
hide-label
144+
:label="t('files_sharing', 'Expiration date')"
144145
:placeholder="t('files_sharing', 'Expiration date')"
145146
type="date"
146147
@input="onExpirationChange" />

cypress/e2e/files/FilesUtils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
*
2121
*/
2222

23+
export const closeSidebar = () => {
24+
// {force: true} as it might be hidden behind toasts
25+
cy.get('[data-cy-sidebar] .app-sidebar__close').click({ force: true })
26+
}
27+
2328
export const getRowForFile = (filename: string) => cy.get(`[data-cy-files-list-row-name="${CSS.escape(filename)}"]`)
2429

2530
export const getActionsForFile = (filename: string) => getRowForFile(filename).find('[data-cy-files-list-row-actions]')

cypress/e2e/files_sharing/FilesSharingUtils.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55
/* eslint-disable jsdoc/require-jsdoc */
6+
67
import { triggerActionForFile } from '../files/FilesUtils'
78

89
export interface ShareSetting {
@@ -12,6 +13,7 @@ export interface ShareSetting {
1213
share: boolean
1314
download: boolean
1415
note: string
16+
expiryDate: Date
1517
}
1618

1719
export function createShare(fileName: string, username: string, shareSettings: Partial<ShareSetting> = {}) {
@@ -31,15 +33,20 @@ export function createShare(fileName: string, username: string, shareSettings: P
3133
updateShare(fileName, 0, shareSettings)
3234
}
3335

36+
export function openSharingDetails(index: number) {
37+
cy.get('#app-sidebar-vue').within(() => {
38+
cy.get('[data-cy-files-sharing-share-actions]').eq(index).click()
39+
cy.get('[data-cy-files-sharing-share-permissions-bundle="custom"]').click()
40+
})
41+
}
42+
3443
export function updateShare(fileName: string, index: number, shareSettings: Partial<ShareSetting> = {}) {
3544
openSharingPanel(fileName)
45+
openSharingDetails(index)
3646

3747
cy.intercept({ times: 1, method: 'PUT', url: '**/apps/files_sharing/api/v1/shares/*' }).as('updateShare')
3848

3949
cy.get('#app-sidebar-vue').within(() => {
40-
cy.get('[data-cy-files-sharing-share-actions]').eq(index).click()
41-
cy.get('[data-cy-files-sharing-share-permissions-bundle="custom"]').click()
42-
4350
if (shareSettings.download !== undefined) {
4451
cy.get('[data-cy-files-sharing-share-permissions-checkbox="download"]').find('input').as('downloadCheckbox')
4552
if (shareSettings.download) {
@@ -86,13 +93,22 @@ export function updateShare(fileName: string, index: number, shareSettings: Part
8693

8794
if (shareSettings.note !== undefined) {
8895
cy.findByRole('checkbox', { name: /note to recipient/i }).check({ force: true, scrollBehavior: 'nearest' })
89-
cy.findByRole('textbox', { name: /note to recipient/i }).type(shareSettings.note)
96+
cy.findByRole('textbox', { name: /note for the share recipient/i }).type(shareSettings.note)
97+
}
98+
99+
if (shareSettings.expiryDate !== undefined) {
100+
cy.findByRole('checkbox', { name: /expiration date/i })
101+
.check({ force: true, scrollBehavior: 'nearest' })
102+
cy.get('#share-date-picker')
103+
.type(`${shareSettings.expiryDate.getFullYear()}-${String(shareSettings.expiryDate.getMonth() + 1).padStart(2, '0')}-${String(shareSettings.expiryDate.getDate()).padStart(2, '0')}`)
90104
}
91105

92106
cy.get('[data-cy-files-sharing-share-editor-action="save"]').click({ scrollBehavior: 'nearest' })
93107

94108
cy.wait('@updateShare')
95109
})
110+
// close all toasts
111+
cy.get('.toast-success').findAllByRole('button').click({ force: true, multiple: true })
96112
}
97113

98114
export function openSharingPanel(fileName: string) {
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*!
2+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { User } from '@nextcloud/cypress'
7+
import { randomBytes } from 'crypto'
8+
import { closeSidebar } from '../files/FilesUtils.ts'
9+
import { createShare, openSharingDetails, openSharingPanel, updateShare } from './FilesSharingUtils.ts'
10+
11+
describe('files_sharing: Expiry date', () => {
12+
const expectedDefaultDate = new Date(Date.now() + 2 * 24 * 60 * 60 * 1000)
13+
const expectedDefaultDateString = `${expectedDefaultDate.getFullYear()}-${String(expectedDefaultDate.getMonth() + 1).padStart(2, '0')}-${String(expectedDefaultDate.getDate()).padStart(2, '0')}`
14+
const fortnight = new Date(Date.now() + 14 * 24 * 60 * 60 * 1000)
15+
const fortnightString = `${fortnight.getFullYear()}-${String(fortnight.getMonth() + 1).padStart(2, '0')}-${String(fortnight.getDate()).padStart(2, '0')}`
16+
17+
let alice: User
18+
let bob: User
19+
20+
before(() => {
21+
// Ensure we have the admin setting setup for default dates with 2 days in the future
22+
cy.runOccCommand('config:app:set --value yes core shareapi_default_internal_expire_date')
23+
cy.runOccCommand('config:app:set --value 2 core shareapi_internal_expire_after_n_days')
24+
25+
cy.createRandomUser().then((user) => {
26+
alice = user
27+
cy.login(alice)
28+
})
29+
cy.createRandomUser().then((user) => {
30+
bob = user
31+
})
32+
})
33+
34+
after(() => {
35+
cy.runOccCommand('config:app:delete core shareapi_default_internal_expire_date')
36+
cy.runOccCommand('config:app:delete core shareapi_enforce_internal_expire_date')
37+
cy.runOccCommand('config:app:delete core shareapi_internal_expire_after_n_days')
38+
})
39+
40+
beforeEach(() => {
41+
cy.runOccCommand('config:app:delete core shareapi_enforce_internal_expire_date')
42+
})
43+
44+
it('See default expiry date is set and enforced', () => {
45+
// Enforce the date
46+
cy.runOccCommand('config:app:set --value yes core shareapi_enforce_internal_expire_date')
47+
const dir = prepareDirectory()
48+
49+
validateExpiryDate(dir, expectedDefaultDateString)
50+
cy.findByRole('checkbox', { name: /expiration date/i })
51+
.should('be.checked')
52+
.and('be.disabled')
53+
})
54+
55+
it('See default expiry date is set also if not enforced', () => {
56+
const dir = prepareDirectory()
57+
58+
validateExpiryDate(dir, expectedDefaultDateString)
59+
cy.findByRole('checkbox', { name: /expiration date/i })
60+
.should('be.checked')
61+
.and('not.be.disabled')
62+
.check({ force: true, scrollBehavior: 'nearest' })
63+
})
64+
65+
it('Can set custom expiry date', () => {
66+
const dir = prepareDirectory()
67+
updateShare(dir, 0, { expiryDate: fortnight })
68+
validateExpiryDate(dir, fortnightString)
69+
})
70+
71+
it('Custom expiry date survives reload', () => {
72+
const dir = prepareDirectory()
73+
updateShare(dir, 0, { expiryDate: fortnight })
74+
validateExpiryDate(dir, fortnightString)
75+
76+
cy.visit('/apps/files')
77+
validateExpiryDate(dir, fortnightString)
78+
})
79+
80+
/**
81+
* Regression test for https://github.com/nextcloud/server/pull/50192
82+
* Ensure that admin default settings do not always override the user set value.
83+
*/
84+
it('Custom expiry date survives unrelated update', () => {
85+
const dir = prepareDirectory()
86+
updateShare(dir, 0, { expiryDate: fortnight })
87+
validateExpiryDate(dir, fortnightString)
88+
89+
closeSidebar()
90+
updateShare(dir, 0, { note: 'Only note changed' })
91+
validateExpiryDate(dir, fortnightString)
92+
93+
cy.visit('/apps/files')
94+
validateExpiryDate(dir, fortnightString)
95+
})
96+
97+
/**
98+
* Prepare directory, login and share to bob
99+
*/
100+
function prepareDirectory(): string {
101+
const name = randomBytes(4)
102+
.toString('hex')
103+
cy.mkdir(alice, `/${name}`)
104+
cy.login(alice)
105+
cy.visit('/apps/files')
106+
createShare(name, bob.userId)
107+
return name
108+
}
109+
110+
/**
111+
* Validate expiry date on a share
112+
*
113+
* @param filename The filename to validate
114+
* @param expectedDate The expected date in YYYY-MM-dd
115+
*/
116+
function validateExpiryDate(filename: string, expectedDate: string) {
117+
openSharingPanel(filename)
118+
openSharingDetails(0)
119+
120+
cy.get('#share-date-picker')
121+
.should('exist')
122+
.and('have.value', expectedDate)
123+
}
124+
125+
})

cypress/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
"extends": "../tsconfig.json",
33
"include": ["./**/*.ts"],
44
"compilerOptions": {
5-
"types": ["cypress", "cypress-axe", "cypress-wait-until", "dockerode"],
5+
"types": ["@testing-library/cypress", "cypress", "cypress-axe", "cypress-wait-until", "dockerode"],
66
}
77
}

dist/4235-4235.js

Lines changed: 0 additions & 3 deletions
This file was deleted.

dist/4235-4235.js.map

Lines changed: 0 additions & 1 deletion
This file was deleted.

dist/4735-4735.js

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)