Skip to content

33. Esercizio pratico: versione 13

La versione 13 modifica la versione 12 nei seguenti punti:

  • vengono ristrutturate alcune parti del codice (refactoring);
  • la gestione della sessione viene effettuata in modo diverso tramite il modulo [flask_session];
  • si utilizzano password crittografate nel file di configurazione;

La versione 13 si ottiene inizialmente copiando la versione 12:

Image

Image

  • in [1], la configurazione verrà rifattorizzata. In particolare, verrà rimossa dalla cartella [flask];
  • in [2], lo script principale verrà rifattorizzato. Verrà inoltre rimosso dalla cartella [flask];
  • in [3], la configurazione verrà suddivisa in più file;
  • in [4]: lo script principale [main] verrà semplificato spostando parte del codice in altri file;
  • in [5], il controller di autenticazione verrà modificato poiché d'ora in poi le password degli utenti saranno crittografate;
  • in [6], il controller principale integrerà il codice precedentemente presente nello script principale [main];

33.1. Rifattorizzazione della configurazione dell’applicazione

Image

Vengono visualizzati tre nuovi file di configurazione:

  • [mvc]: per configurare l'architettura MVC dell'applicazione;
  • [parameters]: che raggrupperà tutte le costanti dell'applicazione;
  • [syspath]: che configura il Python Path dell'applicazione;

Il file [syspath.py] è il seguente:


def configure(config: dict) -> dict:
    import os

    # cartella di questo file
    script_dir = os.path.dirname(os.path.abspath(__file__))

    # percorso radice
    root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"

    # dipendenze
    absolute_dependencies = [
        # cartelle del progetto
        # BaseEntity, MyException
        f"{root_dir}/classes/02/entities",
        # InterfaceImpôtsDao, InterfaceImpôtsMétier, InterfaceImpôtsUi
        f"{root_dir}/impots/v04/interfaces",
        # AbstractImpôtsdao, ImpôtsConsole, ImpôtsMétier
        f"{root_dir}/impots/v04/services",
        # ImpotsDaoWithAdminDataInDatabase
        f"{root_dir}/impots/v05/services",
        # AdminData, ImpôtsError, TaxPayer
        f"{root_dir}/impots/v04/entities",
        # Costanti, intervalli
        f"{root_dir}/impots/v05/entities",
        # Logger, SendAdminMail
        f"{root_dir}/impots/http-servers/02/utilities",
        # cartella dello script principale
        script_dir,
        # configurazioni [database, layers, parameters, controllers, views]
        f"{script_dir}/../configs",
        # controllori
        f"{script_dir}/../controllers",
        # risposte HTTP
        f"{script_dir}/../responses",
        # modelli delle viste
        f"{script_dir}/../models_for_views",
    ]

    # si imposta il syspath
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # si rende la configurazione
    return {
        "root_dir": root_dir,
        "script_dir": script_dir
    }
  • lo script [syspath] serve a configurare il Python Path dell’applicazione (righe 40-41);
  • fornisce due informazioni utili agli altri script di configurazione (righe 45-46);

Lo script [mvc] configura l’architettura MVC dell’applicazione web jSON / XML / HTML:


def configure(config: dict) -> dict:
    # configurazione dell'applicazione MVC

    # i controller
    from AfficherCalculImpotController import AfficherCalculImpotController
    from AuthentifierUtilisateurController import AuthentifierUtilisateurController
    from CalculerImpotController import CalculerImpotController
    from CalculerImpotsController import CalculerImpotsController
    from FinSessionController import FinSessionController
    from GetAdminDataController import GetAdminDataController
    from InitSessionController import InitSessionController
    from ListerSimulationsController import ListerSimulationsController
    from MainController import MainController
    from SupprimerSimulationController import SupprimerSimulationController
    # le risposte HTTP
    from HtmlResponse import HtmlResponse
    from JsonResponse import JsonResponse
    from XmlResponse import XmlResponse
    # i modelli delle viste
    from ModelForAuthentificationView import ModelForAuthentificationView
    from ModelForCalculImpotView import ModelForCalculImpotView
    from ModelForErreursView import ModelForErreursView
    from ModelForListeSimulationsView import ModelForListeSimulationsView

    # azioni consentite e relativi controllori
    controllers = {
        # inizializzazione di una sessione di calcolo
        "init-session": InitSessionController(),
        # autenticazione di un utente
        "authentifier-utilisateur": AuthentifierUtilisateurController(),
        # calcolo dell'imposta in modalità individuale
        "calculer-impot": CalculerImpotController(),
        # calcolo dell'imposta in modalità batch
        "calculer-impots": CalculerImpotsController(),
        # elenco delle simulazioni
        "lister-simulations": ListerSimulationsController(),
        # eliminazione di una simulazione
        "supprimer-simulation": SupprimerSimulationController(),
        # fine della sessione di calcolo
        "fin-session": FinSessionController(),
        # visualizzazione della schermata di calcolo dell'imposta
        "afficher-calcul-impot": AfficherCalculImpotController(),
        # recupero dei dati dall'amministrazione fiscale
        "get-admindata": GetAdminDataController(),
        # controller principale
        "main-controller": MainController()
    }
    # i diversi tipi di risposta (json, xml, html)
    responses = {
        "json": JsonResponse(),
        "html": HtmlResponse(),
        "xml": XmlResponse()
    }
    # le viste HTML e i relativi modelli dipendono dallo stato restituito dal controller
    views = [
        {
            # vista di autenticazione
            "états": [
                700,    # /init-session - operazione riuscita
                201,    # /autenticazione-utente - errore
            ],
            "view_name""views/vue-authentification.html",
            "model_for_view": ModelForAuthentificationView()
        },
        {
            # pagina di calcolo dell'imposta
            "états"[
                200,    # /autenticare-utente completato con successo
                300,    # /calcolo-imposta completato con successo
                301,    # /calcolo-imposta fallito
                800,    # /visualizza-calcolo-imposta (operazione riuscita)
            ],
            "view_name""views/vue-calcul-impot.html",
            "model_for_view": ModelForCalculImpotView()
        },
        {
            # visualizzazione dell'elenco delle simulazioni
            "états"[
                500,    # /elenco-simulazioni (operazione riuscita)
                600,    # /eliminare-simulazione (operazione riuscita)
            ],
            "view_name""views/vue-liste-simulations.html",
            "model_for_view": ModelForListeSimulationsView()
        },

    ]
    # visualizzazione degli errori imprevisti
    view_erreurs = {
        "view_name""views/vue-erreurs.html",
        "model_for_view": ModelForErreursView()
    }
    # reindirizzamenti
    redirections = [
        {
            "états": [
                400,  # /fine-sessione riuscita
            ],
            # reindirizzamento a URL
            "to""/init-session/html",
        }
    ]


    # si ripristina la configurazione MVC
    return {
        # controller
        "controllers": controllers,
        # risposte HTTP
        "responses": responses,
        # viste e modelli
        "views": views,
        # elenco dei reindirizzamenti
        "redirections": redirections,
        # pagina degli errori imprevisti
        "view_erreurs": view_erreurs
    }
  • righe 1-101: questo codice è noto;
  • righe 105-116: si restituisce la configurazione MVC dell’applicazione;

Lo script [parameters] raccoglie le costanti dell'applicazione:


def configure(config: dict) -> dict:
    # configurazione dell'applicazione

    # script_dir
    script_dir = config['syspath']['script_dir']

    # configurazione dell'applicazione
    parameters = {
        # utenti autorizzati a utilizzare l'applicazione
        "users"[
            {
                "login""admin",
                "password""$pbkdf2-sha256$29000$mPM.h3COkTIGYOzde68VIg$7LH5Q7rN/1hW.Xa.6rcmR6h52PntvVqd5.na7EtgQNw"
            }
        ],
        # file di log
        "logsFilename"f"{script_dir}/../data/logs/logs.txt",
        # configurazione del server SMTP
        "adminMail": {
            # server SMTP
            "smtp-server""localhost",
            # porta del server SMTP
            "smtp-port""25",
            # amministratore
            "from""guest@localhost.com",
            "to""guest@localhost.com",
            # oggetto dell'e-mail
            "subject""plantage du serveur de calcul d'impôts",
            # TLS impostato su True se il server SMTP richiede l'autenticazione, altrimenti su False
            "tls"False
        },
        # durata della pausa del thread in secondi
        "sleep_time"0,
        # server Redis
        "redis": {
            "host""127.0.0.1",
            "port"6379
        },
    }

    # si imposta la configurazione dell'applicazione
    return parameters
  • riga 13: d'ora in poi le password degli utenti saranno crittografate;
  • righe 34-38: la configurazione di un server [Redis] su cui torneremo;

Con questi nuovi file di configurazione, lo script [config] diventa il seguente:


def configure(config: dict) -> dict:
    # configurazione del syspath
    import syspath
    config['syspath'] = syspath.configure(config)

    # configurazione dell'applicazione
    import parameters
    config['parameters'] = parameters.configure(config)

    # configurazione del database
    import database
    config["database"] = database.configure(config)

    # istanziazione dei livelli dell'applicazione
    import layers
    config['layers'] = layers.configure(config)

    # configurazione MVC del livello [web]
    import mvc
    config['mvc'] = mvc.configure(config)

    # si restituisce la configurazione
    return config

33.2. Rifattorizzazione dello script principale [main]

Image

Lo script principale [main.py] si limita ad avviare il server:


# si attende un parametro mysql o pgres
import sys

syntaxe = f"{sys.argv[0]} mysql / pgres"
erreur = len(sys.argv) != 2
if not erreur:
    sgbd = sys.argv[1].lower()
    erreur = sgbd != "mysql" and sgbd != "pgres"
if erreur:
    print(f"syntaxe : {syntaxe}")
    sys.exit()

# si configura l'applicazione
import config
config = config.configure({'sgbd': sgbd})

# dipendenze
from SendAdminMail import SendAdminMail
from Logger import Logger
from ImpôtsError import ImpôtsError
import redis

# invio di un'e-mail all'amministratore
def send_adminmail(config: dict, message: str):
    # si invia un'e-mail all'amministratore dell'applicazione
    config_mail = config['parameters']['adminMail']
    config_mail["logger"] = config['logger']
    SendAdminMail.send(config_mail, message)

# verifica del file di log
logger = None
erreur = False
message_erreur = None
try:
    # registratore
    logger = Logger(config['parameters']['logsFilename'])
except BaseException as exception:
    # log della console
    print(f"L'erreur suivante s'est produite : {exception}")
    # si registra l'errore
    erreur = True
    message_erreur = f"{exception}"
# si memorizza il logger nella configurazione
config['logger'] = logger
# gestione degli errori
if erreur:
    # invio di un'e-mail all'amministratore
    send_adminmail(config, message_erreur)
    # chiusura dell'applicazione
    sys.exit(1)

# log di avvio
log = "[serveur] démarrage du serveur"
logger.write(f"{log}\n")

# verifica della disponibilità del server Redis
redis_client = redis.Redis(host=config["parameters"]["redis"]["host"],
                           port=config["parameters"]["redis"]["port"])
# si esegue il ping del server Redis
try:
    redis_client.ping()
except BaseException as exception:
    # Redis non disponibile
    log = f"[serveur] Le serveur Redis n'est pas disponible : {exception}"
    # console
    print(log)
    # log
    logger.write(f"{log}\n")
    # fine
    sys.exit(1)

# si memorizza il client [redis] nella configurazione
config['redis_client'] = redis_client

# recupero dei dati dall'amministrazione fiscale
erreur = False
try:
    # admindata sarà un dato a livello di applicazione in sola lettura
    config["admindata"] = config["layers"]["dao"].get_admindata()
    # log di esito positivo
    logger.write("[serveur] connexion à la base de données réussie\n")
except ImpôtsError as ex:
    # si registra l'errore
    erreur = True
    # log degli errori
    log = f"L'erreur suivante s'est produite : {ex}"
    # console
    print(log)
    # file di log
    logger.write(f"{log}\n")
    # e-mail all'amministratore
    send_adminmail(config, log)

# il thread principale non ha più bisogno del logger
logger.close()

# se si è verificato un errore, ci si arresta
if erreur:
    sys.exit(2)

# importazione delle route dell'applicazione web
import routes
routes.config=config
routes.execute(__name__)
  • righe 56-73: vengono introdotti un server Redis e il relativo client;
  • righe 102-104: le route dell’applicazione sono state esternalizzate nello script [routes];

33.2.1. I moduli [flask_session] e [redis]

Il server [Redis] verrà utilizzato per memorizzare le sessioni degli utenti. Utilizzeremo il modulo [flask_session] per gestire tali sessioni. Questo modulo può memorizzare le sessioni degli utenti in diverse posizioni. Redis è una di queste e la utilizzeremo.

Il modulo [flask_session] deve essere installato in un terminale PyCharm:


(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install flask-session
Collecting flask-session

Per comunicare con il server Redis, abbiamo bisogno di un client Redis. Questo ci verrà fornito dal modulo [redis], che installeremo a nostra volta:


(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install redis
Collecting redis

33.2.2. Il server Redis

Il server Redis servirà a memorizzare le sessioni degli utenti. Il modulo [flask_session] funziona nel modo seguente:

  • ogni utente ha un identificativo di sessione ed è proprio questo che viene inviato al client, e nient’altro. Il client riceve un cookie di sessione una sola volta, al termine della sua prima richiesta. Questo cookie contiene l’identificativo di sessione dell’utente, che non cambierà più nel corso delle successive richieste del client. Per questo motivo il server non deve inviare un nuovo cookie di sessione;
  • in precedenza il contenuto della sessione veniva inviato al client. Questo non avverrà più. Il contenuto della sessione dell’utente verrà memorizzato sul server Redis;

Laragon viene fornito con un server Redis disattivato per impostazione predefinita. È quindi necessario attivarlo:

Image

  • in [3], attivare il server [Redis];
  • in [4], lasciare la porta [6379] che i client Redis utilizzano di default;

I servizi Laragon vengono riavviati automaticamente dopo l’attivazione di Redis:

Image

Il server Redis può essere interrogato in modalità comando. Si apre un terminale Laragon [6]:

Image

  • in [1], il comando [redis-cli] avvia il client in modalità comando del server Redis;

Nel luglio 2019, il client Redis può utilizzare 172 comandi per interagire con il server [https://redis.io/commands#list]. Uno di questi, [command count] [2], visualizza il numero [3].

La scrittura in [Redis] avviene tramite il comando Redis [set attribut valeur] [4]. Il valore può quindi essere recuperato con il comando [get attribut] [5].

Potrebbe essere necessario svuotare la memoria di Redis. Ciò si effettua con il comando [flushdb] [6]. Successivamente, se si richiede il valore dell’attributo [titre] [7], si ottiene un riferimento [nil] [8] che indica che l’attributo non è stato trovato. È inoltre possibile utilizzare il comando [exists] [9-10] per verificare l’esistenza di un attributo.

Per uscire dal client Redis, digitare il comando [quit] [11].

È inoltre possibile utilizzare un'interfaccia web per gestire le chiavi presenti nel server Redis. A tal fine, è necessario che il server Apache di Laragon sia in esecuzione:

Image

Si ottiene la seguente interfaccia:

Image

  • in [1-4], una delle sessioni memorizzate sul server Redis;

33.2.3. Gestione del server Redis nello script principale [main]

Lo script [main] verifica la presenza del server Redis nel modo seguente:


import redis

# si verifica la disponibilità del server Redis
redis_client = redis.Redis(host=config["parameters"]["redis"]["host"],
                           port=config["parameters"]["redis"]["port"])
# si esegue il ping del server Redis
try:
    redis_client.ping()
except BaseException as exception:
    # Redis non disponibile
    log = f"[serveur] Le serveur Redis n'est pas disponible : {exception}"
    # console
    print(log)
    # log
    logger.write(f"{log}\n")
    # fine
    sys.exit(1)

# si memorizza il client [redis] nella configurazione
config['redis_client'] = redis_client
  • riga 4: il costruttore della classe [redis.Redis] crea un client del server Redis. Le sue caratteristiche (indirizzo, porta) si trovano nello script [parameters];
  • riga 8: il metodo [ping] consente di verificare la presenza del server Redis;
  • righe 9-17: se il ping non va a buon fine, l'errore viene registrato nel log e il server viene arrestato;
  • riga 20: il riferimento del client Redis viene inserito nella configurazione;

33.2.4. Gestione delle rotte nello script principale [main]

La gestione delle rotte in [main] si limita alle seguenti righe:


# importazione delle rotte dell'applicazione web
import routes
routes.config=config
routes.execute(__name__)
  • riga 1: le rotte sono state esternalizzate nel modulo [routes];
  • riga 3: le route devono conoscere la configurazione di esecuzione;
  • riga 4: si avvia l'applicazione Flask passandole il nome dello script eseguito (__main__);

Lo script delle route è il seguente:


# dipendenze
import os

from flask import Flask, redirect, request, session, url_for
from flask_api import status
from flask_session import Session

# Applicazione Flask
app = Flask(__name__, template_folder="../flask/templates", static_folder="../flask/static")

# configurazione dell'applicazione
config = {}

# il front controller
def front_controller() -> tuple:
    # la richiesta viene inoltrata al controller principale
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

@app.route('/', methods=['GET'])
def index() -> tuple:
    # reindirizzamento a /init-session/html
    return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)

# init-session
@app.route('/init-session/<string:type_response>', methods=['GET'])
def init_session(type_response: str) -> tuple:
    # si esegue il controller associato all'azione
    return front_controller()

# autenticazione utente
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur() -> tuple:
    # si esegue il controller associato all'azione
    return front_controller()

# calcolo-imposta
@app.route('/calculer-impot', methods=['POST'])
def calculer_impot() -> tuple:
    # si esegue il controller associato all'azione
    return front_controller()

# calcolo dell'imposta in batch
@app.route('/calculer-impots', methods=['POST'])
def calculer_impots():
    # si esegue il controller associato all'azione
    return front_controller()

# elenco delle simulazioni
@app.route('/lister-simulations', methods=['GET'])
def lister_simulations() -> tuple:
    # viene eseguito il controller associato all'azione
    return front_controller()

# elimina-simulazione
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
def supprimer_simulation(numero: int) -> tuple:
    # viene eseguito il controller associato all'azione
    return front_controller()

# fine-sessione
@app.route('/fin-session', methods=['GET'])
def fin_session() -> tuple:
    # si esegue il controller associato all'azione
    return front_controller()

# visualizza-calcolo-imposta
@app.route('/afficher-calcul-impot', methods=['GET'])
def afficher_calcul_impot() -> tuple:
    # si esegue il controller associato all'azione
    return front_controller()

# get-admindata
@app.route('/get-admindata', methods=['GET'])
def get_admindata() -> tuple:
    # viene eseguito il controller associato all'azione
    return front_controller()

def execute(name: str):
    # chiave segreta della sessione
    app.secret_key = os.urandom(12).hex()
    # Flask-Session
    app.config.update(SESSION_TYPE='redis',
                      SESSION_REDIS=config['redis_client'])
    Session(app)
    # caso in cui l'applicazione Flask viene avviata tramite uno script da riga di comando
    if name == '__main__':
        app.config.update(ENV="development", DEBUG=True)
        app.run(threaded=True)
  • riga 9: l'applicazione Flask viene istanziata;
  • riga 12: la configurazione dell'applicazione non è ancora nota al momento della scrittura dello script. Diventa nota solo al momento della sua esecuzione;
  • righe 20-77: le route dell’applicazione così come erano definite nella versione precedente. Non cambiano;
  • righe 14-18: tutte le route si limitano a chiamare la funzione [front_controller]. Abbiamo eliminato da quest’ultima il codice iniziale. Ora si limita a chiamare il controller principale dell’applicazione web;
  • righe 79-89: [execute] è la funzione chiamata dallo script [main] per avviare l’applicazione web;
  • riga 81: il modulo [flask_session] utilizza la chiave segreta di Flask;
  • righe 82-84: configurazione del modulo [flask_session]. Consiste nell’aggiungere le chiavi [SESSION_TYPE, SESSION_REDIS] alla configurazione [app.config] dell’applicazione Flask [app]:
  • [SESSION_TYPE]: il tipo di sessione. Ne esistono diversi. Il tipo [redis] indica che [flask_session] utilizza un server [redis] per memorizzare le sessioni degli utenti. Per questo motivo, è necessario definire la chiave [SESSION_REDIS], che deve essere il riferimento di un client Redis;
  • riga 85: la sessione [Flask-Session] è associata all’applicazione Flask;
  • righe 86-89: se il parametro [name] della riga 79 è la stringa [__main__], allora l’applicazione Flask viene avviata;

33.3. Rifattorizzazione del controller principale

Il codice che in precedenza si trovava nella funzione [front_controller] dello script [main] è stato spostato nel controller principale:

Image


# importazione delle dipendenze
import threading
import time
from random import randint

from flask_api import status
from werkzeug.local import LocalProxy

from InterfaceController import InterfaceController
from Logger import Logger
from SendAdminMail import SendAdminMail

def send_adminmail(config: dict, message: str):
    # invio di un'e-mail all'amministratore dell'applicazione
    config_mail = config['parameters']['adminMail']
    config_mail["logger"] = config['logger']
    SendAdminMail.send(config_mail, message)

# controller principale dell'applicazione
class MainController(InterfaceController):
    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        # elaborazione della richiesta
        logger = None
        action = None
        type_response1 = None
        try:
            # si recuperano gli elementi del percorso
            params = request.path.split('/')

            # l'azione è il primo elemento
            action = params[1]

            # nessun errore iniziale
            erreur = False

            # il tipo di sessione deve essere noto prima di determinate azioni
            type_response1 = session.get('typeResponse')
            if type_response1 is None and action != "init-session":
                # si registra l'errore
                résultat = {"action": action, "état"101,
                            "réponse"["pas de session en cours. Commencer par action [init-session]"]}
                erreur = True

            # registrare nel log
            logger = Logger(config['parameters']['logsFilename'])

            # viene memorizzato in una configurazione associata al thread
            thread_config = {"logger": logger}
            thread_name = threading.current_thread().name
            config[thread_name] = {"config": thread_config}

            # si registra la richiesta
            logger.write(f"[MainController] requête : {request}\n")

            # si interrompe il thread se richiesto
            sleep_time = config['parameters']['sleep_time']
            if sleep_time != 0:
                # la pausa è casuale, in modo che alcuni thread vengano interrotti e altri no
                aléa = randint(01)
                if aléa == 1:
                    # registrazione prima della pausa
                    logger.write(f"[front_controller] mis en pause du thread pendant {sleep_time} seconde(s)\n")
                    # pausa
                    time.sleep(sleep_time)

            # per alcune azioni è necessario essere autenticati
            user = session.get('user')
            if not erreur and user is None and action not in ["init-session""authentifier-utilisateur"]:
                # si rileva l'errore
                résultat = {"action": action, "état"101,
                            "réponse"[f"action [{action}] demandée par utilisateur non authentifié"]}
                erreur = True

            # Ci sono errori?
            if erreur:
                # la richiesta non è valida
                status_code = status.HTTP_400_BAD_REQUEST
            else:
                # viene eseguito il controller associato all'azione
                controller = config['mvc']['controllers'][action]
                résultat, status_code = controller.execute(request, session, config)

        except BaseException as exception:
            # altre eccezioni (impreviste)
            résultat = {"action": action, "état"131"réponse"[f"{exception}"]}
            status_code = status.HTTP_400_BAD_REQUEST

        finally:
            pass

        # si registra il risultato inviato al cliente
        log = f"[MainController] {résultat}\n"
        logger.write(log)

        # Si è verificato un errore irreversibile?
        if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
            # viene inviata un'e-mail all'amministratore dell'applicazione
            send_adminmail(config, log)

        # si determina il tipo desiderato per la risposta
        type_response2 = session.get('typeResponse')
        if type_response2 is None and type_response1 is None:
            # il tipo di sessione non è ancora stato stabilito - sarà jSON
            type_response = 'json'
        elif type_response2 is not None:
            # il tipo di risposta è noto e presente nella sessione
            type_response = type_response2
        else:
            # altrimenti si continua a utilizzare type_response1
            type_response = type_response1

        # si costruisce la risposta da inviare
        response_builder = config['mvc']['responses'][type_response]
        response, status_code = response_builder \
            .build_http_response(request, session, config, status_code, résultat)

        # si chiude il file di log se era stato aperto
        if logger:
            logger.close()

        # si invia la risposta HTTP
        return response, status_code

Tutto questo codice è già stato visto in un momento o nell’altro.

33.4. Gestione delle password crittografate

Per gestire le password crittografate utilizzeremo il modulo [passlib], che si installa da un terminale PyCharm:


(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install passlib
Collecting passlib

Ecco un esempio di script che crittografa la password che gli viene passata come parametro:

Image

Lo script [create_hashed_password] è il seguente (https://passlib.readthedocs.io/en/stable/):


import sys

# funzione di crittografia
from passlib.hash import pbkdf2_sha256

# si attende la password da crittografare
syntaxe = f"{sys.argv[0]} password"
erreur = len(sys.argv) != 2
if erreur:
    print(f"syntaxe : {syntaxe}")
    sys.exit()
else:
    password = sys.argv[1]

# si crittografa la password
hash = pbkdf2_sha256.hash(password)
print(f"version cryptée de [{password}] = {hash}")

# verifica
correct = pbkdf2_sha256.verify(password, hash)
print(correct)
  • riga 16: si crittografa la password passata come parametro;
  • riga 20: si confronta la password [password] passata come parametro con la sua versione crittografata [hash]. La funzione [verify] crittografa la password [password] e confronta la stringa crittografata ottenuta con [hash]. Restituisce True se le due stringhe sono uguali;

Lo script sopra riportato ci permette di ottenere la versione crittografata della password [admin]:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/08/passlib/create_hashed_password.py admin
version cryptée de [admin] = $pbkdf2-sha256$29000$fU9pTendO6c0ZoyR8r5Xqg$5ZXywIUnbMfN2hPnBaefiuqWjEbmAY.Lu06i4dwcnek
True

Riga 2, il valore che inseriamo nello script [parameters]:


        "users"[
            {
                "login""admin",
                "password""$pbkdf2-sha256$29000$fU9pTendO6c0ZoyR8r5Xqg$5ZXywIUnbMfN2hPnBaefiuqWjEbmAY.Lu06i4dwcnek"
            }
        ],

Il controller di autenticazione [AuthentifierUtilisateurController] si evolve come segue:


from passlib.handlers.pbkdf2 import pbkdf2_sha256

            # si verifica la validità della coppia (utente, password)
            users = config['parameters']['users']
            i = 0
            nbusers = len(users)
            trouvé = False
            while not trouvé and i < nbusers:
                trouvé = user == users[i]["login"] and pbkdf2_sha256.verify(password, users[i]["password"])
                i += 1
            # Trovata?
            if not trouvé:

33.5. Test

Oltre ai test con un browser, è possibile utilizzare anche i client presenti nella cartella [http-servers/07] scritti per la versione 12. Dovrebbero funzionare anche per la versione 13:

Image

  • in [1], i tre client devono funzionare;
  • in [2], i due test dovrebbero funzionare;