Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>pull/34852/head
parent
aa81b87f26
commit
8aea25b5b9
@ -1,102 +0,0 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @copyright Copyright (c) 2021, Lukas Reschke <lukas@statuscode.ch> |
||||
* |
||||
* @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/>. |
||||
* |
||||
*/ |
||||
namespace OC\Http\Client; |
||||
|
||||
use IPLib\Address\IPv6; |
||||
use IPLib\Factory; |
||||
use IPLib\ParseStringFlag; |
||||
use OCP\Http\Client\LocalServerException; |
||||
use Psr\Log\LoggerInterface; |
||||
use Symfony\Component\HttpFoundation\IpUtils; |
||||
|
||||
class LocalAddressChecker { |
||||
private LoggerInterface $logger; |
||||
|
||||
public function __construct(LoggerInterface $logger) { |
||||
$this->logger = $logger; |
||||
} |
||||
|
||||
public function throwIfLocalIp(string $ip) : void { |
||||
$parsedIp = Factory::parseAddressString( |
||||
$ip, |
||||
ParseStringFlag::IPV4_MAYBE_NON_DECIMAL | ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED |
||||
); |
||||
if ($parsedIp === null) { |
||||
/* Not an IP */ |
||||
return; |
||||
} |
||||
/* Replace by normalized form */ |
||||
if ($parsedIp instanceof IPv6) { |
||||
$ip = (string)($parsedIp->toIPv4() ?? $parsedIp); |
||||
} else { |
||||
$ip = (string)$parsedIp; |
||||
} |
||||
|
||||
$localRanges = [ |
||||
'100.64.0.0/10', // See RFC 6598 |
||||
'192.0.0.0/24', // See RFC 6890 |
||||
]; |
||||
if ( |
||||
(bool)filter_var($ip, FILTER_VALIDATE_IP) && |
||||
( |
||||
!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) || |
||||
IpUtils::checkIp($ip, $localRanges) |
||||
)) { |
||||
$this->logger->warning("Host $ip was not connected to because it violates local access rules"); |
||||
throw new LocalServerException('Host violates local access rules'); |
||||
} |
||||
} |
||||
|
||||
public function throwIfLocalAddress(string $uri) : void { |
||||
$host = parse_url($uri, PHP_URL_HOST); |
||||
if ($host === false || $host === null) { |
||||
$this->logger->warning("Could not detect any host in $uri"); |
||||
throw new LocalServerException('Could not detect any host'); |
||||
} |
||||
|
||||
$host = idn_to_utf8(strtolower(urldecode($host))); |
||||
// Remove brackets from IPv6 addresses |
||||
if (strpos($host, '[') === 0 && substr($host, -1) === ']') { |
||||
$host = substr($host, 1, -1); |
||||
} |
||||
|
||||
// Disallow local network top-level domains from RFC 6762 |
||||
$localTopLevelDomains = ['local','localhost','intranet','internal','private','corp','home','lan']; |
||||
$topLevelDomain = substr((strrchr($host, '.') ?: ''), 1); |
||||
if (in_array($topLevelDomain, $localTopLevelDomains)) { |
||||
$this->logger->warning("Host $host was not connected to because it violates local access rules"); |
||||
throw new LocalServerException('Host violates local access rules'); |
||||
} |
||||
|
||||
// Disallow hostname only |
||||
if (substr_count($host, '.') === 0 && !(bool)filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { |
||||
$this->logger->warning("Host $host was not connected to because it violates local access rules"); |
||||
throw new LocalServerException('Host violates local access rules'); |
||||
} |
||||
|
||||
$this->throwIfLocalIp($host); |
||||
} |
||||
} |
||||
@ -0,0 +1,74 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/* |
||||
* @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at> |
||||
* |
||||
* @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at> |
||||
* |
||||
* @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/>. |
||||
*/ |
||||
|
||||
namespace OC\Net; |
||||
|
||||
use function filter_var; |
||||
use function in_array; |
||||
use function strrchr; |
||||
use function substr; |
||||
use function substr_count; |
||||
|
||||
/** |
||||
* Classifier for network hostnames |
||||
* |
||||
* @internal |
||||
*/ |
||||
class HostnameClassifier { |
||||
private const LOCAL_TOPLEVEL_DOMAINS = [ |
||||
'local', |
||||
'localhost', |
||||
'intranet', |
||||
'internal', |
||||
'private', |
||||
'corp', |
||||
'home', |
||||
'lan', |
||||
]; |
||||
|
||||
/** |
||||
* Check host identifier for local hostname |
||||
* |
||||
* IP addresses are not considered local. Use the IpAddressClassifier for those. |
||||
* |
||||
* @param string $hostname |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function isLocalHostname(string $hostname): bool { |
||||
// Disallow local network top-level domains from RFC 6762 |
||||
$topLevelDomain = substr((strrchr($hostname, '.') ?: ''), 1); |
||||
if (in_array($topLevelDomain, self::LOCAL_TOPLEVEL_DOMAINS)) { |
||||
return true; |
||||
} |
||||
|
||||
// Disallow hostname only |
||||
if (substr_count($hostname, '.') === 0 && !filter_var($hostname, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { |
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
} |
||||
@ -0,0 +1,81 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/* |
||||
* @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at> |
||||
* |
||||
* @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at> |
||||
* |
||||
* @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/>. |
||||
*/ |
||||
|
||||
namespace OC\Net; |
||||
|
||||
use IPLib\Address\IPv6; |
||||
use IPLib\Factory; |
||||
use IPLib\ParseStringFlag; |
||||
use Symfony\Component\HttpFoundation\IpUtils; |
||||
use function filter_var; |
||||
|
||||
/** |
||||
* Classifier for IP addresses |
||||
* |
||||
* @internal |
||||
*/ |
||||
class IpAddressClassifier { |
||||
private const LOCAL_ADDRESS_RANGES = [ |
||||
'100.64.0.0/10', // See RFC 6598 |
||||
'192.0.0.0/24', // See RFC 6890 |
||||
]; |
||||
|
||||
/** |
||||
* Check host identifier for local IPv4 and IPv6 address ranges |
||||
* |
||||
* Hostnames are not considered local. Use the HostnameClassifier for those. |
||||
* |
||||
* @param string $ip |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function isLocalAddress(string $ip): bool { |
||||
$parsedIp = Factory::parseAddressString( |
||||
$ip, |
||||
ParseStringFlag::IPV4_MAYBE_NON_DECIMAL | ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED |
||||
); |
||||
if ($parsedIp === null) { |
||||
/* Not an IP */ |
||||
return false; |
||||
} |
||||
/* Replace by normalized form */ |
||||
if ($parsedIp instanceof IPv6) { |
||||
$ip = (string)($parsedIp->toIPv4() ?? $parsedIp); |
||||
} else { |
||||
$ip = (string)$parsedIp; |
||||
} |
||||
|
||||
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { |
||||
/* Range address */ |
||||
return true; |
||||
} |
||||
if (IpUtils::checkIp($ip, self::LOCAL_ADDRESS_RANGES)) { |
||||
/* Within local range */ |
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
} |
||||
@ -0,0 +1,76 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/* |
||||
* @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at> |
||||
* |
||||
* @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at> |
||||
* |
||||
* @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/>. |
||||
*/ |
||||
|
||||
namespace OC\Security; |
||||
|
||||
use OC\Net\HostnameClassifier; |
||||
use OC\Net\IpAddressClassifier; |
||||
use OCP\IConfig; |
||||
use OCP\Security\IRemoteHostValidator; |
||||
use Psr\Log\LoggerInterface; |
||||
use function strpos; |
||||
use function strtolower; |
||||
use function substr; |
||||
use function urldecode; |
||||
|
||||
/** |
||||
* @internal |
||||
*/ |
||||
final class RemoteHostValidator implements IRemoteHostValidator { |
||||
private IConfig $config; |
||||
private HostnameClassifier $hostnameClassifier; |
||||
private IpAddressClassifier $ipAddressClassifier; |
||||
private LoggerInterface $logger; |
||||
|
||||
public function __construct(IConfig $config, |
||||
HostnameClassifier $hostnameClassifier, |
||||
IpAddressClassifier $ipAddressClassifier, |
||||
LoggerInterface $logger) { |
||||
$this->config = $config; |
||||
$this->hostnameClassifier = $hostnameClassifier; |
||||
$this->ipAddressClassifier = $ipAddressClassifier; |
||||
$this->logger = $logger; |
||||
} |
||||
|
||||
public function isValid(string $host): bool { |
||||
if ($this->config->getSystemValueBool('allow_local_remote_servers', false)) { |
||||
return true; |
||||
} |
||||
|
||||
$host = idn_to_utf8(strtolower(urldecode($host))); |
||||
// Remove brackets from IPv6 addresses |
||||
if (strpos($host, '[') === 0 && substr($host, -1) === ']') { |
||||
$host = substr($host, 1, -1); |
||||
} |
||||
|
||||
if ($this->hostnameClassifier->isLocalHostname($host) |
||||
|| $this->ipAddressClassifier->isLocalAddress($host)) { |
||||
$this->logger->warning("Host $host was not connected to because it violates local access rules"); |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
} |
||||
@ -0,0 +1,51 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/* |
||||
* @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at> |
||||
* |
||||
* @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at> |
||||
* |
||||
* @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/>. |
||||
*/ |
||||
|
||||
namespace OCP\Security; |
||||
|
||||
/** |
||||
* Validator for remote hosts |
||||
* |
||||
* @since 26.0.0 |
||||
*/ |
||||
interface IRemoteHostValidator { |
||||
|
||||
/** |
||||
* Validate if a host may be connected to |
||||
* |
||||
* By default, Nextcloud does not connect to any local servers. That is neither |
||||
* localhost nor any host in the local network. |
||||
* |
||||
* Admins can overwrite this behavior with the global `allow_local_remote_servers` |
||||
* settings flag. If the flag is set to `true`, local hosts will be considered |
||||
* valid. |
||||
* |
||||
* @param string $host hostname of the remote server, IPv4 or IPv6 address |
||||
* |
||||
* @return bool |
||||
* @since 26.0.0 |
||||
*/ |
||||
public function isValid(string $host): bool; |
||||
} |
||||
@ -1,158 +0,0 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @copyright Copyright (c) 2021, Lukas Reschke <lukas@statuscode.ch> |
||||
* |
||||
* @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/>. |
||||
* |
||||
*/ |
||||
|
||||
namespace Test\Http\Client; |
||||
|
||||
use OCP\Http\Client\LocalServerException; |
||||
use OC\Http\Client\LocalAddressChecker; |
||||
use Psr\Log\LoggerInterface; |
||||
|
||||
class LocalAddressCheckerTest extends \Test\TestCase { |
||||
/** @var LocalAddressChecker */ |
||||
private $localAddressChecker; |
||||
|
||||
protected function setUp(): void { |
||||
parent::setUp(); |
||||
|
||||
$logger = $this->createMock(LoggerInterface::class); |
||||
$this->localAddressChecker = new LocalAddressChecker($logger); |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider dataPreventLocalAddress |
||||
* @param string $uri |
||||
*/ |
||||
public function testThrowIfLocalAddress($uri) : void { |
||||
$this->expectException(LocalServerException::class); |
||||
$this->localAddressChecker->throwIfLocalAddress('http://' . $uri); |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider dataAllowLocalAddress |
||||
* @param string $uri |
||||
*/ |
||||
public function testThrowIfLocalAddressGood($uri) : void { |
||||
$this->localAddressChecker->throwIfLocalAddress('http://' . $uri); |
||||
$this->assertTrue(true); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* @dataProvider dataInternalIPs |
||||
* @param string $ip |
||||
*/ |
||||
public function testThrowIfLocalIpBad($ip) : void { |
||||
$this->expectException(LocalServerException::class); |
||||
$this->localAddressChecker->throwIfLocalIp($ip); |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider dataPublicIPs |
||||
* @param string $ip |
||||
*/ |
||||
public function testThrowIfLocalIpGood($ip) : void { |
||||
$this->localAddressChecker->throwIfLocalIp($ip); |
||||
$this->assertTrue(true); |
||||
} |
||||
|
||||
public function dataPublicIPs() : array { |
||||
return [ |
||||
['8.8.8.8'], |
||||
['8.8.4.4'], |
||||
['2001:4860:4860::8888'], |
||||
['2001:4860:4860::8844'], |
||||
]; |
||||
} |
||||
|
||||
public function dataInternalIPs() : array { |
||||
return [ |
||||
['192.168.0.1'], |
||||
['fe80::200:5aee:feaa:20a2'], |
||||
['0:0:0:0:0:ffff:10.0.0.1'], |
||||
['0:0:0:0:0:ffff:127.0.0.0'], |
||||
['10.0.0.1'], |
||||
['::'], |
||||
['::1'], |
||||
['100.100.100.200'], |
||||
['192.0.0.1'], |
||||
]; |
||||
} |
||||
|
||||
public function dataPreventLocalAddress():array { |
||||
return [ |
||||
['localhost/foo.bar'], |
||||
['localHost/foo.bar'], |
||||
['random-host/foo.bar'], |
||||
['[::1]/bla.blub'], |
||||
['[::]/bla.blub'], |
||||
['192.168.0.1'], |
||||
['172.16.42.1'], |
||||
['[fdf8:f53b:82e4::53]/secret.ics'], |
||||
['[fe80::200:5aee:feaa:20a2]/secret.ics'], |
||||
['[0:0:0:0:0:ffff:10.0.0.1]/secret.ics'], |
||||
['[0:0:0:0:0:ffff:127.0.0.0]/secret.ics'], |
||||
['10.0.0.1'], |
||||
['another-host.local'], |
||||
['service.localhost'], |
||||
['!@#$'], // test invalid url |
||||
['100.100.100.200'], |
||||
['192.0.0.1'], |
||||
['randomdomain.internal'], |
||||
['0177.0.0.9'], |
||||
['⑯⑨。②⑤④。⑯⑨。②⑤④'], |
||||
['127。②⑤④。⑯⑨.②⑤④'], |
||||
['127.0.00000000000000000000000000000000001'], |
||||
['127.1'], |
||||
['127.000.001'], |
||||
['0177.0.0.01'], |
||||
['0x7f.0x0.0x0.0x1'], |
||||
['0x7f000001'], |
||||
['2130706433'], |
||||
['00000000000000000000000000000000000000000000000000177.1'], |
||||
['0x7f.1'], |
||||
['127.0x1'], |
||||
['[0000:0000:0000:0000:0000:0000:0000:0001]'], |
||||
['[0:0:0:0:0:0:0:1]'], |
||||
['[0:0:0:0::0:0:1]'], |
||||
['%31%32%37%2E%30%2E%30%2E%31'], |
||||
['%31%32%37%2E%30%2E%30.%31'], |
||||
['[%3A%3A%31]'], |
||||
]; |
||||
} |
||||
|
||||
public function dataAllowLocalAddress():array { |
||||
return [ |
||||
['example.com/foo.bar'], |
||||
['example.net/foo.bar'], |
||||
['example.org/foo.bar'], |
||||
['8.8.8.8/bla.blub'], |
||||
['8.8.4.4/bla.blub'], |
||||
['8.8.8.8'], |
||||
['8.8.4.4'], |
||||
['[2001:4860:4860::8888]/secret.ics'], |
||||
]; |
||||
} |
||||
} |
||||
@ -0,0 +1,78 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/* |
||||
* @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at> |
||||
* |
||||
* @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at> |
||||
* |
||||
* @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/>. |
||||
*/ |
||||
|
||||
namespace lib\Net; |
||||
|
||||
use OC\Net\HostnameClassifier; |
||||
use Test\TestCase; |
||||
|
||||
class HostnameClassifierTest extends TestCase { |
||||
private HostnameClassifier $classifier; |
||||
|
||||
protected function setUp(): void { |
||||
parent::setUp(); |
||||
|
||||
$this->classifier = new HostnameClassifier(); |
||||
} |
||||
|
||||
public function localHostnamesData():array { |
||||
return [ |
||||
['localhost'], |
||||
['localHost'], |
||||
['random-host'], |
||||
['another-host.local'], |
||||
['service.localhost'], |
||||
['randomdomain.internal'], |
||||
]; |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider localHostnamesData |
||||
*/ |
||||
public function testLocalHostname(string $host): void { |
||||
$isLocal = $this->classifier->isLocalHostname($host); |
||||
|
||||
self::assertTrue($isLocal); |
||||
} |
||||
|
||||
public function publicHostnamesData(): array { |
||||
return [ |
||||
['example.com'], |
||||
['example.net'], |
||||
['example.org'], |
||||
['host.domain'], |
||||
['cloud.domain.tld'], |
||||
]; |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider publicHostnamesData |
||||
*/ |
||||
public function testPublicHostname(string $host): void { |
||||
$isLocal = $this->classifier->isLocalHostname($host); |
||||
|
||||
self::assertFalse($isLocal); |
||||
} |
||||
} |
||||
@ -0,0 +1,80 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/* |
||||
* @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at> |
||||
* |
||||
* @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at> |
||||
* |
||||
* @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/>. |
||||
*/ |
||||
|
||||
namespace lib\Net; |
||||
|
||||
use OC\Net\IpAddressClassifier; |
||||
use Test\TestCase; |
||||
|
||||
class IpAddressClassifierTest extends TestCase { |
||||
private IpAddressClassifier $classifier; |
||||
|
||||
protected function setUp(): void { |
||||
parent::setUp(); |
||||
|
||||
$this->classifier = new IpAddressClassifier(); |
||||
} |
||||
|
||||
public function publicIpAddressData(): array { |
||||
return [ |
||||
['8.8.8.8'], |
||||
['8.8.4.4'], |
||||
['2001:4860:4860::8888'], |
||||
['2001:4860:4860::8844'], |
||||
]; |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider publicIpAddressData |
||||
*/ |
||||
public function testPublicAddress(string $ip): void { |
||||
$isLocal = $this->classifier->isLocalAddress($ip); |
||||
|
||||
self::assertFalse($isLocal); |
||||
} |
||||
|
||||
public function localIpAddressData(): array { |
||||
return [ |
||||
['192.168.0.1'], |
||||
['fe80::200:5aee:feaa:20a2'], |
||||
['0:0:0:0:0:ffff:10.0.0.1'], |
||||
['0:0:0:0:0:ffff:127.0.0.0'], |
||||
['10.0.0.1'], |
||||
['::'], |
||||
['::1'], |
||||
['100.100.100.200'], |
||||
['192.0.0.1'], |
||||
]; |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider localIpAddressData |
||||
*/ |
||||
public function testLocalAddress(string $ip): void { |
||||
$isLocal = $this->classifier->isLocalAddress($ip); |
||||
|
||||
self::assertTrue($isLocal); |
||||
} |
||||
} |
||||
@ -0,0 +1,144 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/* |
||||
* @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at> |
||||
* |
||||
* @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at> |
||||
* |
||||
* @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/>. |
||||
*/ |
||||
|
||||
namespace lib\Security; |
||||
|
||||
use OC\Net\HostnameClassifier; |
||||
use OC\Net\IpAddressClassifier; |
||||
use OC\Security\RemoteHostValidator; |
||||
use OCP\IConfig; |
||||
use OCP\Server; |
||||
use PHPUnit\Framework\MockObject\MockObject; |
||||
use Psr\Log\NullLogger; |
||||
use Test\TestCase; |
||||
|
||||
class RemoteHostValidatorIntegrationTest extends TestCase { |
||||
|
||||
/** @var IConfig|IConfig&MockObject|MockObject */ |
||||
private IConfig $config; |
||||
private RemoteHostValidator $validator; |
||||
|
||||
protected function setUp(): void { |
||||
parent::setUp(); |
||||
|
||||
// Mock config to avoid any side effects |
||||
$this->config = $this->createMock(IConfig::class); |
||||
|
||||
$this->validator = new RemoteHostValidator( |
||||
$this->config, |
||||
Server::get(HostnameClassifier::class), |
||||
Server::get(IpAddressClassifier::class), |
||||
new NullLogger(), |
||||
); |
||||
} |
||||
|
||||
public function localHostsData(): array { |
||||
return [ |
||||
['[::1]'], |
||||
['[::]'], |
||||
['192.168.0.1'], |
||||
['172.16.42.1'], |
||||
['[fdf8:f53b:82e4::53]'], |
||||
['[fe80::200:5aee:feaa:20a2]'], |
||||
['[0:0:0:0:0:ffff:10.0.0.1]'], |
||||
['[0:0:0:0:0:ffff:127.0.0.0]'], |
||||
['10.0.0.1'], |
||||
['!@#$'], // test invalid url |
||||
['100.100.100.200'], |
||||
['192.0.0.1'], |
||||
['0177.0.0.9'], |
||||
['⑯⑨。②⑤④。⑯⑨。②⑤④'], |
||||
['127。②⑤④。⑯⑨.②⑤④'], |
||||
['127.0.00000000000000000000000000000000001'], |
||||
['127.1'], |
||||
['127.000.001'], |
||||
['0177.0.0.01'], |
||||
['0x7f.0x0.0x0.0x1'], |
||||
['0x7f000001'], |
||||
['2130706433'], |
||||
['00000000000000000000000000000000000000000000000000177.1'], |
||||
['0x7f.1'], |
||||
['127.0x1'], |
||||
['[0000:0000:0000:0000:0000:0000:0000:0001]'], |
||||
['[0:0:0:0:0:0:0:1]'], |
||||
['[0:0:0:0::0:0:1]'], |
||||
['%31%32%37%2E%30%2E%30%2E%31'], |
||||
['%31%32%37%2E%30%2E%30.%31'], |
||||
['[%3A%3A%31]'], |
||||
]; |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider localHostsData |
||||
*/ |
||||
public function testLocalHostsWhenNotAllowed(string $host): void { |
||||
$this->config |
||||
->method('getSystemValueBool') |
||||
->with('allow_local_remote_servers', false) |
||||
->willReturn(false); |
||||
|
||||
$isValid = $this->validator->isValid($host); |
||||
|
||||
self::assertFalse($isValid); |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider localHostsData |
||||
*/ |
||||
public function testLocalHostsWhenAllowed(string $host): void { |
||||
$this->config |
||||
->method('getSystemValueBool') |
||||
->with('allow_local_remote_servers', false) |
||||
->willReturn(true); |
||||
|
||||
$isValid = $this->validator->isValid($host); |
||||
|
||||
self::assertTrue($isValid); |
||||
} |
||||
|
||||
public function externalAddressesData():array { |
||||
return [ |
||||
['8.8.8.8'], |
||||
['8.8.4.4'], |
||||
['8.8.8.8'], |
||||
['8.8.4.4'], |
||||
['[2001:4860:4860::8888]'], |
||||
]; |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider externalAddressesData |
||||
*/ |
||||
public function testExternalHost(string $host): void { |
||||
$this->config |
||||
->method('getSystemValueBool') |
||||
->with('allow_local_remote_servers', false) |
||||
->willReturn(false); |
||||
|
||||
$isValid = $this->validator->isValid($host); |
||||
|
||||
self::assertTrue($isValid); |
||||
} |
||||
} |
||||
@ -0,0 +1,111 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/* |
||||
* @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at> |
||||
* |
||||
* @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at> |
||||
* |
||||
* @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/>. |
||||
*/ |
||||
|
||||
namespace lib\Security; |
||||
|
||||
use OC\Net\HostnameClassifier; |
||||
use OC\Net\IpAddressClassifier; |
||||
use OC\Security\RemoteHostValidator; |
||||
use OCP\IConfig; |
||||
use PHPUnit\Framework\MockObject\MockObject; |
||||
use Psr\Log\LoggerInterface; |
||||
use Test\TestCase; |
||||
|
||||
class RemoteHostValidatorTest extends TestCase { |
||||
|
||||
/** @var IConfig|IConfig&MockObject|MockObject */ |
||||
private IConfig $config; |
||||
/** @var HostnameClassifier|HostnameClassifier&MockObject|MockObject */ |
||||
private HostnameClassifier $hostnameClassifier; |
||||
/** @var IpAddressClassifier|IpAddressClassifier&MockObject|MockObject */ |
||||
private IpAddressClassifier $ipAddressClassifier; |
||||
/** @var MockObject|LoggerInterface|LoggerInterface&MockObject */ |
||||
private LoggerInterface $logger; |
||||
private RemoteHostValidator $validator; |
||||
|
||||
protected function setUp(): void { |
||||
parent::setUp(); |
||||
|
||||
$this->config = $this->createMock(IConfig::class); |
||||
$this->hostnameClassifier = $this->createMock(HostnameClassifier::class); |
||||
$this->ipAddressClassifier = $this->createMock(IpAddressClassifier::class); |
||||
$this->logger = $this->createMock(LoggerInterface::class); |
||||
|
||||
$this->validator = new RemoteHostValidator( |
||||
$this->config, |
||||
$this->hostnameClassifier, |
||||
$this->ipAddressClassifier, |
||||
$this->logger, |
||||
); |
||||
} |
||||
|
||||
public function testValid(): void { |
||||
$host = 'nextcloud.com'; |
||||
$this->hostnameClassifier |
||||
->method('isLocalHostname') |
||||
->with($host) |
||||
->willReturn(false); |
||||
$this->ipAddressClassifier |
||||
->method('isLocalAddress') |
||||
->with($host) |
||||
->willReturn(false); |
||||
|
||||
$valid = $this->validator->isValid($host); |
||||
|
||||
self::assertTrue($valid); |
||||
} |
||||
|
||||
public function testLocalHostname(): void { |
||||
$host = 'localhost'; |
||||
$this->hostnameClassifier |
||||
->method('isLocalHostname') |
||||
->with($host) |
||||
->willReturn(true); |
||||
$this->ipAddressClassifier |
||||
->method('isLocalAddress') |
||||
->with($host) |
||||
->willReturn(false); |
||||
|
||||
$valid = $this->validator->isValid($host); |
||||
|
||||
self::assertFalse($valid); |
||||
} |
||||
|
||||
public function testLocalAddress(): void { |
||||
$host = '10.0.0.10'; |
||||
$this->hostnameClassifier |
||||
->method('isLocalHostname') |
||||
->with($host) |
||||
->willReturn(false); |
||||
$this->ipAddressClassifier |
||||
->method('isLocalAddress') |
||||
->with($host) |
||||
->willReturn(true); |
||||
|
||||
$valid = $this->validator->isValid($host); |
||||
|
||||
self::assertFalse($valid); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue