generated from Nodarx/template
Add WOPI discovery service and connection diagnostics
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use KTXM\ServiceWopi\Console\CheckCommand;
|
||||
use KTXM\ServiceWopi\Discovery\Action;
|
||||
use KTXM\ServiceWopi\Discovery\DiscoveryClient;
|
||||
use KTXM\ServiceWopi\Discovery\DiscoveryDocument;
|
||||
use KTXM\ServiceWopi\Discovery\ServerAddress;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
final class DiscoveryTest extends TestCase
|
||||
{
|
||||
private function xml(string $action = 'view', string $url = 'https://office.test/editor?', string $requires = ''): string
|
||||
{
|
||||
return '<wopi-discovery><net-zone name="external-https"><app name="Word">'
|
||||
. '<action ext="docx" name="' . $action . '" requires="' . $requires
|
||||
. '" urlsrc="' . htmlspecialchars($url, ENT_XML1 | ENT_QUOTES) . '"/>'
|
||||
. '</app></net-zone><proof-key modulus="abc" exponent="AQAB"/></wopi-discovery>';
|
||||
}
|
||||
|
||||
public function testParsesActionsAndDoesNotPermitEditingWithoutHostCapabilities(): void
|
||||
{
|
||||
$server = new ServerAddress('https://OFFICE.test:443/');
|
||||
self::assertSame('https://office.test/hosting/discovery', $server->discoveryUrl());
|
||||
$document = DiscoveryDocument::parse($this->xml(), $server);
|
||||
self::assertTrue($document->hasProofKeys);
|
||||
self::assertTrue($document->actions[0]->supportedBy([]));
|
||||
$edit = DiscoveryDocument::parse($this->xml('edit'), $server)->actions[0];
|
||||
self::assertFalse($edit->supportedBy([]));
|
||||
self::assertFalse($edit->supportedBy(['locks']));
|
||||
self::assertTrue($edit->supportedBy(['locks', 'update']));
|
||||
$extra = DiscoveryDocument::parse($this->xml('view', requires: ' containers, future '), $server)->actions[0];
|
||||
self::assertFalse($extra->supportedBy([]));
|
||||
}
|
||||
|
||||
public function testAcceptsEuroOfficeExternalHttpLabelOnlyWithTrustedHttpsUrls(): void
|
||||
{
|
||||
$xml = str_replace('external-https', 'external-http', $this->xml(
|
||||
url: 'https://office.test/editor?<ui=UI_LLCC&><wopisrc=WOPI_SOURCE&>&',
|
||||
));
|
||||
$document = DiscoveryDocument::parse($xml, new ServerAddress('https://office.test'));
|
||||
$source = 'https://app.test/wopi/files/123';
|
||||
parse_str(parse_url($document->actions[0]->launchUrl($source), PHP_URL_QUERY), $parameters);
|
||||
self::assertSame(['wopisrc' => $source], $parameters);
|
||||
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
DiscoveryDocument::parse(str_replace('https://office.test', 'http://office.test', $xml), new ServerAddress('https://office.test'));
|
||||
}
|
||||
|
||||
#[DataProvider('invalidServers')]
|
||||
public function testRejectsInvalidServerAddresses(string $url): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
new ServerAddress($url);
|
||||
}
|
||||
|
||||
public static function invalidServers(): array
|
||||
{
|
||||
return array_map(static fn ($url) => [$url], [
|
||||
'http://office.test', 'file:///etc/passwd', 'https://user:pass@office.test',
|
||||
'https://office.test?secret=x', 'https://office.test/#fragment',
|
||||
"https://office.test/\r\n", 'https://office.test\\@other.test',
|
||||
]);
|
||||
}
|
||||
|
||||
#[DataProvider('invalidDocuments')]
|
||||
public function testRejectsInvalidDiscovery(string $xml): void
|
||||
{
|
||||
$this->expectException(RuntimeException::class);
|
||||
DiscoveryDocument::parse($xml, new ServerAddress('https://office.test'));
|
||||
}
|
||||
|
||||
public static function invalidDocuments(): array
|
||||
{
|
||||
return [
|
||||
[''], ['<html><body>Welcome</body></html>'], ['<wopi-discovery>'],
|
||||
['<!DOCTYPE wopi-discovery [<!ENTITY x SYSTEM "file:///etc/passwd">]><wopi-discovery>&x;</wopi-discovery>'],
|
||||
['<wopi-discovery><net-zone name="external-http"><app/></net-zone></wopi-discovery>'],
|
||||
[str_repeat('x', DiscoveryDocument::MAX_BYTES + 1)],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('untrustedActions')]
|
||||
public function testRejectsUntrustedActions(string $url): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
DiscoveryDocument::parse($this->xml(url: $url), new ServerAddress('https://office.test'));
|
||||
}
|
||||
|
||||
public static function untrustedActions(): array
|
||||
{
|
||||
return array_map(static fn ($url) => [$url], [
|
||||
'http://office.test/editor', 'https://evil.test/editor',
|
||||
'https://office.test.evil.test/editor', 'https://office.test:8443/editor',
|
||||
'https://user@office.test/editor', '//office.test/editor',
|
||||
'javascript:alert(1)', 'https://office.test/editor#fragment',
|
||||
]);
|
||||
}
|
||||
|
||||
#[DataProvider('launchTemplates')]
|
||||
public function testBuildsLaunchUrlWithoutTokenAndRemovesUnknownPlaceholders(string $template): void
|
||||
{
|
||||
$action = DiscoveryDocument::parse($this->xml(url: $template), new ServerAddress('https://office.test'))->actions[0];
|
||||
$source = 'https://app.test/m/service_wopi/files/opaque-id';
|
||||
$url = $action->launchUrl($source);
|
||||
self::assertStringNotContainsString('<', $url);
|
||||
self::assertStringNotContainsString('access_token', $url);
|
||||
parse_str(parse_url($url, PHP_URL_QUERY), $parameters);
|
||||
self::assertSame($source, $parameters['WOPISrc']);
|
||||
self::assertCount(1, $parameters);
|
||||
}
|
||||
|
||||
public static function launchTemplates(): array
|
||||
{
|
||||
return [
|
||||
['https://office.test/editor'],
|
||||
['https://office.test/editor?'],
|
||||
['https://office.test/editor?<ui=UI_LLCC&><future=UNKNOWN&>'],
|
||||
['https://office.test/editor?<WOPISrc=WOPI_SOURCE&><ui=UI_LLCC&>'],
|
||||
];
|
||||
}
|
||||
|
||||
public function testCommandReports404WithoutClaimingConnectionSuccess(): void
|
||||
{
|
||||
$client = $this->createStub(DiscoveryClient::class);
|
||||
$client->method('fetch')->willThrowException(new RuntimeException('Discovery returned HTTP 404.'));
|
||||
$command = new CommandTester(new CheckCommand($client));
|
||||
self::assertSame(1, $command->execute(['server' => 'https://office.test']));
|
||||
self::assertStringContainsString('HTTP 404', $command->getDisplay());
|
||||
}
|
||||
|
||||
public function testCommandQualifiesViewingOnly(): void
|
||||
{
|
||||
$client = $this->createStub(DiscoveryClient::class);
|
||||
$client->method('fetch')->willReturn(DiscoveryDocument::parse($this->xml(), new ServerAddress('https://office.test')));
|
||||
$command = new CommandTester(new CheckCommand($client));
|
||||
self::assertSame(0, $command->execute(['server' => 'https://office.test']));
|
||||
self::assertStringContainsString('does not verify', $command->getDisplay());
|
||||
}
|
||||
|
||||
public function testEditOnlyDiscoveryDoesNotPassReadOnlyQualification(): void
|
||||
{
|
||||
$client = $this->createStub(DiscoveryClient::class);
|
||||
$client->method('fetch')->willReturn(DiscoveryDocument::parse($this->xml('edit'), new ServerAddress('https://office.test')));
|
||||
$command = new CommandTester(new CheckCommand($client));
|
||||
self::assertSame(1, $command->execute(['server' => 'https://office.test']));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
|
||||
$loader = require dirname(__DIR__, 4) . '/vendor/autoload.php';
|
||||
$loader->addPsr4('KTXM\\ServiceWopi\\', dirname(__DIR__, 2) . '/lib/');
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit bootstrap="bootstrap.php" failOnWarning="true" failOnNotice="true" failOnDeprecation="true" cacheDirectory="/tmp/ktrix-wopi-phpunit">
|
||||
<testsuites><testsuite name="WOPI"><directory>Unit</directory></testsuite></testsuites>
|
||||
</phpunit>
|
||||
Reference in New Issue
Block a user