Skip to content

33. Esercizio applicativo: Versione 13

La versione 13 modifica la versione 12 nei seguenti modi:

  • Alcune parti del codice sono state ristrutturate (rifattorizzazione);
  • la gestione delle sessioni viene gestita in modo diverso utilizzando il modulo [flask_session];
  • nel file di configurazione vengono utilizzate password crittografate;

La versione 13 viene inizialmente creata copiando la versione 12:

Image

Image

  • in [1], la configurazione verrà rifattorizzata. In particolare, verrà spostata fuori dalla cartella [flask];
  • in [2], lo script principale verrà rifattorizzato. Verrà inoltre spostato fuori dalla cartella [flask];
  • in [3], la configurazione verrà suddivisa in più file;
  • in [4]: semplificheremo lo script principale [main] spostando il codice in altri file;
  • in [5], il controller di autenticazione verrà modificato poiché le password degli utenti saranno ora crittografate;
  • in [6], il controller principale incorporerà il codice precedentemente presente nello script principale [main];

33.1. Rifattorizzazione della configurazione dell'applicazione

Image

Vengono creati tre nuovi file di configurazione:

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

Il file [syspath.py] è il seguente:

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

    #  folder of this file
    script_dir = os.path.dirname(os.path.abspath(__file__))

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

    #  dependencies
    absolute_dependencies = [
        #  project files
        #  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",
        #  Constants, slices
        f"{root_dir}/impots/v05/entities",
        #  Logger, SendAdminMail
        f"{root_dir}/impots/http-servers/02/utilities",
        #  main script folder
        script_dir,
        #  configs [database, layers, parameters, controllers, views]
        f"{script_dir}/../configs",
        #  controllers
        f"{script_dir}/../controllers",
        #  answers HTTP
        f"{script_dir}/../responses",
        #  view models
        f"{script_dir}/../models_for_views",
    ]

    #  set the syspath
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    #  we return the configuration
    return {
        "root_dir": root_dir,
        "script_dir": script_dir
    }
  • Lo script [syspath] viene utilizzato per 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:
    #  application configuration MVC

    #  controllers
    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
    #  answers HTTP
    from HtmlResponse import HtmlResponse
    from JsonResponse import JsonResponse
    from XmlResponse import XmlResponse
    #  view models
    from ModelForAuthentificationView import ModelForAuthentificationView
    from ModelForCalculImpotView import ModelForCalculImpotView
    from ModelForErreursView import ModelForErreursView
    from ModelForListeSimulationsView import ModelForListeSimulationsView

    #  authorized shares and their controllers
    controllers = {
        #  initialization of a calculation session
        "init-session": InitSessionController(),
        #  user authentication
        "authentifier-utilisateur": AuthentifierUtilisateurController(),
        #  tax calculation in individual mode
        "calculer-impot": CalculerImpotController(),
        #  batch mode tax calculation
        "calculer-impots": CalculerImpotsController(),
        #  list of simulations
        "lister-simulations": ListerSimulationsController(),
        #  deleting a simulation
        "supprimer-simulation": SupprimerSimulationController(),
        #  end of calculation session
        "fin-session": FinSessionController(),
        #  display tax calculation view
        "afficher-calcul-impot": AfficherCalculImpotController(),
        #  obtaining data from tax authorities
        "get-admindata": GetAdminDataController(),
        #  main controller
        "main-controller": MainController()
    }
    #  different response types (json, xml, html)
    responses = {
        "json": JsonResponse(),
        "html": HtmlResponse(),
        "xml": XmlResponse()
    }
    #  HTML views and their models depend on the state rendered by the controller
    views = [
        {
            #  authentication view
            "états": [
                700,    #  /init-session - success
                201,    #  /authentifier-user failure
            ],
            "view_name": "views/vue-authentification.html",
            "model_for_view": ModelForAuthentificationView()
        },
        {
            #  tax calculation
            "états": [
                200,    #  /authentifier-user success
                300,    #  /calculate-tax-success
                301,    #  /calculate-tax failure
                800,    #  /show-tax-calculation-success
            ],
            "view_name": "views/vue-calcul-impot.html",
            "model_for_view": ModelForCalculImpotView()
        },
        {
            #  view of simulation list
            "états": [
                500,    #  /lister-simulations success
                600,    #  /suppress-simulation success
            ],
            "view_name": "views/vue-liste-simulations.html",
            "model_for_view": ModelForListeSimulationsView()
        },

    ]
    #  view of unexpected errors
    view_erreurs = {
        "view_name": "views/vue-erreurs.html",
        "model_for_view": ModelForErreursView()
    }
    #  redirections
    redirections = [
        {
            "états": [
                400,  #  /end-session success
            ],
            #  redirection to URL
            "to": "/init-session/html",
        }
    ]


    #  return the MVC configuration
    return {
        #  controllers
        "controllers": controllers,
        #  answers HTTP
        "responses": responses,
        #  views and models
        "views": views,
        #  list of redirections
        "redirections": redirections,
        #  view of unexpected errors
        "view_erreurs": view_erreurs
    }
  • righe 1-101: questo codice è noto;
  • Righe 105–116: rendiamo la configurazione MVC dell'applicazione;

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

def configure(config: dict) -> dict:
    #  application setup

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

    #  application configuration
    parameters = {
        #  users authorized to use the application
        "users": [
            {
                "login": "admin",
                "password": "$pbkdf2-sha256$29000$mPM.h3COkTIGYOzde68VIg$7LH5Q7rN/1hW.Xa.6rcmR6h52PntvVqd5.na7EtgQNw"
            }
        ],
        #  log file
        "logsFilename": f"{script_dir}/../data/logs/logs.txt",
        #  server config SMTP
        "adminMail": {
            #  server SMTP
            "smtp-server": "localhost",
            #  server port SMTP
            "smtp-port": "25",
            #  director
            "from": "guest@localhost.com",
            "to": "guest@localhost.com",
            #  mail subject
            "subject": "plantage du serveur de calcul d'impôts",
            #  tls to True if server SMTP requires authorization, False otherwise
            "tls": False
        },
        #  thread pause time in seconds
        "sleep_time": 0,
        #  redis server
        "redis": {
            "host": "127.0.0.1",
            "port": 6379
        },
    }

    #  we return the application settings
    return parameters
  • riga 13: le password degli utenti saranno ora crittografate;
  • righe 34–38: configurazione di un server [Redis], su cui torneremo più avanti;

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

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

    #  application setup
    import parameters
    config['parameters'] = parameters.configure(config)

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

    #  instantiation of application layers
    import layers
    config['layers'] = layers.configure(config)

    #  configuration MVC of the [web] layer
    import mvc
    config['mvc'] = mvc.configure(config)

    #  we return the configuration
    return config

33.2. Rifattorizzazione dello script principale [main]

Image

Lo script principale [main.py] avvia semplicemente il server:

#  a mysql or pgres parameter is expected
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()

#  configure the application
import config
config = config.configure({'sgbd': sgbd})

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

#  send an e-mail to the administrator
def send_adminmail(config: dict, message: str):
    #  send an e-mail to the application administrator
    config_mail = config['parameters']['adminMail']
    config_mail["logger"] = config['logger']
    SendAdminMail.send(config_mail, message)

#  check log file
logger = None
erreur = False
message_erreur = None
try:
    #  logger
    logger = Logger(config['parameters']['logsFilename'])
except BaseException as exception:
    #  log console
    print(f"L'erreur suivante s'est produite : {exception}")
    #  we note the error
    erreur = True
    message_erreur = f"{exception}"
#  store the logger in the config
config['logger'] = logger
#  error handling
if erreur:
    #  mail to administrator
    send_adminmail(config, message_erreur)
    #  end of application
    sys.exit(1)

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

#  check Redis server availability
redis_client = redis.Redis(host=config["parameters"]["redis"]["host"],
                           port=config["parameters"]["redis"]["port"])
#  ping the Redis server
try:
    redis_client.ping()
except BaseException as exception:
    #  Redis not available
    log = f"[serveur] Le serveur Redis n'est pas disponible : {exception}"
    #  console
    print(log)
    #  log
    logger.write(f"{log}\n")
    #  end
    sys.exit(1)

#  save client [redis] in config
config['redis_client'] = redis_client

#  data recovery from tax authorities
erreur = False
try:
    #  admindata will be read-only application data
    config["admindata"] = config["layers"]["dao"].get_admindata()
    #  success log
    logger.write("[serveur] connexion à la base de données réussie\n")
except ImpôtsError as ex:
    #  we note the error
    erreur = True
    #  error log
    log = f"L'erreur suivante s'est produite : {ex}"
    #  console
    print(log)
    #  log file
    logger.write(f"{log}\n")
    #  mail to administrator
    send_adminmail(config, log)

#  the main thread no longer needs the logger
logger.close()

#  if there has been an error, we stop
if erreur:
    sys.exit(2)

#  import routes from web application
import routes
routes.config=config
routes.execute(__name__)
  • righe 56–73: vengono introdotti un server Redis e il relativo client;
  • righe 102–104: i percorsi dell'applicazione sono stati esternalizzati nello script [routes];

33.2.1. I moduli [flask_session] e [redis]

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

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 sarà fornito dal modulo [redis], che installeremo a sua 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 verrà utilizzato per memorizzare le sessioni degli utenti. Il modulo [flask_session] funziona come segue:

  • Ogni utente ha un ID di sessione, ed è proprio questo che viene inviato al client — e nient’altro. Il client riceve un cookie di sessione una sola volta, dopo la sua prima richiesta. Questo cookie contiene l’ID di sessione dell’utente, che non cambierà man mano che il client effettua richieste successive. Questo è il motivo per cui il server non ha bisogno di inviare un nuovo cookie di sessione;
  • In precedenza, il contenuto della sessione veniva inviato al client. Questo non accadrà più. Il contenuto della sessione dell'utente verrà memorizzato sul server Redis;

Laragon include un server Redis che non è abilitato di default. È quindi necessario iniziare abilitandolo:

Image

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

I servizi Laragon vengono riavviati automaticamente dopo l'abilitazione di Redis:

Image

È possibile eseguire query sul server Redis in modalità comando. Aprire un terminale Laragon [6]:

Image

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

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

La scrittura su [Redis] avviene utilizzando il comando Redis [set attributo valore] [4]. Il valore può quindi essere recuperato utilizzando il comando [get attributo] [5].

Potrebbe essere necessario svuotare il database Redis. Ciò avviene utilizzando il comando [flushdb] [6]. Quindi, se si interroga il valore dell'attributo [title] [7], si ottiene un riferimento [nil] [8] che indica che l'attributo non è stato trovato. È anche possibile utilizzare il comando [exists] [9-10] per verificare se un attributo esiste.

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

È inoltre possibile utilizzare un'interfaccia web per gestire le chiavi sul server Redis. A tal fine, il server Apache di Laragon deve essere in esecuzione:

Image

Viene visualizzata 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 come segue:

import redis

#  check Redis server availability
redis_client = redis.Redis(host=config["parameters"]["redis"]["host"],
                           port=config["parameters"]["redis"]["port"])
#  ping the Redis server
try:
    redis_client.ping()
except BaseException as exception:
    #  Redis not available
    log = f"[serveur] Le serveur Redis n'est pas disponible : {exception}"
    #  console
    print(log)
    #  log
    logger.write(f"{log}\n")
    #  end
    sys.exit(1)

#  save client [redis] in config
config['redis_client'] = redis_client
  • riga 4: il costruttore della classe [redis.Redis] crea un client del server Redis. Le sue proprietà (indirizzo, porta) si trovano nello script [parameters];
  • riga 8: il metodo [ping] verifica la presenza del server Redis;
  • righe 9–17: se il ping fallisce, l'errore viene registrato e il server viene arrestato;
  • riga 20: il riferimento al client Redis viene memorizzato nella configurazione;

33.2.4. Gestione delle rotte nello script principale [main]

La gestione dei percorsi in [main] è limitata alle seguenti righe:

1
2
3
4
#  import routes from web application
import routes
routes.config=config
routes.execute(__name__)
  • riga 1: i percorsi sono stati esternalizzati nel modulo [routes];
  • riga 3: i percorsi devono conoscere la configurazione di esecuzione;
  • riga 4: avviamo l'applicazione Flask passandole il nome dello script eseguito (__main__);

Lo script delle rotte è il seguente:

#  dependencies
import os

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

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

#  application configuration
config = {}

#  the front controller
def front_controller() -> tuple:
    #  forward the request to the main controller
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

@app.route('/', methods=['GET'])
def index() -> tuple:
    #  redirect to /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:
    #  execute the controller associated with the action
    return front_controller()

#  authenticate-user
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur() -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  calculate-tax
@app.route('/calculer-impot', methods=['POST'])
def calculer_impot() -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  batch tax calculation
@app.route('/calculer-impots', methods=['POST'])
def calculer_impots():
    #  execute the controller associated with the action
    return front_controller()

#  lister-simulations
@app.route('/lister-simulations', methods=['GET'])
def lister_simulations() -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  delete-simulation
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
def supprimer_simulation(numero: int) -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  end of session
@app.route('/fin-session', methods=['GET'])
def fin_session() -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  display-calculation-tax
@app.route('/afficher-calcul-impot', methods=['GET'])
def afficher_calcul_impot() -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  get-admindata
@app.route('/get-admindata', methods=['GET'])
def get_admindata() -> tuple:
    #  execute the controller associated with the action
    return front_controller()

def execute(name: str):
    #  session secret key
    app.secret_key = os.urandom(12).hex()
    #  Flask-Session
    app.config.update(SESSION_TYPE='redis',
                      SESSION_REDIS=config['redis_client'])
    Session(app)
    #  if you launch the Flask application via a console script
    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. È nota solo al momento dell'esecuzione;
  • righe 20–77: i percorsi dell'applicazione sono quelli definiti nella versione precedente. Questo rimane invariato;
  • righe 14–18: tutti i percorsi chiamano semplicemente la funzione [front_controller]. Abbiamo rimosso il codice originale da questa funzione. Ora chiama semplicemente 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]. Ciò comporta l'aggiunta delle chiavi [SESSION_TYPE, SESSION_REDIS] alla configurazione [app.config] dell'applicazione Flask [app]:
  • [SESSION_TYPE]: il tipo di sessione. Esistono diversi tipi. Il tipo [redis] indica che [flask_session] utilizza un server [redis] per memorizzare le sessioni degli utenti. Per questo motivo, dobbiamo definire la chiave [SESSION_REDIS], che deve essere il riferimento di un client Redis;
  • riga 85: [Flask-Session] è associato all'applicazione Flask;
  • righe 86–89: se il parametro [name] alla riga 79 è la stringa [__main__], allora viene avviata l'applicazione Flask;

33.3. Rifattorizzazione del controller principale

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

Image

#  import dependencies
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):
    #  send an e-mail to the application administrator
    config_mail = config['parameters']['adminMail']
    config_mail["logger"] = config['logger']
    SendAdminMail.send(config_mail, message)

#  main application controller
class MainController(InterfaceController):
    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        #  we process the request
        logger = None
        action = None
        type_response1 = None
        try:
            #  path elements are retrieved
            params = request.path.split('/')

            #  action is the 1st element
            action = params[1]

            #  no errors at the start
            erreur = False

            #  session type must be known prior to certain actions
            type_response1 = session.get('typeResponse')
            if type_response1 is None and action != "init-session":
                #  we note the error
                résultat = {"action": action, "état": 101,
                            "réponse": ["pas de session en cours. Commencer par action [init-session]"]}
                erreur = True

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

            #  we store it in a config associated with the thread
            thread_config = {"logger": logger}
            thread_name = threading.current_thread().name
            config[thread_name] = {"config": thread_config}

            #  log the request
            logger.write(f"[MainController] requête : {request}\n")

            #  the thread is interrupted if requested
            sleep_time = config['parameters']['sleep_time']
            if sleep_time != 0:
                #  pause is randomized so that some threads are interrupted and others not
                aléa = randint(0, 1)
                if aléa == 1:
                    #  log before break
                    logger.write(f"[front_controller] mis en pause du thread pendant {sleep_time} seconde(s)\n")
                    #  break
                    time.sleep(sleep_time)

            #  some actions require authentication
            user = session.get('user')
            if not erreur and user is None and action not in ["init-session", "authentifier-utilisateur"]:
                #  we note the error
                résultat = {"action": action, "état": 101,
                            "réponse": [f"action [{action}] demandée par utilisateur non authentifié"]}
                erreur = True

            #  are there any mistakes?
            if erreur:
                #  the request is invalid
                status_code = status.HTTP_400_BAD_REQUEST
            else:
                #  execute the controller associated with the action
                controller = config['mvc']['controllers'][action]
                résultat, status_code = controller.execute(request, session, config)

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

        finally:
            pass

        #  we log the result sent to the customer
        log = f"[MainController] {résultat}\n"
        logger.write(log)

        #  was there a fatal error?
        if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
            #  send an e-mail to the application administrator
            send_adminmail(config, log)

        #  determine the desired type of response
        type_response2 = session.get('typeResponse')
        if type_response2 is None and type_response1 is None:
            #  the session type has not yet been set - it will be jSON
            type_response = 'json'
        elif type_response2 is not None:
            #  the type of response is known and in the session
            type_response = type_response2
        else:
            #  otherwise continue to use type_response1
            type_response = type_response1

        #  build the response to be sent
        response_builder = config['mvc']['responses'][type_response]
        response, status_code = response_builder \
            .build_http_response(request, session, config, status_code, résultat)

        #  close the log file if it has been opened
        if logger:
            logger.close()

        #  we send the HTTP response
        return response, status_code

Tutto questo codice è stato trattato in un momento o nell'altro.

33.4. Gestione delle password crittografate

Per gestire le password crittografate, useremo il modulo [passlib], che installiamo 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

#  encryption function
from passlib.hash import pbkdf2_sha256

#  wait for the password to be encrypted
syntaxe = f"{sys.argv[0]} password"
erreur = len(sys.argv) != 2
if erreur:
    print(f"syntaxe : {syntaxe}")
    sys.exit()
else:
    password = sys.argv[1]

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

#  check
correct = pbkdf2_sha256.verify(password, hash)
print(correct)
  • riga 16: la password passata come parametro viene crittografata;
  • riga 20: confrontiamo la password [password] passata come parametro con la sua versione crittografata [hash]. La funzione [verify] crittografa la password [password] e confronta la stringa crittografata risultante 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

            #  check the validity of the (user, password) pair
            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
            #  found?
            if not trouvé:

33.5. Test

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

Image

  • in [1], tutti e tre i client dovrebbero funzionare;
  • in [2], entrambi i test dovrebbero funzionare;