--- gitea: none include_toc: true --- # Construire une liste d'options Rougail permet de construite des options [Tiramisu](https://forge.cloud.silique.fr/gnunux/tiramisu) à partir de dictionnaire écrit en YAML. Le principal avantage étant que l'écrire des options est beaucoup plus simple. Une fois chargé, on retrouve toute la puissance de Tiramisu dans la gestion de la configuration. Avant de commencer, il faut connaitre les spécificités du format de fichier YAML, des notions de [Jinja](https://jinja.palletsprojects.com/) et de [Tiramisu](https://forge.cloud.silique.fr/gnunux/tiramisu). # La configuration du proxy type Mozilla Firefox L'objectif de ce première exemple est de reproduire cette page de paramètres de Mozilla Firefox : ![Firefox parameters page](firefox.png "Firefox parameters page") Les variables vont être créées dans plusieurs fichiers dans un but didactique. Bien évidement toutes les variables pourront être mise dans le même fichier. ## La famille proxy Nous allons classer toutes ces variables dans une famille. Cette famille s'appelera proxy. Créons le premier fichier dict/00-proxy.yml ```yml --- version: '1.0' proxy: description: Configure Proxy Access to the Internet type: family ``` Le type de la famille est ici précisé parce qu'on n'a pas de variable ou de famille à l'intérieur de cette famille. Le moteur pensera alors que c'est une simple variable. ## Le type de proxy Il est possible de définir plusieurs modes de configuration du proxy (de "pas de proxy" à "la configuration d'un fichier de configuration automatique"). Nous allons donc créer une première variable dans cette famille dont la description est "Proxy mode". Créons le deuxième fichier dict/01-proxy\_mode.yml ```yml --- version: '1.0' proxy: proxy_mode: description: Proxy mode type: choice choices: - No proxy - Auto-detect proxy settings for this network - Use system proxy settings - Manual proxy configuration - Automatic proxy configuration URL default: No proxy mandatory: true ``` Cette variable nécessite une valeur (la valeur `None` n'est pas acceptable), elle est donc obligatoire (`mandatory`). Si l'utilisateur ne précise pas de valeurs, la valeur de cette variable sera "No proxy" (`default`). La variable est à choix (`type`: choice) donc la liste des valeurs disponible est contrainte (`choices`), seul les valeurs suivantes sont autorisés : - No proxy - Auto-detect proxy settings for this network - Use system proxy settings - Manual proxy configuration - Automatic proxy configuration URL Testons nos deux premiers dictionnaires : ```python >>> from rougail import Rougail, RougailConfig >>> from pprint import pprint >>> RougailConfig['dictionaries_dir'] = ['dict'] >>> rougail = Rougail() >>> config = rougail.get_config() >>> config.property.read_only() >>> pprint(config.value.dict(), sort_dicts=False) {'rougail.proxy.proxy_mode': 'No proxy'} ``` ## Le mode manuel Si l'utilisateur choisi le mode de proxy "Manual proxy configuration", on veut voir apparaitre (`disabled`) une nouvelle sous-famille appelé manual. Créons le fichier dict/02-proxy\_manual.yml : ```yml --- version: '1.0' proxy: manual: description: Manual proxy configuration type: family disabled: type: jinja jinja: | {% if rougail.proxy.proxy_mode != 'Manual proxy configuration' %} the mode proxy is not manual {% endif %} ``` ### La configuration du proxy HTTP Dans cette famille ajoutons une sous-famille `http_proxy` contenant les variables `address` et `port`. Créons le fichier dict/03-proxy\_manual\_http\_proxy.yml : ```yml --- version: '1.0' proxy: manual: http_proxy: description: HTTP Proxy address: description: HTTP address type: domainname mandatory: true port: description: HTTP Port type: port default: '8080' ``` Les deux variables ont des types particuliers (`domainname` ou `port`) pour valider les valeurs configurer par l'utilisateur. Pas la peine de précisé le type de `http_proxy` parce qu'on a déclaré les sous-variables dans ce fichier. ### Dupliquer la configuration HTTP vers HTTPS On veux proposer à l'utilisateur la possiblité de renseigner le même proxy pour les requêtes HTTPS. Créons le fichier dict/04-proxy\_manual\_http\_use\_for\_https.yml : ```yml version: '1.0' proxy: manual: use_for_https: description: Also use this proxy for HTTPS type: boolean ``` Cette variable est de type `boolean`. Sa valeur par défaut est `True`. ### La configuration du proxy HTTPS Ajoutons une nouvelle sous-famille `ssl_proxy` avec les variables `address` et `port`. Créons le fichier dict/05-proxy\_manual\_ssl\_proxy.yml : ```yml version: '1.0' proxy: manual: ssl_proxy: description: HTTPS Proxy hidden: type: variable variable: rougail.proxy.manual.use_for_https address: description: HTTPS address type: domainname default: type: jinja jinja: | {% if rougail.proxy.manual.use_for_https %} {{ rougail.proxy.manual.http_proxy.address }} {% endif %} mandatory: true port: description: HTTPS Port type: port default: type: jinja jinja: | {% if rougail.proxy.manual.use_for_https %} {{ rougail.proxy.manual.http_proxy.port }} {% endif %} mandatory: true ``` On vient de recréer un variable avec le nom "address". Ce n'est pas un problème, puisqu'elle est dans une autre famille. Suivant la valeur de la variable `rougail.proxy.mandatory.use_for_https` (sans passé par une fonction Jinja ce coup-ci) cette famille apparaitra ou disparaitra (`hidden`). Contrairement à tout à l'heure, la famille n'est pas désactivé (`disabled`) parce que les variables devront rester accessible en mode lecture seule. Testons ce point précis : ```python >>> from rougail import Rougail, RougailConfig >>> from pprint import pprint >>> RougailConfig['dictionaries_dir'] = ['dict'] >>> rougail = Rougail() >>> config = rougail.get_config() >>> config.property.read_only() >>> pprint(config.value.dict(), sort_dicts=False) {'rougail.proxy.proxy_mode': 'No proxy'} >>> config.property.read_write() >>> config.option('rougail.proxy.proxy_mode').value.set('Manual proxy configuration') >>> config.option('rougail.proxy.manual.http_proxy.address').value.set('proxy.example') >>> pprint(config.value.dict(), sort_dicts=False) {'rougail.proxy.proxy_mode': 'Manual proxy configuration', 'rougail.proxy.manual.http_proxy.address': 'proxy.example', 'rougail.proxy.manual.http_proxy.port': '8080', 'rougail.proxy.manual.use_for_https': True} >>> config.property.read_only() >>> pprint(config.value.dict(), sort_dicts=False) {'rougail.proxy.proxy_mode': 'Manual proxy configuration', 'rougail.proxy.manual.http_proxy.address': 'proxy.example', 'rougail.proxy.manual.http_proxy.port': '8080', 'rougail.proxy.manual.use_for_https': True, 'rougail.proxy.manual.ssl_proxy.address': 'proxy.example', 'rougail.proxy.manual.ssl_proxy.port': '8080'} ``` Alors que la famille `rougail.proxy.manual` n'était pas présente lors que `rougail.proxy.proxy_mode` est à `No proxy`, la famille `rougail.proxy.manual.ssl_proxy` devient visible en mode lecture seule (et uniquement en mode lecture seule) si `rougail.proxy.manual.use_for_https` est à 'True'. On voit bien également que les valeurs des variables de `rougail.proxy.manual.http_proxy` on bien été copié dans `rougail.proxy.manual.ssl_proxy`. Ces valeurs ne seront copié que si `use_for_https` est à "True" comme précisé dans les valeurs par défaut des variables. De plus, si l'utilisateur modifie la valeur de la variable `rougail.proxy.manual.ssl_proxy.address` mais qu'il cache par la suite cette variable, la valeur de cette variable reviens à la valeur par défaut : ```python >>> config.property.read_write() >>> config.option('rougail.proxy.manual.use_for_https').value.set(False) >>> config.option('rougail.proxy.manual.ssl_proxy.address').value.set('other.proxy.example') >>> pprint(config.value.dict(), sort_dicts=False) {'rougail.proxy.proxy_mode': 'Manual proxy configuration', 'rougail.proxy.manual.http_proxy.address': 'proxy.example', 'rougail.proxy.manual.http_proxy.port': '8080', 'rougail.proxy.manual.use_for_https': False, 'rougail.proxy.manual.ssl_proxy.address': 'other.proxy.example', 'rougail.proxy.manual.ssl_proxy.port': '8080'} >>> config.option('rougail.proxy.manual.use_for_https').value.set(False) >>> config.property.read_only() >>> pprint(config.value.dict(), sort_dicts=False) {'rougail.proxy.proxy_mode': 'Manual proxy configuration', 'rougail.proxy.manual.http_proxy.address': 'proxy.example', 'rougail.proxy.manual.http_proxy.port': '8080', 'rougail.proxy.manual.use_for_https': False, 'rougail.proxy.manual.ssl_proxy.address': 'proxy.example', 'rougail.proxy.manual.ssl_proxy.port': '8080'} ``` ### La configuration du proxy SOCKS Ajoutons une nouvelle sous-famille `socks_proxy` avec les variables `address`, `port` et `version`. Créons le fichier dict/06-proxy\_manual\_socks\_proxy.yml : ```yml version: '1.0' proxy: manual: socks_proxy: description: SOCKS Proxy address: description: SOCKS Address type: domainname port: description: SOCKS Port type: port version: description: SOCKS host version used by proxy type: choice choices: - v4 - v5 default: v5 ``` ## Le mode détection automatique Ajoutons une nouvelle sous-variable `auto`. Créons le fichier dict/07-proxy\_auto.yml : ```yml version: '1.0' proxy: auto: type: web_address description: Automatic proxy configuration URL mandatory: true disabled: type: jinja jinja: | {% if rougail.proxy.proxy_mode != 'Automatic proxy configuration URL' %} the proxy mode is not automatic {% endif %} ``` Le type `web_address` impose une valeur qui commence par http:// ou https://. ## Les exceptions au proxy Enfin ajoutons une variable contenant les exceptions au proxy. Pour cela créons le fichier dict/07-proxy\_no\_proxy.yml : ```yml version: '1.0' proxy: no_proxy: description: Address for which proxy will be desactivated multi: true disabled: type: jinja jinja: | {% if rougail.proxy.proxy_mode == 'No proxy' %} proxy mode is no proxy {% endif %} validators: - type: jinja jinja: '{{ rougail.no_proxy | valid_no_proxy }}' ``` Il peut y avoir plusieurs exceptions au proxy au proxy, la variable est donc `multi`. Cette varible n'est pas accessible uniquement si aucun proxy n'est défini. Enfin, on veut valider le contenu de la variable. Il n'y a pas de type correspond dans Rougail, donc on fait la fonction de validation suivante dans le fichier `functions.py` : ```python from tiramisu import DomainnameOption def valid_no_proxy(value: str): try: DomainnameOption('', '', value, allow_ip=True, allow_cidr_network=True, allow_without_dot=True, allow_startswith_dot=True, ) except ValueError as err: return err ``` Pour tester : ```python from rougail import Rougail, RougailConfig RougailConfig['functions_file'] = 'functions.py' from pprint import pprint RougailConfig['dictionaries_dir'] = ['dict'] rougail = Rougail() config = rougail.get_config() config.property.read_only() pprint(config.value.dict(), sort_dicts=False) ``` ## La demande d'authentification Rien de particulier à la création de la demande d'authentification. Pour cela créons le fichier dict/08-proxy\_prompt\_authentication.yml : ```yml version: '1.0' proxy: prompt_authentication: description: Prompt for authentication if password is saved type: boolean default: true disabled: type: jinja jinja: | {% if rougail.proxy.proxy_mode == 'No proxy' %} proxy mode is no proxy {% endif %} ``` ## Le DNS du proxy SOCKS v5 La question sur le DNS pour le proxy SOCKS v5 n'apparait que si le proxy est configuré et que la version du proxy SOCKS sélectionné est bien la "v5". Créons le fichier dict.09-proxy\_proxy\_dns\_socks5.yml : ```yml version: '1.0' proxy: proxy_dns_socks5: description: Use proxy DNS when using SOCKS v5 type: boolean default: false disabled: type: jinja params: socks_version: type: variable variable: rougail.proxy.manual.socks_proxy.version propertyerror: false jinja: | {% if rougail.proxy.proxy_mode == 'No proxy' %} the proxy mode is no proxy {% elif socks_version is undefined or socks_version == 'v4' %} socks version is v4 {% else %} false {% endif %} ``` La difficulté ici c'est que la variable `rougail.proxy.manual.socks_proxy.version` peut être désactivé (et donc non utilisable dans un calcul). Dans ce cas, nous allons ajouter un paramètre (ici appelé `socks_version`) qui contiendra, s'il n'y a pas d'erreur de propriété, la valeur de la variable. Sinon le paramètre ne sera pas passé au template Jinja. C'est pourquoi il faut tester dans le template Jinja si la variable `socks_version` existe bien. ## Le DNS à travers HTTPS Enfin nous allons configurer le DNS à travers HTTPS dans le fichier 10-proxy\_dns\_over\_https.yml : ```yml version: '1.0' proxy: dns_over_https: description: DNS over HTTPS enable_dns_over_https: description: Enable DNS over HTTPS type: boolean default: false provider: description: Use Provider type: choice choices: - Cloudflare - NextDNS - Custom default: Cloudflare disabled: type: jinja jinja: | {% if not rougail.proxy.dns_over_https.enable_dns_over_https %} Enable DNS over HTTPS is False {% endif %} custom_dns_url: description: Custom DNS URL type: web_address mandatory: true disabled: type: jinja params: provider: type: variable variable: rougail.proxy.dns_over_https.provider propertyerror: false jinja: | {% if provider is defined or provider != 'Custom' %} provider is not custom {% endif %} validators: - type: jinja jinja: | {% if rougail.proxy.dns_over_https.custom_dns_url.startswith('http://') %} only https is allowed {% endif %} ``` La seule particularitée ici est qu'on a ajouté une validation supplémentaire à la variable `custom_dns_url`. Seul une adresse commençant par https:// est autorisé (pas http://). # La configuration du proxy type FoxyProxy Voici maintenant l'intégration d'une partie du plugin Firefox FoxyProxy. L'idée est d'avoir un espace de nom spécifique à FoxyProxy et de retrouver dedans une partie du paramétrage qu'on aura fait dans l'espace de nom principal. Voici à quoi ressemble la page : ![FoxyProxy parameters page](foxyproxy.png "FoxyProxy parameters page") Il est possible, dans ce plugin, de spécifié un nombre illimité de proxy. Notre famille "proxy" ne sera plus de type "family" comme tout a l'heure mais du type "leadership". Voici le contenu complet de la configuration du proxy type FoxyProxy à mettre dans le fichier foxyproxy/00-base.yml : ```yml --- version: '1.0' proxy: _type: leadership title: description: Title or Description multi: true color: description: Color mandatory: true type: type: choice choices: - HTTP - HTTPS/SSL - SOCKS5 - SOCKS4 - PAC URL - WPAD - System (use system settings) - Direct (no proxy) default: Direct (no proxy) address: description: IP address, DNS name, server name multi: true mandatory: true disabled: type: jinja jinja: | {% if foxyproxy.proxy.type not in ['HTTP', 'HTTPS/SSL', 'SOCKS5', 'SOCKS4'] %} proxy does not need address {% endif %} port: description: Port type: port mandatory: true default: type: jinja params: firefox_port: type: variable variable: rougail.proxy.manual.http_proxy.port propertyerror: false jinja: | {% if firefox_port is not undefined %} {{ firefox_port }} {% endif %} disabled: type: jinja jinja: | {% if foxyproxy.proxy.type not in ['HTTP', 'HTTPS/SSL', 'SOCKS5', 'SOCKS4'] %} proxy does not need port {% endif %} username: description: Username type: unix_user mandatory: type: jinja jinja: | {% if foxyproxy.proxy.password %} username is mandatory {% endif %} disabled: type: jinja jinja: | {% if foxyproxy.proxy.type not ['HTTP', 'HTTPS/SSL', 'SOCKS5', 'SOCKS4'] %} proxy does not need username {% endif %} password: description: Password type: secret disabled: type: jinja jinja: | {% if foxyproxy.proxy.type not in ['HTTP', 'HTTPS/SSL', 'SOCKS5', 'SOCKS4'] %} proxy does not need password {% endif %} url: type: web_address disabled: type: jinja jinja: | {% if foxyproxy.proxy.type not in ['PAC URL', 'WPAD'] %} proxy does not need url {% endif %} ``` Quelques remarques : - dans la famille meneuse `foxyproxy.proxy` il y a une variable "type", donc pour spécifier le type on utilise l'attribut `_type` - une variable suiveuse peut également être multiple (ce qui est le cas de `foxyproxy.proxy.address` - `foxyproxy.proxy.username` devient obligatoire si `foxyproxy.proxy.password` est spécifié, en effet un mot de passe sans nom d'utilisateur n'a pas de sens Testons : ```yml >>> from rougail import Rougail, RougailConfig >>> from pprint import pprint >>> RougailConfig['dictionaries_dir'] = ['dict'] >>> RougailConfig['extra_dictionaries']['foxyproxy'] = ['foxyproxy/'] >>> RougailConfig['functions_file'] = 'functions.py' >>> rougail = Rougail() >>> config = rougail.get_config() >>> config.option('rougail.proxy.proxy_mode').value.set('Manual proxy configuration') >>> config.option('rougail.proxy.manual.http_proxy.address').value.set('proxy.example') >>> config.option('foxyproxy.proxy.title').value.set(['MyProxy']) >>> config.option('foxyproxy.proxy.type', 0).value.set('HTTP') >>> config.option('foxyproxy.proxy.color', 0).value.set('#00000') >>> config.property.read_only() >>> pprint(config.value.dict(), sort_dicts=False) {'rougail.proxy.proxy_mode': 'Manual proxy configuration', 'rougail.proxy.manual.http_proxy.address': 'proxy.example', 'rougail.proxy.manual.http_proxy.port': '8080', 'rougail.proxy.manual.use_for_https': True, 'rougail.proxy.manual.ssl_proxy.address': 'proxy.example', 'rougail.proxy.manual.ssl_proxy.port': '8080', 'rougail.proxy.manual.socks_proxy.address': None, 'rougail.proxy.manual.socks_proxy.port': None, 'rougail.proxy.manual.socks_proxy.version': 'v5', 'rougail.proxy.no_proxy': [], 'rougail.proxy.prompt_authentication': True, 'rougail.proxy.proxy_dns_socks5': False, 'rougail.proxy.dns_over_https.enable_dns_over_https': False, 'foxyproxy.proxy.title': [{'foxyproxy.proxy.title': 'MyProxy', 'foxyproxy.proxy.color': '#00000', 'foxyproxy.proxy.type': 'HTTP', 'foxyproxy.proxy.address': ['proxy.example'], 'foxyproxy.proxy.port': '8080', 'foxyproxy.proxy.username': None, 'foxyproxy.proxy.password': None}]} ``` Concernant `foxyproxy.proxy.username` et `foxyproxy.proxy.password`, pour éviter une boucle infini, on ne peut pas dire que `foxyproxy.proxy.password` est désactivé si `foxyproxy.proxy.username` est vide (ce qui pourrait être une option intéressante). Dans ce cas il ne faut pas tester la valeur obligatoire. Si vous préférez cette option, voici un second dictionnaire extra "foxyproxy/01-redefine.xml" qui va redéfinir le comportement uniquement des variable `foxyproxy.proxy.username` et `foxyproxy.proxy.password` : ```yml --- foxyproxy: username: redefine: true mandatory: false password: hidden: type: jinja jinja: | {% if not rougail.foxyproxy.username %} no username defined {% endif %} ```