metis-desk/docs/auditorias/resources-global-scope/04-aislamiento-autorizacion.md
2026-10-25 01:19:17 +02:00

95 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 04 — Aislamiento y autorización
## Lo que garantiza hoy cada control
**CONFIRMADO:** `requireAuthenticated` valida sesión mediante userId/authVersion y usuario activo. No recibe companyId ni aplica ACL por compañía. `ResourceUseCases` valida UUID de path; `SequelizeResourceRepository.load` comprueba existencia de compañía y carga exclusivamente sus recursos. Esto protege referencias entre entidades del contexto, pero no establece una ACL por operador sobre esa compañía. El modelo de soporte compartido está documentado expresamente en docs/arquitectura/proveedor-de-relaciones-tipadas.md; no es por sí mismo una vulnerabilidad del modelo actual. [auth: require-authenticated.ts:6](../../../modules/identity/src/api/infrastructure/http/require-authenticated.ts#L6) [load: sequelize-resource.repository.ts:58](../../../modules/resources/src/api/infrastructure/persistence/sequelize-resource.repository.ts#L58) [routes: resource.routes.ts:54](../../../modules/resources/src/api/infrastructure/http/resource.routes.ts#L54).
**CONFIRMADO:** existe `isInitialAdmin`, usado en `IdentityUseCases.administrator` para gestionar usuarios. No existe un catálogo de roles en estas rutas ni un guard de administración global de recursos. El catálogo de definiciones acepta scope `global` con el mismo authenticate. No denominar a ese control «administrador de compañía», que no está implementado. [admin: identity.use-cases.ts:75](../../../modules/identity/src/api/application/identity.use-cases.ts#L75) [catalogRoutes: resource-catalog.routes.ts:65](../../../modules/resources/src/api/infrastructure/http/resource-catalog.routes.ts#L65).
**RIESGO ALTO (A01):** un operador autenticado puede invocar rutas de catálogo global sin permiso específico, según composición/rutas inspeccionadas. Es un déficit respecto al objetivo de edición global restringida; no se realizó explotación HTTP ni se afirma escalada respecto al producto actual, que describe un espacio común de soporte. **RIESGO CRÍTICO condicionado (A02):** si se usa esa autenticación como aislamiento de operadores por compañía, cambiar companyId a otra compañía existente no encuentra una ACL que lo impida. Aceptar/rechazar este modelo de soporte compartido es decisión de producto previa a R2.2. [runtime: identity-runtime.ts:17](../../../apps/server/src/infrastructure/http/identity-runtime.ts#L17) [auth: require-authenticated.ts:6](../../../modules/identity/src/api/infrastructure/http/require-authenticated.ts#L6).
Las relaciones tipadas usan contextos emitidos por `SessionRelationshipReadAccess`, WeakMap y reautorización de sesión/existencia de compañía antes de leer. **CONFIRMADO:** tampoco implementan una ACL operador–compañía. No extender su companyId obligatorio a NULL para representar un producto global: es el contexto operativo del consumidor. [relationshipAuth: relationship-read-access.ts:7](../../../apps/server/src/infrastructure/http/relationship-read-access.ts#L7).
**CONFIRMADO:** registerIdentityRoutes monta su router en /api antes de registrar los módulos; su middleware de escrituras comprueba Origin y x-csrf-token de sesión. Esto protege contra CSRF, no concede privilegio global. [csrf: identity.routes.ts:105](../../../modules/identity/src/api/infrastructure/http/identity.routes.ts#L105).
## Inventario de filtros y consultas
Clasificación de intención futura: **1** solo compañía; **2** globales permitidos + compañía; **3** solo globales; **4** referencia concreta autorizada; **5** administración global. Esta tabla cubre las familias encontradas en lecturas y escrituras; sus consumidores se expanden en [05](05-consumidores-resource.md).
| Punto / símbolos | Filtro actual | Intención futura / actuación |
| --- | --- | --- |
| readInventory, load, ResourceUseCases.save/setActive/remove | companyId → IDs del contexto | 1 para gestión local; nuevo lector 2/4 para productos; no ampliar escritura local |
| ResourceCatalog.visible, availability | global/local de definiciones | 2 de **plantillas**, 5 al editar global; no es catálogo de productos |
| ResourceInventoryService.list | inventory por compañía | Mantener 1 en contrato genérico; método explícito 2/4 para aplicaciones |
| Software repository load/save/remove | instalaciones/revisiones/activos de compañía; recursos vía servicio | 1 para operación y licencia; 2/4 solo para resourceId software |
| Application repository load/save/remove | configuraciones/perfiles/personas de compañía | 1 para operación; 2/4 para aplicación |
| Certificate repository | compañía; servicio de recursos y usage certificate | 1/4; rechazar globales |
| Remote repository / remoteReferences | compañía; cualquier resource local como target | 1/4; no heredar globales por compartir servicio |
| Support repository | resources.companyId=:companyId, support stockKey/usage | 1/4; conservar restricción compuesta |
| DirectoryDomainReader / NetworkShareReader | ID + companyId, join proyección | 1/4; nunca productos globales |
| Options/SummaryResolvers directorio y SMB | contexto autorizado + companyId + IDs/search/cursor | 1/4; búsqueda especializada permanece local |
| readResourceProjections | mailboxes/access con compañía | 1; mailbox/access son operativos |
| readResourceProjectionHistory | solo itemId tras selección de raíz | 4; no exponer como API por ID sin comprobar raíz |
| removeResource | ID bloqueado; DELETE final id AND companyId | 1/4 local; ruta 5 global separada con protección de usados |
| prepareResourceTemplateUpdates | DISTINCT companyId por plantilla y carga por compañía | Administración transversal 5; procesar globales aparte y mantener orden de locks |
| ResourceDashboardReader stats/entries/activity | filtro compañía opcional; joins companies | Administración/transversal existente; no aplicar 2 automáticamente, revisar recuentos NULL |
| migrateLegacyResources / migrateSupportResources | proyecciones por compañía / IDs legacy | Migración interna; conservar propiedad, no activar lectura global |
| Web inventario, software, aplicaciones, certificados, remote, entornos y puestos | inventoryKeys.company(id) y filtros en memoria | 1 para inventario operativo; 2/4 explícito para nombres/aplicaciones en todos los consumidores |
Evidencia: [read: resource-inventory-relational.ts:195](../../../modules/resources/src/api/infrastructure/persistence/resource-inventory-relational.ts#L195) [catalog: resource-catalog.aggregate.ts:14](../../../modules/resources/src/api/domain/resource-catalog.aggregate.ts#L14) [service: resource-inventory.service.ts:7](../../../modules/resources/src/api/application/resource-inventory.service.ts#L7) [softwareRead: sequelize-software-installation.repository.ts:27](../../../modules/companies/src/api/infrastructure/persistence/sequelize-software-installation.repository.ts#L27) [applicationRead: sequelize-application-configuration.repository.ts:34](../../../modules/companies/src/api/infrastructure/persistence/sequelize-application-configuration.repository.ts#L34) [certificateFK: configuration-snapshots.ts:80](../../../modules/companies/src/api/infrastructure/persistence/configuration-snapshots.ts#L80) [remoteRead: sequelize-remote-connection.repository.ts:92](../../../modules/companies/src/api/infrastructure/persistence/sequelize-remote-connection.repository.ts#L92) [supportRead: sequelize-support.repository.ts:21](../../../modules/assets/src/api/infrastructure/persistence/sequelize-support.repository.ts#L21) [directoryRead: directory-domain-reader.ts:19](../../../modules/resources/src/api/infrastructure/persistence/directory-domain-reader.ts#L19) [shareRead: network-share-reader.ts:22](../../../modules/resources/src/api/infrastructure/persistence/network-share-reader.ts#L22) [directoryOptions: directory-domain-options-reader.ts:15](../../../modules/resources/src/api/infrastructure/persistence/directory-domain-options-reader.ts#L15) [shareOptions: network-share-options.ts:11](../../../modules/resources/src/api/infrastructure/persistence/network-share-options.ts#L11) [directorySummaries: directory-domain-summary-resolver.ts:27](../../../modules/resources/src/api/infrastructure/persistence/directory-domain-summary-resolver.ts#L27) [shareSummaries: network-share-summary-resolver.ts:27](../../../modules/resources/src/api/infrastructure/persistence/network-share-summary-resolver.ts#L27) [remove: remove-resource.ts:37](../../../modules/resources/src/api/infrastructure/persistence/remove-resource.ts#L37) [propagation: prepare-resource-template-updates.ts:15](../../../modules/resources/src/api/infrastructure/persistence/prepare-resource-template-updates.ts#L15) [dashboard: resource-dashboard-reader.ts:15](../../../modules/resources/src/api/infrastructure/persistence/resource-dashboard-reader.ts#L15) [legacy: migrate-legacy-resources.ts:216](../../../modules/resources/src/api/infrastructure/persistence/migrate-legacy-resources.ts#L216) [supportMigration: migrate-support-resources.ts:44](../../../modules/resources/src/api/infrastructure/persistence/migrate-support-resources.ts#L44) [projectionRead: resource-projection-reader.ts:33](../../../modules/resources/src/api/infrastructure/persistence/resource-projection-reader.ts#L33) [processRead: sequelize-process.repository.ts:109](../../../modules/companies/src/api/infrastructure/persistence/sequelize-process.repository.ts#L109) [inventoryAPI: inventory-api.ts:11](../../../modules/resources/src/web/resources/shared/api/inventory-api.ts#L11).
## Contrato propuesto de visibilidad y acceso
**PROPUESTO:** autorizar primero el contexto operativo C desde sesión. Para un propósito application, aceptar `(scope=global AND plantilla elegible global AND sin integración operativa) OR (scope=company AND owner=C AND usage application)`. Para software se añade OS **local**; para licencia se exige company=C y usage license. El contexto de compañía sigue siendo obligatorio aunque el producto no tenga propietario.
| Operación | Operador autenticado en contexto C autorizado | Administrador global explícitamente autorizado | Sin sesión |
| --- | --- | --- | --- |
| Buscar aplicación | Globales elegibles activos + locales C | Igual, o catálogo administrativo | 401 |
| Resolver referencia usada | Global elegible incluso inactivo + local C | Según finalidad de consulta | 401 |
| Leer recurso local de B desde C | Denegar/404 uniforme | Solo tras autorizar contexto B, sin bypass implícito | 401 |
| Crear/editar recurso local | Política de escritura C | Misma política contextual | 401 |
| Crear/editar/desactivar global | Denegar | Permiso específico | 401 |
| Eliminar global | Denegar | Solo sin referencias actuales/históricas y operación explícita | 401 |
| Ver instalaciones/usuarios/licencias de otras compañías desde producto global | Denegar | Solo vista agregada autorizada expresamente | 401 |
| Cambiar ámbito, plantilla o fusionar | Denegar | Fuera del CRUD ordinario; contrato de migración aprobado | 401 |
Esta matriz **no describe permisos ya implementados**. Propuesta inicial: guard público de Identity para la capacidad de gestión global, provisionalmente mapeado al administrador inicial si se aprueba. No crear un RBAC genérico solo para este cambio; tampoco concederla a cualquier usuario por poder editar una compañía. El control debe cubrir también cambios de plantilla/campos globales que propagan al catálogo.
## Referencias y riesgo IDOR
Hoy la referencia enviada por navegador se verifica en backend (`softwareReferences`/`applicationReferences`) contra recursos cargados de C; IDs ajenos fallan aunque se manipule el formulario. Edición de instalación/configuración fija destino; remove consulta id y companyId. Las FKs simples solo prueban existencia de Resource, no propiedad. [softwarePolicy: software-installation-policy.ts:5](../../../modules/companies/src/api/application/software-installation-policy.ts#L5) [softwareMapper: software-installation-input.mapper.ts:15](../../../modules/companies/src/api/application/software-installation-input.mapper.ts#L15) [applicationPolicy: application-configuration-policy.ts:5](../../../modules/companies/src/api/application/application-configuration-policy.ts#L5) [applicationMapper: application-configuration-input.mapper.ts:11](../../../modules/companies/src/api/application/application-configuration-input.mapper.ts#L11).
**PROPUESTO:** Resources posee la decisión de visibilidad/elegibilidad de Resource; los casos de uso de Companies poseen la validación de activo/perfil/licencia y la asociación. Exponer un puerto público de resolución por compañía, IDs y propósito con datos mínimos y procedencia; resolver IDs desde servidor en alta, edición, importación futura y carga de referencias. No aceptar un scope del navegador como acreditación. Para un global, comprobar además que plantilla y campos no tienen propietario local.
**NO CONFIRMADO:** endpoint público de importación masiva de Resource. Se localizaron migraciones internas de proyecciones y herramientas de asistencia; cualquier futuro importador debe usar los mismos contratos de Application. No crear un canal privilegiado alternativo que escriba IDs sin validar.
**RIESGO ALTO:** invocar un futuro getById/findByPk global sin contexto ni purpose abre IDOR de recursos locales y exposición de notas/integraciones. Las lecturas actuales por IDs de hijos están precedidas por raíces filtradas; conservar esa precondición explícita. Respuesta no debe confirmar nombres, fabricante o historial de B en errores para C.
## Concurrencia
**RIESGO ALTO:** la instalación/configuración abre transacción propia y `resources.list(company)` abre otra; no se transmite Transaction. Una desactivación entre validación y commit puede dejar una asociación nueva a un recurso ya inactivo. Una FK evita referencia inexistente, no garantiza active/eligibility al commit. [softwareRead: sequelize-software-installation.repository.ts:27](../../../modules/companies/src/api/infrastructure/persistence/sequelize-software-installation.repository.ts#L27) [applicationRead: sequelize-application-configuration.repository.ts:34](../../../modules/companies/src/api/infrastructure/persistence/sequelize-application-configuration.repository.ts#L34) [service: resource-inventory.service.ts:7](../../../modules/resources/src/api/application/resource-inventory.service.ts#L7).
Propuesta: definir punto de serialización y orden global de locks entre catálogo/recurso/compañía antes de implementar; resolver/validar en la misma unidad de consistencia mediante servicio público ligado a transacción o mecanismo equivalente. No exportar repositorios de Resources. Probar carrera de desactivación, creación, borrado y propagación; definir si una asociación concurrente se rechaza o se conserva con revisión. Ver [10](10-plan-implementacion.md).
## Índice de SQL directo sobre resources
Extraído de las fuentes activas; complementa las familias de consultas anteriores. Incluye DML de migraciones sin ejecutarlas. `readInventory` y `writeInventory` usan helpers de tabla y se citan en la tabla principal, pues no contienen SQL literal sobre resources.
| Sentencia / localización | Clasificación de intención futura |
| --- | --- |
| [modules/assets/src/api/infrastructure/persistence/sequelize-support.repository.ts:21](../../../modules/assets/src/api/infrastructure/persistence/sequelize-support.repository.ts#L21) | 1/4: datos operativos de compañía; conservar filtro |
| [modules/resources/src/api/infrastructure/persistence/directory-domain-options-reader.ts:32](../../../modules/resources/src/api/infrastructure/persistence/directory-domain-options-reader.ts#L32) | 1/4: datos operativos de compañía; conservar filtro |
| [modules/resources/src/api/infrastructure/persistence/directory-domain-reader.ts:28](../../../modules/resources/src/api/infrastructure/persistence/directory-domain-reader.ts#L28) | 1/4: datos operativos de compañía; conservar filtro |
| [modules/resources/src/api/infrastructure/persistence/directory-domain-summary-resolver.ts:67](../../../modules/resources/src/api/infrastructure/persistence/directory-domain-summary-resolver.ts#L67) | 1/4: datos operativos de compañía; conservar filtro |
| [modules/resources/src/api/infrastructure/persistence/migrate-support-resources.ts:205](../../../modules/resources/src/api/infrastructure/persistence/migrate-support-resources.ts#L205) | 4: migración interna, raíz/revisión previamente seleccionada |
| [modules/resources/src/api/infrastructure/persistence/network-share-options.ts:24](../../../modules/resources/src/api/infrastructure/persistence/network-share-options.ts#L24) | 1/4: datos operativos de compañía; conservar filtro |
| [modules/resources/src/api/infrastructure/persistence/network-share-reader.ts:32](../../../modules/resources/src/api/infrastructure/persistence/network-share-reader.ts#L32) | 1/4: datos operativos de compañía; conservar filtro |
| [modules/resources/src/api/infrastructure/persistence/network-share-summary-resolver.ts:67](../../../modules/resources/src/api/infrastructure/persistence/network-share-summary-resolver.ts#L67) | 1/4: datos operativos de compañía; conservar filtro |
| [modules/resources/src/api/infrastructure/persistence/prepare-resource-template-updates.ts:55](../../../modules/resources/src/api/infrastructure/persistence/prepare-resource-template-updates.ts#L55) | 5: consulta transversal interna; decidir globals explícitamente |
| [modules/resources/src/api/infrastructure/persistence/remove-resource.ts:101](../../../modules/resources/src/api/infrastructure/persistence/remove-resource.ts#L101) | 1/4: borrado local de raíz autorizada |
| [modules/resources/src/api/infrastructure/persistence/resource-dashboard-reader.ts:20](../../../modules/resources/src/api/infrastructure/persistence/resource-dashboard-reader.ts#L20) | 5: consulta transversal interna; decidir globals explícitamente |
| [modules/resources/src/api/infrastructure/persistence/resource-dashboard-reader.ts:45](../../../modules/resources/src/api/infrastructure/persistence/resource-dashboard-reader.ts#L45) | 5: consulta transversal interna; decidir globals explícitamente |
| [modules/resources/src/api/infrastructure/persistence/resource-dashboard-reader.ts:49](../../../modules/resources/src/api/infrastructure/persistence/resource-dashboard-reader.ts#L49) | 5: consulta transversal interna; decidir globals explícitamente |
| [modules/resources/src/api/infrastructure/persistence/resource-dashboard-reader.ts:58](../../../modules/resources/src/api/infrastructure/persistence/resource-dashboard-reader.ts#L58) | 5: consulta transversal interna; decidir globals explícitamente |
| [modules/resources/src/api/infrastructure/persistence/resource-revision-specifications.ts:126](../../../modules/resources/src/api/infrastructure/persistence/resource-revision-specifications.ts#L126) | 4: migración interna, raíz/revisión previamente seleccionada |