systeme:documenso:minio
Différences
Ci-dessous, les différences entre deux révisions de la page.
| Les deux révisions précédentesRévision précédenteProchaine révision | Révision précédente | ||
| systeme:documenso:minio [2026/04/11 15:26] – [Webhook Python] techer.charles_educ-valadon-limoges.fr | systeme:documenso:minio [2026/04/11 17:55] (Version actuelle) – [Webhook Python] techer.charles_educ-valadon-limoges.fr | ||
|---|---|---|---|
| Ligne 152: | Ligne 152: | ||
| <WRAP center round info> | <WRAP center round info> | ||
| - | Pour sécuriser le webhook de MinIO vers le Proxy, utilisation d'une signature HMAC (RECOMMANDÉE) et vérification dans **main.py** | + | Pour sécuriser le webhook de MinIO vers le Proxy, utilisation d'un secret partagé |
| + | </ | ||
| + | |||
| + | <WRAP center round info> | ||
| + | Gérer les fichiers volumineux : | ||
| + | |||
| + | Microsoft Graph refuse : | ||
| + | * les PUT /content simples | ||
| + | * dès que le fichier dépasse ~4–10 Mo (limite variable) | ||
| + | |||
| + | Pour des fichiers > | ||
| + | * upload en session + chunks obligatoire | ||
| + | |||
| + | Principe Graph (officiel) | ||
| + | * Créer une upload session | ||
| + | * Envoyer le fichier par blocs (chunks) | ||
| + | * Graph reconstruit le fichier côté SharePoint | ||
| + | </ | ||
| + | |||
| + | <WRAP center round info> | ||
| + | Gestion de plusieurs dossiers dans une seule équipe SharePoint : | ||
| + | * dossiers automatiquement créés dans Sharepoint | ||
| + | * aucun droit d’écriture manuel côté SharePoint | ||
| + | * traçabilité MinIO → SharePoint | ||
| + | |||
| + | Structure : | ||
| + | < | ||
| + | Documents | ||
| + | ├── administration | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | ├── bts-sio | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | ├── bts-mco | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | </ | ||
| + | |||
| + | Mapping pédagogique MinIO → SharePoint (préfixe MinIO = dossier SharePoint) | ||
| + | ^ MinIO (bucket pedagogie) | ||
| + | |administration/ | ||
| + | |bts-sio/ | ||
| + | |bts-sio/ | ||
| </ | </ | ||
| Ligne 158: | Ligne 204: | ||
| <code python main.py> | <code python main.py> | ||
| from flask import Flask, request, abort | from flask import Flask, request, abort | ||
| - | import os | + | import os, requests |
| from graph import graph_request | from graph import graph_request | ||
| - | SHARED_SECRET | + | TENANT_ID |
| + | SITE_PATH = os.getenv(" | ||
| + | GRAPH_BASE = " | ||
| + | |||
| + | WEBHOOK_SECRET = os.getenv(" | ||
| if not WEBHOOK_SECRET: | if not WEBHOOK_SECRET: | ||
| raise RuntimeError(" | raise RuntimeError(" | ||
| + | # Détection de la taille du fichier / Upload thresholds | ||
| + | MAX_SIMPLE_UPLOAD = 4 * 1024 * 1024 # 4 MB | ||
| + | CHUNK_SIZE = 10 * 1024 * 1024 # 10 MB | ||
| app = Flask(__name__) | app = Flask(__name__) | ||
| - | TENANT_ID = os.getenv(" | ||
| - | SITE_PATH = os.getenv(" | ||
| - | GRAPH_BASE = " | ||
| + | # ───────────────────────────── | ||
| + | # Sécurité webhook MinIO | ||
| + | # ───────────────────────────── | ||
| def verify_minio_webhook(req): | def verify_minio_webhook(req): | ||
| auth = req.headers.get(" | auth = req.headers.get(" | ||
| Ligne 183: | Ligne 236: | ||
| if token != WEBHOOK_SECRET: | if token != WEBHOOK_SECRET: | ||
| abort(403, " | abort(403, " | ||
| + | |||
| + | # ───────────────────────────── | ||
| + | # SharePoint drive | ||
| + | # ───────────────────────────── | ||
| def get_drive_id(): | def get_drive_id(): | ||
| site = graph_request( | site = graph_request( | ||
| Ligne 200: | Ligne 256: | ||
| print(f" | print(f" | ||
| + | # ───────────────────────────── | ||
| + | # Upload helpers | ||
| + | # ───────────────────────────── | ||
| + | def create_upload_session(sp_path): | ||
| + | res = graph_request( | ||
| + | " | ||
| + | f" | ||
| + | json={ | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | } | ||
| + | ) | ||
| + | return res[" | ||
| + | |||
| + | |||
| + | def upload_simple(sp_path, | ||
| + | graph_request( | ||
| + | " | ||
| + | f" | ||
| + | data=data | ||
| + | ) | ||
| + | |||
| + | |||
| + | def upload_chunked(sp_path, | ||
| + | upload_url = create_upload_session(sp_path) | ||
| + | size = os.path.getsize(file_path) | ||
| + | |||
| + | with open(file_path, | ||
| + | start = 0 | ||
| + | while start < size: | ||
| + | chunk = f.read(CHUNK_SIZE) | ||
| + | end = start + len(chunk) - 1 | ||
| + | |||
| + | headers = { | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | |||
| + | r = requests.put(upload_url, | ||
| + | r.raise_for_status() | ||
| + | start += len(chunk) | ||
| + | |||
| + | |||
| + | # ───────────────────────────── | ||
| + | # Event handlers | ||
| + | # ───────────────────────────── | ||
| + | def upload_object(key): | ||
| + | sp_path = map_mapping_path(key) | ||
| + | |||
| + | if not sp_path: | ||
| + | print(f" | ||
| + | return | ||
| + | |||
| + | print(f" | ||
| + | |||
| + | # Télécharger depuis MinIO → /tmp (à implémenter) | ||
| + | file_path = f"/ | ||
| + | |||
| + | size = os.path.getsize(file_path) | ||
| + | |||
| + | if size <= MAX_SIMPLE_UPLOAD: | ||
| + | with open(file_path, | ||
| + | upload_simple(key, | ||
| + | else: | ||
| + | upload_chunked(key, | ||
| + | |||
| + | os.remove(file_path) | ||
| + | |||
| + | |||
| + | def delete_object(key): | ||
| + | sp_path = map_mapping_path(key) | ||
| + | |||
| + | if not sp_path: | ||
| + | return | ||
| + | |||
| + | print(f" | ||
| + | |||
| + | graph_request( | ||
| + | " | ||
| + | f" | ||
| + | ) | ||
| + | |||
| + | # ───────────────────────────── | ||
| + | # Mappping | ||
| + | # ───────────────────────────── | ||
| + | def map_mapping_path(key: | ||
| + | """ | ||
| + | Transforme une clé MinIO en chemin SharePoint autorisé. | ||
| + | Retourne None si le chemin n'est pas declare. | ||
| + | """ | ||
| + | |||
| + | key = key.lstrip("/" | ||
| + | |||
| + | ALLOWED_PREFIXES = [ | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | ] | ||
| + | |||
| + | for prefix in ALLOWED_PREFIXES: | ||
| + | if key.startswith(prefix): | ||
| + | return key | ||
| + | |||
| + | # Tout le reste est ignoré | ||
| + | return None | ||
| + | |||
| + | |||
| + | |||
| + | # ───────────────────────────── | ||
| + | # Webhook endpoint | ||
| + | # ───────────────────────────── | ||
| @app.route("/ | @app.route("/ | ||
| def s3_event(): | def s3_event(): | ||
| Ligne 214: | Ligne 382: | ||
| return "", | return "", | ||
| - | |||
| - | def upload_object(key): | ||
| - | print(f" | ||
| - | url = f" | ||
| - | graph_request(" | ||
| - | |||
| - | def delete_object(key): | ||
| - | print(f" | ||
| - | url = f" | ||
| - | graph_request(" | ||
| if __name__ == " | if __name__ == " | ||
| Ligne 423: | Ligne 581: | ||
| Pour sécuriser le webhook MinIO vers le Proxy, utilisation d'un header statique contenant un token pour empêcher toute requête non légitime d’appeler /s3event. | Pour sécuriser le webhook MinIO vers le Proxy, utilisation d'un header statique contenant un token pour empêcher toute requête non légitime d’appeler /s3event. | ||
| + | |||
| * Configurer le webhook avec le token : | * Configurer le webhook avec le token : | ||
| Ligne 495: | Ligne 653: | ||
| Please restart your server with `mc admin service restart minio`. | Please restart your server with `mc admin service restart minio`. | ||
| </ | </ | ||
| + | |||
| + | * créer le bucket | ||
| + | |||
| + | < | ||
| + | mc mb minio/lycee | ||
| + | |||
| + | => résultat attendu | ||
| + | Bucket created successfully `minio/ | ||
| + | </ | ||
| + | |||
| + | * vérification | ||
| + | |||
| + | < | ||
| + | mc ls minio | ||
| + | </ | ||
| + | |||
| * Lier le bucket aux événements afin que MinIO envoie les données | * Lier le bucket aux événements afin que MinIO envoie les données | ||
| + | |||
| + | <WRAP center round info> | ||
| + | Filtrage côté MinIO (RECOMMANDÉ) : le proxy ne reçoit QUE ce qui est attendu | ||
| + | </ | ||
| + | |||
| < | < | ||
| - | mc event add minio/test arn: | + | mc event add minio/lycee arn: |
| + | | ||
| + | --prefix administration/ | ||
| + | --prefix bts-sio/ \ | ||
| + | --prefix bts-mco/ | ||
| + | |||
| => doit renvoyer | => doit renvoyer | ||
| Ligne 571: | Ligne 756: | ||
| * Fichier supprimé dans SharePoint | * Fichier supprimé dans SharePoint | ||
| + | ===== Gérer les fichiers volumineux (Upload Microsoft Graph en chunks > | ||
| + | Microsoft Graph refuse : | ||
| + | * les PUT /content simples | ||
| + | * dès que le fichier dépasse ~4–10 Mo (limite variable) | ||
| + | * Pour des fichiers > | ||
| + | Principe Graph (officiel) | ||
| + | * Créer une upload session | ||
| + | * Envoyer le fichier par blocs (chunks) | ||
| + | * Graph reconstruit le fichier côté SharePoint | ||
| + | |||
| + | ==== Détection de la taille ==== | ||
| + | |||
| + | <code python> | ||
| + | import os | ||
| + | |||
| + | MAX_SIMPLE_UPLOAD = 4 * 1024 * 1024 # 4 MB | ||
| + | |||
| + | size = os.path.getsize(file_path) | ||
| + | |||
| + | if size <= MAX_SIMPLE_UPLOAD: | ||
| + | upload_simple(path, | ||
| + | else: | ||
| + | upload_chunked(path, | ||
| + | </ | ||
| + | |||
| + | * Création de la session d’upload | ||
| + | |||
| + | <code python> | ||
| + | def create_upload_session(drive_id, | ||
| + | return graph_request( | ||
| + | " | ||
| + | f" | ||
| + | json={ | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | } | ||
| + | )[" | ||
| + | </ | ||
| + | |||
| + | * Upload par chunks (robuste et reprenable) | ||
| + | |||
| + | < | ||
| + | import requests | ||
| + | |||
| + | def upload_chunked(sp_path, | ||
| + | upload_url = create_upload_session(DRIVE_ID, | ||
| + | |||
| + | chunk_size = 10 * 1024 * 1024 # 10 MB | ||
| + | size = os.path.getsize(local_file) | ||
| + | |||
| + | with open(local_file, | ||
| + | start = 0 | ||
| + | while start < size: | ||
| + | data = f.read(chunk_size) | ||
| + | end = start + len(data) - 1 | ||
| + | |||
| + | headers = { | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | |||
| + | r = requests.put(upload_url, | ||
| + | r.raise_for_status() | ||
| + | |||
| + | start += chunk_size | ||
| + | |||
| + | </ | ||
systeme/documenso/minio.1775913976.txt.gz · Dernière modification : 2026/04/11 15:26 de techer.charles_educ-valadon-limoges.fr
