From bed3ffb3124ac9d4ab83ce21460356b00eeca82e Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 17 Aug 2023 08:45:46 +0200 Subject: [PATCH] feat(admin): Show an error when the admin is throttled Signed-off-by: Joas Schilling --- .../lib/Controller/CheckSetupController.php | 7 +++ .../Controller/CheckSetupControllerTest.php | 8 +++ core/js/setupchecks.js | 8 +++ core/js/tests/specs/setupchecksSpec.js | 61 +++++++++++++++++++ 4 files changed, 84 insertions(+) diff --git a/apps/settings/lib/Controller/CheckSetupController.php b/apps/settings/lib/Controller/CheckSetupController.php index 0353862bab0ba..80be57268b07c 100644 --- a/apps/settings/lib/Controller/CheckSetupController.php +++ b/apps/settings/lib/Controller/CheckSetupController.php @@ -90,6 +90,7 @@ use OCP\IURLGenerator; use OCP\Lock\ILockingProvider; use OCP\Notification\IManager; +use OCP\Security\Bruteforce\IThrottler; use OCP\Security\ISecureRandom; use Psr\Log\LoggerInterface; @@ -123,6 +124,8 @@ class CheckSetupController extends Controller { private $iniGetWrapper; /** @var IDBConnection */ private $connection; + /** @var IThrottler */ + private $throttler; /** @var ITempManager */ private $tempManager; /** @var IManager */ @@ -148,6 +151,7 @@ public function __construct($AppName, ISecureRandom $secureRandom, IniGetWrapper $iniGetWrapper, IDBConnection $connection, + IThrottler $throttler, ITempManager $tempManager, IManager $manager, IAppManager $appManager, @@ -162,6 +166,7 @@ public function __construct($AppName, $this->logger = $logger; $this->dispatcher = $dispatcher; $this->db = $db; + $this->throttler = $throttler; $this->lockingProvider = $lockingProvider; $this->dateTimeFormatter = $dateTimeFormatter; $this->memoryInfo = $memoryInfo; @@ -920,6 +925,8 @@ public function check() { 'cronInfo' => $this->getLastCronInfo(), 'cronErrors' => $this->getCronErrors(), 'isFairUseOfFreePushService' => $this->isFairUseOfFreePushService(), + 'isBruteforceThrottled' => $this->throttler->getAttempts($this->request->getRemoteAddress()) !== 0, + 'bruteforceRemoteAddress' => $this->request->getRemoteAddress(), 'serverHasInternetConnectionProblems' => $this->hasInternetConnectivityProblems(), 'isMemcacheConfigured' => $this->isMemcacheConfigured(), 'memcacheDocs' => $this->urlGenerator->linkToDocs('admin-performance'), diff --git a/apps/settings/tests/Controller/CheckSetupControllerTest.php b/apps/settings/tests/Controller/CheckSetupControllerTest.php index 564c1cbb62d0c..d4af6bd603cd8 100644 --- a/apps/settings/tests/Controller/CheckSetupControllerTest.php +++ b/apps/settings/tests/Controller/CheckSetupControllerTest.php @@ -59,6 +59,7 @@ use OCP\IURLGenerator; use OCP\Lock\ILockingProvider; use OCP\Notification\IManager; +use OCP\Security\Bruteforce\IThrottler; use PHPUnit\Framework\MockObject\MockObject; use Psr\Http\Message\ResponseInterface; use Psr\Log\LoggerInterface; @@ -143,6 +144,7 @@ protected function setUp(): void { $this->logger = $this->getMockBuilder(LoggerInterface::class)->getMock(); $this->db = $this->getMockBuilder(Connection::class) ->disableOriginalConstructor()->getMock(); + $this->throttler = $this->createMock(IThrottler::class); $this->lockingProvider = $this->getMockBuilder(ILockingProvider::class)->getMock(); $this->dateTimeFormatter = $this->getMockBuilder(IDateTimeFormatter::class)->getMock(); $this->memoryInfo = $this->getMockBuilder(MemoryInfo::class) @@ -174,6 +176,7 @@ protected function setUp(): void { $this->secureRandom, $this->iniGetWrapper, $this->connection, + $this->throttler, $this->tempManager, $this->notificationManager, $this->appManager, @@ -659,6 +662,8 @@ public function testCheck() { 'isFairUseOfFreePushService' => false, 'temporaryDirectoryWritable' => false, \OCA\Settings\SetupChecks\LdapInvalidUuids::class => ['pass' => true, 'description' => 'Invalid UUIDs of LDAP users or groups have been found. Please review your "Override UUID detection" settings in the Expert part of the LDAP configuration and use "occ ldap:update-uuid" to update them.', 'severity' => 'warning'], + 'isBruteforceThrottled' => false, + 'bruteforceRemoteAddress' => '', ] ); $this->assertEquals($expected, $this->checkSetupController->check()); @@ -683,6 +688,7 @@ public function testGetCurlVersion() { $this->secureRandom, $this->iniGetWrapper, $this->connection, + $this->throttler, $this->tempManager, $this->notificationManager, $this->appManager, @@ -1410,6 +1416,7 @@ public function testIsMysqlUsedWithoutUTF8MB4(string $db, bool $useUTF8MB4, bool $this->secureRandom, $this->iniGetWrapper, $this->connection, + $this->throttler, $this->tempManager, $this->notificationManager, $this->appManager, @@ -1464,6 +1471,7 @@ public function testIsEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(string $m $this->secureRandom, $this->iniGetWrapper, $this->connection, + $this->throttler, $this->tempManager, $this->notificationManager, $this->appManager, diff --git a/core/js/setupchecks.js b/core/js/setupchecks.js index 4fb020e44a311..c3e892de2945d 100644 --- a/core/js/setupchecks.js +++ b/core/js/setupchecks.js @@ -215,6 +215,14 @@ type: OC.SetupChecks.MESSAGE_TYPE_INFO }); } + if (data.isBruteforceThrottled) { + messages.push({ + msg: t('core', 'Your remote address was identified as "{remoteAddress}" and is bruteforce throttled at the moment slowing down the performance of various requests. If the remote address is not your address this can be an indication that a proxy is not configured correctly. Further information can be found in the {linkstart}documentation ↗{linkend}.', { remoteAddress: data.bruteforceRemoteAddress }) + .replace('{linkstart}', '') + .replace('{linkend}', ''), + type: OC.SetupChecks.MESSAGE_TYPE_ERROR + }); + } if(!data.hasWorkingFileLocking) { messages.push({ msg: t('core', 'Transactional file locking is disabled, this might lead to issues with race conditions. Enable "filelocking.enabled" in config.php to avoid these problems. See the {linkstart}documentation ↗{linkend} for more information.') diff --git a/core/js/tests/specs/setupchecksSpec.js b/core/js/tests/specs/setupchecksSpec.js index 43f42d2610e66..163a21c46a786 100644 --- a/core/js/tests/specs/setupchecksSpec.js +++ b/core/js/tests/specs/setupchecksSpec.js @@ -814,6 +814,67 @@ describe('OC.SetupChecks tests', function() { }); }); + it('should return an error if the admin IP is bruteforce throttled', function(done) { + var async = OC.SetupChecks.checkSetup(); + + suite.server.requests[0].respond( + 200, + { + 'Content-Type': 'application/json', + }, + JSON.stringify({ + hasFileinfoInstalled: true, + isGetenvServerWorking: true, + isReadOnlyConfig: false, + wasEmailTestSuccessful: true, + hasWorkingFileLocking: true, + hasDBFileLocking: false, + hasValidTransactionIsolationLevel: true, + suggestedOverwriteCliURL: '', + isRandomnessSecure: true, + isFairUseOfFreePushService: true, + isBruteforceThrottled: true, + bruteforceRemoteAddress: '::1', + serverHasInternetConnectionProblems: false, + isMemcacheConfigured: true, + forwardedForHeadersWorking: true, + reverseProxyDocs: 'https://docs.nextcloud.com/foo/bar.html', + isCorrectMemcachedPHPModuleInstalled: true, + hasPassedCodeIntegrityCheck: true, + OpcacheSetupRecommendations: [], + isSettimelimitAvailable: true, + hasFreeTypeSupport: true, + missingIndexes: [], + missingPrimaryKeys: [], + missingColumns: [], + cronErrors: [], + cronInfo: { + diffInSeconds: 0 + }, + isMemoryLimitSufficient: true, + appDirsWithDifferentOwner: [], + isImagickEnabled: true, + areWebauthnExtensionsEnabled: true, + is64bit: true, + recommendedPHPModules: [], + pendingBigIntConversionColumns: [], + isMysqlUsedWithoutUTF8MB4: false, + isDefaultPhoneRegionSet: true, + isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed: true, + reverseProxyGeneratedURL: 'https://server', + temporaryDirectoryWritable: true, + }) + ); + + async.done(function( data, s, x ){ + expect(data).toEqual([{ + msg: 'Your remote address was identified as "::1" and is bruteforce throttled at the moment slowing down the performance of various requests. If the remote address is not your address this can be an indication that a proxy is not configured correctly. Further information can be found in the documentation ↗.', + type: OC.SetupChecks.MESSAGE_TYPE_ERROR + }]); + done(); + }); + }); + it('should return an error if set_time_limit is unavailable', function(done) { var async = OC.SetupChecks.checkSetup();