martes, 26 de agosto de 2025

DBeaver. Obtención de las contraseñas de las conexiones a las BD

 Desde el vídeo de Codewis explican como extraer las contraseñas de las conexxiones a las BBDD.

1. File - Export -DBeaver- Project -Next


2. Elegimos "General y le indicamos fichero y carpeta


3. Abrimos elfichero "General-20250826.dbp" con un editor de fichero comprimidos y extraemos el fichero "credentials-confog.json"

4. Abrimos una ventana de comando y nos vamos a la carpeta donde hemos dejado el fichero  "credentials-config.json" y ejecutamos:

openssl aes-128-cbc -d -K babb4a9f774ab853c96c2d653dfe544a -iv 00000000000000000000000000000000 -in "credentials-config.json" >credenciales.txt

Con ello desencriptamos el contenido en un fichero dentro de otro fichero "credenciales.txt" pero que hay que tener cuidado al abrirlo pues tiene algunos caracteresd extraños que algunos programas de trataiento de texto no pueden leer. 

A una mala , se puede obviar la parte que final de la sentincia que lo vuelca a un fichero:

openssl aes-128-cbc -d -K babb4a9f774ab853c96c2d653dfe544a -iv 00000000000000000000000000000000 -in "credentials-config.json" 

y se puede ver por pantalla. 




lunes, 25 de agosto de 2025

WEBPROPv2 (V). Validación de usuarios sin nginx (III). Log in. Segundo factor de autenticación

 0.Introdución

En el módulo routes01mnu.py, en el endpoint "/softprop/validate_login" que estaba definido en la función  post_login estaba este código que hace referencia al módulo xmtotp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
        # 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)	


Veamos pues el módulo xmtotp.py

 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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import os
import pyotp
import qrcode

from basicutils import xmdb, xmother
from models.xmusermodels import UserStore, UserStoreHst

def get_totp(username:str, service:str='Softprop@Tav'):
	"""
	Generates a new TOTP secret and QR code for the given username and service.
	
	Args:
		username (str): The username for which to generate the TOTP secret.
		service (str): The service name for which the TOTP is being generated.
		
	Returns:
		tuple: A tuple containing:
			- has_totp (bool): True if the user already has a TOTP secret, False otherwise.
			- totp (pyotp.TOTP): The TOTP object.
			- image_path (str): The path to the saved QR code image.
	"""
	# Generate a new TOTP secret
	secret=pyotp.random_base32()
	has_totp=False
	has_2fa=False
	image_path_url=None
	myuser=None
	
	# Check if the user already has a TOTP secret
	with xmdb.session_local() as db:
		try:
			myuser=db.query(UserStore).filter(UserStore.ldap_user==username).first()
			#if myuser and myuser.totp_activated: 
			if myuser and myuser.totp_secret is not None and len(myuser.totp_secret.strip())>0: 
				secret=myuser.totp_secret
				has_2fa=True
				if myuser.totp_activated: 
					has_totp=True
		except Exception as e:
			print(f"An error occurred while querying the user: {e}")
			return False, None, None

		
		totp = pyotp.TOTP(secret)
		
		
		if not has_2fa:
			# Update the user with the new TOTP secret
			myuser.totp_secret=secret
			myuser.totp_user=username
			myuser.totp_service=service
			myuser.totp_activated=False
			
			xmdb.merge_to_main_and_histo(db, myuser, UserStore, UserStoreHst)
			db.commit() 

		# Si no se ha autenticado antes
		if not has_totp:	
			# Generate the provisioning URI and save image
			uri = totp.provisioning_uri(name=username, issuer_name=service)
			# Generate the QR code image	
			image_path_file=xmother.get_project_path()+os.sep+'static'+os.sep+'qrcodes'+os.sep+username+'_qrcode.png'
			image_path_url='/static'+os.sep+'qrcodes'+os.sep+username+'_qrcode.png'
			print (image_path_file, image_path_url)
			qrcode.make(uri).save(image_path_file)
		# Check if the image was saved successfully	
	return has_totp, totp, image_path_url

def validate_totp(totp, code:str, username:str)->bool:
	"""
	Validates the provided TOTP code against the generated TOTP object.
	
	Args:
		totp (pyotp.TOTP): The TOTP object.
		code (str): The TOTP code to validate.
		
	Returns:
		bool: True if the code is valid, False otherwise.
	"""
	si_verifica=totp.verify(code) or username=='eduard' # For testing purposes, allow 'eduard' to bypass TOTP verification
	if si_verifica:
		with xmdb.session_local() as db:
			try:
				myuser=db.query(UserStore).filter(UserStore.ldap_user==username).first()
				if myuser and myuser.totp_activated==False:
					myuser.totp_activated=True
					xmdb.merge_to_main_and_histo(db, myuser, UserStore, UserStoreHst)
					db.commit() 	
			except Exception as e:
				print(f"An error occurred while querying the user: {e}")
				return False
	return si_verifica








WEBPROPv2 (IV). Validación de usuarios sin nginx (II). Log in. Utilización del LDAP

 0. Introducción

En el módulo routes01menu.py en el endpoint "/softprop/validate_login" que está definido por la función post_login, hacemos esta llamada

ldap_authenticated= xmldap.autheticate_by_login(login.usuari, login.paraula_de_pass)

Por tanto todo el tema de autenticación por LDAP se realiza en módulo "xmldap.py"

1. el módulo xmldap.py

Este módulo accede a unas propiedades para poder conectarse al servidor LDAP.

En concreto el fichero que guarda las propiedades tiene este contenido:

ldapWindows:
  serverName: mi-servidor.edificio.poblacion
  userPrefix: edificio\

Veamos el código del módulo xmldap.py

 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
from ldap3 import Server, Connection, ALL, SUBTREE, ALL_ATTRIBUTES ,SAFE_SYNC, NTLM, ALL_ATTRIBUTES
import sys
import os
#------Imprescindible para poder importar de otras carpetas (de basicutils)
import sys
from pathlib import Path
path_root1 = Path(__file__).parents[1] # Damos un salto de directorio hacia arriba-> ximoutilsmod
sys.path.append(str(path_root1))
path_root0 = Path(__file__).parents[0] # El mismo directorio
sys.path.append(str(path_root0))
from basicutils import xmcrypto, xmfile
# ------Fin imprescindible


def get_props():
	logs=[]
	logs.append('env='+str(os.environ))
	logs.append('os.getenv("CONF_PYTHON")='+str(os.getenv("CONF_PYTHON")))
	xmfile.add_lines_to_file('/home/eduard/kk.log', logs)
	conf_file=os.getenv("CONF_PYTHON")+'ldap.encrypted.yml'
	config= xmcrypto.get_properties_from_file(conf_file)
	return config['ldapWindows']

'''Autheticate by login name and password'''
def autheticate_by_login(username: str, password: str)->bool:
	config=get_props()
	try:
		server = Server(config['serverName'], get_info=ALL)
		my_user=config['userPrefix']+username
		conn = Connection(server, user=my_user, password=password)
		if conn.bind():
			print('xmldap.autheticate_by_login: OK')
			return True
		else:
			print('xmldap.autheticate_by_login: FALSE')
			return False
	except Exception as e:
		print(f"LDAP error1 : {e}", file=sys.stderr)
		if 'conn' in locals():
			conn.unbind()
		return False

Lo importante de este código es:

  • La importación de los objetos necesarios de la biblioteca ldap3, en este caso Server y Connection que se encuentra en la primera línea
  • La función get_props solamente sirve para leer las propiedades que se muestran arriba que se encuentran en un fichero encriptado
  • Para ver si las credenciales son correctas primeramewnte se define el servidor con solo decir su nombre (Línea  28).
  • El usuario hay que pasarlo con un prefijo que se encuentra en propiedades (Línea 29)
  • Si se intenta hacer la conexión al servidor con el usuario (+ prefijo) y contraseña introducida, yu es correcta entonces las credenciales del usuario son correctas y quedan validadas.(Líneas 28-33)
  • Si no se establece la conexión, entonces hya un error (seguramente de credenciales) 



viernes, 22 de agosto de 2025

WEBPROPv2 (III). Validación de usuarios sin nginx (I). Log in. Visión general

 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








jueves, 21 de agosto de 2025

WEBPROPv2 (II). Configurar el objeto app de FastHTML (mnu_fh.py). Exception handllers. Beforeware. Mounting folders

0. Introducción

Se ha visto que el módulo que  tiene el "main" es mnu_main.py que ejecuta el servidor web uvicorn al que se el pasa entre otros parámetros el objeto "app".

Pues en este post veremos como se configura el objeto app para que utilice recursos como js o css, utilice rutas estáticas, redirija excepciones y utilice una función para que antes de ir a una ruta o "endpoint" compruebe que el usuario tiene permiso para ello.

1. Configuración del objeto app

Se realiza en el módulo menu_fh.py :

  • Crea la lista de "headers" (líneas 21-43):
    • Utiliza la codificación UTF-8
    • Par un diseño "responsive" utiliza la etiqueta "meta" para asignar toda la anchura del dispositivo y hacer una escalado del 100%
    • Adjunta o enlaza librerias js o css:
      • Font Awesome
      • El css propio para el tree.menu
      • Libreria bootstrap
      • Librería Ag-Grid
  • Crea un manejador de las excepciones 400, 403 y 404 (líneas 45-49)
  • Se define una funcion tipo "Beforeware" que se ejecutará en todos los "endpoints". Se especifica su funcionamiento más adelante (Líneas 52-88)
  • Se montan las carpetas "static" y "downloads" a un path de la app (líneas 93-94)
  • Se añaden estos objetos al objeto app (líneas 86-90)
 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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#1. Imports

from fastapi.staticfiles import StaticFiles
from fasthtml import common as fh


#------Imprescindible para poder importar de otras carpetas (de basicutils)
import sys
from pathlib import Path
path_root1 = Path(__file__).parents[1] # Damos un salto de directorio hacia arriba-> ximoutilsmod
sys.path.append(str(path_root1))
path_root0 = Path(__file__).parents[0] # El mismo directorio
sys.path.append(str(path_root0))
# ------Fin imprescindible

from basicutils import xmother
from session import xmsession


#1. Defining Headers (Styles, Scripts, etc)
my_headers = [
	fh.Meta(charset='UTF-8'),

	# Responsive design:  Uses the width of the device and renders the page at 100% zoom (no auto zoom)
	fh.Meta(name='viewport', content='width=device-width, initial-scale=1'),
	
	# Link libraries of js and css

	#  1. Font Awesome
	fh.Link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css'),
	
	#  2. My Menu-tree library
	fh.Link(rel='stylesheet', href='../static/css/tree-menu.css'),
	
	#  3. Bootstrap
	fh.Link(href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css", rel="stylesheet"),
        fh.Script(src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"),
        fh.Script(src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js"),
	
	#  4. Ag-Grid
	fh.Script(src="https://cdn.jsdelivr.net/npm/ag-grid-community/dist/ag-grid-community.min.js"),
	
]

exception_handlers={
        400: lambda req, exc: fh.Titled("400: Bad Request: La petició no és correcta."),
	403: lambda req, exc: fh.Titled("403: Forbidden: No té permís a esta pàgina."),
	404: lambda req, exc: fh.Titled("404: Not Found: No existeix esta pàgina!"),
}

#2. Beforeware function to be executed in all endpoints
async def my_before_function(req, tab_id, sess):
	'''
	Beforeware function: checks that the user is authenticated
	'''
	print('Des de beforeware, la URL es:', req.url)
	# 1. Validem que haja tab_id i que l'usuari estiga en la sessió de la BD
	if tab_id is None: tab_id= req.query_params.get('tab_id', None)

	if tab_id is None:	
		try:
			tab_id= (await req.form()).get('tab_id', None)
		except Exception as e:
			print("Error al llegir el tab_id del form:", e)
			tab_id = None
	if tab_id is None:	
		try:
			tab_id= (await req.json()).get('tab_id', None)
		except Exception as e:
			print("Error al llegir el tab_id del JSON:", e)
			tab_id = None	

	my_response,myerror=xmsession.validate(tab_id=tab_id, req=req)
	if (len(myerror.strip())>1):
		print ("redirecting from my_before_function " )
		return my_response

#OJO: Aquelles rutes que comecen per "/softprop/nchk/" no seran comprovades l'usuari
my_skip=[r'/favicon\.ico', r'/static/.*', r'.*\.css', 
		 r'/softprop/nchk/.*',
		 '/softprop/login', '/softprop/validate_login', 
		]
my_beforeware= fh.Beforeware(my_before_function, skip=my_skip)

#3. Creating FastAPI App
app = fh.FastHTML(
	hdrs=my_headers, 
	before=my_beforeware,
	secret_key=xmother.new_id(25), 
	exception_handlers=exception_handlers)

#3. Mounting the static folder for accessing the menu.css
app.mount("/static"   , fh.StaticFiles(directory="static"   ), name="static"   )
app.mount("/downloads", fh.StaticFiles(directory="downloads"), name="downloads")

2. Exception handlers

Es un parámetro del objeto app que és una lista de diccionarios que se le indica el código hhtp del error y lo que se tiene que mostrar. En este caso se muestra una pantalla cuyo título es la descripción del error.

El código se muestra muestra el detalle:

exception_handlers={
        400: lambda req, exc: fh.Titled("400: Bad Request: La petició no és correcta."),
	403: lambda req, exc: fh.Titled("403: Forbidden: No té permís a esta pàgina."),
	404: lambda req, exc: fh.Titled("404: Not Found: No existeix esta pàgina!"),
}


3. La función "beforeware"

Un beforeware es una función que intercepta las peticiones HTTP antes de que lleguen al manejador (es decir, antes del código que atiende /ruta/...), para:
  • Validar autenticación/autorización.
  • Añadir datos a la petición.
  • Redirigir, denegar o registrar información.
  • Omitirse en rutas específicas si se configura.
En este caso, el beforeware comprueba si el usuario está autenticado mediante un tab_id.

Veamos que es lo que contiene el código:
  • Definimos una función asíncrona que recibe como parámetros la "request", el "tab_id" y la "sessión" (Línea 2)
  • Esta función recibe como parámetro el tab_id, y si no se le pasa dicho parámetro lo recoge de la "request" (Línea 8)
  • Si no lo encuentra lo busca primeramente en el FORM i si no en el BODY (Líneas 11-24)
  • Se valida mediante el tab_id con la llamada a la función xmsession.validate () (Línea 26)
  • Se definen aquellas rutas (endpoints) que no hace falta validar con el tab_id, que constituyen una lista de "strings" que puden incluir expresiones regulares. (Líneas 32-35)
  • Se asocia la función anterior como "beforeware global" que actuará en todos los endpoints excepto aquellos que se le indican en el parámetro "skip" (Línea 36)
  • Se le passa el beforeware con el parámetro "before" al objeto "app" (Línea 41)
 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
#2. Beforeware function to be executed in all endpoints
async def my_before_function(req, tab_id, sess):
	'''
	Beforeware function: checks that the user is authenticated
	'''
	print('Des de beforeware, la URL es:', req.url)
	# 1. Validem que haja tab_id i que l'usuari estiga en la sessió de la BD
	if tab_id is None: tab_id= req.query_params.get('tab_id', None)

	# 2. If tab_id is not in the query parameters, we try to get it from the FORM 
	if tab_id is None:	
		try:
			tab_id= (await req.form()).get('tab_id', None)
		except Exception as e:
			print("Error al llegir el tab_id del form:", e)
			tab_id = None
	
	# 3. If tab_id is still None, we try to get it from the JSON body
	if tab_id is None:	
		try:
			tab_id= (await req.json()).get('tab_id', None)
		except Exception as e:
			print("Error al llegir el tab_id del JSON:", e)
			tab_id = None	

	my_response,myerror=xmsession.validate(tab_id=tab_id, req=req)
	if (len(myerror.strip())>1):
		print ("redirecting from my_before_function " )
		return my_response

#OJO: Aquelles rutes que comecen per "/softprop/nchk/" no seran comprovades l'usuari
my_skip=[r'/favicon\.ico', r'/static/.*', r'.*\.css', 
		 r'/softprop/nchk/.*',
		 '/softprop/login', '/softprop/validate_login', 
	]
my_beforeware= fh.Beforeware(my_before_function, skip=my_skip)

#3. Creating FastAPI App
app = fh.FastHTML(
	hdrs=my_headers, 
	before=my_beforeware,
	secret_key=xmother.new_id(25), 
	exception_handlers=exception_handlers)

4. La función xmsession.validate para ver si el usuario tiene permiso

En el módulo xmsession se tiene esta función que :
  • Se crea una ruta de redirección para el caso de error que es "/softprop/login" para que ingrese las credenciales al sistema.
  • El primer error que lnaza es no pasar-le un tab_id
  • Si el tab_id no está presente en las cookies que duran 3600 segundos lanza error
  • Compueba que el tab_id esté en la sessión guardada en la BD 
  • Si hay error lo devuelve a la pantalla de login


 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
def validate(tab_id:str, req)->tuple:
	redirection='/softprop/login'
	my_error=""

	# 1. Si es la primera vegada, no tenim tab_id i envia un script per a arreplegar-lo
	if tab_id is None: 
		my_error="No s'ha inclòs el tab_id en el request"
			
	#2. Check if the tab_id is in the cookies
	#   Cookies that has finished the max_age=3600 seconds are not present in the session
	if len(my_error)==0:
		cookies = req.cookies
		if tab_id not in cookies.keys(): 
			my_error="El tab_id no està present en el cookies, pot estar caducat"

	#3. Verifiquem que l'usuari existisca i estiga actiu
	if len(my_error)==0:
		with xmdb.session_local() as db:
			user, my_error=test_session(req, db, tab_id)
	
			if len(user.strip())==0 and len(my_error.strip())==0:
				my_error="Usuari no trobat"
			
			# 4. Guardem la petició de l'usuari per a fer una auditoria
			else:
				user_request=RequestStore(
					description=str(req.url)+'|'+datetime.now(timezone.utc).strftime('%Y%m%d%H%M%S%f'),
					user=user,
					tab_id=tab_id,
					url = str(req.url),
					method=req.method,
					user_agent=get_user_agent(req),
					user_ip=get_user_ip(req),
					is_nginx=get_is_nginx(req)
				)
				user_request=db.merge(user_request)
				db.flush()		
				db.commit()

	# Si hi ha un error redireccionem altra vegada al login
	if len(my_error.strip())>0:		
		a=fh.RedirectResponse(redirection, status_code=303)
		print('enviem a  /softprop/login perque: ' + my_error)	
		return a, my_error  

	return '',''








WEBPROPv2 (I). Arranque de la aplicacion (mnu_main.py)

 1. Arranque de la aplicación

Vemos el módulo que arranca la aplicación. El arranque se realiza con la sentencia uvicorn.run().

Este módulo:

  • Arranca el servidor web unicorv
  • Le passa los parametros necesarios al servidor para arrancar con https:
    • nombre del servidor o ip, 
    • puerto, 
    • certificado (certifcdo + clave)
  • Crea las tablas en base al modelo objeto relacional
  • Utiliza el módulo menu_fh para configurar el objeto app de FastHTML 

El módulo se llama mnu_main.py

 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
#!/home/eduard/MyPython/11.softprop-01/venv_softprop/bin/python3

#1. Imports
import os
from sqlalchemy import Table
#------Imprescindible para poder importar de otras carpetas (de basicutils)
import sys
from pathlib import Path
path_root1 = Path(__file__).parents[1] # Damos un salto de directorio hacia arriba-> ximoutilsmod
sys.path.append(str(path_root1))
path_root0 = Path(__file__).parents[0] # El mismo directorio
sys.path.append(str(path_root0))
# ------Fin imprescindible

#--------------------------------------------------------
# NOTA: No eliminar las siguientes dependencias marcadas en gris 
#       pues, sinó falla el programa
#       De menus: routes01mnu, routes02form, routes03grid, app
#       De basicutils: xmdb, xmfunction
#       De menus.mnu_fh: fh
#       De models.xmallmodels: model_classes
#--------------------------------------------------------
from menus import routes00comp, routes01mnu, routes02form, routes03grid # No eliminar !!!
from basicutils import xmdb, xmfunction

# Ahora importamos las funciones de FastHtml pero mejoradas con nuevos componentes
from menus.mnu_fh import fh, app                         # No eliminar app !!!

#--- Definición de tablas de la Base de datos postgres
from models.xmallmodels import model_classes


# Create database tables
xmdb.Base.metadata.create_all(bind=xmdb.engine)

# Execute the web server, but now not using fh.serve but uvicorm.run instead
#fh.serve()
if __name__ == "__main__":
	import uvicorn
	cert_path="/home/eduard/MyPython/11.softprop-01/static/certs/wildcard.tavernes.es."
	uvicorn.run(app, host="edu.tavernes.es", port=5001,	
	#uvicorn.run(app, host="192.168.10.5", port=5001,	
		ssl_keyfile =cert_path+"key", 
		ssl_certfile=cert_path+"crt"
	)

A destacar:

Línea 1: El shebang que indica que entorno virtual ejecutar el programa

Lineas 4-5: Importacioón de librería del entorno virtual

Líneas 7-12: Truco para poder importar nuestras librerias (permite reconocer las rutas en 2 niveles de subcarpetas

Linea 27: Hemos delegado la configuración de la app de FastHTML al módulo mnu_fh donde se definen las rutas estáticas, las rutas que no vamos a revisar la seguridad ...

Lineas 30 y 34: Importamos las clases que originaran tablas(línea 30)  y las creamos (linea 34) 

Línea 39: importamos el servidor web "uvicorn"

linea 40: Definimos la ubicación del certificado de servidor junto con la raiz del nombre de los ficheros de clave y certifcado

Línea 41: Ejecutamos el servidor web, le indicamos la app que hemos complementado, la URL del servidor web (o IP) el puerto y la rutas del certificado y clave asociada.