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:


- 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

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]

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:

- 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:

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

- 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:

Je krijgt dan de volgende interface te zien:

- 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:

# 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(0, 1)
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:

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’:

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