Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions .github/workflows/cypress-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ jobs:
collabora:
image: ${{ matrix.code-image == 'release' && 'collabora/code:latest' || 'juliushaertl/nc-code-nightly:latest' }}
env:
extra_params: '--o:ssl.enable=false --o:home_mode.enable=true'
aliasgroup1: 'http://172.17.0.1'
extra_params: '--o:ssl.enable=false --o:home_mode.enable=true --o:logging.level=debug'
aliasgroup1: 'http://172.17.0.1:8081'
ports:
- "9980:9980"

Expand Down Expand Up @@ -136,6 +136,10 @@ jobs:
php occ config:system:set memcache.local --value="\\OC\\Memcache\\APCu"
php -f index.php
PHP_CLI_SERVER_WORKERS=20 php -S 0.0.0.0:8081 &

sleep 10
curl -v http://172.17.0.1:8081/status.php

export OC_PASS=1234561
php occ user:add --password-from-env user1
php occ user:add --password-from-env user2
Expand All @@ -147,11 +151,10 @@ jobs:
php occ app:enable --force spreed
php occ app:list
php occ config:system:set trusted_domains 1 --value="172.17.0.1"
php occ config:system:set overwrite.cli.url --value="http://172.17.0.1:8081"

php occ config:app:set richdocuments wopi_url --value="http://localhost:9980"
php occ config:app:set richdocuments public_wopi_url --value="http://localhost:9980"
php occ config:system:set allow_local_remote_servers --value true --type bool
php occ richdocuments:activate-config
php occ richdocuments:activate-config --wopi-url="http://localhost:9980" --callback-url="http://172.17.0.1:8081"

curl http://admin:admin@localhost:8081/ocs/v1.php/cloud/capabilities\?format\=json -H 'OCS-APIRequest: true'

Expand Down Expand Up @@ -222,6 +225,10 @@ jobs:
path: data/nextcloud.log
retention-days: 5

- name: Dump docker logs on failure
if: failure()
uses: jwalton/gh-docker-logs@v2

summary:
runs-on: ubuntu-latest
needs: [ cypress ]
Expand Down
12 changes: 8 additions & 4 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
image: ${{ matrix.code-image == 'release' && 'collabora/code:latest' || 'ghcr.io/juliusknorr/code-nightly:latest' }}
env:
extra_params: '--o:ssl.enable=false'
aliasgroup1: 'http://nextcloud'
aliasgroup1: 'http://172.17.0.1:8081'
ports:
- "9980:9980"

Expand Down Expand Up @@ -108,6 +108,7 @@ jobs:
mkdir data
echo '<?php $CONFIG=["memcache.local"=>"\OC\Memcache\APCu","hashing_default_password"=>true];' > config/config.php
./occ maintenance:install --verbose --database=${{ matrix.databases }} --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password
./occ config:system:set overwrite.cli.url --value="http://172.17.0.1:8081"
./occ app:enable --force ${{ env.APP_NAME }}

- name: Run ${{ matrix.scenarios }} integration tests
Expand Down Expand Up @@ -143,7 +144,7 @@ jobs:
image: ghcr.io/juliusknorr/nextcloud-dev-code:latest
env:
extra_params: '--o:ssl.enable=false'
aliasgroup1: 'http://nextcloud'
aliasgroup1: 'http://172.17.0.1:8081'
ports:
- "9980:9980"

Expand Down Expand Up @@ -187,6 +188,7 @@ jobs:
mkdir data
echo '<?php $CONFIG=["memcache.local"=>"\OC\Memcache\APCu","hashing_default_password"=>true];' > config/config.php
./occ maintenance:install --verbose --database=${{ matrix.databases }} --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password
./occ config:system:set overwrite.cli.url --value="http://172.17.0.1:8081"
./occ app:enable --force ${{ env.APP_NAME }}

- name: Run ${{ matrix.scenarios }} integration tests
Expand Down Expand Up @@ -224,7 +226,7 @@ jobs:
image: ghcr.io/juliusknorr/nextcloud-dev-code:latest
env:
extra_params: '--o:ssl.enable=false'
aliasgroup1: 'http://nextcloud'
aliasgroup1: 'http://172.17.0.1:8081'
ports:
- "9980:9980"

Expand Down Expand Up @@ -268,6 +270,7 @@ jobs:
mkdir data
echo '<?php $CONFIG=["memcache.local"=>"\OC\Memcache\APCu","hashing_default_password"=>true];' > config/config.php
./occ maintenance:install --verbose --database=${{ matrix.databases }} --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password
./occ config:system:set overwrite.cli.url --value="http://172.17.0.1:8081"
./occ app:enable --force ${{ env.APP_NAME }}

- name: Run ${{ matrix.scenarios }} integration tests
Expand Down Expand Up @@ -315,7 +318,7 @@ jobs:
image: ghcr.io/juliusknorr/nextcloud-dev-code:latest
env:
extra_params: '--o:ssl.enable=false'
aliasgroup1: 'http://nextcloud'
aliasgroup1: 'http://172.17.0.1:8081'
ports:
- "9980:9980"

Expand Down Expand Up @@ -358,6 +361,7 @@ jobs:
echo '<?php $CONFIG=["memcache.local"=>"\OC\Memcache\APCu","hashing_default_password"=>true];' > config/config.php
./occ maintenance:install --verbose --database=oci --database-name=XE --database-host=127.0.0.1 --database-port=1521 --database-user=autotest --database-pass=owncloud --admin-user admin --admin-pass password
php -f index.php
./occ config:system:set overwrite.cli.url --value="http://172.17.0.1:8081"
./occ app:enable --force ${{ env.APP_NAME }}

- name: Run ${{ matrix.scenarios }} integration tests
Expand Down
5 changes: 5 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
use OCA\Richdocuments\Preview\OpenDocument;
use OCA\Richdocuments\Preview\Pdf;
use OCA\Richdocuments\Reference\OfficeTargetReferenceProvider;
use OCA\Richdocuments\SetupCheck\CollaboraUpdate;
use OCA\Richdocuments\SetupCheck\ConnectivityCheck;
use OCA\Richdocuments\Storage\SecureViewWrapper;
use OCA\Richdocuments\TaskProcessing\SlideDeckGenerationProvider;
use OCA\Richdocuments\TaskProcessing\SlideDeckGenerationTaskType;
Expand Down Expand Up @@ -107,6 +109,9 @@ public function register(IRegistrationContext $context): void {
$context->registerTaskProcessingProvider(TextToSpreadsheetProvider::class);
$context->registerTaskProcessingProvider(SlideDeckGenerationProvider::class);
$context->registerTaskProcessingTaskType(SlideDeckGenerationTaskType::class);

$context->registerSetupCheck(CollaboraUpdate::class);
$context->registerSetupCheck(ConnectivityCheck::class);
}

public function boot(IBootContext $context): void {
Expand Down
8 changes: 8 additions & 0 deletions lib/Command/ActivateConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int
return 1;
}

try {
$this->connectivityService->testWopiAccess($output);
} catch (\Throwable $e) {
$output->writeln('<error>Failed to verify WOPI connectivity');
$output->writeln($e->getMessage());
return 1;
}

try {
$this->connectivityService->autoConfigurePublicUrl();
} catch (\Throwable $e) {
Expand Down
9 changes: 2 additions & 7 deletions lib/Controller/SettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
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
Expand Down Expand Up @@ -69,9 +68,7 @@ public function __construct(

public function checkSettings(): DataResponse {
try {
$output = new NullOutput();
$this->connectivityService->testDiscovery($output);
$this->connectivityService->testCapabilities($output);
$this->connectivityService->test();
} catch (\Exception $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
return new DataResponse([
Expand Down Expand Up @@ -184,9 +181,7 @@ public function setSettings(
}

try {
$output = new NullOutput();
$this->connectivityService->testDiscovery($output);
$this->connectivityService->testCapabilities($output);
$this->connectivityService->test();
$this->connectivityService->autoConfigurePublicUrl();
} catch (\Throwable $e) {
return new JSONResponse([
Expand Down
12 changes: 12 additions & 0 deletions lib/Service/CapabilitiesService.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,18 @@ public function getProductName(): string {
return $this->l10n->t('Nextcloud Office');
}

public function isEnterprise(): bool {
return ($this->getCapabilities()['productName'] ?? false) === 'Collabora Online';
}

public function isCode(): bool {
return ($this->getCapabilities()['productName'] ?? false) === 'Collabora Online Development Edition';
}

public function hasWopiAccessCheck(): bool {
return $this->getCapabilities()['hasWopiAccessCheck'] ?? false;
}

public function hasOtherOOXMLApps(): bool {
if ($this->appManager->isEnabledForUser('officeonline')) {
return true;
Expand Down
75 changes: 68 additions & 7 deletions lib/Service/ConnectivityService.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,96 @@
namespace OCA\Richdocuments\Service;

use Exception;
use GuzzleHttp\Exception\ClientException;
use OCA\Richdocuments\AppConfig;
use OCA\Richdocuments\WOPI\Parser;
use OCP\Http\Client\IClientService;
use OCP\IL10N;
use OCP\IURLGenerator;
use Symfony\Component\Console\Output\OutputInterface;

class ConnectivityService {
public function __construct(
private AppConfig $appConfig,
private DiscoveryService $discoveryService,
private CapabilitiesService $capabilitiesService,
private IClientService $clientService,
private IURLGenerator $urlGenerator,
private Parser $parser,
private IL10N $l10n,
) {
}

/**
* @throws Exception
*/
public function testDiscovery(OutputInterface $output): void {
public function testDiscovery(?OutputInterface $output = null): void {
$this->discoveryService->resetCache();
$this->discoveryService->fetch();
$output->writeln('<info>✓ Fetched /hosting/discovery endpoint</info>');
$output?->writeln('<info>✓ Fetched /hosting/discovery endpoint</info>');

$this->parser->getUrlSrcValue('application/vnd.openxmlformats-officedocument.wordprocessingml.document');
$output->writeln('<info>✓ Valid mimetype response</info>');
$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>');
$output?->writeln('<info>✓ Valid capabilities entry</info>');
}

public function testCapabilities(OutputInterface $output): void {
public function testCapabilities(?OutputInterface $output = null): void {
$this->capabilitiesService->resetCache();
$this->capabilitiesService->fetch();
$output->writeln('<info>✓ Fetched /hosting/capabilities endpoint</info>');
$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>');
$output?->writeln('<info>✓ Detected WOPI server: ' . $this->capabilitiesService->getServerProductName() . ' ' . $this->capabilitiesService->getProductVersion() . '</info>');
}

public function testWopiAccess(?OutputInterface $output = null): void {
$client = $this->clientService->newClient();

if (!$this->capabilitiesService->hasWopiAccessCheck()) {
return;
}

$url = str_replace('/hosting/capabilities', '/hosting/wopiAccessCheck', $this->capabilitiesService->getCapabilitiesEndpoint());
$callbackUrl = $this->appConfig->getNextcloudUrl() ?: trim($this->urlGenerator->getAbsoluteURL(''), '/');

try {
$result = $client->post($url, array_merge(RemoteOptionsService::getDefaultOptions(), [
'body' => json_encode(['callbackUrl' => $callbackUrl . '/status.php']), 'headers' => ['Content-Type' => 'application/json']
]));
} catch (ClientException $e) {
$result = $e->getResponse();
}
$response = json_decode($result->getBody(), true);

$errorMessage = match ($response['status']) {
'Ok' => null,
'NotHttpSuccess' => $this->l10n->t('The connection was successful but the response to the request was not 200'),
'HostNotFound' => $this->l10n->t('DNS error, the host is not known by the Collabora Online server'),
'WopiHostNotAllowed' => $this->l10n->t('The host for this request is not allowed to be used as a WOPI Host, this is likely a configuration issue in coolwsd.xml'),
'ConnectionAborted' => $this->l10n->t('The connection was aborted by the destination server'),
'CertificateValidation' => $this->l10n->t('The certificate of the response is invalid or otherwise not accepted'),
Comment thread
juliusknorr marked this conversation as resolved.
'SelfSignedCertificate' => $this->l10n->t('The certificate of the response is self-signed and not trusted by the system'),
'ExpiredCertificate' => $this->l10n->t('The certificate of the response is expired'),
'SslHandshakeFail' => $this->l10n->t('Couldn’t establish an SSL/TLS connection'),
'MissingSsl' => $this->l10n->t('The response wasn’t using SSL/TLS contrary to expected'),
'NotHttps' => $this->l10n->t('HTTPS is expected to connect to Collabora Online as the WOPI host uses it. This is necessary to prevent mixed content errors.'),
'NoScheme' => $this->l10n->t('A scheme (http:// or https://) for the WOPI host URL must be specified'),
'Timeout' => $this->l10n->t('The request didn’t get a response within the time frame allowed'),
default => $this->l10n->t('Unknown error "%s". Check the server logs of Collabora for more details.', [$response['status']]),
};

if ($errorMessage) {
throw new \Exception(
$this->l10n->t('The Collabora server could not properly reach the Nextcloud server at %s', [$callbackUrl]) . ' ' . $errorMessage
);
}

$output?->writeln('WOPI access was verified');
}

/**
Expand All @@ -59,4 +111,13 @@ public function autoConfigurePublicUrl(): void {
$detectedUrl = $this->appConfig->domainOnly($determinedUrl);
$this->appConfig->setAppValue('public_wopi_url', $detectedUrl);
}

/**
* @throws Exception
*/
public function test(?OutputInterface $output = null): void {
$this->testDiscovery($output);
$this->testCapabilities($output);
$this->testWopiAccess($output);
}
}
68 changes: 68 additions & 0 deletions lib/SetupCheck/CollaboraUpdate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Richdocuments\SetupCheck;

use OCA\Richdocuments\Service\CapabilitiesService;
use OCP\Http\Client\IClientService;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\SetupCheck\ISetupCheck;
use OCP\SetupCheck\SetupResult;
use Psr\Log\LoggerInterface;

class CollaboraUpdate implements ISetupCheck {

public function __construct(
protected IL10N $l10n,
protected CapabilitiesService $capabilitiesService,
protected IURLGenerator $urlGenerator,
protected IClientService $clientService,
protected LoggerInterface $logger,
) {
}

public function getCategory(): string {
return 'office';
}

public function getName(): string {
return $this->l10n->t('Collabora server version check');
}

public function run(): SetupResult {
$client = $this->clientService->newClient();

$url = null;
if ($this->capabilitiesService->isCode()) {
$url = 'https://rating.collaboraonline.com/UpdateCheck';
}

if ($this->capabilitiesService->isEnterprise()) {
$url = 'https://rating.collaboraonline.com/UpdateCheck?product=cool';
}

if ($url === null) {
return SetupResult::success();
}

// FIXME internet conection check config

$response = $client->get($url, ['timeout' => 5]);
$response = json_decode($response->getBody(), true);
$latestVersion = $response['coolwsd_version'] ?? null;

$installedVersion = $this->capabilitiesService->getProductVersion();

if ($latestVersion !== null && version_compare($latestVersion, $installedVersion, '>')) {
return SetupResult::warning($this->l10n->t('Collabora server version is out of date. Currently using %s, new version is available: %s', [$installedVersion, $latestVersion]));
}

return SetupResult::success();
}
}
Loading
Loading