Skip to content

Commit

Permalink
feat(config): add maximum.supported.desktop.version
Browse files Browse the repository at this point in the history
Signed-off-by: skjnldsv <[email protected]>
  • Loading branch information
skjnldsv committed Nov 27, 2024
1 parent 21666e0 commit b872242
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 26 deletions.
16 changes: 14 additions & 2 deletions apps/dav/lib/Connector/Sabre/BlockLegacyClientPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,25 @@ public function beforeHandler(RequestInterface $request) {
}

$minimumSupportedDesktopVersion = $this->config->getSystemValue('minimum.supported.desktop.version', '2.3.0');
$maximumSupportedDesktopVersion = $this->config->getSystemValue('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) {

// 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) {
$customClientDesktopLink = htmlspecialchars($this->themingDefaults->getSyncClientUrl());
$minimumSupportedDesktopVersion = htmlspecialchars($minimumSupportedDesktopVersion);

throw new \Sabre\DAV\Exception\Forbidden("This version of the client is unsupported. Upgrade to <a href=\"$customClientDesktopLink\">version $minimumSupportedDesktopVersion or later</a>.");
}

// 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) {
$customClientDesktopLink = htmlspecialchars($this->themingDefaults->getSyncClientUrl());
$maximumSupportedDesktopVersion = htmlspecialchars($maximumSupportedDesktopVersion);

throw new \Sabre\DAV\Exception\Forbidden("This version of the client is unsupported. Downgrade to <a href=\"$customClientDesktopLink\">version $maximumSupportedDesktopVersion or earlier</a>.");
}
}
}
79 changes: 55 additions & 24 deletions apps/dav/tests/unit/Connector/Sabre/BlockLegacyClientPluginTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
use Sabre\HTTP\RequestInterface;
use Test\TestCase;

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

/**
* Class BlockLegacyClientPluginTest
*
Expand All @@ -40,29 +46,41 @@ protected function setUp(): void {

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);

public function testBeforeHandlerException(string $userAgent, ERROR_TYPE $errorType): void {
$this->themingDefaults
->expects($this->once())
->expects($this->atMost(1))
->method('getSyncClientUrl')
->willReturn('https://nextcloud.com/install/#install-clients');

$this->config
->expects($this->once())
->expects($this->exactly(2))
->method('getSystemValue')
->with('minimum.supported.desktop.version', '2.3.0')
->willReturn('1.7.0');

$this->expectExceptionMessage('This version of the client is unsupported. Upgrade to <a href="https://nextcloud.com/install/#install-clients">version 1.7.0 or later</a>.');
->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 <a href="https://nextcloud.com/install/#install-clients">version 1.7.0 or later</a>.'
: 'This version of the client is unsupported. Downgrade to <a href="https://nextcloud.com/install/#install-clients">version 2.0.0 or earlier</a>.';
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
$this->expectExceptionMessage($errorString);
}

/** @var RequestInterface|MockObject $request */
$request = $this->createMock('\Sabre\HTTP\RequestInterface');
Expand All @@ -72,29 +90,35 @@ 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): void {
public function testBeforeHandlerExceptionPreventXSSAttack(string $userAgent, ERROR_TYPE $errorType): void {
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);

$this->themingDefaults
->expects($this->once())
->expects($this->atMost(1))
->method('getSyncClientUrl')
->willReturn('https://example.com"><script>alter("hacked");</script>');

$this->config
->expects($this->once())
->expects($this->exactly(2))
->method('getSystemValue')
->with('minimum.supported.desktop.version', '2.3.0')
->willReturn('1.7.0 <script>alert("unsafe")</script>');

$this->expectExceptionMessage('This version of the client is unsupported. Upgrade to <a href="https://example.com&quot;&gt;&lt;script&gt;alter(&quot;hacked&quot;);&lt;/script&gt;">version 1.7.0 &lt;script&gt;alert(&quot;unsafe&quot;)&lt;/script&gt; or later</a>.');
->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 <a href="https://example.com&quot;&gt;&lt;script&gt;alter(&quot;hacked&quot;);&lt;/script&gt;">version 1.7.0 &lt;script&gt;alert(&quot;unsafe&quot;)&lt;/script&gt; or later</a>.'
: 'This version of the client is unsupported. Downgrade to <a href="https://example.com&quot;&gt;&lt;script&gt;alter(&quot;hacked&quot;);&lt;/script&gt;">version 2.0.0 &lt;script&gt;alert(&quot;unsafe&quot;)&lt;/script&gt; or earlier</a>.';
$this->expectExceptionMessage($errorString);

/** @var RequestInterface|MockObject $request */
$request = $this->createMock('\Sabre\HTTP\RequestInterface');
Expand All @@ -104,15 +128,17 @@ public function testBeforeHandlerExceptionPreventXSSAttack(string $userAgent): v
->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 @@ -129,10 +155,14 @@ public function testBeforeHandlerSuccess(string $userAgent): void {
->willReturn($userAgent);

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

$this->blockLegacyClientVersionPlugin->beforeHandler($request);
}
Expand All @@ -145,6 +175,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 @@ -2136,6 +2136,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

0 comments on commit b872242

Please sign in to comment.