Skip to content

36. Ejercicio de aplicación: version 16

Image

36.1. Introducción

Los URL de nuestra aplicación tienen, por el momento, el formato [/action/param1/param2/…]. Nos gustaría poder anteponer un prefijo a estos URL. Por ejemplo, con el prefijo [/do], tendríamos URL con el formato [/do/action/param1/param2/…].

36.2. La nueva configuración de las rutas

Image

  • en [2], el cálculo de las rutas se modificará;
  • en [1], el archivo [config] se modifica para reflejar este cambio;

La configuración [config] queda de la siguiente manera:


def configure(config: dict) -> dict:
    # configuración de syspath
    import syspath
    config['syspath'] = syspath.configure(config)

    # configuración de la aplicación
    import parameters
    config['parameters'] = parameters.configure(config)

    # configuración de la base de datos
    import database
    config["database"] = database.configure(config)

    # instanciación de las capas de la aplicación
    import layers
    config['layers'] = layers.configure(config)

    # configuración MVC de la capa [web]
    config['mvc'] = {}

    # Configuración de los controladores de la capa [web]
    import controllers
    config['mvc'].update(controllers.configure(config))

    # acciones ASV (Acción Mostrar vista)
    import asv_actions
    config['mvc'].update(asv_actions.configure(config))

    # acciones ADS (Acción «Do Something»)
    import ads_actions
    config['mvc'].update(ads_actions.configure(config))

    # configuración de respuestas HTTP
    import responses
    config['mvc'].update(responses.configure(config))

    # configuración de rutas
    import routes
    routes.configure(config)

    # se aplica la configuración
    return config
  • líneas 37-39: el módulo [routes] (línea 38) se encarga de configurar las rutas (línea 39);

El archivo de rutas sin token CSRF [configs/routes_without_csrftoken] evoluciona de la siguiente manera:


# dependencias

from flask import redirect, request, session, url_for
from flask_api import status

# configuración de la aplicación
config = {}

# el controlador principal
def front_controller() -> tuple:
    # se reenvía la solicitud al controlador principal
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

# raíz de la aplicación
def index() -> tuple:
    # redirección a /init-session/html
    return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)

# init-session
def init_session(type_response: str) -> tuple:
    # se ejecuta el controlador asociado a la acción
    return front_controller()

# autenticar-usuario
def authentifier_utilisateur() -> tuple:
    # se ejecuta el controlador asociado a la acción
    return front_controller()

# calcular-impuesto
def calculer_impot() -> tuple:
    # se ejecuta el controlador asociado a la acción
    return front_controller()


Se han eliminado las rutas del archivo. Solo quedan las funciones asociadas a ellas.

Los archivos de rutas con el identificador CSRF y [configs/routes_with_csrftoken] corren la misma suerte:


# dependencias

from flask import redirect, request, session, url_for
from flask_api import status
from flask_wtf.csrf import generate_csrf

# configuración
config = {}

# el controlador frontal
def front_controller() -> tuple:
    # se reenvía la solicitud al controlador principal
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

# raíz de la aplicación
def index() -> tuple:
    # redirección a /init-session/html
    return redirect(url_for("init_session", type_response="html"csrf_token=generate_csrf()), status.HTTP_302_FOUND)

# init-session
def init_session(type_response: str, csrf_token: str) -> tuple:
    # se ejecuta el controlador asociado a la acción
    return front_controller()

# autenticar-usuario
def authentifier_utilisateur(csrf_token: str) -> tuple:
    # se ejecuta el controlador asociado a la acción
    return front_controller()



# iniciar sesión sin token CSRF para clients, json y xml
def init_session_without_csrftoken(type_response: str) -> tuple:
    # redirección a /init-session/type_response
    return redirect(url_for("init_session", type_response=type_response, csrf_token=generate_csrf()),
                    status.HTTP_302_FOUND)

Las rutas se calculan mediante el siguiente módulo [configs/routes]:


from flask import Flask

def configure(config: dict):
    # configuración de rutas

    # aplicación Flask
    app = Flask(__name__, template_folder="../flask/templates", static_folder="../flask/static")
    config['app'] = app

    # importación de rutas de la aplicación web
    if config['parameters']['with_csrftoken']:
        import routes_with_csrftoken as routes
    else:
        import routes_without_csrftoken as routes

    # se inyecta la configuración en las rutas
    routes.config = config

    # el prefijo de URL de la aplicación
    prefix_url = config["parameters"]["prefix_url"]

    # token CSRF
    with_csrftoken = config["parameters"]['with_csrftoken']
    if with_csrftoken:
        csrftoken_param = f"/<string:csrf_token>"
    else:
        csrftoken_param = ""

    # las rutas de la aplicación Flask
    # raíz de la aplicación
    app.add_url_rule(f'{prefix_url}/', methods=['GET'],
                     view_func=routes.index)

    # inicio de sesión
    app.add_url_rule(f'{prefix_url}/init-session/<string:type_response>{csrftoken_param}', methods=['GET'],
                     view_func=routes.init_session)

    # iniciar sesión sin token CSRF
    if with_csrftoken:
        app.add_url_rule(f'{prefix_url}/init-session-without-csrftoken/<string:type_response>',
                         methods=['GET'],
                         view_func=routes.init_session_without_csrftoken)

    # autenticar-usuario
    app.add_url_rule(f'{prefix_url}/authentifier-utilisateur{csrftoken_param}', methods=['POST'],
                     view_func=routes.authentifier_utilisateur)

    # calcular-impuesto
    app.add_url_rule(f'{prefix_url}/calculer-impot{csrftoken_param}', methods=['POST'],
                     view_func=routes.calculer_impot)

    # cálculo del impuesto por lotes
    app.add_url_rule(f'{prefix_url}/calculer-impots{csrftoken_param}', methods=['POST'],
                     view_func=routes.calculer_impots)

    # listar-simulaciones
    app.add_url_rule(f'{prefix_url}/lister-simulations{csrftoken_param}', methods=['GET'],
                     view_func=routes.lister_simulations)

    # eliminar-simulación
    app.add_url_rule(f'{prefix_url}/supprimer-simulation/<int:numero>{csrftoken_param}', methods=['GET'],
                     view_func=routes.supprimer_simulation)

    # fin-sesión
    app.add_url_rule(f'{prefix_url}/fin-session{csrftoken_param}', methods=['GET'],
                     view_func=routes.fin_session)

    # mostrar-cálculo-impuestos
    app.add_url_rule(f'{prefix_url}/afficher-calcul-impot{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_calcul_impot)

    # get-datos-de-administración
    app.add_url_rule(f'{prefix_url}/get-admindata{csrftoken_param}', methods=['GET'],
                     view_func=routes.get_admindata)

    # mostrar-vista-cálculo-impuestos
    app.add_url_rule(f'{prefix_url}/afficher-vue-calcul-impot{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_calcul_impot)

    # mostrar-vista-autenticación
    app.add_url_rule(f'{prefix_url}/afficher-vue-authentification{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_authentification)

    # mostrar-vista-lista-simulaciones
    app.add_url_rule(f'{prefix_url}/afficher-vue-liste-simulations{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_liste_simulations)

    # mostrar-vista-lista_errores
    app.add_url_rule(f'{prefix_url}/afficher-vue-liste-erreurs{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_liste_erreurs)

    # caso especial
    if with_csrftoken:
        # iniciar-sesión-sin-token-csrf para los clients, json y xml
        app.add_url_rule(f'{prefix_url}/init-session-without-csrftoken', methods=['GET'],
                         view_func=routes.init_session_without_csrftoken)
  • líneas 6-8: se crea la aplicación Flask y se incluye en la configuración;
  • líneas 10-14: se importa el archivo de rutas adecuado para la situación. Recordemos que el archivo importado carece, de hecho, de rutas. Solo contiene las funciones asociadas a ellas;
  • línea 17: las funciones asociadas a las rutas necesitan conocer la configuración de la aplicación;
  • línea 20: se anota el prefijo de URL. Este puede estar vacío;
  • líneas 22-27: las rutas con el token CSRF tienen un parámetro adicional respecto a las que no lo tienen. Para gestionar esta diferencia, se utiliza la variable [csrftoken_param]:
    • contiene la cadena vacía si no hay ningún token CSRF en las rutas;
    • contiene la cadena [/<string:csrf_token>] si hay un token CSRF;
  • líneas 29-96: cada ruta de la aplicación está asociada a una función del archivo de rutas importado en las líneas 10-14;

36.3. Los nuevos controladores

Image

En el version anterior, todos los controladores obtenían la acción que se estaba procesando de la siguiente manera:


    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        # se recuperan los elementos del path
        params = request.path.split('/')
        action = params[1]

Este código ya no funciona si hay un prefijo, por ejemplo, [/do/lister-simulations]. En este caso, la acción de la línea 3 sería [do] y, por lo tanto, sería incorrecta.

Transformamos este código de la siguiente manera:


    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        # se recuperan los elementos del path
        prefix_url = config["parameters"]["prefix_url"]
        params = request.path[len(prefix_url):].split('/')
        action = params[1]

Hacemos esto en todos los controladores.

36.4. Los nuevos modelos

Image

En el version anterior, cada clase de modelo generaba un modelo de la siguiente manera:


def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        # se encapsulan los datos de la página en la plantilla
        modèle = {}
        # estado de la aplicación
        état = résultat["état"]
        # la plantilla depende del estado
        

        # token CSRF
        modèle['csrf_token'] = super().get_csrftoken(config)

        # acciones posibles desde la vista
        modèle['actions_possibles'] = []

        # se devuelve la plantilla
        return modèle

A partir de ahora, la plantilla tendrá una clave adicional, el prefijo de URL:


def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        # encapsulamos los datos de la página en el modelo
        modèle = {}
        # estado de la aplicación
        état = résultat["état"]
        # la plantilla depende del estado
        

        # token CSRF
        modèle['csrf_token'] = super().get_csrftoken(config)

        # acciones posibles desde la vista
        modèle['actions_possibles'] = []
    
        # prefijo de URL
        modèle["prefix_url"] = config["parameters"]["prefix_url"]

        # se devuelve la plantilla
        return modèle

36.5. Los nuevos fragmentos

Image

Todos los fragmentos que contengan URL deben modificarse.

El fragmento [v-authentification]


<!-- formulario HTML - se envían sus valores con la acción [authentifier-utilisateur] -->
<form method="post" action="{{modèle.prefix_url}}/authentifier-utilisateur{{modèle.csrf_token}}">

    <!-- título -->
    <div class="alert alert-primary" role="alert">
        <h4>Veuillez vous authentifier</h4>
    </div>


</form>

El fragmento [v-calcul-impot]


<!-- formulario HTML enviado -->
<form method="post" action="{{modèle.prefix_url}}/calculer-impot{{modèle.csrf_token}}">
    <!-- mensaje en 12 columnas sobre fondo azul -->
    

</form>

El fragmento [v-liste-simulations]




{% if modèle.simulations is defined and modèle.simulations|length!=0 %}


<!-- tabla de simulaciones -->
<table class="table table-sm table-hover table-striped">
    
    <tr>
        <th scope="row">{{simulation.id}}</th>
        <td>{{simulation.marié}}</td>
        <td>{{simulation.enfants}}</td>
        <td>{{simulation.salaire}}</td>
        <td>{{simulation.impôt}}</td>
        <td>{{simulation.surcôte}}</td>
        <td>{{simulation.décôte}}</td>
        <td>{{simulation.réduction}}</td>
        <td>{{simulation.taux}}</td>
        <td><a href="{{modèle.prefix_url}}/supprimer-simulation/{{simulation.id}}{{modèle.csrf_token}}">Supprimer</a></td>
    </tr>
    {% endfor %}
    </tr>
    </tbody>
</table>
{% endif %}

El fragmento [v-menu]


<!-- menú Bootstrap -->
<nav class="nav flex-column">
    <!-- visualización de una lista de enlaces HTML -->
    {% for optionMenu in modèle.optionsMenu %}
    <a class="nav-link" href="{{modèle.prefix_url}}{{optionMenu.url}}{{modèle.csrf_token}}">{{optionMenu.text}}</a>
    {% endfor %}
</nav>

36.6. La nueva respuesta HTML

Image

En el anterior version, el código de la respuesta HTML terminaba con una redirección:


class HtmlResponse(InterfaceResponse):

    def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
                            résultat: dict) -> (Response, int):
        # la respuesta HTML depende del código de estado devuelto por el controlador
        état = résultat["état"]

        

        # ahora hay que generar el URL de redireccionamiento sin olvidar el token CSRF si se solicita
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""

        # respuesta de redireccionamiento
        return redirect(f"{ads['to']}{csrf_token}"), status.HTTP_302_FOUND

Este código pasa a ser ahora el siguiente:


def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
                            résultat: dict) -> (Response, int):
        

        # Ahora hay que generar el URL de redireccionamiento sin olvidar el token CSRF si se solicita
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""

        # respuesta de redireccionamiento
        return redirect(f"{config['parameters']['prefix_url']}{ads['to']}{csrf_token}"), status.HTTP_302_FOUND

36.7. Pruebas

Image

Pongamos el siguiente prefijo en el archivo de configuración [parameters]:



        # token csrf
        "with_csrftoken"True,
        # bases gestionadas MySQL (mysql), PostgreSQL (pgres)
        "databases"["mysql""pgres"],
        # prefijo de los URL de la aplicación
        # establecer la cadena como vacía si no se desea ningún prefijo o /prefijo en caso contrario
        "prefix_url""/do",

Ejecutemos la aplicación y solicitemos el URL [http://localhost:5000/do]; la respuesta es la siguiente:

Image

  • en [1], el prefijo de URL;
  • en [2], el token CSRF;

La aplicación también se puede probar con las pruebas de consola de [http-clients/09]:

Image

En los archivos de configuración [1] y [2], el prefijo de URL debe incluirse en el URL del servidor:


        "server": {
            # "urlServer": "http://127.0.0.1:5000",
            "urlServer""http://127.0.0.1:5000/do",
            "user": {
                "login""admin",
                "password""admin"
            },
            "url_services": {
                
            }
  • línea 3: el URL del servidor incluye ahora el prefijo de URL;

Una vez realizada esta modificación, todas las pruebas de consola deberían funcionar.