aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorskjnldsv <skjnldsv@protonmail.com>2025-02-20 16:29:19 +0100
committerskjnldsv <skjnldsv@protonmail.com>2025-02-21 09:53:11 +0100
commite5b9034c200b19c7b655d8f007d350f4f2cec8d0 (patch)
tree23e5bedef6702923fc38ef785ff6a7473a1c7a20
parent09844868ba9109b8eff50d233cfcde61262d27b3 (diff)
downloadnextcloud-server-e5b9034c200b19c7b655d8f007d350f4f2cec8d0.tar.gz
nextcloud-server-e5b9034c200b19c7b655d8f007d350f4f2cec8d0.zip
chore(files_external): add cypress tests for user credentials action
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
-rw-r--r--apps/files_external/src/actions/enterCredentialsAction.ts52
-rw-r--r--apps/files_external/src/views/CredentialsDialog.vue2
-rw-r--r--cypress/e2e/files_external/StorageUtils.ts38
-rw-r--r--cypress/e2e/files_external/files-user-credentials.cy.ts138
4 files changed, 205 insertions, 25 deletions
diff --git a/apps/files_external/src/actions/enterCredentialsAction.ts b/apps/files_external/src/actions/enterCredentialsAction.ts
index 45dfd65daf8..580f15ad876 100644
--- a/apps/files_external/src/actions/enterCredentialsAction.ts
+++ b/apps/files_external/src/actions/enterCredentialsAction.ts
@@ -1,26 +1,9 @@
/**
- * @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
- *
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- *
- * @license AGPL-3.0-or-later
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
// eslint-disable-next-line n/no-extraneous-import
-import type { AxiosResponse } from 'axios'
+import type { AxiosResponse } from '@nextcloud/axios'
import type { Node } from '@nextcloud/files'
import type { StorageConfig } from '../services/externalStorage'
@@ -45,9 +28,21 @@ type CredentialResponse = {
password?: string,
}
+/**
+ * Set credentials for external storage
+ *
+ * @param node The node for which to set the credentials
+ * @param login The username
+ * @param password The password
+ */
async function setCredentials(node: Node, login: string, password: string): Promise<null|true> {
- const configResponse = await axios.put(generateUrl('apps/files_external/userglobalstorages/{id}', node.attributes), {
- backendOptions: { user: login, password },
+ const configResponse = await axios.request({
+ method: 'PUT',
+ url: generateUrl('apps/files_external/userglobalstorages/{id}', { id: node.attributes.id }),
+ confirmPassword: PwdConfirmationMode.Strict,
+ data: {
+ backendOptions: { user: login, password },
+ },
}) as AxiosResponse<StorageConfig>
const config = configResponse.data
@@ -64,8 +59,10 @@ async function setCredentials(node: Node, login: string, password: string): Prom
return true
}
+export const ACTION_CREDENTIALS_EXTERNAL_STORAGE = 'credentials-external-storage'
+
export const action = new FileAction({
- id: 'credentials-external-storage',
+ id: ACTION_CREDENTIALS_EXTERNAL_STORAGE,
displayName: () => t('files', 'Enter missing credentials'),
iconSvgInline: () => LoginSvg,
@@ -98,7 +95,14 @@ export const action = new FileAction({
))
if (login && password) {
- return await setCredentials(node, login, password)
+ try {
+ await setCredentials(node, login, password)
+ showSuccess(t('files_external', 'Credentials successfully set'))
+ } catch (error) {
+ showError(t('files_external', 'Error while setting credentials: {error}', {
+ error: (error as Error).message,
+ }))
+ }
}
return null
diff --git a/apps/files_external/src/views/CredentialsDialog.vue b/apps/files_external/src/views/CredentialsDialog.vue
index 56c8093db56..7e71c83cf10 100644
--- a/apps/files_external/src/views/CredentialsDialog.vue
+++ b/apps/files_external/src/views/CredentialsDialog.vue
@@ -76,7 +76,7 @@ export default defineComponent({
computed: {
dialogButtons() {
return [{
- label: t('files_external', 'Submit'),
+ label: t('files_external', 'Confirm'),
type: 'primary',
nativeType: 'submit',
}]
diff --git a/cypress/e2e/files_external/StorageUtils.ts b/cypress/e2e/files_external/StorageUtils.ts
new file mode 100644
index 00000000000..0f7fec65edf
--- /dev/null
+++ b/cypress/e2e/files_external/StorageUtils.ts
@@ -0,0 +1,38 @@
+/**
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import type { User } from "@nextcloud/cypress"
+
+export type StorageConfig = {
+ [key: string]: string
+}
+
+export enum StorageBackend {
+ DAV = 'dav',
+ SMB = 'smb',
+ SFTP = 'sftp',
+}
+
+export enum AuthBackend {
+ GlobalAuth = 'password::global',
+ LoginCredentials = 'password::logincredentials',
+ Password = 'password::password',
+ SessionCredentials = 'password::sessioncredentials',
+ UserGlobalAuth = 'password::global::user',
+ UserProvided = 'password::userprovided',
+}
+
+/**
+ * Create a storage via occ
+ */
+export function createStorageWithConfig(mountPoint: string, storageBackend: StorageBackend, authBackend: AuthBackend, configs: StorageConfig, user?: User): Cypress.Chainable {
+ const configsFlag = Object.keys(configs).map(key => `--config "${key}=${configs[key]}"`).join(' ')
+ const userFlag = user ? `--user ${user.userId}` : ''
+
+ const command = `files_external:create "${mountPoint}" "${storageBackend}" "${authBackend}" ${configsFlag} ${userFlag}`
+
+ cy.log(`Creating storage with command: ${command}`)
+ return cy.runOccCommand(command)
+}
diff --git a/cypress/e2e/files_external/files-user-credentials.cy.ts b/cypress/e2e/files_external/files-user-credentials.cy.ts
new file mode 100644
index 00000000000..8434792f371
--- /dev/null
+++ b/cypress/e2e/files_external/files-user-credentials.cy.ts
@@ -0,0 +1,138 @@
+/**
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import { User } from '@nextcloud/cypress'
+import { AuthBackend, createStorageWithConfig, StorageBackend } from './StorageUtils'
+import { getActionEntryForFile, getRowForFile, navigateToFolder, triggerInlineActionForFile } from '../files/FilesUtils'
+
+import { ACTION_CREDENTIALS_EXTERNAL_STORAGE } from '../../../apps/files_external/src/actions/enterCredentialsAction'
+import { handlePasswordConfirmation } from '../settings/usersUtils'
+
+describe('Files user credentials', { testIsolation: true }, () => {
+ let user1: User
+ let user2: User
+ let storageUser: User
+
+ beforeEach(() => {
+ })
+
+ before(() => {
+ cy.runOccCommand('app:enable files_external')
+
+ // Create some users
+ cy.createRandomUser().then((user) => user1 = user)
+ cy.createRandomUser().then((user) => user2 = user)
+
+ // This user will hold the webdav storage
+ cy.createRandomUser().then((user) => {
+ storageUser = user
+ cy.uploadFile(user, 'image.jpg')
+ })
+ })
+
+ after(() => {
+ // Cleanup global storages
+ cy.runOccCommand(`files_external:list --output=json`).then(({stdout}) => {
+ const list = JSON.parse(stdout)
+ list.forEach((storage) => cy.runOccCommand(`files_external:delete --yes ${storage.mount_id}`), { failOnNonZeroExit: false })
+ })
+
+ cy.runOccCommand('app:disable files_external')
+ })
+
+ it('Create a user storage with user credentials', () => {
+ const url = Cypress.config('baseUrl') + '/remote.php/dav/files/' + storageUser.userId
+ createStorageWithConfig(storageUser.userId, StorageBackend.DAV, AuthBackend.UserProvided, { host: url.replace('index.php/', ''), 'secure': 'false' })
+
+ cy.login(user1)
+ cy.visit('/apps/files/extstoragemounts')
+ getRowForFile(storageUser.userId).should('be.visible')
+
+ cy.intercept('PUT', '**/apps/files_external/userglobalstorages/*').as('setCredentials')
+
+ triggerInlineActionForFile(storageUser.userId, ACTION_CREDENTIALS_EXTERNAL_STORAGE)
+
+ // See credentials dialog
+ const storageDialog = cy.findByRole('dialog', { name: 'Storage credentials' })
+ storageDialog.should('be.visible')
+ storageDialog.findByRole('textbox', { name: 'Login' }).type(storageUser.userId)
+ storageDialog.get('input[type="password"]').type(storageUser.password)
+ storageDialog.get('button').contains('Confirm').click()
+ storageDialog.should('not.exist')
+
+ // Storage dialog now closed, the user auth dialog should be visible
+ const authDialog = cy.findByRole('dialog', { name: 'Confirm your password' })
+ authDialog.should('be.visible')
+ handlePasswordConfirmation(user1.password)
+
+ // Wait for the credentials to be set
+ cy.wait('@setCredentials')
+
+ // Auth dialog should be closed and the set credentials button should be gone
+ authDialog.should('not.exist', { timeout: 2000 })
+ getActionEntryForFile(storageUser.userId, ACTION_CREDENTIALS_EXTERNAL_STORAGE).should('not.exist')
+
+ // Finally, the storage should be accessible
+ cy.visit('/apps/files')
+ navigateToFolder(storageUser.userId)
+ getRowForFile('image.jpg').should('be.visible')
+ })
+
+ it('Create a user storage with GLOBAL user credentials', () => {
+ const url = Cypress.config('baseUrl') + '/remote.php/dav/files/' + storageUser.userId
+ createStorageWithConfig('storage1', StorageBackend.DAV, AuthBackend.UserGlobalAuth, { host: url.replace('index.php/', ''), 'secure': 'false' })
+
+ cy.login(user2)
+ cy.visit('/apps/files/extstoragemounts')
+ getRowForFile('storage1').should('be.visible')
+
+ cy.intercept('PUT', '**/apps/files_external/userglobalstorages/*').as('setCredentials')
+
+ triggerInlineActionForFile('storage1', ACTION_CREDENTIALS_EXTERNAL_STORAGE)
+
+ // See credentials dialog
+ const storageDialog = cy.findByRole('dialog', { name: 'Storage credentials' })
+ storageDialog.should('be.visible')
+ storageDialog.findByRole('textbox', { name: 'Login' }).type(storageUser.userId)
+ storageDialog.get('input[type="password"]').type(storageUser.password)
+ storageDialog.get('button').contains('Confirm').click()
+ storageDialog.should('not.exist')
+
+ // Storage dialog now closed, the user auth dialog should be visible
+ const authDialog = cy.findByRole('dialog', { name: 'Confirm your password' })
+ authDialog.should('be.visible')
+ handlePasswordConfirmation(user2.password)
+
+ // Wait for the credentials to be set
+ cy.wait('@setCredentials')
+
+ // Auth dialog should be closed and the set credentials button should be gone
+ authDialog.should('not.exist', { timeout: 2000 })
+ getActionEntryForFile('storage1', ACTION_CREDENTIALS_EXTERNAL_STORAGE).should('not.exist')
+
+ // Finally, the storage should be accessible
+ cy.visit('/apps/files')
+ navigateToFolder('storage1')
+ getRowForFile('image.jpg').should('be.visible')
+ })
+
+ it('Create another user storage while reusing GLOBAL user credentials', () => {
+ const url = Cypress.config('baseUrl') + '/remote.php/dav/files/' + storageUser.userId
+ createStorageWithConfig('storage2', StorageBackend.DAV, AuthBackend.UserGlobalAuth, { host: url.replace('index.php/', ''), 'secure': 'false' })
+
+ cy.login(user2)
+ cy.visit('/apps/files/extstoragemounts')
+ getRowForFile('storage2').should('be.visible')
+
+ // Since we already have set the credentials, the action should not be present
+ getActionEntryForFile('storage1', ACTION_CREDENTIALS_EXTERNAL_STORAGE).should('not.exist')
+ getActionEntryForFile('storage2', ACTION_CREDENTIALS_EXTERNAL_STORAGE).should('not.exist')
+
+ // Finally, the storage should be accessible
+ cy.visit('/apps/files')
+ navigateToFolder('storage2')
+ getRowForFile('image.jpg').should('be.visible')
+ })
+})