Outils pour utilisateurs

Outils du site


systeme:documenso:minio

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentesRévision précédente
Prochaine révision
Révision précédente
systeme:documenso:minio [2026/04/11 14:59] – [Sécuriser le webhook MinIO → Proxy] techer.charles_educ-valadon-limoges.frsysteme: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** de la signature.  Voir plus loin pour la configuration de la signature dans le Webhook.  +Pour sécuriser le webhook de MinIO vers le Proxy, utilisation d'un secret partagé (token) et vérification dans **main.py** du secret.  Voir plus loin pour la configuration de la signature dans le Webhook.   
 +</WRAP> 
 + 
 +<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 > 250 Mo (vidéos, archives, sauvegardes) 
 +  * 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> 
 + 
 +<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 :  
 +<code> 
 +Documents 
 +├── administration 
 +│   ├── gestion 
 +│   ├── scolaires 
 +│   └── administration 
 +├── bts-sio 
 +│   ├── stages 
 +│   ├── ccf 
 +│   └── livrets 
 +├── bts-mco 
 +│   ├── stages 
 +│   ├── ccf 
 +│   └── livrets 
 +</code> 
 + 
 +Mapping pédagogique MinIO → SharePoint (préfixe MinIO = dossier SharePoint) 
 +^ MinIO (bucket pedagogie)  ^  SharePoint (Documents) 
 +|administration/gestion/|Documents/administration/gestion/
 +|bts-sio/|Documents/bts-sio/
 +|bts-sio/stages/|Documents/bts-sio/stages/|
 </WRAP> </WRAP>
  
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 hmachashlib, os+import osrequests
  
 from graph import graph_request from graph import graph_request
- 
-SHARED_SECRET = os.getenv("WEBHOOK_SECRET").encode() 
- 
-app = Flask(__name__) 
  
 TENANT_ID = os.getenv("TENANT_ID") TENANT_ID = os.getenv("TENANT_ID")
Ligne 170: Ligne 212:
 GRAPH_BASE = "https://graph.microsoft.com/v1.0" GRAPH_BASE = "https://graph.microsoft.com/v1.0"
  
 +WEBHOOK_SECRET = os.getenv("WEBHOOK_SECRET")
 +if not WEBHOOK_SECRET:
 +    raise RuntimeError("WEBHOOK_SECRET not set")
  
-def verify_hmac(request): +# Détection de la taille du fichier /  Upload thresholds    
-    signature request.headers.get("X-Minio-Signature"+MAX_SIMPLE_UPLOAD 4 * 1024 * 1024  # 4 MB 
-    if not signature: +CHUNK_SIZE = 10 * 1024 * 1024        # 10 MB
-        abort(401)+
  
-    body request.data +app Flask(__name__) 
-    expected = hmac.new+ 
-        SHARED_SECRET, body, hashlib.sha256 + 
-    ).hexdigest()+# ───────────────────────────── 
 +# Sécurité webhook MinIO 
 +# ─────────────────────────────   
 +def verify_minio_webhook(req)
 +    auth = req.headers.get("Authorization")
  
-    if not hmac.compare_digest(signature, expected): +    if not auth or not auth.startswith("Bearer "): 
-        abort(403)+        abort(401, "Missing Authorization header")
  
 +    token = auth[len("Bearer "):].strip()
  
 +    if token != WEBHOOK_SECRET:
 +        abort(403, "Invalid MinIO webhook token")
 +        
 +# ─────────────────────────────
 +# SharePoint drive
 +# ─────────────────────────────
 def get_drive_id(): def get_drive_id():
     site = graph_request(     site = graph_request(
Ligne 201: Ligne 256:
 print(f"Drive ID initialized: {DRIVE_ID}") print(f"Drive ID initialized: {DRIVE_ID}")
  
 +# ─────────────────────────────
 +# Upload helpers
 +# ─────────────────────────────
 +def create_upload_session(sp_path):
 +    res = graph_request(
 +        "POST",
 +        f"{GRAPH_BASE}/drives/{DRIVE_ID}/root:/{sp_path}:/createUploadSession",
 +        json={
 +            "item": {
 +                "@microsoft.graph.conflictBehavior": "replace"
 +            }
 +        }
 +    )
 +    return res["uploadUrl"]
 +
 +
 +def upload_simple(sp_path, data):
 +    graph_request(
 +        "PUT",
 +        f"{GRAPH_BASE}/drives/{DRIVE_ID}/root:/{sp_path}:/content",
 +        data=data
 +    )
 +
 +
 +def upload_chunked(sp_path, file_path):
 +    upload_url = create_upload_session(sp_path)
 +    size = os.path.getsize(file_path)
 +
 +    with open(file_path, "rb") as f:
 +        start = 0
 +        while start < size:
 +            chunk = f.read(CHUNK_SIZE)
 +            end = start + len(chunk) - 1
 +
 +            headers = {
 +                "Content-Length": str(len(chunk)),
 +                "Content-Range": f"bytes {start}-{end}/{size}"
 +            }
 +
 +            r = requests.put(upload_url, headers=headers, data=chunk)
 +            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"Ignored (non declare): {key}", flush=True)
 +        return
 +
 +    print(f"Upload autortise: {sp_path}", flush=True)
 +
 +    # Télécharger depuis MinIO → /tmp (à implémenter)
 +    file_path = f"/tmp/{os.path.basename(key)}"
 +
 +    size = os.path.getsize(file_path)
 +
 +    if size <= MAX_SIMPLE_UPLOAD:
 +        with open(file_path, "rb") as f:
 +            upload_simple(key, f.read())
 +    else:
 +        upload_chunked(key, file_path)
 +
 +    os.remove(file_path)
 +
 +
 +def delete_object(key):
 +    sp_path = map_mapping_path(key)
 +
 +    if not sp_path:
 +        return
 +
 +    print(f"Delete pédagogique: {sp_path}", flush=True)
 +
 +    graph_request(
 +        "DELETE",
 +        f"{GRAPH_BASE}/drives/{DRIVE_ID}/root:/{key}"
 +    )
 + 
 +# ─────────────────────────────
 +# Mappping
 +# ─────────────────────────────
 +def map_mapping_path(key: str) -> str | None:
 +    """
 +    Transforme une clé MinIO en chemin SharePoint autorisé.
 +    Retourne None si le chemin n'est pas declare.
 +    """
 +
 +    key = key.lstrip("/")
 +
 +    ALLOWED_PREFIXES = [
 +        "administration/",
 +        "bts-sio/",
 +        "bts-mco/"
 +    ]
 +
 +    for prefix in ALLOWED_PREFIXES:
 +        if key.startswith(prefix):
 +            return key
 +
 +    # Tout le reste est ignoré
 +    return None
 +
 +
 +
 +# ─────────────────────────────
 +# Webhook endpoint
 +# ─────────────────────────────
 @app.route("/s3event", methods=["POST"]) @app.route("/s3event", methods=["POST"])
 def s3_event(): def s3_event():
-    verify_hmac(request) +    verify_minio_webhook(request) 
-    event = request.json+    event = request.get_json()
     record = event["Records"][0]     record = event["Records"][0]
     event_name = record["eventName"]     event_name = record["eventName"]
Ligne 215: Ligne 382:
  
     return "", 204     return "", 204
- 
-def upload_object(key): 
-    print(f"Uploading {key} to SharePoint") 
-    url = f"{GRAPH_BASE}/drives/{DRIVE_ID}/root:/{key}:/content" 
-    graph_request("PUT", url) 
- 
-def delete_object(key): 
-    print(f"Deleting {key} from SharePoint") 
-    url = f"{GRAPH_BASE}/drives/{DRIVE_ID}/root:/{key}" 
-    graph_request("DELETE", url) 
  
 if __name__ == "__main__": if __name__ == "__main__":
Ligne 284: Ligne 441:
 AUTHORITY=https://login.microsoftonline.com/<ID du tenant> AUTHORITY=https://login.microsoftonline.com/<ID du tenant>
 GRAPH_SCOPE=https://graph.microsoft.com/.default GRAPH_SCOPE=https://graph.microsoft.com/.default
 +
 +WEBHOOK_SECRET="super-secret-minio"
  
 DOCUMENT_LIBRARY="Documents" DOCUMENT_LIBRARY="Documents"
Ligne 420: Ligne 579:
 </WRAP> </WRAP>
  
-Pour sécuriser le webhook MinIO vers le Proxy, utilisation d'une signature HMAC (RECOMMANDÉE) 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.
-  * signature du webhook par MinIO, +
-  * vérification de la signature par le proxy.+
  
- +  
-  * Configurer le webhook avec HMAC :+  * Configurer le webhook avec le token :
  
 <code> <code>
Ligne 496: Ligne 653:
 Please restart your server with `mc admin service restart minio`. Please restart your server with `mc admin service restart minio`.
 </code> </code>
 +
 +  * créer le bucket
 +
 +<code>
 +mc mb minio/lycee
 +
 +=> résultat attendu
 +Bucket created successfully `minio/lycee`.
 +</code>
 +
 +  * vérification
 +
 +<code>
 +mc ls minio
 +</code>
 +
  
   *  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
 +</WRAP>
 +
  
 <code> <code>
-mc event add minio/test arn:minio:sqs::sharepoint:webhook --event put,delete+mc event add minio/lycee arn:minio:sqs::sharepoint:webhook 
 +  --event put,delete 
 +  --prefix administration/
 +  --prefix bts-sio/ \ 
 +  --prefix bts-mco/ 
 + 
  
 => doit renvoyer => doit renvoyer
Ligne 572: Ligne 756:
   * Fichier supprimé dans SharePoint   * Fichier supprimé dans SharePoint
  
 +===== Gérer les fichiers volumineux (Upload Microsoft Graph en chunks > 250 Mo) =====
 +Microsoft Graph refuse :
 +  * les PUT /content simples
 +  * dès que le fichier dépasse ~4–10 Mo (limite variable)
 +  * Pour des fichiers > 250 Mo (vidéos, archives, sauvegardes) 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
 +
 +==== 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, file_path)
 +else:
 +    upload_chunked(path, file_path)
 +</code>
 +
 +  * Création de la session d’upload
 +
 +<code python>
 +def create_upload_session(drive_id, sp_path):
 +    return graph_request(
 +        "POST",
 +        f"{GRAPH_BASE}/drives/{drive_id}/root:/{sp_path}:/createUploadSession",
 +        json={
 +            "item": {
 +                "@microsoft.graph.conflictBehavior": "replace"
 +            }
 +        }
 +    )["uploadUrl"]
 +</code>
 +
 +  * Upload par chunks (robuste et reprenable)
 +
 +<code>
 +import requests
 +
 +def upload_chunked(sp_path, local_file):
 +    upload_url = create_upload_session(DRIVE_ID, sp_path)
 +
 +    chunk_size = 10 * 1024 * 1024  # 10 MB
 +    size = os.path.getsize(local_file)
 +
 +    with open(local_file, "rb") as f:
 +        start = 0
 +        while start < size:
 +            data = f.read(chunk_size)
 +            end = start + len(data) - 1
 +
 +            headers = {
 +                "Content-Length": str(len(data)),
 +                "Content-Range": f"bytes {start}-{end}/{size}"
 +            }
 +
 +            r = requests.put(upload_url, headers=headers, data=data)
 +            r.raise_for_status()
 +
 +            start += chunk_size
 +
 +</code>
systeme/documenso/minio.1775912363.txt.gz · Dernière modification : 2026/04/11 14:59 de techer.charles_educ-valadon-limoges.fr