45
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user