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:03] – [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
  
-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 199: 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 216: 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 230: 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 439: 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 511: 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.1775916214.txt.gz · Dernière modification : 2026/04/11 16:03 de techer.charles_educ-valadon-limoges.fr