94 lines
3.7 KiB
PHP
94 lines
3.7 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Auth;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\AuditLog;
|
|
use App\Models\DirectoryUser;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Str;
|
|
use Laravel\Socialite\Facades\Socialite;
|
|
use Laravel\Socialite\Two\InvalidStateException;
|
|
|
|
/** Connexion au back-office et au portail agent par SSO Entra (aucun mot de passe local). */
|
|
class EntraLoginController extends Controller
|
|
{
|
|
public function redirect(): RedirectResponse
|
|
{
|
|
return Socialite::driver('azure')->scopes(['openid', 'profile', 'email'])->redirect();
|
|
}
|
|
|
|
public function callback(Request $request): RedirectResponse
|
|
{
|
|
// Entra renvoie ?error=… en cas de refus ou de mauvaise configuration : on l'affiche au lieu d'une 500.
|
|
if ($request->filled('error')) {
|
|
Log::warning('SSO Entra refusé', ['error' => $request->query('error'), 'description' => $request->query('error_description')]);
|
|
abort(403, 'Connexion Entra refusée : '.Str::limit((string) $request->query('error_description'), 300));
|
|
}
|
|
|
|
try {
|
|
$entra = Socialite::driver('azure')->user();
|
|
} catch (InvalidStateException) {
|
|
// Session perdue entre /login et le retour Entra : cookie expiré, ou connexion lancée
|
|
// sur un autre nom d'hôte (127.0.0.1 au lieu de localhost). On relance une fois,
|
|
// depuis l'hôte du callback, ce qui suffit dans ces deux cas.
|
|
if (! $request->session()->pull('sso_retry')) {
|
|
$request->session()->put('sso_retry', true);
|
|
|
|
return redirect()->route('login');
|
|
}
|
|
abort(419, 'Session de connexion expirée. Fermez l\'onglet et ouvrez de nouveau '.config('app.url').'/login');
|
|
}
|
|
$request->session()->forget('sso_retry');
|
|
$tenant = Tenant::where('entra_tenant_id', config('signature.default_tenant.entra_tenant_id'))->firstOrFail();
|
|
|
|
$directoryUser = DirectoryUser::where('tenant_id', $tenant->id)->where('entra_oid', $entra->getId())->first();
|
|
abort_unless($directoryUser && $directoryUser->account_enabled, 403, 'Compte inconnu de l\'annuaire synchronisé.');
|
|
|
|
$user = User::updateOrCreate(
|
|
['entra_oid' => $entra->getId()],
|
|
[
|
|
'tenant_id' => $tenant->id,
|
|
'name' => $directoryUser->display_name ?? $entra->getName(),
|
|
'email' => $directoryUser->mail ?? $entra->getEmail(),
|
|
'role' => $this->roleFor($directoryUser),
|
|
],
|
|
);
|
|
|
|
Auth::login($user);
|
|
$request->session()->regenerate();
|
|
AuditLog::record('admin', $user->email, 'login', null, ['role' => $user->role], $tenant->id);
|
|
|
|
return redirect()->intended($user->role ? route('admin.dashboard') : route('portal.home'));
|
|
}
|
|
|
|
public function logout(Request $request): RedirectResponse
|
|
{
|
|
Auth::logout();
|
|
$request->session()->invalidate();
|
|
$request->session()->regenerateToken();
|
|
|
|
return redirect('/');
|
|
}
|
|
|
|
/** Rôle le plus élevé parmi les groupes AD de rôle dont l'agent est membre (recalculé à chaque connexion). */
|
|
private function roleFor(DirectoryUser $user): ?string
|
|
{
|
|
$groups = $user->groups()->pluck('display_name')->map(fn ($n) => strtolower($n))->all();
|
|
$role = null;
|
|
foreach (config('signature.roles') as $name => $group) {
|
|
if (in_array(strtolower($group), $groups, true)
|
|
&& (User::ROLE_RANK[$name] ?? 0) > (User::ROLE_RANK[$role] ?? 0)) {
|
|
$role = $name;
|
|
}
|
|
}
|
|
|
|
return $role;
|
|
}
|
|
}
|