Skip to content

33. Anwendungsübung: Version 13

Version 13 unterscheidet sich von Version 12 in folgenden Punkten:

  • Bestimmte Teile des Codes wurden umstrukturiert (Refactoring);
  • Die Sitzungsverwaltung wird mithilfe des Moduls [flask_session] anders gehandhabt;
  • In der Konfigurationsdatei werden verschlüsselte Passwörter verwendet;

Version 13 wird zunächst durch Kopieren von Version 12 erstellt:

Image

Image

  • In [1] wird die Konfiguration überarbeitet. Insbesondere wird sie aus dem Ordner [flask] verschoben;
  • in [2] wird das Hauptskript überarbeitet. Es wird ebenfalls aus dem Ordner [flask] verschoben;
  • in [3] wird die Konfiguration auf mehrere Dateien aufgeteilt;
  • in [4]: Wir vereinfachen das Hauptskript [main], indem wir Code in andere Dateien verschieben;
  • in [5] wird der Authentifizierungs-Controller geändert, da Benutzerpasswörter nun verschlüsselt werden;
  • in [6] wird der Hauptcontroller den Code übernehmen, der zuvor im Hauptskript [main] enthalten war;

33.1. Umgestaltung der Anwendungskonfiguration

Image

Es werden drei neue Konfigurationsdateien erstellt:

  • [mvc]: zur Konfiguration der MVC-Architektur der Anwendung;
  • [parameters]: enthält alle Konstanten der Anwendung;
  • [syspath]: zur Konfiguration des Python-Pfads der Anwendung;

Die Datei [syspath.py] sieht wie folgt aus:

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
    }
  • Das Skript [syspath] dient zur Konfiguration des Python-Pfads der Anwendung (Zeilen 40–41);
  • es liefert zwei Informationen, die für die anderen Konfigurationsskripte nützlich sind (Zeilen 45–46);

Das Skript [mvc] konfiguriert die MVC-Architektur der JSON/XML/HTML-Webanwendung:

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
    }
  • Zeilen 1–101: Dieser Code ist bekannt;
  • Zeilen 105–116: Wir rendern die MVC-Konfiguration der Anwendung;

Das Skript [parameters] sammelt die Konstanten der Anwendung:

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
  • Zeile 13: Benutzerkennwörter werden nun verschlüsselt;
  • Zeilen 34–38: Konfiguration eines [Redis]-Servers, auf den wir später zurückkommen werden;

Mit diesen neuen Konfigurationsdateien sieht das Skript [config] nun wie folgt aus:

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. Umgestaltung des Hauptskripts [main]

Image

Das Hauptskript [main.py] startet einfach den 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__)
  • Zeilen 56–73: Ein Redis-Server und sein Client werden eingeführt;
  • Zeilen 102–104: Die Routen der Anwendung wurden in das Skript [routes] ausgelagert;

33.2.1. Die Module [flask_session] und [redis]

Der [Redis]-Server wird zum Speichern von Benutzersitzungen verwendet. Wir werden das [flask_session]-Modul zur Verwaltung dieser Sitzungen nutzen. Dieses Modul kann Benutzersitzungen an verschiedenen Orten speichern. Redis ist einer davon, und wir werden es verwenden.

Das Modul [flask_session] muss in einem PyCharm-Terminal installiert werden:


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

Um mit dem Redis-Server zu kommunizieren, benötigen wir einen Redis-Client. Dieser wird vom Modul [redis] bereitgestellt, das wir ebenfalls installieren werden:


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

33.2.2. Der Redis-Server

Der Redis-Server wird zum Speichern von Benutzersitzungen verwendet. Das Modul [flask_session] funktioniert wie folgt:

  • Jeder Benutzer hat eine Sitzungs-ID, und genau diese wird an den Client gesendet – und nur diese. Der Client erhält nur einmal, nach seiner ersten Anfrage, ein Sitzungs-Cookie. Dieses Cookie enthält die Sitzungs-ID des Benutzers, die sich bei nachfolgenden Anfragen des Clients nicht ändert. Aus diesem Grund muss der Server kein neues Sitzungs-Cookie senden;
  • Bisher wurde der Sitzungsinhalt an den Client gesendet. Dies ist nun nicht mehr der Fall. Der Sitzungsinhalt des Benutzers wird auf dem Redis-Server gespeichert;

Laragon verfügt über einen Redis-Server, der standardmäßig nicht aktiviert ist. Sie müssen ihn daher zunächst aktivieren:

Image

  • Aktivieren Sie in [3] den [Redis]-Server;
  • Lassen Sie in [4] den Port [6379] unverändert, den Redis-Clients standardmäßig verwenden;

Die Laragon-Dienste werden nach der Aktivierung von Redis automatisch neu gestartet:

Image

Der Redis-Server kann im Befehlsmodus abgefragt werden. Öffnen Sie ein Laragon-Terminal [6]:

Image

  • In [1] startet der Befehl [redis-cli] den Client im Befehlsmodus für den Redis-Server;

Seit Juli 2019 kann der Redis-Client 172 Befehle zur Interaktion mit dem Server verwenden [https://redis.io/commands#list]. Einer davon [Befehlszahl] [2] zeigt diese Zahl an [3].

Das Schreiben in [Redis] erfolgt mit dem Redis-Befehl [set attribute value] [4]. Der Wert kann dann mit dem Befehl [get attribute] [5] abgerufen werden.

Es kann erforderlich sein, die Redis-Datenbank zu leeren. Dies geschieht mit dem Befehl [flushdb] [6]. Wenn Sie anschließend den Wert des Attributs [title] abfragen [7], erhalten Sie eine [nil]-Referenz [8], die anzeigt, dass das Attribut nicht gefunden wurde. Sie können auch den Befehl [exists] [9-10] verwenden, um zu prüfen, ob ein Attribut existiert.

Um den Redis-Client zu beenden, geben Sie den Befehl [quit] ein [11].

Sie können die Schlüssel auf dem Redis-Server auch über eine Weboberfläche verwalten. Dazu muss der Laragon-Apache-Server laufen:

Image

Es erscheint die folgende Oberfläche:

Image

  • in [1-4] eine der auf dem Redis-Server gespeicherten Sitzungen;

33.2.3. Verwaltung des Redis-Servers im Hauptskript [main]

Das Skript [main] prüft wie folgt, ob der Redis-Server vorhanden ist:

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
  • Zeile 4: Der Klassenkonstruktor [redis.Redis] erstellt einen Redis-Server-Client. Seine Eigenschaften (Adresse, Port) sind im Skript [parameters] zu finden;
  • Zeile 8: Die Methode [ping] prüft, ob der Redis-Server erreichbar ist;
  • Zeilen 9–17: Wenn der Ping fehlschlägt, wird der Fehler protokolliert und der Server gestoppt;
  • Zeile 20: Die Redis-Client-Referenz wird in der Konfiguration gespeichert;

33.2.4. Routenverwaltung im Hauptskript [main]

Die Routenverwaltung in [main] beschränkt sich auf die folgenden Zeilen:

1
2
3
4
#  import routes from web application
import routes
routes.config=config
routes.execute(__name__)
  • Zeile 1: Die Routen wurden in das Modul [routes] ausgelagert;
  • Zeile 3: Die Routen müssen die Ausführungskonfiguration kennen;
  • Zeile 4: Wir starten die Flask-Anwendung, indem wir ihr den Namen des ausgeführten Skripts (__main__) übergeben;

Das Routen-Skript lautet wie folgt:

#  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)
  • Zeile 9: Die Flask-Anwendung wird instanziiert;
  • Zeile 12: Die Anwendungskonfiguration ist zum Zeitpunkt der Skripterstellung noch nicht bekannt. Sie wird erst bei der Ausführung bekannt;
  • Zeilen 20–77: Die Routen der Anwendung entsprechen denen der vorherigen Version. Dies bleibt unverändert;
  • Zeilen 14–18: Alle Routen rufen einfach die Funktion [front_controller] auf. Wir haben diese Funktion von ihrem ursprünglichen Code befreit. Sie ruft nun einfach den Hauptcontroller der Webanwendung auf;
  • Zeilen 79–89: [execute] ist die Funktion, die vom Skript [main] aufgerufen wird, um die Webanwendung zu starten;
  • Zeile 81: Das Modul [flask_session] verwendet den geheimen Schlüssel von Flask;
  • Zeilen 82–84: Konfiguration des Moduls [flask_session]. Dazu müssen die Schlüssel [SESSION_TYPE, SESSION_REDIS] zur Konfiguration [app.config] der Flask-Anwendung [app] hinzugefügt werden:
  • [SESSION_TYPE]: der Sitzungstyp. Es gibt mehrere Typen. Der Typ [redis] gibt an, dass [flask_session] einen [redis]-Server zum Speichern von Benutzersitzungen verwendet. Aus diesem Grund müssen wir den Schlüssel [SESSION_REDIS] definieren, der die Referenz eines Redis-Clients sein muss;
  • Zeile 85: Die [Flask-Session] wird der Flask-Anwendung zugeordnet;
  • Zeilen 86–89: Wenn der Parameter [name] in Zeile 79 die Zeichenkette [__main__] ist, wird die Flask-Anwendung gestartet;

33.3. Umgestaltung des Hauptcontrollers

Der Code, der zuvor in der Funktion [front_controller] des Skripts [main] stand, wurde in den Hauptcontroller verschoben:

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

All dieser Code wurde bereits zu irgendeinem Zeitpunkt behandelt.

33.4. Umgang mit verschlüsselten Passwörtern

Um verschlüsselte Passwörter zu verarbeiten, verwenden wir das Modul [passlib], das wir über ein PyCharm-Terminal installieren:


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

Hier ist ein Beispielskript, das das als Parameter übergebene Passwort verschlüsselt:

Image

Das Skript [create_hashed_password] lautet wie folgt (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)
  • Zeile 16: Das als Parameter übergebene Passwort wird verschlüsselt;
  • Zeile 20: Wir vergleichen das als Parameter übergebene Passwort [password] mit seiner verschlüsselten Version [hash]. Die Funktion [verify] verschlüsselt das Passwort [password] und vergleicht die resultierende verschlüsselte Zeichenkette mit [hash]. Gibt True zurück, wenn die beiden Zeichenketten identisch sind;

Mit dem obigen Skript können wir die verschlüsselte Version des Passworts [admin] ermitteln:


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

Zeile 2, der Wert, den wir in das Skript [parameters] eingegeben haben:


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

Der Authentifizierungscontroller [AuthentifierUtilisateurController] entwickelt sich wie folgt:

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. Tests

Neben den Tests mit einem Browser können Sie auch die im Ordner [http-servers/07] enthaltenen Clients verwenden, die für Version 12 geschrieben wurden. Diese sollten auch mit Version 13 funktionieren:

Image

  • In [1] sollten alle drei Clients funktionieren;
  • in [2] sollten beide Tests funktionieren;