diff --git a/config/config.sample.php b/config/config.sample.php index 5190e46ad7f91..6dd0660781510 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -1523,9 +1523,7 @@ * - IPv4 addresses, e.g. `192.168.2.123` * - IPv4 ranges in CIDR notation, e.g. `192.168.2.0/24` * - IPv6 addresses, e.g. `fd9e:21a7:a92c:2323::1` - * - * _(CIDR notation for IPv6 is currently work in progress and thus not - * available as of yet)_ + * - IPv6 ranges in CIDR notation, e.g. `fd9e:21a7:a92c:2323::/64` * * When an incoming request's `REMOTE_ADDR` matches any of the IP addresses * specified here, it is assumed to be a proxy instead of a client. Thus, the diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index 2c745973ed2f4..bb3904003aaf0 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -13,6 +13,7 @@ * @author Lukas Reschke * @author Mitar * @author Morris Jobke + * @author Oliver Wegner * @author Robin Appelman * @author Robin McCorkell * @author Roeland Jago Douma @@ -43,6 +44,7 @@ use OC\Security\TrustedDomainHelper; use OCP\IConfig; use OCP\IRequest; +use OCP\Net\IIpAddressFactory; use OCP\Security\ICrypto; use OCP\Security\ISecureRandom; @@ -111,6 +113,8 @@ class Request implements \ArrayAccess, \Countable, IRequest { protected $crypto; /** @var CsrfTokenManager|null */ protected $csrfTokenManager; + /** @var IIpAddressFactory */ + protected $ipAddressFactory; /** @var bool */ protected $contentDecoded = false; @@ -136,12 +140,14 @@ public function __construct(array $vars= [], ISecureRandom $secureRandom = null, IConfig $config, CsrfTokenManager $csrfTokenManager = null, - string $stream = 'php://input') { + string $stream = 'php://input', + IIpAddressFactory $ipAddressFactory = null) { $this->inputStream = $stream; $this->items['params'] = []; $this->secureRandom = $secureRandom; $this->config = $config; $this->csrfTokenManager = $csrfTokenManager; + $this->ipAddressFactory = $ipAddressFactory; if(!array_key_exists('method', $vars)) { $vars['method'] = 'GET'; @@ -598,42 +604,28 @@ public function getId(): string { return $this->requestId; } - /** - * Checks if given $remoteAddress matches given $trustedProxy. - * If $trustedProxy is an IPv4 IP range given in CIDR notation, true will be returned if - * $remoteAddress is an IPv4 address within that IP range. - * Otherwise $remoteAddress will be compared to $trustedProxy literally and the result - * will be returned. - * @return boolean true if $remoteAddress matches $trustedProxy, false otherwise - */ - protected function matchesTrustedProxy($trustedProxy, $remoteAddress) { - $cidrre = '/^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\/([0-9]{1,2})$/'; - - if (preg_match($cidrre, $trustedProxy, $match)) { - $net = $match[1]; - $shiftbits = min(32, max(0, 32 - intval($match[2]))); - $netnum = ip2long($net) >> $shiftbits; - $ipnum = ip2long($remoteAddress) >> $shiftbits; - - return $ipnum === $netnum; - } - - return $trustedProxy === $remoteAddress; - } - /** * Checks if given $remoteAddress matches any entry in the given array $trustedProxies. - * For details regarding what "match" means, refer to `matchesTrustedProxy`. + * 'Matching' here means + * - $remoteAddress is either equal to an entry in $trustedProxies or + * - $remoteAddress is an IP address in the range of any IP ranges specified in $trustedProxies * @return boolean true if $remoteAddress matches any entry in $trustedProxies, false otherwise */ protected function isTrustedProxy($trustedProxies, $remoteAddress) { - foreach ($trustedProxies as $tp) { - if ($this->matchesTrustedProxy($tp, $remoteAddress)) { - return true; + if ($this->ipAddressFactory !== null) { + $ipAddressRemote = $this->ipAddressFactory->getInstance($remoteAddress); + + foreach ($trustedProxies as $tp) { + $ipAddressProxy = $this->ipAddressFactory->getInstance($tp); + if ($ipAddressProxy->containsAddress($ipAddressRemote)) { + return true; + } } - } - return false; + return false; + } else { + return \in_array($remoteAddress, $trustedProxies); + } } /** diff --git a/lib/private/Net/AbstractIpAddress.php b/lib/private/Net/AbstractIpAddress.php new file mode 100644 index 0000000000000..0c9b89eac43c5 --- /dev/null +++ b/lib/private/Net/AbstractIpAddress.php @@ -0,0 +1,149 @@ + + * + * @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 . + * + */ + +namespace OC\Net; + +use OCP\Net\IIpAddress; + +abstract class AbstractIpAddress implements IIpAddress { + abstract public function getMaxBitlength(): int; + abstract protected function getCidrRegex(): string; + abstract protected function matchCidr(IIpAddress $other): bool; + + protected $original = ''; + protected $netPart = ''; + protected $netmaskBits = 0; + + /** + * Constructor that takes an IP address in string form and + * initializes this instance to represent that address + * + * @param string $address + */ + public function __construct(string $address) { + $this->setOriginal($address); + + if (preg_match($this->getCidrRegex(), $address, $match)) { + $this->setNetPart($match[1]); + $this->setNetmaskBits(max(0, min($this->getMaxBitlength(), intval($match[2])))); + } else { + $this->setNetPart($address); + $this->setNetmaskbits($this->getMaxBitlength()); + } + } + + /** + * Sets the literal address string that this instance + * represents + * + * @param string $original + */ + protected function setOriginal(string $original) { + $this->original = $original; + } + + /** + * Returns the literal address string that this instance + * represents + * + * @return string + */ + protected function getOriginal(): string { + return $this->original; + } + + /** + * Sets the network part of the + * address/range represented by this instance + * + * @param string $netPart + */ + protected function setNetPart(string $netPart) { + $this->netPart = $netPart; + } + + /** + * Returns the network part of the + * address/range represented by this instance + * + * @return string + */ + protected function getNetPart(): string { + return $this->netPart; + } + + /** + * Sets the number of bits of the net part of the IP + * address/range represented by this instance + * + * @param int $bits + */ + protected function setNetmaskBits(int $bits) { + $this->netmaskBits = $bits; + } + + /** + * Returns the number of bits of the net part of the IP + * address/range represented by this instance + * + * @return int + */ + protected function getNetmaskBits(): int { + return $this->netmaskBits; + } + + /** + * Returns whether $other is literally equivalent to this instance + * + * @return bool + */ + protected function matchOriginal(IIpAddress $other): bool { + return $other->getOriginal() === $this->getOriginal(); + } + + /** + * Returns whether this instance represents an IP range (vs. + * a single IP address) + * + * @return bool + */ + public function isRange(): bool { + return $this->getNetmaskBits() < $this->getMaxBitlength(); + } + + /** + * Returns whether given $other address is either + * - equal to this instance regarding its IP address or + * - is contained in the IP address range represented by this instance + * + * @param IIpAddress $other + * @return bool + */ + public function containsAddress(IIpAddress $other): bool { + return $this->isRange() + ? $this->matchCidr($other) + : $this->matchOriginal($other); + } +} + diff --git a/lib/private/Net/IpAddressFactory.php b/lib/private/Net/IpAddressFactory.php new file mode 100644 index 0000000000000..ef9caf002ecd7 --- /dev/null +++ b/lib/private/Net/IpAddressFactory.php @@ -0,0 +1,63 @@ + + * + * @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 . + * + */ + +namespace OC\Net; + +use OCP\Net\IIpAddressFactory; +use OCP\Net\IIpAddress; +use OC\Net\IpAddressV4; +use OC\Net\IpAddressV6; + +/** + * This factory creates instances of IIpAddress, given an IP address + * string (e.g. "192.168.1.2" or subnet string in CIDR format (e.g. + * "192.168.1.0/24"). + */ +class IpAddressFactory implements IIpAddressFactory { + /** + * Returns whether $address represents an IPv6 address + * + * @param string $address + * @return bool + */ + protected static function isIpv6(string $address): bool { + return strpos($address, ':') !== false; + } + + /** + * Creates a new instance conforming to IIpAddress and + * representing the given $address. + * + * @param string $address + * @return IIpAddress + */ + public function getInstance(string $address): IIpAddress { + if (self::isIpv6($address)) { + return new IpAddressV6($address); + } else { + return new IpAddressV4($address); + } + } +} + diff --git a/lib/private/Net/IpAddressV4.php b/lib/private/Net/IpAddressV4.php new file mode 100644 index 0000000000000..e31fe1ef31979 --- /dev/null +++ b/lib/private/Net/IpAddressV4.php @@ -0,0 +1,65 @@ + + * + * @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 . + * + */ + +namespace OC\Net; + +use OCP\Net\IIpAddress; +use OC\Net\AbstractIpAddress; + +class IpAddressV4 extends AbstractIpAddress { + /** + * Returns the length of the represented IP address format in bits. + * + * @return int + */ + public function getMaxBitlength(): int { + return 32; + } + + /** + * Returns the regular expression for recognizing CIDR notation. + * + * @return string + */ + protected function getCidrRegex(): string { + return '/^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\/([0-9]{1,2})$/'; + } + + /** + * Returns whether given $other address is either + * - equal to this instance regarding its IP address or + * - is contained in the IP address range represented by this instance + * + * @param IIpAddress $other + * @return bool + */ + protected function matchCidr(IIpAddress $other): bool { + $shiftbits = $this->getMaxBitlength() - $this->getNetmaskBits(); + $thisnum = ip2long($this->getNetPart()) >> $shiftbits; + $othernum = ip2long($other->getNetPart()) >> $shiftbits; + + return $othernum === $thisnum; + } +} + diff --git a/lib/private/Net/IpAddressV6.php b/lib/private/Net/IpAddressV6.php new file mode 100644 index 0000000000000..7833b4450a6c6 --- /dev/null +++ b/lib/private/Net/IpAddressV6.php @@ -0,0 +1,85 @@ + + * + * @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 . + * + */ + +namespace OC\Net; + +use OCP\Net\IIpAddress; +use OC\Net\AbstractIpAddress; + +class IpAddressV6 extends AbstractIpAddress { + /** + * Returns the length of the represented IP address format in bits. + * + * @return int + */ + public function getMaxBitlength(): int { + return 128; + } + + /** + * Returns the regular expression for recognizing CIDR notation. + * + * @return string + */ + protected function getCidrRegex(): string { + return '/^([0-9a-fA-F:]+[0-9a-fA-F:]+)\/([0-9]{1,3})$/'; + } + + /** + * Returns whether given $other address is either + * - equal to this instance regarding its IP address or + * - is contained in the IP address range represented by this instance + * + * @param IIpAddress $other + * @return bool + */ + protected function matchCidr(IIpAddress $other): bool { + $thisaddrn = inet_pton($this->getNetPart()); + $otheraddrn = inet_pton($other->getNetPart()); + if ($thisaddrn === false || $otheraddrn === false) { + // if we can't handle ipV6 addresses, simply compare strings: + return $this->matchOriginal($other); + } + + $netbits = $this->getNetmaskBits(); + $thisaddra = unpack('C*', $thisaddrn); + $otheraddra = unpack('C*', $otheraddrn); + + for ($i = 1; $i <= ceil($netbits / 8); $i++) { + $mask = ($i * 8 <= $netbits) + ? 0xff + : 0xff ^ (0xff >> ($netbits % 8)); + + $thisaddrb = $thisaddra[$i] & $mask; + $otheraddrb = $otheraddra[$i] & $mask; + + if (($thisaddrb ^ $otheraddrb) !== 0) { + return false; + } + } + + return true; + } +} + diff --git a/lib/private/Server.php b/lib/private/Server.php index 8ae2cb7652cb7..f20dc67cb0e9c 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -19,6 +19,7 @@ * @author Jörn Friedrich Dreyer * @author Lukas Reschke * @author Morris Jobke + * @author Oliver Wegner * @author Piotr Mrówczyński * @author Robin Appelman * @author Robin McCorkell @@ -96,6 +97,7 @@ use OC\Mail\Mailer; use OC\Memcache\ArrayCache; use OC\Memcache\Factory; +use OC\Net\IpAddressFactory; use OC\Notification\Manager; use OC\OCS\DiscoveryService; use OC\Remote\Api\ApiFactory; @@ -150,6 +152,7 @@ use OCP\IUser; use OCP\Lock\ILockingProvider; use OCP\Log\ILogFactory; +use OCP\Net\IIpAddressFactory; use OCP\Remote\Api\IApiFactory; use OCP\Remote\IInstanceFactory; use OCP\RichObjectStrings\IValidator; @@ -824,7 +827,8 @@ public function __construct($webRoot, \OC\Config $config) { $this->getSecureRandom(), $this->getConfig(), $this->getCsrfTokenManager(), - $stream + $stream, + $this->getIpAddressFactory() ); }); $this->registerAlias('Request', \OCP\IRequest::class); @@ -1012,7 +1016,10 @@ public function __construct($webRoot, \OC\Config $config) { : null, ], $c->getSecureRandom(), - $c->getConfig() + $c->getConfig(), + null, + 'php://input', + $c->getIpAddressFactory() ); return new CryptoWrapper( @@ -1200,6 +1207,8 @@ public function __construct($webRoot, \OC\Config $config) { ); }); + $this->registerAlias(IIpAddressFactory::class, IpAddressFactory::class); + $this->connectDispatcher(); } @@ -2055,4 +2064,11 @@ public function getRemoteInstanceFactory() { public function getStorageFactory() { return $this->query(IStorageFactory::class); } + + /** + * @return \OCP\Net\IIpAddressFactory + */ + public function getIpAddressFactory() { + return $this->query(IIpAddressFactory::class); + } } diff --git a/lib/public/Net/IIpAddress.php b/lib/public/Net/IIpAddress.php new file mode 100644 index 0000000000000..2f084dd06f4db --- /dev/null +++ b/lib/public/Net/IIpAddress.php @@ -0,0 +1,58 @@ + + * + * @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 . + * + */ + +/** + * Public interface of ownCloud for use by core and apps. + * IIpAddress interface + */ + +namespace OCP\Net; + +/** + * This interface provides functionalities of an IP address or range, + * e.g. checking if an IP address is within an IP range. + * + * @since 16.0.0 + */ +interface IIpAddress { + + /** + * Returns whether this instance represents an IP range. + * + * @return boolean true if this is an IP range, false if it's a single IP address + * @since 16.0.0 + */ + public function isRange(): bool; + + /** + * Returns if $other is equal to or contained in the IP + * address(es) which this instance represents. + * + * @return boolean true if $other is part of (or equal to) $this in terms of + * IP range terms, false otherwise + * @since 16.0.0 + */ + public function containsAddress(IIpAddress $other): bool; +} + diff --git a/lib/public/Net/IIpAddressFactory.php b/lib/public/Net/IIpAddressFactory.php new file mode 100644 index 0000000000000..170618923d0c3 --- /dev/null +++ b/lib/public/Net/IIpAddressFactory.php @@ -0,0 +1,55 @@ + + * + * @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 . + * + */ + +/** + * Public interface of ownCloud for use by core and apps. + * IIpAddressFactory interface + */ + +namespace OCP\Net; + +/** + * This interface provides functionalities to create instances + * of IIpAddress + * + * @since 16.0.0 + */ +interface IIpAddressFactory { + + /** + * Returns a new instance of IIpAddress. The concrete implementation + * chosen depends on the kind of address (or subnet) that $address + * represents, e.g. + * - "192.168.0.2" for an Ipv4 address + * - "::1" for an Ipv6 address + * - "192.168.1.0/24" for an Ipv4 subnet + * - "2001:db8:85a3:8d3:1319:8a2e::/96" for an Ipv6 subnet + * + * @param string $address + * @return instance of IIpAddress + * @since 16.0.0 + */ + public function getInstance(string $address): IIpAddress; +} + diff --git a/tests/lib/AppFramework/Http/RequestTest.php b/tests/lib/AppFramework/Http/RequestTest.php index c0e8dc97ef28c..3f77a524290b0 100644 --- a/tests/lib/AppFramework/Http/RequestTest.php +++ b/tests/lib/AppFramework/Http/RequestTest.php @@ -3,6 +3,8 @@ * @copyright 2013 Thomas Tanghus (thomas@tanghus.net) * @copyright 2016 Lukas Reschke lukas@owncloud.com * + * @author Oliver Wegner + * * This file is licensed under the Affero General Public License version 3 or * later. * See the COPYING-README file. @@ -11,6 +13,7 @@ namespace Test\AppFramework\Http; use OC\AppFramework\Http\Request; +use OC\Net\IpAddressFactory; use OC\Security\CSRF\CsrfToken; use OC\Security\CSRF\CsrfTokenManager; use OCP\Security\ISecureRandom; @@ -43,6 +46,7 @@ protected function setUp() { $this->config = $this->getMockBuilder(IConfig::class)->getMock(); $this->csrfTokenManager = $this->getMockBuilder('\OC\Security\CSRF\CsrfTokenManager') ->disableOriginalConstructor()->getMock(); + $this->ipAddressFactory = new IpAddressFactory(); } protected function tearDown() { @@ -61,7 +65,8 @@ public function testRequestAccessors() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); // Countable @@ -94,7 +99,8 @@ public function testPrecedence() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame(3, count($request)); @@ -117,7 +123,8 @@ public function testImmutableArrayAccess() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $request['nickname'] = 'Janey'; @@ -137,7 +144,8 @@ public function testImmutableMagicAccess() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $request->{'nickname'} = 'Janey'; @@ -157,7 +165,8 @@ public function testGetTheMethodRight() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $request->post; @@ -174,7 +183,8 @@ public function testTheMethodIsRight() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('GET', $request->method); @@ -196,7 +206,8 @@ public function testJsonPost() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('POST', $request->method); @@ -220,7 +231,8 @@ public function testNotJsonPost() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertEquals('POST', $request->method); @@ -242,7 +254,8 @@ public function testPatch() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('PATCH', $request->method); @@ -267,7 +280,8 @@ public function testJsonPatchAndPut() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('PUT', $request->method); @@ -288,7 +302,8 @@ public function testJsonPatchAndPut() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('PATCH', $request->method); @@ -316,7 +331,8 @@ public function testPutStream() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('PUT', $request->method); @@ -346,7 +362,8 @@ public function testSetUrlParameters() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $newParams = array('id' => '3', 'test' => 'test2'); @@ -368,7 +385,8 @@ public function testGetIdWithModUnique() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('GeneratedUniqueIdByModUnique', $request->getId()); @@ -385,7 +403,8 @@ public function testGetIdWithoutModUnique() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('GeneratedByOwnCloudItself', $request->getId()); @@ -397,7 +416,8 @@ public function testGetIdWithoutModUniqueStable() { \OC::$server->getSecureRandom(), $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $firstId = $request->getId(); $secondId = $request->getId(); @@ -422,7 +442,8 @@ public function testGetRemoteAddressWithoutTrustedRemote() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('10.0.0.2', $request->getRemoteAddress()); @@ -451,7 +472,8 @@ public function testGetRemoteAddressWithNoTrustedHeader() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('10.0.0.2', $request->getRemoteAddress()); @@ -480,7 +502,8 @@ public function testGetRemoteAddressWithSingleTrustedRemote() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('10.4.0.5', $request->getRemoteAddress()); @@ -509,7 +532,8 @@ public function testGetRemoteAddressIPv6WithSingleTrustedRemote() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('10.4.0.5', $request->getRemoteAddress()); @@ -542,7 +566,8 @@ public function testGetRemoteAddressVerifyPriorityHeader() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('192.168.0.233', $request->getRemoteAddress()); @@ -575,7 +600,8 @@ public function testGetRemoteAddressIPv6VerifyPriorityHeader() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('192.168.0.233', $request->getRemoteAddress()); @@ -604,7 +630,38 @@ public function testGetRemoteAddressWithMatchingCidrTrustedRemote() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory + ); + + $this->assertSame('192.168.0.233', $request->getRemoteAddress()); + } + + public function testGetRemoteAddressIPv6WithMatchingCidrTrustedRemote() { + $this->config + ->expects($this->at(0)) + ->method('getSystemValue') + ->with('trusted_proxies') + ->will($this->returnValue(['2001:db8:85a3:8d3:1319:8a20::/95'])); + $this->config + ->expects($this->at(1)) + ->method('getSystemValue') + ->with('forwarded_for_headers') + ->will($this->returnValue(['HTTP_X_FORWARDED_FOR'])); + + $request = new Request( + [ + 'server' => [ + 'REMOTE_ADDR' => '2001:db8:85a3:8d3:1319:8a21:370:7348', + 'HTTP_X_FORWARDED' => '10.4.0.5, 10.4.0.4', + 'HTTP_X_FORWARDED_FOR' => '192.168.0.233' + ], + ], + $this->secureRandom, + $this->config, + $this->csrfTokenManager, + $this->stream, + $this->ipAddressFactory ); $this->assertSame('192.168.0.233', $request->getRemoteAddress()); @@ -628,12 +685,38 @@ public function testGetRemoteAddressWithNotMatchingCidrTrustedRemote() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('192.168.3.99', $request->getRemoteAddress()); } + public function testGetRemoteAddressIPv6WithNotMatchingCidrTrustedRemote() { + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->with('trusted_proxies') + ->will($this->returnValue(['2001:db8:85a3:8d3:1319:8a20::/95'])); + + $request = new Request( + [ + 'server' => [ + 'REMOTE_ADDR' => '2001:db8:85a3:8d3:1319:8a22:370:7348', + 'HTTP_X_FORWARDED' => '10.4.0.5, 10.4.0.4', + 'HTTP_X_FORWARDED_FOR' => '192.168.0.233' + ], + ], + $this->secureRandom, + $this->config, + $this->csrfTokenManager, + $this->stream, + $this->ipAddressFactory + ); + + $this->assertSame('2001:db8:85a3:8d3:1319:8a22:370:7348', $request->getRemoteAddress()); + } + /** * @return array */ @@ -679,7 +762,8 @@ public function testGetHttpProtocol($input, $expected) { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame($expected, $request->getHttpProtocol()); @@ -707,7 +791,8 @@ public function testGetServerProtocolWithOverride() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('customProtocol', $request->getServerProtocol()); @@ -729,7 +814,8 @@ public function testGetServerProtocolWithProtoValid() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $requestHttp = new Request( [ @@ -740,7 +826,8 @@ public function testGetServerProtocolWithProtoValid() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); @@ -764,7 +851,8 @@ public function testGetServerProtocolWithHttpsServerValueOn() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('https', $request->getServerProtocol()); } @@ -785,7 +873,8 @@ public function testGetServerProtocolWithHttpsServerValueOff() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('http', $request->getServerProtocol()); } @@ -806,7 +895,8 @@ public function testGetServerProtocolWithHttpsServerValueEmpty() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('http', $request->getServerProtocol()); } @@ -823,7 +913,8 @@ public function testGetServerProtocolDefault() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('http', $request->getServerProtocol()); } @@ -844,7 +935,8 @@ public function testGetServerProtocolBehindLoadBalancers() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('https', $request->getServerProtocol()); @@ -866,7 +958,8 @@ public function testUserAgent($testAgent, $userAgent, $matches) { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame($matches, $request->isUserAgent($userAgent)); @@ -884,7 +977,8 @@ public function testUndefinedUserAgent($testAgent, $userAgent, $matches) { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertFalse($request->isUserAgent($userAgent)); @@ -1001,7 +1095,8 @@ public function testInsecureServerHostServerNameHeader() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('from.server.name:8080', $request->getInsecureServerHost()); @@ -1018,7 +1113,8 @@ public function testInsecureServerHostHttpHostHeader() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('from.host.header:8080', $request->getInsecureServerHost()); @@ -1036,7 +1132,8 @@ public function testInsecureServerHostHttpFromForwardedHeaderSingle() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('from.forwarded.host:8080', $request->getInsecureServerHost()); @@ -1054,7 +1151,8 @@ public function testInsecureServerHostHttpFromForwardedHeaderStacked() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('from.forwarded.host2:8080', $request->getInsecureServerHost()); @@ -1082,7 +1180,8 @@ public function testGetServerHostWithOverwriteHost() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('my.overwritten.host', $request->getServerHost()); @@ -1104,7 +1203,8 @@ public function testGetServerHostWithTrustedDomain() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('my.trusted.host', $request->getServerHost()); @@ -1131,7 +1231,8 @@ public function testGetServerHostWithUntrustedDomain() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('my.trusted.host', $request->getServerHost()); @@ -1158,7 +1259,8 @@ public function testGetServerHostWithNoTrustedDomain() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('', $request->getServerHost()); @@ -1175,7 +1277,8 @@ public function testGetOverwriteHostDefaultNull() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertNull(self::invokePrivate($request, 'getOverwriteHost')); @@ -1203,7 +1306,8 @@ public function testGetOverwriteHostWithOverwrite() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('www.owncloud.org', self::invokePrivate($request, 'getOverwriteHost')); @@ -1224,7 +1328,8 @@ public function testGetPathInfoNotProcessible() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $request->getPathInfo(); @@ -1245,7 +1350,8 @@ public function testGetRawPathInfoNotProcessible() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $request->getRawPathInfo(); @@ -1268,7 +1374,8 @@ public function testGetPathInfoWithoutSetEnvGeneric($requestUri, $scriptName, $e $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame($expected, $request->getPathInfo()); @@ -1291,7 +1398,8 @@ public function testGetRawPathInfoWithoutSetEnvGeneric($requestUri, $scriptName, $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame($expected, $request->getRawPathInfo()); @@ -1314,7 +1422,8 @@ public function testGetRawPathInfoWithoutSetEnv($requestUri, $scriptName, $expec $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame($expected, $request->getRawPathInfo()); @@ -1337,7 +1446,8 @@ public function testGetPathInfoWithoutSetEnv($requestUri, $scriptName, $expected $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame($expected, $request->getPathInfo()); @@ -1392,7 +1502,8 @@ public function testGetRequestUriWithoutOverwrite() { $this->secureRandom, $this->config, $this->csrfTokenManager, - $this->stream + $this->stream, + $this->ipAddressFactory ); $this->assertSame('/test.php', $request->getRequestUri()); diff --git a/tests/lib/Net/IpAddressFactoryTest.php b/tests/lib/Net/IpAddressFactoryTest.php new file mode 100644 index 0000000000000..cd7bd7423b6e5 --- /dev/null +++ b/tests/lib/Net/IpAddressFactoryTest.php @@ -0,0 +1,81 @@ + + * + * @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 . + * + */ + +namespace Test\Net; + +use OCP\Net\IIpAddressFactory; +use OCP\Net\IIpAddress; +use OC\Net\IpAddressFactory; +use OC\Net\IpAddressV4; +use OC\Net\IpAddressV6; + +class IpAddressFactoryTest extends \Test\TestCase { + public function testNewForIpv4Address() { + $factory = new IpAddressFactory(); + $ipaddress = $factory->getInstance('192.168.11.22'); + + $this->assertTrue($ipaddress instanceof IIpAddress); + $this->assertTrue($ipaddress instanceof IpAddressV4); + } + + public function testNewForIpv4Subnet() { + $factory = new IpAddressFactory(); + $ipaddress = $factory->getInstance('192.168.11.0/24'); + + $this->assertTrue($ipaddress instanceof IIpAddress); + $this->assertTrue($ipaddress instanceof IpAddressV4); + } + + public function testNewForIpv6Address() { + $factory = new IpAddressFactory(); + $ipaddress = $factory->getInstance('2001:db8:85a3:8d3:1319:8a2e:370:7348'); + + $this->assertTrue($ipaddress instanceof IIpAddress); + $this->assertTrue($ipaddress instanceof IpAddressV6); + } + + public function testNewForIpv6AddressAbbreviated() { + $factory = new IpAddressFactory(); + $ipaddress = $factory->getInstance('2001:db8:85a3:8d3:1319:8a2e::7348'); + + $this->assertTrue($ipaddress instanceof IIpAddress); + $this->assertTrue($ipaddress instanceof IpAddressV6); + } + + public function testNewForIpv6AddressLocalhost() { + $factory = new IpAddressFactory(); + $ipaddress = $factory->getInstance('::1'); + + $this->assertTrue($ipaddress instanceof IIpAddress); + $this->assertTrue($ipaddress instanceof IpAddressV6); + } + + public function testNewForIpv6Subnet() { + $factory = new IpAddressFactory(); + $ipaddress = $factory->getInstance('2001:db8:85a3:8d3:1319:8a2e::/96'); + + $this->assertTrue($ipaddress instanceof IIpAddress); + $this->assertTrue($ipaddress instanceof IpAddressV6); + } +} + diff --git a/tests/lib/Net/IpAddressV4Test.php b/tests/lib/Net/IpAddressV4Test.php new file mode 100644 index 0000000000000..5521afd17218a --- /dev/null +++ b/tests/lib/Net/IpAddressV4Test.php @@ -0,0 +1,88 @@ + + * + * @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 . + * + */ + +namespace Test\Net; + +use OCP\Net\IIpAddress; +use OC\Net\IpAddressV4; + +class IpAddressV4Test extends \Test\TestCase { + public function testIsRangeAddress() { + $ipaddress = new IpAddressV4('192.168.11.22'); + + $this->assertFalse($ipaddress->isRange()); + } + + public function testIsRangeLocalhost() { + $ipaddress = new IpAddressV4('127.0.0.1'); + + $this->assertFalse($ipaddress->isRange()); + } + + public function testIsRangeRangeSome() { + $ipaddress = new IpAddressV4('192.168.11.0/24'); + + $this->assertTrue($ipaddress->isRange()); + } + + public function testIsRangeRangeAll() { + $ipaddress = new IpAddressV4('192.168.11.22/32'); + + $this->assertFalse($ipaddress->isRange()); + } + + public function testIsRangeRangeNone() { + $ipaddress = new IpAddressV4('0.0.0.0/0'); + + $this->assertTrue($ipaddress->isRange()); + } + + public function testContainsAddressSingleMatch() { + $ip1 = new IpAddressV4('192.168.11.22'); + $ip2 = new IpAddressV4('192.168.11.22'); + + $this->assertTrue($ip1->containsAddress($ip2)); + } + + public function testContainsAddressSingleNoMatch() { + $ip1 = new IpAddressV4('192.168.11.22'); + $ip2 = new IpAddressV4('192.168.11.23'); + + $this->assertFalse($ip1->containsAddress($ip2)); + } + + public function testContainsAddressRangeMatch() { + $ip1 = new IpAddressV4('192.168.11.0/24'); + $ip2 = new IpAddressV4('192.168.11.23'); + + $this->assertTrue($ip1->containsAddress($ip2)); + } + + public function testContainsAddressRangeNoMatch() { + $ip1 = new IpAddressV4('192.168.11.0/24'); + $ip2 = new IpAddressV4('192.168.12.23'); + + $this->assertFalse($ip1->containsAddress($ip2)); + } +} + diff --git a/tests/lib/Net/IpAddressV6Test.php b/tests/lib/Net/IpAddressV6Test.php new file mode 100644 index 0000000000000..195c4cff159c0 --- /dev/null +++ b/tests/lib/Net/IpAddressV6Test.php @@ -0,0 +1,88 @@ + + * + * @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 . + * + */ + +namespace Test\Net; + +use OCP\Net\IIpAddress; +use OC\Net\IpAddressV6; + +class IpAddressV6Test extends \Test\TestCase { + public function testIsRangeAddress() { + $ipaddress = new IpAddressV6('2001:db8:85a3:8d3:1319:8a2e:370:7348'); + + $this->assertFalse($ipaddress->isRange()); + } + + public function testIsRangeLocalhost() { + $ipaddress = new IpAddressV6('::1'); + + $this->assertFalse($ipaddress->isRange()); + } + + public function testIsRangeRangeSome() { + $ipaddress = new IpAddressV6('2001:db8:85a3:8d3:1319::/80'); + + $this->assertTrue($ipaddress->isRange()); + } + + public function testIsRangeRangeAll() { + $ipaddress = new IpAddressV6('2001:db8:85a3:8d3:1319:8a2e:370:7348/128'); + + $this->assertFalse($ipaddress->isRange()); + } + + public function testIsRangeRangeNone() { + $ipaddress = new IpAddressV6('::/0'); + + $this->assertTrue($ipaddress->isRange()); + } + + public function testContainsAddressSingleMatch() { + $ip1 = new IpAddressV6('2001:db8:85a3:8d3:1319:8a2e:370:7348'); + $ip2 = new IpAddressV6('2001:db8:85a3:8d3:1319:8a2e:370:7348'); + + $this->assertTrue($ip1->containsAddress($ip2)); + } + + public function testContainsAddressSingleNoMatch() { + $ip1 = new IpAddressV6('2001:db8:85a3:8d3:1319:8a2e:370:7348'); + $ip2 = new IpAddressV6('2001:db8:85a3:8d3:1319:8a2e:370:7349'); + + $this->assertFalse($ip1->containsAddress($ip2)); + } + + public function testContainsAddressRangeMatch() { + $ip1 = new IpAddressV6('2001:db8:85a3:8d3:1319:8a2e::/96'); + $ip2 = new IpAddressV6('2001:db8:85a3:8d3:1319:8a2e:370:7348'); + + $this->assertTrue($ip1->containsAddress($ip2)); + } + + public function testContainsAddressRangeNoMatch() { + $ip1 = new IpAddressV6('2001:db8:85a3:8d3:1319:8a2e::/96'); + $ip2 = new IpAddressV6('2001:db8:85a3:8d3:1319:8a2f:370:7348'); + + $this->assertFalse($ip1->containsAddress($ip2)); + } +} +