Aller au contenu

Plug-ins

BunkerWeb est livré avec un système de plugins permettant d'ajouter facilement de nouvelles fonctionnalités. Une fois qu'un plugin est installé, vous pouvez le gérer à l'aide de paramètres supplémentaires définis par le plugin.

Plugins officiels

Voici la liste des plugins "officiels" que nous maintenons (voir le dépôt bunkerweb-plugins pour plus d'informations) :

Nom Version Description Lien
ClamAV 1.9 Analyse automatiquement les fichiers téléchargés avec le moteur antivirus ClamAV et rejette la demande lorsqu'un fichier est détecté comme malveillant. bunkerweb-plugins/clamav
Coraza 1.9 Inspectez les requĂŞtes Ă  l'aide du WAF Coraza (alternative Ă  ModSecurity). bunkerweb-plugins/coraza
Discorde 1.9 Envoyez des notifications de sécurité à un canal Discord à l'aide d'un Webhook. bunkerweb-plugins/discord
Lâche 1.9 Envoyez des notifications de sécurité à un canal Slack à l'aide d'un Webhook. bunkerweb-plugins/slack
VirusTotal 1.9 Analyse automatiquement les fichiers téléchargés à l'aide de l'API VirusTotal et rejette la demande lorsqu'un fichier est détecté comme malveillant. bunkerweb-plugins/virustotal
Crochet Web 1.9 Envoyez des notifications de sécurité à un point de terminaison HTTP personnalisé à l'aide d'un Webhook. bunkerweb-plugins/webhook

Comment utiliser un plugin

Automatique

Si vous souhaitez installer rapidement des plugins externes, vous pouvez utiliser ce EXTERNAL_PLUGIN_URLS paramètre. Il prend une liste d'URL séparées par des espaces, chacune pointant vers une archive compressée (format zip) contenant un ou plusieurs plugins.

Vous pouvez utiliser la valeur suivante si vous souhaitez installer automatiquement les plugins officiels : EXTERNAL_PLUGIN_URLS=https://github.com/bunkerity/bunkerweb-plugins/archive/refs/tags/v1.9.zip

Manuelle

La première étape consiste à installer le plugin en plaçant ses fichiers dans le dossier de données correspondant plugins . La procédure dépend de votre intégration :

Lors de l'utilisation de l'intégration Docker, les plugins doivent être placés dans le volume monté sur /data/plugins dans le conteneur du planificateur.

La première chose à faire est de créer le dossier des plugins :

mkdir -p ./bw-data/plugins

Ensuite, vous pouvez déposer les plugins de votre choix dans ce dossier :

git clone https://github.com/bunkerity/bunkerweb-plugins && \
cp -rp ./bunkerweb-plugins/* ./bw-data/plugins
Utilisation d'un dossier local pour les données persistantes

Le planificateur s'exécute en tant qu'utilisateur non privilégié avec UID 101 et GID 101 à l'intérieur du conteneur. La raison en est la sécurité : en cas d'exploitation d'une vulnérabilité, l'attaquant n'aura pas de privilèges root complets (UID/GID 0). Mais il y a un inconvénient : si vous utilisez un dossier local pour les données persistantes, vous devrez définir les permissions correctes afin que l'utilisateur non privilégié puisse écrire des données dedans. Quelque chose comme ça devrait faire l'affaire :

mkdir bw-data && \
chown root:101 bw-data && \
chmod 770 bw-data

Alternativement, si le dossier existe déjà :

chown -R root:101 bw-data && \
chmod -R 770 bw-data

Si vous utilisez Docker en mode rootless ou podman, les UID et GID dans le conteneur seront mappés à des valeurs différentes sur l'hôte. Vous devrez d'abord vérifier vos subuid et subgid initiaux :

grep ^$(whoami): /etc/subuid && \
grep ^$(whoami): /etc/subgid

Par exemple, si vous avez une valeur de 100000, l'UID/GID mappé sera 100100 (100000 + 100) :

mkdir bw-data && \
sudo chgrp 100100 bw-data && \
chmod 770 bw-data

Ou si le dossier existe déjà :

sudo chgrp -R 100100 bw-data && \
chmod -R 770 bw-data

Ensuite, vous pouvez monter le volume lors du démarrage de votre stack Docker :

services:
...
  bw-scheduler:
  image: bunkerity/bunkerweb-scheduler:1.6.5-rc1
  volumes:
    - ./bw-data:/data
...

Lors de l'utilisation de l'intégration Docker autoconf, les plugins doivent être placés dans le volume monté sur /data/plugins dans le conteneur du planificateur.

La première chose à faire est de créer le dossier des plugins :

mkdir -p ./bw-data/plugins

Ensuite, vous pouvez déposer les plugins de votre choix dans ce dossier :

git clone https://github.com/bunkerity/bunkerweb-plugins && \
cp -rp ./bunkerweb-plugins/* ./bw-data/plugins

Étant donné que le planificateur s'exécute en tant qu'utilisateur non privilégié avec UID et GID 101, vous devrez modifier les permissions :

chown -R 101:101 ./bw-data

Ensuite, vous pouvez monter le volume lors du démarrage de votre stack Docker :

services:
...
  bw-scheduler:
    image: bunkerity/bunkerweb-scheduler:1.6.5-rc1
    volumes:
      - ./bw-data:/data
...

Obsolète

L'intégration Swarm est obsolète et sera supprimée dans une future version. Veuillez envisager d'utiliser l'intégration Kubernetes à la place.

Plus d'informations sont disponibles dans la documentation de l'intégration Swarm.

Lors de l'utilisation de l'intégration Swarm, les plugins doivent être placés dans le volume monté sur /data/plugins dans le conteneur du planificateur.

Volume Swarm

La configuration d'un volume Swarm qui persistera lorsque le service du planificateur s'exécute sur différents nœuds n'est pas couverte dans cette documentation. Nous supposons que vous avez un dossier partagé monté sur /shared sur tous les nœuds.

La première chose à faire est de créer le dossier des plugins :

mkdir -p /shared/bw-plugins

Ensuite, vous pouvez déposer les plugins de votre choix dans ce dossier :

git clone https://github.com/bunkerity/bunkerweb-plugins && \
cp -rp ./bunkerweb-plugins/* /shared/bw-plugins

Étant donné que le planificateur s'exécute en tant qu'utilisateur non privilégié avec UID et GID 101, vous devrez modifier les permissions :

chown -R 101:101 /shared/bw-plugins

Ensuite, vous pouvez monter le volume lors du démarrage de votre stack Swarm :

services:
...
  bw-scheduler:
    image: bunkerity/bunkerweb-scheduler:1.6.5-rc1
    volumes:
      - /shared/bw-plugins:/data/plugins
...

Lors de l'utilisation de l'intégration Kubernetes, les plugins doivent être placés dans le volume monté sur /data/plugins dans le conteneur du planificateur.

La première chose à faire est de déclarer un PersistentVolumeClaim qui contiendra nos données de plugins :

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-bunkerweb-plugins
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi

Vous pouvez maintenant ajouter le montage de volume et un conteneur d'initialisation pour provisionner automatiquement le volume :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: bunkerweb-scheduler
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: bunkerweb-scheduler
  template:
    metadata:
      labels:
        app: bunkerweb-scheduler
    spec:
      serviceAccountName: sa-bunkerweb
      containers:
        - name: bunkerweb-scheduler
          image: bunkerity/bunkerweb-scheduler:1.6.5-rc1
          imagePullPolicy: Always
          env:
            - name: KUBERNETES_MODE
              value: "yes"
            - name: "DATABASE_URI"
              value: "mariadb+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db"
          volumeMounts:
            - mountPath: "/data/plugins"
              name: vol-plugins
      initContainers:
        - name: bunkerweb-scheduler-init
          image: alpine/git
          command: ["/bin/sh", "-c"]
          args: ["git clone https://github.com/bunkerity/bunkerweb-plugins /data/plugins && chown -R 101:101 /data/plugins"]
          volumeMounts:
            - mountPath: "/data/plugins"
              name: vol-plugins
      volumes:
        - name: vol-plugins
          persistentVolumeClaim:
            claimName: pvc-bunkerweb-plugins

Lors de l'utilisation de l'intégration Linux, les plugins doivent être écrits dans le dossier /etc/bunkerweb/plugins :

git clone https://github.com/bunkerity/bunkerweb-plugins && \
cp -rp ./bunkerweb-plugins/* /etc/bunkerweb/plugins && \
chown -R nginx:nginx /etc/bunkerweb/plugins

Écrire un plugin

Structure

Plugins existants

Si la documentation n'est pas suffisante, vous pouvez consulter le code source existant des plugins officiels et des plugins core (déjà inclus dans BunkerWeb mais ce sont des plugins, techniquement parlant).

Ă€ quoi ressemble la structure d'un plugin :

plugin /
    confs / conf_type / conf_name.conf
    ui / actions.py
         hooks.py
         template.html
         blueprints / <blueprint_file(s)>
              templates / <blueprint_template(s)>
    jobs / my-job.py
    templates / my-template.json
          my-template / configs / conf_type / conf_name.conf
    plugin.lua
    plugin.json

  • conf_name.conf : Ajout de configurations NGINX personnalisĂ©es (en tant que modèles Jinja2).

  • actions.py : Script Ă  exĂ©cuter sur le serveur Flask. Ce script s'exĂ©cute dans un contexte Flask, ce qui vous donne accès Ă  des bibliothèques et des utilitaires tels que jinja2 et requests.

  • hooks.py : Fichier Python personnalisĂ© qui contient les hooks de flask et qui sera exĂ©cutĂ© lors du chargement du plugin.

  • template.html : Page de plugin personnalisĂ©e accessible via l'interface utilisateur.

  • Dossier Blueprints (dans l'interface utilisateur) : Ce dossier est utilisĂ© pour remplacer les blueprints Flask existants ou en crĂ©er de nouveaux. Ă€ l'intĂ©rieur, vous pouvez inclure des fichiers de blueprint et un **** sous-dossier de modèles facultatif pour les modèles spĂ©cifiques aux blueprints.

  • jobs py file : Fichiers Python personnalisĂ©s exĂ©cutĂ©s en tant que jobs par le planificateur.

  • my-template.json : Ajoutez des modèles personnalisĂ©s pour remplacer les valeurs par dĂ©faut des paramètres et appliquer facilement des configurations personnalisĂ©es.

  • plugin.lua : Code exĂ©cutĂ© sur NGINX Ă  l'aide du module NGINX LUA.

  • plugin.json : MĂ©tadonnĂ©es, paramètres et dĂ©finitions de tâches pour votre plugin.

Commencer

La première étape consiste à créer un dossier qui contiendra le plugin :

mkdir myplugin && \
cd myplugin

Métadonnées

Un fichier nommé plugin.json et écrit à la racine du dossier du plugin doit contenir des métadonnées sur le plugin. En voici un exemple :

{
  "id": "myplugin",
  "name": "My Plugin",
  "description": "Just an example plugin.",
  "version": "1.0",
  "stream": "partial",
  "settings": {
    "DUMMY_SETTING": {
      "context": "multisite",
      "default": "1234",
      "help": "Here is the help of the setting.",
      "id": "dummy-id",
      "label": "Dummy setting",
      "regex": "^.*$",
      "type": "text"
    }
  },
  "jobs": [
    {
      "name": "my-job",
      "file": "my-job.py",
      "every": "hour"
    }
  ]
}

Voici le détail des champs :

Champ Obligatoire Type Description
id oui corde ID interne du plugin : doit être unique parmi les autres plugins (y compris les "core") et ne contenir que des caractères minuscules.
name oui corde Nom de votre plugin.
description oui corde Description de votre plugin.
version oui corde Version de votre plugin.
stream oui corde Informations sur la prise en charge des flux : no, yes ou partial.
settings oui Dict Liste des paramètres de votre plugin.
jobs Non liste Liste des jobs de votre plugin.

Chaque paramètre comporte les champs suivants (la clé est l'ID des paramètres utilisés dans une configuration) :

Champ Obligatoire Type Description
context oui corde Contexte du cadre : multisite ou global.
default oui corde Valeur par défaut du paramètre.
help oui corde Texte d'aide sur le plugin (affiché dans l'interface utilisateur Web).
id oui corde ID interne utilisé par l'interface utilisateur web pour les éléments HTML.
label oui corde Étiquette affichée par l'interface utilisateur Web.
regex oui corde L'expression régulière utilisée pour valider la valeur fournie par l'utilisateur.
type oui corde Le type du champ : text, check, select ou password.
multiple Non corde ID unique pour regrouper plusieurs paramètres avec des chiffres comme suffixe.
select Non liste Liste des valeurs de chaîne possibles lorsque est type . select

Chaque emploi comporte les champs suivants :

Champ Obligatoire Type Description
name oui corde Nom de l'emploi.
file oui corde Nom du fichier à l'intérieur du dossier jobs.
every oui corde Fréquence de planification des tâches : minute, hour, day week , ou once (pas de fréquence, une seule fois avant de (ré)générer la configuration).

Configurations

Vous pouvez ajouter des configurations NGINX personnalisées en ajoutant un dossier nommé confs avec un contenu similaire aux configurations personnalisées. Chaque sous-dossier à l'intérieur des confs contiendra des modèles jinja2 qui seront générés et chargés dans le contexte correspondant (http server-http default-server-http stream server-stream, , modsec modsec-crs, crs-plugins-before et crs-plugins-after).

Voici un exemple de fichier de modèle de configuration à l'intérieur du dossier confs/server-http nommé example.conf :

location /setting {
  default_type 'text/plain';
    content_by_lua_block {
        ngx.say('{{ DUMMY_SETTING }}')
    }
}

{{ DUMMY_SETTING }} sera remplacée par la valeur de la DUMMY_SETTING choisie par l'utilisateur du plugin.

Modèles

Pour plus d'informations, consultez la documentation des modèles.

LUA

Scénario principal

Sous le capot, BunkerWeb utilise le module LUA de NGINX pour exécuter du code dans NGINX. Les plugins qui ont besoin d'exécuter du code doivent fournir un fichier lua dans le répertoire racine du dossier du plugin en utilisant la id valeur de plugin.json comme nom. Voici un exemple nommé myplugin.lua :

local class     = require "middleclass"
local plugin    = require "bunkerweb.plugin"
local utils     = require "bunkerweb.utils"


local myplugin = class("myplugin", plugin)


function myplugin:initialize(ctx)
    plugin.initialize(self, "myplugin", ctx)
    self.dummy = "dummy"
end

function myplugin:init()
    self.logger:log(ngx.NOTICE, "init called")
    return self:ret(true, "success")
end

function myplugin:set()
    self.logger:log(ngx.NOTICE, "set called")
    return self:ret(true, "success")
end

function myplugin:access()
    self.logger:log(ngx.NOTICE, "access called")
    return self:ret(true, "success")
end

function myplugin:log()
    self.logger:log(ngx.NOTICE, "log called")
    return self:ret(true, "success")
end

function myplugin:log_default()
    self.logger:log(ngx.NOTICE, "log_default called")
    return self:ret(true, "success")
end

function myplugin:preread()
    self.logger:log(ngx.NOTICE, "preread called")
    return self:ret(true, "success")
end

function myplugin:log_stream()
    self.logger:log(ngx.NOTICE, "log_stream called")
    return self:ret(true, "success")
end

return myplugin

Les fonctions déclarées sont automatiquement appelées dans des contextes spécifiques. Voici le détail de chaque fonction :

Fonction Contexte Description Valeur de retour
init init_by_lua Appelé lorsque NGINX vient de démarrer ou a reçu un ordre de rechargement. Le cas d'utilisation typique consiste à préparer toutes les données qui seront utilisées par votre plugin. ret, msg
  • ret (boolĂ©en) : true s'il n'y a pas d'erreur ou sinon false
  • msg (chaĂ®ne) : message de rĂ©ussite ou d'erreur
set set_by_lua Appelé avant chaque requête reçue par le serveur. Le cas d'utilisation typique est celui de l'informatique avant la phase d'accès. ret, msg
  • ret (boolĂ©en) : true s'il n'y a pas d'erreur ou sinon false
  • msg (chaĂ®ne) : message de rĂ©ussite ou d'erreur
access access_by_lua Appelé à chaque requête reçue par le serveur. Le cas d'utilisation typique consiste à effectuer les vérifications de sécurité ici et à refuser la demande si nécessaire. ret, msg,status,redirect
  • ret (boolĂ©en) : true si pas d'erreur ou sinon false
  • msg (chaĂ®ne) : message de rĂ©ussite ou d'erreur
  • status (nombre) : interrompt le processus en cours et renvoie l'Ă©tat HTTP
  • redirect (URL) : si dĂ©fini, redirigera vers l'URL
donnée
log log_by_lua Appelé lorsqu'une requête est terminée (et avant qu'elle ne soit consignée dans les journaux d'accès). Le cas d'utilisation typique est de créer des statistiques ou de calculer des compteurs par exemple. ret, msg
  • ret (boolĂ©en) : true s'il n'y a pas d'erreur ou sinon false
  • msg (chaĂ®ne) : message de rĂ©ussite ou d'erreur
log_default log_by_lua Identique mais log uniquement appelé sur le serveur par défaut. ret, msg
  • ret (boolĂ©en) : true s'il n'y a pas d'erreur ou sinon false
  • msg (chaĂ®ne) : message de rĂ©ussite ou d'erreur
preread preread_by_lua Similaire Ă  la access fonction mais pour le mode flux. ret, msg,status
  • ret (boolĂ©en) : true s'il n'y a pas d'erreur ou sinon false
  • msg (chaĂ®ne) : message de rĂ©ussite ou d'erreur
  • status (nombre) : interrompre le processus en cours et renvoyer l'Ă©tat
log_stream log_by_lua Similaire Ă  la log fonction mais pour le mode flux. ret, msg
  • ret (boolĂ©en) : true s'il n'y a pas d'erreur ou sinon false
  • msg (chaĂ®ne) : message de rĂ©ussite ou d'erreur

Bibliothèques

Toutes les directives du module LUA NGINX sont disponibles et le module LUA de flux NGINX. En plus de cela, vous pouvez utiliser les bibliothèques LUA incluses dans BunkerWeb : voir ce script pour la liste complète.

Si vous avez besoin de bibliothèques supplémentaires, vous pouvez les mettre dans le dossier racine du plugin et y accéder en les préfixant avec votre ID de plugin. Voici un exemple de fichier nommé mylibrary.lua :

local _M = {}

_M.dummy = function ()
    return "dummy"
end

return _M

Et voici comment vous pouvez l'utiliser Ă  partir du fichier myplugin.lua :

local mylibrary = require "myplugin.mylibrary"

...

mylibrary.dummy()

...

Aides

Certains modules d'aide fournissent des aides utiles courantes :

  • self.variables : permet d'accĂ©der et de stocker les attributs des plugins
  • self.logger : journaux d'impression
  • bunkerweb.utils : diverses fonctions utiles
  • bunkerweb.datastore : accĂ©der aux donnĂ©es globales partagĂ©es sur une instance (magasin de clĂ©s/valeurs)
  • bunkerweb.clusterstore : accĂ©der Ă  un magasin de donnĂ©es Redis partagĂ© entre les instances de BunkerWeb (magasin clĂ©/valeur)

Pour accéder aux fonctions, il faut d'abord demander les modules :

local utils       = require "bunkerweb.utils"
local datastore   = require "bunkerweb.datastore"
local clustestore = require "bunkerweb.clustertore"

Récupérer une valeur de réglage :

local myvar = self.variables["DUMMY_SETTING"]
if not myvar then
    self.logger:log(ngx.ERR, "can't retrieve setting DUMMY_SETTING")
else
    self.logger:log(ngx.NOTICE, "DUMMY_SETTING = " .. value)
end

Stockez quelque chose dans le cache local :

local ok, err = self.datastore:set("plugin_myplugin_something", "somevalue")
if not ok then
    self.logger:log(ngx.ERR, "can't save plugin_myplugin_something into datastore : " .. err)
else
    self.logger:log(ngx.NOTICE, "successfully saved plugin_myplugin_something into datastore")
end

Vérifiez si une adresse IP est globale :

local ret, err = utils.ip_is_global(ngx.ctx.bw.remote_addr)
if ret == nil then
    self.logger:log(ngx.ERR, "error while checking if IP " .. ngx.ctx.bw.remote_addr .. " is global or not : " .. err)
elseif not ret then
    self.logger:log(ngx.NOTICE, "IP " .. ngx.ctx.bw.remote_addr .. " is not global")
else
    self.logger:log(ngx.NOTICE, "IP " .. ngx.ctx.bw.remote_addr .. " is global")
end

Plus d'exemples

Si vous souhaitez voir la liste complète des fonctions disponibles, vous pouvez consulter les fichiers présents dans le répertoire lua du dépôt.

Emplois

BunkerWeb utilise un planificateur de tâches interne pour des tâches périodiques telles que le renouvellement des certificats avec certbot, le téléchargement de listes noires, le téléchargement de fichiers MMDB, ... Vous pouvez ajouter des tâches de votre choix en les plaçant dans un sous-dossier nommé tâches et en les répertoriant dans le fichier de métadonnées plugin.json. N'oubliez pas d'ajouter les permissions d'exécution pour tout le monde afin d'éviter tout problème lorsqu'un utilisateur clone et installe votre plugin.

Page du plugin

Tout ce qui concerne l'interface utilisateur web se trouve dans le sous-dossier ui comme nous l'avons vu dans la section précédente sur la structure..

Conditions préalables

Lorsque vous souhaitez créer une page de plugin, vous avez besoin de deux fichiers :

  • template.html qui seront accessibles avec un GET /plugins/<plugin_id>.

  • actions.py oĂą vous pouvez ajouter des scripts et de la logique avec un POST /plugins/<plugin_id>. Notez que ce fichier a besoin d'une fonction portant le mĂŞme nom que le plugin pour fonctionner. Ce fichier est nĂ©cessaire mĂŞme si la fonction est vide.

Exemple de base

Modèle Jinja 2

Le fichier template.html est un modèle Jinja2, veuillez vous référer à la documentation Jinja2 si nécessaire.

Nous pouvons mettre de côté le ** fichier actions.py et commencer à utiliser uniquement le modèle dans une situation GET**. Le modèle peut accéder au contexte de l'application et aux libs, ce qui vous permet d'utiliser les utilitaires Jinja, request ou flask.

Par exemple, vous pouvez obtenir les arguments de requête dans votre modèle comme ceci :

<p>request args : {{ request.args.get() }}.</p>

Actions.py

Jeton CSRF

Please note that every form submission is protected via a CSRF token, you will need to include the following snippet into your forms :

<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />

Vous pouvez alimenter votre page de plugin avec des scripts supplémentaires avec le ** fichier actions.py lors de l 'envoi d'un POST /plugins/<plugin_id>**.

Vous avez deux fonctions par défaut dans actions.py :

pre_render fonction

Cela vous permet de récupérer les données lorsque vous OBTENEZ le modèle, et d'utiliser les données avec la variable pre_render disponible dans Jinja pour afficher le contenu de manière plus dynamique.

def pre_render(**kwargs)
  return <pre_render_data>

BunkerWeb vous enverra ce type de réponse :

return jsonify({"status": "ok|ko", "code" : XXX, "data": <pre_render_data>}), 200

Fonction <plugin_id>

Cela vous permet de récupérer des données lorsque vous effectuez un POST à partir du point de terminaison du modèle, qui doit être utilisé dans AJAX.

def myplugin(**kwargs)
  return <plugin_id_data>

BunkerWeb vous enverra ce type de réponse :

return jsonify({"message": "ok", "data": <plugin_id_data>}), 200

Ce à quoi vous pouvez accéder à partir de action.py

Voici les arguments qui sont passés et auxquels on accède sur action.py fonctions :

function(app=app, args=request.args.to_dict() or request.json or None)

Bibliothèques Python Disponibles

L'interface utilisateur Web de BunkerWeb inclut un ensemble de bibliothèques Python préinstallées que vous pouvez utiliser dans le fichier actions.py de votre plugin ou d'autres scripts liés à l'interface utilisateur. Elles sont disponibles prêtes à l'emploi sans nécessiter d'installations supplémentaires.

Voici la liste complète des bibliothèques incluses :

  • bcrypt - Bibliothèque de hachage de mots de passe
  • biscuit-python - Jetons d'authentification Biscuit
  • certbot - Client ACME pour Let's Encrypt
  • Flask - Framework web
  • Flask-Login - Gestion des sessions utilisateur
  • Flask-Session[cachelib] - Stockage des sessions cĂ´tĂ© serveur
  • Flask-WTF - Gestion des formulaires et protection CSRF
  • gunicorn[gthread] - Serveur HTTP WSGI
  • pillow - Traitement d'images
  • psutil - Utilitaires système et processus
  • python_dateutil - Utilitaires de date et heure
  • qrcode - GĂ©nĂ©ration de codes QR
  • regex - Expressions rĂ©gulières avancĂ©es
  • urllib3 - Client HTTP
  • user_agents - Analyse des agents utilisateur

Utilisation des Bibliothèques dans Votre Plugin

Pour importer et utiliser ces bibliothèques dans votre fichier actions.py, utilisez simplement l'instruction import standard de Python. Par exemple :

from flask import request
import bcrypt
Bibliothèques Externes

Si vous avez besoin de bibliothèques non listées ci-dessus, installez-les dans le dossier ui de votre plugin et importez-les en utilisant la directive import classique. Assurez-vous de la compatibilité avec l'environnement existant pour éviter les conflits.

Quelques exemples

  • RĂ©cupĂ©rer les donnĂ©es soumises par le formulaire
from flask import request

def myplugin(**kwargs) :
    my_form_value = request.form["my_form_input"]
  return my_form_value
  • AccĂ©der Ă  la configuration de l'application

action.py

from flask import request

def pre_render(**kwargs) :
    config = kwargs['app'].config["CONFIG"].get_config(methods=False)
  return config

modèle

<!-- metadata + config -->
<div>{{ pre_render }}</div>

Hooks.py

Cette documentation décrit les hooks de cycle de vie utilisés pour gérer les différentes étapes d'une demande au sein de l'application. Chaque crochet est associé à une phase spécifique.

Ces hooks sont exécutés avant le traitement d'une requête entrante. Ils sont généralement utilisés pour des tâches de prétraitement telles que l'authentification, la validation ou la journalisation.

Si le hook retourne un objet de réponse, Flask ignorera le traitement de la requête et retournera la réponse directement. Cela peut être utile pour court-circuiter le pipeline de traitement des requêtes.

Exemple :

from flask import request, Response

def before_request():
    print("Before-request: Validating request...", flush=True)
    # Perform authentication, validation, or logging here
    if not is_valid_request(request): # We are in the app context
        return Response("Invalid request!", status=400)

def is_valid_request(request):
    # Dummy validation logic
    return "user" in request

Ces hooks qui s'exécutent après le traitement de la requête. Ils sont idéaux pour les tâches de post-traitement telles que le nettoyage, la journalisation supplémentaire ou la modification de la réponse avant qu'elle ne soit renvoyée.

Ils reçoivent l'objet de réponse en tant qu'argument et peuvent le modifier avant de le retourner. Le premier crochet after_request qui retourne une réponse sera utilisé comme réponse finale.

Exemple :

from flask import request

def after_request(response):
    print("After-request: Logging response...", flush=True)
    # Perform logging, cleanup, or response modifications here
    log_response(response)
    return response

def log_response(response):
    # Dummy logging logic
    print("Response logged:", response, flush=True)

Ces hooks sont invoqués lorsque le contexte de la requête est en cours de destruction. Ces hooks sont utilisés pour libérer des ressources ou gérer les erreurs qui se sont produites au cours du cycle de vie de la demande.

Exemple:

def teardown_request(error=None):
    print("Teardown-request: Cleaning up resources...", flush=True)
    # Perform cleanup, release resources, or handle errors here
    if error:
        handle_error(error)
    cleanup_resources()

def handle_error(error):
    # Dummy error handling logic
    print("Error encountered:", error, flush=True)

def cleanup_resources():
    # Dummy resource cleanup logic
    print("Resources have been cleaned up.", flush=True)

Ces points d'entrée sont utilisés pour injecter du contexte supplémentaire dans des modèles ou des vues. Ils enrichissent le contexte d'exécution en transmettant des données courantes (telles que des informations sur l'utilisateur ou des paramètres de configuration) aux modèles.

Si un processeur de contexte retourne un dictionnaire, les clés et les valeurs seront ajoutées au contexte pour tous les modèles. Cela permet de partager des données entre plusieurs vues ou modèles.

Exemple :

def context_processor() -> dict:
    print("Context-processor: Injecting context data...", flush=True)
    # Return a dictionary containing context data for templates/views
    return {
        "current_user": "John Doe",
        "app_version": "1.0.0",
        "feature_flags": {"new_ui": True}
    }

Cette conception de crochet de cycle de vie offre une approche modulaire et systématique de la gestion de divers aspects du cycle de vie d'une demande :

  • ModularitĂ© : Chaque crochet est responsable d'une phase distincte, ce qui permet de sĂ©parer les prĂ©occupations.
  • MaintenabilitĂ© : les dĂ©veloppeurs peuvent facilement ajouter, modifier ou supprimer des implĂ©mentations de hooks sans affecter les autres parties du cycle de vie de la requĂŞte.
  • ExtensibilitĂ© : le cadre est flexible, ce qui permet d'ajouter des crochets ou d'apporter des amĂ©liorations Ă  mesure que les exigences de l'application Ă©voluent.

En définissant clairement les responsabilités de chaque hook et leurs préfixes de journalisation associés, le système garantit que chaque étape du traitement des requêtes est transparente et gérable.

Plans

Dans Flask, les blueprints servent de moyen modulaire pour organiser les composants associés, tels que les vues, les modèles et les fichiers statiques, au sein de votre application. Ils vous permettent de regrouper logiquement les fonctionnalités et peuvent être utilisés pour créer de nouvelles sections de votre application ou remplacer celles existantes.

Création d'un Blueprint

Pour définir un blueprint, vous devez créer une instance de la Blueprint classe, en spécifiant son nom et son chemin d'importation. Vous définissez ensuite les itinéraires et les vues associés à ce blueprint.

Exemple : Définition d'un nouveau blueprint

from os.path import dirname
from flask import Blueprint, render_template

# Define the blueprint
my_blueprint = Blueprint('my_blueprint', __name__, template_folder=dirname(__file__) + '/templates') # The template_folder is set to avoid conflicts with the original blueprint

# Define a route within the blueprint
@my_blueprint.route('/my_blueprint')
def my_blueprint_page():
    return render_template('my_blueprint.html')

Dans cet exemple, un blueprint nommé my_blueprint est créé et un itinéraire /my_blueprint est défini à l'intérieur de celui-ci.

Remplacement d'un blueprint existant

Les Blueprints peuvent également remplacer les Blueprints existants pour modifier ou étendre les fonctionnalités. Pour ce faire, assurez-vous que le nouveau blueprint porte le même nom que celui que vous remplacez et enregistrez-le après l'original.

Exemple : Remplacement d'un blueprint existant

from os.path import dirname
from flask import Flask, Blueprint

# Original blueprint
instances = Blueprint('instances', __name__, template_folder=dirname(__file__) + '/templates') # The template_folder is set to avoid conflicts with the original blueprint

@instances.route('/instances')
def override_instances():
    return "My new instances page"

Dans ce scénario, l'accès à l'URL /instances affiche "Ma nouvelle page d'instances", car le instances blueprint, enregistré en dernier, remplace le blueprint d'origine instances .

À propos de la dérogation

Soyez prudent lorsque vous remplacez des blueprints existants, car cela peut avoir un impact sur le comportement de l'application. Assurez-vous que les modifications sont conformes aux exigences de l'application et n'introduisent pas d'effets secondaires inattendus.

Toutes les routes existantes seront supprimées du blueprint original, vous devrez donc les réimplémenter si nécessaire.

Conventions de nommage

Important

Assurez-vous que le nom du blueprint correspond au nom de la variable de blueprint, sinon il ne sera pas considéré comme un blueprint valide et ne sera pas enregistré.

Pour plus de cohérence et de clarté, il est conseillé de suivre les conventions de nommage suivantes :

  • Noms de blueprint: utilisez des noms courts et tout en minuscules. Des traits de soulignement peuvent ĂŞtre utilisĂ©s pour la lisibilitĂ©, par exemple, user_auth.

  • Noms de fichiers: faites correspondre le nom de fichier au nom du blueprint, en vous assurant qu'il est entièrement en minuscules avec des traits de soulignement si nĂ©cessaire, par exemple, user_auth.py.

Cette pratique s'aligne sur les conventions de nommage des modules de Python et permet de maintenir une structure de projet claire.

Exemple : Blueprint et nommage de fichier

plugin /
    ui / blueprints / user_auth.py
                      templates / user_auth.html

Dans cette structure, user_auth.py contient le user_auth blueprint et user_auth.html est le modèle associé, en respectant les conventions de nommage recommandées.