Skip to content

Commit 8e6be5b

Browse files
authored
Merge pull request #60237 from nextcloud/backport/60165/stable32
[stable32] fix(core): prompt for password once when installing recommended apps
2 parents afddbd4 + 6f62920 commit 8e6be5b

9 files changed

Lines changed: 199 additions & 39 deletions

File tree

core/src/components/setup/RecommendedApps.vue

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@
5858
<script>
5959
import { t } from '@nextcloud/l10n'
6060
import { loadState } from '@nextcloud/initial-state'
61+
import { PwdConfirmationMode } from '@nextcloud/password-confirmation'
6162
import { generateUrl, imagePath } from '@nextcloud/router'
6263
import axios from '@nextcloud/axios'
63-
import pLimit from 'p-limit'
6464
import logger from '../../logger.js'
6565
6666
import NcButton from '@nextcloud/vue/components/NcButton'
@@ -140,35 +140,41 @@ export default {
140140
}
141141
},
142142
methods: {
143-
installApps() {
144-
this.installingApps = true
145-
146-
const limit = pLimit(1)
147-
const installing = this.recommendedApps
143+
async installApps() {
144+
const apps = this.recommendedApps
148145
.filter(app => !app.active && app.isCompatible && app.canInstall && app.isSelected)
149-
.map(app => limit(async () => {
150-
logger.info(`installing ${app.id}`)
151-
app.loading = true
152-
return axios.post(generateUrl('settings/apps/enable'), { appIds: [app.id], groups: [] })
153-
.catch(error => {
154-
logger.error(`could not install ${app.id}`, { error })
155-
app.isSelected = false
156-
app.installationError = true
157-
})
158-
.then(() => {
159-
logger.info(`installed ${app.id}`)
160-
app.loading = false
161-
app.active = true
162-
})
163-
}))
164-
logger.debug(`installing ${installing.length} recommended apps`)
165-
Promise.all(installing)
166-
.then(() => {
167-
logger.info('all recommended apps installed, redirecting …')
146+
if (apps.length === 0) {
147+
return
148+
}
168149
169-
window.location = this.defaultPageUrl
150+
this.installingApps = true
151+
apps.forEach(app => {
152+
app.loading = true
153+
})
154+
const appIds = apps.map(app => app.id)
155+
logger.debug(`installing ${apps.length} recommended apps`, { appIds })
156+
157+
try {
158+
await axios.post(
159+
generateUrl('settings/apps/enable'),
160+
{ appIds, groups: [] },
161+
{ confirmPassword: PwdConfirmationMode.Strict },
162+
)
163+
apps.forEach(app => {
164+
app.loading = false
165+
app.active = true
170166
})
171-
.catch(error => logger.error('could not install recommended apps', { error }))
167+
logger.info('all recommended apps installed, redirecting …')
168+
window.location = this.defaultPageUrl
169+
} catch (error) {
170+
logger.error('could not install recommended apps', { error })
171+
apps.forEach(app => {
172+
app.loading = false
173+
app.isSelected = false
174+
app.installationError = true
175+
})
176+
this.installingApps = false
177+
}
172178
},
173179
customIcon(appId) {
174180
if (!(appId in recommended) || !recommended[appId].icon) {

core/src/recommendedapps.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@
44
*/
55

66
import { getCSPNonce } from '@nextcloud/auth'
7+
import axios from '@nextcloud/axios'
78
import { translate as t } from '@nextcloud/l10n'
9+
import { addPasswordConfirmationInterceptors } from '@nextcloud/password-confirmation'
810
import Vue from 'vue'
911

1012
import logger from './logger.js'
1113
import RecommendedApps from './components/setup/RecommendedApps.vue'
1214

15+
addPasswordConfirmationInterceptors(axios)
16+
1317
// eslint-disable-next-line camelcase
1418
__webpack_nonce__ = getCSPNonce()
1519

cypress/e2e/core-utils.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,33 @@ export function getUnifiedSearchModal() {
1010
return cy.get('#unified-search')
1111
}
1212

13+
/**
14+
* Confirm the password-confirmation modal if it is visible.
15+
* Used by flows that hit endpoints requiring strict re-authentication.
16+
*
17+
* @param adminPassword Password to type into the confirmation dialog.
18+
*/
19+
export function handlePasswordConfirmation(adminPassword = 'admin') {
20+
const handleModal = (context: Cypress.Chainable) => {
21+
return context.contains('.modal-container', 'Authentication required')
22+
.if()
23+
.within(() => {
24+
cy.get('input[type="password"]')
25+
.type(adminPassword)
26+
cy.findByRole('button', { name: 'Confirm' })
27+
.click()
28+
})
29+
}
30+
31+
return cy.get('body')
32+
.if()
33+
.then(() => handleModal(cy.get('body')))
34+
.else()
35+
// Handle if inside a cy.within
36+
.root().closest('body')
37+
.then(($body) => handleModal(cy.wrap($body)))
38+
}
39+
1340
/**
1441
* Open the unified search modal
1542
*/

cypress/e2e/core/setup.ts

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6+
import { handlePasswordConfirmation } from '../core-utils.ts'
7+
8+
type RecommendedAppsMode = 'skip' | 'install-success' | 'install-failure'
9+
610
/**
711
* DO NOT RENAME THIS FILE to .cy.ts ⚠️
812
* This is not following the pattern of the other files in this folder
@@ -28,6 +32,22 @@ describe('Can install Nextcloud', { testIsolation: true, retries: 0 }, () => {
2832
sharedSetup()
2933
})
3034

35+
it('Sqlite - Install recommended apps (success)', () => {
36+
cy.visit('/')
37+
cy.get('[data-cy-setup-form]').should('be.visible')
38+
cy.get('[data-cy-setup-form-field="dbtype-sqlite"] input').check({ force: true })
39+
40+
sharedSetup('install-success')
41+
})
42+
43+
it('Sqlite - Install recommended apps (failure)', () => {
44+
cy.visit('/')
45+
cy.get('[data-cy-setup-form]').should('be.visible')
46+
cy.get('[data-cy-setup-form-field="dbtype-sqlite"] input').check({ force: true })
47+
48+
sharedSetup('install-failure')
49+
})
50+
3151
it('MySQL', () => {
3252
cy.visit('/')
3353
cy.get('[data-cy-setup-form]').should('be.visible')
@@ -109,8 +129,12 @@ describe('Can install Nextcloud', { testIsolation: true, retries: 0 }, () => {
109129

110130
/**
111131
* Shared admin setup function for the Nextcloud setup
132+
*
133+
* @param mode How to handle the recommended apps screen at the end of the
134+
* install assistant: skip it, exercise the install button with a
135+
* stubbed success response, or stub a failure response.
112136
*/
113-
function sharedSetup() {
137+
function sharedSetup(mode: RecommendedAppsMode = 'skip') {
114138
const randAdmin = 'admin-' + Math.random().toString(36).substring(2, 15)
115139

116140
// mock appstore
@@ -139,10 +163,41 @@ function sharedSetup() {
139163
.should('be.visible')
140164
})
141165

142-
// Skip the setup apps
143-
cy.get('[data-cy-setup-recommended-apps-skip]').click()
166+
if (mode === 'skip') {
167+
// Skip the setup apps
168+
cy.get('[data-cy-setup-recommended-apps-skip]').click()
144169

145-
// Go to files
146-
cy.visit('/apps/files/')
147-
cy.get('[data-cy-files-content]').should('be.visible')
170+
// Go to files
171+
cy.visit('/apps/files/')
172+
cy.get('[data-cy-files-content]').should('be.visible')
173+
return
174+
}
175+
176+
// Stub the bulk enable endpoint so we exercise the frontend flow without
177+
// hitting the real app store.
178+
cy.intercept('POST', '**/settings/apps/enable', mode === 'install-success'
179+
? { statusCode: 200, body: { data: { update_required: false } } }
180+
: { statusCode: 500, body: { data: { message: 'Forced failure' } } }).as('enableApps')
181+
182+
cy.get('[data-cy-setup-recommended-apps-install]').click()
183+
184+
// The strict password-confirmation dialog must appear and must result in a
185+
// Basic auth header on the enable request.
186+
cy.findByRole('dialog', { name: 'Authentication required' })
187+
.should('be.visible')
188+
handlePasswordConfirmation(randAdmin)
189+
cy.wait('@enableApps')
190+
.its('request.headers.authorization')
191+
.should('match', /^Basic /)
192+
193+
if (mode === 'install-success') {
194+
// Frontend redirects via window.location to the default page.
195+
cy.location('pathname', { timeout: 10000 })
196+
.should('not.include', '/core/apps/recommended')
197+
} else {
198+
// Stay on the recommended-apps page and surface the per-app error state.
199+
cy.location('pathname').should('include', '/core/apps/recommended')
200+
cy.get('[data-cy-setup-recommended-apps]')
201+
.should('contain.text', 'App download or installation failed')
202+
}
148203
}

dist/core-common.js

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

dist/core-common.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/core-recommendedapps.js

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

dist/core-recommendedapps.js.license

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
44
SPDX-License-Identifier: BSD-3-Clause
55
SPDX-License-Identifier: AGPL-3.0-or-later
66
SPDX-License-Identifier: (MPL-2.0 OR Apache-2.0)
7+
SPDX-FileCopyrightText: webfansplz
8+
SPDX-FileCopyrightText: perfect-debounce developers
9+
SPDX-FileCopyrightText: hookable developers
710
SPDX-FileCopyrightText: escape-html developers
11+
SPDX-FileCopyrightText: atomiks
812
SPDX-FileCopyrightText: Tobias Koppers @sokra
913
SPDX-FileCopyrightText: T. Jameson Little <t.jameson.little@gmail.com>
1014
SPDX-FileCopyrightText: Roman Shtylman <shtylman@gmail.com>
@@ -13,10 +17,20 @@ SPDX-FileCopyrightText: Matt Zabriskie
1317
SPDX-FileCopyrightText: GitHub Inc.
1418
SPDX-FileCopyrightText: Feross Aboukhadijeh
1519
SPDX-FileCopyrightText: Evan You
20+
SPDX-FileCopyrightText: Eduardo San Martin Morote
1621
SPDX-FileCopyrightText: Dr.-Ing. Mario Heiderich, Cure53 <mario@cure53.de> (https://cure53.de/)
22+
SPDX-FileCopyrightText: David Clark
23+
SPDX-FileCopyrightText: Anthony Fu <https://github.com/antfu>
24+
SPDX-FileCopyrightText: Anthony Fu <anthonyfu117@hotmail.com>
1725

1826

1927
This file is generated from multiple sources. Included packages:
28+
- @floating-ui/core
29+
- version: 1.7.5
30+
- license: MIT
31+
- @floating-ui/utils
32+
- version: 0.2.11
33+
- license: MIT
2034
- @nextcloud/auth
2135
- version: 2.6.0
2236
- license: GPL-3.0-or-later
@@ -26,6 +40,9 @@ This file is generated from multiple sources. Included packages:
2640
- @nextcloud/browser-storage
2741
- version: 0.5.0
2842
- license: GPL-3.0-or-later
43+
- @nextcloud/capabilities
44+
- version: 1.2.1
45+
- license: GPL-3.0-or-later
2946
- semver
3047
- version: 7.7.2
3148
- license: ISC
@@ -41,6 +58,48 @@ This file is generated from multiple sources. Included packages:
4158
- @nextcloud/logger
4259
- version: 3.0.3
4360
- license: GPL-3.0-or-later
61+
- @nextcloud/vue
62+
- version: 9.6.0
63+
- license: AGPL-3.0-or-later
64+
- @vue/devtools-shared
65+
- version: 8.1.0
66+
- license: MIT
67+
- @vue/reactivity
68+
- version: 3.5.30
69+
- license: MIT
70+
- @vue/runtime-core
71+
- version: 3.5.30
72+
- license: MIT
73+
- @vue/runtime-dom
74+
- version: 3.5.30
75+
- license: MIT
76+
- @vue/shared
77+
- version: 3.5.30
78+
- license: MIT
79+
- @vueuse/core
80+
- version: 14.2.1
81+
- license: MIT
82+
- @vueuse/shared
83+
- version: 14.2.1
84+
- license: MIT
85+
- perfect-debounce
86+
- version: 2.1.0
87+
- license: MIT
88+
- @vue/devtools-api
89+
- version: 8.1.0
90+
- license: MIT
91+
- @vue/devtools-kit
92+
- version: 8.1.0
93+
- license: MIT
94+
- vue-router
95+
- version: 5.0.3
96+
- license: MIT
97+
- vue
98+
- version: 3.5.30
99+
- license: MIT
100+
- @nextcloud/password-confirmation
101+
- version: 6.1.0
102+
- license: MIT
44103
- @nextcloud/router
45104
- version: 3.1.0
46105
- license: GPL-3.0-or-later
@@ -53,6 +112,9 @@ This file is generated from multiple sources. Included packages:
53112
- base64-js
54113
- version: 1.5.1
55114
- license: MIT
115+
- birpc
116+
- version: 2.9.0
117+
- license: MIT
56118
- css-loader
57119
- version: 7.1.2
58120
- license: MIT
@@ -62,6 +124,9 @@ This file is generated from multiple sources. Included packages:
62124
- escape-html
63125
- version: 1.0.3
64126
- license: MIT
127+
- hookable
128+
- version: 5.5.3
129+
- license: MIT
65130
- ieee754
66131
- version: 1.2.1
67132
- license: BSD-3-Clause
@@ -74,6 +139,9 @@ This file is generated from multiple sources. Included packages:
74139
- style-loader
75140
- version: 4.0.0
76141
- license: MIT
142+
- tabbable
143+
- version: 6.4.0
144+
- license: MIT
77145
- vue-loader
78146
- version: 15.11.1
79147
- license: MIT

dist/core-recommendedapps.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)