Skip to content
Merged
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
19 changes: 15 additions & 4 deletions apps/dav/lib/Connector/Sabre/BlockLegacyClientPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,22 @@ public function beforeHandler(RequestInterface $request) {
return;
}

$minimumSupportedDesktopVersion = $this->config->getSystemValue('minimum.supported.desktop.version', '2.3.0');
$minimumSupportedDesktopVersion = $this->config->getSystemValueString('minimum.supported.desktop.version', '2.3.0');
$maximumSupportedDesktopVersion = $this->config->getSystemValueString('maximum.supported.desktop.version', '99.99.99');

// Check if the client is a desktop client
preg_match(IRequest::USER_AGENT_CLIENT_DESKTOP, $userAgent, $versionMatches);
if (isset($versionMatches[1]) &&
version_compare($versionMatches[1], $minimumSupportedDesktopVersion) === -1) {
throw new \Sabre\DAV\Exception\Forbidden('Unsupported client version.');

// If the client is a desktop client and the version is too old, block it
if (isset($versionMatches[1]) && version_compare($versionMatches[1], $minimumSupportedDesktopVersion) === -1) {
$minimumSupportedDesktopVersion = htmlspecialchars($minimumSupportedDesktopVersion);
throw new \Sabre\DAV\Exception\Forbidden("This version of the client is unsupported. Upgrade to version $minimumSupportedDesktopVersion or later.");
}

// If the client is a desktop client and the version is too new, block it
if (isset($versionMatches[1]) && version_compare($versionMatches[1], $maximumSupportedDesktopVersion) === 1) {
$maximumSupportedDesktopVersion = htmlspecialchars($maximumSupportedDesktopVersion);
throw new \Sabre\DAV\Exception\Forbidden("This version of the client is unsupported. Downgrade to version $maximumSupportedDesktopVersion or earlier.");
}
}
}
88 changes: 74 additions & 14 deletions apps/dav/tests/unit/Connector/Sabre/BlockLegacyClientPluginTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
use Sabre\HTTP\RequestInterface;
use Test\TestCase;

enum ERROR_TYPE {
case MIN_ERROR;
case MAX_ERROR;
case NONE;
}

/**
* Class BlockLegacyClientPluginTest
*
Expand All @@ -33,19 +39,38 @@ protected function setUp(): void {
$this->blockLegacyClientVersionPlugin = new BlockLegacyClientPlugin($this->config);
}

public function oldDesktopClientProvider(): array {
public static function oldDesktopClientProvider(): array {
return [
['Mozilla/5.0 (Windows) mirall/1.5.0'],
['Mozilla/5.0 (Bogus Text) mirall/1.6.9'],
['Mozilla/5.0 (Windows) mirall/1.5.0', ERROR_TYPE::MIN_ERROR],
['Mozilla/5.0 (Bogus Text) mirall/1.6.9', ERROR_TYPE::MIN_ERROR],
['Mozilla/5.0 (Windows) mirall/2.5.0', ERROR_TYPE::MAX_ERROR],
['Mozilla/5.0 (Bogus Text) mirall/2.0.1', ERROR_TYPE::MAX_ERROR],
['Mozilla/5.0 (Windows) mirall/2.0.0', ERROR_TYPE::NONE],
['Mozilla/5.0 (Bogus Text) mirall/2.0.0', ERROR_TYPE::NONE],
];
}

/**
* @dataProvider oldDesktopClientProvider
*/
public function testBeforeHandlerException(string $userAgent): void {
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
$this->expectExceptionMessage('Unsupported client version.');
public function testBeforeHandlerException(string $userAgent, ERROR_TYPE $errorType): void {
$this->config
->expects($this->exactly(2))
->method('getSystemValueString')
->willReturnCallback(function (string $key) {
if ($key === 'minimum.supported.desktop.version') {
return '1.7.0';
}
return '2.0.0';
});

if ($errorType !== ERROR_TYPE::NONE) {
$errorString = $errorType === ERROR_TYPE::MIN_ERROR
? 'This version of the client is unsupported. Upgrade to version 1.7.0 or later.'
: 'This version of the client is unsupported. Downgrade to version 2.0.0 or earlier.';
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
$this->expectExceptionMessage($errorString);
}

/** @var RequestInterface|MockObject $request */
$request = $this->createMock('\Sabre\HTTP\RequestInterface');
Expand All @@ -55,20 +80,50 @@ public function testBeforeHandlerException(string $userAgent): void {
->with('User-Agent')
->willReturn($userAgent);

$this->blockLegacyClientVersionPlugin->beforeHandler($request);
}

/**
* Ensure that there is no room for XSS attack through configured URL / version
* @dataProvider oldDesktopClientProvider
*/
public function testBeforeHandlerExceptionPreventXSSAttack(string $userAgent, ERROR_TYPE $errorType): void {
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);

$this->config
->expects($this->exactly(2))
->method('getSystemValueString')
->willReturnCallback(function (string $key) {
if ($key === 'minimum.supported.desktop.version') {
return '1.7.0 <script>alert("unsafe")</script>';
}
return '2.0.0 <script>alert("unsafe")</script>';
});

$errorString = $errorType === ERROR_TYPE::MIN_ERROR
? 'This version of the client is unsupported. Upgrade to version 1.7.0 &lt;script&gt;alert(&quot;unsafe&quot;)&lt;/script&gt; or later.'
: 'This version of the client is unsupported. Downgrade to version 2.0.0 &lt;script&gt;alert(&quot;unsafe&quot;)&lt;/script&gt; or earlier.';
$this->expectExceptionMessage($errorString);

/** @var RequestInterface|MockObject $request */
$request = $this->createMock('\Sabre\HTTP\RequestInterface');
$request
->expects($this->once())
->method('getSystemValue')
->with('minimum.supported.desktop.version', '2.3.0')
->willReturn('1.7.0');
->method('getHeader')
->with('User-Agent')
->willReturn($userAgent);

$this->blockLegacyClientVersionPlugin->beforeHandler($request);
}

public function newAndAlternateDesktopClientProvider(): array {
public static function newAndAlternateDesktopClientProvider(): array {
return [
['Mozilla/5.0 (Windows) mirall/1.7.0'],
['Mozilla/5.0 (Bogus Text) mirall/1.9.3'],
['Mozilla/5.0 (Not Our Client But Old Version) LegacySync/1.1.0'],
['Mozilla/5.0 (Windows) mirall/4.7.0'],
['Mozilla/5.0 (Bogus Text) mirall/3.9.3'],
['Mozilla/5.0 (Not Our Client But Old Version) LegacySync/45.0.0'],
];
}

Expand All @@ -85,10 +140,14 @@ public function testBeforeHandlerSuccess(string $userAgent): void {
->willReturn($userAgent);

$this->config
->expects($this->once())
->method('getSystemValue')
->with('minimum.supported.desktop.version', '2.3.0')
->willReturn('1.7.0');
->expects($this->exactly(2))
->method('getSystemValueString')
->willReturnCallback(function (string $key) {
if ($key === 'minimum.supported.desktop.version') {
return '1.7.0';
}
return '10.0.0';
});

$this->blockLegacyClientVersionPlugin->beforeHandler($request);
}
Expand All @@ -101,6 +160,7 @@ public function testBeforeHandlerNoUserAgent(): void {
->method('getHeader')
->with('User-Agent')
->willReturn(null);

$this->blockLegacyClientVersionPlugin->beforeHandler($request);
}
}
9 changes: 9 additions & 0 deletions config/config.sample.php
Original file line number Diff line number Diff line change
Expand Up @@ -2141,6 +2141,15 @@
*/
'minimum.supported.desktop.version' => '2.3.0',

/**
* The maximum Nextcloud desktop client version that will be allowed to sync with
* this server instance. All connections made from later clients will be denied
* by the server.
*
* Defaults to 99.99.99
*/
'maximum.supported.desktop.version' => '99.99.99',

/**
* Option to allow local storage to contain symlinks.
* WARNING: Not recommended. This would make it possible for Nextcloud to access
Expand Down
Loading