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 16:06] – [Webhook Python] techer.charles_educ-valadon-limoges.frsysteme:documenso:minio [2026/04/11 17:55] (Version actuelle) – [Webhook Python] techer.charles_educ-valadon-limoges.fr
Ligne 169: Ligne 169:
   * Envoyer le fichier par blocs (chunks)   * Envoyer le fichier par blocs (chunks)
   * Graph reconstruit le fichier côté SharePoint   * 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 174: 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
Ligne 182: Ligne 212:
 GRAPH_BASE = "https://graph.microsoft.com/v1.0" GRAPH_BASE = "https://graph.microsoft.com/v1.0"
  
-SHARED_SECRET = os.getenv("WEBHOOK_SECRET")+WEBHOOK_SECRET = os.getenv("WEBHOOK_SECRET")
 if not WEBHOOK_SECRET: if not WEBHOOK_SECRET:
     raise RuntimeError("WEBHOOK_SECRET not set")     raise RuntimeError("WEBHOOK_SECRET not set")
  
-# Détection de la taille du fichier    +# Détection de la taille du fichier /  Upload thresholds   
 MAX_SIMPLE_UPLOAD = 4 * 1024 * 1024  # 4 MB MAX_SIMPLE_UPLOAD = 4 * 1024 * 1024  # 4 MB
 +CHUNK_SIZE = 10 * 1024 * 1024        # 10 MB
  
-size os.path.getsize(file_path)+app Flask(__name__)
  
-if size <= MAX_SIMPLE_UPLOAD: 
-    upload_simple(path, file_path) 
-else: 
-    upload_chunked(path, file_path) 
  
-app Flask(__name__)+# ───────────────────────────── 
 +# Sécurité webhook MinIO 
 +# ─────────────────────────────   
 +def verify_minio_webhook(req): 
 +    auth req.headers.get("Authorization"
 + 
 +    if not auth or not auth.startswith("Bearer "): 
 +        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(): 
 +    site = graph_request( 
 +        "GET", 
 +        f"{GRAPH_BASE}/sites/{SITE_PATH}" 
 +    ) 
 +    drive = graph_request( 
 +        "GET", 
 +        f"{GRAPH_BASE}/sites/{site['id']}/drive" 
 +    ) 
 +    return drive["id"
 + 
 +# INITIALISATION AU DÉMARRAGE 
 +print("Initializing SharePoint drive…"
 +DRIVE_ID = get_drive_id() 
 +print(f"Drive ID initialized: {DRIVE_ID}")
  
-#Création de la session d’upload +───────────────────────────── 
-def create_upload_session(drive_id, sp_path): +# Upload helpers 
-    return graph_request(+# ───────────────────────────── 
 +def create_upload_session(sp_path): 
 +    res = graph_request(
         "POST",         "POST",
-        f"{GRAPH_BASE}/drives/{drive_id}/root:/{sp_path}:/createUploadSession",+        f"{GRAPH_BASE}/drives/{DRIVE_ID}/root:/{sp_path}:/createUploadSession",
         json={         json={
             "item": {             "item": {
Ligne 208: Ligne 268:
             }             }
         }         }
-    )["uploadUrl"]+    ) 
 +    return res["uploadUrl"]
  
-# Upload par chunks (robuste et reprenable)     
-def upload_chunked(sp_path, local_file): 
-    upload_url = create_upload_session(DRIVE_ID, sp_path) 
  
-    chunk_size = 10 * 1024 * 1024  # 10 MB +def upload_simple(sp_path, data): 
-    size = os.path.getsize(local_file)+    graph_request( 
 +        "PUT", 
 +        f"{GRAPH_BASE}/drives/{DRIVE_ID}/root:/{sp_path}:/content", 
 +        data=data 
 +    )
  
-    with open(local_file, "rb") as f:+ 
 +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         start = 0
         while start < size:         while start < size:
-            data = f.read(chunk_size+            chunk = f.read(CHUNK_SIZE
-            end = start + len(data) - 1+            end = start + len(chunk) - 1
  
             headers = {             headers = {
-                "Content-Length": str(len(data)),+                "Content-Length": str(len(chunk)),
                 "Content-Range": f"bytes {start}-{end}/{size}"                 "Content-Range": f"bytes {start}-{end}/{size}"
             }             }
  
-            r = requests.put(upload_url, headers=headers, data=data)+            r = requests.put(upload_url, headers=headers, data=chunk)
             r.raise_for_status()             r.raise_for_status()
 +            start += len(chunk)
  
-            start += chunk_size 
  
 +# ─────────────────────────────
 +# Event handlers
 +# ─────────────────────────────
 +def upload_object(key):
 +    sp_path = map_mapping_path(key)
  
-   +    if not sp_path: 
-def verify_minio_webhook(req): +        print(f"Ignored (non declare): {key}", flush=True) 
-    auth = req.headers.get("Authorization")+        return
  
-    if not auth or not auth.startswith("Bearer "): +    print(f"Upload autortise: {sp_path}", flush=True)
-        abort(401"Missing Authorization header")+
  
-    token = auth[len("Bearer "):].strip()+    # Télécharger depuis MinIO → /tmp (à implémenter) 
 +    file_path = f"/tmp/{os.path.basename(key)}"
  
-    if token !WEBHOOK_SECRET+    size = os.path.getsize(file_path) 
-        abort(403, "Invalid MinIO webhook token")+ 
 +    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 get_drive_id(): + 
-    site = graph_request( +def delete_object(key): 
-        "GET", +    sp_path map_mapping_path(key) 
-        f"{GRAPH_BASE}/sites/{SITE_PATH}"+ 
 +    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}"
     )     )
-    drive = graph_request+  
-        "GET"+# ───────────────────────────── 
-        f"{GRAPH_BASE}/sites/{site['id']}/drive+# Mappping 
-    ) +# ───────────────────────────── 
-    return drive["id"]+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. 
 +    """
  
-# INITIALISATION AU DÉMARRAGE +    key key.lstrip("/")
-print("Initializing SharePoint drive…"+
-DRIVE_ID get_drive_id() +
-print(f"Drive ID initialized: {DRIVE_ID}")+
  
 +    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():
Ligne 276: 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 485: 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 557: 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
systeme/documenso/minio.1775916377.txt.gz · Dernière modification : 2026/04/11 16:06 de techer.charles_educ-valadon-limoges.fr