33. تمرين تطبيقي: الإصدار 13
يعدل الإصدار 13 الإصدار 12 بالطرق التالية:
- تمت إعادة هيكلة أجزاء معينة من الكود (إعادة الهيكلة)؛
- تتم إدارة الجلسة بشكل مختلف باستخدام وحدة [flask_session]؛
- يتم استخدام كلمات مرور مشفرة في ملف التكوين؛
تم إنشاء الإصدار 13 مبدئيًا عن طريق نسخ الإصدار 12:


- في [1]، سيتم إعادة هيكلة التكوين. على وجه الخصوص، سيتم نقله خارج مجلد [flask]؛
- في [2]، سيتم إعادة هيكلة البرنامج النصي الرئيسي. كما سيتم نقله خارج مجلد [flask]؛
- في [3]، سيتم تقسيم التكوين إلى عدة ملفات؛
- في [4]: سنقوم بتبسيط البرنامج النصي الرئيسي [main] عن طريق نقل الكود إلى ملفات أخرى؛
- في [5]، سيتم تعديل وحدة التحكم في المصادقة حيث سيتم الآن تشفير كلمات مرور المستخدمين؛
- في [6]، ستدمج وحدة التحكم الرئيسية الكود الذي كان موجودًا سابقًا في البرنامج النصي الرئيسي [main]؛
33.1. إعادة هيكلة تكوين التطبيق

يتم إنشاء ثلاثة ملفات تكوين جديدة:
- [mvc]: لتكوين بنية MVC للتطبيق؛
- [parameters]: الذي سيحتوي على جميع ثوابت التطبيق؛
- [syspath]: الذي يكوّن مسار Python الخاص بالتطبيق؛
ملف [syspath.py] كما يلي:
| 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
}
|
- يُستخدم البرنامج النصي [syspath] لتكوين مسار Python الخاص بالتطبيق (السطران 40–41)؛
- وهو يوفر معلومتين مفيدتين لبرامج النصوص البرمجية الأخرى الخاصة بالتكوين (السطران 45–46)؛
يقوم البرنامج النصي [mvc] بتكوين بنية MVC لتطبيق الويب 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
}
|
- الأسطر 1-101: هذا الكود معروف؛
- الأسطر 105–116: نقوم بعرض تكوين MVC للتطبيق؛
يجمع البرنامج النصي [parameters] ثوابت التطبيق:
| 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
|
- السطر 13: سيتم الآن تشفير كلمات مرور المستخدمين؛
- الأسطر 34–38: تكوين خادم [Redis]، والذي سنعود إليه لاحقًا؛
مع ملفات التكوين الجديدة هذه، يصبح النص البرمجي [config] كما يلي:
| 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. إعادة هيكلة البرنامج النصي الرئيسي [main]

يقوم البرنامج النصي الرئيسي [main.py] ببساطة بتشغيل الخادم:
| # 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__)
|
- الأسطر 56–73: تم تقديم خادم Redis وعميله؛
- الأسطر 102–104: تم نقل مسارات التطبيق إلى البرنامج النصي [routes]؛
33.2.1. وحدتا [flask_session] و[redis]
سيُستخدم خادم [Redis] لتخزين جلسات المستخدمين. سنستخدم الوحدة النمطية [flask_session] لإدارة هذه الجلسات. يمكن لهذه الوحدة النمطية تخزين جلسات المستخدمين في عدة مواقع. Redis هو أحدها، وسنستخدمه.
يجب تثبيت الوحدة النمطية [flask_session] في محطة PyCharm:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install flask-session
Collecting flask-session
…
للتواصل مع خادم Redis، نحتاج إلى عميل Redis. سيتم توفير ذلك بواسطة الوحدة النمطية [redis]، والتي سنقوم بتثبيتها أيضًا:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install redis
Collecting redis
…
33.2.2. خادم Redis
سيُستخدم خادم Redis لتخزين جلسات المستخدمين. تعمل الوحدة النمطية [flask_session] على النحو التالي:
- لكل مستخدم معرف جلسة عمل، وهذا هو ما يتم إرساله إلى العميل — ولا شيء سوى ذلك. يتلقى العميل ملف تعريف ارتباط جلسة العمل مرة واحدة فقط، بعد طلبه الأول. يحتوي ملف تعريف الارتباط هذا على معرف جلسة عمل المستخدم، والذي لن يتغير مع قيام العميل بطلبات لاحقة. لهذا السبب لا يحتاج الخادم إلى إرسال ملف تعريف ارتباط جلسة عمل جديد؛
- في السابق، كان محتوى الجلسة يُرسل إلى العميل. لن يكون هذا هو الحال بعد الآن. سيتم تخزين محتوى جلسة المستخدم على خادم Redis؛
يأتي Laragon مزودًا بخادم Redis غير ممكّن بشكل افتراضي. لذلك، يجب أن تبدأ بتمكينه:

- في [3]، قم بتمكين خادم [Redis]؛
- في [4]، اترك المنفذ [6379]، الذي يستخدمه عملاء Redis بشكل افتراضي؛
يتم إعادة تشغيل خدمات Laragon تلقائيًا بعد تمكين Redis:

يمكن الاستعلام عن خادم Redis في وضع الأوامر. افتح محطة Laragon [6]:

- في [1]، يقوم الأمر [redis-cli] بتشغيل العميل في وضع الأوامر لخادم Redis؛
اعتبارًا من يوليو 2019، يمكن لعميل Redis استخدام 172 أمرًا للتفاعل مع الخادم [https://redis.io/commands#list]. يعرض أحدها [عدد الأوامر] [2] هذا الرقم [3].
تتم الكتابة إلى [Redis] باستخدام أمر Redis [set attribute value] [4]. يمكن بعد ذلك استرداد القيمة باستخدام الأمر [get attribute] [5].
قد يكون من الضروري مسح قاعدة بيانات Redis. ويتم ذلك باستخدام الأمر [flushdb] [6]. بعد ذلك، إذا استعلمت عن قيمة السمة [title] [7]، فستحصل على مرجع [nil] [8] يشير إلى عدم العثور على السمة. يمكنك أيضًا استخدام الأمر [exists] [9-10] للتحقق من وجود السمة.
للخروج من عميل Redis، اكتب الأمر [quit] [11].
يمكنك أيضًا استخدام واجهة ويب لإدارة المفاتيح على خادم Redis. للقيام بذلك، يجب أن يكون خادم Laragon Apache قيد التشغيل:

تظهر الواجهة التالية:

- في [1-4]، إحدى الجلسات المخزنة على خادم Redis؛
33.2.3. إدارة خادم Redis في البرنامج النصي الرئيسي [main]
يتحقق البرنامج النصي [main] من وجود خادم Redis على النحو التالي:
| 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
|
- السطر 4: يقوم منشئ الفئة [redis.Redis] بإنشاء عميل خادم Redis. توجد خصائصه (العنوان، المنفذ) في البرنامج النصي [parameters]؛
- السطر 8: تتحقق طريقة [ping] من وجود خادم Redis؛
- الأسطر 9–17: إذا فشل اختبار الاتصال، يتم تسجيل الخطأ وإيقاف الخادم؛
- السطر 20: يتم تخزين مرجع عميل Redis في التكوين؛
33.2.4. إدارة المسارات في البرنامج النصي الرئيسي [main]
تقتصر معالجة المسارات في [main] على الأسطر التالية:
| # import routes from web application
import routes
routes.config=config
routes.execute(__name__)
|
- السطر 1: تم نقل المسارات إلى الوحدة النمطية [routes]؛
- السطر 3: يجب أن تعرف المسارات تكوين التنفيذ؛
- السطر 4: نقوم بتشغيل تطبيق Flask عن طريق تمرير اسم البرنامج النصي الذي يتم تنفيذه (__main__) إليه؛
نص البرنامج النصي للمسارات هو كما يلي:
| # 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)
|
- السطر 9: يتم إنشاء مثيل لتطبيق Flask؛
- السطر 12: لا تكون تكوينات التطبيق معروفة بعد عند كتابة البرنامج النصي. ولا تُعرف إلا عند التنفيذ؛
- الأسطر 20–77: مسارات التطبيق كما هي محددة في الإصدار السابق. وهذا لم يتغير؛
- الأسطر 14–18: جميع المسارات تستدعي ببساطة الدالة [front_controller]. لقد قمنا بإزالة الكود الأصلي من هذه الدالة. وهي الآن تستدعي ببساطة وحدة التحكم الرئيسية لتطبيق الويب؛
- الأسطر 79-89: [execute] هي الدالة التي يستدعيها البرنامج النصي [main] لتشغيل تطبيق الويب؛
- السطر 81: تستخدم الوحدة النمطية [flask_session] المفتاح السري لـ Flask؛
- الأسطر 82–84: تكوين وحدة [flask_session]. يتضمن ذلك إضافة المفاتيح [SESSION_TYPE, SESSION_REDIS] إلى تكوين [app.config] لتطبيق Flask [app]:
- [SESSION_TYPE]: نوع الجلسة. هناك عدة أنواع. يشير النوع [redis] إلى أن [flask_session] يستخدم خادم [redis] لتخزين جلسات المستخدمين. ولهذا السبب، يجب علينا تعريف المفتاح [SESSION_REDIS]، الذي يجب أن يكون مرجعًا لعميل Redis؛
- السطر 85: يرتبط [Flask-Session] بتطبيق Flask؛
- الأسطر 86–89: إذا كانت المعلمة [name] في السطر 79 هي السلسلة [__main__]، فسيتم تشغيل تطبيق Flask؛
33.3. إعادة هيكلة وحدة التحكم الرئيسية
تم نقل الكود الذي كان موجودًا سابقًا في دالة [front_controller] في البرنامج النصي [main] إلى وحدة التحكم الرئيسية:

| # 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
|
تمت تغطية كل هذا الكود في وقت أو آخر.
33.4. التعامل مع كلمات المرور المشفرة
للتعامل مع كلمات المرور المشفرة، سنستخدم الوحدة النمطية [passlib]، التي نقوم بتثبيتها من محطة PyCharm:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install passlib
Collecting passlib
…
فيما يلي نموذج لبرنامج نصي يقوم بتشفير كلمة المرور التي يتم تمريرها إليه كمعلمة:

النص البرمجي [create_hashed_password] هو كما يلي (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)
|
- السطر 16: يتم تشفير كلمة المرور التي تم تمريرها كمعلمة؛
- السطر 20: نقارن كلمة المرور [password] التي تم تمريرها كمعلمة بنسختها المشفرة [hash]. تقوم الدالة [verify] بتشفير كلمة المرور [password] ومقارنة السلسلة المشفرة الناتجة بـ [hash]. تُرجع True إذا كانت السلسلتان متساويتين؛
يسمح لنا البرنامج النصي أعلاه بالحصول على النسخة المشفرة لكلمة المرور [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
السطر 2، القيمة التي نضعها في البرنامج النصي [parameters]:
"users": [
{
"login": "admin",
"password": "$pbkdf2-sha256$29000$fU9pTendO6c0ZoyR8r5Xqg$5ZXywIUnbMfN2hPnBaefiuqWjEbmAY.Lu06i4dwcnek"
}
],
تتطور وحدة التحكم في المصادقة [AuthentifierUtilisateurController] على النحو التالي:
| 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. الاختبارات
بالإضافة إلى الاختبار باستخدام متصفح، يمكنك أيضًا استخدام برامج العميل الموجودة في المجلد [http-servers/07] والمكتوبة للإصدار 12. ومن المفترض أن تعمل أيضًا مع الإصدار 13:

- في [1]، يجب أن تعمل جميع العملاء الثلاثة؛
- في [2]، يجب أن يعمل كلا الاختبارين؛