Skip to content

Commit 1442477

Browse files
Merge pull request #43713 from nextcloud/backport/43712/stable28
[stable28] fix: Add bruteforce protection to federation endpoint
2 parents 4d077e9 + 3324609 commit 1442477

File tree

2 files changed

+30
-3
lines changed

2 files changed

+30
-3
lines changed

apps/federation/lib/Controller/OCSAuthAPIController.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
use OCP\AppFramework\Utility\ITimeFactory;
3939
use OCP\BackgroundJob\IJobList;
4040
use OCP\IRequest;
41+
use OCP\Security\Bruteforce\IThrottler;
4142
use OCP\Security\ISecureRandom;
4243
use Psr\Log\LoggerInterface;
4344

@@ -56,6 +57,7 @@ class OCSAuthAPIController extends OCSController {
5657
private DbHandler $dbHandler;
5758
private LoggerInterface $logger;
5859
private ITimeFactory $timeFactory;
60+
private IThrottler $throttler;
5961

6062
public function __construct(
6163
string $appName,
@@ -65,7 +67,8 @@ public function __construct(
6567
TrustedServers $trustedServers,
6668
DbHandler $dbHandler,
6769
LoggerInterface $logger,
68-
ITimeFactory $timeFactory
70+
ITimeFactory $timeFactory,
71+
IThrottler $throttler
6972
) {
7073
parent::__construct($appName, $request);
7174

@@ -75,13 +78,15 @@ public function __construct(
7578
$this->dbHandler = $dbHandler;
7679
$this->logger = $logger;
7780
$this->timeFactory = $timeFactory;
81+
$this->throttler = $throttler;
7882
}
7983

8084
/**
8185
* Request received to ask remote server for a shared secret, for legacy end-points
8286
*
8387
* @NoCSRFRequired
8488
* @PublicPage
89+
* @BruteForceProtection(action=federationSharedSecret)
8590
*
8691
* @param string $url URL of the server
8792
* @param string $token Token of the server
@@ -100,6 +105,7 @@ public function requestSharedSecretLegacy(string $url, string $token): DataRespo
100105
*
101106
* @NoCSRFRequired
102107
* @PublicPage
108+
* @BruteForceProtection(action=federationSharedSecret)
103109
*
104110
* @param string $url URL of the server
105111
* @param string $token Token of the server
@@ -117,6 +123,7 @@ public function getSharedSecretLegacy(string $url, string $token): DataResponse
117123
*
118124
* @NoCSRFRequired
119125
* @PublicPage
126+
* @BruteForceProtection(action=federationSharedSecret)
120127
*
121128
* @param string $url URL of the server
122129
* @param string $token Token of the server
@@ -127,6 +134,7 @@ public function getSharedSecretLegacy(string $url, string $token): DataResponse
127134
*/
128135
public function requestSharedSecret(string $url, string $token): DataResponse {
129136
if ($this->trustedServers->isTrustedServer($url) === false) {
137+
$this->throttler->registerAttempt('federationSharedSecret', $this->request->getRemoteAddress());
130138
$this->logger->error('remote server not trusted (' . $url . ') while requesting shared secret', ['app' => 'federation']);
131139
throw new OCSForbiddenException();
132140
}
@@ -159,6 +167,7 @@ public function requestSharedSecret(string $url, string $token): DataResponse {
159167
*
160168
* @NoCSRFRequired
161169
* @PublicPage
170+
* @BruteForceProtection(action=federationSharedSecret)
162171
*
163172
* @param string $url URL of the server
164173
* @param string $token Token of the server
@@ -169,11 +178,13 @@ public function requestSharedSecret(string $url, string $token): DataResponse {
169178
*/
170179
public function getSharedSecret(string $url, string $token): DataResponse {
171180
if ($this->trustedServers->isTrustedServer($url) === false) {
181+
$this->throttler->registerAttempt('federationSharedSecret', $this->request->getRemoteAddress());
172182
$this->logger->error('remote server not trusted (' . $url . ') while getting shared secret', ['app' => 'federation']);
173183
throw new OCSForbiddenException();
174184
}
175185

176186
if ($this->isValidToken($url, $token) === false) {
187+
$this->throttler->registerAttempt('federationSharedSecret', $this->request->getRemoteAddress());
177188
$expectedToken = $this->dbHandler->getToken($url);
178189
$this->logger->error(
179190
'remote server (' . $url . ') didn\'t send a valid token (got "' . $token . '" but expected "'. $expectedToken . '") while getting shared secret',

apps/federation/tests/Controller/OCSAuthAPIControllerTest.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
use OCP\AppFramework\OCS\OCSForbiddenException;
3434
use OCP\AppFramework\Utility\ITimeFactory;
3535
use OCP\IRequest;
36+
use OCP\Security\Bruteforce\IThrottler;
3637
use OCP\Security\ISecureRandom;
3738
use Psr\Log\LoggerInterface;
3839
use Test\TestCase;
@@ -59,6 +60,9 @@ class OCSAuthAPIControllerTest extends TestCase {
5960
/** @var \PHPUnit\Framework\MockObject\MockObject|ITimeFactory */
6061
private $timeFactory;
6162

63+
/** @var \PHPUnit\Framework\MockObject\MockObject|IThrottler */
64+
private $throttler;
65+
6266
private OCSAuthAPIController $ocsAuthApi;
6367

6468
/** @var int simulated timestamp */
@@ -74,6 +78,7 @@ protected function setUp(): void {
7478
$this->jobList = $this->createMock(JobList::class);
7579
$this->logger = $this->createMock(LoggerInterface::class);
7680
$this->timeFactory = $this->createMock(ITimeFactory::class);
81+
$this->throttler = $this->createMock(IThrottler::class);
7782

7883
$this->ocsAuthApi = new OCSAuthAPIController(
7984
'federation',
@@ -83,7 +88,8 @@ protected function setUp(): void {
8388
$this->trustedServers,
8489
$this->dbHandler,
8590
$this->logger,
86-
$this->timeFactory
91+
$this->timeFactory,
92+
$this->throttler
8793
);
8894

8995
$this->timeFactory->method('getTime')
@@ -108,8 +114,14 @@ public function testRequestSharedSecret(string $token, string $localToken, bool
108114
} else {
109115
$this->jobList->expects($this->never())->method('add');
110116
$this->jobList->expects($this->never())->method('remove');
117+
if (!$isTrustedServer) {
118+
$this->throttler->expects($this->once())
119+
->method('registerAttempt')
120+
->with('federationSharedSecret');
121+
}
111122
}
112123

124+
113125
try {
114126
$this->ocsAuthApi->requestSharedSecret($url, $token);
115127
$this->assertTrue($ok);
@@ -144,7 +156,8 @@ public function testGetSharedSecret(bool $isTrustedServer, bool $isValidToken, b
144156
$this->trustedServers,
145157
$this->dbHandler,
146158
$this->logger,
147-
$this->timeFactory
159+
$this->timeFactory,
160+
$this->throttler
148161
]
149162
)->setMethods(['isValidToken'])->getMock();
150163

@@ -162,6 +175,9 @@ public function testGetSharedSecret(bool $isTrustedServer, bool $isValidToken, b
162175
} else {
163176
$this->secureRandom->expects($this->never())->method('generate');
164177
$this->trustedServers->expects($this->never())->method('addSharedSecret');
178+
$this->throttler->expects($this->once())
179+
->method('registerAttempt')
180+
->with('federationSharedSecret');
165181
}
166182

167183
try {

0 commit comments

Comments
 (0)