Compare commits
10 Commits
454767aa58
...
e5e6f72f92
| Author | SHA1 | Date | |
|---|---|---|---|
| e5e6f72f92 | |||
| de45e62d20 | |||
| e2b68f2d68 | |||
| 751af7adb4 | |||
| 8ab2d988fe | |||
| 1fa3806556 | |||
| 6e387a66ea | |||
| 1817a697ae | |||
| 916c4587f6 | |||
| 33e25a8b8e |
+14
-7
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
|
||||||
@@ -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,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'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,8 +25,8 @@ class ProfileController extends Controller
|
|||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
|
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'Pseudo' => ['required', 'string', 'max:100', 'unique:utilisateurs,Pseudo,' . $user->ID_Utilisateur . ',ID_Utilisateur'],
|
'Pseudo' => ['required', 'string', 'max:100', 'unique:utilisateurs,Pseudo,'.$user->ID_Utilisateur.',ID_Utilisateur'],
|
||||||
'Email' => ['required', 'string', 'email', 'max:255', 'unique:utilisateurs,Email,' . $user->ID_Utilisateur . ',ID_Utilisateur'],
|
'Email' => ['required', 'string', 'email', 'max:255', 'unique:utilisateurs,Email,'.$user->ID_Utilisateur.',ID_Utilisateur'],
|
||||||
'MotDePasse_Actuel' => ['nullable', 'string'],
|
'MotDePasse_Actuel' => ['nullable', 'string'],
|
||||||
'MotDePasse_Nouveau' => ['nullable', 'string', 'min:8', 'confirmed'],
|
'MotDePasse_Nouveau' => ['nullable', 'string', 'min:8', 'confirmed'],
|
||||||
], [
|
], [
|
||||||
@@ -41,7 +42,7 @@ class ProfileController extends Controller
|
|||||||
|
|
||||||
// Vérifier le mot de passe actuel si on veut le modifier
|
// Vérifier le mot de passe actuel si on veut le modifier
|
||||||
if ($request->filled('MotDePasse_Nouveau')) {
|
if ($request->filled('MotDePasse_Nouveau')) {
|
||||||
if (!Hash::check($request->MotDePasse_Actuel, $user->MotDePasse)) {
|
if (! Hash::check($request->MotDePasse_Actuel, $user->MotDePasse)) {
|
||||||
return back()->withErrors([
|
return back()->withErrors([
|
||||||
'MotDePasse_Actuel' => 'Le mot de passe actuel est incorrect.',
|
'MotDePasse_Actuel' => 'Le mot de passe actuel est incorrect.',
|
||||||
])->onlyInput('Email', 'Pseudo');
|
])->onlyInput('Email', 'Pseudo');
|
||||||
@@ -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.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ForceHttps
|
||||||
|
{
|
||||||
|
public function handle(Request $request, Closure $next)
|
||||||
|
{
|
||||||
|
// En production, forcer HTTPS même si on arrive en HTTP.
|
||||||
|
// 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(
|
||||||
|
str_replace('http://', 'https://', $request->url()),
|
||||||
|
308
|
||||||
|
)->send();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = [
|
||||||
|
|||||||
@@ -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 = [
|
||||||
|
|||||||
@@ -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 = [
|
||||||
|
|||||||
@@ -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 = [
|
||||||
@@ -42,4 +44,4 @@ class Commentaire extends Model
|
|||||||
{
|
{
|
||||||
return $this->belongsTo(Commentaire::class, 'ID_Parent_Commentaire', 'ID_Commentaire');
|
return $this->belongsTo(Commentaire::class, 'ID_Parent_Commentaire', 'ID_Commentaire');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = [
|
||||||
@@ -59,4 +61,4 @@ class Ressource extends Model
|
|||||||
{
|
{
|
||||||
return $this->hasMany(Utilisateur_Progression::class, 'ID_Ressource', 'ID_Ressource');
|
return $this->hasMany(Utilisateur_Progression::class, 'ID_Ressource', 'ID_Ressource');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = [
|
||||||
|
|||||||
@@ -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 = [
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
@@ -20,10 +21,15 @@ 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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'];
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
|
||||||
use Illuminate\Support\Facades\URL;
|
use Illuminate\Support\Facades\URL;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
@@ -20,7 +20,10 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
// Forcer HTTPS
|
if (! app()->environment('local')) {
|
||||||
URL::forceScheme('https');
|
URL::forceScheme('https');
|
||||||
|
}
|
||||||
|
// ForceHttps middleware se charge de forcer HTTPS
|
||||||
|
// TrustProxies middleware reconnaît les headers du proxy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -1,9 +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;
|
|
||||||
|
|
||||||
return Application::configure(basePath: dirname(__DIR__))
|
return Application::configure(basePath: dirname(__DIR__))
|
||||||
->withRouting(
|
->withRouting(
|
||||||
@@ -12,6 +13,7 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware): void {
|
->withMiddleware(function (Middleware $middleware): void {
|
||||||
|
$middleware->append(ForceHttps::class);
|
||||||
$middleware->append(SecurityHeaders::class);
|
$middleware->append(SecurityHeaders::class);
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions): void {
|
->withExceptions(function (Exceptions $exceptions): void {
|
||||||
|
|||||||
+1
-1
@@ -52,7 +52,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'url' => env('APP_URL', 'http://localhost'),
|
'url' => env('APP_URL', 'https://app.sam-coffre.duckdns.org/login'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ return [
|
|||||||
'public' => [
|
'public' => [
|
||||||
'driver' => 'local',
|
'driver' => 'local',
|
||||||
'root' => storage_path('app/public'),
|
'root' => storage_path('app/public'),
|
||||||
'url' => rtrim(env('APP_URL', 'http://localhost'), '/').'/storage',
|
'url' => rtrim(env('APP_URL', 'https://app.sam-coffre.duckdns.org/login'), '/').'/storage',
|
||||||
'visibility' => 'public',
|
'visibility' => 'public',
|
||||||
'throw' => false,
|
'throw' => false,
|
||||||
'report' => false,
|
'report' => false,
|
||||||
|
|||||||
+1
-1
@@ -46,7 +46,7 @@ return [
|
|||||||
'username' => env('MAIL_USERNAME'),
|
'username' => env('MAIL_USERNAME'),
|
||||||
'password' => env('MAIL_PASSWORD'),
|
'password' => env('MAIL_PASSWORD'),
|
||||||
'timeout' => null,
|
'timeout' => null,
|
||||||
'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
|
'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'https://app.sam-coffre.duckdns.org/login'), PHP_URL_HOST)),
|
||||||
],
|
],
|
||||||
|
|
||||||
'ses' => [
|
'ses' => [
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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).
|
||||||
@@ -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.
@@ -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.
@@ -5,6 +5,18 @@
|
|||||||
|
|
||||||
RewriteEngine On
|
RewriteEngine On
|
||||||
|
|
||||||
|
# Reconnaître les headers du reverse proxy pour HTTPS
|
||||||
|
<IfModule mod_setenvif.c>
|
||||||
|
SetEnvIf X-Forwarded-Proto "https" HTTPS=on
|
||||||
|
SetEnvIf X-Forwarded-Proto "^https$" SSL_PROTOCOL=TLSv1
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# Forcer HTTPS en production
|
||||||
|
RewriteCond %{ENV:HTTPS} !=on
|
||||||
|
RewriteCond %{HTTP_HOST} !^localhost
|
||||||
|
RewriteCond %{HTTP_HOST} !^127\.0\.0\.1
|
||||||
|
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
|
||||||
|
|
||||||
# Handle Authorization Header
|
# Handle Authorization Header
|
||||||
RewriteCond %{HTTP:Authorization} .
|
RewriteCond %{HTTP:Authorization} .
|
||||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
+24
-1
@@ -1,13 +1,35 @@
|
|||||||
<?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('/debug-https', function (Request $request) {
|
||||||
|
return response()->json([
|
||||||
|
'isSecure' => $request->isSecure(),
|
||||||
|
'url' => $request->url(),
|
||||||
|
'scheme' => $request->getScheme(),
|
||||||
|
'headers' => [
|
||||||
|
'X-Forwarded-Proto' => $request->header('X-Forwarded-Proto'),
|
||||||
|
'X-Forwarded-For' => $request->header('X-Forwarded-For'),
|
||||||
|
'X-Forwarded-Host' => $request->header('X-Forwarded-Host'),
|
||||||
|
'X-Forwarded-Port' => $request->header('X-Forwarded-Port'),
|
||||||
|
'CF-Visitor' => $request->header('CF-Visitor'),
|
||||||
|
],
|
||||||
|
'SERVER_HTTPS' => $_SERVER['HTTPS'] ?? 'not set',
|
||||||
|
'REQUEST_SCHEME' => $_SERVER['REQUEST_SCHEME'] ?? 'not set',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
Route::get('/resources', function () {
|
Route::get('/resources', function () {
|
||||||
return view('resources');
|
return view('resources');
|
||||||
})->name('resources.index');
|
})->name('resources.index');
|
||||||
@@ -25,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');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user