This commit is contained in:
mrKamoo
2026-09-30 16:27:32 +02:00
commit 310724708f
147 changed files with 21327 additions and 0 deletions
@@ -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;
}
}