Skip to content

33. Exercício prático: versão 13

A versão 13 altera a versão 12 nos seguintes pontos:

  • reestrutura-se certas partes do código (refatoração);
  • a sessão é gerenciada de maneira diferente por meio do módulo [flask_session];
  • utilizam-se senhas criptografadas no arquivo de configuração;

A versão 13 é obtida inicialmente por meio da cópia da versão 12:

Image

Image

  • no [1], a configuração será refatorada. Ela será removida, notadamente, da pasta [flask];
  • em [2], o script principal será refatorado. Ele também será removido da pasta [flask];
  • em [3], a configuração será dividida em vários arquivos;
  • no [4]: vamos simplificar o script principal [main], transferindo parte do código para outros arquivos;
  • em [5], o controlador de autenticação será modificado, já que, a partir de agora, as senhas dos usuários serão criptografadas;
  • no [6], o controlador principal incorporará o código anteriormente presente no script principal [main];

33.1. Reestruturação da configuração do aplicativo

Image

Aparecem três novos arquivos de configuração:

  • [mvc]: para configurar a arquitetura MVC do aplicativo;
  • [parameters]: que reunirá todas as constantes do aplicativo;
  • [syspath]: que configura o Python Path do aplicativo;

O arquivo [syspath.py] é o seguinte:


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

    # pasta deste arquivo
    script_dir = os.path.dirname(os.path.abspath(__file__))

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

    # dependências
    absolute_dependencies = [
        # pastas do projeto
        # 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",
        # Constantes, faixas
        f"{root_dir}/impots/v05/entities",
        # Logger, SendAdminMail
        f"{root_dir}/impots/http-servers/02/utilities",
        # pasta do script principal
        script_dir,
        # configurações [database, layers, parameters, controllers, views]
        f"{script_dir}/../configs",
        # controladores
        f"{script_dir}/../controllers",
        # respostas HTTP
        f"{script_dir}/../responses",
        # modelos de visualizações
        f"{script_dir}/../models_for_views",
    ]

    # definimos o syspath
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # fazemos a configuração
    return {
        "root_dir": root_dir,
        "script_dir": script_dir
    }
  • o script [syspath] serve para configurar o Python Path do aplicativo (linhas 40-41);
  • ele fornece duas informações úteis para os outros scripts de configuração (linhas 45-46);

O script [mvc] configura a arquitetura MVC do aplicativo web jSON / XML / HTML:


def configure(config: dict) -> dict:
    # configuração do aplicativo MVC

    # os controladores
    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
    # as respostas HTTP
    from HtmlResponse import HtmlResponse
    from JsonResponse import JsonResponse
    from XmlResponse import XmlResponse
    # modelos das visualizações
    from ModelForAuthentificationView import ModelForAuthentificationView
    from ModelForCalculImpotView import ModelForCalculImpotView
    from ModelForErreursView import ModelForErreursView
    from ModelForListeSimulationsView import ModelForListeSimulationsView

    # ações autorizadas e seus controladores
    controllers = {
        # inicialização de uma sessão de cálculo
        "init-session": InitSessionController(),
        # autenticação de um usuário
        "authentifier-utilisateur": AuthentifierUtilisateurController(),
        # cálculo do imposto no modo individual
        "calculer-impot": CalculerImpotController(),
        # cálculo do imposto no modo em lote
        "calculer-impots": CalculerImpotsController(),
        # lista de simulações
        "lister-simulations": ListerSimulationsController(),
        # exclusão de uma simulação
        "supprimer-simulation": SupprimerSimulationController(),
        # encerramento da sessão de cálculo
        "fin-session": FinSessionController(),
        # exibição da tela de cálculo do imposto
        "afficher-calcul-impot": AfficherCalculImpotController(),
        # obtenção dos dados da administração fiscal
        "get-admindata": GetAdminDataController(),
        # controlador principal
        "main-controller": MainController()
    }
    # os diferentes tipos de resposta (json, xml, html)
    responses = {
        "json": JsonResponse(),
        "html": HtmlResponse(),
        "xml": XmlResponse()
    }
    # as visualizações HTML e seus modelos dependem do status retornado pelo controlador
    views = [
        {
            # visualização de autenticação
            "états": [
                700,    # /init-session – sucesso
                201,    # /autenticar-usuário – falha
            ],
            "view_name""views/vue-authentification.html",
            "model_for_view": ModelForAuthentificationView()
        },
        {
            # visualização do cálculo do imposto
            "états"[
                200,    # /autenticar-usuário bem-sucedido
                300,    # /calcular-imposto bem-sucedido
                301,    # /calcular-imposto falha
                800,    # /exibir-cálculo-do-imposto bem-sucedido
            ],
            "view_name""views/vue-calcul-impot.html",
            "model_for_view": ModelForCalculImpotView()
        },
        {
            # visualização da lista de simulações
            "états"[
                500,    # /listar-simulações (sucesso)
                600,    # /excluir-simulação bem-sucedido
            ],
            "view_name""views/vue-liste-simulations.html",
            "model_for_view": ModelForListeSimulationsView()
        },

    ]
    # visualização de erros inesperados
    view_erreurs = {
        "view_name""views/vue-erreurs.html",
        "model_for_view": ModelForErreursView()
    }
    # redirecionamentos
    redirections = [
        {
            "états": [
                400,  # /encerrar-sessão bem-sucedido
            ],
            # redirecionamento para o URL
            "to""/init-session/html",
        }
    ]


    # configuração sendo restaurada para MVC
    return {
        # controladores
        "controllers": controllers,
        # respostas HTTP
        "responses": responses,
        # visualizações e modelos
        "views": views,
        # lista de redirecionamentos
        "redirections": redirections,
        # visualização de erros inesperados
        "view_erreurs": view_erreurs
    }
  • linhas 1-101: esse código é conhecido;
  • linhas 105-116: é gerada a configuração MVC do aplicativo;

O script [parameters] reúne as constantes do aplicativo:


def configure(config: dict) -> dict:
    # configuração do aplicativo

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

    # configuração do aplicativo
    parameters = {
        # usuários autorizados a usar o aplicativo
        "users"[
            {
                "login""admin",
                "password""$pbkdf2-sha256$29000$mPM.h3COkTIGYOzde68VIg$7LH5Q7rN/1hW.Xa.6rcmR6h52PntvVqd5.na7EtgQNw"
            }
        ],
        # arquivo de logs
        "logsFilename"f"{script_dir}/../data/logs/logs.txt",
        # configuração do servidor SMTP
        "adminMail": {
            # servidor SMTP
            "smtp-server""localhost",
            # porta do servidor SMTP
            "smtp-port""25",
            # administrador
            "from""guest@localhost.com",
            "to""guest@localhost.com",
            # assunto do e-mail
            "subject""plantage du serveur de calcul d'impôts",
            # TLS definido como True se o servidor SMTP exigir autenticação; caso contrário, definido como False
            "tls"False
        },
        # duração da pausa do thread em segundos
        "sleep_time"0,
        # servidor Redis
        "redis": {
            "host""127.0.0.1",
            "port"6379
        },
    }

    # fazemos a configuração do aplicativo
    return parameters
  • linha 13: a partir de agora, as senhas dos usuários serão criptografadas;
  • linhas 34-38: a configuração de um servidor [Redis], sobre o qual voltaremos a falar;

Com esses novos arquivos de configuração, o script [config] fica da seguinte forma:


def configure(config: dict) -> dict:
    # configuração do syspath
    import syspath
    config['syspath'] = syspath.configure(config)

    # configuração do aplicativo
    import parameters
    config['parameters'] = parameters.configure(config)

    # configuração do banco de dados
    import database
    config["database"] = database.configure(config)

    # instanciação das camadas do aplicativo
    import layers
    config['layers'] = layers.configure(config)

    # configuração MVC da camada [web]
    import mvc
    config['mvc'] = mvc.configure(config)

    # retornamos a configuração
    return config

33.2. Reestruturação do script principal [main]

Image

O script principal [main.py] limita-se a iniciar o servidor:


# aguarda um parâmetro mysql ou 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()

# configura-se o aplicativo
import config
config = config.configure({'sgbd': sgbd})

# dependências
from SendAdminMail import SendAdminMail
from Logger import Logger
from ImpôtsError import ImpôtsError
import redis

# envio de um e-mail ao administrador
def send_adminmail(config: dict, message: str):
    # envio de um e-mail ao administrador do aplicativo
    config_mail = config['parameters']['adminMail']
    config_mail["logger"] = config['logger']
    SendAdminMail.send(config_mail, message)

# verificação do arquivo de logs
logger = None
erreur = False
message_erreur = None
try:
    # registrador de logs
    logger = Logger(config['parameters']['logsFilename'])
except BaseException as exception:
    # log da console
    print(f"L'erreur suivante s'est produite : {exception}")
    # registra-se o erro
    erreur = True
    message_erreur = f"{exception}"
# o logger é armazenado na configuração
config['logger'] = logger
# gestão de erros
if erreur:
    # e-mail para o administrador
    send_adminmail(config, message_erreur)
    # encerramento do aplicativo
    sys.exit(1)

# log de inicialização
log = "[serveur] démarrage du serveur"
logger.write(f"{log}\n")

# verificando a disponibilidade do servidor Redis
redis_client = redis.Redis(host=config["parameters"]["redis"]["host"],
                           port=config["parameters"]["redis"]["port"])
# fazendo ping no servidor Redis
try:
    redis_client.ping()
except BaseException as exception:
    # Redis indisponível
    log = f"[serveur] Le serveur Redis n'est pas disponible : {exception}"
    # console
    print(log)
    # log
    logger.write(f"{log}\n")
    # fim
    sys.exit(1)

# o cliente [redis] é armazenado na configuração
config['redis_client'] = redis_client

# recuperação dos dados da administração fiscal
erreur = False
try:
    # admindata será um dado de escopo de aplicação somente para leitura
    config["admindata"] = config["layers"]["dao"].get_admindata()
    # registro de sucesso
    logger.write("[serveur] connexion à la base de données réussie\n")
except ImpôtsError as ex:
    # registro do erro
    erreur = True
    # registro de erro
    log = f"L'erreur suivante s'est produite : {ex}"
    # console
    print(log)
    # arquivo de logs
    logger.write(f"{log}\n")
    # e-mail para o administrador
    send_adminmail(config, log)

# o thread principal não precisa mais do logger
logger.close()

# se houver erro, o processo é interrompido
if erreur:
    sys.exit(2)

# importação das rotas do aplicativo web
import routes
routes.config=config
routes.execute(__name__)
  • linhas 56-73: introduz-se um servidor Redis e seu cliente;
  • linhas 102-104: as rotas da aplicação foram externalizadas no script [routes];

33.2.1. Os módulos [flask_session] e [redis]

O servidor [Redis] será utilizado para armazenar as sessões dos usuários. Utilizaremos o módulo [flask_session] para gerenciar essas sessões. Esse módulo pode armazenar as sessões dos usuários em vários locais. O Redis é um deles e é o que utilizaremos.

O módulo [flask_session] deve ser instalado em um terminal PyCharm:


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

Para se comunicar com o servidor Redis, precisamos de um cliente Redis. Esse cliente será fornecido pelo módulo [redis], que também instalaremos:


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

33.2.2. O servidor Redis

O servidor Redis servirá para armazenar as sessões dos usuários. O módulo [flask_session] funciona da seguinte maneira:

  • cada usuário possui um identificador de sessão, e é exatamente isso que é enviado ao cliente — e somente isso. O cliente recebe um cookie de sessão apenas uma vez, após sua primeira solicitação. Esse cookie contém o identificador de sessão do usuário, que não mudará ao longo das solicitações do cliente. É por isso que o servidor não precisa reenviar um novo cookie de sessão;
  • anteriormente, o conteúdo da sessão era enviado ao cliente. Isso não acontecerá mais. O conteúdo da sessão do usuário será armazenado no servidor Redis;

O Laragon vem com um servidor Redis desativado por padrão. Portanto, é preciso começar ativando-o:

Image

  • em [3], ative o servidor [Redis];
  • em [4], mantenha a porta [6379] que os clientes Redis utilizam por padrão;

Os serviços do Laragon são reiniciados automaticamente após a ativação do Redis:

Image

O servidor Redis pode ser consultado no modo de comando. Abra um terminal Laragon [6]:

Image

  • em [1], o comando [redis-cli] inicia o cliente no modo de comando do servidor Redis;

Em julho de 2019, o cliente Redis pode utilizar 172 comandos para se comunicar com o servidor [https://redis.io/commands#list]. Um deles, [command count] [2], exibe este número: [3].

A gravação em [Redis] é feita com o comando Redis [set attribut valeur] [4]. O valor pode então ser recuperado com o comando [get attribut] [5].

Pode ser necessário esvaziar a memória do Redis. Isso é feito com o comando [flushdb] [6]. Em seguida, se solicitarmos o valor do atributo [titre] [7], obteremos uma referência [nil] [8] indicando que o atributo não foi encontrado. Também é possível usar o comando [exists] [9-10] para verificar a existência de um atributo.

Para sair do cliente Redis, digite o comando [quit] [11].

Também é possível usar uma interface web para gerenciar as chaves presentes no servidor Redis. Para isso, é necessário que o servidor Apache do Laragon esteja em execução:

Image

Aparece a seguinte interface:

Image

  • em [1-4], uma das sessões armazenadas no servidor Redis;

33.2.3. Gerenciamento do servidor Redis no script principal [main]

O script [main] verifica a presença do servidor Redis da seguinte maneira:


import redis

# verifica-se a disponibilidade do servidor Redis
redis_client = redis.Redis(host=config["parameters"]["redis"]["host"],
                           port=config["parameters"]["redis"]["port"])
# faz-se um ping no servidor Redis
try:
    redis_client.ping()
except BaseException as exception:
    # Redis indisponível
    log = f"[serveur] Le serveur Redis n'est pas disponible : {exception}"
    # console
    print(log)
    # log
    logger.write(f"{log}\n")
    # fim
    sys.exit(1)

# o cliente [redis] é armazenado na configuração
config['redis_client'] = redis_client
  • linha 4: o construtor da classe [redis.Redis] cria um cliente do servidor Redis. As características desse cliente (endereço, porta) são encontradas no script [parameters];
  • linha 8: o método [ping] permite verificar se o servidor Redis está presente;
  • linhas 9-17: se o ping não for bem-sucedido, o erro é registrado no log e o servidor é desligado;
  • linha 20: a referência do cliente Redis é inserida na configuração;

33.2.4. Gerenciamento de rotas no script principal [main]

O gerenciamento de rotas no [main] limita-se às seguintes linhas:


# importação das rotas do aplicativo web
import routes
routes.config=config
routes.execute(__name__)
  • linha 1: as rotas foram externalizadas no módulo [routes];
  • linha 3: as rotas precisam conhecer a configuração da execução;
  • linha 4: inicia-se a aplicação Flask, passando-lhe o nome do script executado (__main__);

O script das rotas é o seguinte:


# dependências
import os

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

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

# configuração da aplicação
config = {}

# o controlador principal
def front_controller() -> tuple:
    # a solicitação é encaminhada para o controlador principal
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

@app.route('/', methods=['GET'])
def index() -> tuple:
    # redirecionamento para /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:
    # é executado o controlador associado à ação
    return front_controller()

# autenticar-usuário
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur() -> tuple:
    # executa-se o controlador associado à ação
    return front_controller()

# calcular-imposto
@app.route('/calculer-impot', methods=['POST'])
def calculer_impot() -> tuple:
    # executa-se o controlador associado à ação
    return front_controller()

# cálculo do imposto em lotes
@app.route('/calculer-impots', methods=['POST'])
def calculer_impots():
    # executa-se o controlador associado à ação
    return front_controller()

# listar simulações
@app.route('/lister-simulations', methods=['GET'])
def lister_simulations() -> tuple:
    # executa-se o controlador associado à ação
    return front_controller()

# excluir-simulação
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
def supprimer_simulation(numero: int) -> tuple:
    # é executado o controlador associado à ação
    return front_controller()

# fim-sessão
@app.route('/fin-session', methods=['GET'])
def fin_session() -> tuple:
    # executa-se o controlador associado à ação
    return front_controller()

# exibir-cálculo-imposto
@app.route('/afficher-calcul-impot', methods=['GET'])
def afficher_calcul_impot() -> tuple:
    # executa-se o controlador associado à ação
    return front_controller()

# obter-dados-administrativos
@app.route('/get-admindata', methods=['GET'])
def get_admindata() -> tuple:
    # é executado o controlador associado à ação
    return front_controller()

def execute(name: str):
    # chave secreta da sessão
    app.secret_key = os.urandom(12).hex()
    # Flask-Session
    app.config.update(SESSION_TYPE='redis',
                      SESSION_REDIS=config['redis_client'])
    Session(app)
    # caso em que a aplicação Flask seja iniciada por meio de um script de console
    if name == '__main__':
        app.config.update(ENV="development", DEBUG=True)
        app.run(threaded=True)
  • linha 9: a aplicação Flask é instanciada;
  • linha 12: a configuração da aplicação ainda não é conhecida no momento da escrita do script. Ela só é conhecida no momento da execução;
  • linhas 20-77: as rotas da aplicação, tal como estavam definidas na versão anterior. Isso não muda;
  • linhas 14-18: todas as rotas se limitam a chamar a função [front_controller]. Removemos o código inicial dessa função. Agora, ela se limita a chamar o controlador principal do aplicativo web;
  • linhas 79-89: [execute] é a função chamada pelo script [main] para iniciar o aplicativo web;
  • linha 81: o módulo [flask_session] utiliza a chave secreta do Flask;
  • linhas 82-84: configuração do módulo [flask_session]. Ela consiste em adicionar as chaves [SESSION_TYPE, SESSION_REDIS] à configuração [app.config] do aplicativo Flask [app]:
  • [SESSION_TYPE]: o tipo da sessão. Existem vários tipos. O tipo [redis] indica que [flask_session] utiliza um servidor [redis] para armazenar as sessões dos usuários. Por isso, é necessário definir a chave [SESSION_REDIS], que deve ser a referência de um cliente Redis;
  • linha 85: a sessão [Flask-Session] está associada ao aplicativo Flask;
  • linhas 86-89: se o parâmetro [name] da linha 79 for a string [__main__], então a aplicação Flask é iniciada;

33.3. Reestruturação do controlador principal

O código que antes estava na função [front_controller] do script [main] foi movido para o controlador principal:

Image


# importação de dependências
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):
    # envio de um e-mail ao administrador da aplicação
    config_mail = config['parameters']['adminMail']
    config_mail["logger"] = config['logger']
    SendAdminMail.send(config_mail, message)

# controlador principal da aplicação
class MainController(InterfaceController):
    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        # processamento da solicitação
        logger = None
        action = None
        type_response1 = None
        try:
            # recuperam-se os elementos do caminho
            params = request.path.split('/')

            # a ação é o primeiro elemento
            action = params[1]

            # sem erros iniciais
            erreur = False

            # o tipo de sessão deve ser conhecido antes de determinadas ações
            type_response1 = session.get('typeResponse')
            if type_response1 is None and action != "init-session":
                # o erro é registrado
                résultat = {"action": action, "état"101,
                            "réponse"["pas de session en cours. Commencer par action [init-session]"]}
                erreur = True

            # registrar no log
            logger = Logger(config['parameters']['logsFilename'])

            # armazenamos em uma configuração associada ao thread
            thread_config = {"logger": logger}
            thread_name = threading.current_thread().name
            config[thread_name] = {"config": thread_config}

            # registra-se a solicitação
            logger.write(f"[MainController] requête : {request}\n")

            # interrompe-se o thread, caso tenha sido solicitado
            sleep_time = config['parameters']['sleep_time']
            if sleep_time != 0:
                # a pausa é aleatória para que alguns threads sejam interrompidos e outros não
                aléa = randint(01)
                if aléa == 1:
                    # registro antes da pausa
                    logger.write(f"[front_controller] mis en pause du thread pendant {sleep_time} seconde(s)\n")
                    # pausa
                    time.sleep(sleep_time)

            # para algumas ações, é necessário estar autenticado
            user = session.get('user')
            if not erreur and user is None and action not in ["init-session""authentifier-utilisateur"]:
                # observa-se o erro
                résultat = {"action": action, "état"101,
                            "réponse"[f"action [{action}] demandée par utilisateur non authentifié"]}
                erreur = True

            # há erros?
            if erreur:
                # a solicitação é inválida
                status_code = status.HTTP_400_BAD_REQUEST
            else:
                # o controlador associado à ação é executado
                controller = config['mvc']['controllers'][action]
                résultat, status_code = controller.execute(request, session, config)

        except BaseException as exception:
            # outras exceções (inesperadas)
            résultat = {"action": action, "état"131"réponse"[f"{exception}"]}
            status_code = status.HTTP_400_BAD_REQUEST

        finally:
            pass

        # o resultado enviado ao cliente está sendo registrado no log
        log = f"[MainController] {résultat}\n"
        logger.write(log)

        # houve algum erro fatal?
        if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
            # envia-se um e-mail ao administrador do aplicativo
            send_adminmail(config, log)

        # determina-se o tipo desejado para a resposta
        type_response2 = session.get('typeResponse')
        if type_response2 is None and type_response1 is None:
            # o tipo de sessão ainda não foi definido — será jSON
            type_response = 'json'
        elif type_response2 is not None:
            # o tipo da resposta é conhecido e está na sessão
            type_response = type_response2
        else:
            # caso contrário, continua-se a usar type_response1
            type_response = type_response1

        # construímos a resposta a ser enviada
        response_builder = config['mvc']['responses'][type_response]
        response, status_code = response_builder \
            .build_http_response(request, session, config, status_code, résultat)

        # fecha-se o arquivo de logs, caso tenha sido aberto
        if logger:
            logger.close()

        # envia-se a resposta HTTP
        return response, status_code

Todo esse código já foi visto em algum momento.

33.4. Gerenciamento de senhas criptografadas

Para gerenciar senhas criptografadas, utilizaremos o módulo [passlib], que é instalado a partir de um terminal do PyCharm:


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

Aqui está um exemplo de script que criptografa a senha passada como parâmetro:

Image

O script [create_hashed_password] é o seguinte (https://passlib.readthedocs.io/en/stable/):


import sys

# função de criptografia
from passlib.hash import pbkdf2_sha256

# aguarda a senha a ser criptografada
syntaxe = f"{sys.argv[0]} password"
erreur = len(sys.argv) != 2
if erreur:
    print(f"syntaxe : {syntaxe}")
    sys.exit()
else:
    password = sys.argv[1]

# criptografa-se a senha
hash = pbkdf2_sha256.hash(password)
print(f"version cryptée de [{password}] = {hash}")

# verificação
correct = pbkdf2_sha256.verify(password, hash)
print(correct)
  • linha 16: criptografa-se a senha passada como parâmetro;
  • linha 20: compara-se a senha [password] passada como parâmetro com sua versão criptografada [hash]. A função [verify] criptografa a senha [password] e compara a sequência criptografada obtida com [hash]. Retorna True se as duas sequências forem iguais;

O script acima nos permite obter a versão criptografada da senha [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

Linha 2, o valor que inserimos no script [parameters]:


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

O controlador de autenticação [AuthentifierUtilisateurController] evolui da seguinte forma:


from passlib.handlers.pbkdf2 import pbkdf2_sha256

            # verificando a validade do par (usuário, senha)
            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
            # Encontrado?
            if not trouvé:

33.5. Testes

Além dos testes com um navegador, também é possível utilizar os clientes da pasta [http-servers/07], escritos para a versão 12. Eles devem funcionar também na versão 13:

Image

  • no [1], os três clientes devem funcionar;
  • no [2], os dois testes devem funcionar;