Merge branch 'bump-php8.5' into 'develop'
chore: ⬆️ Bump to php8.5 + laravel13 + added saml2
See merge request ldc-web/projets/tregalette!36
This commit is contained in:
commit
cd2d35e305
16 changed files with 4092 additions and 2825 deletions
|
|
@ -66,3 +66,10 @@ GOOGLE_RECAPTCHA_VERSION=
|
|||
UWIL_ADMIN_SECURE_CIDR_LIST=""
|
||||
# - LIST SEPARATED WITH ',' (Optional)
|
||||
UWIL_ADMIN_SECURE_CIDR_PROJECT_ALLOWED_LIST=""
|
||||
|
||||
# SAML
|
||||
SAML2_DEFAULT_TENANT_UUID=
|
||||
SAML2_SP_CERT_x509=
|
||||
SAML2_SP_CERT_PRIVATEKEY=
|
||||
SAML2_CONTACT_TECHNICAL_EMAIL=
|
||||
SAML2_CONTACT_SUPPORT_EMAIL=
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/ldcweb-production-8.2:170625-080812
|
||||
FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/uwil-production-8.5:070426-150359
|
||||
COPY --chown=www-data:www-data . /app/srcs
|
||||
USER root
|
||||
RUN chmod -R 755 /app/srcs/storage
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class StaticPageController extends Controller
|
|||
/**
|
||||
* Game rules show
|
||||
*
|
||||
* @param \App\Models\StaticPage $legalPage
|
||||
* @param \App\Models\StaticPage $rulesPage
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function rules(StaticPage $rulesPage)
|
||||
|
|
|
|||
|
|
@ -40,6 +40,12 @@ class Kernel extends HttpKernel
|
|||
\App\Http\Middleware\SecureHeaders::class
|
||||
],
|
||||
|
||||
'saml' => [
|
||||
\App\Http\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
],
|
||||
|
||||
'api' => [
|
||||
\Illuminate\Routing\Middleware\ThrottleRequests::class . ':api',
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
|
|
@ -67,6 +73,5 @@ class Kernel extends HttpKernel
|
|||
'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class,
|
||||
'crm_questions' => \App\Http\Middleware\CrmMiddlewareQuestions::class,
|
||||
'json_only' => \App\Http\Middleware\JsonOnly::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 \Uwil\AdminSecure\Laravel\Exceptions\AdminSecureException 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');
|
||||
}
|
||||
}
|
||||
|
|
@ -3,10 +3,16 @@
|
|||
namespace App\Providers;
|
||||
|
||||
use App\Listeners\PasswordHasReset;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Events\PasswordReset;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Slides\Saml2\Events\SignedIn;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
|
@ -33,4 +39,45 @@ class EventServiceProvider extends ServiceProvider
|
|||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register any events for your application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
Event::listen('Slides\Saml2\Events\SignedIn', function (SignedIn $event) {
|
||||
$messageId = $event->getAuth()->getLastMessageId();
|
||||
|
||||
if (Cache::get(config('cache.prefix') . "_saml_message_id_" . $messageId)) {
|
||||
logger()->error(
|
||||
'SAML MESSAGE ID HANDLER',
|
||||
['error' => 'Message ID : ' . $messageId . " replayed !"]
|
||||
);
|
||||
abort(403);
|
||||
} else {
|
||||
// Cache for 8h.
|
||||
Cache::put(config('cache.prefix') . "_saml_message_id_" . $messageId, $messageId, 28800);
|
||||
}
|
||||
|
||||
$user = $event->getSaml2User();
|
||||
$email = $user->getNameId();
|
||||
$lUser = User::query()->where('email', $email)->first();
|
||||
|
||||
if (!$lUser) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
Auth::guard('backend')->login($lUser);
|
||||
session(['sessionIndex' => $user->getSessionIndex()]);
|
||||
$lUser->last_connection = now();
|
||||
$lUser->saveQuietly();
|
||||
});
|
||||
|
||||
Event::listen('Slides\Saml2\Events\SignedOut', function () {
|
||||
Auth::guard('backend')->logout();
|
||||
Session::save();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ trait ActivityLog
|
|||
*/
|
||||
public static function bootActivityLog(): void
|
||||
{
|
||||
static::observe(ModelObserver::class);
|
||||
static::whenBooted(function () {
|
||||
static::observe(ModelObserver::class);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,33 +4,34 @@
|
|||
"type": "project",
|
||||
"description": "Tregalette website.",
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"php": "^8.5",
|
||||
"axlon/laravel-postal-code-validation": "^3.4",
|
||||
"diglactic/laravel-breadcrumbs": "^8.0",
|
||||
"diglactic/laravel-breadcrumbs": "*",
|
||||
"dxw/cidr": "^3.2",
|
||||
"guzzlehttp/guzzle": "^7.2",
|
||||
"jenssegers/agent": "^2.6",
|
||||
"laravel/framework": "^10.0",
|
||||
"laravel/tinker": "^2.8",
|
||||
"laravel/framework": "^13.0",
|
||||
"laravel/tinker": "^3.0",
|
||||
"laravel/ui": "^4.2",
|
||||
"ldc/ldccrmv4client": "^4.0",
|
||||
"ldc/sitemap-generator": "^2.0",
|
||||
"mews/purifier": "^3.4",
|
||||
"propaganistas/laravel-phone": "^5.0",
|
||||
"propaganistas/laravel-phone": "^6.0",
|
||||
"scaler-tech/laravel-saml2": "^2.7",
|
||||
"sentry/sentry-laravel": "*",
|
||||
"uwil/k8s-laravel": "*"
|
||||
"uwil/admin-secure-laravel": "^5.1",
|
||||
"uwil/k8s-laravel": "*",
|
||||
"uwil/sitemap-generator": "^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"laravel-lang/common": "^4.0",
|
||||
"laravel-lang/common": "^6.0",
|
||||
"laravel/pint": "^1.0",
|
||||
"laravel/sail": "^1.18",
|
||||
"mockery/mockery": "^1.4.4",
|
||||
"nunomaduro/collision": "^7.0",
|
||||
"phpunit/phpunit": "^10.0",
|
||||
"nunomaduro/collision": "^8.0",
|
||||
"phpunit/phpunit": "^12.0",
|
||||
"slevomat/coding-standard": "^8.10",
|
||||
"spatie/laravel-ignition": "^2.0",
|
||||
"thedoctor0/laravel-factory-generator": "^1.2"
|
||||
"spatie/laravel-ignition": "^2.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
|
|||
5967
composer.lock
generated
5967
composer.lock
generated
File diff suppressed because it is too large
Load diff
397
config/saml2.php
Normal file
397
config/saml2.php
Normal file
|
|
@ -0,0 +1,397 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Tenant Model
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This will allow you to override the tenant model with your own.
|
||||
|
|
||||
*/
|
||||
|
||||
'tenantModel' => \Slides\Saml2\Models\Tenant::class,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Use built-in routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If "useRoutes" set to true, the package defines five new routes:
|
||||
|
|
||||
| Method | URI | Name
|
||||
| -------|---------------------------------|------------------
|
||||
| POST | {routesPrefix}/{uuid}/acs | saml.acs
|
||||
| GET | {routesPrefix}/{uuid}/login | saml.login
|
||||
| GET | {routesPrefix}/{uuid}/logout | saml.logout
|
||||
| GET | {routesPrefix}/{uuid}/metadata | saml.metadata
|
||||
| GET | {routesPrefix}/{uuid}/sls | saml.sls
|
||||
|
|
||||
*/
|
||||
|
||||
'useRoutes' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Built-in routes prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define the prefix for built-in routes.
|
||||
|
|
||||
*/
|
||||
|
||||
'routesPrefix' => '/saml2',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Middle groups to use for the SAML routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Note, Laravel 5.2 requires a group which includes StartSession
|
||||
|
|
||||
*/
|
||||
|
||||
'routesMiddleware' => ['saml'],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Signature validation
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Set to true if you want to use parameters from $_SERVER to validate the signature.
|
||||
|
|
||||
*/
|
||||
|
||||
'retrieveParametersFromServer' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Login redirection URL.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The redirection URL after successful login.
|
||||
|
|
||||
*/
|
||||
|
||||
'loginRoute' => '/bo',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Logout redirection URL.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The redirection URL after successful logout.
|
||||
|
|
||||
*/
|
||||
|
||||
'logoutRoute' => '/bo',
|
||||
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Login error redirection URL.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The redirection URL after login failing.
|
||||
|
|
||||
*/
|
||||
|
||||
'errorRoute' => '/bo/login-error',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Strict mode.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If 'strict' is True, then the PHP Toolkit will reject unsigned
|
||||
| or unencrypted messages if it expects them signed or encrypted
|
||||
| Also will reject the messages if not strictly follow the SAML
|
||||
| standard: Destination, NameId, Conditions... are validated too.
|
||||
|
|
||||
*/
|
||||
|
||||
'strict' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Debug mode.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When enabled, errors must be printed.
|
||||
|
|
||||
*/
|
||||
|
||||
'debug' => env('SAML2_DEBUG', env('APP_DEBUG', false)),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Whether to use `X-Forwarded-*` headers to determine port/domain/protocol.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If 'proxyVars' is True, then the Saml lib will trust proxy headers
|
||||
| e.g X-Forwarded-Proto / HTTP_X_FORWARDED_PROTO. This is useful if
|
||||
| your application is running behind a load balancer which terminates SSL.
|
||||
|
|
||||
*/
|
||||
|
||||
'proxyVars' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Service Provider configuration.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| General setting of the service provider.
|
||||
|
|
||||
*/
|
||||
|
||||
'sp' => [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| NameID format.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Specifies constraints on the name identifier to be used to
|
||||
| represent the requested subject.
|
||||
|
|
||||
*/
|
||||
|
||||
'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| SP Certificates.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Usually x509cert and privateKey of the SP are provided by files placed at
|
||||
| the certs folder. But we can also provide them with the following parameters.
|
||||
|
|
||||
*/
|
||||
|
||||
'x509cert' => env('SAML2_SP_CERT_x509', ''),
|
||||
'privateKey' => env('SAML2_SP_CERT_PRIVATEKEY', ''),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Identifier (URI) of the SP entity.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Leave blank to use the 'saml.metadata' route.
|
||||
|
|
||||
*/
|
||||
|
||||
'entityId' => env('SAML2_SP_ENTITYID', ''),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| The Assertion Consumer Service (ACS) URL.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| URL Location where the <Response> from the IdP will be returned, using HTTP-POST binding.
|
||||
| Leave blank to use the 'saml.acs' route.
|
||||
|
|
||||
*/
|
||||
|
||||
'assertionConsumerService' => [
|
||||
'url' => '',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| The Single Logout Service URL.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Specifies info about where and how the <Logout Response> message MUST be
|
||||
| returned to the requester, in this case our SP.
|
||||
|
|
||||
| URL Location where the <Response> from the IdP will be returned, using HTTP-Redirect binding.
|
||||
| Leave blank to use the 'saml.sls' route.
|
||||
|
|
||||
*/
|
||||
|
||||
'singleLogoutService' => [
|
||||
'url' => ''
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| OneLogin security settings.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
|
|
||||
|
|
||||
*/
|
||||
|
||||
'security' => [
|
||||
|
||||
'signatureAlgorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
|
||||
'digestAlgorithm' => 'http://www.w3.org/2001/04/xmlenc#sha256',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| NameId encryption
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Indicates that the nameID of the <samlp:logoutRequest> sent by this SP
|
||||
| will be encrypted.
|
||||
|
|
||||
*/
|
||||
|
||||
'nameIdEncrypted' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| AuthnRequest signage
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Indicates whether the <samlp:AuthnRequest> messages sent by
|
||||
| this SP will be signed. The Metadata of the SP will offer this info
|
||||
|
|
||||
*/
|
||||
|
||||
'authnRequestsSigned' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Logout request signage
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Indicates whether the <samlp:logoutRequest> messages sent by this SP
|
||||
| will be signed.
|
||||
|
|
||||
*/
|
||||
|
||||
'logoutRequestSigned' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Logout response signage
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Indicates whether the <samlp:logoutResponse> messages sent by this SP
|
||||
| will be signed.
|
||||
|
|
||||
*/
|
||||
|
||||
'logoutResponseSigned' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Whether need to sign metadata.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The possible values:
|
||||
| - false
|
||||
| - true (use certs)
|
||||
| - array:
|
||||
| ```
|
||||
| [
|
||||
| 'keyFileName' => 'metadata.key',
|
||||
| 'certFileName' => 'metadata.crt'
|
||||
| ]
|
||||
| ```
|
||||
|
|
||||
*/
|
||||
|
||||
'signMetadata' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Requirement to sign messages.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Indicates a requirement for the <samlp:Response>, <samlp:LogoutRequest> and
|
||||
| <samlp:LogoutResponse> elements received by this SP to be signed.
|
||||
|
|
||||
*/
|
||||
|
||||
'wantMessagesSigned' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Requirement to sign assertion elements.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Indicates a requirement for the <saml:Assertion> elements received by
|
||||
| this SP to be signed.
|
||||
|
|
||||
*/
|
||||
|
||||
'wantAssertionsSigned' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Requirement to encrypt NameID.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Indicates a requirement for the NameID received by this SP to be encrypted.
|
||||
|
|
||||
*/
|
||||
|
||||
'wantNameIdEncrypted' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication context.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Set to false and no AuthContext will be sent in the AuthNRequest,
|
||||
|
|
||||
| Set true or don't present this parameter and you will get an
|
||||
| AuthContext 'exact' 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
|
||||
|
|
||||
| Set an array with the possible auth context values:
|
||||
| ['urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509']
|
||||
|
|
||||
*/
|
||||
|
||||
'requestedAuthnContext' => true,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Contact information.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| It is recommended to supply a technical and support contacts.
|
||||
|
|
||||
*/
|
||||
|
||||
'contactPerson' => [
|
||||
'technical' => [
|
||||
'givenName' => env('SAML2_CONTACT_TECHNICAL_NAME', env('APP_NAME', 'Technical')),
|
||||
'emailAddress' => env('SAML2_CONTACT_TECHNICAL_EMAIL', 'no@reply.com')
|
||||
],
|
||||
'support' => [
|
||||
'givenName' => env('SAML2_CONTACT_SUPPORT_NAME', env('APP_NAME', 'Support')),
|
||||
'emailAddress' => env('SAML2_CONTACT_SUPPORT_EMAIL', 'no@reply.com')
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Organization information.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The info in en_US lang is recommended, add more if required.
|
||||
|
|
||||
*/
|
||||
|
||||
'organization' => [
|
||||
'en-US' => [
|
||||
'name' => env('SAML2_ORGANIZATION_NAME', env('APP_NAME', 'Name')),
|
||||
'displayname' => env('SAML2_ORGANIZATION_NAME', env('APP_NAME', 'Name')),
|
||||
'url' => env('SAML2_ORGANIZATION_URL', env('APP_URL', 'http://url'))
|
||||
],
|
||||
],
|
||||
|
||||
/**
|
||||
* Default tenant UUID
|
||||
*/
|
||||
|
||||
'default_tenant_uuid' => env('SAML2_DEFAULT_TENANT_UUID', null)
|
||||
|
||||
];
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\Users\RoleEnum;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('email_verified_at');
|
||||
$table->dropColumn('password');
|
||||
$table->date('last_connection')->nullable()->after('role');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->timestamp('email_verified_at')->nullable()->after('email');
|
||||
$table->string('password')->after('email_verified_at');
|
||||
$table->dropColumn('last_connection');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -27,7 +27,6 @@ data:
|
|||
MESBR_REF_SITE: "${APP_MESBR_REF_SITE}"
|
||||
MESBR_REF_CAMP: "${APP_MESBR_REF_CAMP}"
|
||||
MESBR_ENDPOINT: "${APP_MESBR_ENDPOINT}"
|
||||
LDC_WEB_API_KEY: "${APP_LDC_WEB_API_KEY}"
|
||||
GOOGLE_RECAPTCHA_PRIVATE: "${APP_GOOGLE_RECAPTCHA_PRIVATE}"
|
||||
GOOGLE_RECAPTCHA_PUBLIC: "${APP_GOOGLE_RECAPTCHA_PUBLIC}"
|
||||
GOOGLE_RECAPTCHA_VERSION: "${APP_GOOGLE_RECAPTCHA_VERSION}"
|
||||
|
|
@ -40,6 +39,11 @@ data:
|
|||
MAIL_CONTACT_COMMERCIAL: "${APP_MAIL_CONTACT_COMMERCIAL}"
|
||||
MAIL_CONTACT_VARIOUS: "${APP_MAIL_CONTACT_VARIOUS}"
|
||||
UWIL_ADMIN_SECURE_CIDR_LIST: "${APP_GLOBAL_UWIL_ADMIN_SECURE_CIDR_LIST}"
|
||||
SAML2_SP_CERT_x509: "${APP_SAML2_SP_CERT_x509}"
|
||||
SAML2_SP_CERT_PRIVATEKEY: "${APP_SAML2_SP_CERT_PRIVATEKEY}"
|
||||
SAML2_CONTACT_TECHNICAL_EMAIL: "${APP_SAML2_CONTACT_TECHNICAL_EMAIL}"
|
||||
SAML2_CONTACT_SUPPORT_EMAIL: "${APP_SAML2_CONTACT_SUPPORT_EMAIL}"
|
||||
SAML2_DEFAULT_TENANT_UUID: "${APP_SAML2_DEFAULT_TENANT_UUID}"
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@
|
|||
<rule ref="Generic.Files.LineLength"/>
|
||||
<rule ref="Generic.Formatting.DisallowMultipleStatements"/>
|
||||
<rule ref="Generic.Formatting.MultipleStatementAlignment"/>
|
||||
<rule ref="Generic.Formatting.NoSpaceAfterCast"/>
|
||||
<rule ref="Generic.Functions.CallTimePassByReference"/>
|
||||
<rule ref="Generic.Functions.FunctionCallArgumentSpacing"/>
|
||||
<rule ref="Generic.Metrics.CyclomaticComplexity"/>
|
||||
<rule ref="Generic.Metrics.NestingLevel"/>
|
||||
|
|
@ -45,9 +43,6 @@
|
|||
<rule ref="Generic.Strings.UnnecessaryStringConcat"/>
|
||||
<rule ref="Generic.WhiteSpace.DisallowTabIndent"/>
|
||||
<rule ref="Generic.WhiteSpace.ScopeIndent"/>
|
||||
<rule ref="MySource.PHP.EvalObjectFactory"/>
|
||||
<rule ref="MySource.PHP.GetRequestData"/>
|
||||
<rule ref="MySource.PHP.AjaxNullComparison"/>
|
||||
<rule ref="PEAR.Classes.ClassDeclaration"/>
|
||||
<rule ref="PEAR.Commenting.InlineComment"/>
|
||||
<rule ref="PSR1.Classes.ClassDeclaration"/>
|
||||
|
|
@ -80,7 +75,6 @@
|
|||
<rule ref="Squiz.Scope.MemberVarScope"/>
|
||||
<rule ref="Squiz.Scope.MethodScope"/>
|
||||
<rule ref="Squiz.Scope.StaticThisUsage"/>
|
||||
<rule ref="Squiz.WhiteSpace.LanguageConstructSpacing"/>
|
||||
<rule ref="Squiz.WhiteSpace.LogicalOperatorSpacing"/>
|
||||
<rule ref="Squiz.WhiteSpace.MemberVarSpacing">
|
||||
<properties>
|
||||
|
|
|
|||
|
|
@ -3,65 +3,29 @@
|
|||
@section('description', __('Formulaire d\'authentification au back-office.'))
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8 p-0">
|
||||
<div class="card">
|
||||
<div class="card-header">{{ __('Connexion') }}</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ route('bo.login') }}">
|
||||
@csrf
|
||||
<div class="row mb-3">
|
||||
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('Adresse Email') }}</label>
|
||||
<div class="col-md-6">
|
||||
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email"
|
||||
value="{{ old('email') }}" autocomplete="email" autofocus required>
|
||||
@error('email')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong>
|
||||
</span>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Mot de passe') }}</label>
|
||||
<div class="col-md-6">
|
||||
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror"
|
||||
name="password" autocomplete="current-password" required>
|
||||
@error('password')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong>
|
||||
</span>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6 offset-md-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}>
|
||||
<label class="form-check-label user-select-none" for="remember">
|
||||
{{ __('Se souvenir de moi') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-0">
|
||||
<div class="col-md-8 offset-md-4">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span>{{ __('Connexion') }}</span>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 p-2 text-center">
|
||||
<img class="logo img-fluid" src="{{ Vite::asset('resources/assets/images/logo.png') }}" alt="{{ config('app.name') }}">
|
||||
</div>
|
||||
<div class="col-md-8 p-0">
|
||||
<div class="mt-2">
|
||||
@include('modules.flash-messages')
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header fw-bold">{{ __('Connexion') }}</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col text-center">
|
||||
<a class="btn btn-primary" href="{{ route('saml.login', ['uuid' => config('saml2.default_tenant_uuid')]) }}">
|
||||
<span>{{ __('Connexion avec UWIL Support') }}</span>
|
||||
<i class="fa-solid fa-arrow-right-from-bracket"></i>
|
||||
</button>
|
||||
@if (Route::has('bo.password.request'))
|
||||
<a class="btn btn-link" href="{{ route('bo.password.request') }}">
|
||||
{{ __('Mot de passe oublié ?') }}
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue