feat: ✨ Remove LDC Web API dependency
This commit is contained in:
parent
26f818b6b9
commit
a4e0fafa16
11 changed files with 82 additions and 260 deletions
|
|
@ -6,6 +6,12 @@ All notable changes to `admin-secure-laravel` will be documented in this file.
|
|||
- This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
|
||||
- Commits respect [Conventionnal commits](https://www.conventionalcommits.org/en/v1.0.0/) & use [Gitmoji](https://gitmoji.dev/).
|
||||
|
||||
## **[v5.0.0] - 09/01/2026**
|
||||
|
||||
### Added
|
||||
|
||||
- feat: ✨ Remove LDC Web API dependency
|
||||
|
||||
## **[v4.0.0] - 07/04/2025**
|
||||
|
||||
### Added
|
||||
|
|
|
|||
27
README.md
27
README.md
|
|
@ -7,36 +7,19 @@ composer req uwil/admin-secure-laravel
|
|||
.env.example :
|
||||
|
||||
```dotenv
|
||||
# ADMIN SECURE
|
||||
# - API ENDPOINT (Optional)
|
||||
LDC_WEB_API_ENDPOINT="https://ldc-web.ldc.fr/api/ldc-networks"
|
||||
# - API KEY
|
||||
LDC_WEB_API_KEY=""
|
||||
# UWIL ADMIN SECURE
|
||||
# - LIST SEPARATED WITH ',' (Required)
|
||||
UWIL_ADMIN_SECURE_CIDR_LIST=""
|
||||
# - LIST SEPARATED WITH ',' (Optional)
|
||||
LDC_WEB_CIDR_MANUAL_ALLOWED_LIST=""
|
||||
UWIL_ADMIN_SECURE_CIDR_PROJECT_ALLOWED_LIST=""
|
||||
```
|
||||
|
||||
README.md :
|
||||
```
|
||||
Put every min in PROD :
|
||||
php artisan schedule:run
|
||||
```
|
||||
|
||||
## Available Commands
|
||||
```
|
||||
uwil:admin-secure:sync
|
||||
```
|
||||
## Usage
|
||||
Put 'admin-secure' middleware on route to protect it.
|
||||
## Cron configure
|
||||
|
||||
Nothing to do command is already configureed to run everyday.
|
||||
You just need to install this package.
|
||||
**!! Runs only in production !!**
|
||||
|
||||
# Changelog
|
||||
|
||||
Latest version v4.0.0.
|
||||
Latest version v5.0.0.
|
||||
|
||||
See the [CHANGELOG.md](CHANGELOG.md) file for details.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
"name": "uwil/admin-secure-laravel",
|
||||
"description": "Restrict access to LDC Network",
|
||||
"license": "MIT",
|
||||
"version": "4.0.0",
|
||||
"version": "5.0.0",
|
||||
"readme": "https://lab.s-web.fr/ldc-web/projets/ldc-web-admin-secure-laravel/-/blob/main/README.md",
|
||||
"support": {
|
||||
"email": "equipe.web@ldc.fr",
|
||||
"email": "digital@ldc.fr",
|
||||
"issues": "https://lab.s-web.fr/ldc-web/projets/ldc-web-admin-secure-laravel/-/issues"
|
||||
},
|
||||
"require": {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'endpoint' => env('LDC_WEB_API_ENDPOINT', 'https://ldc-web.ldc.fr/api/ldc-networks'),
|
||||
'apiKey' => env('LDC_WEB_API_KEY'),
|
||||
'cidrManualAllowedList' => env('LDC_WEB_CIDR_MANUAL_ALLOWED_LIST')
|
||||
'cidrList' => env('UWIL_ADMIN_SECURE_CIDR_LIST'),
|
||||
'cidrProjectAllowedList' => env('UWIL_ADMIN_SECURE_CIDR_PROJECT_ALLOWED_LIST')
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 LDC Service Web
|
||||
Copyright (c) 2022 LDC Digital
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
|
|
@ -7,29 +7,10 @@ use Dxw\CIDR\IPv4Address;
|
|||
use Dxw\CIDR\IPv4Range;
|
||||
use Dxw\CIDR\IPv6Address;
|
||||
use Dxw\CIDR\IPv6Range;
|
||||
use Illuminate\Http\Client\Response;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Uwil\AdminSecure\Laravel\Exceptions\AdminSecureException;
|
||||
use Uwil\AdminSecure\Laravel\Exceptions\AdminSecureInvalidAdressException;
|
||||
|
||||
abstract class AdminSecure
|
||||
{
|
||||
/**
|
||||
* The cache key where to store ip list.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static string $cache_key = 'ldcweb_ip_list';
|
||||
|
||||
/**
|
||||
* The cache time to live.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static string $cache_ttl = '180 minutes';
|
||||
|
||||
/**
|
||||
* Private ipV4 CIDR list
|
||||
*
|
||||
|
|
@ -89,7 +70,7 @@ abstract class AdminSecure
|
|||
*/
|
||||
public static function fetchClientIp(): IPv4Address|IPv6Address
|
||||
{
|
||||
// ! HA PROXY HACK.
|
||||
// ! REVERSE PROXY HACK.
|
||||
$ip = request()->server('HTTP_X_FORWARDED_FOR') ?? request()->getClientIp();
|
||||
$ip = IPAddress::Make($ip);
|
||||
|
||||
|
|
@ -99,64 +80,6 @@ abstract class AdminSecure
|
|||
return $ip->unwrap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ip list from cache
|
||||
*
|
||||
* @return array<\Dxw\CIDR\IPv4Address|\Dxw\CIDR\IPv6Address>
|
||||
*/
|
||||
public static function getCacheList(): array
|
||||
{
|
||||
try {
|
||||
/** @var array<\Dxw\CIDR\IPv4Address|\Dxw\CIDR\IPv6Address>|null $ip_list */
|
||||
$ip_list = Cache::get(self::$cache_key);
|
||||
if (!$ip_list) {
|
||||
$ip_list = self::fetchIpList();
|
||||
self::validateIpList($ip_list);
|
||||
self::setCacheList($ip_list);
|
||||
return $ip_list;
|
||||
}
|
||||
return $ip_list;
|
||||
} catch (AdminSecureInvalidAdressException) {
|
||||
$ip_list = self::fetchIpList();
|
||||
self::validateIpList($ip_list);
|
||||
self::setCacheList($ip_list);
|
||||
return $ip_list;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set ip list to cache
|
||||
*
|
||||
* @param array<\Dxw\CIDR\IPv4Address|\Dxw\CIDR\IPv6Address> $ip_list
|
||||
* @return void
|
||||
*/
|
||||
public static function setCacheList(array $ip_list): void
|
||||
{
|
||||
Cache::put(self::$cache_key, $ip_list, \DateInterval::createFromDateString(self::$cache_ttl));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch Ip Adress list from Uwil Web
|
||||
*
|
||||
* @return array<\Dxw\CIDR\IPv4Address|\Dxw\CIDR\IPv6Address>
|
||||
* @throws \Uwil\AdminSecure\Laravel\Exceptions\AdminSecureException If Wrong env value.
|
||||
* @throws \Illuminate\Http\Client\RequestException If calling admin secure fails.
|
||||
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
|
||||
*/
|
||||
public static function fetchIpList(): array
|
||||
{
|
||||
// phpcs:enable
|
||||
$endpoint = self::getOptions()->getEndpoint();
|
||||
$key = self::getOptions()->getApiKey();
|
||||
|
||||
$response = Http::get($endpoint, ['token' => $key])->throw();
|
||||
$adresses = self::parseAdresses($response);
|
||||
|
||||
Log::info(\sprintf('ADMINSECURE : Fetched %d ip(s) from LDC WEB', \count($adresses)));
|
||||
|
||||
return $adresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if ipV4 is private
|
||||
*
|
||||
|
|
@ -187,34 +110,6 @@ abstract class AdminSecure
|
|||
$initial || $ipv6_range->containsAddress($ipv6_address));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate ip list
|
||||
*
|
||||
* @param array $ip_list
|
||||
* @return void
|
||||
* @throws \Uwil\AdminSecure\Laravel\Exceptions\AdminSecureInvalidAdressException If an IP is invalid.
|
||||
*/
|
||||
private static function validateIpList(array $ip_list): void
|
||||
{
|
||||
\collect($ip_list)->each(function ($ip) {
|
||||
if (!\is_object($ip) or !\in_array(\get_class($ip), [IPv4Address::class, IPv6Address::class])) {
|
||||
throw new AdminSecureInvalidAdressException('Invalid adresse ' . \strval(\json_encode($ip)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse adresses
|
||||
*
|
||||
* @param \Illuminate\Http\Client\Response $response
|
||||
* @return array<\Dxw\CIDR\IPv4Address|\Dxw\CIDR\IPv6Address>
|
||||
*/
|
||||
private static function parseAdresses(Response $response): array
|
||||
{
|
||||
return \collect($response->json())
|
||||
->map(fn (string $ip) => IPAddress::Make($ip)->unwrap())->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Admin secure options
|
||||
*
|
||||
|
|
|
|||
|
|
@ -2,11 +2,9 @@
|
|||
|
||||
namespace Uwil\AdminSecure\Laravel;
|
||||
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Routing\Router;
|
||||
use Uwil\AdminSecure\Laravel\Console\SyncFromLdcWeb;
|
||||
use Uwil\AdminSecure\Laravel\Http\Middleware\AdminSecureMiddleware;
|
||||
|
||||
class AdminSecureServiceProvider extends ServiceProvider
|
||||
|
|
@ -22,21 +20,9 @@ class AdminSecureServiceProvider extends ServiceProvider
|
|||
__DIR__ . '/../config' => config_path(),
|
||||
], 'config');
|
||||
|
||||
// * Register the command if we are using the application via the CLI .
|
||||
$this->commands([SyncFromLdcWeb::class]);
|
||||
|
||||
// * Set filter condition
|
||||
$this->app->singleton('hasToFilterIp', fn (Application $app) => $app->environment('production', 'staging'));
|
||||
|
||||
// * Cron Report everyday
|
||||
$this->app->booted(function () {
|
||||
if ($this->app->make('hasToFilterIp')) {
|
||||
/** @var \Illuminate\Console\Scheduling\Schedule */
|
||||
$schedule = $this->app->make(Schedule::class);
|
||||
$schedule->command('uwil:admin-secure:sync')->daily();
|
||||
}
|
||||
});
|
||||
|
||||
/** @var \Illuminate\Routing\Router */
|
||||
$router = $this->app->make(Router::class);
|
||||
$router->aliasMiddleware('admin-secure', AdminSecureMiddleware::class);
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Uwil\AdminSecure\Laravel\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Uwil\AdminSecure\Laravel\AdminSecure;
|
||||
|
||||
/**
|
||||
* Send Report Status To LDC Web.
|
||||
*
|
||||
* @author Vincent BRIDIER <vincent.bridier@ldc.fr>
|
||||
*/
|
||||
class SyncFromLdcWeb extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'uwil:admin-secure:sync';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Sync IP list from LDC Web';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return integer
|
||||
* @throws \Uwil\AdminSecure\Laravel\Exceptions\AdminSecureException If missing LDC_WEB_API_KEY key.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$this->info($this->description);
|
||||
AdminSecure::setCacheList(AdminSecure::fetchIpList());
|
||||
$this->info('Done.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Uwil\AdminSecure\Laravel\Exceptions;
|
||||
|
||||
class AdminSecureInvalidAdressException extends AdminSecureException
|
||||
{
|
||||
}
|
||||
|
|
@ -4,9 +4,7 @@ namespace Uwil\AdminSecure\Laravel\Http\Middleware;
|
|||
|
||||
use Closure;
|
||||
use Dxw\CIDR\IP;
|
||||
use Dxw\CIDR\IPv4Address;
|
||||
use Dxw\CIDR\IPv4Range;
|
||||
use Dxw\CIDR\IPv6Address;
|
||||
use Dxw\CIDR\IPv6Range;
|
||||
use Illuminate\Http\Request;
|
||||
use Uwil\AdminSecure\Laravel\AdminSecure;
|
||||
|
|
@ -27,9 +25,10 @@ class AdminSecureMiddleware
|
|||
}
|
||||
|
||||
$client_ip = AdminSecure::fetchClientIp();
|
||||
$ip_is_allowed = \collect(AdminSecure::getCacheList())
|
||||
->reduce(fn (null|bool $initial, IPv4Address|IPv6Address $ip_address) => $initial || IP::contains(
|
||||
$ip_address,
|
||||
|
||||
$ip_is_allowed = \collect(AdminSecure::getOptions()->getCidrList())
|
||||
->reduce(fn (null|bool $initial, IPv4Range|IPv6Range $ip_range) => $initial || IP::contains(
|
||||
$ip_range,
|
||||
$client_ip
|
||||
)->unwrap());
|
||||
|
||||
|
|
|
|||
118
src/Options.php
118
src/Options.php
|
|
@ -30,7 +30,7 @@ final class Options
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
private static string $cache_config_key = 'ldcweb_ip_cidr_config_list';
|
||||
private static string $cache_config_key = 'uwil_ip_cidr_config_list';
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
|
|
@ -47,33 +47,23 @@ final class Options
|
|||
}
|
||||
|
||||
/**
|
||||
* Get end point url
|
||||
* Get cidr list
|
||||
*
|
||||
* @return string
|
||||
* @return array<\Dxw\CIDR\IPv4Range|\Dxw\CIDR\IPv6Range>
|
||||
*/
|
||||
public function getEndpoint(): string
|
||||
public function getCidrList(): array
|
||||
{
|
||||
return $this->options['endpoint'];
|
||||
return $this->options['cidrList'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get api key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getApiKey(): string
|
||||
{
|
||||
return $this->options['apiKey'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cidr allowed list
|
||||
* Get cidr project allowed list
|
||||
*
|
||||
* @return array<\Dxw\CIDR\IPv4Range|\Dxw\CIDR\IPv6Range>
|
||||
*/
|
||||
public function getCidrAllowedList(): array
|
||||
{
|
||||
return $this->options['cidrManualAllowedList'];
|
||||
return $this->options['cidrProjectAllowedList'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -85,34 +75,19 @@ final class Options
|
|||
private function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'endpoint' => '',
|
||||
'apiKey' => '',
|
||||
'cidrManualAllowedList' => []
|
||||
'cidrList' => [],
|
||||
'cidrProjectAllowedList' => []
|
||||
]);
|
||||
|
||||
$resolver->setAllowedTypes('endpoint', [
|
||||
'string', \Closure::fromCallable([$this, 'validateEndpoint'])
|
||||
]);
|
||||
$resolver->setAllowedTypes('apiKey', 'string');
|
||||
$resolver->setNormalizer('cidrManualAllowedList', \Closure::fromCallable([$this, 'normalizeCidrList']));
|
||||
$resolver->setNormalizer('cidrList', \Closure::fromCallable([$this, 'normalizeCidrList']));
|
||||
$resolver->setNormalizer('cidrProjectAllowedList', \Closure::fromCallable([$this, 'normalizeCidrProjectList']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate endpoint
|
||||
*
|
||||
* @param null|string $value
|
||||
* @return boolean
|
||||
*/
|
||||
private function validateEndpoint(?string $value): bool
|
||||
{
|
||||
return !(!$value or !\filter_var($value, \FILTER_VALIDATE_URL));
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise Cidr list
|
||||
* Normalise Cidr list for cidrList option
|
||||
*
|
||||
* @param \Symfony\Component\OptionsResolver\Options $options
|
||||
* @param string $value
|
||||
* @param string|null $value
|
||||
* @return array
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
|
||||
*/
|
||||
|
|
@ -121,31 +96,60 @@ final class Options
|
|||
?string $value
|
||||
): array {
|
||||
// phpcs:enable
|
||||
return $this->doNormalizeCidrList($value, 'cidrList');
|
||||
}
|
||||
|
||||
$allowed_cidr_config_list = collect(explode(',', strval($value)))->filter();
|
||||
/**
|
||||
* 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(','));
|
||||
/** @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 AdminSecureException(\trans(
|
||||
'LDC_WEB_CIDR_MANUAL_ALLOWED_LIST not valid (ipv4 or ipv6 cidr) :cidr',
|
||||
[
|
||||
'cidr' => $cidr
|
||||
]
|
||||
));
|
||||
}
|
||||
return $ip_range->unwrap();
|
||||
})->all();
|
||||
|
||||
// * Flush cache if hash config hash changed
|
||||
if ($allowed_cidr_config_list_hash !== \collect(Cache::get(self::$cache_config_key))->get('hash')) {
|
||||
Cache::forget(self::$cache_config_key);
|
||||
// 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 AdminSecureException(\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(self::$cache_config_key, fn () => [
|
||||
return \collect(Cache::rememberForever($cacheConfigKey, fn() => [
|
||||
'list' => $allowed_cidr_list,
|
||||
'hash' => $allowed_cidr_config_list_hash
|
||||
]))->get('list');
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue