generated from Nodarx/template
152 lines
6.9 KiB
PHP
152 lines
6.9 KiB
PHP
<?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']));
|
|
}
|
|
}
|