From 709f5a9dd9929f071b3e89b872ea68adbf25c066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Fri, 25 Mar 2022 16:48:46 +0100 Subject: [PATCH] Add more helpful setup checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl wip: tests Signed-off-by: Julius Härtl wip: clear Signed-off-by: Julius Härtl followup Signed-off-by: Julius Härtl --- appinfo/info.xml | 2 +- composer/composer/autoload_classmap.php | 6 +- composer/composer/autoload_static.php | 6 +- cypress/e2e/settings.spec.js | 33 +++- lib/AppInfo/Application.php | 4 +- ...Capabilities.php => CheckConnectivity.php} | 20 +-- lib/Capabilities.php | 1 - lib/Command/ActivateConfig.php | 42 ++--- lib/Controller/SettingsController.php | 127 ++++----------- .../AddContentSecurityPolicyListener.php | 9 ++ lib/Service/BaseRemoteService.php | 93 +++++++++++ lib/Service/CapabilitiesService.php | 126 ++++++--------- lib/Service/ConnectivityService.php | 107 +++++++++++++ .../DiscoveryService.php} | 91 +++-------- lib/Service/InitialStateService.php | 13 ++ lib/Settings/Admin.php | 1 + lib/WOPI/Parser.php | 33 ++-- src/components/AdminSettings.vue | 150 +++++++----------- src/components/SetupHints.vue | 109 +++++++++++++ src/helpers/setupcheck.js | 39 +++++ .../AddContentSecurityPolicyListenerTest.php | 4 + 21 files changed, 621 insertions(+), 395 deletions(-) rename lib/Backgroundjobs/{ObtainCapabilities.php => CheckConnectivity.php} (72%) create mode 100644 lib/Service/BaseRemoteService.php create mode 100644 lib/Service/ConnectivityService.php rename lib/{WOPI/DiscoveryManager.php => Service/DiscoveryService.php} (50%) create mode 100644 src/components/SetupHints.vue create mode 100644 src/helpers/setupcheck.js diff --git a/appinfo/info.xml b/appinfo/info.xml index 3b2b7fa67b..a895755ca6 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -28,7 +28,7 @@ You can also edit your documents off-line with the Collabora Office app from the - OCA\Richdocuments\Backgroundjobs\ObtainCapabilities + OCA\Richdocuments\Backgroundjobs\CheckConnectivity OCA\Richdocuments\Backgroundjobs\Cleanup diff --git a/composer/composer/autoload_classmap.php b/composer/composer/autoload_classmap.php index 2aa64bd592..b5dc25f40c 100644 --- a/composer/composer/autoload_classmap.php +++ b/composer/composer/autoload_classmap.php @@ -9,8 +9,8 @@ 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'OCA\\Richdocuments\\AppConfig' => $baseDir . '/../lib/AppConfig.php', 'OCA\\Richdocuments\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php', + 'OCA\\Richdocuments\\Backgroundjobs\\CheckConnectivity' => $baseDir . '/../lib/Backgroundjobs/CheckConnectivity.php', 'OCA\\Richdocuments\\Backgroundjobs\\Cleanup' => $baseDir . '/../lib/Backgroundjobs/Cleanup.php', - 'OCA\\Richdocuments\\Backgroundjobs\\ObtainCapabilities' => $baseDir . '/../lib/Backgroundjobs/ObtainCapabilities.php', 'OCA\\Richdocuments\\Capabilities' => $baseDir . '/../lib/Capabilities.php', 'OCA\\Richdocuments\\Command\\ActivateConfig' => $baseDir . '/../lib/Command/ActivateConfig.php', 'OCA\\Richdocuments\\Command\\ConvertToBigInt' => $baseDir . '/../lib/Command/ConvertToBigInt.php', @@ -59,8 +59,11 @@ 'OCA\\Richdocuments\\Preview\\OpenDocument' => $baseDir . '/../lib/Preview/OpenDocument.php', 'OCA\\Richdocuments\\Preview\\Pdf' => $baseDir . '/../lib/Preview/Pdf.php', 'OCA\\Richdocuments\\Reference\\OfficeTargetReferenceProvider' => $baseDir . '/../lib/Reference/OfficeTargetReferenceProvider.php', + 'OCA\\Richdocuments\\Service\\BaseRemoteService' => $baseDir . '/../lib/Service/BaseRemoteService.php', 'OCA\\Richdocuments\\Service\\CapabilitiesService' => $baseDir . '/../lib/Service/CapabilitiesService.php', + 'OCA\\Richdocuments\\Service\\ConnectivityService' => $baseDir . '/../lib/Service/ConnectivityService.php', 'OCA\\Richdocuments\\Service\\DemoService' => $baseDir . '/../lib/Service/DemoService.php', + 'OCA\\Richdocuments\\Service\\DiscoveryService' => $baseDir . '/../lib/Service/DiscoveryService.php', 'OCA\\Richdocuments\\Service\\FederationService' => $baseDir . '/../lib/Service/FederationService.php', 'OCA\\Richdocuments\\Service\\FileTargetService' => $baseDir . '/../lib/Service/FileTargetService.php', 'OCA\\Richdocuments\\Service\\FontService' => $baseDir . '/../lib/Service/FontService.php', @@ -74,6 +77,5 @@ 'OCA\\Richdocuments\\Template\\CollaboraTemplateProvider' => $baseDir . '/../lib/Template/CollaboraTemplateProvider.php', 'OCA\\Richdocuments\\TokenManager' => $baseDir . '/../lib/TokenManager.php', 'OCA\\Richdocuments\\UploadException' => $baseDir . '/../lib/UploadException.php', - 'OCA\\Richdocuments\\WOPI\\DiscoveryManager' => $baseDir . '/../lib/WOPI/DiscoveryManager.php', 'OCA\\Richdocuments\\WOPI\\Parser' => $baseDir . '/../lib/WOPI/Parser.php', ); diff --git a/composer/composer/autoload_static.php b/composer/composer/autoload_static.php index 7a0f9877b8..3a8f801489 100644 --- a/composer/composer/autoload_static.php +++ b/composer/composer/autoload_static.php @@ -24,8 +24,8 @@ class ComposerStaticInitRichdocuments 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'OCA\\Richdocuments\\AppConfig' => __DIR__ . '/..' . '/../lib/AppConfig.php', 'OCA\\Richdocuments\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php', + 'OCA\\Richdocuments\\Backgroundjobs\\CheckConnectivity' => __DIR__ . '/..' . '/../lib/Backgroundjobs/CheckConnectivity.php', 'OCA\\Richdocuments\\Backgroundjobs\\Cleanup' => __DIR__ . '/..' . '/../lib/Backgroundjobs/Cleanup.php', - 'OCA\\Richdocuments\\Backgroundjobs\\ObtainCapabilities' => __DIR__ . '/..' . '/../lib/Backgroundjobs/ObtainCapabilities.php', 'OCA\\Richdocuments\\Capabilities' => __DIR__ . '/..' . '/../lib/Capabilities.php', 'OCA\\Richdocuments\\Command\\ActivateConfig' => __DIR__ . '/..' . '/../lib/Command/ActivateConfig.php', 'OCA\\Richdocuments\\Command\\ConvertToBigInt' => __DIR__ . '/..' . '/../lib/Command/ConvertToBigInt.php', @@ -74,8 +74,11 @@ class ComposerStaticInitRichdocuments 'OCA\\Richdocuments\\Preview\\OpenDocument' => __DIR__ . '/..' . '/../lib/Preview/OpenDocument.php', 'OCA\\Richdocuments\\Preview\\Pdf' => __DIR__ . '/..' . '/../lib/Preview/Pdf.php', 'OCA\\Richdocuments\\Reference\\OfficeTargetReferenceProvider' => __DIR__ . '/..' . '/../lib/Reference/OfficeTargetReferenceProvider.php', + 'OCA\\Richdocuments\\Service\\BaseRemoteService' => __DIR__ . '/..' . '/../lib/Service/BaseRemoteService.php', 'OCA\\Richdocuments\\Service\\CapabilitiesService' => __DIR__ . '/..' . '/../lib/Service/CapabilitiesService.php', + 'OCA\\Richdocuments\\Service\\ConnectivityService' => __DIR__ . '/..' . '/../lib/Service/ConnectivityService.php', 'OCA\\Richdocuments\\Service\\DemoService' => __DIR__ . '/..' . '/../lib/Service/DemoService.php', + 'OCA\\Richdocuments\\Service\\DiscoveryService' => __DIR__ . '/..' . '/../lib/Service/DiscoveryService.php', 'OCA\\Richdocuments\\Service\\FederationService' => __DIR__ . '/..' . '/../lib/Service/FederationService.php', 'OCA\\Richdocuments\\Service\\FileTargetService' => __DIR__ . '/..' . '/../lib/Service/FileTargetService.php', 'OCA\\Richdocuments\\Service\\FontService' => __DIR__ . '/..' . '/../lib/Service/FontService.php', @@ -89,7 +92,6 @@ class ComposerStaticInitRichdocuments 'OCA\\Richdocuments\\Template\\CollaboraTemplateProvider' => __DIR__ . '/..' . '/../lib/Template/CollaboraTemplateProvider.php', 'OCA\\Richdocuments\\TokenManager' => __DIR__ . '/..' . '/../lib/TokenManager.php', 'OCA\\Richdocuments\\UploadException' => __DIR__ . '/..' . '/../lib/UploadException.php', - 'OCA\\Richdocuments\\WOPI\\DiscoveryManager' => __DIR__ . '/..' . '/../lib/WOPI/DiscoveryManager.php', 'OCA\\Richdocuments\\WOPI\\Parser' => __DIR__ . '/..' . '/../lib/WOPI/Parser.php', ); diff --git a/cypress/e2e/settings.spec.js b/cypress/e2e/settings.spec.js index 3adefbb183..61fb5c1074 100644 --- a/cypress/e2e/settings.spec.js +++ b/cypress/e2e/settings.spec.js @@ -24,6 +24,20 @@ const collaboraUrl = Cypress.env('collaboraUrl') describe('Office admin settings', function() { + let randUser + + before(function() { + cy.createRandomUser().then(user => { + randUser = user + cy.login(user) + cy.uploadFile(user, 'document.odt', 'application/vnd.oasis.opendocument.text', '/document.odt') + }) + }) + + beforeEach(function() { + cy.login(randUser) + }) + beforeEach(function() { cy.login(new User('admin', 'admin')) cy.visit('/settings/admin/richdocuments') @@ -31,6 +45,10 @@ describe('Office admin settings', function() { method: 'POST', url: '/index.php/apps/richdocuments/ajax/admin.php', }).as('updateSettings') + cy.intercept({ + method: 'GET', + url: collaboraUrl + '/**', + }).as('officeServer') }) it('Error for invalid url', function() { @@ -45,11 +63,21 @@ describe('Office admin settings', function() { .clear() .type((usesHttps ? 'https' : 'http') + '://invalid.example.com{enter}') cy.wait('@updateSettings').its('response.statusCode').should('equal', 500) - cy.get('#security-warning-state-failure .message') + cy.get('.notecard').first() .scrollIntoView() .should('be.visible') .should('contain.text', 'Could not establish connection to the Collabora Online server.') cy.screenshot() + + cy.login(randUser) + cy.visit('/apps/files', { + onBeforeLoad(win) { + cy.spy(win, 'postMessage').as('postMessage') + }, + }) + cy.openFile('document.odt') + cy.waitForViewer() + cy.waitForCollabora() }) it('Opens settings and configure a valid url', function() { @@ -64,7 +92,8 @@ describe('Office admin settings', function() { .clear() .type(collaboraUrl + '{enter}') cy.wait('@updateSettings').its('response.statusCode').should('equal', 200) - cy.get('#security-warning-state-ok .message') + cy.wait('@officeServer').its('response.statusCode').should('equal', 200) + cy.get('.notecard').first() .scrollIntoView() .should('be.visible') .should('contain.text', 'Collabora Online server is reachable.') diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 9e31e7a50e..c1bef6483e 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -44,8 +44,8 @@ use OCA\Richdocuments\Preview\Pdf; use OCA\Richdocuments\Reference\OfficeTargetReferenceProvider; use OCA\Richdocuments\Service\CapabilitiesService; +use OCA\Richdocuments\Service\DiscoveryService; use OCA\Richdocuments\Template\CollaboraTemplateProvider; -use OCA\Richdocuments\WOPI\DiscoveryManager; use OCA\Viewer\Event\LoadViewer; use OCP\App\IAppManager; use OCP\AppFramework\App; @@ -215,7 +215,7 @@ public function checkAndEnableCODEServer() { $appConfig->setAppValue('wopi_url', $new_wopi_url); $appConfig->setAppValue('disable_certificate_verification', 'yes'); - $discoveryManager = $this->getContainer()->get(DiscoveryManager::class); + $discoveryManager = $this->getContainer()->get(DiscoveryService::class); $capabilitiesService = $this->getContainer()->get(CapabilitiesService::class); $discoveryManager->refetch(); diff --git a/lib/Backgroundjobs/ObtainCapabilities.php b/lib/Backgroundjobs/CheckConnectivity.php similarity index 72% rename from lib/Backgroundjobs/ObtainCapabilities.php rename to lib/Backgroundjobs/CheckConnectivity.php index effc885be0..db3173b7e6 100644 --- a/lib/Backgroundjobs/ObtainCapabilities.php +++ b/lib/Backgroundjobs/CheckConnectivity.php @@ -23,20 +23,20 @@ namespace OCA\Richdocuments\Backgroundjobs; -use OC\BackgroundJob\TimedJob; -use OCA\Richdocuments\Service\CapabilitiesService; - -class ObtainCapabilities extends TimedJob { - /** @var CapabilitiesService */ - private $capabilitiesService; - - public function __construct(CapabilitiesService $capabilitiesService) { - $this->capabilitiesService = $capabilitiesService; +use OCA\Richdocuments\Service\ConnectivityService; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\BackgroundJob\TimedJob; +class CheckConnectivity extends TimedJob { + public function __construct( + protected ITimeFactory $timeFactory, + protected ConnectivityService $connectivityService + ) { + parent::__construct($timeFactory); $this->setInterval(60 * 60); } protected function run($argument) { - $this->capabilitiesService->refetch(); + $this->connectivityService->verifyConnection(); } } diff --git a/lib/Capabilities.php b/lib/Capabilities.php index 96e811dd17..10c498e9fd 100644 --- a/lib/Capabilities.php +++ b/lib/Capabilities.php @@ -161,7 +161,6 @@ public function getCapabilities() { 'use_groups' => $this->config->getAppValue('use_groups'), 'doc_format' => $this->config->getAppValue('doc_format'), 'timeout' => $this->config->getAppValue('timeout'), - ] ], ]; diff --git a/lib/Command/ActivateConfig.php b/lib/Command/ActivateConfig.php index 91379abf29..a0d93dd98d 100644 --- a/lib/Command/ActivateConfig.php +++ b/lib/Command/ActivateConfig.php @@ -25,33 +25,19 @@ namespace OCA\Richdocuments\Command; use OCA\Richdocuments\AppConfig; -use OCA\Richdocuments\Service\CapabilitiesService; -use OCA\Richdocuments\WOPI\DiscoveryManager; +use OCA\Richdocuments\Service\ConnectivityService; use OCA\Richdocuments\WOPI\Parser; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class ActivateConfig extends Command { - /** @var AppConfig */ - private $appConfig; - - /** @var CapabilitiesService */ - private $capabilitiesService; - - /** @var DiscoveryManager */ - private $discoveryManager; - - /** @var Parser */ - private $wopiParser; - - public function __construct(AppConfig $appConfig, CapabilitiesService $capabilitiesService, DiscoveryManager $discoveryManager, Parser $wopiParser) { + public function __construct( + private AppConfig $appConfig, + private ConnectivityService $connectivityService, + private Parser $wopiParser + ) { parent::__construct(); - - $this->appConfig = $appConfig; - $this->capabilitiesService = $capabilitiesService; - $this->discoveryManager = $discoveryManager; - $this->wopiParser = $wopiParser; } protected function configure() { @@ -62,17 +48,13 @@ protected function configure() { protected function execute(InputInterface $input, OutputInterface $output) { try { - $this->discoveryManager->refetch(); - $this->capabilitiesService->clear(); - $capaUrlSrc = $this->wopiParser->getUrlSrc('Capabilities'); - if (is_array($capaUrlSrc) && $capaUrlSrc['action'] === 'getinfo') { - $public_wopi_url = str_replace('/hosting/capabilities', '', $capaUrlSrc['urlsrc']); - if ($public_wopi_url !== null) { - $this->appConfig->setAppValue('public_wopi_url', $public_wopi_url); - } + $this->connectivityService->verifyConnection(true); + $publicUrl = $this->wopiParser->getDetectedPublicUrl(); + if ($publicUrl) { + $this->appConfig->setAppValue('public_wopi_url', $publicUrl); + $this->connectivityService->verifyConnection(true); + } - $this->capabilitiesService->clear(); - $this->capabilitiesService->refetch(); $output->writeln('Activated any config changes'); return 0; } catch (\Exception $e) { diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index 7fec58e6fe..28e27e61b1 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -15,17 +15,15 @@ use \OCP\IL10N; use \OCP\IRequest; use OCA\Richdocuments\AppConfig; -use OCA\Richdocuments\Service\CapabilitiesService; +use OCA\Richdocuments\Service\ConnectivityService; use OCA\Richdocuments\Service\DemoService; use OCA\Richdocuments\Service\FontService; use OCA\Richdocuments\UploadException; -use OCA\Richdocuments\WOPI\DiscoveryManager; use OCA\Richdocuments\WOPI\Parser; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataDisplayResponse; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\JSONResponse; -use OCP\AppFramework\Http\NotFoundResponse; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; use OCP\Files\SimpleFS\ISimpleFile; @@ -45,81 +43,37 @@ class SettingsController extends Controller { 'application/vnd.ms-opentype', ]; - /** @var IL10N */ - private $l10n; - /** @var AppConfig */ - private $appConfig; - /** @var IConfig */ - private $config; - /** @var DiscoveryManager */ - private $discoveryManager; - /** @var Parser */ - private $wopiParser; - /** @var string */ - private $userId; - /** @var CapabilitiesService */ - private $capabilitiesService; - /** @var DemoService */ - private $demoService; - /** @var LoggerInterface */ - private $logger; - /** - * @var FontService - */ - private $fontService; - - public function __construct($appName, + public function __construct( + string $appName, IRequest $request, - IL10N $l10n, - AppConfig $appConfig, - IConfig $config, - DiscoveryManager $discoveryManager, - Parser $wopiParser, - CapabilitiesService $capabilitiesService, - DemoService $demoService, - FontService $fontService, - LoggerInterface $logger, - $userId + private IL10N $l10n, + private AppConfig $appConfig, + private IConfig $config, + private ConnectivityService $connectivityService, + private Parser $wopiParser, + private DemoService $demoService, + private FontService $fontService, + private LoggerInterface $logger, + private ?string $userId ) { parent::__construct($appName, $request); - $this->l10n = $l10n; - $this->appConfig = $appConfig; - $this->config = $config; - $this->discoveryManager = $discoveryManager; - $this->wopiParser = $wopiParser; - $this->capabilitiesService = $capabilitiesService; - $this->demoService = $demoService; - $this->logger = $logger; - $this->userId = $userId; - $this->fontService = $fontService; - $this->request = $request; } /** * @PublicPage * @NoCSRFRequired - * @throws \Exception */ - public function checkSettings() { - try { - $response = $this->discoveryManager->fetchFromRemote(); - } catch (\Exception $e) { - $this->logger->error($e->getMessage(), ['exception' => $e]); - return new DataResponse([ - 'status' => $e->getCode(), - 'message' => 'Could not fetch discovery details' - ], Http::STATUS_INTERNAL_SERVER_ERROR); - } - - return new DataResponse(); + public function checkSettings(): DataResponse { + $this->connectivityService->verifyConnection(); + return new DataResponse($this->connectivityService->getStatus()); } - public function demoServers() { + public function demoServers(): DataResponse { $demoServers = $this->demoService->fetchDemoServers(true); if (count($demoServers) > 0) { return new DataResponse($demoServers); } - return new NotFoundResponse(); + return new DataResponse([], Http::STATUS_NOT_FOUND); } /** @@ -194,44 +148,31 @@ public function setSettings($wopi_url, $this->appConfig->setAppValue('canonical_webroot', $canonical_webroot); } - $this->discoveryManager->refetch(); - $this->capabilitiesService->clear(); - try { - $capaUrlSrc = $this->wopiParser->getUrlSrc('Capabilities'); - if (is_array($capaUrlSrc) && $capaUrlSrc['action'] === 'getinfo') { - $public_wopi_url = str_replace('/hosting/capabilities', '', $capaUrlSrc['urlsrc']); - if ($public_wopi_url !== null) { - $this->appConfig->setAppValue('public_wopi_url', $public_wopi_url); - $colon = strpos($public_wopi_url, ':', 0); - if ($this->request->getServerProtocol() !== substr($public_wopi_url, 0, $colon)) { - $message = $this->l10n->t('Saved with error: Collabora Online should expose the same protocol as the server installation. Please check the ssl.enable and ssl.termination settings of your Collabora Online server.'); - } - } - } - } catch (\Exception $e) { - if ($wopi_url !== null) { - return new JSONResponse([ - 'status' => 'error', - 'data' => ['message' => 'Failed to connect to the remote server'] - ], 500); - } + $this->connectivityService->verifyConnection(true); + + $publicUrl = $this->wopiParser->getDetectedPublicUrl(); + if ($publicUrl) { + $this->appConfig->setAppValue('public_wopi_url', $publicUrl); + $this->connectivityService->verifyConnection(true); } - $this->capabilitiesService->clear(); - $this->capabilitiesService->refetch(); - if ($this->capabilitiesService->getCapabilities() === []) { + if (!$this->connectivityService->isDiscoveryReachable()) { return new JSONResponse([ 'status' => 'error', - 'data' => ['message' => 'Failed to connect to the remote server', 'hint' => 'missing_capabilities'] + 'data' => [ + 'message' => 'Failed to connect to the remote server', + 'status' => $this->connectivityService->getStatus(),] ], 500); } - $response = [ + return new JSONResponse([ 'status' => 'success', - 'data' => ['message' => $message] - ]; - - return new JSONResponse($response); + 'data' => [ + 'discovery' => $this->wopiParser->getUrlSrc('application/vnd.oasis.opendocument.text')['urlsrc'], + 'message' => $message, + 'status' => $this->connectivityService->getStatus(), + ] + ]); } public function updateWatermarkSettings($settings = []) { diff --git a/lib/Listener/AddContentSecurityPolicyListener.php b/lib/Listener/AddContentSecurityPolicyListener.php index a0fbbacfc7..8af75c534b 100644 --- a/lib/Listener/AddContentSecurityPolicyListener.php +++ b/lib/Listener/AddContentSecurityPolicyListener.php @@ -52,6 +52,11 @@ public function handle(Event $event): void { $policy->addAllowedFrameDomain("'self'"); $policy->addAllowedFrameDomain("nc:"); + // Allow the admin page settings to request the entered endpoint for checking connectivity + if ($this->isSettingsPage()) { + $policy->addAllowedConnectDomain("*"); + } + foreach ($this->config->getDomainList() as $url) { $policy->addAllowedFrameDomain($url); $policy->addAllowedFormActionDomain($url); @@ -66,4 +71,8 @@ private function isPageLoad(): bool { $scriptNameParts = explode('/', $this->request->getScriptName()); return end($scriptNameParts) === 'index.php'; } + + private function isSettingsPage(): bool { + return str_starts_with($this->request->getPathInfo(), '/settings/admin/richdocuments'); + } } diff --git a/lib/Service/BaseRemoteService.php b/lib/Service/BaseRemoteService.php new file mode 100644 index 0000000000..0714b4b822 --- /dev/null +++ b/lib/Service/BaseRemoteService.php @@ -0,0 +1,93 @@ +cache = $cacheFactory->createDistributed(self::class); + } + protected function getCache(string $key): mixed { + if ($this->data) { + return $this->data; + } + + if ($cached = $this->cache->get($key)) { + return $cached; + } + + if ($cached = $this->getAppDataFile($key)) { + $this->data = json_decode($cached->getContent(), true); + $this->cache->set($key, $this->data); + } + + return $this->data; + } + + protected function setCache(string $key, mixed $data): void { + $this->data = $data; + $this->cache->set($key, $this->data); + $file = $this->getAppDataFile($key); + $file->putContent(json_encode($this->data)); + } + + private function getAppDataFile(string $key) { + try { + $folder = $this->appData->getFolder('cache'); + } catch (NotFoundException $e) { + $folder = $this->appData->newFolder('cache'); + } + + try { + $file = $folder->getFile($key); + } catch (NotFoundException $e) { + $file = $folder->newFile($key); + } + + return $file; + } + + protected function getRequestOptions(): array { + $options = [ + 'timeout' => self::DEFAULT_REMOTE_TIMEOUT, + 'nextcloud' => ['allow_local_address' => true] + ]; + + if ($this->config->getAppValue('richdocuments', 'disable_certificate_verification') === 'yes') { + $options['verify'] = false; + } + + return $options; + } + + protected function getRemoteUrl(string $endpoint): ?string { + $remoteHost = $this->config->getAppValue('richdocuments', 'wopi_url', ''); + if ($remoteHost === '') { + return null; + }; + + return rtrim($remoteHost, '/') . '/' . ltrim($endpoint, '/'); + } + + public function clearCache($key): void { + $this->cache->clear(); + $this->getAppDataFile($key)->delete(); + } +} diff --git a/lib/Service/CapabilitiesService.php b/lib/Service/CapabilitiesService.php index 57f7c008ea..d0184d4a05 100644 --- a/lib/Service/CapabilitiesService.php +++ b/lib/Service/CapabilitiesService.php @@ -23,61 +23,74 @@ namespace OCA\Richdocuments\Service; +use Exception; use OCA\Richdocuments\AppInfo\Application; use OCP\App\IAppManager; +use OCP\Files\IAppData; use OCP\Http\Client\IClientService; -use OCP\ICache; use OCP\ICacheFactory; use OCP\IConfig; use OCP\IL10N; +use OCP\PreConditionNotMetException; use Psr\Log\LoggerInterface; -class CapabilitiesService { - /** @var IConfig */ - private $config; - /** @var IClientService */ - private $clientService; - /** @var ICache */ - private $cache; - /** @var IAppManager */ - private $appManager; - /** @var IL10N */ - private $l10n; - /** @var LoggerInterface */ - private $logger; - - /** @var array */ - private $capabilities; - - - public function __construct(IConfig $config, IClientService $clientService, ICacheFactory $cacheFactory, IAppManager $appManager, IL10N $l10n, LoggerInterface $logger) { +class CapabilitiesService extends BaseRemoteService { + private IAppManager $appManager; + private IL10N $l10n; + + public function clear(): void { + $this->clearCache('capabilities'); + } + + + public function __construct(IAppData $appData, IConfig $config, IClientService $clientService, ICacheFactory $cacheFactory, IAppManager $appManager, IL10N $l10n, LoggerInterface $logger) { + parent::__construct( + $appData, + $clientService, + $config, + $logger, + $cacheFactory + ); $this->config = $config; $this->clientService = $clientService; - $this->cache = $cacheFactory->createDistributed('richdocuments'); $this->appManager = $appManager; $this->l10n = $l10n; $this->logger = $logger; } - public function getCapabilities() { - if (!$this->capabilities) { - $this->capabilities = $this->cache->get('capabilities'); - } + public function getCapabilities(): array { + return $this->getCache('capabilities') ?? []; + } - $isARM64 = php_uname('m') === 'aarch64'; - $CODEAppID = $isARM64 ? 'richdocumentscode_arm64' : 'richdocumentscode'; - $isCODEInstalled = $this->appManager->isEnabledForUser($CODEAppID); - $isCODEEnabled = strpos($this->config->getAppValue('richdocuments', 'wopi_url'), 'proxy.php?req=') !== false; - $shouldRecheckCODECapabilities = $isCODEInstalled && $isCODEEnabled && ($this->capabilities === null || count($this->capabilities) === 0); - if ($this->capabilities === null || $shouldRecheckCODECapabilities) { - $this->refetch(); + /** + * @throws PreConditionNotMetException When there is no server configured + * @throws Exception When the request fails + */ + public function fetch(): void { + $capabilitiesEndpoint = $this->getRemoteUrl('/hosting/capabilities'); + if (!$capabilitiesEndpoint) { + throw new PreConditionNotMetException('Not configured'); } - if (!is_array($this->capabilities)) { - return []; + $client = $this->clientService->newClient(); + + try { + $startTime = microtime(true); + $response = $client->get($capabilitiesEndpoint, $this->getRequestOptions()); + $duration = round(((microtime(true) - $startTime)), 3); + $this->logger->info('Fetched capabilities endpoint from ' . $capabilitiesEndpoint. ' in ' . $duration . ' seconds'); + $responseBody = $response->getBody(); + $capabilities = \json_decode($responseBody, true); + + if (!is_array($capabilities)) { + throw new \InvalidArgumentException('Capabilities didn\'t return an array'); + } + } catch (\Exception $e) { + $this->logger->error('Failed to fetch the Collabora capabilities endpoint: ' . $e->getMessage(), [ 'exception' => $e ]); + throw $e; } - return $this->capabilities; + $this->setCache('capabilities', $capabilities); } public function hasNextcloudBranding(): bool { @@ -123,47 +136,4 @@ public function hasOtherOOXMLApps(): bool { return false; } - - public function clear(): void { - $this->cache->remove('capabilities'); - } - - public function refetch(): void { - $remoteHost = $this->config->getAppValue('richdocuments', 'wopi_url'); - if ($remoteHost === '') { - return; - } - $capabilitiesEndpoint = rtrim($remoteHost, '/') . '/hosting/capabilities'; - - $client = $this->clientService->newClient(); - $options = ['timeout' => 45, 'nextcloud' => ['allow_local_address' => true]]; - - if ($this->config->getAppValue('richdocuments', 'disable_certificate_verification') === 'yes') { - $options['verify'] = false; - } - - try { - $startTime = microtime(true); - $response = $client->get($capabilitiesEndpoint, $options); - $duration = round(((microtime(true) - $startTime)), 3); - $this->logger->info('Fetched capabilities endpoint from ' . $capabilitiesEndpoint. ' in ' . $duration . ' seconds'); - $responseBody = $response->getBody(); - $capabilities = \json_decode($responseBody, true); - - if (!is_array($capabilities)) { - $capabilities = []; - } - } catch (\Exception $e) { - $this->logger->error('Failed to fetch the Collabora capabilities endpoint: ' . $e->getMessage(), [ 'exception' => $e ]); - $capabilities = []; - } - - $this->capabilities = $capabilities; - $ttl = 3600; - if (count($capabilities) === 0) { - $ttl = 60; - } - - $this->cache->set('capabilities', $capabilities, $ttl); - } } diff --git a/lib/Service/ConnectivityService.php b/lib/Service/ConnectivityService.php new file mode 100644 index 0000000000..4f3c3e4b96 --- /dev/null +++ b/lib/Service/ConnectivityService.php @@ -0,0 +1,107 @@ + $this->isConfigured(), + 'discovery' => $this->getDiscoveryStatus(), + 'capabilities' => $this->getCapabilitiesStatus(), + ]; + } + + public function getDiscoveryStatus(): array { + return json_decode($this->config->getAppValue(Application::APPNAME, 'connectivity_discovery', '{}'), true); + } + + public function isDiscoveryReachable(): bool { + return ($this->getDiscoveryStatus()['status'] ?? self::STATUS_FAILED) === self::STATUS_OK; + } + + public function getCapabilitiesStatus(): array { + return json_decode($this->config->getAppValue(Application::APPNAME, 'connectivity_capabilities', '{}'), true); + } + + public function isCapabilitiesReachable(): bool { + return ($this->getCapabilitiesStatus()['status'] ?? self::STATUS_FAILED) === self::STATUS_OK; + } + + public function isConfigured(): bool { + return $this->config->getAppValue(Application::APPNAME, 'connectivity', 'no') === 'yes'; + } + + public function verifyConnection(bool $reconfigure = false): void { + $configured = $this->config->getAppValue(Application::APPNAME, 'connectivity', 'no') === 'yes'; + $discoveryStatus = []; + $capabilitiesStatus = []; + + if (!$configured && !$reconfigure) { + // No need to verify anything yet + return; + } + + if ($reconfigure) { + $this->config->deleteAppValue(Application::APPNAME, 'public_wopi_url'); + $this->discoveryService->clear(); + $this->capabilitiesService->clear(); + } + + try { + $discoveryStatus['last_checked'] = $this->timeFactory->getTime(); + $this->discoveryService->fetch(); + $discoveryStatus['status'] = self::STATUS_OK; + } catch (\Exception $e) { + $discoveryStatus['status'] = self::STATUS_FAILED; + $discoveryStatus['message'] = $e->getMessage(); + } + + try { + $this->parser->getUrlSrc('application/vnd.oasis.opendocument.text'); + } catch (\Exception $e) { + $discoveryStatus['status'] = self::STATUS_FAILED; + $discoveryStatus['message'] = $e->getMessage(); + } + + if ($discoveryStatus['status'] === self::STATUS_OK) { + try { + $capabilitiesStatus['last_checked'] = $this->timeFactory->getTime(); + $this->capabilitiesService->fetch(); + $capabilitiesStatus['status'] = self::STATUS_OK; + } catch (\Exception $e) { + $capabilitiesStatus['status'] = self::STATUS_FAILED; + $capabilitiesStatus['message'] = $e->getMessage(); + } + } + + // If we verify connection for the first time passing, set as configured + $configured = $configured || $discoveryStatus['status'] === self::STATUS_OK; + + $this->config->setAppValue(Application::APPNAME, 'connectivity', $configured ? 'yes' : 'no'); + $this->config->setAppValue(Application::APPNAME, 'connectivity_discovery', json_encode($discoveryStatus)); + $this->config->setAppValue(Application::APPNAME, 'connectivity_capabilities', json_encode($capabilitiesStatus)); + } + + public function reset(): void { + $this->config->deleteAppValue(Application::APPNAME, 'connectivity'); + $this->config->deleteAppValue(Application::APPNAME, 'connectivity_discovery'); + $this->config->deleteAppValue(Application::APPNAME, 'connectivity_capabilities'); + } +} diff --git a/lib/WOPI/DiscoveryManager.php b/lib/Service/DiscoveryService.php similarity index 50% rename from lib/WOPI/DiscoveryManager.php rename to lib/Service/DiscoveryService.php index 167413d9ac..4fc48cba4a 100644 --- a/lib/WOPI/DiscoveryManager.php +++ b/lib/Service/DiscoveryService.php @@ -22,95 +22,54 @@ * */ -namespace OCA\Richdocuments\WOPI; - -use OCP\Http\Client\IClientService; -use OCP\Http\Client\IResponse; -use OCP\ICache; -use OCP\ICacheFactory; -use OCP\IConfig; -use Psr\Log\LoggerInterface; - -class DiscoveryManager { - private IClientService $clientService; - private ICache $cache; - private IConfig $config; - private LoggerInterface $logger; - - private ?string $discovery = null; - - public function __construct( - IClientService $clientService, - ICacheFactory $cacheFactory, - IConfig $config, - LoggerInterface $logger - ) { - $this->clientService = $clientService; - $this->cache = $cacheFactory->createDistributed('richdocuments'); - $this->config = $config; - $this->logger = $logger; - } +namespace OCA\Richdocuments\Service; - public function get(): ?string { - if ($this->discovery) { - return $this->discovery; - } +use Exception; +use OCP\PreConditionNotMetException; - $this->discovery = $this->cache->get('discovery'); - if (!$this->discovery) { - $response = $this->fetchFromRemote(); - $responseBody = $response->getBody(); - $this->discovery = $responseBody; - $this->cache->set('discovery', $this->discovery, 3600); - } +class DiscoveryService extends BaseRemoteService { - return $this->discovery; + public function clear(): void { + $this->clearCache('discovery'); + } + + public function get(): ?string { + return $this->getCache('discovery'); } /** - * @throws \Exception if a network error occurs + * @throws PreConditionNotMetException When there is no server configured + * @throws Exception When the request fails */ - public function fetchFromRemote(): IResponse { - $remoteHost = $this->config->getAppValue('richdocuments', 'wopi_url'); - $wopiDiscovery = rtrim($remoteHost, '/') . '/hosting/discovery'; + public function fetch(): void { + $discoveryEndpoint = $this->getRemoteUrl('/hosting/discovery'); + if (!$discoveryEndpoint) { + throw new PreConditionNotMetException('Not configured'); + } $client = $this->clientService->newClient(); - $options = ['timeout' => 45, 'nextcloud' => ['allow_local_address' => true]]; - - if ($this->config->getAppValue('richdocuments', 'disable_certificate_verification') === 'yes') { - $options['verify'] = false; - } + $options = $this->getRequestOptions(); - if ($this->isProxyStarting($wopiDiscovery)) { + if ($this->isProxyStarting($discoveryEndpoint)) { $options['timeout'] = 180; } $startTime = microtime(true); - $response = $client->get($wopiDiscovery, $options); + $response = $client->get($discoveryEndpoint, $options); $duration = round(((microtime(true) - $startTime)), 3); - $this->logger->info('Fetched discovery endpoint from ' . $wopiDiscovery . ' in ' . $duration . ' seconds'); + $this->logger->info('Fetched discovery endpoint from ' . $discoveryEndpoint . ' in ' . $duration . ' seconds'); - return $response; - } + $body = $response->getBody(); - public function refetch(): void { - $this->cache->remove('discovery'); - $this->discovery = null; + $this->setCache('discovery', $body); } /** * @return boolean indicating if proxy.php is in initialize or false otherwise */ private function isProxyStarting(string $url): bool { - $usesProxy = false; $proxyPos = strrpos($url, 'proxy.php'); - if ($proxyPos === false) { - $usesProxy = false; - } else { - $usesProxy = true; - } - - if ($usesProxy === true) { + if ($proxyPos !== false) { $statusUrl = substr($url, 0, $proxyPos); $statusUrl = $statusUrl . 'proxy.php?status'; @@ -133,7 +92,7 @@ private function isProxyStarting(string $url): bool { return true; } } - } catch (\Exception $e) { + } catch (Exception $e) { // ignore } } diff --git a/lib/Service/InitialStateService.php b/lib/Service/InitialStateService.php index b96376ed2d..b985566c9b 100644 --- a/lib/Service/InitialStateService.php +++ b/lib/Service/InitialStateService.php @@ -27,6 +27,7 @@ use OCA\Richdocuments\AppInfo\Application; use OCA\Richdocuments\Db\Wopi; +use OCA\Richdocuments\WOPI\Parser; use OCP\AppFramework\Services\IInitialState; use OCP\Defaults; use OCP\IConfig; @@ -37,9 +38,11 @@ class InitialStateService { public function __construct( private IInitialState $initialState, + private ConnectivityService $connectivityService, private CapabilitiesService $capabilitiesService, private IURLGenerator $urlGenerator, private Defaults $themingDefaults, + private Parser $parser, private IConfig $config, private ?string $userId, ) { @@ -103,4 +106,14 @@ private function provideOptions(): void { $this->urlGenerator->getAbsoluteURL($this->themingDefaults->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); + $this->initialState->provideInitialState('connectivityStatus', $this->connectivityService->getStatus()); + } } diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php index 0625d84818..c5c4c27978 100644 --- a/lib/Settings/Admin.php +++ b/lib/Settings/Admin.php @@ -76,6 +76,7 @@ public function __construct( public function getForm() { $this->initialState->provideCapabilities(); + $this->initialState->provideSettings(); return new TemplateResponse( 'richdocuments', 'admin', diff --git a/lib/WOPI/Parser.php b/lib/WOPI/Parser.php index 8480bef812..af42166c94 100644 --- a/lib/WOPI/Parser.php +++ b/lib/WOPI/Parser.php @@ -21,22 +21,15 @@ namespace OCA\Richdocuments\WOPI; +use OCA\Richdocuments\Service\DiscoveryService; use Psr\Log\LoggerInterface; class Parser { - /** @var DiscoveryManager */ - private $discoveryManager; - /** @var LoggerInterface */ - private $logger; - - /** - * @param DiscoveryManager $discoveryManager - * @param LoggerInterface $logger - */ - public function __construct(DiscoveryManager $discoveryManager, LoggerInterface $logger) { - $this->discoveryManager = $discoveryManager; - $this->logger = $logger; + public function __construct( + private DiscoveryService $discoveryService, + private LoggerInterface $logger, + ) { } /** @@ -45,7 +38,7 @@ public function __construct(DiscoveryManager $discoveryManager, LoggerInterface * @throws \Exception */ public function getUrlSrc($mimetype) { - $discovery = $this->discoveryManager->get(); + $discovery = $this->discoveryService->get(); $this->logger->debug('WOPI::getUrlSrc discovery: {discovery}', ['discovery' => $discovery]); if (\PHP_VERSION_ID < 80000) { $loadEntities = libxml_disable_entity_loader(true); @@ -55,7 +48,6 @@ public function getUrlSrc($mimetype) { $discoveryParsed = simplexml_load_string($discovery); } - $result = $discoveryParsed->xpath(sprintf('/wopi-discovery/net-zone/app[@name=\'%s\']/action', $mimetype)); if ($result && count($result) > 0) { return [ @@ -67,4 +59,17 @@ public function getUrlSrc($mimetype) { $this->logger->error('Didn\'t find urlsrc for mimetype {mimetype} in this WOPI discovery response: {discovery}', ['mimetype' => $mimetype, 'discovery' => $discovery]); throw new \Exception('Could not find urlsrc in WOPI'); } + + public function getDetectedPublicUrl(): ?string { + try { + $urlSrc = $this->getUrlSrc('Capabilities'); + if (is_array($urlSrc) && $urlSrc['action'] === 'getinfo') { + $urlSrc = $urlSrc['urlsrc'] ?? null; + return $urlSrc ? str_replace('/hosting/capabilities', '', $urlSrc) : null; + } + } catch (\Exception $e) { + } + + return null; + } } diff --git a/src/components/AdminSettings.vue b/src/components/AdminSettings.vue index 3ab6cf9623..a3cb0544f7 100644 --- a/src/components/AdminSettings.vue +++ b/src/components/AdminSettings.vue @@ -31,43 +31,11 @@ {{ 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.') }}

- -
- - - {{ t('richdocuments', 'You have not configured the allow-list for WOPI requests. Without this setting users may download restricted files via WOPI requests to the Nextcloud server.') }} - {{ t('richdocuments', 'Click here for more info') }} - -
- -
-
- {{ 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: ') }}{{ t('richdocuments', 'Connecting Collabora Online Single Click with Nginx') }} -
-
- {{ t('richdocuments', 'Could not establish connection to the Collabora Online server.') }} -
-
- {{ t('richdocuments', 'Setting up a new server') }} -
-
- {{ t('richdocuments', 'Collabora Online should use the same protocol as the server installation.') }} -
-
- {{ t('richdocuments', 'Collabora Online server is reachable.') }} -
-
-
- {{ t('richdocuments', 'Please configure a Collabora Online server to start editing documents') }} -
+
@@ -78,10 +46,9 @@ value="custom" class="radio" :disabled="updating"> -
+

{{ t('richdocuments', 'Nextcloud Office requires a separate server running Collabora Online to provide editing capabilities.') }} - {{ t('richdocuments', 'Collabora Online requires a separate server acting as a WOPI-like Client to provide editing capabilities.') }}

@@ -397,7 +364,7 @@ import Vue from 'vue' import { loadState } from '@nextcloud/initial-state' import { generateUrl, generateFilePath } from '@nextcloud/router' -import { showWarning, showError } from '@nextcloud/dialogs' +import { showError } from '@nextcloud/dialogs' import { NcModal, NcMultiselect } from '@nextcloud/vue' import axios from '@nextcloud/axios' import SettingsCheckbox from './SettingsCheckbox.vue' @@ -407,13 +374,11 @@ import SettingsSelectGroup from './SettingsSelectGroup.vue' import SettingsExternalApps from './SettingsExternalApps.vue' import SettingsInputFile from './SettingsInputFile.vue' import SettingsFontList from './SettingsFontList.vue' +import SetupHints from './SetupHints.vue' +import { SETUP_HINTS, SERVER_MODE } from '../helpers/setupcheck.js' import '@nextcloud/dialogs/dist/index.css' -const SERVER_STATE_OK = 0 -const SERVER_STATE_LOADING = 1 -const SERVER_STATE_CONNECTION_ERROR = 2 -const PROTOCOL_MISMATCH = 3 const fontMimes = [ 'font/ttf', 'application/font-sfnt', @@ -424,6 +389,7 @@ const fontMimes = [ export default { name: 'AdminSettings', components: { + SetupHints, SettingsCheckbox, SettingsInputText, SettingsSelectTag, @@ -446,7 +412,8 @@ 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_OK, + connectivityStatus: loadState('richdocuments', 'connectivityStatus', {}), hostErrors: [window.location.host === 'localhost' || window.location.host === '127.0.0.1', window.location.protocol !== 'https:', false], demoServers: null, CODEInstalled: 'richdocumentscode' in OC.appswebroots, @@ -492,7 +459,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' @@ -513,16 +480,6 @@ export default { ` }, }, - 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)) { @@ -538,9 +495,6 @@ export default { } Vue.set(this.settings, 'data', this.initial.settings) - if (this.settings.wopi_url === '') { - this.serverError = SERVER_STATE_CONNECTION_ERROR - } 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) Vue.set(this.settings, 'fonts', this.initial.fonts ? this.initial.fonts : []) @@ -566,6 +520,9 @@ export default { this.CODEAppID = 'richdocumentscode_arm64' } this.checkIfDemoServerIsActive() + if (this.connectivityStatus.discovery.status) { + this.setupCheck(this.initial.wopi_url, loadState('richdocuments', 'discovery', null)) + } }, methods: { async fetchDemoServers() { @@ -637,22 +594,52 @@ 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.serverError = SETUP_HINTS.SERVER_STATE_OK + this.setupCheck(this.settings.wopi_url, data.data.discovery) + this.connectivityStatus = data.data.status } catch (e) { + this.serverError = SETUP_HINTS.SERVER_STATE_CONNECTION_ERROR + console.error(e) - this.serverError = SERVER_STATE_CONNECTION_ERROR - if (e.response.data.hint === 'missing_capabilities') { - showWarning('Could not connect to the /hosting/capabilities endpoint. Please check if your webserver configuration is up to date.') - } + this.connectivityStatus = e.response?.data?.data?.status } this.checkIfDemoServerIsActive() }, + async setupCheck(wopiUrl, browserUrl) { + this.serverError = SETUP_HINTS.SERVER_STATE_LOADING + + 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 { @@ -660,15 +647,7 @@ export default { generateFilePath('richdocuments', 'ajax', 'admin.php'), data ) - this.updating = false - - const { message } = result?.data?.data || {} - - if (message && message.length > 0) { - showWarning(message) - } - return result } catch (e) { this.updating = false @@ -679,13 +658,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) { @@ -701,16 +680,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 - }, uploadFont(event) { // TODO define font format list const files = event.target.files @@ -782,13 +751,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 0000000000..b755323eb9 --- /dev/null +++ b/src/components/SetupHints.vue @@ -0,0 +1,109 @@ + + + + diff --git a/src/helpers/setupcheck.js b/src/helpers/setupcheck.js new file mode 100644 index 0000000000..4f2e5ff744 --- /dev/null +++ b/src/helpers/setupcheck.js @@ -0,0 +1,39 @@ +/* + * @copyright Copyright (c) 2022 Julius Härtl + * + * @author Julius Härtl + * + * @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 . + * + */ + +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', +} diff --git a/tests/lib/Listener/AddContentSecurityPolicyListenerTest.php b/tests/lib/Listener/AddContentSecurityPolicyListenerTest.php index fe97e01bfa..2d989a10c4 100644 --- a/tests/lib/Listener/AddContentSecurityPolicyListenerTest.php +++ b/tests/lib/Listener/AddContentSecurityPolicyListenerTest.php @@ -79,6 +79,10 @@ public function setUp(): void { $this->request, $this->config, ); + + $this->request->expects($this->any()) + ->method('getPathInfo') + ->willReturn('/apps/files'); } private function getMergedPolicy(): ContentSecurityPolicy {