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 15:50] – [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 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 os+import os, requests
  
 from graph import graph_request from graph import graph_request
  
-SHARED_SECRET = os.getenv("WEBHOOK_SECRET")+TENANT_ID = os.getenv("TENANT_ID"
 +SITE_PATH = os.getenv("SITE_PATH"
 +GRAPH_BASE = "https://graph.microsoft.com/v1.0" 
 + 
 +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 /  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("TENANT_ID") 
-SITE_PATH = os.getenv("SITE_PATH") 
-GRAPH_BASE = "https://graph.microsoft.com/v1.0" 
  
 +# ─────────────────────────────
 +# Sécurité webhook MinIO
 +# ─────────────────────────────  
 def verify_minio_webhook(req): def verify_minio_webhook(req):
     auth = req.headers.get("Authorization")     auth = req.headers.get("Authorization")
Ligne 183: Ligne 236:
     if token != WEBHOOK_SECRET:     if token != WEBHOOK_SECRET:
         abort(403, "Invalid MinIO webhook token")         abort(403, "Invalid MinIO webhook token")
 +         
 +# ───────────────────────────── 
 +# SharePoint drive 
 +# ─────────────────────────────
 def get_drive_id(): def get_drive_id():
     site = graph_request(     site = graph_request(
Ligne 200: 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():
Ligne 214: 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 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`.
 </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 571: 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.1775915441.txt.gz · Dernière modification : 2026/04/11 15:50 de techer.charles_educ-valadon-limoges.fr