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. 






jueves, 19 de junio de 2025

GLPI (V): Tareas programadas

Introducción

El ENS exige una serie de tareas periódicas para comprobar ciertos niveles de aceptación del sistema:

En el procedimiento 3 de seguridad lógica se establecen planes:

  • Anuales (plan de mantenimiento anual)
  • Trimestrales:
    • SAIs 
  • Mensuales (?)
  • Semanales:
    • Servidor: Parches, espacio en disco, antivirus, consumo de memoria ..
    • Copias de seguridad 
    • Cortafuegos (logs, ...)

  • Diarias (?)
Manos a la obra

No se pueden crear tareas programadas de por si. Estas tienen que estar asociadas a un tiquet, para ello empezamos creando un tiquet:

Vamos a Soporte > Tiquet  botón "+Añadir"

Creamos una tarea llamada "ENS - Tareas semanales"
Podemos especificicar la categoría "ENS", y podemos seleccionar uno varios ordenadores sobre los que establecer  las tareas de mantenimiento semanal

Una vez creado el tiquet, vamos a Asistencia (Suport) > Tiquets Recurrentes > "+ Añadir"
Damos un nombre, periodicidad de 7 días,

Ahora se activan las notificaciones para que el responsable las verifique:
Configuració > Notificacions >



miércoles, 18 de junio de 2025

GLPI (IV) Base de Conocimiento

Tenemos que ir a Herramients (Eines) > Base de Conocimientos y botón de Añadir en la parte superior

Nos pide:

  1. Categoría de la base de conocimiento. Se puede crear la categoría. Si se quiere modificar hay que buscar en "Encuentra el menu" de la parte superior izquierda e indicarle:
    Configuración> Llistas desplegables > Categorías de la base de conocimiento.
  2. Tema
  3. Contenido a mostrar
  4. Ficheros de adicionales 
  5. Destinos (a nivel de entidad, grupo, perfil o usuario
Para mejorar el tema hay que definir las categorias anidadas, en mi caso se establece el primer nivel consistente en "Desatacados"(novedades etc), "Público" (Para todos los usuarios) y "Privado" (que es solo para administradores)

En el segundo nivel se establece los programas o temática sobre los que versa la documentación (Correo, contabilidad, tickets, seguridad, ...)

viernes, 13 de junio de 2025

GLPI (III) Formularios

Se ha seguido el vídeo de Paul Portales 

1. Agregar el plugin

Según Paul Portales se debe ir a :

Configuración >Plugins (o complementos) > Tienda (si no se ha registrado hay que registrarse) >Botón de duscubrir

Pero para instalar el plugin es mejor seguir estos pasos:

1. Ir a la página de descargas del plugin

https://github.com/pluginsGLPI/formcreator/releases

2. Descargar una versión compatible que está en formato zip y descomprimirla en una carpeta. Renombrar esta carpeta si es necesario a "formcreator"

3. Copiar esta carpeta "formcreator" dentro de la carpeta de plugins de GLPI

/var/www/html/glpi/plugins/

y dar permisos:

sudo chown -R www-data:www-data /var/www/html/glpi/plugins/formcreator

sudo chmod -R 755 /var/www/html/glpi/plugins/formcreator

4. Instalar el plugin desde la interfaz de GLI. Para ello inicia sesión en GLPI como administrador.

Ve a Configuración > Plugins. Busca el plugin Formcreator en la lista. 

Haz clic en Instalar. Luego, haz clic en Habilitar.

2. Crear un formulario

En GLPI ir a Administración> Formularios > Boton (+ Añadir)

Nos pide los siguientes datos generales: Nombre del formulario, icono, idioma, si está activo, si se ve en la página de inicio del usuario, color, encabezado, y le damos a guardar.

También podemos restringir el acceso a un perfil determinado de usuarios o grupos en "Tipos de accesos" del menú lateral izquierdo

Hay una opción del menú lateral izquierdo de validadadores (Validadors)o aprobación, que tiene un campo por si se quiere que otra persona o grupo valide la información que l usuario ha introducido en el formulario. Para ello, hay un apartado en el menú vertical de la izquierda que se llama "Respuestas de formulario"(Resposta de formulari), y en dicho apartado hay que aprobar lo que el usuario ha introducido.

3. Agregar Campos (Preguntas)

Hay una opción en el menú lateral izquierdo de Questions o Preguntas que son los campos a introducir al formulario. Desde aquí podemos añadir y modificar secciones donde se incluirán los campos y añadir campos (preguntas).

Para los campos se piede:
  • Nombre del campo
  • La sección donde se va a incluir
  • Si su relleno es obligaorio (no puede ir en blanco)
  • Tipo de campo:
    • Actor (glpi,normal,post-only,tech,informatic y Plugion_GLPI_Inventory)
    • Additional Fields (No está acivado el componente para este tipo de campos)
    • DIrecciones IP
    • Campo oculto (como es oculto, hay que darle un valor por defecto)
    • CheckBoxes ( y le damos cada una de las opciones separadas por salto de línea)
    • Email
    • Fecha
    • Fecha y hora
    • Descripcion (Campo de solo lectura, por ejemplo las condiciones del contrato)
    • Enter donde se indica una expresión regular que dicho campo tiene que validar
    • Fichero (para adjuntar uno varios ficheros)
    • Float (que también pide una expresión regular)
    • Hostname
    • LDAP Select: Pide un Directorio de LDAP, un filtro y un atributo (Como (AD) User ID 
    • Lista desplegable (Donde se elige una lista desplegable existente)
    • MultiSelet (Se le da los valores separados consalto de línea)
    • Objeto GLPI (Pide el tipo de objeto, ordenadores, programas, contratos ...)
    • Select (Se le da valores con salto de línea)
    • Tags ???
    • Hora
    • Text
    • TextArea
    • Tipo de petición (Incidencia, Solicitud)
    • Urgencia (Alta, baja,...)
  • Valor por defecto
  • Descripción detallada
  • Expresión regular (solo en algunos tipos de campos)
  • Máximo y mínimo número de caractres del campo
Lo bueno que tiene es que se pueden dar longitud de los campos (justo despues de los 3 iconos hay un símbolo de medio cuadarado rayado que deslizandolo cambia la longitud) y organizar la pantalla como máximo 4 campos por línea. 


Los 3 iconos que aparecen son para borrar, copiar o indicar si el campo es obligado

4. Generar tickets (Objetivos)

En el menú lateral izquierdo del formulario en Objetivos se añade un objetivo y se le da un nombre (por ejemplo "tiquet indcidencias") y tipo "Target ticket" y le damos al botón añadir.
Ahora se le da click sobre el objetivo y nos muestra una pantalla nueva y nos cambia el menú lateral
En el menu lateral "Propiedades" nos pide:
  • Entidad de destino, tiempo que debe responder el tiquet, SLA y OLA (acuerdos de nivel de servicio) ...
En el menu de "Actores" se indican solicitante, supervisor y asignado 

5. Tratamiento de respuestas de formulario

En Administracion > Formularios, elegimos el formulario en cuestión y en el menú lateral izquierdo entramos en "Respuestas de formulario" y al final hay un campo textarea que es un comentario por parte del que tiene que aprobar el formulario.

Una vez aprobado, ya aparece como ticket en el apartado Tiquets de la pantalla del usuario.
Para el usuario administrador se puede ver en Suport > Tiquets
Y en la pantalla de la izquierda se puede reasignar el tiquet a otro actor.
El actor que se le ha asignado, puede apretar el botón de "Responder" y crea unmensaje mediante una textarea con la infomación pertinente para resolver el problema. Tambien se puede adjuntar un archivo para completar la informacfión de resolución del problema.

6. Generar un PDF del Formulario

Si queremos guardar el formulario como PDF vamos a:
Administracion > Formularios y en el menú lateral izquierdo buscamos Formulario y en el campo cabecera marcamos los tres puntitos "..." y le damos al icono de pantalla completa (que es el último)

Podemos añadir tablas, emoticonos, vínculos, imágens etc.

Para añadir una imagen, hay que:
  1. Darle al icono de la imagen
  2. Nos pide un nombre, el cual será el nombre simple del fichero de la imagen con su extensión (sin su ruta), por ejemplo logo01.jpg y opcionalemente una descripción alternativa y tamaño horizontal y vertical
  3. Una vez añadido, nos vamos al icono "<>" para ver el código fuente y vemos que hay un fragmento de código que dice:
    <img src="http://192.168.XXX.XXX/glpi/plugins/formcreator/front/logo01.jpg" alt="Prova Edu" width="50" height="100">
  4. Como en nuestro caso se ha instalado el glpi en la ruta:
    /var/www/glpi del servidor, tenemos que copiar el fichero de la imagen a la carpeta indicada pero con la siguiente ruta: 
    /var/www/glpi/plugins/formcreator/front
    pero hay que cambiarle el usuario a www-data y el grupo a www-data al fichero copiado con el comando chown 
Pero todo esto que hemos añadido se verá solo en la cabecera del formulario.

No hacía falta tanta historia. Pero si se quiere tener documentos con el logo de la empresa y se quieren guardar, esto le daría un poco mas de adaptación del formulario a la imagen dela empresa.

Para impriomir el formulario, aparwece el símbolo de la impresora en el título del formulario y al hacer click se imprime y se puede elegir imprimirlo a PDF