diff --git a/.env.example b/.env.example index 36b210d..862b6b2 100644 --- a/.env.example +++ b/.env.example @@ -75,10 +75,6 @@ AWS_USE_PATH_STYLE_ENDPOINT=false GITEA_URL=https://gitea.sam-coffre.duckdns.org GITEA_TOKEN= GITEA_REPO=Groupe1/Ressources-relationnel -# Token d'un ADMIN Gitea (scopes "write:admin" + "write:issue") : sert à créer un -# compte Gitea à chaque utilisateur (au 1er envoi) et à ouvrir les tickets en leur -# nom (en-tête Sudo). Laisser vide pour retomber sur GITEA_TOKEN / compte de service. -GITEA_ADMIN_TOKEN= # --- Applications mobiles (page /telecharger) --- # Laisser vide pour servir les fichiers de public/downloads/ (.apk / .ipa), diff --git a/app/Http/Controllers/SuggestionController.php b/app/Http/Controllers/SuggestionController.php index c1f11ab..861a51a 100644 --- a/app/Http/Controllers/SuggestionController.php +++ b/app/Http/Controllers/SuggestionController.php @@ -4,7 +4,6 @@ namespace App\Http\Controllers; use App\Models\Utilisateur; use App\Services\GiteaIssueService; -use App\Services\GiteaUserService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Validation\Rule; @@ -18,7 +17,7 @@ class SuggestionController extends Controller } // Traiter la suggestion et créer un ticket dans Gitea - public function store(Request $request, GiteaIssueService $gitea, GiteaUserService $giteaUsers) + public function store(Request $request, GiteaIssueService $gitea) { $data = $request->validate([ 'type' => ['required', Rule::in(['Modification', 'Ajout', 'Correction'])], @@ -38,10 +37,6 @@ class SuggestionController extends Controller $user = Auth::user(); $auteur = $user->Pseudo ?? 'Utilisateur'; - // Compte Gitea de l'utilisateur (créé au premier envoi) pour que le - // ticket soit ouvert en son nom et qu'il puisse le suivre. - $compte = $giteaUsers->assurerCompte($user); - $title = "[{$data['type']}] {$data['titre']}"; $body = "**Type de demande :** {$data['type']}\n" @@ -51,20 +46,9 @@ class SuggestionController extends Controller ."---\n" .'_Ticket créé automatiquement depuis le formulaire de suggestions du site._'; - if ($gitea->createIssue($title, $body, $compte['username'] ?? null)) { - $redirect = redirect()->route('suggestions.create') + if ($gitea->createIssue($title, $body)) { + return redirect()->route('suggestions.create') ->with('success', 'Merci ! Votre suggestion a bien été transmise à l\'équipe.'); - - // Identifiants affichés UNE seule fois, uniquement si le compte vient d'être créé. - if ($compte !== null && $compte['motDePasseInitial'] !== null) { - $redirect->with('gitea_compte', [ - 'url' => rtrim((string) config('services.gitea.url'), '/'), - 'username' => $compte['username'], - 'mot_de_passe' => $compte['motDePasseInitial'], - ]); - } - - return $redirect; } return back()->withInput()->withErrors([ diff --git a/app/Models/Utilisateur.php b/app/Models/Utilisateur.php index b8ab1ae..9597cea 100644 --- a/app/Models/Utilisateur.php +++ b/app/Models/Utilisateur.php @@ -11,7 +11,6 @@ use Illuminate\Support\Carbon; * @property string $Email * @property string $MotDePasse * @property string $Pseudo - * @property string|null $GiteaUsername * @property string $Statut * @property int $ID_Role * @property Carbon $DateInscription @@ -38,7 +37,6 @@ class Utilisateur extends Authenticatable 'Email', 'MotDePasse', 'Pseudo', - 'GiteaUsername', 'Statut', 'ID_Role', 'DateInscription', diff --git a/app/Services/GiteaIssueService.php b/app/Services/GiteaIssueService.php index 416a1d0..6de6bf8 100644 --- a/app/Services/GiteaIssueService.php +++ b/app/Services/GiteaIssueService.php @@ -2,7 +2,6 @@ namespace App\Services; -use Illuminate\Http\Client\Response; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; @@ -11,13 +10,9 @@ class GiteaIssueService /** * Crée un ticket (issue) dans le dépôt Gitea configuré. * - * Si $sudo est fourni (username Gitea de l'utilisateur), le ticket est - * créé en son nom via l'en-tête "Sudo" (nécessite un token admin). En cas - * de refus, on retombe sur une création avec le compte de service. - * * @return bool true si le ticket a bien été créé, false sinon. */ - public function createIssue(string $title, string $body, ?string $sudo = null): bool + public function createIssue(string $title, string $body): bool { $url = rtrim((string) config('services.gitea.url'), '/'); $repo = trim((string) config('services.gitea.repo'), '/'); @@ -30,15 +25,14 @@ class GiteaIssueService } try { - $response = $this->postIssue($url, $repo, $token, $title, $body, $sudo); - - if ($sudo !== null && $response->failed()) { - Log::warning('Création du ticket au nom de l\'utilisateur refusée, repli sur le compte de service.', [ - 'sudo' => $sudo, - 'status' => $response->status(), + // Gitea attend l'en-tête "Authorization: token ". + $response = Http::withToken($token, 'token') + ->acceptJson() + ->timeout(10) + ->post("{$url}/api/v1/repos/{$repo}/issues", [ + 'title' => $title, + 'body' => $body, ]); - $response = $this->postIssue($url, $repo, $token, $title, $body, null); - } } catch (\Throwable $e) { Log::error('Appel API Gitea impossible : '.$e->getMessage()); @@ -56,27 +50,4 @@ class GiteaIssueService return true; } - - private function postIssue( - string $url, - string $repo, - string $token, - string $title, - string $body, - ?string $sudo, - ): Response { - // Gitea attend l'en-tête "Authorization: token ". - $request = Http::withToken($token, 'token') - ->acceptJson() - ->timeout(10); - - if ($sudo !== null) { - $request = $request->withHeaders(['Sudo' => $sudo]); - } - - return $request->post("{$url}/api/v1/repos/{$repo}/issues", [ - 'title' => $title, - 'body' => $body, - ]); - } } diff --git a/app/Services/GiteaUserService.php b/app/Services/GiteaUserService.php deleted file mode 100644 index 0bfa57e..0000000 --- a/app/Services/GiteaUserService.php +++ /dev/null @@ -1,132 +0,0 @@ -GiteaUsername) { - return ['username' => $utilisateur->GiteaUsername, 'motDePasseInitial' => null]; - } - - $client = $this->client(); - - if ($client === null) { - return null; - } - - // Un compte existe peut-être déjà avec cet email (créé hors du site) : on le réutilise. - if ($existant = $this->chercherParEmail($client, $utilisateur->Email)) { - $utilisateur->GiteaUsername = $existant; - $utilisateur->save(); - - return ['username' => $existant, 'motDePasseInitial' => null]; - } - - $username = $this->genererUsername($utilisateur); - $motDePasse = Str::password(16); - - try { - $response = $client->post('/api/v1/admin/users', [ - 'username' => $username, - 'email' => $utilisateur->Email, - 'password' => $motDePasse, - 'full_name' => $utilisateur->Pseudo ?? $username, - 'must_change_password' => true, - 'send_notify' => false, - ]); - } catch (\Throwable $e) { - Log::error('Appel API Gitea (création utilisateur) impossible : '.$e->getMessage()); - - return null; - } - - if ($response->failed()) { - Log::warning('Création du compte Gitea échouée (token admin requis ?)', [ - 'status' => $response->status(), - 'body' => $response->body(), - ]); - - return null; - } - - $utilisateur->GiteaUsername = $username; - $utilisateur->save(); - - return ['username' => $username, 'motDePasseInitial' => $motDePasse]; - } - - private function client(): ?PendingRequest - { - $url = rtrim((string) config('services.gitea.url'), '/'); - $token = (string) (config('services.gitea.admin_token') ?: config('services.gitea.token')); - - if ($url === '' || $token === '') { - Log::warning('Gitea non configuré (URL / token manquant) : compte utilisateur non créé.'); - - return null; - } - - return Http::baseUrl($url) - ->withToken($token, 'token') - ->acceptJson() - ->timeout(10); - } - - private function chercherParEmail(PendingRequest $client, string $email): ?string - { - try { - $response = $client->get('/api/v1/admin/emails/search', ['q' => $email]); - } catch (\Throwable) { - return null; - } - - if ($response->failed()) { - return null; - } - - foreach ($response->json() ?? [] as $entree) { - if (strcasecmp($entree['email'] ?? '', $email) === 0) { - return $entree['username'] ?? null; - } - } - - return null; - } - - /** - * Username déterministe et unique : pseudo en slug, suffixé de l'ID interne. - */ - private function genererUsername(Utilisateur $utilisateur): string - { - $base = Str::slug((string) ($utilisateur->Pseudo ?? '')); - - if ($base === '') { - $base = 'utilisateur'; - } - - return Str::limit($base, 30, '').'-'.$utilisateur->ID_Utilisateur; - } -} diff --git a/config/services.php b/config/services.php index 771ddd0..d3d2fcb 100644 --- a/config/services.php +++ b/config/services.php @@ -37,13 +37,9 @@ return [ // Gitea : création automatique de tickets (issues) depuis le formulaire // de suggestions du site. Le token doit avoir le scope "write:issue". - // Le token admin (scopes write:admin + write:issue) sert à créer les - // comptes Gitea des utilisateurs et à ouvrir les tickets en leur nom ; - // à défaut, le token de service est utilisé. 'gitea' => [ 'url' => env('GITEA_URL', 'https://gitea.sam-coffre.duckdns.org'), 'token' => env('GITEA_TOKEN'), - 'admin_token' => env('GITEA_ADMIN_TOKEN'), 'repo' => env('GITEA_REPO', 'Groupe1/Ressources-relationnel'), ], diff --git a/database/migrations/2026_07_07_120000_add_gitea_username_to_utilisateurs.php b/database/migrations/2026_07_07_120000_add_gitea_username_to_utilisateurs.php deleted file mode 100644 index c4c56c6..0000000 --- a/database/migrations/2026_07_07_120000_add_gitea_username_to_utilisateurs.php +++ /dev/null @@ -1,24 +0,0 @@ -string('GiteaUsername', 100)->nullable()->after('Pseudo'); - }); - } - - public function down(): void - { - Schema::table('utilisateurs', function (Blueprint $table) { - $table->dropColumn('GiteaUsername'); - }); - } -}; diff --git a/resources/views/suggestions/create.blade.php b/resources/views/suggestions/create.blade.php index 72f3b6d..328ce7a 100644 --- a/resources/views/suggestions/create.blade.php +++ b/resources/views/suggestions/create.blade.php @@ -21,26 +21,6 @@ @endif - - @if (session('gitea_compte')) - @php($compte = session('gitea_compte')) -
-

🎉 Un compte Gitea a été créé pour vous !

-

- Vous pouvez maintenant suivre, commenter et créer vos tickets directement sur - {{ $compte['url'] }}. -

-

- Identifiant : {{ $compte['username'] }}
- Mot de passe temporaire : {{ $compte['mot_de_passe'] }} -

-

- ⚠️ Notez ce mot de passe dès maintenant : il ne sera plus jamais affiché. - Il vous sera demandé de le changer à votre première connexion. -

-
- @endif - @if ($errors->any())
diff --git a/tests/Feature/SuggestionTest.php b/tests/Feature/SuggestionTest.php index b533bfb..6c1d454 100644 --- a/tests/Feature/SuggestionTest.php +++ b/tests/Feature/SuggestionTest.php @@ -31,25 +31,10 @@ class SuggestionTest extends TestCase config([ 'services.gitea.url' => 'https://gitea.test', 'services.gitea.token' => 'fake-token', - 'services.gitea.admin_token' => 'fake-admin-token', 'services.gitea.repo' => 'Groupe1/Ressources-relationnel', ]); } - /** - * Fakes par défaut : aucun compte existant, création de compte et de ticket OK. - * - * @param array $overrides - */ - private function fakesGitea(array $overrides = []): void - { - Http::fake($overrides + [ - 'gitea.test/api/v1/admin/emails/search*' => Http::response([]), - 'gitea.test/api/v1/admin/users' => Http::response(['id' => 42], 201), - 'gitea.test/api/v1/repos/*/issues' => Http::response(['number' => 42], 201), - ]); - } - public function test_le_formulaire_de_suggestion_exige_une_authentification(): void { $this->get('/suggestions/nouveau')->assertRedirect('/login'); @@ -58,7 +43,9 @@ class SuggestionTest extends TestCase public function test_une_suggestion_valide_cree_un_ticket_gitea(): void { $this->configurerGitea(); - $this->fakesGitea(); + Http::fake([ + '*/api/v1/repos/*/issues' => Http::response(['number' => 42], 201), + ]); $response = $this->actingAs($this->utilisateur())->post('/suggestions', [ 'type' => 'Correction', @@ -76,110 +63,6 @@ class SuggestionTest extends TestCase }); } - public function test_le_premier_envoi_cree_un_compte_gitea_pour_l_utilisateur(): void - { - $this->configurerGitea(); - $this->fakesGitea(); - - $utilisateur = $this->utilisateur(); - - $response = $this->actingAs($utilisateur)->post('/suggestions', [ - 'type' => 'Ajout', - 'titre' => 'Une nouvelle fonctionnalité', - 'description' => 'Description suffisamment longue de la fonctionnalité.', - ]); - - $usernameAttendu = 'jeantest-'.$utilisateur->ID_Utilisateur; - - $response->assertSessionHas('success'); - // Les identifiants sont flashés une seule fois pour affichage - $response->assertSessionHas('gitea_compte', fn ($compte) => $compte['username'] === $usernameAttendu - && $compte['mot_de_passe'] !== null); - - $this->assertSame($usernameAttendu, $utilisateur->fresh()->GiteaUsername); - - // Le compte a été créé via l'API admin… - Http::assertSent(fn ($request) => str_contains($request->url(), '/api/v1/admin/users') - && $request['username'] === $usernameAttendu - && $request['email'] === 'jean.test@example.com' - && $request['must_change_password'] === true); - - // …et le ticket est ouvert au nom de l'utilisateur (en-tête Sudo) - Http::assertSent(fn ($request) => str_contains($request->url(), '/issues') - && $request->header('Sudo') === [$usernameAttendu]); - } - - public function test_un_compte_gitea_existant_est_reutilise(): void - { - $this->configurerGitea(); - $this->fakesGitea(); - - $utilisateur = $this->utilisateur(); - $utilisateur->GiteaUsername = 'jean-deja-la'; - $utilisateur->save(); - - $response = $this->actingAs($utilisateur)->post('/suggestions', [ - 'type' => 'Modification', - 'titre' => 'Changer un contenu', - 'description' => 'Description suffisamment longue de la modification.', - ]); - - $response->assertSessionHas('success'); - $response->assertSessionMissing('gitea_compte'); - - // Aucune création de compte ne doit être tentée - Http::assertNotSent(fn ($request) => str_contains($request->url(), '/api/v1/admin/users')); - - Http::assertSent(fn ($request) => str_contains($request->url(), '/issues') - && $request->header('Sudo') === ['jean-deja-la']); - } - - public function test_le_ticket_est_cree_meme_si_la_creation_du_compte_echoue(): void - { - $this->configurerGitea(); - // Token sans droits admin : la création de compte est refusée - $this->fakesGitea([ - 'gitea.test/api/v1/admin/emails/search*' => Http::response('Forbidden', 403), - 'gitea.test/api/v1/admin/users' => Http::response('Forbidden', 403), - ]); - - $response = $this->actingAs($this->utilisateur())->post('/suggestions', [ - 'type' => 'Correction', - 'titre' => 'Un bug à corriger', - 'description' => 'Description suffisamment longue du bug rencontré.', - ]); - - // Repli : ticket créé avec le compte de service, sans en-tête Sudo - $response->assertSessionHas('success'); - $response->assertSessionMissing('gitea_compte'); - - Http::assertSent(fn ($request) => str_contains($request->url(), '/issues') - && $request->header('Sudo') === []); - } - - public function test_un_sudo_refuse_retombe_sur_le_compte_de_service(): void - { - $this->configurerGitea(); - $this->fakesGitea([ - // 1er appel (Sudo) refusé, 2e appel (compte de service) accepté - 'gitea.test/api/v1/repos/*/issues' => Http::sequence() - ->push('Not Found', 404) - ->push(['number' => 43], 201), - ]); - - $utilisateur = $this->utilisateur(); - $utilisateur->GiteaUsername = 'jean-deja-la'; - $utilisateur->save(); - - $response = $this->actingAs($utilisateur)->post('/suggestions', [ - 'type' => 'Ajout', - 'titre' => 'Encore une idée géniale', - 'description' => 'Description suffisamment longue de cette idée géniale.', - ]); - - $response->assertSessionHas('success'); - } - public function test_une_suggestion_invalide_est_rejetee(): void { Http::fake(); @@ -197,8 +80,8 @@ class SuggestionTest extends TestCase public function test_un_echec_gitea_affiche_une_erreur(): void { $this->configurerGitea(); - $this->fakesGitea([ - 'gitea.test/api/v1/repos/*/issues' => Http::response(['message' => 'nope'], 500), + Http::fake([ + '*/api/v1/repos/*/issues' => Http::response(['message' => 'nope'], 500), ]); $response = $this->actingAs($this->utilisateur())->post('/suggestions', [