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', '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', 'Use your own server') }}
+
{{ t('richdocuments', 'Use your own server (recommended)') }}
{{ 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.') }}