fix: Properly handle internal urls
Improve handling of different URLs. From now on we will allow to configure: - wopi_url Used by Nextcloud to connect to Collabora in the backend - wop_callback_url Passed to collabora to connect back to Nextcloud (optional, determined from the browser URL if not set) The public_wopi_url which was only partly working is no longer ment to be manually set and will be overwritten depending on the /hosting/discovery response. Further this PR improves: - Add setup check on occ and admin page - Give proper error on individual failures of the setup check - Display configured and detected URLs to make setup issue debugging easier - Refactor services to have a cleaner structure for setup checks, discovery and capabilities fetching Signed-off-by: Julius Härtl <jus@bitgrid.net>
This commit is contained in:
Родитель
7bc8406531
Коммит
1551027aef
|
@ -9,7 +9,8 @@
|
|||
}
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*"
|
||||
"ext-json": "*",
|
||||
"ext-simplexml": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"roave/security-advisories": "dev-master",
|
||||
|
|
|
@ -61,7 +61,9 @@ return array(
|
|||
'OCA\\Richdocuments\\Preview\\Pdf' => $baseDir . '/../lib/Preview/Pdf.php',
|
||||
'OCA\\Richdocuments\\Reference\\OfficeTargetReferenceProvider' => $baseDir . '/../lib/Reference/OfficeTargetReferenceProvider.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',
|
||||
|
@ -75,6 +77,5 @@ return array(
|
|||
'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',
|
||||
);
|
||||
|
|
|
@ -76,7 +76,9 @@ class ComposerStaticInitRichdocuments
|
|||
'OCA\\Richdocuments\\Preview\\Pdf' => __DIR__ . '/..' . '/../lib/Preview/Pdf.php',
|
||||
'OCA\\Richdocuments\\Reference\\OfficeTargetReferenceProvider' => __DIR__ . '/..' . '/../lib/Reference/OfficeTargetReferenceProvider.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',
|
||||
|
@ -90,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',
|
||||
);
|
||||
|
||||
|
|
|
@ -18,8 +18,13 @@ use OCP\App\IAppManager;
|
|||
use OCP\GlobalScale\IConfig as GlobalScaleConfig;
|
||||
|
||||
class AppConfig {
|
||||
// URL that Nextcloud will use to connect to Collabora
|
||||
public const WOPI_URL = 'wopi_url';
|
||||
// URL that the browser will use to connect to Collabora (inherited from the discovery endpoint of Collabora,
|
||||
// either wopi_url or what is configured as server_name)
|
||||
public const PUBLIC_WOPI_URL = 'public_wopi_url';
|
||||
// URL that should be used by Collabora to connect back to Nextcloud (defaults to the users browser host)
|
||||
public const WOPI_CALLBACK_URL = 'wopi_callback_url';
|
||||
|
||||
public const FEDERATION_USE_TRUSTED_DOMAINS = 'federation_use_trusted_domains';
|
||||
|
||||
|
@ -144,6 +149,10 @@ class AppConfig {
|
|||
return $this->config->getAppValue(Application::APPNAME, self::WOPI_URL, '');
|
||||
}
|
||||
|
||||
public function getNextcloudUrl(): string {
|
||||
return $this->config->getAppValue(Application::APPNAME, self::WOPI_CALLBACK_URL, '');
|
||||
}
|
||||
|
||||
public function getDisableCertificateValidation(): bool {
|
||||
return $this->config->getAppValue(Application::APPNAME, 'disable_certificate_verification', 'no') === 'yes';
|
||||
}
|
||||
|
@ -235,7 +244,7 @@ class AppConfig {
|
|||
/**
|
||||
* Strips the path and query parameters from the URL.
|
||||
*/
|
||||
private function domainOnly(string $url): string {
|
||||
public function domainOnly(string $url): string {
|
||||
$parsedUrl = parse_url($url);
|
||||
$scheme = isset($parsedUrl['scheme']) ? $parsedUrl['scheme'] . '://' : '';
|
||||
$host = $parsedUrl['host'] ?? '';
|
||||
|
|
|
@ -45,8 +45,8 @@ use OCA\Richdocuments\Preview\OpenDocument;
|
|||
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;
|
||||
|
@ -220,10 +220,10 @@ class Application extends App implements IBootstrap {
|
|||
$appConfig->setAppValue('wopi_url', $new_wopi_url);
|
||||
$appConfig->setAppValue('disable_certificate_verification', 'yes');
|
||||
|
||||
$discoveryManager = $this->getContainer()->get(DiscoveryManager::class);
|
||||
$discoveryService = $this->getContainer()->get(DiscoveryService::class);
|
||||
$capabilitiesService = $this->getContainer()->get(CapabilitiesService::class);
|
||||
|
||||
$discoveryManager->refetch();
|
||||
$discoveryService->refetch();
|
||||
$capabilitiesService->clear();
|
||||
$capabilitiesService->refetch();
|
||||
}
|
||||
|
|
|
@ -37,6 +37,6 @@ class ObtainCapabilities extends TimedJob {
|
|||
}
|
||||
|
||||
protected function run($argument) {
|
||||
$this->capabilitiesService->refetch();
|
||||
$this->capabilitiesService->fetchFromRemote();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,8 +154,9 @@ class Capabilities implements ICapability {
|
|||
'productName' => $this->capabilitiesService->getProductName(),
|
||||
'editonline_endpoint' => $this->urlGenerator->linkToRouteAbsolute('richdocuments.document.editOnline'),
|
||||
'config' => [
|
||||
'wopi_url' => $this->config->getAppValue('wopi_url'),
|
||||
'public_wopi_url' => $this->config->getAppValue('public_wopi_url'),
|
||||
'wopi_url' => $this->config->getCollaboraUrlInternal(),
|
||||
'public_wopi_url' => $this->config->getCollaboraUrlPublic(),
|
||||
'wopi_callback_url' => $this->config->getNextcloudUrl(),
|
||||
'disable_certificate_verification' => $this->config->getAppValue('disable_certificate_verification'),
|
||||
'edit_groups' => $this->config->getAppValue('edit_groups'),
|
||||
'use_groups' => $this->config->getAppValue('use_groups'),
|
||||
|
|
|
@ -26,54 +26,95 @@ namespace OCA\Richdocuments\Command;
|
|||
|
||||
use OCA\Richdocuments\AppConfig;
|
||||
use OCA\Richdocuments\Service\CapabilitiesService;
|
||||
use OCA\Richdocuments\WOPI\DiscoveryManager;
|
||||
use OCA\Richdocuments\WOPI\Parser;
|
||||
use OCA\Richdocuments\Service\ConnectivityService;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
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 CapabilitiesService $capabilitiesService,
|
||||
) {
|
||||
parent::__construct();
|
||||
|
||||
$this->appConfig = $appConfig;
|
||||
$this->capabilitiesService = $capabilitiesService;
|
||||
$this->discoveryManager = $discoveryManager;
|
||||
$this->wopiParser = $wopiParser;
|
||||
}
|
||||
|
||||
protected function configure() {
|
||||
$this
|
||||
->setName('richdocuments:activate-config')
|
||||
->setAliases(['richdocuments:setup'])
|
||||
->addOption('wopi-url', 'w', InputOption::VALUE_REQUIRED, 'URL that the Nextcloud server will use to connect to Collabora', null)
|
||||
->addOption('callback-url', 'c', InputOption::VALUE_REQUIRED, 'URL that is passed to Collabora to connect back to Nextcloud', null)
|
||||
->setDescription('Activate config changes');
|
||||
}
|
||||
|
||||
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);
|
||||
if ($input->getOption('wopi-url') !== null) {
|
||||
$wopiUrl = $input->getOption('wopi-url');
|
||||
$this->appConfig->setAppValue(AppConfig::WOPI_URL, $wopiUrl);
|
||||
$output->writeln('<info>✓ Set WOPI url to ' . $wopiUrl . '</info>');
|
||||
}
|
||||
|
||||
if ($input->getOption('callback-url') !== null) {
|
||||
$callbackUrl = $input->getOption('callback-url');
|
||||
$this->appConfig->setAppValue(AppConfig::WOPI_CALLBACK_URL, $callbackUrl);
|
||||
$output->writeln('<info>✓ Set callback url to ' . $callbackUrl . '</info>');
|
||||
} else {
|
||||
$this->appConfig->setAppValue(AppConfig::WOPI_CALLBACK_URL, '');
|
||||
$output->writeln('<info>✓ Reset callback url autodetect</info>');
|
||||
}
|
||||
$this->capabilitiesService->clear();
|
||||
$this->capabilitiesService->refetch();
|
||||
$output->writeln('<info>Activated any config changes</info>');
|
||||
|
||||
$output->writeln('Checking configuration');
|
||||
$output->writeln('🛈 Configured WOPI URL: ' . $this->appConfig->getCollaboraUrlInternal());
|
||||
$output->writeln('🛈 Configured public WOPI URL: ' . $this->appConfig->getCollaboraUrlPublic());
|
||||
$output->writeln('🛈 Configured callback URL: ' . $this->appConfig->getNextcloudUrl());
|
||||
$output->writeln('');
|
||||
|
||||
try {
|
||||
$this->connectivityService->testDiscovery($output);
|
||||
} catch (\Throwable $e) {
|
||||
$output->writeln('<error>Failed to fetch discovery endpoint from ' . $this->appConfig->getCollaboraUrlInternal());
|
||||
$output->writeln($e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->connectivityService->testCapabilities($output);
|
||||
} catch (\Throwable $e) {
|
||||
// FIXME: Optional when allowing generic WOPI servers
|
||||
$output->writeln('<error>Failed to fetch capabilities endpoint from ' . $this->capabilitiesService->getCapabilitiesEndpoint());
|
||||
$output->writeln($e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->connectivityService->autoConfigurePublicUrl();
|
||||
} catch (\Throwable $e) {
|
||||
$output->writeln('<error>Failed to determine public URL from discovery response</error>');
|
||||
$output->writeln($e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Summarize URLs for easier debugging
|
||||
|
||||
$output->writeln('');
|
||||
$output->writeln('Collabora URL (used for Nextcloud to contact the Collabora server):');
|
||||
$output->writeln(' ' . $this->appConfig->getCollaboraUrlInternal());
|
||||
|
||||
$output->writeln('Collabora public URL (used in the browser to open Collabora):');
|
||||
$output->writeln(' ' . $this->appConfig->getCollaboraUrlPublic());
|
||||
|
||||
$output->writeln('Callback URL (used by Collabora to connect back to Nextcloud):');
|
||||
$callbackUrl = $this->appConfig->getNextcloudUrl();
|
||||
if ($callbackUrl === '') {
|
||||
$output->writeln(' autodetected (will use the same URL as your user for browsing Nextcloud)');
|
||||
} else {
|
||||
$output->writeln(' ' . $this->appConfig->getNextcloudUrl());
|
||||
}
|
||||
|
||||
return 0;
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln('<error>Failed to activate any config changes</error>');
|
||||
|
|
|
@ -29,21 +29,13 @@ use Doctrine\DBAL\Types\Type;
|
|||
use OC\DB\Connection;
|
||||
use OC\DB\SchemaWrapper;
|
||||
use OCP\DB\Types;
|
||||
use OCP\IDBConnection;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
|
||||
class ConvertToBigInt extends Command {
|
||||
/** @var IDBConnection */
|
||||
private $connection;
|
||||
|
||||
/**
|
||||
* @param IDBConnection $connection
|
||||
*/
|
||||
public function __construct(Connection $connection) {
|
||||
$this->connection = $connection;
|
||||
public function __construct(private Connection $connection) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
|
|
@ -11,28 +11,31 @@
|
|||
|
||||
namespace OCA\Richdocuments\Controller;
|
||||
|
||||
use \OCP\AppFramework\Controller;
|
||||
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\DiscoveryService;
|
||||
use OCA\Richdocuments\Service\FontService;
|
||||
use OCA\Richdocuments\UploadException;
|
||||
use OCA\Richdocuments\WOPI\DiscoveryManager;
|
||||
use OCA\Richdocuments\WOPI\Parser;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
||||
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
|
||||
use OCP\AppFramework\Http\Attribute\PublicPage;
|
||||
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;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
use OCP\PreConditionNotMetException;
|
||||
use OCP\Util;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Console\Output\NullOutput;
|
||||
|
||||
class SettingsController extends Controller {
|
||||
// TODO adapt overview generation if we add more font mimetypes
|
||||
|
@ -45,120 +48,88 @@ 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,
|
||||
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 DiscoveryService $discoveryService,
|
||||
private CapabilitiesService $capabilitiesService,
|
||||
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() {
|
||||
#[PublicPage]
|
||||
#[NoCSRFRequired]
|
||||
public function checkSettings(): DataResponse {
|
||||
try {
|
||||
$response = $this->discoveryManager->fetchFromRemote();
|
||||
$output = new NullOutput();
|
||||
$this->connectivityService->testDiscovery($output);
|
||||
$this->connectivityService->testCapabilities($output);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error($e->getMessage(), ['exception' => $e]);
|
||||
return new DataResponse([
|
||||
'status' => $e->getCode(),
|
||||
'message' => 'Could not fetch discovery details'
|
||||
'data' => [
|
||||
// FIXME ONLY AS ADMIN
|
||||
'message' => 'Failed to connect to the remote server: ' . $e->getMessage(),
|
||||
'settings' => $this->getSettingsData(),
|
||||
],
|
||||
], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
return new DataResponse();
|
||||
return new DataResponse([
|
||||
'status' => 'success',
|
||||
'data' => [
|
||||
'settings' => $this->getSettingsData(),
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function getSettings() {
|
||||
return new JSONResponse([
|
||||
'wopi_url' => $this->appConfig->getAppValue('wopi_url'),
|
||||
#[NoAdminRequired]
|
||||
public function getSettings(): JSONResponse {
|
||||
return new JSONResponse($this->getSettingsData());
|
||||
}
|
||||
|
||||
private function getSettingsData(): array {
|
||||
return [
|
||||
'wopi_url' => $this->appConfig->getCollaboraUrlInternal(),
|
||||
'public_wopi_url' => $this->appConfig->getCollaboraUrlPublic(),
|
||||
'wopi_callback_url' => $this->appConfig->getNextcloudUrl(),
|
||||
'wopi_allowlist' => $this->appConfig->getAppValue('wopi_allowlist'),
|
||||
'public_wopi_url' => $this->appConfig->getAppValue('public_wopi_url'),
|
||||
'disable_certificate_verification' => $this->appConfig->getAppValue('disable_certificate_verification') === 'yes',
|
||||
'edit_groups' => $this->appConfig->getAppValue('edit_groups'),
|
||||
'use_groups' => $this->appConfig->getAppValue('use_groups'),
|
||||
'doc_format' => $this->appConfig->getAppValue('doc_format'),
|
||||
]);
|
||||
'product_name' => $this->capabilitiesService->getServerProductName(),
|
||||
'product_version' => $this->capabilitiesService->getProductVersion(),
|
||||
'product_hash' => $this->capabilitiesService->getProductHash(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $wopi_url
|
||||
* @param string $disable_certificate_verification
|
||||
* @param string $edit_groups
|
||||
* @param string $use_groups
|
||||
* @param string $doc_format
|
||||
* @param string $external_apps
|
||||
* @param string $canonical_webroot
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function setSettings($wopi_url,
|
||||
$wopi_allowlist,
|
||||
$disable_certificate_verification,
|
||||
$edit_groups,
|
||||
$use_groups,
|
||||
$doc_format,
|
||||
$external_apps,
|
||||
$canonical_webroot) {
|
||||
$message = $this->l10n->t('Saved');
|
||||
|
||||
public function setSettings(
|
||||
?string $wopi_url,
|
||||
?string $wopi_allowlist,
|
||||
?bool $disable_certificate_verification,
|
||||
?string $edit_groups,
|
||||
?string $use_groups,
|
||||
?string $doc_format,
|
||||
?string $external_apps,
|
||||
?string $canonical_webroot
|
||||
): JSONResponse {
|
||||
if ($wopi_url !== null) {
|
||||
$this->appConfig->setAppValue('wopi_url', $wopi_url);
|
||||
}
|
||||
|
@ -170,7 +141,7 @@ class SettingsController extends Controller {
|
|||
if ($disable_certificate_verification !== null) {
|
||||
$this->appConfig->setAppValue(
|
||||
'disable_certificate_verification',
|
||||
$disable_certificate_verification === true ? 'yes' : ''
|
||||
$disable_certificate_verification ? 'yes' : ''
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -194,47 +165,30 @@ class SettingsController extends Controller {
|
|||
$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) {
|
||||
$output = new NullOutput();
|
||||
$this->connectivityService->testDiscovery($output);
|
||||
$this->connectivityService->testCapabilities($output);
|
||||
$this->connectivityService->autoConfigurePublicUrl();
|
||||
} catch (\Throwable $e) {
|
||||
return new JSONResponse([
|
||||
'status' => 'error',
|
||||
'data' => ['message' => 'Failed to connect to the remote server']
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
$this->capabilitiesService->clear();
|
||||
$this->capabilitiesService->refetch();
|
||||
if ($this->capabilitiesService->getCapabilities() === []) {
|
||||
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: ' . $e->getMessage()]
|
||||
], 500);
|
||||
}
|
||||
|
||||
$response = [
|
||||
'status' => 'success',
|
||||
'data' => ['message' => $message]
|
||||
'data' => [
|
||||
'message' => $this->l10n->t('Saved'),
|
||||
'settings' => $this->getSettingsData(),
|
||||
]
|
||||
];
|
||||
|
||||
return new JSONResponse($response);
|
||||
}
|
||||
|
||||
public function updateWatermarkSettings($settings = []) {
|
||||
public function updateWatermarkSettings($settings = []): JSONResponse {
|
||||
$supportedOptions = [
|
||||
'watermark_text',
|
||||
'watermark_enabled',
|
||||
|
|
|
@ -97,6 +97,13 @@ class WOPIMiddleware extends Middleware {
|
|||
return new JSONResponse([], Http::STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
if ($controller instanceof WopiController) {
|
||||
$this->logger->error('Uncaught error: ' . $exception->getMessage(), [ 'exception' => $exception ]);
|
||||
return new JSONResponse([
|
||||
'message' => 'Error'
|
||||
], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ class CapabilitiesService {
|
|||
$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();
|
||||
$this->fetchFromRemote();
|
||||
}
|
||||
|
||||
if (!is_array($this->capabilities)) {
|
||||
|
@ -80,6 +80,18 @@ class CapabilitiesService {
|
|||
return $this->capabilities;
|
||||
}
|
||||
|
||||
public function getProductVersion(): ?string {
|
||||
return $this->getCapabilities()['productVersion'] ?? null;
|
||||
}
|
||||
|
||||
public function getProductHash(): ?string {
|
||||
return $this->getCapabilities()['productVersionHash'] ?? null;
|
||||
}
|
||||
|
||||
public function getServerProductName(): ?string {
|
||||
return $this->getCapabilities()['productName'] ?? null;
|
||||
}
|
||||
|
||||
public function hasNextcloudBranding(): bool {
|
||||
$productVersion = $this->getCapabilities()['productVersion'] ?? '0.0.0.0';
|
||||
return version_compare($productVersion, '21.11', '>=');
|
||||
|
@ -124,16 +136,22 @@ class CapabilitiesService {
|
|||
return false;
|
||||
}
|
||||
|
||||
public function clear(): void {
|
||||
public function resetCache(): void {
|
||||
$this->cache->remove('capabilities');
|
||||
}
|
||||
|
||||
public function refetch(): void {
|
||||
public function getCapabilitiesEndpoint(): ?string {
|
||||
$remoteHost = $this->config->getAppValue('richdocuments', 'wopi_url');
|
||||
if ($remoteHost === '') {
|
||||
return null;
|
||||
}
|
||||
return rtrim($remoteHost, '/') . '/hosting/capabilities';
|
||||
}
|
||||
|
||||
public function fetchFromRemote($throw = false): void {
|
||||
if (!$this->getCapabilitiesEndpoint()) {
|
||||
return;
|
||||
}
|
||||
$capabilitiesEndpoint = rtrim($remoteHost, '/') . '/hosting/capabilities';
|
||||
|
||||
$client = $this->clientService->newClient();
|
||||
$options = ['timeout' => 45, 'nextcloud' => ['allow_local_address' => true]];
|
||||
|
@ -144,9 +162,9 @@ class CapabilitiesService {
|
|||
|
||||
try {
|
||||
$startTime = microtime(true);
|
||||
$response = $client->get($capabilitiesEndpoint, $options);
|
||||
$response = $client->get($this->getCapabilitiesEndpoint(), $options);
|
||||
$duration = round(((microtime(true) - $startTime)), 3);
|
||||
$this->logger->info('Fetched capabilities endpoint from ' . $capabilitiesEndpoint. ' in ' . $duration . ' seconds');
|
||||
$this->logger->info('Fetched capabilities endpoint from ' . $this->getCapabilitiesEndpoint(). ' in ' . $duration . ' seconds');
|
||||
$responseBody = $response->getBody();
|
||||
$capabilities = \json_decode($responseBody, true);
|
||||
|
||||
|
@ -155,6 +173,9 @@ class CapabilitiesService {
|
|||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Failed to fetch the Collabora capabilities endpoint: ' . $e->getMessage(), [ 'exception' => $e ]);
|
||||
if ($throw) {
|
||||
throw $e;
|
||||
}
|
||||
$capabilities = [];
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 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/>.
|
||||
*/
|
||||
|
||||
namespace OCA\Richdocuments\Service;
|
||||
|
||||
use Exception;
|
||||
use OCA\Richdocuments\AppConfig;
|
||||
use OCA\Richdocuments\WOPI\Parser;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ConnectivityService {
|
||||
public function __construct(
|
||||
private AppConfig $appConfig,
|
||||
private DiscoveryService $discoveryService,
|
||||
private CapabilitiesService $capabilitiesService,
|
||||
private Parser $parser,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testDiscovery(OutputInterface $output): void {
|
||||
$this->discoveryService->resetCache();
|
||||
$this->discoveryService->fetchFromRemote();
|
||||
$output->writeln('<info>✓ Fetched /hosting/discovery endpoint</info>');
|
||||
|
||||
$this->parser->getUrlSrcValue('application/vnd.openxmlformats-officedocument.wordprocessingml.document');
|
||||
$output->writeln('<info>✓ Valid mimetype response</info>');
|
||||
|
||||
// FIXME: Optional when allowing generic WOPI servers
|
||||
$this->parser->getUrlSrcValue('Capabilities');
|
||||
$output->writeln('<info>✓ Valid capabilities entry</info>');
|
||||
}
|
||||
|
||||
public function testCapabilities(OutputInterface $output): void {
|
||||
$this->capabilitiesService->resetCache();
|
||||
$this->capabilitiesService->fetchFromRemote(true);
|
||||
$output->writeln('<info>✓ Fetched /hosting/capabilities endpoint</info>');
|
||||
|
||||
if ($this->capabilitiesService->getCapabilities() === []) {
|
||||
throw new \Exception('Empty capabilities, unexpected result from ' . $this->capabilitiesService->getCapabilitiesEndpoint());
|
||||
}
|
||||
$output->writeln('<info>✓ Detected WOPI server: ' . $this->capabilitiesService->getServerProductName() . ' ' . $this->capabilitiesService->getProductVersion() . '</info>');
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect public URL of the WOPI server for setting CSP on Nextcloud
|
||||
*
|
||||
* This value is not meant to be set manually. If this turns out to be the wrong URL
|
||||
* it is likely a misconfiguration on your WOPI server. Collabora will inherit the URL to use
|
||||
* form the request and the ssl.enable/ssl.termination settings and server_name (if configured)
|
||||
*/
|
||||
public function autoConfigurePublicUrl(): void {
|
||||
$determinedUrl = $this->parser->getUrlSrcValue('application/vnd.openxmlformats-officedocument.wordprocessingml.document');
|
||||
$detectedUrl = $this->appConfig->domainOnly($determinedUrl);
|
||||
$this->appConfig->setAppValue('public_wopi_url', $detectedUrl);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,24 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 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/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
@ -22,7 +42,7 @@ declare(strict_types=1);
|
|||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Richdocuments\WOPI;
|
||||
namespace OCA\Richdocuments\Service;
|
||||
|
||||
use OCP\Http\Client\IClientService;
|
||||
use OCP\Http\Client\IResponse;
|
||||
|
@ -31,7 +51,7 @@ use OCP\ICacheFactory;
|
|||
use OCP\IConfig;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class DiscoveryManager {
|
||||
class DiscoveryService {
|
||||
private IClientService $clientService;
|
||||
private ICache $cache;
|
||||
private IConfig $config;
|
||||
|
@ -93,7 +113,7 @@ class DiscoveryManager {
|
|||
return $response;
|
||||
}
|
||||
|
||||
public function refetch(): void {
|
||||
public function resetCache(): void {
|
||||
$this->cache->remove('discovery');
|
||||
$this->discovery = null;
|
||||
}
|
|
@ -25,6 +25,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\Richdocuments\Service;
|
||||
|
||||
use OCA\Richdocuments\AppConfig;
|
||||
use OCA\Richdocuments\AppInfo\Application;
|
||||
use OCA\Richdocuments\Db\Wopi;
|
||||
use OCP\AppFramework\Services\IInitialState;
|
||||
|
@ -37,6 +38,7 @@ class InitialStateService {
|
|||
|
||||
public function __construct(
|
||||
private IInitialState $initialState,
|
||||
private AppConfig $appConfig,
|
||||
private CapabilitiesService $capabilitiesService,
|
||||
private IURLGenerator $urlGenerator,
|
||||
private Defaults $themingDefaults,
|
||||
|
@ -54,6 +56,7 @@ class InitialStateService {
|
|||
$this->initialState->provideInitialState('hasDrawSupport', $this->capabilitiesService->hasDrawSupport());
|
||||
$this->initialState->provideInitialState('hasNextcloudBranding', $this->capabilitiesService->hasNextcloudBranding());
|
||||
$this->initialState->provideInitialState('instanceId', $this->config->getSystemValue('instanceid'));
|
||||
$this->initialState->provideInitialState('wopi_callback_url', $this->appConfig->getNextcloudUrl());
|
||||
|
||||
$this->provideOptions();
|
||||
|
||||
|
|
|
@ -34,54 +34,27 @@ use OCP\IConfig;
|
|||
use OCP\Settings\ISettings;
|
||||
|
||||
class Admin implements ISettings {
|
||||
/** @var IConfig */
|
||||
private $config;
|
||||
|
||||
/** @var AppConfig */
|
||||
private $appConfig;
|
||||
|
||||
/** @var TemplateManager */
|
||||
private $manager;
|
||||
|
||||
/** @var CapabilitiesService */
|
||||
private $capabilitiesService;
|
||||
|
||||
/** @var DemoService */
|
||||
private $demoService;
|
||||
|
||||
/** @var InitialStateService */
|
||||
private $initialState;
|
||||
/**
|
||||
* @var FontService
|
||||
*/
|
||||
private $fontService;
|
||||
|
||||
public function __construct(
|
||||
IConfig $config,
|
||||
AppConfig $appConfig,
|
||||
TemplateManager $manager,
|
||||
CapabilitiesService $capabilitiesService,
|
||||
DemoService $demoService,
|
||||
FontService $fontService,
|
||||
InitialStateService $initialStateService
|
||||
private IConfig $config,
|
||||
private AppConfig $appConfig,
|
||||
private TemplateManager $manager,
|
||||
private CapabilitiesService $capabilitiesService,
|
||||
private DemoService $demoService,
|
||||
private FontService $fontService,
|
||||
private InitialStateService $initialStateService
|
||||
) {
|
||||
$this->config = $config;
|
||||
$this->appConfig = $appConfig;
|
||||
$this->manager = $manager;
|
||||
$this->capabilitiesService = $capabilitiesService;
|
||||
$this->demoService = $demoService;
|
||||
$this->initialState = $initialStateService;
|
||||
$this->fontService = $fontService;
|
||||
}
|
||||
|
||||
public function getForm() {
|
||||
$this->initialState->provideCapabilities();
|
||||
public function getForm(): TemplateResponse {
|
||||
$this->initialStateService->provideCapabilities();
|
||||
return new TemplateResponse(
|
||||
'richdocuments',
|
||||
'admin',
|
||||
[
|
||||
'settings' => [
|
||||
'wopi_url' => $this->config->getAppValue('richdocuments', 'wopi_url'),
|
||||
'wopi_url' => $this->appConfig->getCollaboraUrlInternal(),
|
||||
'public_wopi_url' => $this->appConfig->getCollaboraUrlPublic(),
|
||||
'wopi_callback_url' => $this->appConfig->getNextcloudUrl(),
|
||||
'wopi_allowlist' => $this->config->getAppValue('richdocuments', 'wopi_allowlist'),
|
||||
'edit_groups' => $this->config->getAppValue('richdocuments', 'edit_groups'),
|
||||
'use_groups' => $this->config->getAppValue('richdocuments', 'use_groups'),
|
||||
|
|
|
@ -313,6 +313,6 @@ class TokenManager {
|
|||
}
|
||||
|
||||
public function getUrlSrc(File $file): string {
|
||||
return $this->wopiParser->getUrlSrc($file->getMimeType())['urlsrc'];
|
||||
return $this->wopiParser->getUrlSrcValue($file->getMimeType());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,40 +21,37 @@
|
|||
|
||||
namespace OCA\Richdocuments\WOPI;
|
||||
|
||||
use Exception;
|
||||
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
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $mimetype
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getUrlSrc($mimetype) {
|
||||
$discovery = $this->discoveryManager->get();
|
||||
public function getUrlSrcValue(string $appName): string {
|
||||
$result = $this->getUrlSrc($appName)['urlsrc'];
|
||||
|
||||
// Fix for potentially escaped urls that are misconfigured on the Collabora docker image
|
||||
// https://github.com/nextcloud/richdocuments/issues/3262
|
||||
$result = str_replace('\.', '.', $result);
|
||||
|
||||
return (string)$result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function getUrlSrc(string $mimetype): array {
|
||||
$discovery = $this->discoveryService->get();
|
||||
$this->logger->debug('WOPI::getUrlSrc discovery: {discovery}', ['discovery' => $discovery]);
|
||||
if (\PHP_VERSION_ID < 80000) {
|
||||
$loadEntities = libxml_disable_entity_loader(true);
|
||||
$discoveryParsed = simplexml_load_string($discovery);
|
||||
libxml_disable_entity_loader($loadEntities);
|
||||
} else {
|
||||
$discoveryParsed = simplexml_load_string($discovery);
|
||||
}
|
||||
|
||||
|
||||
$result = $discoveryParsed->xpath(sprintf('/wopi-discovery/net-zone/app[@name=\'%s\']/action', $mimetype));
|
||||
if ($result && count($result) > 0) {
|
||||
|
@ -65,6 +62,6 @@ class Parser {
|
|||
}
|
||||
|
||||
$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');
|
||||
throw new Exception('Could not find urlsrc for ' . $mimetype . ' in WOPI discovery response');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,50 +24,57 @@
|
|||
<div>
|
||||
<div class="section">
|
||||
<h2>{{ productName }}</h2>
|
||||
<p v-if="hasNextcloudBranding">
|
||||
<p v-if="hasNextcloudBranding" class="description">
|
||||
{{ t('richdocuments', 'Nextcloud Office is a powerful Collabora Online 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>
|
||||
<p v-else>
|
||||
<p v-else class="description">
|
||||
{{ 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>
|
||||
|
||||
<!-- FIXME: Update this to NcNoteCard for NC25+ once we update the Nextcloud Vue-component library -->
|
||||
<div v-if="settings.wopi_url && settings.wopi_url !== '' && !settings.wopi_allowlist" id="security-warning-state-warning">
|
||||
<span class="icon icon-error-white" />
|
||||
<span class="message">
|
||||
<div v-if="settings.wopi_url && settings.wopi_url !== ''">
|
||||
<NcNoteCard v-if="serverError == 2" type="error">
|
||||
<p>{{ t('richdocuments', 'Could not establish connection to the Collabora Online server.') }}</p>
|
||||
<p>{{ errorMessage }}</p>
|
||||
|
||||
<p v-if="isNginx && serverMode === '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"
|
||||
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>
|
||||
</p>
|
||||
</NcNoteCard>
|
||||
<NcNoteCard v-else-if="serverError == 1" type="warning">
|
||||
<p>{{ t('richdocuments', 'Setting up a new server') }}</p>
|
||||
</NcNoteCard>
|
||||
<NcNoteCard v-else-if="serverError == 3" type="error">
|
||||
<p>{{ t('richdocuments', 'Collabora Online should use the same protocol as the server installation.') }}</p>
|
||||
</NcNoteCard>
|
||||
<NcNoteCard v-else type="success">
|
||||
<p>{{ t('richdocuments', 'Collabora Online server is reachable.') }}</p>
|
||||
<p>{{ settings.product_name }} {{ settings.product_version }} {{ settings.product_hash }}</p>
|
||||
<p class="description">
|
||||
<strong>{{ t('richdocuments', 'URL used by the browser:') }}</strong> <code>{{ settings.public_wopi_url }}</code><br>
|
||||
<strong>{{ t('richdocuments', 'Nextcloud URL used by Collabora:') }}</strong> <code>{{ callbackUrl }}</code>
|
||||
<i>{{ settings.wopi_callback_url ? '' : '(Determined from the browser URL)' }}</i>
|
||||
</p>
|
||||
</NcNoteCard>
|
||||
</div>
|
||||
<NcNoteCard v-else type="warning">
|
||||
<p>{{ t('richdocuments', 'Please configure a Collabora Online server to start editing documents') }}</p>
|
||||
</NcNoteCard>
|
||||
|
||||
<NcNoteCard v-if="settings.wopi_url && settings.wopi_url !== '' && !settings.wopi_allowlist" type="warning">
|
||||
<p>
|
||||
{{ 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.') }}
|
||||
<a title="WOPI settings documentation"
|
||||
href="https://docs.nextcloud.com/server/latest/admin_manual/office/configuration.html#wopi-settings"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="external">{{ t('richdocuments', 'Click here for more info') }}</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</p>
|
||||
</NcNoteCard>
|
||||
|
||||
<fieldset>
|
||||
<div>
|
||||
|
@ -101,7 +108,7 @@
|
|||
:disabled="updating"
|
||||
@change="updateServer">
|
||||
<label for="disable_certificate_verification">{{ t('richdocuments', 'Disable certificate verification (insecure)') }}</label><br>
|
||||
<em>{{ t('Enable if your Collabora Online server uses a self signed certificate') }}</em>
|
||||
<em>{{ t('richdocuments', 'Enable if your Collabora Online server uses a self signed certificate') }}</em>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -398,7 +405,7 @@ import Vue from 'vue'
|
|||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { generateUrl, generateFilePath } from '@nextcloud/router'
|
||||
import { showWarning, showError } from '@nextcloud/dialogs'
|
||||
import { NcModal, NcMultiselect } from '@nextcloud/vue'
|
||||
import { NcModal, NcMultiselect, NcNoteCard } from '@nextcloud/vue'
|
||||
import axios from '@nextcloud/axios'
|
||||
import SettingsCheckbox from './SettingsCheckbox.vue'
|
||||
import SettingsInputText from './SettingsInputText.vue'
|
||||
|
@ -409,6 +416,7 @@ import SettingsInputFile from './SettingsInputFile.vue'
|
|||
import SettingsFontList from './SettingsFontList.vue'
|
||||
|
||||
import '@nextcloud/dialogs/dist/index.css'
|
||||
import { getCallbackBaseUrl } from '../helpers/url.js'
|
||||
|
||||
const SERVER_STATE_OK = 0
|
||||
const SERVER_STATE_LOADING = 1
|
||||
|
@ -433,6 +441,7 @@ export default {
|
|||
SettingsInputFile,
|
||||
SettingsFontList,
|
||||
NcModal,
|
||||
NcNoteCard,
|
||||
},
|
||||
props: {
|
||||
initial: {
|
||||
|
@ -446,7 +455,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: SERVER_STATE_LOADING,
|
||||
errorMessage: null,
|
||||
hostErrors: [window.location.host === 'localhost' || window.location.host === '127.0.0.1', window.location.protocol !== 'https:', false],
|
||||
demoServers: null,
|
||||
CODEInstalled: 'richdocumentscode' in OC.appswebroots,
|
||||
|
@ -512,9 +522,12 @@ export default {
|
|||
</remote_font_config>
|
||||
`
|
||||
},
|
||||
callbackUrl() {
|
||||
return this.settings.wopi_callback_url ? this.settings.wopi_callback_url : getCallbackBaseUrl()
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'settings.wopi_url'(newVal, oldVal) {
|
||||
'settings.public_wopi_url'(newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
const protocol = this.checkUrlProtocol(newVal)
|
||||
const nextcloudProtocol = this.checkUrlProtocol(window.location.href)
|
||||
|
@ -522,6 +535,9 @@ export default {
|
|||
else this.serverError = Object.values(OC.getCapabilities().richdocuments.collabora).length > 0 ? SERVER_STATE_OK : SERVER_STATE_CONNECTION_ERROR
|
||||
}
|
||||
},
|
||||
isSetup() {
|
||||
this.toggleTemplateSettings()
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
for (const key in this.initial.settings) {
|
||||
|
@ -566,8 +582,34 @@ export default {
|
|||
this.CODEAppID = 'richdocumentscode_arm64'
|
||||
}
|
||||
this.checkIfDemoServerIsActive()
|
||||
this.checkSettings()
|
||||
this.toggleTemplateSettings()
|
||||
},
|
||||
methods: {
|
||||
async checkSettings() {
|
||||
this.errorMessage = null
|
||||
this.updating = true
|
||||
this.serverError = SERVER_STATE_LOADING
|
||||
|
||||
let result
|
||||
try {
|
||||
result = await axios.get(generateUrl('/apps/richdocuments/settings/check'))
|
||||
this.serverError = SERVER_STATE_OK
|
||||
|
||||
} catch (e) {
|
||||
this.serverError = SERVER_STATE_CONNECTION_ERROR
|
||||
result = e.response
|
||||
const { message } = e.response.data.data
|
||||
this.errorMessage = message
|
||||
}
|
||||
|
||||
this.updating = false
|
||||
|
||||
const { settings } = result?.data?.data || {}
|
||||
for (const settingKey in settings) {
|
||||
this.settings[settingKey] = settings[settingKey]
|
||||
}
|
||||
},
|
||||
async fetchDemoServers() {
|
||||
try {
|
||||
const result = await axios.get(generateUrl('/apps/richdocuments/settings/demo'))
|
||||
|
@ -654,6 +696,7 @@ export default {
|
|||
this.checkIfDemoServerIsActive()
|
||||
},
|
||||
async updateSettings(data) {
|
||||
this.errorMessage = null
|
||||
this.updating = true
|
||||
try {
|
||||
const result = await axios.post(
|
||||
|
@ -663,15 +706,17 @@ export default {
|
|||
|
||||
this.updating = false
|
||||
|
||||
const { message } = result?.data?.data || {}
|
||||
const { settings } = result?.data?.data || {}
|
||||
|
||||
if (message && message.length > 0) {
|
||||
showWarning(message)
|
||||
for (const settingKey in settings) {
|
||||
this.settings[settingKey] = settings[settingKey]
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (e) {
|
||||
this.updating = false
|
||||
const { message } = e.response.data.data
|
||||
this.errorMessage = message
|
||||
throw e
|
||||
}
|
||||
},
|
||||
|
@ -748,6 +793,13 @@ export default {
|
|||
this.settings.fonts.splice(index, 1)
|
||||
}
|
||||
},
|
||||
toggleTemplateSettings() {
|
||||
if (this.isSetup) {
|
||||
document.getElementById('richdocuments-templates').classList.remove('hidden')
|
||||
} else {
|
||||
document.getElementById('richdocuments-templates').classList.add('hidden')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -757,6 +809,14 @@ export default {
|
|||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.notecard:deep(p:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: var(--color-text-maxcontrast);
|
||||
}
|
||||
|
||||
p.checkbox-details {
|
||||
margin-left: 25px;
|
||||
margin-top: -10px;
|
||||
|
@ -782,13 +842,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) {
|
||||
|
|
|
@ -797,6 +797,8 @@ $(document).ready(function() {
|
|||
|
||||
OCA.RichDocuments.documentsMain = documentsMain
|
||||
|
||||
Config.update('wopi_callback_url', loadState('richdocuments', 'wopi_callback_url', ''))
|
||||
|
||||
if (shouldAskForGuestName()) {
|
||||
PostMessages.sendPostMessage('parent', 'NC_ShowNamePicker')
|
||||
$('#documents-content').guestNamePicker()
|
||||
|
|
|
@ -32,22 +32,29 @@ const getSearchParam = (name) => {
|
|||
return decodeURI(results[1]) || ''
|
||||
}
|
||||
|
||||
const getCallbackBaseUrl = () => {
|
||||
const callbackUrl = Config.get('wopi_callback_url')
|
||||
return callbackUrl || window.location.protocol + '//' + window.location.host + getRootUrl() + '/'
|
||||
}
|
||||
|
||||
const getWopiSrc = (fileId) => {
|
||||
// WOPISrc - URL that loolwsd will access (ie. pointing to ownCloud)
|
||||
// index.php is forced here to avoid different wopi srcs for the same document
|
||||
const wopiurl = getCallbackBaseUrl() + '/index.php/apps/richdocuments/wopi/files/' + fileId
|
||||
console.debug('[getWopiUrl] ' + wopiurl)
|
||||
return wopiurl
|
||||
}
|
||||
|
||||
const getWopiUrl = ({ fileId, title, readOnly, closeButton, revisionHistory, target = undefined }) => {
|
||||
// Only set the revision history parameter if the versions app is enabled
|
||||
revisionHistory = revisionHistory && window?.oc_appswebroots?.files_versions
|
||||
|
||||
// WOPISrc - URL that loolwsd will access (ie. pointing to ownCloud)
|
||||
// index.php is forced here to avoid different wopi srcs for the same document
|
||||
const wopiurl = window.location.protocol + '//' + window.location.host + getRootUrl() + '/index.php/apps/richdocuments/wopi/files/' + fileId
|
||||
console.debug('[getWopiUrl] ' + wopiurl)
|
||||
const wopisrc = encodeURIComponent(wopiurl)
|
||||
|
||||
// urlsrc - the URL from discovery xml that we access for the particular
|
||||
// document; we add various parameters to that.
|
||||
// The discovery is available at
|
||||
// https://<loolwsd-server>:9980/hosting/discovery
|
||||
return Config.get('urlsrc')
|
||||
+ 'WOPISrc=' + wopisrc
|
||||
+ 'WOPISrc=' + encodeURIComponent(getWopiSrc(fileId))
|
||||
+ '&title=' + encodeURIComponent(title)
|
||||
+ '&lang=' + languageToBCP47()
|
||||
+ (closeButton ? '&closebutton=1' : '')
|
||||
|
@ -97,6 +104,7 @@ const getNextcloudUrl = () => {
|
|||
export {
|
||||
getSearchParam,
|
||||
getWopiUrl,
|
||||
getCallbackBaseUrl,
|
||||
|
||||
getDocumentUrlFromTemplate,
|
||||
getDocumentUrlForPublicFile,
|
||||
|
|
|
@ -243,6 +243,7 @@ export default {
|
|||
fileId: fileid, shareToken: this.shareToken, version,
|
||||
})
|
||||
Config.update('urlsrc', data.urlSrc)
|
||||
Config.update('wopi_callback_url', loadState('richdocuments', 'wopi_callback_url', ''))
|
||||
|
||||
// Generate form and submit to the iframe
|
||||
const action = getWopiUrl({
|
||||
|
|
|
@ -7,7 +7,7 @@ script('files', 'jquery.fileupload');
|
|||
<div id="admin-vue" data-initial="<?php p(json_encode($_['settings'], true)); ?>"></div>
|
||||
|
||||
<?php if ($_['settings']['templatesAvailable'] === true) { ?>
|
||||
<form class="section" id="richdocuments-templates" method="post" action="/template/">
|
||||
<form class="section hidden" id="richdocuments-templates" method="post" action="/template/">
|
||||
<input name="files" class="hidden-visually" id="add-template" type="file" />
|
||||
<h2>
|
||||
<?php p($l->t('Global templates')) ?>
|
||||
|
|
Загрузка…
Ссылка в новой задаче