Sécurisation, déploiement CI/CD Gitea et documentation INFCDAAL3
CD - Déploiement continu / deploy (push) Successful in 6m44s
CI - Intégration continue / quality-and-tests (push) Failing after 2m30s

- CI/CD Gitea Actions: ci.yml (Pint + PHPUnit), deploy.yml (déploiement SSH)
- Suppression de l'ancien workflow GitHub cassé
- En-têtes de sécurité: ajout CSP et Permissions-Policy
- RGPD: droit à l'effacement (anonymisation du compte) + tests
- Correctifs: ForceHttps hors env testing, timestamps du modèle Utilisateur,
  suppression des migrations en doublon (migrate échouait sur base neuve)
- Qualité: formatage Laravel Pint sur l'ensemble du code
- Documentation dossier technique (docs/) + .docx + .pptx

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 11:09:34 +02:00
parent de45e62d20
commit e5e6f72f92
38 changed files with 756 additions and 291 deletions
+14 -7
View File
@@ -1,4 +1,8 @@
APP_NAME=Laravel # --- Application ---
# En production : APP_ENV=production et APP_DEBUG=false (ne jamais exposer les
# traces d'erreur / la configuration au public). APP_KEY doit être généré via
# `php artisan key:generate` et rester secret (chiffrement des sessions/cookies).
APP_NAME="(RE)Sources Relationnelles"
APP_ENV=local APP_ENV=local
APP_KEY= APP_KEY=
APP_DEBUG=true APP_DEBUG=true
@@ -20,15 +24,18 @@ LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug LOG_LEVEL=debug
DB_CONNECTION=sqlite # --- Base de données ---
# DB_HOST=127.0.0.1 # En production, utiliser MySQL/MariaDB (données ministérielles) plutôt que SQLite.
# DB_PORT=3306 DB_CONNECTION=mysql
# DB_DATABASE=laravel DB_HOST=127.0.0.1
# DB_USERNAME=root DB_PORT=3306
# DB_PASSWORD= DB_DATABASE=ressources_relationnel
DB_USERNAME=changeme
DB_PASSWORD=changeme
SESSION_DRIVER=database SESSION_DRIVER=database
SESSION_LIFETIME=120 SESSION_LIFETIME=120
# En production, passer à true pour chiffrer les données de session (RGPD).
SESSION_ENCRYPT=false SESSION_ENCRYPT=false
SESSION_PATH=/ SESSION_PATH=/
SESSION_DOMAIN=null SESSION_DOMAIN=null
+50
View File
@@ -0,0 +1,50 @@
name: CI - Intégration continue
# Intégration continue : à chaque push et chaque pull request, on vérifie
# automatiquement la qualité du code (Laravel Pint) puis on exécute les tests
# automatisés (PHPUnit). Un déploiement (voir deploy.yml) ne doit se faire que
# si cette étape est au vert.
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
quality-and-tests:
runs-on: ubuntu-latest
steps:
- name: Récupération du code
uses: actions/checkout@v4
- name: Installation de PHP 8.2
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: pdo_mysql, mbstring, exif, pcntl, bcmath, gd
coverage: none
- name: Cache des dépendances Composer
uses: actions/cache@v4
with:
path: vendor
key: composer-${{ hashFiles('composer.lock') }}
- name: Installation des dépendances
run: composer install --no-interaction --prefer-dist --optimize-autoloader
- name: Préparation de l'environnement de test
run: |
cp .env.example .env
php artisan key:generate
- name: Vérification du style de code (Laravel Pint)
run: vendor/bin/pint --test
- name: Exécution des tests automatisés (PHPUnit)
env:
DB_CONNECTION: sqlite
DB_DATABASE: ':memory:'
run: php artisan test
+49
View File
@@ -0,0 +1,49 @@
name: CD - Déploiement continu
# Déploiement continu : après un push sur master, une fois la CI passée,
# on se connecte en SSH au serveur de production et on met à jour l'application
# (git pull, rebuild des conteneurs, migrations, mise en cache).
#
# Secrets à définir dans Gitea (Settings > Actions > Secrets du dépôt) :
# - SSH_HOST : IP ou domaine du serveur
# - SSH_USER : utilisateur SSH
# - SSH_PRIVATE_KEY : clé privée SSH autorisée sur le serveur
# - SSH_PORT : port SSH (optionnel, 22 par défaut)
on:
push:
branches: [ master ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Déploiement sur le serveur via SSH
uses: appleboy/ssh-action@v1.2.0
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT }}
script: |
set -e
cd /home/sam/mon-app-laravel
# Récupération de la dernière version du code
git pull origin master
# Reconstruction et redémarrage des conteneurs
docker compose up -d --build
# Migrations de la base de données (sans interaction)
docker exec laravel_php php artisan migrate --force
# Mise en cache de la configuration et des routes (performances en prod)
docker exec laravel_php php artisan config:cache
docker exec laravel_php php artisan route:cache
docker exec laravel_php php artisan view:cache
# Correction des permissions sur les dossiers écrits par Laravel
docker exec -u 0 laravel_php chown -R www-data:www-data \
/var/www/storage /var/www/bootstrap/cache
-27
View File
@@ -1,27 +0,0 @@
name: Deploy Laravel App
on:
push:
branches: [ main ] # Déclenche le déploiement lors d'un push sur la branche main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy to Server via SSH
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /home/sam/mon-app-laravel
git pull origin main
docker-compose up -d --build
docker exec laravel_app php artisan migrate --force
docker exec laravel_app php artisan config:cache
docker exec laravel_app php artisan route:cache
- name: Fix permissions
run: |
docker exec -u 0 laravel_php chown -R www-data:www-data /var/www/storage /var/www/bootstrap/cache /tmp
docker exec laravel_php php artisan view:clear
+52 -41
View File
@@ -1,59 +1,70 @@
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p> # (RE)Sources Relationnelles
<p align="center"> Plateforme de partage de ressources relationnelles à destination des citoyens
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a> (simulation d'un projet du Ministère des Solidarités et de la Santé).
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a> Projet collaboratif — **CDA / CESI — Groupe 1**.
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
## About Laravel Application web développée avec **Laravel 12** (architecture MVC), conteneurisée
avec **Docker** et déployée en continu via **Gitea Actions**.
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as: - Application : https://app.sam-coffre.duckdns.org
- Dépôt Git : https://gitea.sam-coffre.duckdns.org/
- [Simple, fast routing engine](https://laravel.com/docs/routing). ## Stack technique
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, powerful, and provides tools required for large, robust applications. | Élément | Technologie |
|---------|-------------|
| Framework | Laravel 12 (PHP 8.2) |
| Base de données | MySQL / MariaDB (SQLite en test) |
| Serveur web | Nginx (reverse proxy + TLS) |
| Conteneurisation | Docker / Docker Compose |
| Gestion de version | Git + Gitea |
| CI/CD | Gitea Actions |
| Qualité de code | Laravel Pint |
| Tests | PHPUnit |
## Learning Laravel ## Installation en local
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. You can also check out [Laravel Learn](https://laravel.com/learn), where you will be guided through building a modern Laravel application. ```bash
git clone https://gitea.sam-coffre.duckdns.org/<groupe>/ressources-relationnel.git
cd ressources-relationnel
cp .env.example .env
composer install
php artisan key:generate
php artisan migrate --seed
npm install && npm run build
php artisan serve
```
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library. ## Développement
## Laravel Sponsors ```bash
# Lancer les tests automatisés
php artisan test
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com). # Vérifier le style de code
vendor/bin/pint --test
### Premium Partners # Corriger automatiquement le style de code
vendor/bin/pint
```
- **[Vehikl](https://vehikl.com)** ## Déploiement
- **[Tighten Co.](https://tighten.co)**
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
- **[64 Robots](https://64robots.com)**
- **[Curotec](https://www.curotec.com/services/technologies/laravel)**
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
- **[Redberry](https://redberry.international/laravel-development)**
- **[Active Logic](https://activelogic.com)**
## Contributing Le déploiement est **automatisé** : un `push` sur la branche `master` déclenche
la CI (qualité + tests) puis, si tout est vert, le déploiement en production.
Voir [`docs/02-plan-de-deploiement.md`](docs/02-plan-de-deploiement.md).
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). ## Documentation (rendu bloc INFCDAAL3)
## Code of Conduct La documentation technique « Déployer et sécuriser » est dans le dossier
[`docs/`](docs/README.md) :
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). - [Plan de sécurisation](docs/01-plan-de-securisation.md)
- [Plan de déploiement](docs/02-plan-de-deploiement.md)
- [Gestion des versions](docs/03-gestion-des-versions.md)
- [Gestion des évolutions](docs/04-gestion-des-evolutions.md)
## Security Vulnerabilities ## Licence
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed. Projet pédagogique — usage limité au cadre de la formation.
## License
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
+2 -1
View File
@@ -2,8 +2,8 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Utilisateur;
use App\Models\Role; use App\Models\Role;
use App\Models\Utilisateur;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
@@ -36,6 +36,7 @@ class LoginController extends Controller
if (Auth::attempt($loginCredentials)) { if (Auth::attempt($loginCredentials)) {
$request->session()->regenerate(); $request->session()->regenerate();
return redirect()->route('home')->with('success', 'Connexion réussie!'); return redirect()->route('home')->with('success', 'Connexion réussie!');
} }
@@ -14,6 +14,7 @@ class ProfileController extends Controller
{ {
/** @var Utilisateur $user */ /** @var Utilisateur $user */
$user = Auth::user(); $user = Auth::user();
return view('profile.settings', compact('user')); return view('profile.settings', compact('user'));
} }
@@ -56,4 +57,34 @@ class ProfileController extends Controller
return back()->with('success', 'Votre profil a été mis à jour avec succès!'); return back()->with('success', 'Votre profil a été mis à jour avec succès!');
} }
// Supprimer / anonymiser son compte (droit à l'effacement RGPD)
public function destroy(Request $request)
{
/** @var Utilisateur $user */
$user = Auth::user();
// Vérification du mot de passe pour confirmer l'action sensible
$request->validate([
'MotDePasse_Confirmation' => ['required', 'string'],
], [
'MotDePasse_Confirmation.required' => 'Veuillez saisir votre mot de passe pour confirmer.',
]);
if (! Hash::check($request->MotDePasse_Confirmation, $user->MotDePasse)) {
return back()->withErrors([
'MotDePasse_Confirmation' => 'Mot de passe incorrect. Suppression annulée.',
]);
}
// Anonymisation des données personnelles, puis déconnexion sécurisée
$user->anonymiser();
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()->route('home')
->with('success', 'Votre compte a été supprimé et vos données personnelles anonymisées.');
}
} }
+4 -2
View File
@@ -9,8 +9,10 @@ class ForceHttps
{ {
public function handle(Request $request, Closure $next) public function handle(Request $request, Closure $next)
{ {
// En production, forcer HTTPS même si on arrive en HTTP // En production, forcer HTTPS même si on arrive en HTTP.
if (!app()->environment('local') && !$request->isSecure()) { // On exclut les environnements local et testing (pas de TLS, et
// la redirection casserait la suite de tests automatisés / CI).
if (! app()->environment(['local', 'testing']) && ! $request->isSecure()) {
return redirect( return redirect(
str_replace('http://', 'https://', $request->url()), str_replace('http://', 'https://', $request->url()),
308 308
+29 -1
View File
@@ -15,13 +15,41 @@ class SecurityHeaders
{ {
$response = $next($request); $response = $next($request);
// Ajouter les headers de sécurité // --- En-têtes de sécurité HTTP (défense en profondeur) ---
// Force le navigateur à n'utiliser que HTTPS pendant 1 an (protection contre le SSL stripping).
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains', true); $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains', true);
// Empêche le navigateur de "deviner" le type MIME d'une ressource (protection contre le MIME sniffing).
$response->headers->set('X-Content-Type-Options', 'nosniff', true); $response->headers->set('X-Content-Type-Options', 'nosniff', true);
// Interdit l'affichage du site dans une iframe externe (protection contre le clickjacking).
$response->headers->set('X-Frame-Options', 'SAMEORIGIN', true); $response->headers->set('X-Frame-Options', 'SAMEORIGIN', true);
// Ancienne protection XSS des navigateurs (conservée pour les navigateurs anciens).
$response->headers->set('X-XSS-Protection', '1; mode=block', true); $response->headers->set('X-XSS-Protection', '1; mode=block', true);
// Limite les informations de provenance envoyées aux sites tiers.
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin', true); $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin', true);
// Désactive des API sensibles du navigateur non utilisées par l'application (RGPD / vie privée).
$response->headers->set('Permissions-Policy', 'geolocation=(), microphone=(), camera=(), payment=()', true);
// Content-Security-Policy : réduit fortement le risque d'injection de scripts (XSS).
// Politique volontairement compatible avec Laravel/Vite (styles et scripts inline autorisés).
// Pour durcir davantage, remplacer 'unsafe-inline' par un système de nonce.
$response->headers->set('Content-Security-Policy', implode('; ', [
"default-src 'self'",
"script-src 'self' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self' data:",
"connect-src 'self'",
"object-src 'none'",
"base-uri 'self'",
"frame-ancestors 'self'",
]), true);
return $response; return $response;
} }
} }
+3
View File
@@ -7,8 +7,11 @@ use Illuminate\Database\Eloquent\Model;
class Activite extends Model class Activite extends Model
{ {
protected $primaryKey = 'ID_Instance'; protected $primaryKey = 'ID_Instance';
protected $table = 'activites'; protected $table = 'activites';
public $incrementing = true; public $incrementing = true;
public $timestamps = false; public $timestamps = false;
protected $fillable = [ protected $fillable = [
+3
View File
@@ -7,8 +7,11 @@ use Illuminate\Database\Eloquent\Model;
class ActiviteMessage extends Model class ActiviteMessage extends Model
{ {
protected $primaryKey = 'ID_Message'; protected $primaryKey = 'ID_Message';
protected $table = 'activite_messages'; protected $table = 'activite_messages';
public $incrementing = true; public $incrementing = true;
public $timestamps = false; public $timestamps = false;
protected $fillable = [ protected $fillable = [
+2
View File
@@ -7,7 +7,9 @@ use Illuminate\Database\Eloquent\Model;
class Categorie extends Model class Categorie extends Model
{ {
public $timestamps = false; public $timestamps = false;
protected $primaryKey = 'ID_Categorie'; protected $primaryKey = 'ID_Categorie';
protected $table = 'categories'; protected $table = 'categories';
protected $fillable = [ protected $fillable = [
+2
View File
@@ -7,7 +7,9 @@ use Illuminate\Database\Eloquent\Model;
class Commentaire extends Model class Commentaire extends Model
{ {
protected $primaryKey = 'ID_Commentaire'; protected $primaryKey = 'ID_Commentaire';
protected $table = 'commentaires'; protected $table = 'commentaires';
public $incrementing = true; public $incrementing = true;
protected $fillable = [ protected $fillable = [
+2
View File
@@ -7,7 +7,9 @@ use Illuminate\Database\Eloquent\Model;
class Ressource extends Model class Ressource extends Model
{ {
protected $primaryKey = 'ID_Ressource'; protected $primaryKey = 'ID_Ressource';
protected $table = 'ressources'; protected $table = 'ressources';
public $incrementing = true; public $incrementing = true;
protected $fillable = [ protected $fillable = [
+2
View File
@@ -7,7 +7,9 @@ use Illuminate\Database\Eloquent\Model;
class Role extends Model class Role extends Model
{ {
public $timestamps = false; public $timestamps = false;
protected $primaryKey = 'ID_Role'; protected $primaryKey = 'ID_Role';
protected $table = 'roles'; protected $table = 'roles';
protected $fillable = [ protected $fillable = [
+2
View File
@@ -7,7 +7,9 @@ use Illuminate\Database\Eloquent\Model;
class TypeRessource extends Model class TypeRessource extends Model
{ {
public $timestamps = false; public $timestamps = false;
protected $primaryKey = 'ID_Type_Ressource'; protected $primaryKey = 'ID_Type_Ressource';
protected $table = 'type_ressources'; protected $table = 'type_ressources';
protected $fillable = [ protected $fillable = [
+24 -1
View File
@@ -4,6 +4,7 @@ namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Carbon;
/** /**
* @property int $ID_Utilisateur * @property int $ID_Utilisateur
@@ -12,7 +13,7 @@ use Illuminate\Notifications\Notifiable;
* @property string $Pseudo * @property string $Pseudo
* @property string $Statut * @property string $Statut
* @property int $ID_Role * @property int $ID_Role
* @property \Illuminate\Support\Carbon $DateInscription * @property Carbon $DateInscription
*/ */
class Utilisateur extends Authenticatable class Utilisateur extends Authenticatable
{ {
@@ -21,9 +22,14 @@ class Utilisateur extends Authenticatable
// Nom de la table // Nom de la table
protected $table = 'utilisateurs'; protected $table = 'utilisateurs';
// La table utilise DateInscription et non created_at/updated_at
public $timestamps = false;
// Clé primaire personnalisée // Clé primaire personnalisée
protected $primaryKey = 'ID_Utilisateur'; protected $primaryKey = 'ID_Utilisateur';
public $incrementing = true; public $incrementing = true;
protected $keyType = 'int'; protected $keyType = 'int';
// Colonnes modifiables (Mass Assignment) // Colonnes modifiables (Mass Assignment)
@@ -88,4 +94,21 @@ class Utilisateur extends Authenticatable
{ {
return $this->hasMany(Utilisateur_Progression::class, 'ID_Utilisateur', 'ID_Utilisateur'); return $this->hasMany(Utilisateur_Progression::class, 'ID_Utilisateur', 'ID_Utilisateur');
} }
/**
* Anonymise le compte (droit à l'effacement RGPD).
*
* Les données personnelles identifiantes (e-mail, pseudo, mot de passe)
* sont remplacées par des valeurs neutres et non réversibles, tandis que
* les contributions liées (ressources, commentaires) restent en base sous
* une forme anonyme afin de préserver l'intégrité référentielle.
*/
public function anonymiser(): void
{
$this->Email = 'anonyme_'.$this->ID_Utilisateur.'@anonymise.local';
$this->Pseudo = 'Utilisateur supprimé';
$this->MotDePasse = bin2hex(random_bytes(32)); // mot de passe non utilisable
$this->Statut = 'Inactif'; // compte désactivé (valeur de l'enum Statut)
$this->save();
}
} }
+2
View File
@@ -7,7 +7,9 @@ use Illuminate\Database\Eloquent\Model;
class Utilisateur_Progression extends Model class Utilisateur_Progression extends Model
{ {
protected $table = 'utilisateur_progressions'; protected $table = 'utilisateur_progressions';
public $timestamps = false; public $timestamps = false;
public $incrementing = false; public $incrementing = false;
protected $primaryKey = ['ID_Utilisateur', 'ID_Ressource']; protected $primaryKey = ['ID_Utilisateur', 'ID_Ressource'];
+1 -2
View File
@@ -2,9 +2,8 @@
namespace App\Providers; namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\URL; use Illuminate\Support\Facades\URL;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest; use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
+2 -2
View File
@@ -1,10 +1,10 @@
<?php <?php
use App\Http\Middleware\ForceHttps;
use App\Http\Middleware\SecurityHeaders;
use Illuminate\Foundation\Application; use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware; use Illuminate\Foundation\Configuration\Middleware;
use App\Http\Middleware\SecurityHeaders;
use App\Http\Middleware\ForceHttps;
return Application::configure(basePath: dirname(__DIR__)) return Application::configure(basePath: dirname(__DIR__))
->withRouting( ->withRouting(
@@ -1,27 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('roles', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('roles');
}
};
@@ -1,32 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('utilisateurs', function (Blueprint $table) {
$table->id();
$table->string('Email')->unique();
$table->string('MotDePasse');
$table->string('Pseudo')->nullable();
$table->unsignedBigInteger('ID_Role')->nullable();
$table->string('Statut')->default('Actif');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('utilisateurs');
}
};
@@ -1,27 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('activites', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('activites');
}
};
@@ -1,27 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('categories');
}
};
@@ -1,27 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('commentaires', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('commentaires');
}
};
@@ -1,27 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('ressources', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('ressources');
}
};
@@ -1,27 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('type_ressources', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('type_ressources');
}
};
+1 -1
View File
@@ -14,7 +14,7 @@ services:
container_name: laravel_web container_name: laravel_web
restart: always restart: always
ports: ports:
- "8000:80" - "8005:80"
- "8443:443" # HTTPS port - "8443:443" # HTTPS port
volumes: volumes:
- .:/var/www - .:/var/www
+99
View File
@@ -0,0 +1,99 @@
# 01 — Plan de sécurisation
Projet **(RE)Sources Relationnelles** — Bloc INFCDAAL3
> Contexte : plateforme ministérielle manipulant des **données personnelles**
> (comptes citoyens, ressources privées, échanges modérés). La sécurité et la
> conformité **RGPD** sont donc des exigences fortes.
## 1. Identification des risques de sécurité
Analyse inspirée du **Top 10 OWASP** et adaptée au projet.
| Risque | Exemple sur le projet | Gravité | Traitement mis en place |
|--------|-----------------------|---------|-------------------------|
| Injection SQL | Recherche / filtres de ressources | Élevée | ORM Eloquent (requêtes préparées), validation des entrées |
| XSS (injection de scripts) | Commentaires, ressources créées par les citoyens | Élevée | Échappement Blade `{{ }}`, en-tête **CSP** |
| CSRF | Formulaires (login, création de ressource) | Élevée | Jeton CSRF Laravel automatique sur les formulaires |
| Vol de session | Cookies de session | Élevée | Cookies `HttpOnly` + `Secure`, HTTPS forcé, `session.regenerate()` à la connexion |
| Mots de passe faibles / en clair | Comptes citoyens | Élevée | Hachage **bcrypt** (`hashed` cast), min. 8 caractères |
| Contrôle d'accès défaillant | Accès Back Office / pages profil | Élevée | Middleware `auth`, rôles (Citoyen / Modérateur / Admin / Super-Admin) |
| Man-in-the-middle | Transport des données | Élevée | **TLS/HTTPS** obligatoire + **HSTS** |
| Exposition d'informations | Messages d'erreur, debug | Moyenne | `APP_DEBUG=false` et `APP_ENV=production` en prod |
| Clickjacking | Intégration en iframe | Moyenne | En-tête `X-Frame-Options: SAMEORIGIN` |
| Dépendances vulnérables | Paquets Composer / npm | Moyenne | `composer audit` / mises à jour régulières |
## 2. Mesures de prévention des risques
### 2.1 Sécurité applicative (implémentée dans le code)
- **Authentification** : hachage bcrypt des mots de passe, régénération de
session à la connexion, invalidation à la déconnexion
(`app/Http/Controllers/LoginController.php`).
- **Contrôle d'accès** : les routes sensibles sont regroupées derrière le
middleware `auth` (`routes/web.php`), avec un modèle de **rôles**
(`app/Models/Role.php`).
- **Validation des entrées** : toutes les données reçues sont validées
(règles `required`, `email`, `unique`, `min:8`, `confirmed`…).
- **Protection CSRF** : activée par défaut sur tous les formulaires POST/PUT.
- **En-têtes de sécurité HTTP** (`app/Http/Middleware/SecurityHeaders.php`) :
- `Strict-Transport-Security` (HSTS) — impose HTTPS,
- `Content-Security-Policy` (CSP) — limite les sources de scripts (anti-XSS),
- `X-Content-Type-Options: nosniff`,
- `X-Frame-Options: SAMEORIGIN` (anti-clickjacking),
- `Referrer-Policy` et `Permissions-Policy`.
- **Forçage HTTPS** (`app/Http/Middleware/ForceHttps.php`) : redirection
automatique de HTTP vers HTTPS hors environnement local/test.
### 2.2 Sécurité du transport et de l'infrastructure
- **TLS/HTTPS** terminé au niveau du reverse proxy (`nginx.conf`) : le port 80
redirige en 301 vers le port 443.
- Nom de domaine du service : `https://app.sam-coffre.duckdns.org`.
- Séparation des rôles conteneurs : PHP-FPM (`laravel_php`) et serveur web
(`laravel_web`) isolés (`docker-compose.yml`).
## 3. Chiffrement des données
| Donnée | État au repos | En transit |
|--------|---------------|-----------|
| Mots de passe | Hachage **bcrypt** (non réversible) | HTTPS |
| Sessions / cookies | Chiffrés via `APP_KEY` (AES-256), `SESSION_ENCRYPT=true` en prod | HTTPS + cookie `Secure`/`HttpOnly` |
| Données personnelles sensibles | Chiffrables via les *casts* `encrypted` d'Eloquent | HTTPS |
| Communications client ↔ serveur | — | **TLS 1.2+** obligatoire |
> Laravel chiffre en **AES-256-CBC** à partir de la clé `APP_KEY`. Cette clé est
> stockée hors dépôt (`.env` est dans `.gitignore`) et ne doit jamais être versionnée.
## 4. Conformité RGPD
- **Minimisation** : seules les données nécessaires sont collectées (pseudo, e-mail).
- **Base légale et information** : pages légales présentes
(`resources/views/legal/mentions.blade.php`,
`resources/views/legal/politique.blade.php`).
- **Droit d'accès / rectification** : espace profil
(`/profile/settings`, `ProfileController`).
- **Droit à l'effacement / anonymisation** : implémenté. Depuis son espace
profil, l'utilisateur peut supprimer son compte (confirmation par mot de passe) ;
ses données personnelles (e-mail, pseudo, mot de passe) sont alors anonymisées
de façon irréversible via `Utilisateur::anonymiser()`
(`app/Http/Controllers/ProfileController@destroy`), tout en préservant
l'intégrité référentielle des contributions.
- **Sécurité par défaut** (privacy by design) : chiffrement, HTTPS, contrôle d'accès.
- **Sous-traitance et hébergement** : hébergement maîtrisé, journalisation des accès.
## 5. Bonnes pratiques de développement
- **Style de code homogène** : **Laravel Pint** (PSR-12), vérifié en CI
(`vendor/bin/pint --test`).
- **Commentaires** : le code de sécurité (middlewares) est documenté en français.
- **Tests automatisés** : `tests/Feature/SecuriteTest.php` vérifie les en-têtes
de sécurité et le contrôle d'accès aux pages protégées.
- **Séparation des environnements** : configuration via `.env` (jamais de secret en dur).
- **Architecture MVC** : contrôleurs / modèles / vues séparés.
## 6. Synthèse — traçabilité risque → mesure
Chaque risque du §1 est couvert par au moins une mesure du §2/§3/§4, et les
mesures critiques (en-têtes, contrôle d'accès) sont **vérifiées automatiquement**
par la CI à chaque évolution du code.
+109
View File
@@ -0,0 +1,109 @@
# 02 — Plan de déploiement
Projet **(RE)Sources Relationnelles** — Bloc INFCDAAL3
## 1. Stratégie de déploiement
Le déploiement repose sur trois piliers :
1. **Conteneurisation** (Docker) pour un environnement reproductible.
2. **Gestion de version** (Gitea) comme source unique de vérité.
3. **Intégration et déploiement continus** (Gitea Actions) pour automatiser
qualité, tests et mise en production.
Approche retenue : à chaque `push` sur `master`, la CI valide le code
(qualité + tests) ; si tout est vert, le déploiement se déclenche automatiquement
sur le serveur.
## 2. Environnements
| Environnement | Rôle | Hébergement |
|---------------|------|-------------|
| **Local (dev)** | Développement sur le poste | Docker / `php artisan serve` |
| **Test (CI)** | Exécution automatisée des tests | Runner Gitea Actions (base SQLite en mémoire) |
| **Pré-production (QA)** *(recommandé)* | Recette avant mise en ligne | Conteneurs Docker, base dédiée |
| **Production** | Service ouvert aux citoyens | Serveur `app.sam-coffre.duckdns.org` |
> La configuration de chaque environnement est portée par un fichier `.env`
> distinct (jamais versionné). Seul `.env.example` est présent dans le dépôt.
## 3. Architecture de déploiement
```
Internet (HTTPS)
┌───────────────────────────┐
│ Nginx (conteneur web) │ :443 TLS / redirection 80→443
│ laravel_web │
└─────────────┬─────────────┘
│ FastCGI :9000
┌───────────────────────────┐
│ PHP-FPM (conteneur app) │ Laravel 12
│ laravel_php │
└─────────────┬─────────────┘
Base de données MySQL
```
Fichiers concernés : `Dockerfile` (image PHP), `docker-compose.yml`
(orchestration), `nginx.conf` (reverse proxy / TLS).
## 4. Pipeline d'intégration et de déploiement continus (CI/CD)
### 4.1 Intégration continue — `.gitea/workflows/ci.yml`
Déclenchée sur chaque `push` et chaque `pull_request` vers `master` :
1. Récupération du code.
2. Installation de PHP 8.2 et des extensions.
3. Installation des dépendances Composer (avec cache).
4. **Vérification du style de code** : `vendor/bin/pint --test`.
5. **Exécution des tests** : `php artisan test` (PHPUnit).
### 4.2 Déploiement continu — `.gitea/workflows/deploy.yml`
Déclenché sur `push` vers `master`, il se connecte en **SSH** au serveur et exécute :
1. `git pull origin master` — récupération de la nouvelle version.
2. `docker compose up -d --build` — reconstruction des conteneurs.
3. `php artisan migrate --force` — application des migrations de base.
4. `config:cache`, `route:cache`, `view:cache` — optimisations de production.
5. Correction des permissions (`storage`, `bootstrap/cache`).
**Secrets Gitea requis** (Settings → Actions → Secrets) : `SSH_HOST`,
`SSH_USER`, `SSH_PRIVATE_KEY`, `SSH_PORT`.
> Prérequis : un **runner Gitea Actions** doit être enregistré sur l'instance
> (`https://gitea.sam-coffre.duckdns.org/`) et les Actions activées pour le dépôt.
## 5. Plan de déploiement détaillé (étapes, ressources, responsables)
| # | Étape | Ressource / Outil | Responsable | Automatisé ? |
|---|-------|-------------------|-------------|--------------|
| 1 | Développement + commit | Git / Gitea | Développeur | Non |
| 2 | Push sur `master` | Gitea | Développeur | Non |
| 3 | Contrôle qualité (Pint) | Gitea Actions | Runner CI | ✅ Oui |
| 4 | Tests automatisés (PHPUnit) | Gitea Actions | Runner CI | ✅ Oui |
| 5 | Connexion au serveur | SSH | Runner CD | ✅ Oui |
| 6 | Récupération du code | Git | Serveur | ✅ Oui |
| 7 | Build des conteneurs | Docker Compose | Serveur | ✅ Oui |
| 8 | Migrations BDD | Artisan | Serveur | ✅ Oui |
| 9 | Mise en cache config/routes/vues | Artisan | Serveur | ✅ Oui |
| 10 | Vérification post-déploiement | Endpoint `/up` (health check) | Équipe | Semi |
## 6. Retour arrière (rollback)
En cas de problème après un déploiement :
- **Code** : `git revert` ou retour à un tag précédent, puis nouveau déploiement.
- **Base de données** : `php artisan migrate:rollback` (migrations réversibles).
- **Conteneurs** : redéploiement de l'image précédente via `docker compose`.
## 7. Supervision
- **Health check** : endpoint `/up` exposé par Laravel (voir `bootstrap/app.php`).
- **Journaux** : `storage/logs/laravel.log` + logs des conteneurs Docker.
- **Historique des déploiements** : onglet *Actions* de Gitea.
+61
View File
@@ -0,0 +1,61 @@
# 03 — Outil de gestion des versions
Projet **(RE)Sources Relationnelles** — Bloc INFCDAAL3
## 1. Outil choisi : Git + Gitea (auto-hébergé)
Le versionnement du code repose sur **Git**, hébergé sur une instance
**Gitea** auto-hébergée par l'équipe :
- Instance : `https://gitea.sam-coffre.duckdns.org/`
- Motivations du choix :
- **Maîtrise des données** : hébergement sur notre propre serveur (cohérent
avec un contexte ministériel / RGPD, pas de dépendance à un tiers).
- **Léger et open source** : Gitea est simple à administrer et gratuit.
- **Fonctionnalités complètes** : dépôts Git, *pull requests*, *issues*,
*releases*, et **Gitea Actions** (CI/CD) intégrés.
## 2. Organisation du dépôt
- **Branche principale** : `master` (branche de référence, protégée, déployée).
- **Branches de fonctionnalité** *(recommandé)* : une branche par fonctionnalité
(`feature/…`), fusionnée via *pull request* après relecture.
- Fichiers de configuration Git présents : `.gitignore`, `.gitattributes`,
`.editorconfig` (cohérence entre développeurs).
## 3. Convention de nommage des commits
Convention recommandée (type *Conventional Commits*) :
```
<type>: <description courte>
Types : feat, fix, docs, style, refactor, test, chore
Exemples :
feat: ajout de la création de ressources
fix: correction de la redirection HTTPS
docs: rédaction du plan de sécurisation
```
## 4. Flux de travail (workflow) proposé
1. Créer une branche depuis `master` : `git checkout -b feature/ma-fonctionnalite`.
2. Développer + commits atomiques.
3. Pousser la branche : `git push origin feature/ma-fonctionnalite`.
4. Ouvrir une **Pull Request** sur Gitea.
5. La **CI** (Pint + tests) s'exécute automatiquement sur la PR.
6. Relecture par un pair, puis fusion dans `master`.
7. La fusion déclenche le **déploiement automatique** en production.
## 5. Gestion des livraisons (releases)
- Utilisation des **tags Git** pour marquer les versions : `v1.0.0`, `v1.1.0`
- Convention **SemVer** : `MAJEUR.MINEUR.CORRECTIF`.
- Les *Releases* Gitea documentent chaque version livrée (notes de version).
## 6. Sécurité du dépôt
- Aucun secret versionné : `.env`, clés et certificats sont exclus via
`.gitignore`.
- Accès au dépôt restreint aux membres de l'équipe (comptes Gitea).
- Historique complet et traçable (auteur, date, message de chaque modification).
+63
View File
@@ -0,0 +1,63 @@
# 04 — Outil de gestion des demandes d'évolutions et des incidents
Projet **(RE)Sources Relationnelles** — Bloc INFCDAAL3
## 1. Outil choisi : les *Issues* de Gitea
La gestion des évolutions et des incidents s'appuie sur le module **Issues** de
Gitea (déjà utilisé pour le code), ce qui évite d'ajouter un outil externe et
centralise le suivi au même endroit que le code et les déploiements.
- Instance : `https://gitea.sam-coffre.duckdns.org/`
## 2. Typologie des demandes (labels)
Chaque demande est catégorisée par un **label** :
| Label | Usage |
|-------|-------|
| `bug` | Incident / anomalie en production |
| `evolution` | Demande d'évolution fonctionnelle |
| `securite` | Faille ou correctif de sécurité (priorité haute) |
| `documentation` | Mise à jour de documentation |
| `priorite:haute` / `priorite:basse` | Niveau de priorité |
## 3. Cycle de vie d'une demande
```
Ouverte → À traiter → En cours → En test (QA) → Résolue / Fermée
```
- Une *issue* est **liée à une branche et à une Pull Request** ; la fusion de la
PR ferme automatiquement l'issue (mot-clé `Closes #12` dans le commit).
- Le tableau **Projects (Kanban)** de Gitea permet de visualiser l'avancement
(colonnes : À faire / En cours / Terminé).
- Les **Milestones** regroupent les demandes par version cible (ex. `v1.1`).
## 4. Processus de gestion des incidents (maintenance corrective)
1. **Détection** : signalement (utilisateur, supervision, logs).
2. **Qualification** : création d'une issue `bug`, évaluation de la gravité.
3. **Priorisation** : affectation d'un label de priorité et d'un responsable.
4. **Correction** : branche `fix/…`, développement, tests.
5. **Validation** : CI verte + recette en QA.
6. **Déploiement** : fusion sur `master` → mise en production automatique.
7. **Clôture** : fermeture de l'issue, note de version si nécessaire.
## 5. Processus de gestion des évolutions (maintenance évolutive)
1. **Recueil du besoin** : issue `evolution` décrivant le besoin et la valeur.
2. **Analyse et estimation** : faisabilité, impact, charge.
3. **Planification** : affectation à un *milestone* / une version.
4. **Réalisation** : branche `feature/…`, développement + tests.
5. **Revue et intégration** : Pull Request relue, CI validée.
6. **Livraison** : déploiement continu, communication de la nouvelle version.
## 6. Traçabilité
Grâce à Gitea, chaque évolution est traçable de bout en bout :
**Issue → Branche → Commits → Pull Request → CI → Déploiement → Release**
Cette chaîne assure la traçabilité complète exigée pour la maintenance d'une
application manipulant des données sensibles.
Binary file not shown.
+36
View File
@@ -0,0 +1,36 @@
# Dossier de rendu — Bloc INFCDAAL3
**Déployer et sécuriser les applications informatiques**
Projet collaboratif : **(RE)Sources Relationnelles**
Ce dossier regroupe les documents techniques attendus pour le bloc INFCDAAL3.
Il sert de base au **document écrit de 15 à 20 pages** demandé dans les consignes.
## Sommaire
| # | Document | Répond au critère de la grille |
|---|----------|--------------------------------|
| 01 | [Plan de sécurisation](01-plan-de-securisation.md) | Risques de sécurité, RGPD, chiffrement, prévention des risques |
| 02 | [Plan de déploiement](02-plan-de-deploiement.md) | Environnement de déploiement, automatisation, intégration continue, étapes et ressources |
| 03 | [Gestion des versions](03-gestion-des-versions.md) | Outil de gestion des versions (Gitea / Git) |
| 04 | [Gestion des évolutions](04-gestion-des-evolutions.md) | Outil de gestion des demandes d'évolutions et des incidents |
## Correspondance avec la grille d'évaluation
- **Déploiement de l'application** → doc 02 + fichiers `Dockerfile`, `docker-compose.yml`, `nginx.conf`, `.gitea/workflows/`
- **Maintenance** (gestion des versions et des évolutions) → docs 03 et 04
- **Sécurité** (risques, RGPD, bonnes pratiques, plan de sécurisation) → doc 01 + code (`app/Http/Middleware/`, tests, Laravel Pint)
- **Organisation / Prestation orale** → non technique (support de soutenance ~10 slides)
## Éléments techniques livrés dans le dépôt
| Élément | Emplacement | Objectif |
|---------|-------------|----------|
| Intégration continue (qualité + tests) | `.gitea/workflows/ci.yml` | Automatiser Pint + PHPUnit à chaque push |
| Déploiement continu (SSH) | `.gitea/workflows/deploy.yml` | Automatiser la mise en production |
| Conteneurisation | `Dockerfile`, `docker-compose.yml` | Environnement reproductible |
| Serveur web / TLS | `nginx.conf` | Redirection HTTP→HTTPS, HTTPS |
| En-têtes de sécurité | `app/Http/Middleware/SecurityHeaders.php` | HSTS, CSP, X-Frame-Options, etc. |
| Forçage HTTPS | `app/Http/Middleware/ForceHttps.php` | Chiffrement du transport |
| Tests de sécurité | `tests/Feature/SecuriteTest.php` | Vérification automatisée |
| Qualité de code | `laravel/pint` (dev) | Standard PSR / style homogène |
Binary file not shown.
+28 -1
View File
@@ -148,12 +148,39 @@
<h2 class="text-2xl font-bold text-red-700 mb-4">⚠️ Zone de danger</h2> <h2 class="text-2xl font-bold text-red-700 mb-4">⚠️ Zone de danger</h2>
<p class="text-gray-600 mb-6">Les actions ci-dessous ne peuvent pas être annulées.</p> <p class="text-gray-600 mb-6">Les actions ci-dessous ne peuvent pas être annulées.</p>
<form action="{{ route('logout') }}" method="POST"> <form action="{{ route('logout') }}" method="POST" class="mb-8">
@csrf @csrf
<button type="submit" class="px-8 py-3 bg-red-600 text-white font-bold rounded-lg hover:bg-red-700 transition-smooth duration-200"> <button type="submit" class="px-8 py-3 bg-red-600 text-white font-bold rounded-lg hover:bg-red-700 transition-smooth duration-200">
🚪 Se déconnecter 🚪 Se déconnecter
</button> </button>
</form> </form>
<hr class="my-6">
<!-- Suppression du compte (droit à l'effacement RGPD) -->
<h3 class="text-lg font-bold text-red-700 mb-2">Supprimer mon compte</h3>
<p class="text-gray-600 text-sm mb-4">
Conformément au RGPD, vous pouvez demander la suppression de votre compte.
Vos données personnelles (e-mail, pseudo) seront définitivement anonymisées.
Cette action est <strong>irréversible</strong>.
</p>
<form action="{{ route('profile.destroy') }}" method="POST"
onsubmit="return confirm('Confirmer la suppression définitive de votre compte ?');">
@csrf
@method('DELETE')
<div class="mb-4 max-w-sm">
<label for="MotDePasse_Confirmation" class="block text-sm font-bold mb-2 text-gray-700">Confirmez avec votre mot de passe</label>
<input
type="password"
id="MotDePasse_Confirmation"
name="MotDePasse_Confirmation"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-500 outline-none">
</div>
<button type="submit" class="px-8 py-3 bg-red-800 text-white font-bold rounded-lg hover:bg-red-900 transition-smooth duration-200">
🗑️ Supprimer définitivement mon compte
</button>
</form>
</div> </div>
</div> </div>
</section> </section>
+7 -3
View File
@@ -1,16 +1,19 @@
<?php <?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\LoginController; use App\Http\Controllers\LoginController;
use App\Http\Controllers\ProfileController; use App\Http\Controllers\ProfileController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::get('/', function () { Route::get('/', function () {
return view('welcome'); return view('welcome');
})->name('home'); })->name('home');
Route::get('/form-csrf', function () {return view('form-csrf');}); Route::get('/form-csrf', function () {
return view('form-csrf');
});
Route::get('/debug-https', function (Illuminate\Http\Request $request) { Route::get('/debug-https', function (Request $request) {
return response()->json([ return response()->json([
'isSecure' => $request->isSecure(), 'isSecure' => $request->isSecure(),
'url' => $request->url(), 'url' => $request->url(),
@@ -44,4 +47,5 @@ Route::post('/logout', [LoginController::class, 'logout'])->name('logout');
Route::middleware('auth')->group(function () { Route::middleware('auth')->group(function () {
Route::get('/profile/settings', [ProfileController::class, 'settings'])->name('profile.settings'); Route::get('/profile/settings', [ProfileController::class, 'settings'])->name('profile.settings');
Route::put('/profile/update', [ProfileController::class, 'update'])->name('profile.update'); Route::put('/profile/update', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
}); });
+68
View File
@@ -0,0 +1,68 @@
<?php
namespace Tests\Feature;
use App\Models\Role;
use App\Models\Utilisateur;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Hash;
use Tests\TestCase;
class SecuriteTest extends TestCase
{
use RefreshDatabase;
/**
* Vérifie que les en-têtes de sécurité sont bien présents sur les réponses.
*/
public function test_les_entetes_de_securite_sont_presents(): void
{
$response = $this->get('/');
$response->assertStatus(200);
$response->assertHeader('X-Content-Type-Options', 'nosniff');
$response->assertHeader('X-Frame-Options', 'SAMEORIGIN');
$response->assertHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
$response->assertHeader('Content-Security-Policy');
$response->assertHeader('Permissions-Policy');
}
/**
* Vérifie que les pages protégées redirigent un visiteur non authentifié
* vers la page de connexion (contrôle d'accès).
*/
public function test_les_pages_protegees_exigent_une_authentification(): void
{
$response = $this->get('/profile/settings');
$response->assertRedirect('/login');
}
/**
* Vérifie le droit à l'effacement RGPD : la suppression du compte
* anonymise bien les données personnelles de l'utilisateur.
*/
public function test_la_suppression_de_compte_anonymise_les_donnees(): void
{
$role = Role::create(['Nom' => 'Utilisateur']);
$user = Utilisateur::create([
'Pseudo' => 'JeanDupont',
'Email' => 'jean.dupont@example.com',
'MotDePasse' => Hash::make('motdepasse123'),
'ID_Role' => $role->ID_Role,
'Statut' => 'Actif',
]);
$response = $this->actingAs($user)->delete('/profile', [
'MotDePasse_Confirmation' => 'motdepasse123',
]);
$response->assertRedirect('/');
$user->refresh();
$this->assertSame('Utilisateur supprimé', $user->Pseudo);
$this->assertSame('Inactif', $user->Statut);
$this->assertStringNotContainsString('jean.dupont', $user->Email);
}
}