Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(config): add maximum.supported.desktop.version #49517

Merged
merged 1 commit into from
Nov 27, 2024
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
18 changes: 15 additions & 3 deletions apps/dav/lib/Connector/Sabre/BlockLegacyClientPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,26 @@ 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) {

// 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>.");
}
}
}
85 changes: 58 additions & 27 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())
->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>.');
->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 <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())
->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>.');
->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 <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())
->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 @@ -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
Loading