diff options
author | Julius Härtl <jus@bitgrid.net> | 2021-06-29 15:42:10 +0300 |
---|---|---|
committer | Julius Härtl <jus@bitgrid.net> | 2021-06-29 19:24:23 +0300 |
commit | bb8daed903274c4a2b92e5ed558c6e197238756d (patch) | |
tree | 5af7ea2ef65ce551462ee0cfbe5e85205c1c2eeb /lib | |
parent | 701d34a9bdf9a6267e7f9400e58bdb1c35b6d3d0 (diff) |
Allow to limit wopi requests to specific source hosts
Signed-off-by: Julius Härtl <jus@bitgrid.net>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/AppInfo/Application.php | 2 | ||||
-rw-r--r-- | lib/Controller/SettingsController.php | 6 | ||||
-rw-r--r-- | lib/Middleware/WOPIMiddleware.php | 142 | ||||
-rw-r--r-- | lib/Settings/Admin.php | 1 |
4 files changed, 151 insertions, 0 deletions
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 638842af..5701d50e 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -29,6 +29,7 @@ use OC\Files\Type\Detection; use OC\Security\CSP\ContentSecurityPolicy; use OCA\Richdocuments\AppConfig; use OCA\Richdocuments\Capabilities; +use OCA\Richdocuments\Middleware\WOPIMiddleware; use OCA\Richdocuments\PermissionManager; use OCA\Richdocuments\Preview\MSExcel; use OCA\Richdocuments\Preview\MSWord; @@ -64,6 +65,7 @@ class Application extends App implements IBootstrap { public function register(IRegistrationContext $context): void { $context->registerTemplateProvider(CollaboraTemplateProvider::class); $context->registerCapability(Capabilities::class); + $context->registerMiddleWare(WOPIMiddleware::class); } public function boot(IBootContext $context): void { diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index ce73dd1d..bec87a4b 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -111,6 +111,7 @@ class SettingsController extends Controller{ public function getSettings() { return new JSONResponse([ 'wopi_url' => $this->appConfig->getAppValue('wopi_url'), + 'wopi_allowlist' => $this->appConfig->getAppValue('wopi_allowlist'), 'public_wopi_url' => $this->appConfig->getAppValue('public_wopi_url'), 'disable_certificate_verification' => $this->appConfig->getAppValue('disable_certificate_verification') === 'yes', 'edit_groups' => $this->appConfig->getAppValue('edit_groups'), @@ -130,6 +131,7 @@ class SettingsController extends Controller{ * @return JSONResponse */ public function setSettings($wopi_url, + $wopi_allowlist, $disable_certificate_verification, $edit_groups, $use_groups, @@ -142,6 +144,10 @@ class SettingsController extends Controller{ $this->appConfig->setAppValue('wopi_url', $wopi_url); } + if ($wopi_allowlist !== null){ + $this->appConfig->setAppValue('wopi_allowlist', $wopi_allowlist); + } + if ($disable_certificate_verification !== null) { $this->appConfig->setAppValue( 'disable_certificate_verification', diff --git a/lib/Middleware/WOPIMiddleware.php b/lib/Middleware/WOPIMiddleware.php new file mode 100644 index 00000000..9f6420e9 --- /dev/null +++ b/lib/Middleware/WOPIMiddleware.php @@ -0,0 +1,142 @@ +<?php +/* + * @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net> + * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch> + * + * @author Julius Härtl <jus@bitgrid.net> + * @author Lukas Reschke <lukas@statuscode.ch> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +declare(strict_types=1); + +namespace OCA\Richdocuments\Middleware; + +use OCA\Richdocuments\AppInfo\Application; +use OCA\Richdocuments\Controller\WopiController; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Middleware; +use OCP\Files\NotPermittedException; +use OCP\IConfig; +use OCP\IRequest; + +class WOPIMiddleware extends Middleware { + + /** @var IConfig */ + private $config; + /** @var IRequest */ + private $request; + + public function __construct(IConfig $config, IRequest $request) { + $this->config = $config; + $this->request = $request; + } + + public function beforeController($controller, $methodName) { + parent::beforeController($controller, $methodName); + + if ($controller instanceof WopiController && !$this->isWOPIAllowed()) { + throw new NotPermittedException(); + } + } + + public function afterException($controller, $methodName, \Exception $exception): Response { + if ($exception instanceof NotPermittedException && $controller instanceof WopiController) { + return new JSONResponse([], Http::STATUS_FORBIDDEN); + } + + throw $exception; + } + + public function isWOPIAllowed(): bool { + $allowedRanges = $this->config->getAppValue(Application::APPNAME, 'wopi_allowlist'); + if($allowedRanges === '') { + return true; + } + $allowedRanges = explode(',', $allowedRanges); + + $userIp = $this->request->getRemoteAddress(); + foreach($allowedRanges as $range) { + if($this->matchCidr($userIp, $range)) { + return true; + } + } + + return false; + } + + /** + * @copyright https://stackoverflow.com/questions/594112/matching-an-ip-to-a-cidr-mask-in-php-5/594134#594134 + * @copyright (IPv4) https://stackoverflow.com/questions/594112/matching-an-ip-to-a-cidr-mask-in-php-5/594134#594134 + * @copyright (IPv6) MW. https://stackoverflow.com/questions/7951061/matching-ipv6-address-to-a-cidr-subnet via + */ + private function matchCidr(string $ip, string $range): bool { + list($subnet, $bits) = array_pad(explode('/', $range), 2, null); + if ($bits === null) { + $bits = 32; + } + $bits = (int)$bits; + + if ($this->isIpv4($ip) && $this->isIpv4($subnet)) { + $mask = -1 << (32 - $bits); + + $ip = ip2long($ip); + $subnet = ip2long($subnet); + $subnet &= $mask; + return ($ip & $mask) === $subnet; + } + + if ($this->isIpv6($ip) && $this->isIPv6($subnet)) { + $subnet = inet_pton($subnet); + $ip = inet_pton($ip); + + $binMask = str_repeat("f", $bits / 4); + switch ($bits % 4) { + case 0: + break; + case 1: + $binMask .= "8"; + break; + case 2: + $binMask .= "c"; + break; + case 3: + $binMask .= "e"; + break; + } + + $binMask = str_pad($binMask, 32, '0'); + $binMask = pack("H*", $binMask); + + if ( ($ip & $binMask) === $subnet ) { + return true; + } + } + return false; + } + + private function isIpv4($ip) { + return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); + } + + private function isIpv6($ip) { + return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6); + } +} diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php index 8dc8c3bd..01fea6bd 100644 --- a/lib/Settings/Admin.php +++ b/lib/Settings/Admin.php @@ -80,6 +80,7 @@ class Admin implements ISettings { [ 'settings' => [ 'wopi_url' => $this->config->getAppValue('richdocuments', 'wopi_url'), + 'wopi_allowlist' => $this->config->getAppValue('richdocuments', 'wopi_allowlist'), 'edit_groups' => $this->config->getAppValue('richdocuments', 'edit_groups'), 'use_groups' => $this->config->getAppValue('richdocuments', 'use_groups'), 'doc_format' => $this->config->getAppValue('richdocuments', 'doc_format'), |