From 5ab10cf6c8db8b99b63e0a510643ba9f8616d79f Mon Sep 17 00:00:00 2001 From: david Date: Thu, 2 Apr 2026 12:26:44 +0200 Subject: [PATCH] =?UTF-8?q?-=20En=20la=20firma=20visible,=20mostrar=20el?= =?UTF-8?q?=20nombre=20de=20organizaci=C3=B3n=20del=20certificado.=20-=20A?= =?UTF-8?q?justar=20versiones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 26 ++-- .../infrastructure/pdf/pyhanko_pdf_signer.py | 4 +- src/signing_service/main.py | 131 +++++++++++------- 3 files changed, 97 insertions(+), 64 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e2b88d4..c5b9b2c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,25 +4,31 @@ build-backend = "setuptools.build_meta" [project] name = "factuges-document-signing-service" -version = "0.3.1" +version = "0.3.4" description = "FastAPI service for signing PDF documents using external secret managers" requires-python = ">=3.11" dependencies = [ - "python-multipart", - "python-dotenv>=1.0", - "fastapi>=0.110", - "uvicorn[standard]>=0.27", + "python-multipart==0.0.9", + "python-dotenv==1.0.1", - "pyhanko>=0.32", - "cryptography>=42", + "fastapi==0.128.0", + "starlette==0.50.0", + "uvicorn[standard]==0.40.0", - # Infisical SDK for secret management - "infisicalsdk", + "pyHanko==0.32.0", + "cryptography==46.0.4", + + "infisicalsdk==1.0.15", # Google Cloud Secret Manager SDK for secret management # "google-cloud-secret-manager>=2.18", ] [project.optional-dependencies] -dev = ["pytest>=8.0", "pytest-cov", "ruff", "mypy"] +dev = [ + "pytest>=8.0", + "pytest-cov", + "ruff", + "mypy", +] \ No newline at end of file diff --git a/src/signing_service/infrastructure/pdf/pyhanko_pdf_signer.py b/src/signing_service/infrastructure/pdf/pyhanko_pdf_signer.py index 33a298b..6796664 100644 --- a/src/signing_service/infrastructure/pdf/pyhanko_pdf_signer.py +++ b/src/signing_service/infrastructure/pdf/pyhanko_pdf_signer.py @@ -11,6 +11,7 @@ from signing_service.domain.ports.pdf_signer import PDFSignerPort from signing_service.infrastructure.pdf.pdf_validator import PDFValidator SIGNATURE_FIELD_NAME = "Signature1" +CERTIFICATE_FIELD_NAME = "organization_name" # "common_name" class PyHankoPDFSigner(PDFSignerPort): @@ -38,7 +39,8 @@ class PyHankoPDFSigner(PDFSignerPort): # 3️⃣ Extract certificate info (from signer) cert = signer.signing_cert - subject = cert.subject.native.get('common_name', 'Desconocido') + subject = cert.subject.native.get( + CERTIFICATE_FIELD_NAME, 'Desconocido') # 5️⃣ Define caja del campo de firma (AcroForm) fields.append_signature_field( diff --git a/src/signing_service/main.py b/src/signing_service/main.py index 7e7af09..4052743 100644 --- a/src/signing_service/main.py +++ b/src/signing_service/main.py @@ -1,13 +1,14 @@ +from contextlib import asynccontextmanager + from fastapi import FastAPI, HTTPException, Request from fastapi.responses import JSONResponse from starlette.middleware.base import BaseHTTPMiddleware from dotenv import load_dotenv from pathlib import Path -from datetime import datetime +from datetime import datetime, UTC from dateutil import tz - from signing_service.application.settings.container import get_settings from signing_service.application.settings.setup_logger import create_logger from signing_service.api.routes.sign_document import router as sign_router @@ -18,37 +19,45 @@ load_dotenv() # 👇 FAIL FAST: load settings at startup settings = get_settings() version = get_package_version() -local_tz = tz.gettz(settings.local_tz) -state_path = Path(settings.state_path) -# Logging -log_dir = state_path -log_dir.mkdir(parents=True, exist_ok=True) -logger = create_logger( - name="factuges-document-signing-service", - # Solo lo genera en producción - log_path=log_dir / "factuges-document-signing-service.log", +def build_logger(): + """Crea el logger de aplicación y asegura el directorio de logs.""" + log_dir = Path(settings.state_path) + log_dir.mkdir(parents=True, exist_ok=True) -) + app_logger = create_logger( + name="factuges-document-signing-service", + log_path=log_dir / "factuges-document-signing-service.log", + ) -logger.info("") -logger.info("============================================================") -logger.info("FactuGES Document Signing Service - START ") -logger.info("Version: %s", version) -logger.info("UTC Now: %s", datetime.utcnow().isoformat()) -logger.info("Environment: %s", settings.app_env) -logger.info("") -logger.info("Log Level: %s", settings.log_level) -logger.info("Log: %s", log_dir / "factuges-document-signing-service.log") -logger.info("") -logger.info("Secret Provider: %s", settings.secret_provider) -logger.info("Infisical project ID: %s", settings.infisical_project_id) -logger.info("Infisical environment: %s", settings.infisical_env_slug) -logger.info("") + app_logger.info("") + app_logger.info( + "============================================================") + app_logger.info("FactuGES Document Signing Service - START") + app_logger.info("Version: %s", version) + app_logger.info("UTC Now: %s", datetime.now(UTC).isoformat()) + app_logger.info("Environment: %s", settings.app_env) + app_logger.info("") + app_logger.info("Log Level: %s", settings.log_level) + app_logger.info( + "Log: %s", + log_dir / "factuges-document-signing-service.log", + ) + app_logger.info("") + app_logger.info("Secret Provider: %s", settings.secret_provider) + app_logger.info("Infisical project ID: %s", settings.infisical_project_id) + app_logger.info("Infisical environment: %s", settings.infisical_env_slug) + app_logger.info("") + + return app_logger + + +logger = build_logger() async def http_exception_handler(request: Request, exc: HTTPException): + """Registra y devuelve errores HTTP controlados.""" logger.error( "HTTPException %s %s | status=%s | detail=%s", request.method, @@ -63,47 +72,63 @@ async def http_exception_handler(request: Request, exc: HTTPException): ) -# Define logging middleware class LoggingMiddleware(BaseHTTPMiddleware): + """Registra request y response para trazabilidad básica.""" + async def dispatch(self, request: Request, call_next): - # Log request details - client_ip = request.client.host + client_ip = request.client.host if request.client else "unknown" method = request.method url = request.url.path - logger.info(f"Request: {method} {url} from {client_ip}") + logger.info("Request: %s %s from %s", method, url, client_ip) - # Process the request response = await call_next(request) - # Log response details - status_code = response.status_code logger.info( - f"Response: {method} {url} returned {status_code} to {client_ip}") + "Response: %s %s returned %s to %s", + method, + url, + response.status_code, + client_ip, + ) return response -app = FastAPI(title="FactuGES Document Signing Service", version=version) - -app.add_event_handler("startup", lambda: logger.info( - "Application startup complete")) - -app.add_event_handler("shutdown", lambda: logger.info( - "Application shutdown complete")) - -app.add_exception_handler(HTTPException, http_exception_handler) - -# Add middleware to the app -app.add_middleware(LoggingMiddleware) +@asynccontextmanager +async def lifespan(app: FastAPI): + """Gestiona el ciclo de vida de la aplicación.""" + logger.info("Starting signing service") + yield + logger.info("Stopping signing service") -# Register routers -app.include_router(sign_router) -logger.info("API routes registered from sign_document router") +async def healthcheck() -> dict[str, str]: + """Endpoint simple de healthcheck.""" + return {"status": "ok"} -app.add_api_route("/health", lambda: {"status": "ok"}, methods=["GET"]) -logger.info("Health check endpoint registered at /health") -app.add_api_route("/version", lambda: {"version": version}, methods=["GET"]) -logger.info("Health check endpoint registered at /version") +async def get_version() -> dict[str, str]: + """Devuelve la versión publicada del servicio.""" + return {"version": version} + + +def create_app() -> FastAPI: + app = FastAPI(lifespan=lifespan, + title="FactuGES Document Signing Service", + version=version) + + app.add_exception_handler(HTTPException, http_exception_handler) + app.add_middleware(LoggingMiddleware) + app.include_router(sign_router) + logger.info("API routes registered from sign_document router") + + app.add_api_route("/health", healthcheck, methods=["GET"]) + logger.info("Health check endpoint registered at /health") + + app.add_api_route("/version", get_version, methods=["GET"]) + logger.info("Version endpoint registered at /version") + return app + + +app = create_app()