283 lines
10 KiB
PHP
283 lines
10 KiB
PHP
<?php
|
|
|
|
namespace Paymenter\Extensions\Others\Authentik\Http\Controllers;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\User;
|
|
use App\Models\Extension;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Support\Carbon;
|
|
use Laravel\Socialite\Facades\Socialite;
|
|
|
|
class AuthentikController extends Controller
|
|
{
|
|
protected $authentik_driver;
|
|
|
|
public function __construct()
|
|
{
|
|
// Initialiser le driver Authentik avec la configuration de l'extension
|
|
$this->initializeAuthentikDriver();
|
|
}
|
|
|
|
/**
|
|
* Initialiser le driver Authentik avec la configuration de l'extension
|
|
*/
|
|
private function initializeAuthentikDriver()
|
|
{
|
|
$extension = Extension::where('extension', 'Authentik')->first();
|
|
|
|
if (!$extension || !$extension->settings) {
|
|
return;
|
|
}
|
|
|
|
$settings = $extension->settings->pluck('value', 'key')->toArray();
|
|
|
|
if (empty($settings['enabled']) || empty($settings['client_id']) || empty($settings['client_secret']) || empty($settings['base_url'])) {
|
|
return;
|
|
}
|
|
|
|
$this->authentik_driver = Socialite::buildProvider(\Paymenter\Extensions\Others\Authentik\Providers\AuthentikProvider::class, [
|
|
'client_id' => $settings['client_id'],
|
|
'client_secret' => $settings['client_secret'],
|
|
'redirect' => '/oauth/authentik/callback',
|
|
'base_url' => $settings['base_url'],
|
|
'authorize_url' => $settings['base_url'] . '/application/o/authorize/',
|
|
'token_url' => $settings['base_url'] . '/application/o/token/',
|
|
'userinfo_url' => $settings['base_url'] . '/application/o/userinfo/',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Rediriger vers Authentik pour l'authentification
|
|
*/
|
|
public function redirect()
|
|
{
|
|
if (!$this->authentik_driver) {
|
|
return redirect()->route('login')->with('error', 'Authentik n\'est pas configuré correctement.');
|
|
}
|
|
|
|
return $this->authentik_driver
|
|
->scopes(['openid', 'email', 'profile'])
|
|
->with(['prompt' => 'consent'])
|
|
->redirect();
|
|
}
|
|
|
|
/**
|
|
* Gérer le callback d'authentification d'Authentik
|
|
*/
|
|
public function handle(Request $request)
|
|
{
|
|
if (!$this->authentik_driver) {
|
|
return redirect()->route('login')->with('error', 'Authentik n\'est pas configuré correctement.');
|
|
}
|
|
|
|
if ($request->input('error')) {
|
|
return redirect()->route('login')->with('error', 'Erreur d\'authentification: ' . $request->input('error_description'));
|
|
}
|
|
|
|
try {
|
|
// Obtenir le token d'accès
|
|
$token = $this->authentik_driver->getAccessTokenResponse($request->get('code'));
|
|
|
|
// Obtenir les données utilisateur
|
|
try {
|
|
$oauth_user_data = $this->authentik_driver->getUserByToken($token['access_token']);
|
|
} catch (\Exception $e) {
|
|
Log::error('Erreur lors de la récupération des données utilisateur Authentik: ' . $e->getMessage());
|
|
|
|
// Fallback: décoder le token ID
|
|
$id_token = $token['id_token'];
|
|
$oauth_user_data = $this->decodeIdToken($id_token);
|
|
|
|
// Essayer d'obtenir l'email depuis l'API Authentik
|
|
try {
|
|
$user_email = $this->getUserEmailFromAuthentik($token['access_token'], $oauth_user_data['sub']);
|
|
if ($user_email) {
|
|
$oauth_user_data['email'] = $user_email;
|
|
}
|
|
} catch (\Exception $emailError) {
|
|
Log::error('Erreur lors de la récupération de l\'email Authentik: ' . $emailError->getMessage());
|
|
}
|
|
}
|
|
|
|
$oauth_user = $this->authentik_driver->mapUserToObject($oauth_user_data);
|
|
|
|
Log::info('Utilisateur Authentik connecté', [
|
|
'email' => $oauth_user->email,
|
|
'name' => $oauth_user->name,
|
|
'id' => $oauth_user->id,
|
|
'raw_data' => $oauth_user_data
|
|
]);
|
|
|
|
// Vérifier si l'utilisateur existe
|
|
$user = null;
|
|
if (!empty($oauth_user->email)) {
|
|
$user = User::where('email', $oauth_user->email)->first();
|
|
}
|
|
|
|
// Récupérer les paramètres de l'extension
|
|
$extension = Extension::where('extension', 'Authentik')->first();
|
|
$settings = $extension ? $extension->settings->pluck('value', 'key')->toArray() : [];
|
|
|
|
if (!$user) {
|
|
// Vérifier si la création automatique d'utilisateurs est activée
|
|
if (empty($settings['auto_create_users'])) {
|
|
Log::warning('Utilisateur Authentik non trouvé et création automatique désactivée', [
|
|
'searched_email' => $oauth_user->email,
|
|
'searched_authentik_id' => $oauth_user->id ?? null,
|
|
'user_data' => $oauth_user_data
|
|
]);
|
|
|
|
return redirect()->route('login')->with('error', 'Votre compte n\'est pas enregistré. Veuillez contacter un administrateur.');
|
|
}
|
|
|
|
// Créer un nouvel utilisateur
|
|
$user = $this->createUserFromAuthentikData($oauth_user, $oauth_user_data);
|
|
} else {
|
|
// Vérifier si l'utilisateur existant est requis
|
|
if (!empty($settings['require_existing_user']) && !$user) {
|
|
return redirect()->route('login')->with('error', 'Votre compte n\'est pas enregistré. Veuillez contacter un administrateur.');
|
|
}
|
|
}
|
|
|
|
// Connecter l'utilisateur
|
|
Auth::login($user, true);
|
|
return redirect()->route('home');
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error('Erreur lors du callback Authentik: ' . $e->getMessage());
|
|
return redirect()->route('login')->with('error', 'Échec de l\'authentification: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Créer un utilisateur à partir des données Authentik
|
|
*/
|
|
private function createUserFromAuthentikData($oauth_user, $oauth_user_data)
|
|
{
|
|
// Dériver nom et prénom
|
|
$rawGivenName = $oauth_user_data['given_name'] ?? ($oauth_user_data['name'] ?? null);
|
|
$rawFamilyName = $oauth_user_data['family_name'] ?? null;
|
|
$fullName = $oauth_user_data['name'] ?? ($oauth_user->name ?? '');
|
|
|
|
$firstName = '';
|
|
$lastName = '';
|
|
|
|
if (!empty($rawGivenName) && !empty($rawFamilyName)) {
|
|
$firstName = $rawGivenName;
|
|
$lastName = $rawFamilyName;
|
|
} elseif (!empty($fullName)) {
|
|
$parts = preg_split('/\s+/', trim($fullName));
|
|
$firstName = $parts[0] ?? '';
|
|
$lastName = trim(implode(' ', array_slice($parts, 1)));
|
|
}
|
|
|
|
$email = $oauth_user->email ?? ($oauth_user_data['email'] ?? null);
|
|
if (empty($email)) {
|
|
throw new \Exception('Authentik n\'a pas fourni d\'email pour l\'utilisateur.');
|
|
}
|
|
|
|
return User::create([
|
|
'first_name' => $firstName,
|
|
'last_name' => $lastName,
|
|
'email' => $email,
|
|
'password' => Str::random(40),
|
|
'email_verified_at' => Carbon::now(),
|
|
'provider' => 'authentik',
|
|
'provider_id' => $oauth_user->id,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Décoder le token ID JWT d'Authentik
|
|
*/
|
|
private function decodeIdToken($idToken)
|
|
{
|
|
// Les tokens JWT ont 3 parties séparées par des points
|
|
$parts = explode('.', $idToken);
|
|
|
|
if (count($parts) !== 3) {
|
|
throw new \Exception('Format de token ID invalide');
|
|
}
|
|
|
|
// Décoder le payload (deuxième partie)
|
|
$payload = $parts[1];
|
|
|
|
// Ajouter le padding si nécessaire
|
|
$payload .= str_repeat('=', (4 - strlen($payload) % 4) % 4);
|
|
|
|
// Décoder base64
|
|
$decoded = base64_decode($payload);
|
|
|
|
if ($decoded === false) {
|
|
throw new \Exception('Échec du décodage du payload du token ID');
|
|
}
|
|
|
|
$userData = json_decode($decoded, true);
|
|
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
throw new \Exception('Échec de l\'analyse du payload du token ID: ' . json_last_error_msg());
|
|
}
|
|
|
|
return $userData;
|
|
}
|
|
|
|
/**
|
|
* Essayer d'obtenir l'email de l'utilisateur depuis l'API Authentik
|
|
*/
|
|
private function getUserEmailFromAuthentik($accessToken, $userId)
|
|
{
|
|
try {
|
|
$extension = Extension::where('extension', 'Authentik')->first();
|
|
$baseUrl = $extension ? $extension->settings->where('key', 'base_url')->first()->value : null;
|
|
|
|
if (!$baseUrl) {
|
|
return null;
|
|
}
|
|
|
|
// Endpoint 1: API des utilisateurs Authentik
|
|
$response = $this->getHttpClient()->get(
|
|
$baseUrl . '/api/v3/core/users/' . $userId . '/',
|
|
['headers' => ['Authorization' => 'Bearer ' . $accessToken]]
|
|
);
|
|
|
|
$userData = json_decode($response->getBody(), true);
|
|
|
|
if (isset($userData['email']) && !empty($userData['email'])) {
|
|
return $userData['email'];
|
|
}
|
|
|
|
// Endpoint 2: Informations de l'utilisateur actuel
|
|
$response = $this->getHttpClient()->get(
|
|
$baseUrl . '/api/v3/core/users/me/',
|
|
['headers' => ['Authorization' => 'Bearer ' . $accessToken]]
|
|
);
|
|
|
|
$userData = json_decode($response->getBody(), true);
|
|
|
|
if (isset($userData['email']) && !empty($userData['email'])) {
|
|
return $userData['email'];
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error('Erreur API Authentik: ' . $e->getMessage());
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Obtenir le client HTTP pour faire des appels aux APIs externes
|
|
*/
|
|
private function getHttpClient()
|
|
{
|
|
return new \GuzzleHttp\Client([
|
|
'timeout' => 10,
|
|
'verify' => false, // Seulement pour le développement, en production devrait être true
|
|
]);
|
|
}
|
|
}
|