diff options
author | Julius Härtl <jus@bitgrid.net> | 2022-03-25 18:48:46 +0300 |
---|---|---|
committer | Julius Härtl <jus@bitgrid.net> | 2022-04-06 18:54:00 +0300 |
commit | 8620100b19cc34f79e048c5b3312070381cdeadd (patch) | |
tree | 3622defcca09cd8152ac410fded2a44e5de09b97 | |
parent | 522cb7bba6687b0b26d33b5c7453eebd909192ff (diff) |
Add more helpful setup checksenh/setup-checks
Signed-off-by: Julius Härtl <jus@bitgrid.net>
-rw-r--r-- | lib/Controller/SettingsController.php | 5 | ||||
-rw-r--r-- | lib/Service/InitialStateService.php | 16 | ||||
-rw-r--r-- | lib/Settings/Admin.php | 10 | ||||
-rw-r--r-- | src/components/AdminSettings.vue | 111 | ||||
-rw-r--r-- | src/components/SetupHints.vue | 89 | ||||
-rw-r--r-- | src/helpers/setupcheck.js | 39 |
6 files changed, 203 insertions, 67 deletions
diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index 6107880b..9ba73e94 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -204,7 +204,10 @@ class SettingsController extends Controller{ $response = [ 'status' => 'success', - 'data' => ['message' => $message] + 'data' => [ + 'discovery' => $this->wopiParser->getUrlSrc('application/vnd.oasis.opendocument.text')['urlsrc'], + 'message' => $message + ] ]; return new JSONResponse($response); diff --git a/lib/Service/InitialStateService.php b/lib/Service/InitialStateService.php index ff85ec4e..db28352c 100644 --- a/lib/Service/InitialStateService.php +++ b/lib/Service/InitialStateService.php @@ -27,6 +27,7 @@ namespace OCA\Richdocuments\Service; use OCA\Richdocuments\AppInfo\Application; use OCA\Richdocuments\Db\Wopi; +use OCA\Richdocuments\WOPI\Parser; use OCP\AppFramework\Services\IInitialState; use OCP\IConfig; @@ -38,6 +39,9 @@ class InitialStateService { /** @var CapabilitiesService */ private $capabilitiesService; + /** @var Parser */ + private $parser; + /** @var IConfig */ private $config; @@ -47,10 +51,12 @@ class InitialStateService { public function __construct( IInitialState $initialState, CapabilitiesService $capabilitiesService, + Parser $parser, IConfig $config ) { $this->initialState = $initialState; $this->capabilitiesService = $capabilitiesService; + $this->parser = $parser; $this->config = $config; } @@ -82,4 +88,14 @@ class InitialStateService { \OC::$server->getURLGenerator()->getAbsoluteURL(\OC::$server->getThemingDefaults()->getLogo()) : false)); } + + public function provideSettings(): void { + $sampleBrowserUrl = null; + try { + $sampleBrowserUrl = $this->parser->getUrlSrc('application/vnd.oasis.opendocument.text')['urlsrc'] ?? null; + } catch (\Throwable $e) { + } + $this->initialState->provideInitialState('discovery', $sampleBrowserUrl); + + } } diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php index e67fe5f3..bc65547c 100644 --- a/lib/Settings/Admin.php +++ b/lib/Settings/Admin.php @@ -28,6 +28,8 @@ use OCA\Richdocuments\Service\CapabilitiesService; use OCA\Richdocuments\Service\DemoService; use OCA\Richdocuments\Service\InitialStateService; use OCA\Richdocuments\TemplateManager; +use OCP\AppFramework\Http\ContentSecurityPolicy; +use OCP\AppFramework\Http\EmptyContentSecurityPolicy; use OCP\AppFramework\Http\TemplateResponse; use OCP\IConfig; use OCP\Settings\ISettings; @@ -70,7 +72,8 @@ class Admin implements ISettings { public function getForm() { $this->initialState->provideCapabilities(); - return new TemplateResponse( + $this->initialState->provideSettings(); + $response = new TemplateResponse( 'richdocuments', 'admin', [ @@ -94,6 +97,11 @@ class Admin implements ISettings { ], 'blank' ); + $csp = new EmptyContentSecurityPolicy(); + // FIXME: This does not seem to get applied + $csp->addAllowedConnectDomain('*'); + $response->setContentSecurityPolicy($csp); + return $response; } public function getSection() { diff --git a/src/components/AdminSettings.vue b/src/components/AdminSettings.vue index 88eca9a9..5ac5a8ce 100644 --- a/src/components/AdminSettings.vue +++ b/src/components/AdminSettings.vue @@ -31,30 +31,10 @@ {{ t('richdocuments', 'Collabora Online is a powerful LibreOffice-based online office suite with collaborative editing, which supports all major documents, spreadsheet and presentation file formats and works together with all modern browsers.') }} </p> - <div v-if="settings.wopi_url && settings.wopi_url !== ''"> - <div v-if="serverError == 2 && isNginx && serverMode === 'builtin'" id="security-warning-state-failure"> - <span class="icon icon-close-white" /><span class="message">{{ t('richdocuments', 'Could not establish connection to the Collabora Online server. This might be due to a missing configuration of your web server. For more information, please visit: ') }}<a title="Connecting Collabora Online Single Click with Nginx" - href="https://www.collaboraoffice.com/online/connecting-collabora-online-single-click-with-nginx/" - target="_blank" - rel="noopener" - class="external">{{ t('richdocuments', 'Connecting Collabora Online Single Click with Nginx') }}</a></span> - </div> - <div v-else-if="serverError == 2" id="security-warning-state-failure"> - <span class="icon icon-close-white" /><span class="message">{{ t('richdocuments', 'Could not establish connection to the Collabora Online server.') }}</span> - </div> - <div v-else-if="serverError == 1" id="security-warning-state-failure"> - <span class="icon icon-loading" /><span class="message">{{ t('richdocuments', 'Setting up a new server') }}</span> - </div> - <div v-else-if="serverError == 3" id="security-warning-state-failure"> - <span class="icon icon-close-white" /><span class="message">{{ t('richdocuments', 'Collabora Online should use the same protocol as the server installation.') }}</span> - </div> - <div v-else id="security-warning-state-ok"> - <span class="icon icon-checkmark-white" /><span class="message">{{ t('richdocuments', 'Collabora Online server is reachable.') }}</span> - </div> - </div> - <div v-else id="security-warning-state-warning"> - <span class="icon icon-error-white" /><span class="message">{{ t('richdocuments', 'Please configure a Collabora Online server to start editing documents') }}</span> - </div> + <SetupHints :settings="settings" + :server-error="serverError" + :is-nginx="isNginx" + :server-mode="serverMode" /> <fieldset> <div> @@ -363,14 +343,13 @@ import SettingsExternalApps from './SettingsExternalApps' import '@nextcloud/dialogs/styles/toast.scss' -const SERVER_STATE_OK = 0 -const SERVER_STATE_LOADING = 1 -const SERVER_STATE_CONNECTION_ERROR = 2 -const PROTOCOL_MISMATCH = 3 +import SetupHints from './SetupHints' +import { SETUP_HINTS, SERVER_MODE } from '../helpers/setupcheck' export default { name: 'AdminSettings', components: { + SetupHints, SettingsCheckbox, SettingsInputText, SettingsSelectTag, @@ -391,7 +370,7 @@ export default { hasNextcloudBranding: loadState('richdocuments', 'hasNextcloudBranding', true), serverMode: '', - serverError: Object.values(OC.getCapabilities().richdocuments.collabora).length > 0 ? SERVER_STATE_OK : SERVER_STATE_CONNECTION_ERROR, + serverError: SETUP_HINTS.SERVER_STATE_LOADING, hostErrors: [window.location.host === 'localhost' || window.location.host === '127.0.0.1', window.location.protocol !== 'https:', false], demoServers: null, CODEInstalled: 'richdocumentscode' in OC.appswebroots, @@ -433,7 +412,7 @@ export default { return t('richdocuments', 'Contact {0} to get an own installation.', [this.settings.demoUrl.provider_name]) }, isSetup() { - return this.serverError === SERVER_STATE_OK + return this.serverError === SETUP_HINTS.SERVER_STATE_OK }, isOoxml() { return this.settings.doc_format === 'ooxml' @@ -442,16 +421,6 @@ export default { return this.hostErrors.some(x => x) }, }, - watch: { - 'settings.wopi_url'(newVal, oldVal) { - if (newVal !== oldVal) { - const protocol = this.checkUrlProtocol(newVal) - const nextcloudProtocol = this.checkUrlProtocol(window.location.href) - if (protocol !== nextcloudProtocol) this.serverError = PROTOCOL_MISMATCH - else this.serverError = Object.values(OC.getCapabilities().richdocuments.collabora).length > 0 ? SERVER_STATE_OK : SERVER_STATE_CONNECTION_ERROR - } - }, - }, beforeMount() { for (const key in this.initial.settings) { if (!Object.prototype.hasOwnProperty.call(this.initial.settings, key)) { @@ -468,7 +437,7 @@ export default { } Vue.set(this.settings, 'data', this.initial.settings) if (this.settings.wopi_url === '') { - this.serverError = SERVER_STATE_CONNECTION_ERROR + this.serverError = SETUP_HINTS.SERVER_STATE_NOT_SETUP } Vue.set(this.settings, 'edit_groups', this.settings.edit_groups ? this.settings.edit_groups.split('|') : null) Vue.set(this.settings, 'use_groups', this.settings.use_groups ? this.settings.use_groups.split('|') : null) @@ -494,6 +463,7 @@ export default { this.CODEAppID = 'richdocumentscode_arm64' } this.checkIfDemoServerIsActive() + this.setupCheck(this.initial.wopi_url, loadState('richdocuments', 'discovery', null)) }, methods: { async fetchDemoServers() { @@ -565,22 +535,50 @@ export default { }) }, async updateServer() { - this.serverError = SERVER_STATE_LOADING + this.serverError = SETUP_HINTS.SERVER_STATE_UPDATING try { - await this.updateSettings({ + const { data } = await this.updateSettings({ wopi_url: this.settings.wopi_url, disable_certificate_verification: this.settings.disable_certificate_verification, }) - this.serverError = SERVER_STATE_OK + this.setupCheck(this.settings.wopi_url, data.data.discovery) } catch (e) { console.error(e) - this.serverError = SERVER_STATE_CONNECTION_ERROR + this.serverError = SETUP_HINTS.SERVER_STATE_CONNECTION_ERROR if (e.response.data.hint === 'missing_capabilities') { + this.serverError = SETUP_HINTS.SERVER_STATE_CONNECTION_ERROR_CAPABILITIES showWarning('Could not connect to the /hosting/capabilities endpoint. Please check if your webserver configuration is up to date.') } } this.checkIfDemoServerIsActive() }, + async setupCheck(wopiUrl, browserUrl) { + if (!wopiUrl) { + this.serverError = SETUP_HINTS.SERVER_STATE_NOT_SETUP + return + } + console.debug('setupCheck', wopiUrl, browserUrl) + // Validate that the Nextcloud server could obtain a proper url + if (!browserUrl) { + this.serverError = SETUP_HINTS.SERVER_STATE_CONNECTION_ERROR + return + } + // Check if discovery returns the proper protocol + if (browserUrl.slice(0, 5) !== (location.protocol + '//' + location.host).slice(0, 5)) { + this.serverError = SETUP_HINTS.PROTOCOL_MISMATCH + return + } + + // Check browser reachability of Collabora Online + try { + await fetch(browserUrl, { mode: 'no-cors' }) + } catch (e) { + this.serverError = SETUP_HINTS.SERVER_STATE_CLIENT_CONNECTION_ERROR + return + } + + this.serverError = SETUP_HINTS.SERVER_STATE_OK + }, async updateSettings(data) { this.updating = true try { @@ -607,13 +605,13 @@ export default { this.settings.demoUrl = this.demoServers ? this.demoServers.find((server) => server.demo_url === this.settings.wopi_url) : null this.settings.CODEUrl = this.CODEInstalled ? window.location.protocol + '//' + window.location.host + generateFilePath(this.CODEAppID, '', '') + 'proxy.php?req=' : null if (this.settings.wopi_url && this.settings.wopi_url !== '') { - this.serverMode = 'custom' + this.serverMode = SERVER_MODE.CUSTOM } if (this.settings.demoUrl) { - this.serverMode = 'demo' + this.serverMode = SERVER_MODE.DEMO this.approvedDemoModal = true } else if (this.settings.CODEUrl && this.settings.CODEUrl === this.settings.wopi_url) { - this.serverMode = 'builtin' + this.serverMode = SERVER_MODE.BUILTIN } }, demoServerLabel(server) { @@ -629,16 +627,6 @@ export default { this.settings.disable_certificate_verification = false await this.updateServer() }, - checkUrlProtocol(string) { - let url - try { - url = new URL(string) - } catch (_) { - return false - } - - return url.protocol - }, }, } </script> @@ -672,13 +660,6 @@ export default { border-bottom: 1px solid var(--color-border); } - #security-warning-state-failure, - #security-warning-state-warning, - #security-warning-state-ok { - margin-top: 10px; - margin-bottom: 20px; - } - .option-inline { margin-left: 25px; &:not(.multiselect) { diff --git a/src/components/SetupHints.vue b/src/components/SetupHints.vue new file mode 100644 index 00000000..4cd85f36 --- /dev/null +++ b/src/components/SetupHints.vue @@ -0,0 +1,89 @@ +<template> + <div> + <div v-if="isState(SETUP_HINTS.SERVER_STATE_NOT_SETUP)" id="security-warning-state-warning"> + <span class="icon icon-error-white" /><span class="message">{{ t('richdocuments', 'Please configure a Collabora Online server to start editing documents') }}</span> + </div> + + <div v-else + :id="failureId"> + <div v-if="isState(SETUP_HINTS.SERVER_STATE_LOADING)"> + <span class="icon icon-loading" /><span class="message">{{ t('richdocuments', 'Checking the existing configuration') }}</span> + </div> + + <div v-else-if="isState(SETUP_HINTS.SERVER_STATE_UPDATING)"> + <span class="icon icon-loading" /><span class="message">{{ t('richdocuments', 'Setting new server URL and checking the configuration') }}</span> + </div> + + <div v-else-if="isState(SETUP_HINTS.SERVER_STATE_CONNECTION_ERROR)"> + <span class="icon icon-close-white" /> + <span class="message"> + {{ t('richdocuments', 'Could not establish connection to the Collabora Online server.') }} + <span v-if="isNginx && serverMode === SERVER_MODE.BUILTIN"> + {{ t('richdocuments', 'This might be due to a missing configuration of your web server. For more information, please visit: ') }} + <a title="Connecting Collabora Online Single Click with Nginx" class="external">{{ t('richdocuments', 'Connecting Collabora Online Single Click with Nginx') }}</a></span> + </span> + </div> + + <div v-else-if="isState(SETUP_HINTS.PROTOCOL_MISMATCH)"> + <span class="icon icon-close-white" /><span class="message">{{ t('richdocuments', 'Collabora Online should use the same protocol as the server installation.') }}</span> + </div> + <div v-else-if="isState(SETUP_HINTS.SERVER_STATE_CLIENT_CONNECTION_ERROR)"> + <span class="icon icon-close-white" /><span class="message">{{ t('richdocuments', 'Collabora Online is not reachable from the browser.') }}</span> + </div> + + <div v-else> + <span class="icon icon-checkmark-white" /><span class="message">{{ t('richdocuments', 'Collabora Online server is reachable.') }}</span> + </div> + </div> + </div> +</template> + +<script> + +import { SETUP_HINTS, SERVER_MODE } from '../helpers/setupcheck' + +export default { + name: 'SetupHints', + props: { + settings: { + type: Object, + }, + serverError: { + type: Number, + }, + serverMode: { + type: String, + }, + isNginx: { + type: Boolean, + }, + }, + data() { + return { + SETUP_HINTS, + SERVER_MODE, + } + }, + computed: { + isState() { + return (hint) => this.serverError === hint + }, + failureId() { + if (this.isState(SETUP_HINTS.SERVER_STATE_OK)) { + return 'security-warning-state-ok' + } + + return 'security-warning-state-failure' + }, + }, +} +</script> + +<style scoped> +#security-warning-state-failure, +#security-warning-state-warning, +#security-warning-state-ok { + margin-top: 10px; + margin-bottom: 20px; +} +</style> diff --git a/src/helpers/setupcheck.js b/src/helpers/setupcheck.js new file mode 100644 index 00000000..4f2e5ff7 --- /dev/null +++ b/src/helpers/setupcheck.js @@ -0,0 +1,39 @@ +/* + * @copyright Copyright (c) 2022 Julius Härtl <jus@bitgrid.net> + * + * @author Julius Härtl <jus@bitgrid.net> + * + * @license GNU AGPL version 3 or any later version + * + * 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/>. + * + */ + +export const SETUP_HINTS = { + SERVER_STATE_NOT_SETUP: -3, + SERVER_STATE_LOADING: -2, + SERVER_STATE_UPDATING: -1, + SERVER_STATE_OK: 0, + + SERVER_STATE_CONNECTION_ERROR: 2, + SERVER_STATE_CLIENT_CONNECTION_ERROR: 3, + PROTOCOL_MISMATCH: 4, + SERVER_STATE_CONNECTION_ERROR_CAPABILITIES: 5, +} + +export const SERVER_MODE = { + CUSTOM: 'custom', + BUILTIN: 'builtin', + DEMO: 'demo', +} |