Skip to content

33. Toepassingsopdracht: versie 13

Versie 13 wijzigt versie 12 op de volgende punten:

  • bepaalde delen van de code worden geherstructureerd (refactoring);
  • de sessie wordt anders beheerd met behulp van de module [flask_session];
  • er worden versleutelde wachtwoorden gebruikt in het configuratiebestand;

Versie 13 wordt in eerste instantie verkregen door versie 12 te kopiëren:

Image

Image

  • in [1] wordt de configuratie geherstructureerd. Deze wordt met name uit de map [flask] verwijderd;
  • in [2] wordt het hoofdscript herzien. Het wordt ook uit de map [flask] gehaald;
  • in [3] wordt de configuratie over meerdere bestanden verdeeld;
  • in [4]: het hoofdscript [main] wordt vereenvoudigd door code naar andere bestanden te verplaatsen;
  • in [5] wordt de authenticatiecontroller aangepast, aangezien de wachtwoorden van gebruikers voortaan versleuteld zullen worden;
  • in [6] zal de hoofdcontroller code opnemen die voorheen in het hoofdscript [main] stond;

33.1. Herstructurering van de configuratie van de applicatie

Image

Er verschijnen drie nieuwe configuratiebestanden:

  • [mvc]: om de architectuur MVC van de applicatie in te stellen;
  • [parameters]: waarin alle constanten van de applicatie worden verzameld;
  • [syspath]: dat het Python-pad van de applicatie configureert;

Het bestand [syspath.py] ziet er als volgt uit:


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

    # map van dit bestand
    script_dir = os.path.dirname(os.path.abspath(__file__))

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

    # afhankelijkheden
    absolute_dependencies = [
        # projectmappen
        # 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",
        # Constanten, segmenten
        f"{root_dir}/impots/v05/entities",
        # Logger, SendAdminMail
        f"{root_dir}/impots/http-servers/02/utilities",
        # map van het hoofdscript
        script_dir,
        # configuraties [database, layers, parameters, controllers, views]
        f"{script_dir}/../configs",
        # controllers
        f"{script_dir}/../controllers",
        # antwoorden HTTP
        f"{script_dir}/../responses",
        # weergavemodellen
        f"{script_dir}/../models_for_views",
    ]

    # het syspath instellen
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # de configuratie wordt doorgegeven
    return {
        "root_dir": root_dir,
        "script_dir": script_dir
    }
  • het script [syspath] dient om het Python-pad van de applicatie te configureren (regels 40-41);
  • het levert twee nuttige gegevens op voor de andere configuratiescripts (regels 45-46);

Het script [mvc] configureert de architectuur MVC van de webapplicatie jSON / XML / HTML:


def configure(config: dict) -> dict:
    # configuratie van de applicatie MVC

    # de 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
    # de antwoorden HTTP
    from HtmlResponse import HtmlResponse
    from JsonResponse import JsonResponse
    from XmlResponse import XmlResponse
    # de sjablonen van de weergaven
    from ModelForAuthentificationView import ModelForAuthentificationView
    from ModelForCalculImpotView import ModelForCalculImpotView
    from ModelForErreursView import ModelForErreursView
    from ModelForListeSimulationsView import ModelForListeSimulationsView

    # toegestane acties en hun controllers
    controllers = {
        # initialisatie van een reken-sessie
        "init-session": InitSessionController(),
        # authenticatie van een gebruiker
        "authentifier-utilisateur": AuthentifierUtilisateurController(),
        # belastingberekening in de individuele modus
        "calculer-impot": CalculerImpotController(),
        # berekening van de belasting in batchmodus
        "calculer-impots": CalculerImpotsController(),
        # lijst met simulaties
        "lister-simulations": ListerSimulationsController(),
        # verwijdering van een simulatie
        "supprimer-simulation": SupprimerSimulationController(),
        # beëindiging van de berekeningssessie
        "fin-session": FinSessionController(),
        # weergave van het overzicht van de belastingberekening
        "afficher-calcul-impot": AfficherCalculImpotController(),
        # opvragen van gegevens bij de belastingdienst
        "get-admindata": GetAdminDataController(),
        # hoofdcontroller
        "main-controller": MainController()
    }
    # de verschillende soorten antwoorden (json, xml, html)
    responses = {
        "json": JsonResponse(),
        "html": HtmlResponse(),
        "xml": XmlResponse()
    }
    # de weergaven HTML en hun sjablonen zijn afhankelijk van de status die door de controller wordt geretourneerd
    views = [
        {
            # authenticatieweergave
            "états": [
                700,    # /init-session - geslaagd
                201,    # /gebruiker-authenticeren - mislukt
            ],
            "view_name""views/vue-authentification.html",
            "model_for_view": ModelForAuthentificationView()
        },
        {
            # overzicht van de belastingberekening
            "états"[
                200,    # /gebruiker-authenticeren geslaagd
                300,    # /belasting-berekenen geslaagd
                301,    # /belasting-berekenen mislukt
                800,    # /belastingberekening-weergeven geslaagd
            ],
            "view_name""views/vue-calcul-impot.html",
            "model_for_view": ModelForCalculImpotView()
        },
        {
            # overzicht van de lijst met simulaties
            "états"[
                500,    # /simulaties weergeven geslaagd
                600,    # /simulatie-verwijderen geslaagd
            ],
            "view_name""views/vue-liste-simulations.html",
            "model_for_view": ModelForListeSimulationsView()
        },

    ]
    # overzicht van onverwachte fouten
    view_erreurs = {
        "view_name""views/vue-erreurs.html",
        "model_for_view": ModelForErreursView()
    }
    # omleidingen
    redirections = [
        {
            "états": [
                400,  # /sessie-beëindigen geslaagd
            ],
            # omleiding naar URL
            "to""/init-session/html",
        }
    ]


    # de configuratie wordt doorgegeven aan MVC
    return {
        # controllers
        "controllers": controllers,
        # reacties HTTP
        "responses": responses,
        # weergaven en sjablonen
        "views": views,
        # lijst met omleidingen
        "redirections": redirections,
        # overzicht van onverwachte fouten
        "view_erreurs": view_erreurs
    }
  • regels 1-101: deze code is bekend;
  • regels 105-116: de configuratie MVC van de applicatie wordt weergegeven;

Het script [parameters] verzamelt de constanten van de applicatie:


def configure(config: dict) -> dict:
    # instellingen van de applicatie

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

    # configuratie van de applicatie
    parameters = {
        # gebruikers die de applicatie mogen gebruiken
        "users"[
            {
                "login""admin",
                "password""$pbkdf2-sha256$29000$mPM.h3COkTIGYOzde68VIg$7LH5Q7rN/1hW.Xa.6rcmR6h52PntvVqd5.na7EtgQNw"
            }
        ],
        # logbestand
        "logsFilename"f"{script_dir}/../data/logs/logs.txt",
        # serverconfiguratie SMTP
        "adminMail": {
            # server SMTP
            "smtp-server""localhost",
            # serverpoort SMTP
            "smtp-port""25",
            # beheerder
            "from""guest@localhost.com",
            "to""guest@localhost.com",
            # onderwerp van de e-mail
            "subject""plantage du serveur de calcul d'impôts",
            # TLS op True als de server SMTP authenticatie vereist, anders op False
            "tls"False
        },
        # pauzetijd van de thread in seconden
        "sleep_time"0,
        # Redis-server
        "redis": {
            "host""127.0.0.1",
            "port"6379
        },
    }

    # de instellingen van de applicatie worden doorgegeven
    return parameters
  • regel 13: voortaan worden de wachtwoorden van de gebruikers versleuteld;
  • regels 34-38: de configuratie van een server [Redis], waar we later op terugkomen;

Met deze nieuwe configuratiebestanden ziet het script [config] er als volgt uit:


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

    # instellingen van de applicatie
    import parameters
    config['parameters'] = parameters.configure(config)

    # configuratie van de database
    import database
    config["database"] = database.configure(config)

    # instantiëren van de applicatielagen
    import layers
    config['layers'] = layers.configure(config)

    # configuratie van de laag MVC
    import mvc
    config['mvc'] = mvc.configure(config)

    # de configuratie wordt teruggegeven
    return config

33.2. Herstructurering van het hoofdscript [main]

Image

Het hoofdscript [main.py] zorgt alleen voor het opstarten van de server:


# er wordt gewacht op een mysql- of pgres-parameter
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()

# de applicatie wordt geconfigureerd
import config
config = config.configure({'sgbd': sgbd})

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

# er wordt een e-mail naar de beheerder verzonden
def send_adminmail(config: dict, message: str):
    # er wordt een e-mail verzonden naar de beheerder van de applicatie
    config_mail = config['parameters']['adminMail']
    config_mail["logger"] = config['logger']
    SendAdminMail.send(config_mail, message)

# het logbestand controleren
logger = None
erreur = False
message_erreur = None
try:
    # logboekfunctie
    logger = Logger(config['parameters']['logsFilename'])
except BaseException as exception:
    # consoollogboek
    print(f"L'erreur suivante s'est produite : {exception}")
    # de fout wordt genoteerd
    erreur = True
    message_erreur = f"{exception}"
# de logger wordt opgeslagen in de configuratie
config['logger'] = logger
# foutafhandeling
if erreur:
    # e-mail naar de beheerder
    send_adminmail(config, message_erreur)
    # afsluiten van de applicatie
    sys.exit(1)

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

# de beschikbaarheid van de Redis-server wordt gecontroleerd
redis_client = redis.Redis(host=config["parameters"]["redis"]["host"],
                           port=config["parameters"]["redis"]["port"])
# de Redis-server wordt gepingt
try:
    redis_client.ping()
except BaseException as exception:
    # Redis niet beschikbaar
    log = f"[serveur] Le serveur Redis n'est pas disponible : {exception}"
    # console
    print(log)
    # log
    logger.write(f"{log}\n")
    # einde
    sys.exit(1)

# de client [redis] wordt opgeslagen in de configuratie
config['redis_client'] = redis_client

# gegevens ophalen van de belastingdienst
erreur = False
try:
    # admindata wordt een alleen-lezen gegeven op applicatieniveau
    config["admindata"] = config["layers"]["dao"].get_admindata()
    # succeslogboek
    logger.write("[serveur] connexion à la base de données réussie\n")
except ImpôtsError as ex:
    # de fout wordt geregistreerd
    erreur = True
    # foutlogboek
    log = f"L'erreur suivante s'est produite : {ex}"
    # console
    print(log)
    # logbestand
    logger.write(f"{log}\n")
    # e-mail naar de beheerder
    send_adminmail(config, log)

# de hoofdthread heeft de logger niet meer nodig
logger.close()

# als er een fout is opgetreden, wordt het proces gestopt
if erreur:
    sys.exit(2)

# de routes van de webapplicatie importeren
import routes
routes.config=config
routes.execute(__name__)
  • regels 56-73: er worden een Redis-server en de bijbehorende client geïmplementeerd;
  • regels 102-104: de routes van de applicatie zijn uitbesteed aan het script [routes];

33.2.1. De modules [flask_session] en [redis]

De server [Redis] wordt gebruikt om de sessies van gebruikers op te slaan. We gaan de module [flask_session] gebruiken om deze sessies te beheren. Deze module kan de sessies van gebruikers op verschillende locaties opslaan. Redis is een van die locaties en daar gaan we gebruik van maken.

De module [flask_session] moet worden geïnstalleerd op een terminal PyCharm:


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

Om met de Redis-server te communiceren, hebben we een Redis-client nodig. Deze wordt geleverd door de module [redis], die we ook installeren:


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

33.2.2. De Redis-server

De Redis-server wordt gebruikt om de sessies van gebruikers op te slaan. De module [flask_session] werkt als volgt:

  • elke gebruiker heeft een sessie-ID en dat is het enige wat naar de client wordt verzonden. De client ontvangt slechts één keer een sessiecookie, na zijn eerste verzoek. Deze cookie bevat de sessie-ID van de gebruiker, die tijdens de verzoeken van de client niet meer zal veranderen. Daarom hoeft de server geen nieuwe sessiecookie terug te sturen;
  • voorheen werd de inhoud van de sessie naar de client verzonden. Dit is nu niet meer het geval. De inhoud van de sessie van de gebruiker wordt opgeslagen op de Redis-server;

Laragon wordt geleverd met een Redis-server die standaard niet is ingeschakeld. Je moet deze dus eerst inschakelen:

Image

  • in [3], activeer de server [Redis];
  • in [4] de poort [6379] laten staan die Redis-clients standaard gebruiken;

De Laragon-services worden automatisch opnieuw gestart nadat Redis is geactiveerd:

Image

De Redis-server kan in de opdrachtmodus worden geraadpleegd. Open een Laragon-terminal [6]:

Image

  • in [1] start het commando [redis-cli] de client in de commandomodus van de Redis-server;

In juli 2019 kan de Redis-client 172 commando’s gebruiken om met de server [https://redis.io/commands#list] te communiceren. Een daarvan, [command count] [2], geeft dit getal weer: [3].

Het schrijven naar [Redis] gebeurt met het Redis-commando [set attribut valeur] [4]. De waarde kan vervolgens worden opgehaald met het commando [get attribut] [5].

Het kan nodig zijn om het geheugen van Redis te legen. Dit gebeurt met het commando [flushdb] [6]. Als vervolgens de waarde van het attribuut [titre] [7] wordt opgevraagd, wordt een verwijzing [nil] [8] geretourneerd, wat aangeeft dat het attribuut niet is gevonden. Je kunt ook het commando [exists] [9-10] gebruiken om te controleren of een attribuut bestaat.

Om de Redis-client af te sluiten, voer je de opdracht [quit] [11] in.

Je kunt ook een webinterface gebruiken om de sleutels op de Redis-server te beheren. Hiervoor moet de Apache-server van Laragon zijn gestart:

Image

Je krijgt dan de volgende interface te zien:

Image

  • in [1-4], een van de sessies die op de Redis-server zijn opgeslagen;

33.2.3. Beheer van de Redis-server in het hoofdscript [main]

Het script [main] controleert op de aanwezigheid van de Redis-server op de volgende manier:


import redis

# de beschikbaarheid van de Redis-server wordt gecontroleerd
redis_client = redis.Redis(host=config["parameters"]["redis"]["host"],
                           port=config["parameters"]["redis"]["port"])
# de Redis-server wordt gepingt
try:
    redis_client.ping()
except BaseException as exception:
    # Redis niet beschikbaar
    log = f"[serveur] Le serveur Redis n'est pas disponible : {exception}"
    # console
    print(log)
    # log
    logger.write(f"{log}\n")
    # einde
    sys.exit(1)

# de client [redis] wordt opgeslagen in de configuratie
config['redis_client'] = redis_client
  • regel 4: de constructor van de klasse [redis.Redis] maakt een client voor de Redis-server aan. De kenmerken hiervan (adres, poort) zijn te vinden in het script [parameters];
  • regel 8: met de methode [ping] wordt gecontroleerd of de Redis-server aanwezig is;
  • regels 9-17: als de ping mislukt, wordt de fout gelogd en wordt de server gestopt;
  • regel 20: de referentie van de Redis-client wordt in de configuratie opgenomen;

33.2.4. Routebeheer in het hoofdscript [main]

Het routebeheer in [main] beperkt zich tot de volgende regels:


# routes van de webapplicatie importeren
import routes
routes.config=config
routes.execute(__name__)
  • regel 1: de routes zijn uitbesteed aan de module [routes];
  • regel 3: de routes moeten de uitvoeringsconfiguratie kennen;
  • regel 4: de Flask-applicatie wordt gestart door de naam van het uitgevoerde script (__main__) door te geven;

Het script voor de routes is als volgt:


# afhankelijkheden
import os

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

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

# configuratie van de applicatie
config = {}

# de frontcontroller
def front_controller() -> tuple:
    # het verzoek wordt doorgestuurd naar de hoofdcontroller
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

@app.route('/', methods=['GET'])
def index() -> tuple:
    # omleiding naar /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:
    # de bij de actie behorende controller wordt uitgevoerd
    return front_controller()

# gebruiker authenticeren
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur() -> tuple:
    # de bij de actie behorende controller wordt uitgevoerd
    return front_controller()

# belasting-berekenen
@app.route('/calculer-impot', methods=['POST'])
def calculer_impot() -> tuple:
    # de bij de actie behorende controller wordt uitgevoerd
    return front_controller()

# belasting berekenen in batches
@app.route('/calculer-impots', methods=['POST'])
def calculer_impots():
    # de bij de actie behorende controller wordt uitgevoerd
    return front_controller()

# simulaties weergeven
@app.route('/lister-simulations', methods=['GET'])
def lister_simulations() -> tuple:
    # de bij de actie behorende controller wordt uitgevoerd
    return front_controller()

# simulatie verwijderen
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
def supprimer_simulation(numero: int) -> tuple:
    # de bij de actie behorende controller wordt uitgevoerd
    return front_controller()

# einde-sessie
@app.route('/fin-session', methods=['GET'])
def fin_session() -> tuple:
    # de bij de actie behorende controller wordt uitgevoerd
    return front_controller()

# belastingberekening weergeven
@app.route('/afficher-calcul-impot', methods=['GET'])
def afficher_calcul_impot() -> tuple:
    # de bij de actie behorende controller wordt uitgevoerd
    return front_controller()

# get-admindata
@app.route('/get-admindata', methods=['GET'])
def get_admindata() -> tuple:
    # de bij de actie behorende controller wordt uitgevoerd
    return front_controller()

def execute(name: str):
    # geheime sleutel van de sessie
    app.secret_key = os.urandom(12).hex()
    # Flask-Session
    app.config.update(SESSION_TYPE='redis',
                      SESSION_REDIS=config['redis_client'])
    Session(app)
    # geval waarin de Flask-applicatie via een consolescript wordt gestart
    if name == '__main__':
        app.config.update(ENV="development", DEBUG=True)
        app.run(threaded=True)
  • regel 9: de Flask-applicatie wordt geïnstantieerd;
  • regel 12: de configuratie van de applicatie is op het moment van het schrijven van het script nog niet bekend. Deze wordt pas bekend op het moment van uitvoering;
  • regels 20-77: de routes van de applicatie zoals ze in de vorige versie waren gedefinieerd. Dit verandert niet;
  • regels 14-18: alle routes roepen alleen de functie [front_controller] aan. We hebben de oorspronkelijke code hieruit verwijderd. De functie roept nu alleen de hoofdcontroller van de webapplicatie aan;
  • regels 79-89: [execute] is de functie die door het script [main] wordt aangeroepen om de webapplicatie te starten;
  • regel 81: de module [flask_session] gebruikt de geheime sleutel van Flask;
  • regels 82-84: configuratie van de module [flask_session]. Deze bestaat uit het toevoegen van de sleutels [SESSION_TYPE, SESSION_REDIS] aan de configuratie [app.config] van de Flask-applicatie [app]:
  • [SESSION_TYPE]: het type van de sessie. Er zijn verschillende soorten. Het type [redis] geeft aan dat [flask_session] een server [redis] gebruikt om de sessies van gebruikers op te slaan. Daarom moet de sleutel [SESSION_REDIS] worden gedefinieerd, die de referentie van een Redis-client moet zijn;
  • regel 85: de sessie [Flask-Session] is gekoppeld aan de Flask-applicatie;
  • regels 86-89: als de parameter [name] uit regel 79 de tekenreeks [__main__] is, dan wordt de Flask-applicatie gestart;

33.3. Herstructurering van de hoofdcontroller

De code die voorheen in de functie [front_controller] van het script [main] stond, is verplaatst naar de hoofdcontroller:

Image


# afhankelijkheden importeren
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):
    # er wordt een e-mail verzonden naar de beheerder van de applicatie
    config_mail = config['parameters']['adminMail']
    config_mail["logger"] = config['logger']
    SendAdminMail.send(config_mail, message)

# hoofdcontroller van de applicatie
class MainController(InterfaceController):
    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        # het verzoek wordt verwerkt
        logger = None
        action = None
        type_response1 = None
        try:
            # de elementen van het pad worden opgehaald
            params = request.path.split('/')

            # de actie is het eerste element
            action = params[1]

            # in eerste instantie geen fouten
            erreur = False

            # het sessietype moet bekend zijn vóór bepaalde acties
            type_response1 = session.get('typeResponse')
            if type_response1 is None and action != "init-session":
                # de fout wordt genoteerd
                résultat = {"action": action, "état"101,
                            "réponse"["pas de session en cours. Commencer par action [init-session]"]}
                erreur = True

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

            # de fout wordt opgeslagen in een configuratie die aan de thread is gekoppeld
            thread_config = {"logger": logger}
            thread_name = threading.current_thread().name
            config[thread_name] = {"config": thread_config}

            # het verzoek wordt gelogd
            logger.write(f"[MainController] requête : {request}\n")

            # de thread wordt onderbroken indien dit is gevraagd
            sleep_time = config['parameters']['sleep_time']
            if sleep_time != 0:
                # de pauze is willekeurig, zodat sommige threads worden onderbroken en andere niet
                aléa = randint(01)
                if aléa == 1:
                    # logboek vóór de pauze
                    logger.write(f"[front_controller] mis en pause du thread pendant {sleep_time} seconde(s)\n")
                    # pauze
                    time.sleep(sleep_time)

            # voor bepaalde acties moet men geauthenticeerd zijn
            user = session.get('user')
            if not erreur and user is None and action not in ["init-session""authentifier-utilisateur"]:
                # de fout wordt genoteerd
                résultat = {"action": action, "état"101,
                            "réponse"[f"action [{action}] demandée par utilisateur non authentifié"]}
                erreur = True

            # zijn er fouten?
            if erreur:
                # het verzoek is ongeldig
                status_code = status.HTTP_400_BAD_REQUEST
            else:
                # de bij de actie behorende controller wordt uitgevoerd
                controller = config['mvc']['controllers'][action]
                résultat, status_code = controller.execute(request, session, config)

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

        finally:
            pass

        # het naar de klant verzonden resultaat wordt gelogd
        log = f"[MainController] {résultat}\n"
        logger.write(log)

        # is er een fatale fout opgetreden?
        if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
            # er wordt een e-mail verzonden naar de beheerder van de applicatie
            send_adminmail(config, log)

        # het gewenste type voor het antwoord wordt bepaald
        type_response2 = session.get('typeResponse')
        if type_response2 is None and type_response1 is None:
            # het sessietype is nog niet vastgesteld – dit wordt jSON
            type_response = 'json'
        elif type_response2 is not None:
            # het type van het antwoord is bekend en zit in de sessie
            type_response = type_response2
        else:
            # anders blijft men type_response1 gebruiken
            type_response = type_response1

        # het te verzenden antwoord wordt samengesteld
        response_builder = config['mvc']['responses'][type_response]
        response, status_code = response_builder \
            .build_http_response(request, session, config, status_code, résultat)

        # we sluiten het logbestand als het geopend was
        if logger:
            logger.close()

        # het antwoord wordt verzonden HTTP
        return response, status_code

Al deze code is al eens eerder aan bod gekomen.

33.4. Beheer van versleutelde wachtwoorden

Voor het beheer van versleutelde wachtwoorden gebruiken we de module [passlib], die we installeren vanuit een Pycharm-terminal:


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

Hier volgt een voorbeeld van een script dat het wachtwoord versleutelt dat als parameter wordt doorgegeven:

Image

Het script [create_hashed_password] ziet er als volgt uit (https://passlib.readthedocs.io/en/stable/):


import sys

# versleutelingsfunctie
from passlib.hash import pbkdf2_sha256

# er wordt gewacht op het wachtwoord dat moet worden versleuteld
syntaxe = f"{sys.argv[0]} password"
erreur = len(sys.argv) != 2
if erreur:
    print(f"syntaxe : {syntaxe}")
    sys.exit()
else:
    password = sys.argv[1]

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

# controle
correct = pbkdf2_sha256.verify(password, hash)
print(correct)
  • regel 16: het als parameter doorgegeven wachtwoord wordt versleuteld;
  • regel 20: het als parameter doorgegeven wachtwoord [password] wordt vergeleken met de versleutelde versie [hash]. De functie [verify] versleutelt het wachtwoord [password] en vergelijkt de verkregen versleutelde tekenreeks met [hash]. Geeft True terug als beide tekenreeksen gelijk zijn;

Met het bovenstaande script kunnen we de versleutelde versie van het wachtwoord [admin] verkrijgen:


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

Regel 2, de waarde die we in het script invoeren: [parameters]:


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

De authenticatiecontroller [AuthentifierUtilisateurController] verandert als volgt:


from passlib.handlers.pbkdf2 import pbkdf2_sha256

            # de geldigheid van het paar (gebruikersnaam, wachtwoord) wordt gecontroleerd
            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
            # gevonden?
            if not trouvé:

33.5. Tests

Naast het testen met een browser kunnen we ook de clients uit de map [http-servers/07] gebruiken die voor versie 12 zijn geschreven. Deze zouden ook voor versie 13 moeten ‘werken’:

Image

  • in [1] moeten de drie clients werken;
  • in [2] moeten de twee tests werken;