0. Introducción
Ya se vió en el apartado anterior que había una función "beforeware" que salvo los "endpoints" de recursos (imágenes, css, etc) verificaba que el usuario se había identificado correctamente, En caso contrario lo enviaba a una pantalla de "log in" con doble factor de identificación,
Por otra parte el tratamiento de los endpoints está definido en los siguientes módulos python:
- routes00comp.py : Pruebas de acciones que fectan al departamento de informatica
- routes01mnu.py: Acciones del menú y de "log in"
- routes02form: Acciones del formulario
- routes03grid:Acciones de la grid
1. Proceso de "Log in"
Para accedir a la pantalla de login de forma directa se introduce3 esta url:
https://edu.tavernes.es:5001/softprop/login
El tratamiento de este endpoint está en el módulo routes01mnu.py tal como se vió en la introducción
La pantalla es esta:
Primero te pide usuario y contraseña y luego te pide el segundo factor de autenticación.
1.1 Primera pantalla del log in
Solamente nos pide usuario y contraseña. El endpoint tratado en routes01mnu.py tiene el siguiente código específico para esta pantalla, donde se hace una llamada a xmloginform.login_form()
@app.get("/softprop/login") def get_login(): ''' Primero muestra el formulario de login sin campos de TOTP''' a=xmloginform.login_form() return a
Y el código específico para crear el formulario es:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | def login_form(): ''' Simple login form ''' # 1. js to save the tab:id in th session storage with open(f"{xmother.get_project_path()}/static/js/form_script.js", newline='') as f: myjs=f.read() # 2. Crete the random string of the tab_id tab_id = xmother.get_random_string() # 3. Fields in the form fields=[['usuari','text'],['paraula_de_pass','password'],['tab_id','hidden']] buttons=[ {'name':'Identificar-se', 'type':'submit', 'cls':'btn btn-primary'}, {'name':'Cancel·lar' , 'type':'reset' , 'cls':'btn btn-secondary'} ] return fh.Div( fh.Script(code=myjs), # Este js guarda en la session store el tab_id com id_tab fh.H2("Identificar-se al sistema"), fh.Form( *[ fh.Div( fh.Label(field[0].capitalize().replace("_"," ") , **{'cls':"form-label", 'for':field[0]}) if field[0] !='tab_id' else '', fh.Input( type=field[1], value=tab_id if field[0]=='tab_id' else "", ** {'name':field[0], 'id':field[0], 'cls':"form-control"}), cls="mb-3" ) for field in fields ], fh.Div( fh.Div(id="error-message", cls='text-danger text-center mb-3'), id="totp_div" ), fh.Div( *[fh.Button(button.get("name"), ** button) for button in buttons ], cls="d-flex justify-content-between" ), id="my_form", # COMMENT:hx_post="/softprop/validate_login?tab_id="+tab_id, hx_target="#error-message", hx_swap="innerHTML" hx_post="/softprop/validate_login?tab_id="+tab_id, hx_target="#totp_div", hx_swap="innerHTML" ), #fh.Form(id="logout-form", style="display-none", action="/logout", method="POST"), cls="card shadow-lg p-4", style="width: 350px;", ) |
Donde:
- Se recoge el código javascript desde el fichero específico "form_script.js" para ser insertado mediante la sentencia fh.Script() (Líneas 4 y 17)
- Se definen los campos (usuario y contraseña) y los tipos de controles que son y se insertan en el formulario (Líneas 10, 20-29)
- Se definen los botones de "Identificarse" y "cancelar" (Líneas 11-14, 33-36)
- Se define el campo de solo lectura de mensajes (Línea 29-32)
- Se redirige al endpoint "/softprop/validate_login" donde se recoge el mensaje resultante de la ejecución y se crea dicho mensaje mediante adición de código html (hx_target) en el "div" cuyo id es "totp_div"
1.2 Endpoint /softprop/validate de routes01menu.py que valida usuario y contraseña y el segundo factor de autenticación
Veamos el código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | @app.post("/softprop/validate_login") def post_login(req, login: xmclasses.Login): ''' 1. Verifica usuario y contraseña 2. Pregunta un doble factor de autenticación TOTP 3. Añade una cookie httponly con la clave "tab_id" con valores: 3.1 Usuario 3.2 Duracion una hora (3600 segundos) ''' # -------- COMPROBACION DE ERRORES ------- # 1. Si no autenticamos al usuario con el login y la password # Le devolvemos un error ldap_authenticated= xmldap.autheticate_by_login(login.usuari, login.paraula_de_pass) if not ldap_authenticated: print ("returning to first login page") return fh.Div("Error en l'usuari o la paraula de pas", cls="alert alert-danger") # 2. Comprobamos si tiene TOTP has_totp, totp, image_path=xmtotp.get_totp(login.usuari) # 3. Si no ha introducido el código TOTP mostramos la pantalla # para que lo ingrese no_digits=login.totp_digits is None if no_digits: print("Mostrando el input para introducir el TOTP") return xmother.get_totp_html(fh, has_totp, image_path) # 4. No ha introducido el TOTP correcto totp_correcto= xmtotp.validate_totp(totp, login.totp_digits, login.usuari) if not totp_correcto: print("Mostrando otra vez el input para introducir el TOTP") return xmother.get_totp_html(fh, has_totp, image_path, True) #------ VAMOS A LA PANTALLA DE MENU ---------------- #5. Si ha introducido el código TOTP correcto # Mostramos el menu a_user='' with xmdb.session_local() as db: a_user, myerror=xmsession.set_session(req=req, db=db, user=login.usuari, tab_id=login.tab_id) if len(a_user.strip())==0: print ("returning to the first login page") return fh.Div("Error al crear la sessió: "+myerror, cls="alert alert-danger") ck_dict={'httponly':True, 'max_age':3600} tab_id_dict={ "username": login.usuari } # COMMENT: sec_dict={'secure': True} cookies_dicts=[ # COMMENT: {'key':"session_id", 'value':session_id } | ck_dict, {'key':login.tab_id, 'value':json.dumps(tab_id_dict)} | ck_dict, # COMMENT: {'key':"username" , 'value':login.usuari} | ck_dict ] #COMMENT: redirection='/softprop/tree?tab_id='+urllib.parse.quote(login.tab_id) redirection='/softprop/tree?tab_id='+login.tab_id a=fh.RedirectResponse(redirection, status_code=303) for cookie in cookies_dicts: a.set_cookie(**cookie) print('passant per /softprop/validate_login i anem a /softprop/tree ja que ens hem validat') return a |
Donde:
- Primeramente se consulta al "LDAP" para verificar el usuario y contraseña. (Línea 13)
- En la primera pantalla solo se piede usuario y contraseña y no se tipde el código de 2º factor (TOTP). Es por ello que si despues de verificar el usuario y contraseña detecta que no se ha introducido el 2º factor, te añade el código HTML a la pantalla para pedir el 2º factor (Líneas 19-26)
- Se verifica si el código TOTP (2º factor) és correcto. Si no es corre cto lo vuelve a pedir (Líneas 28-32)
- Se guarda la sesión en BD. Si hubiera algún error al crearla, se reportaría (Líneas 37-44)
- En las líneas 45 a 57, se crea una cookie httponly (que se guarda en el navegador y es solo accesible desde el servidor para dar mayor seguridad) cuya duración es de una hora (3600 segundos). En dicha cookie se guardan estos valores:
- key: el tab_id generado
- value.username: el nombre del usuario
- httponly: true
- max_age: 3600
- Se redirige a la pantalla del menu tree (menú de arbol) con el código de status 303 que corresponde a la redirección
No hay comentarios :
Publicar un comentario