From 2e78425352efe90f43d96992b823dc4c3404d6aa Mon Sep 17 00:00:00 2001 From: Oleksander Piskun Date: Sat, 16 May 2026 06:04:28 +0000 Subject: [PATCH] fix: normalize missing bruteforce_protection and headers_to_exclude on ExApp routes Signed-off-by: Oleksander Piskun --- lib/Controller/ExAppProxyController.php | 11 +++--- lib/Db/ExAppMapper.php | 12 +++++++ lib/Service/ExAppService.php | 5 ++- lib/Service/HarpService.php | 7 ++-- tests/php/Db/ExAppMapperTest.php | 45 +++++++++++++++++++++++++ 5 files changed, 67 insertions(+), 13 deletions(-) create mode 100644 tests/php/Db/ExAppMapperTest.php diff --git a/lib/Controller/ExAppProxyController.php b/lib/Controller/ExAppProxyController.php index 0d0b4b2f..557ed305 100644 --- a/lib/Controller/ExAppProxyController.php +++ b/lib/Controller/ExAppProxyController.php @@ -15,6 +15,7 @@ use OC\Security\CSP\ContentSecurityPolicyNonceManager; use OCA\AppAPI\AppInfo\Application; use OCA\AppAPI\Db\ExApp; +use OCA\AppAPI\Db\ExAppMapper; use OCA\AppAPI\Db\ExAppRouteAccessLevel; use OCA\AppAPI\ProxyResponse; use OCA\AppAPI\Service\AppAPIService; @@ -261,9 +262,7 @@ private function prepareProxy( ); return null; } - $bruteforceProtection = isset($route['bruteforce_protection']) - ? json_decode($route['bruteforce_protection'], true) - : []; + $bruteforceProtection = ExAppMapper::parseJsonList($route['bruteforce_protection'] ?? null); if (!empty($bruteforceProtection)) { $delay = $this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), Application::APP_ID); } @@ -350,8 +349,10 @@ private function passesExAppProxyRouteAccessLevelCheck(int $accessLevel): bool { } private function buildHeadersWithExclude(array $route, array $headers): array { - $headersToExclude = json_decode($route['headers_to_exclude'], true); - $headersToExclude = array_map('strtolower', $headersToExclude); + $headersToExclude = array_map( + 'strtolower', + array_filter(ExAppMapper::parseJsonList($route['headers_to_exclude'] ?? null), 'is_string') + ); if (!in_array('x-origin-ip', $headersToExclude)) { $headersToExclude[] = 'x-origin-ip'; diff --git a/lib/Db/ExAppMapper.php b/lib/Db/ExAppMapper.php index 21666220..a50ab1d0 100644 --- a/lib/Db/ExAppMapper.php +++ b/lib/Db/ExAppMapper.php @@ -25,6 +25,18 @@ public function __construct(IDBConnection $db) { parent::__construct($db, 'ex_apps'); } + /** + * Decode a JSON-list column (`bruteforce_protection`, `headers_to_exclude`) into an array, + * tolerating NULL / non-string / malformed values from legacy rows. + */ + public static function parseJsonList(mixed $raw): array { + if (!is_string($raw)) { + return []; + } + $decoded = json_decode($raw, true); + return is_array($decoded) ? $decoded : []; + } + /** * @throws Exception * diff --git a/lib/Service/ExAppService.php b/lib/Service/ExAppService.php index e6a47740..48c7e426 100644 --- a/lib/Service/ExAppService.php +++ b/lib/Service/ExAppService.php @@ -432,9 +432,8 @@ public function getExApps(): array { public function registerExAppRoutes(ExApp $exApp, array $routes): ?ExApp { try { $this->exAppMapper->registerExAppRoutes($exApp, $routes); - $exApp->setRoutes($routes); - return $exApp; - } catch (Exception $e) { + return $this->exAppMapper->findByAppId($exApp->getAppid()); + } catch (Exception|MultipleObjectsReturnedException|DoesNotExistException $e) { $this->logger->error(sprintf('Error while registering ExApp %s routes: %s. Routes: %s', $exApp->getAppid(), $e->getMessage(), json_encode($routes))); return null; } diff --git a/lib/Service/HarpService.php b/lib/Service/HarpService.php index cfaabb44..f473a8f0 100644 --- a/lib/Service/HarpService.php +++ b/lib/Service/HarpService.php @@ -13,6 +13,7 @@ use GuzzleHttp\Exception\ClientException; use OCA\AppAPI\Db\DaemonConfig; use OCA\AppAPI\Db\ExApp; +use OCA\AppAPI\Db\ExAppMapper; use OCA\AppAPI\DeployActions\ManualActions; use OCP\ICertificateManager; use OCP\IConfig; @@ -116,14 +117,10 @@ public function getHarpExApp(ExApp $exApp): array { 'host' => $this->getExAppHost($exApp), 'port' => $exApp->getPort(), 'routes' => array_map(function ($route) { - $bruteforceList = json_decode($route['bruteforce_protection'], true); - if (!$bruteforceList) { - $bruteforceList = []; - } return [ 'url' => $route['url'], 'access_level' => $route['access_level'], - 'bruteforce_protection' => $bruteforceList, + 'bruteforce_protection' => ExAppMapper::parseJsonList($route['bruteforce_protection'] ?? null), ]; }, $exApp->getRoutes()), ]; diff --git a/tests/php/Db/ExAppMapperTest.php b/tests/php/Db/ExAppMapperTest.php new file mode 100644 index 00000000..03977031 --- /dev/null +++ b/tests/php/Db/ExAppMapperTest.php @@ -0,0 +1,45 @@ + [null, []], + 'empty string' => ['', []], + 'empty array string' => ['[]', []], + 'valid array of ints' => ['[401,403]', [401, 403]], + 'valid array of strings' => ['["Cookie","Authorization"]', ['Cookie', 'Authorization']], + 'invalid json' => ['{broken', []], + 'json literal "null"' => ['null', []], + 'json scalar string' => ['"foo"', []], + 'json scalar int' => ['42', []], + 'non-string input (array)' => [['Cookie'], []], + 'non-string input (int)' => [42, []], + 'non-string input (bool)' => [false, []], + ]; + } +}