perf: 🚨 resolve linters errors
This commit is contained in:
parent
ddb4c688dc
commit
cd6bb56bb6
7 changed files with 97 additions and 115 deletions
|
|
@ -23,6 +23,20 @@
|
|||
"phpunit/php-code-coverage": "^11",
|
||||
"larastan/larastan": "^2.9"
|
||||
},
|
||||
"scripts": {
|
||||
"post-autoload-dump": [
|
||||
"@php ./vendor/bin/testbench package:discover --ansi"
|
||||
],
|
||||
"phpcs": [
|
||||
"./vendor/bin/phpcs"
|
||||
],
|
||||
"phpstan": [
|
||||
"./vendor/bin/phpstan"
|
||||
],
|
||||
"rector": [
|
||||
"./vendor/bin/rector"
|
||||
]
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Uwil\\LaravelAdminSecure\\": "src/"
|
||||
|
|
@ -40,12 +54,6 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"post-autoload-dump": [
|
||||
"@php ./vendor/bin/testbench package:discover --ansi"
|
||||
],
|
||||
"test": "vendor/bin/phpunit"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
'cidrList' => env('UWIL_ADMIN_SECURE_CIDR_LIST'),
|
||||
'cidrProjectAllowedList' => env('UWIL_ADMIN_SECURE_CIDR_PROJECT_ALLOWED_LIST')
|
||||
'cidrList' => env('UWIL_ADMIN_SECURE_CIDR_LIST'),
|
||||
'cidrProjectAllowedList' => env('UWIL_ADMIN_SECURE_CIDR_PROJECT_ALLOWED_LIST'),
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Uwil\LaravelAdminSecure;
|
||||
|
||||
use Dxw\CIDR\IPAddress;
|
||||
|
|
@ -11,115 +13,90 @@ use Uwil\LaravelAdminSecure\Exceptions\AdminSecureException;
|
|||
|
||||
abstract class AdminSecure
|
||||
{
|
||||
/**
|
||||
* Private ipV4 CIDR list
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
/** @var array<int,string> Private ipV4 CIDR list. */
|
||||
private static array $private_ip_v4_cidr_list = [
|
||||
'10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'
|
||||
'10.0.0.0/8',
|
||||
'172.16.0.0/12',
|
||||
'192.168.0.0/16',
|
||||
];
|
||||
|
||||
/**
|
||||
* Private ipV4 CIDR loopback
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
/** @var string Private ipV4 CIDR loopback. */
|
||||
private static string $private_ip_v4_loopback_cidr = '127.0.0.0/8';
|
||||
|
||||
/**
|
||||
* Private ipV6 CIDR list
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
/** @var array<int,string> Private ipV6 CIDR list. */
|
||||
private static array $private_ip_v6_cidr_list = [
|
||||
'fd00::/8'
|
||||
'fd00::/8',
|
||||
];
|
||||
|
||||
/**
|
||||
* Private ipV6 CIDR loopback
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static string $private_ip_v6_loopback_cidr = '::1/128';
|
||||
|
||||
/**
|
||||
* Private options
|
||||
*
|
||||
* @var \Uwil\AdminSecure\Laravel\Options|null
|
||||
*/
|
||||
/** @var \Uwil\LaravelAdminSecure\Options|null Private options. */
|
||||
private static $options = null;
|
||||
|
||||
/**
|
||||
* Check if ip is private
|
||||
*
|
||||
* @param \Dxw\CIDR\IPv4Address|\Dxw\CIDR\IPv6Address $ip_address
|
||||
* @return boolean
|
||||
* Check if ip is private.
|
||||
*/
|
||||
public static function ipIsPrivate(IPv4Address|IPv6Address $ip_address): bool
|
||||
public static function ipIsPrivate(IPv4Address | IPv6Address $ip_address): bool
|
||||
{
|
||||
return \get_class($ip_address) === IPv4Address::class ?
|
||||
return $ip_address instanceof IPv4Address ?
|
||||
self::ipV4IsPrivate($ip_address) : self::ipV6IsPrivate($ip_address);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch client IP
|
||||
* Fetch client IP.
|
||||
*
|
||||
* @return \Dxw\CIDR\IPv4Address|\Dxw\CIDR\IPv6Address
|
||||
* @throws \Uwil\AdminSecure\Laravel\Exceptions\AdminSecureException If the ip is invalid.
|
||||
* @throws \Uwil\LaravelAdminSecure\Exceptions\AdminSecureException If the ip is invalid.
|
||||
*/
|
||||
public static function fetchClientIp(): IPv4Address|IPv6Address
|
||||
public static function fetchClientIp(): IPv4Address | IPv6Address
|
||||
{
|
||||
// ! REVERSE PROXY HACK.
|
||||
$ip = request()->server('HTTP_X_FORWARDED_FOR') ?? request()->getClientIp();
|
||||
$ip = IPAddress::Make($ip);
|
||||
$forwarded = request()->server('HTTP_X_FORWARDED_FOR');
|
||||
$ip = is_string($forwarded) && $forwarded !== ''
|
||||
? trim(explode(',', $forwarded)[0])
|
||||
: request()->getClientIp() ?? '0.0.0.0';
|
||||
$ip = IPAddress::Make($ip);
|
||||
|
||||
if ($ip->isErr()) {
|
||||
throw new AdminSecureException(\trans('Client IP not valid ') . $ip->getErr());
|
||||
}
|
||||
|
||||
return $ip->unwrap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if ipV4 is private
|
||||
*
|
||||
* @param \Dxw\CIDR\IPv4Address $ipv4_address
|
||||
* @return boolean
|
||||
* Check if ipV4 is private.
|
||||
*/
|
||||
private static function ipV4IsPrivate(IPv4Address $ipv4_address): bool
|
||||
{
|
||||
return \collect(self::$private_ip_v4_cidr_list)
|
||||
->concat([self::$private_ip_v4_loopback_cidr])
|
||||
->map(fn (string $ipv4_cidr) => IPv4Range::Make($ipv4_cidr)->unwrap())
|
||||
->reduce(fn (bool|null $initial, IPv4Range $ipv4_range) =>
|
||||
$initial || $ipv4_range->containsAddress($ipv4_address));
|
||||
->reduce(fn (?bool $initial, IPv4Range $ipv4_range) => $initial || $ipv4_range->containsAddress($ipv4_address));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if ipV6 is private
|
||||
*
|
||||
* @param \Dxw\CIDR\IPv6Address $ipv6_address
|
||||
* @return boolean
|
||||
* Check if ipV6 is private.
|
||||
*/
|
||||
private static function ipV6IsPrivate(IPv6Address $ipv6_address): bool
|
||||
{
|
||||
return \collect(self::$private_ip_v6_cidr_list)
|
||||
->concat([self::$private_ip_v6_loopback_cidr])
|
||||
->map(fn (string $ipv6_cidr) => IPv6Range::Make($ipv6_cidr)->unwrap())
|
||||
->reduce(fn (bool|null $initial, IPv6Range $ipv6_range) =>
|
||||
$initial || $ipv6_range->containsAddress($ipv6_address));
|
||||
->reduce(fn (?bool $initial, IPv6Range $ipv6_range) => $initial || $ipv6_range->containsAddress($ipv6_address));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Admin secure options
|
||||
*
|
||||
* @return \Uwil\AdminSecure\Laravel\Options
|
||||
* Get Admin secure options.
|
||||
*/
|
||||
public static function getOptions(): Options
|
||||
{
|
||||
if (!self::$options) {
|
||||
if (! self::$options) {
|
||||
self::$options = new Options(\config('admin-secure'));
|
||||
}
|
||||
|
||||
return self::$options;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Uwil\LaravelAdminSecure;
|
||||
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Routing\Router;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Uwil\LaravelAdminSecure\Middlewares\AdminSecureMiddleware;
|
||||
|
||||
|
|
@ -19,7 +22,7 @@ class AdminSecureServiceProvider extends ServiceProvider
|
|||
__DIR__ . '/../config' => config_path(),
|
||||
], 'config');
|
||||
|
||||
// * Set filter condition
|
||||
// * Set filter condition.
|
||||
$this->app->singleton('hasToFilterIp', fn (Application $app) => $app->environment('production', 'staging'));
|
||||
|
||||
/** @var \Illuminate\Routing\Router */
|
||||
|
|
@ -32,6 +35,7 @@ class AdminSecureServiceProvider extends ServiceProvider
|
|||
*
|
||||
* @return void
|
||||
*/
|
||||
#[\Override]
|
||||
public function register()
|
||||
{
|
||||
$this->mergeConfigFrom(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Uwil\LaravelAdminSecure\Exceptions;
|
||||
|
||||
class AdminSecureException extends \Exception
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Uwil\LaravelAdminSecure\Http\Middleware;
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Uwil\LaravelAdminSecure\Middlewares;
|
||||
|
||||
use Closure;
|
||||
use Dxw\CIDR\IP;
|
||||
use Dxw\CIDR\IPv4Range;
|
||||
use Dxw\CIDR\IPv6Range;
|
||||
use Illuminate\Http\Request;
|
||||
|
|
@ -14,29 +15,25 @@ class AdminSecureMiddleware
|
|||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
if (!\app()->make('hasToFilterIp')) {
|
||||
if (! \app()->make('hasToFilterIp')) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$client_ip = AdminSecure::fetchClientIp();
|
||||
|
||||
|
||||
$ip_is_allowed = \collect(AdminSecure::getOptions()->getCidrList())
|
||||
->reduce(fn (null|bool $initial, IPv4Range|IPv6Range $ip_range) => $initial || IP::contains(
|
||||
$ip_range,
|
||||
->reduce(fn (?bool $initial, IPv4Range | IPv6Range $ip_range) => $initial || $ip_range->containsAddress(
|
||||
$client_ip
|
||||
)->unwrap());
|
||||
));
|
||||
|
||||
$ip_is_allowed_by_config = \collect(AdminSecure::getOptions()->getCidrAllowedList())
|
||||
->reduce(fn (null|bool $initial, IPv4Range|IPv6Range $ip_range) => $initial || IP::contains(
|
||||
$ip_range,
|
||||
->reduce(fn (?bool $initial, IPv4Range | IPv6Range $ip_range) => $initial || $ip_range->containsAddress(
|
||||
$client_ip
|
||||
)->unwrap());
|
||||
));
|
||||
|
||||
if ($ip_is_allowed or $ip_is_allowed_by_config or AdminSecure::ipIsPrivate($client_ip)) {
|
||||
return $next($request);
|
||||
|
|
@ -15,27 +15,19 @@ use Uwil\LaravelAdminSecure\Exceptions\AdminSecureException;
|
|||
*/
|
||||
final class Options
|
||||
{
|
||||
/**
|
||||
* @var array<string, mixed> The configuration options
|
||||
*/
|
||||
/** @var array<string, mixed> The configuration options. */
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* @var \Symfony\Component\OptionsResolver\OptionsResolver The options resolver
|
||||
*/
|
||||
/** @var \Symfony\Component\OptionsResolver\OptionsResolver The options resolver. */
|
||||
private $resolver;
|
||||
|
||||
/**
|
||||
* The cache key where to store config cidr list.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
/** @var string The cache key where to store config cidr list. */
|
||||
private static string $cache_config_key = 'uwil_ip_cidr_config_list';
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param array<string, mixed> $options The configuration options.
|
||||
* @param array<string, mixed> $options The configuration options.
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
|
|
@ -47,7 +39,7 @@ final class Options
|
|||
}
|
||||
|
||||
/**
|
||||
* Get cidr list
|
||||
* Get cidr list.
|
||||
*
|
||||
* @return array<\Dxw\CIDR\IPv4Range|\Dxw\CIDR\IPv6Range>
|
||||
*/
|
||||
|
|
@ -57,7 +49,7 @@ final class Options
|
|||
}
|
||||
|
||||
/**
|
||||
* Get cidr project allowed list
|
||||
* Get cidr project allowed list.
|
||||
*
|
||||
* @return array<\Dxw\CIDR\IPv4Range|\Dxw\CIDR\IPv6Range>
|
||||
*/
|
||||
|
|
@ -69,26 +61,24 @@ final class Options
|
|||
/**
|
||||
* Configures the options of the client.
|
||||
*
|
||||
* @param \Symfony\Component\OptionsResolver\OptionsResolver $resolver The resolver for the options.
|
||||
* @return void
|
||||
* @param \Symfony\Component\OptionsResolver\OptionsResolver $resolver The resolver for the options.
|
||||
*/
|
||||
private function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'cidrList' => [],
|
||||
'cidrProjectAllowedList' => []
|
||||
'cidrList' => [],
|
||||
'cidrProjectAllowedList' => [],
|
||||
]);
|
||||
|
||||
$resolver->setNormalizer('cidrList', \Closure::fromCallable([$this, 'normalizeCidrList']));
|
||||
$resolver->setNormalizer('cidrProjectAllowedList', \Closure::fromCallable([$this, 'normalizeCidrProjectList']));
|
||||
$resolver->setNormalizer('cidrList', $this->normalizeCidrList(...));
|
||||
$resolver->setNormalizer('cidrProjectAllowedList', $this->normalizeCidrProjectList(...));
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise Cidr list for cidrList option
|
||||
* Normalise Cidr list for cidrList option.
|
||||
*
|
||||
* @return array<\Dxw\CIDR\IPv4Range|\Dxw\CIDR\IPv6Range>
|
||||
*
|
||||
* @param \Symfony\Component\OptionsResolver\Options $options
|
||||
* @param string|null $value
|
||||
* @return array
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
|
||||
*/
|
||||
private function normalizeCidrList(
|
||||
|
|
@ -100,11 +90,10 @@ final class Options
|
|||
}
|
||||
|
||||
/**
|
||||
* Normalise Cidr list for cidrProjectAllowedList option
|
||||
* Normalise Cidr list for cidrProjectAllowedList option.
|
||||
*
|
||||
* @return array<\Dxw\CIDR\IPv4Range|\Dxw\CIDR\IPv6Range>
|
||||
*
|
||||
* @param \Symfony\Component\OptionsResolver\Options $options
|
||||
* @param string|null $value
|
||||
* @return array
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
|
||||
*/
|
||||
private function normalizeCidrProjectList(
|
||||
|
|
@ -118,40 +107,43 @@ final class Options
|
|||
/**
|
||||
* Normalize CIDR list with specific cache key.
|
||||
*
|
||||
* @param string|null $value
|
||||
* @param string $optionName
|
||||
* @return array<\Dxw\CIDR\IPv4Range|\Dxw\CIDR\IPv6Range>
|
||||
*/
|
||||
private function doNormalizeCidrList(?string $value, string $optionName): array
|
||||
{
|
||||
$cacheConfigKey = self::$cache_config_key . '_' . $optionName;
|
||||
$allowed_cidr_config_list = collect(explode(',', strval($value)))->filter();
|
||||
$cacheConfigKey = self::$cache_config_key . '_' . $optionName;
|
||||
$allowed_cidr_config_list = collect(explode(',', strval($value)))->filter();
|
||||
$allowed_cidr_config_list_hash = \md5($allowed_cidr_config_list->implode(','));
|
||||
|
||||
/** @var array{list: array<\Dxw\CIDR\IPv4Range|\Dxw\CIDR\IPv6Range>, hash: string}|null $cached */
|
||||
$cached = Cache::get($cacheConfigKey);
|
||||
|
||||
// Flush cache if config hash changed
|
||||
if ($allowed_cidr_config_list_hash !== \collect(Cache::get($cacheConfigKey))->get('hash')) {
|
||||
if ($cached === null || $cached['hash'] !== $allowed_cidr_config_list_hash) {
|
||||
Cache::forget($cacheConfigKey);
|
||||
|
||||
/** @var array<\Dxw\CIDR\IPv4Range|\Dxw\CIDR\IPv6Range> $allowed_cidr_list */
|
||||
$allowed_cidr_list = $allowed_cidr_config_list
|
||||
->map(function (string $cidr) {
|
||||
$ip_range = !IPv4Range::Make($cidr)->isErr() ? IPv4Range::Make($cidr) : IPv6Range::Make($cidr);
|
||||
$ip_range = ! IPv4Range::Make($cidr)->isErr() ? IPv4Range::Make($cidr) : IPv6Range::Make($cidr);
|
||||
if ($ip_range->isErr()) {
|
||||
throw new AdminSecureException(\trans(
|
||||
'CIDR LIST not valid (ipv4 or ipv6 cidr) :cidr',
|
||||
[
|
||||
'cidr' => $cidr
|
||||
]
|
||||
['cidr' => $cidr]
|
||||
));
|
||||
}
|
||||
|
||||
return $ip_range->unwrap();
|
||||
})->all();
|
||||
|
||||
Cache::forever($cacheConfigKey, [
|
||||
'list' => $allowed_cidr_list,
|
||||
'hash' => $allowed_cidr_config_list_hash,
|
||||
]);
|
||||
|
||||
return $allowed_cidr_list;
|
||||
}
|
||||
|
||||
/** @var array<\Dxw\CIDR\IPv4Range|\Dxw\CIDR\IPv6Range> $allowed_cidr_list */
|
||||
return \collect(Cache::rememberForever($cacheConfigKey, fn() => [
|
||||
'list' => $allowed_cidr_list,
|
||||
'hash' => $allowed_cidr_config_list_hash
|
||||
]))->get('list');
|
||||
return $cached['list'];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue