Signed-off-by: Joas Schilling <coding@schilljs.com> Signed-off-by: Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>pull/46473/head
parent
202e5b1e95
commit
047479ccf9
@ -0,0 +1,48 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
/** |
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OC\Security\Ip; |
||||
|
||||
use InvalidArgumentException; |
||||
use IPLib\Address\AddressInterface; |
||||
use IPLib\Factory; |
||||
use OCP\Security\Ip\IAddress; |
||||
use OCP\Security\Ip\IRange; |
||||
|
||||
/** |
||||
* @since 30.0.0 |
||||
*/ |
||||
class Address implements IAddress { |
||||
private readonly AddressInterface $ip; |
||||
|
||||
public function __construct(string $ip) { |
||||
$ip = Factory::parseAddressString($ip); |
||||
if ($ip === null) { |
||||
throw new InvalidArgumentException('Given IP address can’t be parsed'); |
||||
} |
||||
$this->ip = $ip; |
||||
} |
||||
|
||||
public static function isValid(string $ip): bool { |
||||
return Factory::parseAddressString($ip) !== null; |
||||
} |
||||
|
||||
public function matches(IRange... $ranges): bool { |
||||
foreach($ranges as $range) { |
||||
if ($range->contains($this)) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
public function __toString(): string { |
||||
return $this->ip->toString(); |
||||
} |
||||
} |
||||
@ -0,0 +1,39 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
/** |
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OC\Security\Ip; |
||||
|
||||
use InvalidArgumentException; |
||||
use IPLib\Factory; |
||||
use IPLib\Range\RangeInterface; |
||||
use OCP\Security\Ip\IAddress; |
||||
use OCP\Security\Ip\IRange; |
||||
|
||||
class Range implements IRange { |
||||
private readonly RangeInterface $range; |
||||
|
||||
public function __construct(string $range) { |
||||
$range = Factory::parseRangeString($range); |
||||
if ($range === null) { |
||||
throw new InvalidArgumentException('Given range can’t be parsed'); |
||||
} |
||||
$this->range = $range; |
||||
} |
||||
|
||||
public static function isValid(string $range): bool { |
||||
return Factory::parseRangeString($range) !== null; |
||||
} |
||||
|
||||
public function contains(IAddress $address): bool { |
||||
return $this->range->contains(Factory::parseAddressString((string) $address)); |
||||
} |
||||
|
||||
public function __toString(): string { |
||||
return $this->range->toString(); |
||||
} |
||||
} |
||||
@ -0,0 +1,71 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OC\Security\Ip; |
||||
|
||||
use OCP\IConfig; |
||||
use OCP\IRequest; |
||||
use OCP\Security\Ip\IAddress; |
||||
use OCP\Security\Ip\IRange; |
||||
use OCP\Security\Ip\IRemoteAddress; |
||||
|
||||
class RemoteAddress implements IRemoteAddress, IAddress { |
||||
public const SETTING_NAME = 'allowed_admin_ranges'; |
||||
|
||||
private readonly ?IAddress $ip; |
||||
|
||||
public function __construct( |
||||
private IConfig $config, |
||||
IRequest $request, |
||||
) { |
||||
$remoteAddress = $request->getRemoteAddress(); |
||||
$this->ip = $remoteAddress === '' |
||||
? null |
||||
: new Address($remoteAddress); |
||||
} |
||||
|
||||
public static function isValid(string $ip): bool { |
||||
return Address::isValid($ip); |
||||
} |
||||
|
||||
public function matches(IRange... $ranges): bool { |
||||
return $this->ip === null |
||||
? true |
||||
: $this->ip->matches(... $ranges); |
||||
} |
||||
|
||||
public function allowsAdminActions(): bool { |
||||
if ($this->ip === null) { |
||||
return true; |
||||
} |
||||
|
||||
$allowedAdminRanges = $this->config->getSystemValue(self::SETTING_NAME, false); |
||||
|
||||
// Don't apply restrictions on empty or invalid configuration |
||||
if ( |
||||
$allowedAdminRanges === false |
||||
|| !is_array($allowedAdminRanges) |
||||
|| empty($allowedAdminRanges) |
||||
) { |
||||
return true; |
||||
} |
||||
|
||||
foreach ($allowedAdminRanges as $allowedAdminRange) { |
||||
if ((new Range($allowedAdminRange))->contains($this->ip)) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
public function __toString(): string { |
||||
return (string) $this->ip; |
||||
} |
||||
} |
||||
@ -1,64 +0,0 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OC\Security; |
||||
|
||||
use IPLib\Factory; |
||||
use OCP\IConfig; |
||||
use OCP\IRequest; |
||||
use Psr\Log\LoggerInterface; |
||||
|
||||
class RemoteIpAddress { |
||||
public const SETTING_NAME = 'allowed_admin_ranges'; |
||||
|
||||
public function __construct( |
||||
private IConfig $config, |
||||
private IRequest $request, |
||||
private LoggerInterface $logger, |
||||
) { |
||||
} |
||||
|
||||
public function allowsAdminActions(): bool { |
||||
$allowedAdminRanges = $this->config->getSystemValue(self::SETTING_NAME, false); |
||||
if ($allowedAdminRanges === false) { |
||||
// No restriction applied |
||||
return true; |
||||
} |
||||
|
||||
if (!is_array($allowedAdminRanges)) { |
||||
return true; |
||||
} |
||||
|
||||
if (empty($allowedAdminRanges)) { |
||||
return true; |
||||
} |
||||
|
||||
$ipAddress = Factory::parseAddressString($this->request->getRemoteAddress()); |
||||
if ($ipAddress === null) { |
||||
$this->logger->warning( |
||||
'Unable to parse remote IP "{ip}"', |
||||
['ip' => $ipAddress,] |
||||
); |
||||
|
||||
return false; |
||||
} |
||||
|
||||
foreach ($allowedAdminRanges as $rangeString) { |
||||
$range = Factory::parseRangeString($rangeString); |
||||
if ($range === null) { |
||||
continue; |
||||
} |
||||
if ($range->contains($ipAddress)) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
} |
||||
@ -0,0 +1,35 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
/** |
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OCP\Security\Ip; |
||||
|
||||
/** |
||||
* @since 30.0.0 |
||||
*/ |
||||
interface IAddress { |
||||
/** |
||||
* Check if a given IP address is valid |
||||
* |
||||
* @since 30.0.0 |
||||
*/ |
||||
public static function isValid(string $ip): bool; |
||||
|
||||
/** |
||||
* Check if current address is contained by given ranges |
||||
* |
||||
* @since 30.0.0 |
||||
*/ |
||||
public function matches(IRange... $ranges): bool; |
||||
|
||||
/** |
||||
* Normalized IP address |
||||
* |
||||
* @since 30.0.0 |
||||
*/ |
||||
public function __toString(): string; |
||||
} |
||||
@ -0,0 +1,37 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
/** |
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OCP\Security\Ip; |
||||
|
||||
/** |
||||
* IP Range (IPv4 or IPv6) |
||||
* |
||||
* @since 30.0.0 |
||||
*/ |
||||
interface IRange { |
||||
/** |
||||
* Check if a given range is valid |
||||
* |
||||
* @since 30.0.0 |
||||
*/ |
||||
public static function isValid(string $range): bool; |
||||
|
||||
/** |
||||
* Check if an address is in the current range |
||||
* |
||||
* @since 30.0.0 |
||||
*/ |
||||
public function contains(IAddress $address): bool; |
||||
|
||||
/** |
||||
* Normalized IP range |
||||
* |
||||
* @since 30.0.0 |
||||
*/ |
||||
public function __toString(): string; |
||||
} |
||||
@ -0,0 +1,22 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
/** |
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OCP\Security\Ip; |
||||
|
||||
/** |
||||
* IP address of the connected client |
||||
* |
||||
* @since 30.0.0 |
||||
*/ |
||||
interface IRemoteAddress { |
||||
/** |
||||
* Check if the current remote address is allowed to perform admin actions |
||||
* @since 30.0.0 |
||||
*/ |
||||
public function allowsAdminActions(): bool; |
||||
} |
||||
Loading…
Reference in new issue