Compare commits
No commits in common. "master" and "develop" have entirely different histories.
7 changed files with 4 additions and 350 deletions
|
|
@ -85,8 +85,8 @@ class StatsController extends Controller
|
||||||
* - total of specific url from a brand
|
* - total of specific url from a brand
|
||||||
* - total of all urls for each brand
|
* - total of all urls for each brand
|
||||||
*/
|
*/
|
||||||
if (!$requestValidated['brand']) {
|
$datasets = match (true) {
|
||||||
$datasets = $this->buildDatasets(
|
!$requestValidated['brand'] => $this->buildDatasets(
|
||||||
Brand::all(),
|
Brand::all(),
|
||||||
$periodInterval,
|
$periodInterval,
|
||||||
$periodEnum,
|
$periodEnum,
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,5 @@ class Kernel extends HttpKernel
|
||||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||||
'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class,
|
'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class,
|
||||||
'admin-secure' => \App\Http\Middleware\AdminSecureMiddleware::class,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
|
||||||
|
|
||||||
use App\Lib\AdminSecure;
|
|
||||||
use Closure;
|
|
||||||
use Dxw\CIDR\IP;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class AdminSecureMiddleware
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Handle an incoming request.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param \Closure $next
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function handle(Request $request, Closure $next)
|
|
||||||
{
|
|
||||||
if (config('app.env') === 'local') {
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
$client_ip = AdminSecure::fetchClientIp();
|
|
||||||
|
|
||||||
$ip_is_allowed = \collect(AdminSecure::getOptions()->getCidrList())
|
|
||||||
->reduce(function ($initial, $ip_range) use ($client_ip) {
|
|
||||||
return $initial || IP::contains(
|
|
||||||
$ip_range,
|
|
||||||
$client_ip
|
|
||||||
)->unwrap();
|
|
||||||
});
|
|
||||||
|
|
||||||
$ip_is_allowed_by_config = \collect(AdminSecure::getOptions()->getCidrAllowedList())
|
|
||||||
->reduce(function ($initial, $ip_range) use ($client_ip) {
|
|
||||||
return $initial || IP::contains(
|
|
||||||
$ip_range,
|
|
||||||
$client_ip
|
|
||||||
)->unwrap();
|
|
||||||
});
|
|
||||||
|
|
||||||
if ($ip_is_allowed or $ip_is_allowed_by_config or AdminSecure::ipIsPrivate($client_ip)) {
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
abort(403, \trans('Denied IP'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Lib;
|
|
||||||
|
|
||||||
use App\Lib\Options;
|
|
||||||
use Dxw\CIDR\IPAddress;
|
|
||||||
use Dxw\CIDR\IPv4Address;
|
|
||||||
use Dxw\CIDR\IPv4Range;
|
|
||||||
use Dxw\CIDR\IPv6Address;
|
|
||||||
use Dxw\CIDR\IPv6Range;
|
|
||||||
|
|
||||||
abstract class AdminSecure
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Private ipV4 CIDR list
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private static $private_ip_v4_cidr_list = [
|
|
||||||
'10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Private ipV4 CIDR loopback
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private static $private_ip_v4_loopback_cidr = '127.0.0.0/8';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Private ipV6 CIDR list
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private static $private_ip_v6_cidr_list = [
|
|
||||||
'fd00::/8'
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Private ipV6 CIDR loopback
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private static $private_ip_v6_loopback_cidr = '::1/128';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Private options
|
|
||||||
*
|
|
||||||
* @var \App\Lib\Options|null
|
|
||||||
*/
|
|
||||||
private static $options = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if ip is private
|
|
||||||
*
|
|
||||||
* @param \Dxw\CIDR\IPv4Address|\Dxw\CIDR\IPv6Address $ip_address
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
public static function ipIsPrivate($ip_address): bool
|
|
||||||
{
|
|
||||||
return \get_class($ip_address) === IPv4Address::class ?
|
|
||||||
self::ipV4IsPrivate($ip_address) : self::ipV6IsPrivate($ip_address);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch client IP
|
|
||||||
*
|
|
||||||
* @return \Dxw\CIDR\IPv4Address|\Dxw\CIDR\IPv6Address
|
|
||||||
* @throws \Exception If the ip is invalid.
|
|
||||||
*/
|
|
||||||
public static function fetchClientIp()
|
|
||||||
{
|
|
||||||
// ! REVERSE PROXY HACK.
|
|
||||||
$ip = request()->server('HTTP_X_FORWARDED_FOR') ?? request()->getClientIp();
|
|
||||||
$ip = IPAddress::Make($ip);
|
|
||||||
|
|
||||||
if ($ip->isErr()) {
|
|
||||||
throw new \Exception(\trans('Client IP not valid ') . $ip->getErr());
|
|
||||||
}
|
|
||||||
return $ip->unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if ipV4 is private
|
|
||||||
*
|
|
||||||
* @param \Dxw\CIDR\IPv4Address $ipv4_address
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
private static function ipV4IsPrivate(IPv4Address $ipv4_address): bool
|
|
||||||
{
|
|
||||||
return \collect(self::$private_ip_v4_cidr_list)
|
|
||||||
->concat([self::$private_ip_v4_loopback_cidr])
|
|
||||||
->map(function (string $ipv4_cidr) {
|
|
||||||
return IPv4Range::Make($ipv4_cidr)->unwrap();
|
|
||||||
})
|
|
||||||
->reduce(function ($initial, IPv4Range $ipv4_range) use ($ipv4_address) {
|
|
||||||
return $initial || $ipv4_range->containsAddress($ipv4_address);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if ipV6 is private
|
|
||||||
*
|
|
||||||
* @param \Dxw\CIDR\IPv6Address $ipv6_address
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
private static function ipV6IsPrivate(IPv6Address $ipv6_address): bool
|
|
||||||
{
|
|
||||||
return \collect(self::$private_ip_v6_cidr_list)
|
|
||||||
->concat([self::$private_ip_v6_loopback_cidr])
|
|
||||||
->map(function (string $ipv6_cidr) {
|
|
||||||
return IPv6Range::Make($ipv6_cidr)->unwrap();
|
|
||||||
})
|
|
||||||
->reduce(function ($initial, IPv6Range $ipv6_range) use ($ipv6_address) {
|
|
||||||
return $initial || $ipv6_range->containsAddress($ipv6_address);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get Admin secure options
|
|
||||||
*
|
|
||||||
* @return \App\Lib\Options
|
|
||||||
*/
|
|
||||||
public static function getOptions(): Options
|
|
||||||
{
|
|
||||||
if (!self::$options) {
|
|
||||||
self::$options = new Options(\config('admin-secure'));
|
|
||||||
}
|
|
||||||
return self::$options;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,159 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Lib;
|
|
||||||
|
|
||||||
use Dxw\CIDR\IPv4Range;
|
|
||||||
use Dxw\CIDR\IPv6Range;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration
|
|
||||||
*/
|
|
||||||
final class Options
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var array<string, mixed> The configuration options
|
|
||||||
*/
|
|
||||||
private $options;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \Symfony\Component\OptionsResolver\OptionsResolver The options resolver
|
|
||||||
*/
|
|
||||||
private $resolver;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The cache key where to store config cidr list.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private static $cache_config_key = 'uwil_ip_cidr_config_list';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class constructor.
|
|
||||||
*
|
|
||||||
* @param array<string, mixed> $options The configuration options.
|
|
||||||
*/
|
|
||||||
public function __construct(array $options = [])
|
|
||||||
{
|
|
||||||
$this->resolver = new OptionsResolver();
|
|
||||||
|
|
||||||
$this->configureOptions($this->resolver);
|
|
||||||
|
|
||||||
$this->options = $this->resolver->resolve($options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get cidr list
|
|
||||||
*
|
|
||||||
* @return array<\Dxw\CIDR\IPv4Range|\Dxw\CIDR\IPv6Range>
|
|
||||||
*/
|
|
||||||
public function getCidrList(): array
|
|
||||||
{
|
|
||||||
return $this->options['cidrList'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get cidr project allowed list
|
|
||||||
*
|
|
||||||
* @return array<\Dxw\CIDR\IPv4Range|\Dxw\CIDR\IPv6Range>
|
|
||||||
*/
|
|
||||||
public function getCidrAllowedList(): array
|
|
||||||
{
|
|
||||||
return $this->options['cidrProjectAllowedList'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the options of the client.
|
|
||||||
*
|
|
||||||
* @param \Symfony\Component\OptionsResolver\OptionsResolver $resolver The resolver for the options.
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
private function configureOptions(OptionsResolver $resolver): void
|
|
||||||
{
|
|
||||||
$resolver->setDefaults([
|
|
||||||
'cidrList' => [],
|
|
||||||
'cidrProjectAllowedList' => []
|
|
||||||
]);
|
|
||||||
|
|
||||||
$resolver->setNormalizer('cidrList', \Closure::fromCallable([$this, 'normalizeCidrList']));
|
|
||||||
$resolver->setNormalizer('cidrProjectAllowedList', \Closure::fromCallable([$this, 'normalizeCidrProjectList']));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalise Cidr list for cidrList option
|
|
||||||
*
|
|
||||||
* @param \Symfony\Component\OptionsResolver\Options $options
|
|
||||||
* @param string|null $value
|
|
||||||
* @return array
|
|
||||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
|
|
||||||
*/
|
|
||||||
private function normalizeCidrList(
|
|
||||||
\Symfony\Component\OptionsResolver\Options $options,
|
|
||||||
?string $value
|
|
||||||
): array {
|
|
||||||
// phpcs:enable
|
|
||||||
return $this->doNormalizeCidrList($value, 'cidrList');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalise Cidr list for cidrProjectAllowedList option
|
|
||||||
*
|
|
||||||
* @param \Symfony\Component\OptionsResolver\Options $options
|
|
||||||
* @param string|null $value
|
|
||||||
* @return array
|
|
||||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
|
|
||||||
*/
|
|
||||||
private function normalizeCidrProjectList(
|
|
||||||
\Symfony\Component\OptionsResolver\Options $options,
|
|
||||||
?string $value
|
|
||||||
): array {
|
|
||||||
// phpcs:enable
|
|
||||||
return $this->doNormalizeCidrList($value, 'cidrProjectAllowedList');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
$allowed_cidr_config_list_hash = \md5($allowed_cidr_config_list->implode(','));
|
|
||||||
$allowed_cidr_list = [];
|
|
||||||
|
|
||||||
// Flush cache if config hash changed
|
|
||||||
if ($allowed_cidr_config_list_hash !== \collect(Cache::get($cacheConfigKey))->get('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);
|
|
||||||
if ($ip_range->isErr()) {
|
|
||||||
throw new \Exception(\trans(
|
|
||||||
'CIDR LIST not valid (ipv4 or ipv6 cidr) :cidr',
|
|
||||||
[
|
|
||||||
'cidr' => $cidr
|
|
||||||
]
|
|
||||||
));
|
|
||||||
}
|
|
||||||
return $ip_range->unwrap();
|
|
||||||
})->all();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var array<\Dxw\CIDR\IPv4Range|\Dxw\CIDR\IPv6Range> $allowed_cidr_list */
|
|
||||||
return \collect(Cache::rememberForever($cacheConfigKey, function() use ($allowed_cidr_list, $allowed_cidr_config_list_hash) {
|
|
||||||
return [
|
|
||||||
'list' => $allowed_cidr_list,
|
|
||||||
'hash' => $allowed_cidr_config_list_hash
|
|
||||||
];
|
|
||||||
}))->get('list');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
4
composer.lock
generated
4
composer.lock
generated
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "d479877b418347bca0a45f46759155b1",
|
"content-hash": "dfc6b62ebe219dbd2b3a2db68a4922f2",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "24slides/laravel-saml2",
|
"name": "24slides/laravel-saml2",
|
||||||
|
|
@ -10514,7 +10514,7 @@
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
"prefer-lowest": false,
|
"prefer-lowest": false,
|
||||||
"platform": {
|
"platform": {
|
||||||
"php": "^8.5"
|
"php": "^8.4"
|
||||||
},
|
},
|
||||||
"platform-dev": {},
|
"platform-dev": {},
|
||||||
"plugin-api-version": "2.9.0"
|
"plugin-api-version": "2.9.0"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
'cidrList' => env('UWIL_ADMIN_SECURE_CIDR_LIST'),
|
|
||||||
'cidrProjectAllowedList' => env('UWIL_ADMIN_SECURE_CIDR_PROJECT_ALLOWED_LIST')
|
|
||||||
];
|
|
||||||
Loading…
Add table
Reference in a new issue