Skip to content

Commit 5e785fb

Browse files
authored
Merge pull request #49517 from nextcloud/feat/maximum.supported.desktop.version
2 parents 21666e0 + 8c0f8db commit 5e785fb

File tree

3 files changed

+82
-30
lines changed

3 files changed

+82
-30
lines changed

apps/dav/lib/Connector/Sabre/BlockLegacyClientPlugin.php

+15-3
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,26 @@ public function beforeHandler(RequestInterface $request) {
4949
return;
5050
}
5151

52-
$minimumSupportedDesktopVersion = $this->config->getSystemValue('minimum.supported.desktop.version', '2.3.0');
52+
$minimumSupportedDesktopVersion = $this->config->getSystemValueString('minimum.supported.desktop.version', '2.3.0');
53+
$maximumSupportedDesktopVersion = $this->config->getSystemValueString('maximum.supported.desktop.version', '99.99.99');
54+
55+
// Check if the client is a desktop client
5356
preg_match(IRequest::USER_AGENT_CLIENT_DESKTOP, $userAgent, $versionMatches);
54-
if (isset($versionMatches[1]) &&
55-
version_compare($versionMatches[1], $minimumSupportedDesktopVersion) === -1) {
57+
58+
// If the client is a desktop client and the version is too old, block it
59+
if (isset($versionMatches[1]) && version_compare($versionMatches[1], $minimumSupportedDesktopVersion) === -1) {
5660
$customClientDesktopLink = htmlspecialchars($this->themingDefaults->getSyncClientUrl());
5761
$minimumSupportedDesktopVersion = htmlspecialchars($minimumSupportedDesktopVersion);
5862

5963
throw new \Sabre\DAV\Exception\Forbidden("This version of the client is unsupported. Upgrade to <a href=\"$customClientDesktopLink\">version $minimumSupportedDesktopVersion or later</a>.");
6064
}
65+
66+
// If the client is a desktop client and the version is too new, block it
67+
if (isset($versionMatches[1]) && version_compare($versionMatches[1], $maximumSupportedDesktopVersion) === 1) {
68+
$customClientDesktopLink = htmlspecialchars($this->themingDefaults->getSyncClientUrl());
69+
$maximumSupportedDesktopVersion = htmlspecialchars($maximumSupportedDesktopVersion);
70+
71+
throw new \Sabre\DAV\Exception\Forbidden("This version of the client is unsupported. Downgrade to <a href=\"$customClientDesktopLink\">version $maximumSupportedDesktopVersion or earlier</a>.");
72+
}
6173
}
6274
}

apps/dav/tests/unit/Connector/Sabre/BlockLegacyClientPluginTest.php

+58-27
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616
use Sabre\HTTP\RequestInterface;
1717
use Test\TestCase;
1818

19+
enum ERROR_TYPE {
20+
case MIN_ERROR;
21+
case MAX_ERROR;
22+
case NONE;
23+
}
24+
1925
/**
2026
* Class BlockLegacyClientPluginTest
2127
*
@@ -40,29 +46,41 @@ protected function setUp(): void {
4046

4147
public static function oldDesktopClientProvider(): array {
4248
return [
43-
['Mozilla/5.0 (Windows) mirall/1.5.0'],
44-
['Mozilla/5.0 (Bogus Text) mirall/1.6.9'],
49+
['Mozilla/5.0 (Windows) mirall/1.5.0', ERROR_TYPE::MIN_ERROR],
50+
['Mozilla/5.0 (Bogus Text) mirall/1.6.9', ERROR_TYPE::MIN_ERROR],
51+
['Mozilla/5.0 (Windows) mirall/2.5.0', ERROR_TYPE::MAX_ERROR],
52+
['Mozilla/5.0 (Bogus Text) mirall/2.0.1', ERROR_TYPE::MAX_ERROR],
53+
['Mozilla/5.0 (Windows) mirall/2.0.0', ERROR_TYPE::NONE],
54+
['Mozilla/5.0 (Bogus Text) mirall/2.0.0', ERROR_TYPE::NONE],
4555
];
4656
}
4757

4858
/**
4959
* @dataProvider oldDesktopClientProvider
5060
*/
51-
public function testBeforeHandlerException(string $userAgent): void {
52-
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
53-
61+
public function testBeforeHandlerException(string $userAgent, ERROR_TYPE $errorType): void {
5462
$this->themingDefaults
55-
->expects($this->once())
63+
->expects($this->atMost(1))
5664
->method('getSyncClientUrl')
5765
->willReturn('https://nextcloud.com/install/#install-clients');
5866

5967
$this->config
60-
->expects($this->once())
61-
->method('getSystemValue')
62-
->with('minimum.supported.desktop.version', '2.3.0')
63-
->willReturn('1.7.0');
64-
65-
$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>.');
68+
->expects($this->exactly(2))
69+
->method('getSystemValueString')
70+
->willReturnCallback(function (string $key) {
71+
if ($key === 'minimum.supported.desktop.version') {
72+
return '1.7.0';
73+
}
74+
return '2.0.0';
75+
});
76+
77+
if ($errorType !== ERROR_TYPE::NONE) {
78+
$errorString = $errorType === ERROR_TYPE::MIN_ERROR
79+
? 'This version of the client is unsupported. Upgrade to <a href="https://nextcloud.com/install/#install-clients">version 1.7.0 or later</a>.'
80+
: 'This version of the client is unsupported. Downgrade to <a href="https://nextcloud.com/install/#install-clients">version 2.0.0 or earlier</a>.';
81+
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
82+
$this->expectExceptionMessage($errorString);
83+
}
6684

6785
/** @var RequestInterface|MockObject $request */
6886
$request = $this->createMock('\Sabre\HTTP\RequestInterface');
@@ -72,29 +90,35 @@ public function testBeforeHandlerException(string $userAgent): void {
7290
->with('User-Agent')
7391
->willReturn($userAgent);
7492

75-
7693
$this->blockLegacyClientVersionPlugin->beforeHandler($request);
7794
}
7895

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

86103
$this->themingDefaults
87-
->expects($this->once())
104+
->expects($this->atMost(1))
88105
->method('getSyncClientUrl')
89106
->willReturn('https://example.com"><script>alter("hacked");</script>');
90107

91108
$this->config
92-
->expects($this->once())
93-
->method('getSystemValue')
94-
->with('minimum.supported.desktop.version', '2.3.0')
95-
->willReturn('1.7.0 <script>alert("unsafe")</script>');
96-
97-
$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>.');
109+
->expects($this->exactly(2))
110+
->method('getSystemValueString')
111+
->willReturnCallback(function (string $key) {
112+
if ($key === 'minimum.supported.desktop.version') {
113+
return '1.7.0 <script>alert("unsafe")</script>';
114+
}
115+
return '2.0.0 <script>alert("unsafe")</script>';
116+
});
117+
118+
$errorString = $errorType === ERROR_TYPE::MIN_ERROR
119+
? '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>.'
120+
: '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>.';
121+
$this->expectExceptionMessage($errorString);
98122

99123
/** @var RequestInterface|MockObject $request */
100124
$request = $this->createMock('\Sabre\HTTP\RequestInterface');
@@ -104,15 +128,17 @@ public function testBeforeHandlerExceptionPreventXSSAttack(string $userAgent): v
104128
->with('User-Agent')
105129
->willReturn($userAgent);
106130

107-
108131
$this->blockLegacyClientVersionPlugin->beforeHandler($request);
109132
}
110133

111-
public function newAndAlternateDesktopClientProvider(): array {
134+
public static function newAndAlternateDesktopClientProvider(): array {
112135
return [
113136
['Mozilla/5.0 (Windows) mirall/1.7.0'],
114137
['Mozilla/5.0 (Bogus Text) mirall/1.9.3'],
115138
['Mozilla/5.0 (Not Our Client But Old Version) LegacySync/1.1.0'],
139+
['Mozilla/5.0 (Windows) mirall/4.7.0'],
140+
['Mozilla/5.0 (Bogus Text) mirall/3.9.3'],
141+
['Mozilla/5.0 (Not Our Client But Old Version) LegacySync/45.0.0'],
116142
];
117143
}
118144

@@ -129,10 +155,14 @@ public function testBeforeHandlerSuccess(string $userAgent): void {
129155
->willReturn($userAgent);
130156

131157
$this->config
132-
->expects($this->once())
133-
->method('getSystemValue')
134-
->with('minimum.supported.desktop.version', '2.3.0')
135-
->willReturn('1.7.0');
158+
->expects($this->exactly(2))
159+
->method('getSystemValueString')
160+
->willReturnCallback(function (string $key) {
161+
if ($key === 'minimum.supported.desktop.version') {
162+
return '1.7.0';
163+
}
164+
return '10.0.0';
165+
});
136166

137167
$this->blockLegacyClientVersionPlugin->beforeHandler($request);
138168
}
@@ -145,6 +175,7 @@ public function testBeforeHandlerNoUserAgent(): void {
145175
->method('getHeader')
146176
->with('User-Agent')
147177
->willReturn(null);
178+
148179
$this->blockLegacyClientVersionPlugin->beforeHandler($request);
149180
}
150181
}

config/config.sample.php

+9
Original file line numberDiff line numberDiff line change
@@ -2136,6 +2136,15 @@
21362136
*/
21372137
'minimum.supported.desktop.version' => '2.3.0',
21382138

2139+
/**
2140+
* The maximum Nextcloud desktop client version that will be allowed to sync with
2141+
* this server instance. All connections made from later clients will be denied
2142+
* by the server.
2143+
*
2144+
* Defaults to 99.99.99
2145+
*/
2146+
'maximum.supported.desktop.version' => '99.99.99',
2147+
21392148
/**
21402149
* Option to allow local storage to contain symlinks.
21412150
* WARNING: Not recommended. This would make it possible for Nextcloud to access

0 commit comments

Comments
 (0)