Skip to content

33. Практичне завдання: версія 13

Версія 13 відрізняється від версії 12 у таких аспектах:

  • переструктуруються деякі частини коду (рефакторинг);
  • сесія обробляється інакше за допомогою модуля [flask_session];
  • у файлі конфігурації використовуються зашифровані паролі;

Версію 13 спочатку отримують шляхом копіювання версії 12:

Image

Image

  • у [1] конфігурація буде рефакторована. Зокрема, її вилучають із папки [flask];
  • у [2] головний скрипт буде рефакторований. Його також вилучають із папки [flask];
  • у [3] конфігурація буде розділена на кілька файлів;
  • у [4]: ми спростимо головний скрипт [main], перемістивши частину коду в інші файли;
  • у [5] контролер автентифікації буде змінено, оскільки відтепер паролі користувачів будуть зашифровані;
  • у [6] головний контролер інтегрує код, який раніше містився в головному скрипті [main];

33.1. Рефакторизація конфігурації додатка

Image

З’являються три нові файли конфігурації:

  • [mvc]: для налаштування архітектури MVC додатка;
  • [parameters]: для об’єднання всіх констант додатка;
  • [syspath]: для налаштування Python Path додатка;

Файл [syspath.py] має такий вигляд:


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

    # папка цього файлу
    script_dir = os.path.dirname(os.path.abspath(__file__))

    # кореневий шлях
    root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"

    # залежності
    absolute_dependencies = [
        # папки проекту
        # 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",
        # Константи, діапазони
        f"{root_dir}/impots/v05/entities",
        # Логер, SendAdminMail
        f"{root_dir}/impots/http-servers/02/utilities",
        # папка головного скрипта
        script_dir,
        # конфігурації [database, layers, parameters, controllers, views]
        f"{script_dir}/../configs",
        # контролери
        f"{script_dir}/../controllers",
        # відповіді HTTP
        f"{script_dir}/../responses",
        # шаблони переглядів
        f"{script_dir}/../models_for_views",
    ]

    # встановлюємо syspath
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # виконується конфігурація
    return {
        "root_dir": root_dir,
        "script_dir": script_dir
    }
  • скрипт [syspath] служить для налаштування Python Path додатка (рядки 40–41);
  • він надає дві корисні інформації для інших скриптів налаштування (рядки 45–46);

Скрипт [mvc] налаштовує архітектуру MVC веб-додатку jSON / XML / HTML:


def configure(config: dict) -> dict:
    # конфігурація додатка MVC

    # контролери
    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
    # відповіді HTTP
    from HtmlResponse import HtmlResponse
    from JsonResponse import JsonResponse
    from XmlResponse import XmlResponse
    # шаблони переглядів
    from ModelForAuthentificationView import ModelForAuthentificationView
    from ModelForCalculImpotView import ModelForCalculImpotView
    from ModelForErreursView import ModelForErreursView
    from ModelForListeSimulationsView import ModelForListeSimulationsView

    # дозволені дії та їхні контролери
    controllers = {
        # ініціалізація обчислювальної сесії
        "init-session": InitSessionController(),
        # аутентифікація користувача
        "authentifier-utilisateur": AuthentifierUtilisateurController(),
        # розрахунок податку в індивідуальному режимі
        "calculer-impot": CalculerImpotController(),
        # розрахунок податку в пакетному режимі
        "calculer-impots": CalculerImpotsController(),
        # перелік моделювань
        "lister-simulations": ListerSimulationsController(),
        # видалення моделювання
        "supprimer-simulation": SupprimerSimulationController(),
        # завершення сеансу розрахунку
        "fin-session": FinSessionController(),
        # відображення вікна розрахунку податку
        "afficher-calcul-impot": AfficherCalculImpotController(),
        # отримання даних від податкової служби
        "get-admindata": GetAdminDataController(),
        # головний контролер
        "main-controller": MainController()
    }
    # різні типи відповідей (json, xml, html)
    responses = {
        "json": JsonResponse(),
        "html": HtmlResponse(),
        "xml": XmlResponse()
    }
    # представлення HTML та їхні шаблони залежать від стану, що повертається контролером
    views = [
        {
            # вигляд автентифікації
            "états": [
                700,    # /init-session — успішно
                201,    # /authentifier-utilisateur — невдача
            ],
            "view_name""views/vue-authentification.html",
            "model_for_view": ModelForAuthentificationView()
        },
        {
            # сторінка розрахунку податку
            "états"[
                200,    # /автентифікація-користувача успішно
                300,    # /розрахувати-податок успішно
                301,    # /розрахувати-податок невдача
                800,    # /перегляд-розрахунку-податку успішно
            ],
            "view_name""views/vue-calcul-impot.html",
            "model_for_view": ModelForCalculImpotView()
        },
        {
            # перегляд списку моделювань
            "états"[
                500,    # /перелік-симуляцій успішно
                600,    # /видалити-симуляцію успішно
            ],
            "view_name""views/vue-liste-simulations.html",
            "model_for_view": ModelForListeSimulationsView()
        },

    ]
    # перегляд несподіваних помилок
    view_erreurs = {
        "view_name""views/vue-erreurs.html",
        "model_for_view": ModelForErreursView()
    }
    # перенаправлення
    redirections = [
        {
            "états": [
                400,  # /завершення-сесії успішно
            ],
            # перенаправлення на URL
            "to""/init-session/html",
        }
    ]


    # віддаємо конфігурацію MVC
    return {
        # контролери
        "controllers": controllers,
        # відповіді HTTP
        "responses": responses,
        # перегляди та шаблони
        "views": views,
        # список перенаправлень
        "redirections": redirections,
        # перегляд несподіваних помилок
        "view_erreurs": view_erreurs
    }
  • рядки 1–101: цей код відомий;
  • рядки 105–116: відтворюється конфігурація MVC додатка;

Скрипт [parameters] збирає константи додатка:


def configure(config: dict) -> dict:
    # налаштування програми

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

    # конфігурація додатка
    parameters = {
        # користувачі, яким дозволено користуватися додатком
        "users"[
            {
                "login""admin",
                "password""$pbkdf2-sha256$29000$mPM.h3COkTIGYOzde68VIg$7LH5Q7rN/1hW.Xa.6rcmR6h52PntvVqd5.na7EtgQNw"
            }
        ],
        # файл журналу
        "logsFilename"f"{script_dir}/../data/logs/logs.txt",
        # конфігурація сервера SMTP
        "adminMail": {
            # сервер SMTP
            "smtp-server""localhost",
            # порт сервера SMTP
            "smtp-port""25",
            # адміністратор
            "from""guest@localhost.com",
            "to""guest@localhost.com",
            # тема листа
            "subject""plantage du serveur de calcul d'impôts",
            # tls — True, якщо сервер SMTP вимагає авторизації, інакше — False
            "tls"False
        },
        # тривалість паузи потоку в секундах
        "sleep_time"0,
        # сервер Redis
        "redis": {
            "host""127.0.0.1",
            "port"6379
        },
    }

    # задаємо параметри додатка
    return parameters
  • рядок 13: відтепер паролі користувачів будуть шифруватися;
  • рядки 34–38: конфігурація сервера [Redis], до якої ми ще повернемося;

З цими новими конфігураційними файлами скрипт [config] набуває такого вигляду:


def configure(config: dict) -> dict:
    # конфігурація syspath
    import syspath
    config['syspath'] = syspath.configure(config)

    # параметри додатка
    import parameters
    config['parameters'] = parameters.configure(config)

    # конфігурація бази даних
    import database
    config["database"] = database.configure(config)

    # інстанціювання рівнів додатка
    import layers
    config['layers'] = layers.configure(config)

    # налаштування MVC шару [web]
    import mvc
    config['mvc'] = mvc.configure(config)

    # повертаємо конфігурацію
    return config

33.2. Рефакторизація головного скрипта [main]

Image

Головний скрипт [main.py] лише запускає сервер:


# очікується параметр mysql або pgres
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()

# налаштовуємо додаток
import config
config = config.configure({'sgbd': sgbd})

# залежності
from SendAdminMail import SendAdminMail
from Logger import Logger
from ImpôtsError import ImpôtsError
import redis

# надсилання електронного листа адміністратору
def send_adminmail(config: dict, message: str):
    # надсилається електронний лист адміністратору додатка
    config_mail = config['parameters']['adminMail']
    config_mail["logger"] = config['logger']
    SendAdminMail.send(config_mail, message)

# перевірка файлу журналу
logger = None
erreur = False
message_erreur = None
try:
    # реєстратор
    logger = Logger(config['parameters']['logsFilename'])
except BaseException as exception:
    # журнал консолі
    print(f"L'erreur suivante s'est produite : {exception}")
    # зафіксувати помилку
    erreur = True
    message_erreur = f"{exception}"
# записувати логер у конфігурацію
config['logger'] = logger
# обробка помилки
if erreur:
    # лист адміністратору
    send_adminmail(config, message_erreur)
    # завершення роботи програми
    sys.exit(1)

# журнал запуску
log = "[serveur] démarrage du serveur"
logger.write(f"{log}\n")

# перевірка доступності сервера Redis
redis_client = redis.Redis(host=config["parameters"]["redis"]["host"],
                           port=config["parameters"]["redis"]["port"])
# виконується ping-перевірка сервера Redis
try:
    redis_client.ping()
except BaseException as exception:
    # Redis недоступний
    log = f"[serveur] Le serveur Redis n'est pas disponible : {exception}"
    # консоль
    print(log)
    # журнал
    logger.write(f"{log}\n")
    # кінець
    sys.exit(1)

# заносимо клієнта [redis] до конфігурації
config['redis_client'] = redis_client

# отримання даних від податкової служби
erreur = False
try:
    # admindata буде даними на рівні додатка, доступними лише для читання
    config["admindata"] = config["layers"]["dao"].get_admindata()
    # журнал успішного виконання
    logger.write("[serveur] connexion à la base de données réussie\n")
except ImpôtsError as ex:
    # зафіксовано помилку
    erreur = True
    # журнал помилок
    log = f"L'erreur suivante s'est produite : {ex}"
    # консоль
    print(log)
    # файл журналу
    logger.write(f"{log}\n")
    # лист адміністратору
    send_adminmail(config, log)

# головний потік більше не потребує логгера
logger.close()

# якщо сталася помилка, процес зупиняється
if erreur:
    sys.exit(2)

# імпортуємо маршрути веб-додатку
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] працює наступним чином:

  • кожен користувач має ідентифікатор сесії, і саме він надсилається клієнту — і лише він. Клієнт отримує сесійний файл cookie лише один раз, після свого першого запиту. Цей файл cookie містить ідентифікатор сесії користувача, який не змінюватиметься під час подальших запитів клієнта. Саме тому серверу не потрібно надсилати новий сесійний файл cookie;
  • раніше вміст сесії надсилався клієнту. Тепер цього більше не відбуватиметься. Вміст сесії користувача зберігатиметься на сервері Redis;

У Laragon сервер Redis за замовчуванням не активований. Тому спочатку його потрібно активувати:

Image

  • у [3] увімкніть сервер [Redis];
  • у [4] залиште порт [6379], який клієнти Redis використовують за замовчуванням;

Служби Laragon автоматично перезапускаються після активації Redis:

Image

Сервер Redis можна запитувати в командному режимі. Відкриваємо термінал Laragon [6]:

Image

  • у [1] команда [redis-cli] запускає клієнт у командному режимі сервера Redis;

У липні 2019 року клієнт Redis може використовувати 172 команди для взаємодії з сервером [https://redis.io/commands#list]. Одна з них, [command count] [2], виводить це число [3].

Запис у [Redis] здійснюється за допомогою команди Redis [set attribut valeur] [4]. Потім значення можна отримати за допомогою команди [get attribut] [5].

Може знадобитися очищення пам’яті Redis. Це робиться за допомогою команди [flushdb] [6]. Далі, якщо запитати значення атрибута [titre] [7], отримаємо посилання [nil] [8], яке вказує, що атрибут не знайдено. Також можна використати команду [exists] [9-10], щоб перевірити наявність атрибута.

Щоб вийти з клієнта Redis, введіть команду [quit] [11].

Також можна використовувати веб-інтерфейс для управління ключами на сервері Redis. Для цього потрібно запустити сервер Apache в Laragon:

Image

З’явиться такий інтерфейс:

Image

  • [1-4] — одна з сесій, збережених на сервері Redis;

33.2.3. Управління сервером Redis у головному скрипті [main]

Скрипт [main] перевіряє наявність сервера Redis наступним чином:


import redis

# перевіряємо доступність сервера Redis
redis_client = redis.Redis(host=config["parameters"]["redis"]["host"],
                           port=config["parameters"]["redis"]["port"])
# виконується ping-тест сервера Redis
try:
    redis_client.ping()
except BaseException as exception:
    # Redis недоступний
    log = f"[serveur] Le serveur Redis n'est pas disponible : {exception}"
    # консоль
    print(log)
    # журнал
    logger.write(f"{log}\n")
    # кінець
    sys.exit(1)

# заносимо клієнта [redis] у конфігурацію
config['redis_client'] = redis_client
  • рядок 4: конструктор класу [redis.Redis] створює клієнт Redis-сервера. Його параметри (адреса, порт) знаходяться у скрипті [parameters];
  • рядок 8: метод [ping] дозволяє перевірити наявність сервера Redis;
  • рядки 9–17: якщо ping не вдається, то помилка записується в журнал, а сервер зупиняється;
  • рядок 20: посилання на клієнт Redis вноситься до конфігурації;

33.2.4. Управління маршрутами в головному скрипті [main]

Управління маршрутами у [main] обмежується такими рядками:


# імпортуємо маршрути веб-додатку
import routes
routes.config=config
routes.execute(__name__)
  • рядок 1: маршрути були винесені в модуль [routes];
  • рядок 3: маршрути потребують інформації про конфігурацію виконання;
  • рядок 4: запускається додаток Flask із передачею йому імені виконуваного скрипта (__main__);

Скрипт маршрутів має такий вигляд:


# залежності
import os

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

# додаток Flask
app = Flask(__name__, template_folder="../flask/templates", static_folder="../flask/static")

# конфігурація додатка
config = {}

# фронт-контролер
def front_controller() -> tuple:
    # запит перенаправляється до головного контролера
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

@app.route('/', methods=['GET'])
def index() -> tuple:
    # перенаправлення на /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:
    # виконується контролер, пов'язаний з цією дією
    return front_controller()

# аутентифікація користувача
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur() -> tuple:
    # виконується контролер, пов'язаний з цією дією
    return front_controller()

# розрахувати-податок
@app.route('/calculer-impot', methods=['POST'])
def calculer_impot() -> tuple:
    # виконується контролер, пов'язаний з цією дією
    return front_controller()

# пакетний розрахунок податку
@app.route('/calculer-impots', methods=['POST'])
def calculer_impots():
    # виконується контролер, пов'язаний з цією дією
    return front_controller()

# перелік-моделювання
@app.route('/lister-simulations', methods=['GET'])
def lister_simulations() -> tuple:
    # виконується контролер, пов'язаний з цією дією
    return front_controller()

# видалити-симуляцію
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
def supprimer_simulation(numero: int) -> tuple:
    # виконується контролер, пов'язаний з цією дією
    return front_controller()

# завершення сеансу
@app.route('/fin-session', methods=['GET'])
def fin_session() -> tuple:
    # виконується контролер, пов'язаний з цією дією
    return front_controller()

# показати-розрахунок-податку
@app.route('/afficher-calcul-impot', methods=['GET'])
def afficher_calcul_impot() -> tuple:
    # виконується контролер, пов'язаний з цією дією
    return front_controller()

# отримати-дані-адміністратора
@app.route('/get-admindata', methods=['GET'])
def get_admindata() -> tuple:
    # виконується контролер, пов'язаний з цією дією
    return front_controller()

def execute(name: str):
    # секретний ключ сеансу
    app.secret_key = os.urandom(12).hex()
    # Flask-Session
    app.config.update(SESSION_TYPE='redis',
                      SESSION_REDIS=config['redis_client'])
    Session(app)
    # випадок, коли додаток Flask запускається через консольний скрипт
    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], було переміщено до головного контролера:

Image


# імпорт залежностей
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):
    # надсилання електронного листа адміністратору додатка
    config_mail = config['parameters']['adminMail']
    config_mail["logger"] = config['logger']
    SendAdminMail.send(config_mail, message)

# головний контролер додатка
class MainController(InterfaceController):
    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        # обробка запиту
        logger = None
        action = None
        type_response1 = None
        try:
            # отримуються елементи шляху
            params = request.path.split('/')

            # дія є першим елементом
            action = params[1]

            # початково помилок немає
            erreur = False

            # тип сесії має бути відомий перед виконанням певних дій
            type_response1 = session.get('typeResponse')
            if type_response1 is None and action != "init-session":
                # помилка зафіксована
                résultat = {"action": action, "état"101,
                            "réponse"["pas de session en cours. Commencer par action [init-session]"]}
                erreur = True

            # заносити в журнал
            logger = Logger(config['parameters']['logsFilename'])

            # зберігаємо її у конфігурації, пов’язаній із потоком
            thread_config = {"logger": logger}
            thread_name = threading.current_thread().name
            config[thread_name] = {"config": thread_config}

            # заносимо запит у журнал
            logger.write(f"[MainController] requête : {request}\n")

            # потоку припиняють роботу, якщо це було запропоновано
            sleep_time = config['parameters']['sleep_time']
            if sleep_time != 0:
                # пауза є випадковою, щоб одні потоки були перервані, а інші — ні
                aléa = randint(01)
                if aléa == 1:
                    # запис у журнал перед паузою
                    logger.write(f"[front_controller] mis en pause du thread pendant {sleep_time} seconde(s)\n")
                    # пауза
                    time.sleep(sleep_time)

            # для виконання деяких дій потрібно пройти автентифікацію
            user = session.get('user')
            if not erreur and user is None and action not in ["init-session""authentifier-utilisateur"]:
                # помічено помилку
                résultat = {"action": action, "état"101,
                            "réponse"[f"action [{action}] demandée par utilisateur non authentifié"]}
                erreur = True

            # чи є помилки?
            if erreur:
                # запит недійсний
                status_code = status.HTTP_400_BAD_REQUEST
            else:
                # виконується контролер, пов'язаний з цією дією
                controller = config['mvc']['controllers'][action]
                résultat, status_code = controller.execute(request, session, config)

        except BaseException as exception:
            # інші (несподівані) винятки
            résultat = {"action": action, "état"131"réponse"[f"{exception}"]}
            status_code = status.HTTP_400_BAD_REQUEST

        finally:
            pass

        # результат, надісланий клієнту, записується в журнал
        log = f"[MainController] {résultat}\n"
        logger.write(log)

        # чи сталася фатальна помилка?
        if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
            # надсилається електронний лист адміністратору додатка
            send_adminmail(config, log)

        # визначається бажаний тип відповіді
        type_response2 = session.get('typeResponse')
        if type_response2 is None and type_response1 is None:
            # тип сеансу ще не встановлено — це буде jSON
            type_response = 'json'
        elif type_response2 is not None:
            # тип відповіді відомий і використовується в сесії
            type_response = type_response2
        else:
            # інакше продовжуємо використовувати type_response1
            type_response = type_response1

        # формується відповідь для відправлення
        response_builder = config['mvc']['responses'][type_response]
        response, status_code = response_builder \
            .build_http_response(request, session, config, status_code, résultat)

        # закриваємо файл журналу, якщо він був відкритий
        if logger:
            logger.close()

        # відправляється відповідь HTTP
        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

Ось приклад скрипта, що шифрує пароль, переданий йому як параметр:

Image

Скрипт [create_hashed_password] виглядає так (https://passlib.readthedocs.io/en/stable/):


import sys

# функція шифрування
from passlib.hash import pbkdf2_sha256

# очікується пароль для шифрування
syntaxe = f"{sys.argv[0]} password"
erreur = len(sys.argv) != 2
if erreur:
    print(f"syntaxe : {syntaxe}")
    sys.exit()
else:
    password = sys.argv[1]

# шифрується пароль
hash = pbkdf2_sha256.hash(password)
print(f"version cryptée de [{password}] = {hash}")

# перевірка
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

            # перевіряється правильність пари (користувач, пароль)
            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
            # знайдено?
            if not trouvé:

33.5. Тестування

Окрім тестування за допомогою браузера, можна також використовувати клієнти з папки [http-servers/07], написані для версії 12. Вони повинні «працювати» також і для версії 13:

Image

  • у [1] усі три клієнти мають працювати;
  • у [2] обидва тести мають працювати;