From 01c81bfb1a7d119178ef0d8c9dca3820bbabb59c Mon Sep 17 00:00:00 2001 From: DioCrafts Date: Wed, 2 Apr 2025 23:14:12 +0200 Subject: [PATCH] adding features --- .github/docker-hub-setup.md | 54 +++ .github/workflows/docker-build.yml | 56 +++ .github/workflows/docker-publish.yml | 63 +++ CLAUDE.md | 60 +++ doc/DAV-CLIENT-SETUP.md | 246 ++++++++++ doc/DAV-IMPLEMENTATION-PLAN.md | 286 +++++++++++ doc/DAV-INTEGRATION.md | 603 +++++++++++++++++++++++ doc/OIDC-ARCHITECTURE.md | 162 +++++++ doc/OIDC-CONFIG-EXAMPLES.md | 218 +++++++++ doc/OIDC-INTEGRATION.md | 702 +++++++++++++++++++++++++++ 10 files changed, 2450 insertions(+) create mode 100644 .github/docker-hub-setup.md create mode 100644 .github/workflows/docker-build.yml create mode 100644 .github/workflows/docker-publish.yml create mode 100644 CLAUDE.md create mode 100644 doc/DAV-CLIENT-SETUP.md create mode 100644 doc/DAV-IMPLEMENTATION-PLAN.md create mode 100644 doc/DAV-INTEGRATION.md create mode 100644 doc/OIDC-ARCHITECTURE.md create mode 100644 doc/OIDC-CONFIG-EXAMPLES.md create mode 100644 doc/OIDC-INTEGRATION.md diff --git a/.github/docker-hub-setup.md b/.github/docker-hub-setup.md new file mode 100644 index 00000000..dfe2341d --- /dev/null +++ b/.github/docker-hub-setup.md @@ -0,0 +1,54 @@ +# Configuración para Docker Hub y GitHub Actions + +Este documento explica cómo configurar los secretos necesarios para publicar imágenes de Docker en Docker Hub usando GitHub Actions. + +## Requisitos previos + +1. Una cuenta en [Docker Hub](https://hub.docker.com/) +2. Un repositorio en Docker Hub donde subir la imagen +3. Un token de acceso personal (PAT) de Docker Hub + +## Pasos para configurar los secretos en GitHub + +1. Genera un token de acceso en Docker Hub + - Inicia sesión en [Docker Hub](https://hub.docker.com/) + - Ve a tu perfil (esquina superior derecha) → Account Settings → Security + - Haz clic en "New Access Token" + - Proporciona una descripción como "GitHub Actions" + - Selecciona los permisos apropiados (normalmente "Read, Write, Delete") + - Haz clic en "Generate" + - **IMPORTANTE**: Copia el token generado, ya que no podrás verlo de nuevo + +2. Configura los secretos en tu repositorio de GitHub + - Ve a tu repositorio en GitHub + - Haz clic en "Settings" → "Secrets and variables" → "Actions" + - Haz clic en "New repository secret" + - Añade los siguientes secretos: + - Nombre: `DOCKERHUB_USERNAME` + Valor: Tu nombre de usuario de Docker Hub + - Nombre: `DOCKERHUB_TOKEN` + Valor: El token de acceso que generaste en el paso anterior + +## Uso + +Una vez configurados los secretos, los flujos de trabajo de GitHub Actions podrán autenticarse con Docker Hub y publicar imágenes. + +Cuando crees una nueva [release en GitHub](https://docs.github.com/es/repositories/releasing-projects-on-github/managing-releases-in-a-repository#creating-a-release), el flujo de trabajo `docker-publish.yml` se activará automáticamente y: + +1. Construirá la imagen de Docker +2. La etiquetará con el número de versión de la release +3. La subirá a Docker Hub + +## Verificación + +Para verificar que la configuración está correcta: + +1. Crea una nueva release en GitHub +2. Ve a la pestaña "Actions" y observa el progreso del flujo de trabajo +3. Una vez completado, verifica que la imagen aparezca en tu repositorio de Docker Hub + +## Notas adicionales + +- Para entornos de producción, considera usar un usuario de servicio en Docker Hub en lugar de tu cuenta personal +- Rota regularmente los tokens de acceso para mayor seguridad +- Considera agregar escaneo de vulnerabilidades en las imágenes como parte del flujo de trabajo \ No newline at end of file diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 00000000..33d1a5ce --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,56 @@ +name: Docker Build and Test + +# Trigger on push to main branch or on pull requests +on: + push: + branches: [ "main", "dev" ] + pull_request: + branches: [ "main", "dev" ] + +jobs: + # Build and test Docker image but don't push + build-and-test: + name: Build and Test Docker Image + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + # Set up Docker Buildx + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + # Extract metadata (tags, labels) for Docker + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: test/oxicloud + tags: | + type=ref,event=branch + type=ref,event=pr + type=sha + + # Build Docker image but don't push + - name: Build Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: false + load: true + tags: test/oxicloud:test + cache-from: type=gha + cache-to: type=gha,mode=max + + # Run some basic tests against the built image + - name: Test Docker image + run: | + docker run --rm test/oxicloud:test --version || true + docker run --rm test/oxicloud:test --help || true + + # Verify the image structure + echo "✅ Checking Docker image layers and size" + docker image inspect test/oxicloud:test + + echo "✅ Docker build and test completed successfully" \ No newline at end of file diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 00000000..2b7b18ff --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,63 @@ +name: Docker Hub Release + +# Trigger the workflow when a release is published +on: + release: + types: [published] + +jobs: + # Build and publish Docker image + build-and-push: + name: Build and Push Docker Image + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + # Set up Docker Buildx for efficient builds + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + # Login to Docker Hub + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + # Extract metadata (tags, labels) for Docker + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ secrets.DOCKERHUB_USERNAME }}/oxicloud + # Generate Docker tags based on release tag, commit SHA, and latest + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=ref,event=branch + type=sha + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }} + + # Build and push Docker image + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + platforms: linux/amd64,linux/arm64 + # Build args if needed + build-args: | + VERSION=${{ github.ref_name }} + + # Post successful build notification + - name: Post Success Notification + if: success() + run: | + echo "🚢 Docker image for version ${{ github.ref_name }} has been successfully pushed to Docker Hub!" \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..75cdd725 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,60 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Build/Lint/Test Commands + +### Building +- Build debug version: `cargo build` +- Build release version: `cargo build --release` +- Run the application: `cargo run` + +### Testing +- Run all tests: `cargo test` +- Run a specific test: `cargo test test_name` +- Run tests for a specific module: `cargo test module_name` +- Run tests with feature flags: `cargo test --features test_utils` + +### Linting +- Run clippy linting: `cargo clippy` +- Format code: `cargo fmt` + +## Code Style Guidelines + +### Architecture +- This project follows a hexagonal/clean architecture pattern: + - `application`: Contains services (use cases), DTOs, and ports + - `domain`: Contains core entities, repositories (interfaces), and domain services + - `infrastructure`: Contains concrete implementations of repository interfaces + - `interfaces`: Contains HTTP/API handlers and routes + +### Error Handling +- Use the `DomainError` type for domain-level errors +- Use the `AppError` type for API/HTTP-level errors +- Use the `ErrorContext` trait to add context to errors from external crates +- Follow the error factory pattern for creating common error types + +### Naming Conventions +- Types and structs: PascalCase +- Functions and methods: snake_case +- Constants and statics: SCREAMING_SNAKE_CASE +- Modules and files: snake_case +- Use descriptive names that express intent + +### Testing +- Use mock objects for dependencies in unit tests +- Use the `#[tokio::test]` attribute for async tests +- Include both positive and negative test cases +- Follow the Arrange-Act-Assert pattern in tests + +### Imports +- Group imports by source: + 1. Standard library imports + 2. External crate imports + 3. Local crate imports (with `crate::` prefix) +- Use explicit imports (no glob imports except in tests) + +### Documentation +- Document public API functions and types with doc comments +- Include examples where helpful +- Document error cases and conditions \ No newline at end of file diff --git a/doc/DAV-CLIENT-SETUP.md b/doc/DAV-CLIENT-SETUP.md new file mode 100644 index 00000000..7b3eb2ae --- /dev/null +++ b/doc/DAV-CLIENT-SETUP.md @@ -0,0 +1,246 @@ +# Configuración de Clientes DAV para OxiCloud + +Esta guía proporciona instrucciones para configurar varios clientes que soportan WebDAV, CalDAV y CardDAV para conectar con OxiCloud. + +## Tabla de Contenidos + +1. [URLs de Conexión](#urls-de-conexión) +2. [Clientes WebDAV](#clientes-webdav) +3. [Clientes CalDAV](#clientes-caldav) +4. [Clientes CardDAV](#clientes-carddav) +5. [Solución de Problemas](#solución-de-problemas) + +## URLs de Conexión + +Usa las siguientes URLs para conectar tus clientes con OxiCloud: + +- **WebDAV**: `https://tu-servidor.com/webdav/` +- **CalDAV**: + - Principal: `https://tu-servidor.com/caldav/` + - Calendario específico: `https://tu-servidor.com/caldav/{nombre-calendario}/` +- **CardDAV**: + - Principal: `https://tu-servidor.com/carddav/addressbooks/` + - Libreta específica: `https://tu-servidor.com/carddav/addressbooks/{nombre-libreta}/` + +## Clientes WebDAV + +### Windows + +#### Windows Explorer + +1. Abre el Explorador de Windows +2. Haz clic derecho en "Este equipo" y selecciona "Agregar una ubicación de red" +3. Haz clic en "Siguiente" +4. Selecciona "Elegir una ubicación de red personalizada" y haz clic en "Siguiente" +5. En el campo de dirección, introduce: `https://tu-servidor.com/webdav/` +6. Haz clic en "Siguiente" +7. Introduce un nombre para la conexión (ej. "OxiCloud") +8. Haz clic en "Siguiente" y luego en "Finalizar" +9. Introduce tus credenciales cuando se te soliciten + +#### Problemas Comunes en Windows + +- **Error de SSL**: Asegúrate de que tu certificado SSL sea válido y confiable para Windows +- **Bloqueo por WebClient**: Asegúrate de que el servicio "WebClient" de Windows esté activado +- **Límite de tamaño**: Windows limita por defecto las cargas a 50MB, modifica el registro para aumentarlo: + +``` +[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WebClient\Parameters] +"FileSizeLimitInBytes"=dword:00FFFFFF +``` + +### macOS + +#### Finder + +1. En Finder, haz clic en "Ir" en la barra de menú +2. Selecciona "Conectar al servidor..." o presiona ⌘+K +3. Introduce `https://tu-servidor.com/webdav/` como dirección del servidor +4. Haz clic en "Conectar" +5. Introduce tus credenciales cuando se te soliciten +6. Selecciona si deseas guardar la contraseña en el llavero + +### Linux + +#### GNOME Files (Nautilus) + +1. Abre Nautilus (Archivos) +2. Haz clic en "Otras ubicaciones" en el panel lateral +3. En la parte inferior, introduce `davs://tu-servidor.com/webdav/` en "Conectar al servidor" +4. Haz clic en "Conectar" +5. Introduce tus credenciales cuando se te soliciten + +#### Dolphin (KDE) + +1. Abre Dolphin +2. En la barra de dirección, escribe `webdavs://tu-servidor.com/webdav/` +3. Introduce tus credenciales cuando se te soliciten + +### Clientes Multiplataforma + +#### Cyberduck + +1. Descarga e instala [Cyberduck](https://cyberduck.io/) +2. Haz clic en "Nueva conexión" +3. Selecciona "WebDAV (HTTP/SSL)" como tipo de conexión +4. Introduce los siguientes datos: + - Servidor: `tu-servidor.com` + - Puerto: `443` + - Ruta: `/webdav/` + - Nombre de usuario: tu nombre de usuario + - Contraseña: tu contraseña +5. Haz clic en "Conectar" + +## Clientes CalDAV + +### Apple Calendar (macOS/iOS) + +#### macOS + +1. Abre la aplicación Calendario +2. Haz clic en "Calendario" en la barra de menú +3. Selecciona "Añadir cuenta..." +4. Selecciona "Otra cuenta de CalDAV..." +5. Completa la información: + - Correo electrónico: tu dirección de correo + - Contraseña: tu contraseña + - Dirección del servidor: `tu-servidor.com` + - Ruta: `/caldav/` (deja en blanco si no funciona) +6. Haz clic en "Iniciar sesión" + +#### iOS + +1. Ve a Ajustes > Calendario > Cuentas > Añadir cuenta +2. Selecciona "Otra" +3. Selecciona "Añadir cuenta CalDAV" +4. Completa la información: + - Servidor: `https://tu-servidor.com/caldav/` + - Nombre de usuario: tu nombre de usuario + - Contraseña: tu contraseña + - Descripción: "OxiCloud Calendario" +5. Toca "Siguiente" y luego "Guardar" + +### Mozilla Thunderbird con Lightning + +1. Instala Thunderbird y la extensión Lightning +2. Haz clic en el botón de calendario en la barra lateral +3. Haz clic derecho en el panel izquierdo y selecciona "Nuevo calendario" +4. Selecciona "En la red" y haz clic en "Siguiente" +5. Selecciona "CalDAV" como formato +6. Introduce `https://tu-servidor.com/caldav/nombre-calendario/` como ubicación +7. Haz clic en "Siguiente", introduce un nombre para el calendario +8. Completa la configuración y haz clic en "Finalizar" +9. Introduce tus credenciales cuando se te soliciten + +### Nextcloud Desktop Sync + +1. Descarga e instala el cliente de sincronización de Nextcloud +2. Durante la configuración, selecciona "Solo sincronización de calendario y contactos" +3. Introduce `https://tu-servidor.com` como dirección del servidor +4. Introduce tus credenciales +5. En las opciones de sincronización, selecciona los calendarios que deseas sincronizar + +## Clientes CardDAV + +### Apple Contacts (macOS/iOS) + +#### macOS + +1. Abre la aplicación Contactos +2. Haz clic en "Contactos" en la barra de menú +3. Selecciona "Añadir cuenta..." +4. Selecciona "Otra cuenta de CardDAV..." +5. Completa la información: + - Correo electrónico: tu dirección de correo + - Contraseña: tu contraseña + - Dirección del servidor: `tu-servidor.com` + - Ruta: `/carddav/addressbooks/` (deja en blanco si no funciona) +6. Haz clic en "Iniciar sesión" + +#### iOS + +1. Ve a Ajustes > Contactos > Cuentas > Añadir cuenta +2. Selecciona "Otra" +3. Selecciona "Añadir cuenta CardDAV" +4. Completa la información: + - Servidor: `https://tu-servidor.com/carddav/addressbooks/` + - Nombre de usuario: tu nombre de usuario + - Contraseña: tu contraseña + - Descripción: "OxiCloud Contactos" +5. Toca "Siguiente" y luego "Guardar" + +### Mozilla Thunderbird + +1. Instala Thunderbird y la extensión CardBook +2. Abre CardBook desde el menú de Thunderbird +3. Haz clic en "Libreta de direcciones" > "Nuevo" > "Libreta de direcciones remota" +4. Selecciona "CardDAV" como tipo +5. Introduce `https://tu-servidor.com/carddav/addressbooks/nombre-libreta/` como URL +6. Introduce un nombre para la libreta de direcciones +7. Introduce tus credenciales +8. Haz clic en "Validar" y luego en "Aceptar" + +### Cliente Evolution (Linux) + +1. Abre Evolution +2. Ve a Archivo > Nuevo > Libreta de direcciones +3. Selecciona "CardDAV" como tipo +4. Introduce `https://tu-servidor.com/carddav/addressbooks/nombre-libreta/` como URL +5. Introduce un nombre para la libreta de direcciones +6. Introduce tus credenciales +7. Haz clic en "Aplicar" + +## Solución de Problemas + +### Problemas Comunes + +1. **Error de autenticación** + - Verifica que estés usando las credenciales correctas + - Asegúrate de que tu cuenta tenga acceso a los recursos DAV + - Si usas autenticación de dos factores, es posible que necesites crear una contraseña de aplicación específica + +2. **No se pueden encontrar calendarios/libretas** + - Verifica que hayas creado calendarios o libretas de direcciones en OxiCloud + - Asegúrate de estar usando la URL correcta, incluyendo la terminación con `/` + - Verifica los permisos de los recursos + +3. **Error SSL/TLS** + - Asegúrate de que tu certificado SSL sea válido y confiable + - Verifica que la fecha y hora de tu dispositivo sean correctas + - En algunos clientes, puede ser necesario confiar manualmente en el certificado + +4. **Sincronización lenta** + - Limita el número de elementos en tus calendarios y libretas de direcciones + - Verifica la calidad de tu conexión a Internet + - Algunas operaciones masivas (como importar muchos contactos) pueden tardar tiempo + +### Herramientas de Diagnóstico + +1. **Verificación de conectividad**: Prueba la conexión básica con: + ``` + curl -v https://tu-servidor.com/webdav/ + ``` + +2. **Prueba de autenticación**: + ``` + curl -v -u usuario:contraseña https://tu-servidor.com/webdav/ + ``` + +3. **Prueba de funcionalidad WebDAV**: + ``` + curl -X PROPFIND -H "Depth: 1" -u usuario:contraseña https://tu-servidor.com/webdav/ + ``` + +4. **Prueba de funcionalidad CalDAV**: + ``` + curl -X PROPFIND -H "Depth: 1" -u usuario:contraseña https://tu-servidor.com/caldav/ + ``` + +5. **Logs del servidor**: Si tienes acceso, revisa los logs del servidor para identificar problemas específicos. + +### Contacto para Soporte + +Si continúas experimentando problemas, contacta con soporte en: +- Email: soporte@ejemplo.com +- Foro: https://ejemplo.com/foro +- Sistema de tickets: https://soporte.ejemplo.com \ No newline at end of file diff --git a/doc/DAV-IMPLEMENTATION-PLAN.md b/doc/DAV-IMPLEMENTATION-PLAN.md new file mode 100644 index 00000000..56d6eca9 --- /dev/null +++ b/doc/DAV-IMPLEMENTATION-PLAN.md @@ -0,0 +1,286 @@ +# Plan de Implementación DAV para OxiCloud + +Este documento presenta un plan de implementación estructurado para añadir soporte WebDAV, CalDAV y CardDAV a OxiCloud. + +## Resumen Ejecutivo + +La implementación de los protocolos DAV (WebDAV, CalDAV y CardDAV) permitirá a OxiCloud interoperar con una amplia gama de clientes y dispositivos, aumentando significativamente su versatilidad y utilidad. Este plan propone un enfoque por fases que prioriza primero WebDAV (para acceso a archivos), seguido de CalDAV (para calendarios) y finalmente CardDAV (para contactos). + +## Fases de Implementación + +### Fase 1: Infraestructura DAV Común (Estimado: 2-3 semanas) + +**Objetivos:** +- Establecer la infraestructura básica compartida por todos los protocolos DAV +- Implementar el manejo de solicitudes XML y respuestas +- Crear adaptadores para las operaciones básicas DAV + +**Tareas:** +1. **Semana 1: Diseño y Arquitectura** + - Diseñar la arquitectura de los componentes DAV + - Definir interfaces para adaptadores DAV + - Seleccionar bibliotecas para procesamiento XML y RFC4918 + +2. **Semana 2: Implementación Base** + - Implementar manejadores de serialización/deserialización XML + - Desarrollar middleware para procesamiento de solicitudes DAV + - Crear estructuras comunes (propiedades, espacios de nombres) + - Implementar validación de solicitudes DAV + +3. **Semana 3: Framework de Pruebas** + - Configurar entorno de pruebas para protocolos DAV + - Implementar clientes de prueba automatizados + - Crear casos de prueba para operaciones DAV básicas + +**Entregables:** +- Framework de procesamiento XML para solicitudes/respuestas DAV +- Adaptadores base para las entidades existentes +- Suite de pruebas para operaciones DAV + +### Fase 2: WebDAV (Estimado: 3-4 semanas) + +**Objetivos:** +- Implementar el protocolo WebDAV completo (RFC4918) +- Permitir acceso a archivos y carpetas vía WebDAV +- Asegurar compatibilidad con clientes WebDAV comunes + +**Tareas:** +1. **Semana 1: Operaciones Básicas** + - Implementar métodos PROPFIND y PROPPATCH + - Desarrollar endpoint OPTIONS (descubrimiento de capacidades) + - Implementar operaciones GET, HEAD, PUT (lectura/escritura) + +2. **Semana 2: Operaciones Avanzadas** + - Implementar MKCOL (creación de directorios) + - Desarrollar DELETE para recursos WebDAV + - Implementar COPY y MOVE para archivos y directorios + +3. **Semana 3: Bloqueo y Características Extendidas** + - Implementar LOCK y UNLOCK para recursos + - Añadir soporte para propiedades personalizadas + - Desarrollar características de WebDAV extendidas (si es necesario) + +4. **Semana 4: Pruebas y Optimización** + - Realizar pruebas con clientes reales (Windows, macOS, Linux) + - Optimizar rendimiento para transferencias grandes + - Documentar APIs y comportamiento WebDAV + +**Entregables:** +- Implementación completa de WebDAV (RFC4918) +- Documentación de uso de WebDAV con OxiCloud +- Compatibilidad con los clientes WebDAV más comunes + +### Fase 3: CalDAV (Estimado: 4-5 semanas) + +**Objetivos:** +- Implementar el protocolo CalDAV (RFC4791) +- Crear entidades y repositorios para calendarios y eventos +- Soportar operaciones de calendario con clientes comunes + +**Tareas:** +1. **Semana 1: Modelo de Datos** + - Implementar entidades Calendar y CalendarEvent + - Desarrollar repositorios para almacenamiento de datos + - Crear DTOs y adaptadores CalDAV + +2. **Semana 2: Endpoints Básicos** + - Implementar PROPFIND para detección de calendarios + - Desarrollar MKCALENDAR para creación de calendarios + - Implementar GET/PUT para eventos individuales + +3. **Semana 3: Consultas Avanzadas** + - Implementar REPORT para consultas de calendario + - Desarrollar soporte para búsqueda por rango de fechas + - Añadir manejo de recurrencias (reglas RRULE) + +4. **Semana 4: Interoperabilidad** + - Implementar sincronización eficiente (collection-sync) + - Añadir soporte para zonas horarias + - Desarrollar manejo de alarmas y notificaciones + +5. **Semana 5: Pruebas y Refinamiento** + - Probar con clientes CalDAV populares + - Optimizar rendimiento para calendarios grandes + - Documentar APIs y comportamiento CalDAV + +**Entregables:** +- Implementación completa de CalDAV (RFC4791) +- Soporte para creación y gestión de calendarios +- Compatibilidad con clientes CalDAV populares +- Documentación de uso de CalDAV con OxiCloud + +### Fase 4: CardDAV (Estimado: 3-4 semanas) + +**Objetivos:** +- Implementar el protocolo CardDAV (RFC6352) +- Crear entidades y repositorios para libretas de direcciones y contactos +- Soportar operaciones de contactos con clientes comunes + +**Tareas:** +1. **Semana 1: Modelo de Datos** + - Implementar entidades AddressBook y Contact + - Desarrollar repositorios para almacenamiento de datos + - Crear DTOs y adaptadores CardDAV + +2. **Semana 2: Endpoints Básicos** + - Implementar PROPFIND para detección de libretas + - Desarrollar MKCOL para creación de libretas de direcciones + - Implementar GET/PUT para contactos individuales + +3. **Semana 3: Consultas y Búsqueda** + - Implementar REPORT para consultas de contactos + - Desarrollar búsqueda de contactos por criterios + - Añadir soporte para grupos de contactos + +4. **Semana 4: Pruebas y Refinamiento** + - Probar con clientes CardDAV populares + - Optimizar rendimiento para libretas grandes + - Documentar APIs y comportamiento CardDAV + +**Entregables:** +- Implementación completa de CardDAV (RFC6352) +- Soporte para creación y gestión de libretas de direcciones +- Compatibilidad con clientes CardDAV populares +- Documentación de uso de CardDAV con OxiCloud + +### Fase 5: Integración y Lanzamiento (Estimado: 2-3 semanas) + +**Objetivos:** +- Integrar todos los protocolos DAV en una solución cohesiva +- Asegurar compatibilidad cruzada entre protocolos +- Preparar la documentación y materiales para lanzamiento + +**Tareas:** +1. **Semana 1: Integración** + - Consolidar código compartido entre protocolos + - Asegurar coherencia de comportamiento + - Refinar manejo de errores y recuperación + +2. **Semana 2: Pruebas de Sistema** + - Realizar pruebas de integración end-to-end + - Validar rendimiento bajo carga + - Verificar seguridad y permisos + +3. **Semana 3: Documentación y Lanzamiento** + - Finalizar guías de usuario para clientes DAV + - Crear documentación para desarrolladores + - Preparar materiales de lanzamiento + +**Entregables:** +- Solución DAV completa e integrada +- Documentación comprensiva para usuarios y desarrolladores +- Paquete de lanzamiento listo para despliegue + +## Requisitos de Infraestructura + +### Dependencias de Bibliotecas + +```toml +# Añadir a Cargo.toml +[dependencies] +# Procesamiento XML +quick-xml = "0.30.0" +xml-rs = "0.8.14" + +# Soporte para iCalendar +icalendar = "0.15.0" + +# Soporte para vCard +vcard = "0.2.0" + +# Utilidades para DAV +http-multipart = "0.3.0" +``` + +### Esquema de Base de Datos + +Las nuevas tablas para CalDAV y CardDAV deben ser creadas como parte de la fase correspondiente. Ver el esquema completo en el documento principal de implementación. + +## Estrategia de Pruebas + +### Pruebas Unitarias + +- Pruebas de serialización/deserialización XML +- Pruebas de validación de entradas +- Pruebas de lógica de negocio para cada operación DAV + +### Pruebas de Integración + +- Pruebas end-to-end con clientes simulados +- Pruebas de flujos completos (creación, actualización, eliminación) +- Pruebas de concurrencia y manejo de conflictos + +### Pruebas de Compatibilidad + +- Matriz de pruebas con clientes reales (al menos 3 por protocolo) +- Pruebas en diferentes sistemas operativos +- Verificación de conformidad con RFCs + +## Consideraciones de Rendimiento + +1. **Optimización de Consultas** + - Implementar paginación para conjuntos grandes de resultados + - Optimizar consultas SQL para calendarios y contactos + - Utilizar índices adecuados para búsqueda rápida + +2. **Caché** + - Implementar caché de propiedades para respuestas PROPFIND + - Usar ETags para validación de caché + - Aplicar caché de consultas para reportes frecuentes + +3. **Procesamiento Eficiente** + - Procesamiento XML eficiente para solicitudes grandes + - Streaming de datos para archivos grandes + - Procesamiento asíncrono para operaciones costosas + +## Riesgos y Mitigación + +| Riesgo | Impacto | Probabilidad | Estrategia de Mitigación | +|--------|---------|--------------|--------------------------| +| Problemas de compatibilidad con clientes | Alto | Medio | Pruebas tempranas con variedad de clientes, seguir estrictamente las especificaciones | +| Rendimiento insuficiente | Medio | Bajo | Pruebas de carga desde el inicio, diseño para escalabilidad | +| Complejidad excesiva | Medio | Medio | Enfoque modular, abstracciones claras, revisiones de código frecuentes | +| Problemas de seguridad | Alto | Bajo | Revisiones de seguridad, validación estricta de entradas, pruebas de penetración | +| Retrasos en el cronograma | Medio | Medio | Planificación conservadora, hitos claros, enfoque iterativo | + +## Criterios de Éxito + +1. **Compatibilidad** + - Todos los protocolos cumplen con sus respectivos RFCs + - Compatibilidad verificada con al menos 3 clientes principales por protocolo + - Funciona en todos los sistemas operativos principales + +2. **Rendimiento** + - Tiempo de respuesta para operaciones típicas < 500ms + - Soporta calendarios con >1000 eventos sin degradación significativa + - Soporta libretas con >1000 contactos sin degradación significativa + +3. **Usabilidad** + - Proceso de configuración de cliente sencillo y documentado + - Mensajes de error claros y específicos + - Documentación completa para usuarios y desarrolladores + +## Recursos Necesarios + +1. **Equipo de Desarrollo** + - 1-2 desarrolladores de backend (Rust) + - 1 desarrollador de frontend (para integración UI si es necesario) + - 1 tester + +2. **Infraestructura** + - Entorno de pruebas con múltiples sistemas operativos + - Clientes DAV variados para pruebas + - Servidor de CI/CD para pruebas automatizadas + +3. **Habilidades** + - Experiencia con protocolos HTTP avanzados + - Conocimiento de procesamiento XML + - Familiaridad con los estándares WebDAV, CalDAV y CardDAV + +## Próximos Pasos + +1. Asignar recursos al proyecto +2. Establecer repositorio de código y estructura inicial +3. Iniciar la Fase 1 (Infraestructura DAV Común) +4. Configurar entorno de CI/CD para pruebas +5. Revisar y refinar el plan según sea necesario durante la implementación \ No newline at end of file diff --git a/doc/DAV-INTEGRATION.md b/doc/DAV-INTEGRATION.md new file mode 100644 index 00000000..e4d5959d --- /dev/null +++ b/doc/DAV-INTEGRATION.md @@ -0,0 +1,603 @@ +# Integración de WebDAV, CalDAV y CardDAV en OxiCloud + +Este documento describe el diseño e implementación de los protocolos WebDAV, CalDAV y CardDAV en OxiCloud, extendiendo la plataforma para soportar clientes y dispositivos que utilizan estos estándares. + +## Tabla de Contenidos + +1. [Introducción](#introducción) +2. [Arquitectura de la Implementación](#arquitectura-de-la-implementación) +3. [WebDAV](#webdav) +4. [CalDAV](#caldav) +5. [CardDAV](#carddav) +6. [Consideraciones de Seguridad](#consideraciones-de-seguridad) +7. [Pruebas y Compatibilidad](#pruebas-y-compatibilidad) + +## Introducción + +### WebDAV (Web Distributed Authoring and Versioning) +WebDAV es una extensión del protocolo HTTP que permite a los clientes realizar operaciones sobre archivos en un servidor remoto, como crear, modificar, mover y eliminar archivos y directorios. + +### CalDAV (Calendaring Extensions to WebDAV) +CalDAV es un protocolo basado en WebDAV que permite a los clientes acceder y gestionar datos de calendario, como eventos y tareas. + +### CardDAV (vCard Extensions to WebDAV) +CardDAV es un protocolo que extiende WebDAV para permitir el acceso y gestión de datos de contactos en formato vCard. + +## Arquitectura de la Implementación + +La implementación de los protocolos DAV se integra en la arquitectura hexagonal existente de OxiCloud: + +``` +┌────────────────────────────────────────────────────────────────────┐ +│ INTERFACES │ +│ │ +│ ┌───────────────┐ ┌───────────────┐ ┌───────────────────────┐ │ +│ │ │ │ │ │ │ │ +│ │ REST API │ │ WebDAV API │ │ CalDAV/CardDAV API │ │ +│ │ │ │ │ │ │ │ +│ └───────┬───────┘ └───────┬───────┘ └───────────┬───────────┘ │ +│ │ │ │ │ +└──────────┼──────────────────┼──────────────────────┼──────────────┘ + │ │ │ + ▼ ▼ ▼ +┌──────────────────────────────────────────────────────────────────┐ +│ APLICACIÓN │ +│ │ +│ ┌───────────┐ ┌────────────┐ ┌───────────┐ ┌──────────────┐ │ +│ │ │ │ │ │ │ │ │ │ +│ │FileService│ │FolderService│ │CalService │ │ContactService│ │ +│ │ │ │ │ │ │ │ │ │ +│ └─────┬─────┘ └──────┬─────┘ └─────┬─────┘ └──────┬───────┘ │ +│ │ │ │ │ │ +└────────┼───────────────┼──────────────┼───────────────┼─────────┘ + │ │ │ │ + ▼ ▼ ▼ ▼ +┌────────────────────────────────────────────────────────────────┐ +│ DOMINIO │ +│ │ +│ ┌─────────┐ ┌──────────┐ ┌────────────┐ ┌───────────────┐ │ +│ │ │ │ │ │ │ │ │ │ +│ │ File │ │ Folder │ │ Calendar │ │ Contact │ │ +│ │ │ │ │ │ │ │ │ │ +│ └─────────┘ └──────────┘ └────────────┘ └───────────────┘ │ +│ │ +└────────────────────────────────────────────────────────────────┘ +``` + +### Componentes Principales + +1. **Adaptadores DAV**: Convertirán entre las especificaciones DAV y los modelos de OxiCloud +2. **Servicios de Aplicación**: Se extenderán para incluir funcionalidades específicas DAV +3. **Modelos de Dominio**: Se añadirán nuevas entidades para Calendar y Contact +4. **Repositorios**: Implementaciones de almacenamiento para calendarios y contactos + +## WebDAV + +### Endpoints Requeridos + +| Método HTTP | Endpoint | Descripción | +|-------------|----------|-------------| +| OPTIONS | /webdav/{path} | Indica las capacidades WebDAV soportadas | +| PROPFIND | /webdav/{path} | Recupera propiedades de recursos | +| PROPPATCH | /webdav/{path} | Modifica propiedades de recursos | +| MKCOL | /webdav/{path} | Crea colecciones (directorios) | +| GET | /webdav/{path} | Recupera contenido de recursos | +| HEAD | /webdav/{path} | Recupera metadatos de recursos | +| PUT | /webdav/{path} | Crea o actualiza recursos | +| DELETE | /webdav/{path} | Elimina recursos | +| COPY | /webdav/{path} | Copia recursos | +| MOVE | /webdav/{path} | Mueve recursos | +| LOCK | /webdav/{path} | Bloquea recursos | +| UNLOCK | /webdav/{path} | Desbloquea recursos | + +### Implementación + +1. **Manejador WebDAV**: + +```rust +// src/interfaces/api/handlers/webdav_handler.rs +use std::sync::Arc; +use axum::{ + Router, + routing::get, + extract::{Path, State, Request, Extension}, + http::StatusCode, + response::Response, +}; +use http::{Method, header}; + +use crate::common::di::AppState; +use crate::interfaces::middleware::auth::CurrentUser; +use crate::application::ports::file_ports::{FileRetrievalUseCase, FileUploadUseCase}; +use crate::application::ports::folder_ports::FolderUseCase; +use crate::common::errors::AppError; + +pub fn webdav_routes() -> Router> { + Router::new() + .route("/webdav/*path", get(handle_get)) + .route_with_tsr("/webdav/*path", axum::routing::on( + Method::OPTIONS, handle_options, + Method::PROPFIND, handle_propfind, + Method::PROPPATCH, handle_proppatch, + Method::MKCOL, handle_mkcol, + Method::PUT, handle_put, + Method::DELETE, handle_delete, + Method::COPY, handle_copy, + Method::MOVE, handle_move, + Method::LOCK, handle_lock, + Method::UNLOCK, handle_unlock, + )) +} + +// Implementar funciones para cada método WebDAV... +``` + +2. **Adaptador WebDAV**: + +```rust +// src/application/adapters/webdav_adapter.rs +use xml::reader::{EventReader, XmlEvent}; +use xml::writer::{EventWriter, EmitterConfig, XmlEvent as WriteEvent}; +use std::io::{Read, Write}; +use crate::application::dtos::file_dto::FileDto; +use crate::application::dtos::folder_dto::FolderDto; + +/// Convierte entre objetos de OxiCloud y representaciones WebDAV +pub struct WebDavAdapter; + +impl WebDavAdapter { + /// Convierte una propiedad PROPFIND en XML a un objeto de solicitud + pub fn parse_propfind(reader: R) -> Result { + // Implementación... + } + + /// Genera respuesta XML para PROPFIND basada en archivos y carpetas + pub fn generate_propfind_response( + writer: W, + files: &[FileDto], + folders: &[FolderDto], + base_url: &str, + ) -> Result<(), Error> { + // Implementación... + } + + // Otros métodos para manejar diferentes operaciones WebDAV... +} +``` + +## CalDAV + +### Endpoints Requeridos + +| Método HTTP | Endpoint | Descripción | +|-------------|----------|-------------| +| PROPFIND | /caldav/{calendar} | Recupera propiedades del calendario | +| REPORT | /caldav/{calendar} | Consulta eventos del calendario | +| MKCALENDAR | /caldav/{calendar} | Crea un nuevo calendario | +| PUT | /caldav/{calendar}/{event}.ics | Crea o actualiza un evento | +| GET | /caldav/{calendar}/{event}.ics | Recupera un evento | +| DELETE | /caldav/{calendar}/{event}.ics | Elimina un evento | + +### Implementación + +1. **Nuevas Entidades de Dominio**: + +```rust +// src/domain/entities/calendar.rs +use uuid::Uuid; +use chrono::{DateTime, Utc}; + +#[derive(Debug, Clone)] +pub struct Calendar { + id: Uuid, + name: String, + owner_id: String, + description: Option, + color: Option, + created_at: DateTime, + updated_at: DateTime, +} + +// src/domain/entities/calendar_event.rs +use uuid::Uuid; +use chrono::{DateTime, Utc}; + +#[derive(Debug, Clone)] +pub struct CalendarEvent { + id: Uuid, + calendar_id: Uuid, + summary: String, + description: Option, + location: Option, + start_time: DateTime, + end_time: DateTime, + all_day: bool, + rrule: Option, // Regla de recurrencia + ical_data: String, // Datos iCalendar completos + created_at: DateTime, + updated_at: DateTime, +} +``` + +2. **Repositorios**: + +```rust +// src/domain/repositories/calendar_repository.rs +use async_trait::async_trait; +use uuid::Uuid; +use crate::domain::entities::calendar::Calendar; +use crate::common::errors::Result; + +#[async_trait] +pub trait CalendarRepository: Send + Sync { + async fn create_calendar(&self, calendar: Calendar) -> Result; + async fn get_calendar_by_id(&self, id: &Uuid) -> Result; + async fn get_calendars_by_owner(&self, owner_id: &str) -> Result>; + async fn update_calendar(&self, calendar: Calendar) -> Result; + async fn delete_calendar(&self, id: &Uuid) -> Result<()>; +} + +// src/domain/repositories/calendar_event_repository.rs +use async_trait::async_trait; +use uuid::Uuid; +use chrono::{DateTime, Utc}; +use crate::domain::entities::calendar_event::CalendarEvent; +use crate::common::errors::Result; + +#[async_trait] +pub trait CalendarEventRepository: Send + Sync { + async fn create_event(&self, event: CalendarEvent) -> Result; + async fn get_event_by_id(&self, id: &Uuid) -> Result; + async fn get_events_by_calendar(&self, calendar_id: &Uuid) -> Result>; + async fn get_events_in_timerange( + &self, + calendar_id: &Uuid, + start: &DateTime, + end: &DateTime + ) -> Result>; + async fn update_event(&self, event: CalendarEvent) -> Result; + async fn delete_event(&self, id: &Uuid) -> Result<()>; +} +``` + +3. **Servicio CalDAV**: + +```rust +// src/application/services/caldav_service.rs +use std::sync::Arc; +use uuid::Uuid; +use chrono::{DateTime, Utc}; +use crate::domain::repositories::calendar_repository::CalendarRepository; +use crate::domain::repositories::calendar_event_repository::CalendarEventRepository; +use crate::domain::entities::calendar::Calendar; +use crate::domain::entities::calendar_event::CalendarEvent; +use crate::application::dtos::calendar_dto::{CalendarDto, CalendarEventDto}; +use crate::common::errors::{Result, DomainError}; + +pub struct CalDavService { + calendar_repository: Arc, + event_repository: Arc, +} + +impl CalDavService { + pub fn new( + calendar_repository: Arc, + event_repository: Arc, + ) -> Self { + Self { + calendar_repository, + event_repository, + } + } + + // Implementar métodos para operaciones CalDAV... +} +``` + +4. **Manejador CalDAV**: + +```rust +// src/interfaces/api/handlers/caldav_handler.rs +use std::sync::Arc; +use axum::{ + Router, + routing::{get, put, delete}, + extract::{Path, State, Request, Extension}, + http::StatusCode, + response::Response, +}; +use http::{Method, header}; + +use crate::common::di::AppState; +use crate::interfaces::middleware::auth::CurrentUser; +use crate::application::services::caldav_service::CalDavService; +use crate::common::errors::AppError; + +pub fn caldav_routes() -> Router> { + Router::new() + .route("/caldav/", get(get_calendars)) + .route("/caldav/:calendar", get(get_calendar)) + .route_with_tsr("/caldav/:calendar", axum::routing::on( + Method::PROPFIND, handle_calendar_propfind, + Method::REPORT, handle_calendar_report, + Method::MKCALENDAR, handle_mkcalendar, + )) + .route("/caldav/:calendar/:event", get(get_event)) + .route("/caldav/:calendar/:event", put(put_event)) + .route("/caldav/:calendar/:event", delete(delete_event)) +} + +// Implementar funciones para cada método CalDAV... +``` + +## CardDAV + +### Endpoints Requeridos + +| Método HTTP | Endpoint | Descripción | +|-------------|----------|-------------| +| PROPFIND | /carddav/addressbooks/{addressbook} | Recupera propiedades de la libreta de direcciones | +| REPORT | /carddav/addressbooks/{addressbook} | Consulta contactos | +| MKCOL | /carddav/addressbooks/{addressbook} | Crea una nueva libreta de direcciones | +| PUT | /carddav/addressbooks/{addressbook}/{contact}.vcf | Crea o actualiza un contacto | +| GET | /carddav/addressbooks/{addressbook}/{contact}.vcf | Recupera un contacto | +| DELETE | /carddav/addressbooks/{addressbook}/{contact}.vcf | Elimina un contacto | + +### Implementación + +1. **Nuevas Entidades de Dominio**: + +```rust +// src/domain/entities/address_book.rs +use uuid::Uuid; +use chrono::{DateTime, Utc}; + +#[derive(Debug, Clone)] +pub struct AddressBook { + id: Uuid, + name: String, + owner_id: String, + description: Option, + created_at: DateTime, + updated_at: DateTime, +} + +// src/domain/entities/contact.rs +use uuid::Uuid; +use chrono::{DateTime, Utc}; + +#[derive(Debug, Clone)] +pub struct Contact { + id: Uuid, + address_book_id: Uuid, + full_name: String, + first_name: Option, + last_name: Option, + email: Option, + phone: Option, + address: Option, + organization: Option, + vcard_data: String, // Datos vCard completos + created_at: DateTime, + updated_at: DateTime, +} +``` + +2. **Repositorios**: + +```rust +// src/domain/repositories/address_book_repository.rs +use async_trait::async_trait; +use uuid::Uuid; +use crate::domain::entities::address_book::AddressBook; +use crate::common::errors::Result; + +#[async_trait] +pub trait AddressBookRepository: Send + Sync { + async fn create_address_book(&self, address_book: AddressBook) -> Result; + async fn get_address_book_by_id(&self, id: &Uuid) -> Result; + async fn get_address_books_by_owner(&self, owner_id: &str) -> Result>; + async fn update_address_book(&self, address_book: AddressBook) -> Result; + async fn delete_address_book(&self, id: &Uuid) -> Result<()>; +} + +// src/domain/repositories/contact_repository.rs +use async_trait::async_trait; +use uuid::Uuid; +use crate::domain::entities::contact::Contact; +use crate::common::errors::Result; + +#[async_trait] +pub trait ContactRepository: Send + Sync { + async fn create_contact(&self, contact: Contact) -> Result; + async fn get_contact_by_id(&self, id: &Uuid) -> Result; + async fn get_contacts_by_address_book(&self, address_book_id: &Uuid) -> Result>; + async fn search_contacts(&self, address_book_id: &Uuid, query: &str) -> Result>; + async fn update_contact(&self, contact: Contact) -> Result; + async fn delete_contact(&self, id: &Uuid) -> Result<()>; +} +``` + +3. **Servicio CardDAV**: + +```rust +// src/application/services/carddav_service.rs +use std::sync::Arc; +use uuid::Uuid; +use crate::domain::repositories::address_book_repository::AddressBookRepository; +use crate::domain::repositories::contact_repository::ContactRepository; +use crate::domain::entities::address_book::AddressBook; +use crate::domain::entities::contact::Contact; +use crate::application::dtos::address_book_dto::{AddressBookDto, ContactDto}; +use crate::common::errors::{Result, DomainError}; + +pub struct CardDavService { + address_book_repository: Arc, + contact_repository: Arc, +} + +impl CardDavService { + pub fn new( + address_book_repository: Arc, + contact_repository: Arc, + ) -> Self { + Self { + address_book_repository, + contact_repository, + } + } + + // Implementar métodos para operaciones CardDAV... +} +``` + +4. **Manejador CardDAV**: + +```rust +// src/interfaces/api/handlers/carddav_handler.rs +use std::sync::Arc; +use axum::{ + Router, + routing::{get, put, delete}, + extract::{Path, State, Request, Extension}, + http::StatusCode, + response::Response, +}; +use http::{Method, header}; + +use crate::common::di::AppState; +use crate::interfaces::middleware::auth::CurrentUser; +use crate::application::services::carddav_service::CardDavService; +use crate::common::errors::AppError; + +pub fn carddav_routes() -> Router> { + Router::new() + .route("/carddav/addressbooks/", get(get_address_books)) + .route("/carddav/addressbooks/:addressbook", get(get_address_book)) + .route_with_tsr("/carddav/addressbooks/:addressbook", axum::routing::on( + Method::PROPFIND, handle_addressbook_propfind, + Method::REPORT, handle_addressbook_report, + Method::MKCOL, handle_mkaddressbook, + )) + .route("/carddav/addressbooks/:addressbook/:contact", get(get_contact)) + .route("/carddav/addressbooks/:addressbook/:contact", put(put_contact)) + .route("/carddav/addressbooks/:addressbook/:contact", delete(delete_contact)) +} + +// Implementar funciones para cada método CardDAV... +``` + +## Esquema de Base de Datos + +```sql +-- Esquema para CalDAV +CREATE TABLE calendar ( + id UUID PRIMARY KEY, + name VARCHAR(255) NOT NULL, + owner_id VARCHAR(255) NOT NULL, + description TEXT, + color VARCHAR(50), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE calendar_event ( + id UUID PRIMARY KEY, + calendar_id UUID NOT NULL REFERENCES calendar(id) ON DELETE CASCADE, + summary VARCHAR(255) NOT NULL, + description TEXT, + location TEXT, + start_time TIMESTAMPTZ NOT NULL, + end_time TIMESTAMPTZ NOT NULL, + all_day BOOLEAN NOT NULL DEFAULT FALSE, + rrule TEXT, + ical_uid VARCHAR(255) NOT NULL, + ical_data TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Esquema para CardDAV +CREATE TABLE address_book ( + id UUID PRIMARY KEY, + name VARCHAR(255) NOT NULL, + owner_id VARCHAR(255) NOT NULL, + description TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE contact ( + id UUID PRIMARY KEY, + address_book_id UUID NOT NULL REFERENCES address_book(id) ON DELETE CASCADE, + full_name VARCHAR(255) NOT NULL, + first_name VARCHAR(255), + last_name VARCHAR(255), + email VARCHAR(255), + phone VARCHAR(100), + address TEXT, + organization VARCHAR(255), + vcard_uid VARCHAR(255) NOT NULL, + vcard_data TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Índices para búsqueda eficiente +CREATE INDEX idx_calendar_owner ON calendar(owner_id); +CREATE INDEX idx_calendar_event_calendar ON calendar_event(calendar_id); +CREATE INDEX idx_address_book_owner ON address_book(owner_id); +CREATE INDEX idx_contact_address_book ON contact(address_book_id); +CREATE INDEX idx_contact_name ON contact(full_name); +``` + +## Consideraciones de Seguridad + +1. **Autenticación** + - Utilizar la autenticación existente de OxiCloud + - Soportar autenticación HTTP Basic para clientes DAV + - Implementar el esquema de autenticación Digest si es necesario + +2. **Autorización** + - Verificar permisos de usuario para acceder a recursos + - Implementar control de acceso basado en propietario y permisos compartidos + - Asegurar que los usuarios solo puedan acceder a sus propios calendarios y libretas de direcciones + +3. **Prevención de Ataques** + - Validar y sanitizar todas las entradas XML + - Limitar tamaño máximo de carga útil + - Implementar rate limiting en endpoints DAV + +## Pruebas y Compatibilidad + +### Clientes a Probar + +1. **WebDAV** + - Windows Explorer + - macOS Finder + - Cyberduck + - FileZilla (con extensión WebDAV) + +2. **CalDAV** + - Apple Calendar + - Mozilla Thunderbird (Lightning) + - Microsoft Outlook (con complemento CalDAV) + - Google Calendar (mediante sincronización) + +3. **CardDAV** + - Apple Contacts + - Mozilla Thunderbird + - Microsoft Outlook (con complemento CardDAV) + - Google Contacts (mediante sincronización) + +### Pruebas de Cumplimiento + +- Utilizar la suite de pruebas CalDAVTester para verificar la conformidad con el estándar +- Validar cumplimiento de RFC para cada protocolo +- Pruebas de stress para evaluar rendimiento bajo carga + +### Depuración + +- Implementar logging detallado para operaciones DAV +- Crear herramientas de diagnóstico para depurar solicitudes DAV complejas +- Proporcionar mensajes de error claros para ayudar en la resolución de problemas \ No newline at end of file diff --git a/doc/OIDC-ARCHITECTURE.md b/doc/OIDC-ARCHITECTURE.md new file mode 100644 index 00000000..25075e22 --- /dev/null +++ b/doc/OIDC-ARCHITECTURE.md @@ -0,0 +1,162 @@ +# Arquitectura de Integración OIDC en OxiCloud + +Este documento describe la arquitectura y el flujo de autenticación OpenID Connect (OIDC) en OxiCloud. + +## Diagrama de Arquitectura + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ │ +│ PROVEEDOR DE IDENTIDAD │ +│ │ +│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ +│ │ │ │ │ │ │ │ +│ │ Authentik │ │ Authelia │ │ KeyCloak │ │ +│ │ │ │ │ │ │ │ +│ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ │ +│ │ │ │ │ +└────────────┼──────────────────────┼──────────────────────┼─────────────┘ + │ │ │ + │ │ │ + │ │ │ + │ OIDC │ + │ │ │ + │ │ │ +┌────────────┼──────────────────────┼──────────────────────┼─────────────┐ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ │ │ +│ │ OXICLOUD │ │ +│ │ │ │ +│ │ ┌───────────────┐ ┌───────────────┐ │ │ +│ │ │ │ │ │ │ │ +│ │ │ OidcService │◄────►│ AuthService │ │ │ +│ │ │ │ │ │ │ │ +│ │ └───────┬───────┘ └───────┬───────┘ │ │ +│ │ │ │ │ │ +│ │ ▼ ▼ │ │ +│ │ ┌───────────────────────────────────────────┐ │ │ +│ │ │ │ │ │ +│ │ │ AuthApplicationService │ │ │ +│ │ │ │ │ │ +│ │ └───────────────────┬───────────────────────┘ │ │ +│ │ │ │ │ +│ │ ▼ │ │ +│ │ ┌───────────────────────────────────────────┐ │ │ +│ │ │ │ │ │ +│ │ │ Auth Handler │ │ │ +│ │ │ │ │ │ +│ │ └───────────────────────────────────────────┘ │ │ +│ │ │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ + ▲ + │ + │ HTTP/HTTPS + │ + │ +┌────────────────────────────────────────────────────────────────────────┐ +│ │ +│ NAVEGADOR WEB │ +│ │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ │ │ +│ │ Interfaz de Usuario │ │ +│ │ │ │ +│ │ ┌──────────────┐ ┌──────────────┐ │ │ +│ │ │ │ │ │ │ │ +│ │ │ Login.html │ │ oidcAuth.js │ │ │ +│ │ │ │ │ │ │ │ +│ │ └──────────────┘ └──────────────┘ │ │ +│ │ │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +└────────────────────────────────────────────────────────────────────────┘ +``` + +## Flujo de Autenticación OIDC + +El flujo de autenticación OIDC en OxiCloud sigue el flujo de código de autorización (Authorization Code Flow): + +1. **Inicio de la Autenticación**: + - El usuario hace clic en "Login con [Proveedor]" en la página de inicio de sesión. + - El frontend genera un estado aleatorio para protección CSRF. + - El frontend solicita a OxiCloud una URL de autorización. + +2. **Redirección al Proveedor de Identidad**: + - OxiCloud genera una URL de autorización y la devuelve al frontend. + - El navegador redirige al usuario a la página de inicio de sesión del proveedor de identidad. + +3. **Autenticación en el Proveedor**: + - El usuario se autentica en el proveedor de identidad (con contraseña, 2FA, etc.). + - El proveedor redirige al usuario de vuelta a OxiCloud con un código de autorización. + +4. **Intercambio del Código de Autorización**: + - El frontend de OxiCloud recibe el código de autorización y lo envía al backend. + - OxiCloud intercambia el código por tokens de acceso e ID con el proveedor de identidad. + - OxiCloud verifica el token de ID y extrae la información del usuario. + +5. **Creación/Recuperación de Usuario**: + - OxiCloud busca un usuario existente con el ID externo del proveedor. + - Si no existe y la creación automática está habilitada, se crea un nuevo usuario. + - Si no existe y la creación automática está deshabilitada, se devuelve un error. + +6. **Generación de Tokens de Sesión**: + - OxiCloud genera sus propios tokens de acceso y actualización para el usuario. + - Estos tokens se utilizan para autenticar las solicitudes subsiguientes a la API de OxiCloud. + +7. **Respuesta al Cliente**: + - OxiCloud devuelve los tokens y la información del usuario al frontend. + - El frontend almacena los tokens y redirige al usuario a la página principal. + +## Componentes Principales + +### 1. OidcService + +Este servicio gestiona la comunicación con los proveedores OIDC: +- Descubre los endpoints OIDC de los proveedores +- Genera URLs de autorización +- Intercambia códigos de autorización por tokens +- Verifica tokens y extrae información de usuario + +### 2. AuthApplicationService + +Coordina el proceso de autenticación: +- Proporciona una interfaz entre la capa de API y los servicios de dominio +- Gestiona el proceso de creación/recuperación de usuarios +- Coordina la generación de tokens de acceso para OxiCloud + +### 3. Auth Handler + +Expone endpoints HTTP para el flujo de autenticación OIDC: +- `/api/auth/oidc/providers` - Lista los proveedores OIDC disponibles +- `/api/auth/oidc/auth` - Genera una URL de autorización para un proveedor +- `/api/auth/oidc/callback` - Procesa la respuesta del proveedor y completa la autenticación + +### 4. Frontend (oidcAuth.js) + +Gestiona la parte del cliente del flujo de autenticación: +- Muestra botones para los proveedores OIDC +- Inicia el flujo de autenticación +- Maneja la redirección de retorno del proveedor +- Procesa y almacena los tokens de sesión + +## Configuración Multi-Proveedor + +OxiCloud permite configurar múltiples proveedores OIDC simultáneamente: + +1. **Configuración Separada**: Cada proveedor tiene su propia configuración independiente. +2. **Selección de Proveedor**: Los usuarios pueden elegir con qué proveedor autenticarse. +3. **Mapeo de Identidades**: OxiCloud mapea identidades de diferentes proveedores a usuarios internos. + +## Seguridad + +La implementación OIDC en OxiCloud incluye varias medidas de seguridad: + +1. **Protección CSRF**: Utiliza un estado aleatorio para prevenir ataques CSRF. +2. **Validación de Tokens**: Verifica firmas y vigencia de los tokens JWT. +3. **Código de Autorización**: Utiliza el flujo de código de autorización, que es más seguro que el flujo implícito. +4. **HTTPS**: Requiere conexiones HTTPS para todas las comunicaciones OIDC. +5. **Secretos del Cliente**: Los secretos del cliente se almacenan de forma segura y nunca se exponen al frontend. \ No newline at end of file diff --git a/doc/OIDC-CONFIG-EXAMPLES.md b/doc/OIDC-CONFIG-EXAMPLES.md new file mode 100644 index 00000000..ff6cc9a3 --- /dev/null +++ b/doc/OIDC-CONFIG-EXAMPLES.md @@ -0,0 +1,218 @@ +# Ejemplos de Configuración de OIDC para OxiCloud + +Esta guía proporciona ejemplos de configuración para integrar OxiCloud con diferentes proveedores OIDC (OpenID Connect). + +## Índice + +1. [Configuración General de OIDC](#configuración-general-de-oidc) +2. [Authentik](#authentik) +3. [Authelia](#authelia) +4. [KeyCloak](#keycloak) +5. [Resolución de Problemas](#resolución-de-problemas) + +## Configuración General de OIDC + +Para habilitar la integración OIDC en OxiCloud, necesitará establecer las siguientes variables de entorno: + +```bash +# Habilitar OIDC +OXICLOUD_ENABLE_OIDC=true + +# Configuración para cada proveedor OIDC (puede configurar múltiples proveedores) +OXICLOUD_OIDC_PROVIDER__NAME="Nombre Visible" +OXICLOUD_OIDC_PROVIDER__CLIENT_ID="su-client-id" +OXICLOUD_OIDC_PROVIDER__CLIENT_SECRET="su-client-secret" +OXICLOUD_OIDC_PROVIDER__DISCOVERY_URL="https://proveedor.example.com/.well-known/openid-configuration" +OXICLOUD_OIDC_PROVIDER__REDIRECT_URI="https://su-oxicloud.example.com/oidc/callback/" +OXICLOUD_OIDC_PROVIDER__SCOPES="openid profile email" +OXICLOUD_OIDC_PROVIDER__USER_ID_ATTRIBUTE="sub" +OXICLOUD_OIDC_PROVIDER__DEFAULT_ROLE="user" +OXICLOUD_OIDC_PROVIDER__AUTO_CREATE_USERS="true" +``` + +## Authentik + +[Authentik](https://goauthentik.io/) es una plataforma de identidad de código abierto que proporciona autenticación, autorización y gestión de usuarios. + +### 1. Configurar una aplicación en Authentik + +1. Inicia sesión en tu panel de administración de Authentik +2. Ve a "Applications" → "Create" +3. Introduce un nombre para tu aplicación (ej. "OxiCloud") +4. Selecciona "OAuth2/OpenID Provider" como tipo de proveedor +5. En la configuración de OAuth2: + - **Redirect URI/Callback URL**: `https://su-oxicloud.example.com/oidc/callback/authentik` + - **Client Type**: Confidential + - **Client ID**: Se generará automáticamente (anótalo) + - **Client Secret**: Se generará automáticamente (anótalo) + - **Scopes**: openid, email, profile +6. En la configuración de UI: + - **Launch URL**: `https://su-oxicloud.example.com/` + - **Icon**: Opcional, puedes subir un icono para OxiCloud + +### 2. Configurar OxiCloud para Authentik + +```yaml +# docker-compose.yml +version: '3' +services: + oxicloud: + image: oxicloud:latest + environment: + # Configuración general + OXICLOUD_ENABLE_OIDC: "true" + + # Configuración de Authentik + OXICLOUD_OIDC_PROVIDER_AUTHENTIK_NAME: "Authentik" + OXICLOUD_OIDC_PROVIDER_AUTHENTIK_CLIENT_ID: "tu-client-id-de-authentik" + OXICLOUD_OIDC_PROVIDER_AUTHENTIK_CLIENT_SECRET: "tu-client-secret-de-authentik" + OXICLOUD_OIDC_PROVIDER_AUTHENTIK_DISCOVERY_URL: "https://authentik.example.com/application/o/oxicloud/.well-known/openid-configuration" + OXICLOUD_OIDC_PROVIDER_AUTHENTIK_REDIRECT_URI: "https://oxicloud.example.com/oidc/callback/authentik" + OXICLOUD_OIDC_PROVIDER_AUTHENTIK_SCOPES: "openid profile email" + OXICLOUD_OIDC_PROVIDER_AUTHENTIK_USER_ID_ATTRIBUTE: "sub" + OXICLOUD_OIDC_PROVIDER_AUTHENTIK_DEFAULT_ROLE: "user" + OXICLOUD_OIDC_PROVIDER_AUTHENTIK_AUTO_CREATE_USERS: "true" + ports: + - "8085:8085" + volumes: + - ./storage:/app/storage +``` + +## Authelia + +[Authelia](https://www.authelia.com/) es una solución de autenticación multi-factor de código abierto. + +### 1. Configurar Authelia para OxiCloud + +Edita tu configuración de Authelia (`configuration.yml`): + +```yaml +identity_providers: + oidc: + hmac_secret: tu-secreto-seguro # Cambia esto por un valor aleatorio seguro + issuer_private_key: /config/private.pem # Ruta a tu clave privada + cors: + endpoints: ['authorization', 'token', 'revocation', 'introspection'] + allowed_origins: + - https://oxicloud.example.com + clients: + - id: oxicloud + description: OxiCloud + secret: tu-client-secret-seguro # Cambia esto + public: false + authorization_policy: two_factor + redirect_uris: + - https://oxicloud.example.com/oidc/callback/authelia + scopes: ['openid', 'profile', 'email', 'groups'] + userinfo_signing_algorithm: none +``` + +### 2. Configurar OxiCloud para Authelia + +```yaml +# docker-compose.yml +version: '3' +services: + oxicloud: + image: oxicloud:latest + environment: + # Configuración general + OXICLOUD_ENABLE_OIDC: "true" + + # Configuración de Authelia + OXICLOUD_OIDC_PROVIDER_AUTHELIA_NAME: "Authelia" + OXICLOUD_OIDC_PROVIDER_AUTHELIA_CLIENT_ID: "oxicloud" + OXICLOUD_OIDC_PROVIDER_AUTHELIA_CLIENT_SECRET: "tu-client-secret-seguro" + OXICLOUD_OIDC_PROVIDER_AUTHELIA_DISCOVERY_URL: "https://authelia.example.com/.well-known/openid-configuration" + OXICLOUD_OIDC_PROVIDER_AUTHELIA_REDIRECT_URI: "https://oxicloud.example.com/oidc/callback/authelia" + OXICLOUD_OIDC_PROVIDER_AUTHELIA_SCOPES: "openid profile email groups" + OXICLOUD_OIDC_PROVIDER_AUTHELIA_USER_ID_ATTRIBUTE: "sub" + OXICLOUD_OIDC_PROVIDER_AUTHELIA_DEFAULT_ROLE: "user" + OXICLOUD_OIDC_PROVIDER_AUTHELIA_AUTO_CREATE_USERS: "true" + ports: + - "8085:8085" + volumes: + - ./storage:/app/storage +``` + +## KeyCloak + +[KeyCloak](https://www.keycloak.org/) es una solución de gestión de identidad y acceso de código abierto. + +### 1. Configurar un cliente en KeyCloak + +1. Inicia sesión en la consola de administración de KeyCloak +2. Selecciona tu Reino (Realm) +3. Ve a "Clients" → "Create" +4. Completa el formulario: + - **Client ID**: `oxicloud` + - **Client Protocol**: `openid-connect` + - **Root URL**: `https://oxicloud.example.com` +5. En la configuración del cliente: + - **Access Type**: `confidential` + - **Valid Redirect URIs**: `https://oxicloud.example.com/oidc/callback/keycloak` + - **Web Origins**: `https://oxicloud.example.com` (o `+` para permitir todos los orígenes) +6. Guarda la configuración +7. Ve a la pestaña "Credentials" y copia el "Secret" generado + +### 2. Configurar OxiCloud para KeyCloak + +```yaml +# docker-compose.yml +version: '3' +services: + oxicloud: + image: oxicloud:latest + environment: + # Configuración general + OXICLOUD_ENABLE_OIDC: "true" + + # Configuración de KeyCloak + OXICLOUD_OIDC_PROVIDER_KEYCLOAK_NAME: "KeyCloak" + OXICLOUD_OIDC_PROVIDER_KEYCLOAK_CLIENT_ID: "oxicloud" + OXICLOUD_OIDC_PROVIDER_KEYCLOAK_CLIENT_SECRET: "tu-client-secret-de-keycloak" + OXICLOUD_OIDC_PROVIDER_KEYCLOAK_DISCOVERY_URL: "https://keycloak.example.com/realms/tu-realm/.well-known/openid-configuration" + OXICLOUD_OIDC_PROVIDER_KEYCLOAK_REDIRECT_URI: "https://oxicloud.example.com/oidc/callback/keycloak" + OXICLOUD_OIDC_PROVIDER_KEYCLOAK_SCOPES: "openid profile email" + OXICLOUD_OIDC_PROVIDER_KEYCLOAK_USER_ID_ATTRIBUTE: "sub" + OXICLOUD_OIDC_PROVIDER_KEYCLOAK_DEFAULT_ROLE: "user" + OXICLOUD_OIDC_PROVIDER_KEYCLOAK_AUTO_CREATE_USERS: "true" + ports: + - "8085:8085" + volumes: + - ./storage:/app/storage +``` + +## Resolución de Problemas + +### Error: "Failed to discover OIDC provider" + +Este error ocurre cuando OxiCloud no puede acceder al punto de descubrimiento del proveedor OIDC. + +**Soluciones:** +1. Verifica que la URL de descubrimiento sea correcta +2. Asegúrate de que OxiCloud pueda acceder a la URL (verifique firewalls, DNS, etc.) +3. Si tu proveedor utiliza un certificado autofirmado, asegúrate de configurar la confianza adecuada + +### Error: "Invalid redirect URI" + +Tu proveedor OIDC rechaza la URI de redirección. + +**Soluciones:** +1. Asegúrate de que la URI de redirección configurada en OxiCloud coincida exactamente con la registrada en tu proveedor OIDC +2. Verifica que no haya diferencias en protocolo (http vs https), puerto o ruta + +### Error: "User does not exist and auto-creation is disabled" + +**Soluciones:** +1. Habilita la creación automática de usuarios: `OXICLOUD_OIDC_PROVIDER__AUTO_CREATE_USERS="true"` +2. O crea manualmente el usuario en OxiCloud antes de intentar iniciar sesión con OIDC + +### Error: "Could not extract user ID from claim" + +OxiCloud no puede encontrar el atributo de ID de usuario especificado en los claims del token. + +**Soluciones:** +1. Verifica que el atributo configurado (`USER_ID_ATTRIBUTE`) exista en los claims del token +2. Prueba con un atributo diferente, como "sub", "email" o "preferred_username" +3. Configura tu proveedor OIDC para incluir el atributo necesario en los tokens \ No newline at end of file diff --git a/doc/OIDC-INTEGRATION.md b/doc/OIDC-INTEGRATION.md new file mode 100644 index 00000000..bc5d8e34 --- /dev/null +++ b/doc/OIDC-INTEGRATION.md @@ -0,0 +1,702 @@ +# OIDC Integration for OxiCloud + +This document outlines the implementation plan for adding OpenID Connect (OIDC) support to OxiCloud, enabling Single Sign-On (SSO) with identity providers like Authentik, Authelia, KeyCloak, and others. + +## Overview + +OpenID Connect (OIDC) is an identity layer built on top of the OAuth 2.0 protocol. It allows clients to verify the identity of end-users based on the authentication performed by an authorization server, as well as to obtain basic profile information about the end-user. + +Implementing OIDC in OxiCloud will: +1. Allow users to authenticate using their existing identity provider (IdP) credentials +2. Reduce the need for separate username/password management in OxiCloud +3. Enhance security by leveraging modern authentication best practices +4. Provide a seamless experience for users already using SSO in their environment + +## Implementation Plan + +### 1. Add OIDC Configuration Options + +Extend the `AuthConfig` struct in `src/common/config.rs`: + +```rust +pub struct AuthConfig { + pub jwt_secret: String, + pub access_token_expiry_secs: i64, + pub refresh_token_expiry_secs: i64, + pub hash_memory_cost: u32, + pub hash_time_cost: u32, + + // New OIDC configuration + pub enable_oidc: bool, + pub oidc_providers: Vec, +} + +pub struct OidcProviderConfig { + pub name: String, // Display name (e.g., "Authentik", "KeyCloak") + pub client_id: String, // OIDC client ID + pub client_secret: String, // OIDC client secret + pub discovery_url: String, // OIDC discovery URL (.well-known/openid-configuration) + pub redirect_uri: String, // Redirect URI after authentication + pub scopes: Vec, // Scopes to request + pub user_id_attribute: String, // Which claim to use as user ID + pub default_role: String, // Default role for new users + pub auto_create_users: bool, // Create users on first login +} + +impl Default for AuthConfig { + fn default() -> Self { + Self { + // Existing defaults... + + // OIDC defaults + enable_oidc: false, + oidc_providers: Vec::new(), + } + } +} +``` + +Update the environment variable handling in `AppConfig::from_env()` to include OIDC configurations. + +### 2. Create OIDC Service Implementation + +Add a new file `src/domain/services/oidc_service.rs`: + +```rust +use openid::{Client, Discovered, DiscoveredClient, Options, Token, StandardClaims}; +use std::sync::Arc; +use reqwest::Client as HttpClient; +use async_trait::async_trait; +use uuid::Uuid; + +use crate::common::config::OidcProviderConfig; +use crate::domain::entities::user::{User, UserRole}; +use crate::domain::repositories::user_repository::UserRepository; +use crate::common::errors::{DomainError, ErrorKind}; + +pub struct OidcService { + providers: Vec, + user_repository: Arc, +} + +struct OidcProvider { + config: OidcProviderConfig, + client: DiscoveredClient, +} + +impl OidcService { + pub async fn new( + configs: Vec, + user_repository: Arc, + ) -> Result { + let http_client = HttpClient::new(); + let mut providers = Vec::new(); + + for config in configs { + let client = openid::Client::discover( + http_client.clone(), + &config.client_id, + &config.client_secret, + &config.redirect_uri, + &config.discovery_url, + ) + .await + .map_err(|e| DomainError::new( + ErrorKind::InternalError, + "OIDC", + format!("Failed to discover OIDC provider {}: {}", config.name, e) + ))?; + + providers.push(OidcProvider { + config: config.clone(), + client, + }); + } + + Ok(Self { + providers, + user_repository, + }) + } + + pub fn get_provider(&self, provider_name: &str) -> Option<&OidcProvider> { + self.providers.iter().find(|p| p.config.name == provider_name) + } + + pub fn get_providers_info(&self) -> Vec { + self.providers.iter().map(|p| OidcProviderInfo { + name: p.config.name.clone(), + display_name: p.config.name.clone(), + }).collect() + } + + pub fn generate_authorization_url(&self, provider_name: &str, state: &str) -> Result { + let provider = self.get_provider(provider_name).ok_or_else(|| DomainError::new( + ErrorKind::NotFound, + "OIDC", + format!("Provider {} not found", provider_name) + ))?; + + let mut options = Options::default(); + options.scope = Some(provider.config.scopes.join(" ")); + + let auth_url = provider.client.auth_url(&options, Some(state)); + Ok(auth_url.to_string()) + } + + pub async fn process_callback( + &self, + provider_name: &str, + code: &str, + state: &str + ) -> Result<(User, Token), DomainError> { + let provider = self.get_provider(provider_name).ok_or_else(|| DomainError::new( + ErrorKind::NotFound, + "OIDC", + format!("Provider {} not found", provider_name) + ))?; + + // Exchange code for token + let token = provider.client.request_token(code).await.map_err(|e| DomainError::new( + ErrorKind::AccessDenied, + "OIDC", + format!("Failed to exchange code for token: {}", e) + ))?; + + // Extract user information from claims + let claims = token.id_token.payload().clone(); + + // Get user ID from configured attribute + let user_id_attr = &provider.config.user_id_attribute; + let external_user_id = match user_id_attr.as_str() { + "sub" => claims.sub.clone(), + "email" => claims.email.clone().unwrap_or_default(), + // Add other standard claims as needed + _ => claims.additional_claims.get(user_id_attr) + .and_then(|v| v.as_str().map(|s| s.to_string())) + .unwrap_or_default(), + }; + + if external_user_id.is_empty() { + return Err(DomainError::new( + ErrorKind::InvalidInput, + "OIDC", + format!("Could not extract user ID from claim '{}'", user_id_attr) + )); + } + + // Check if user exists with this external ID + let mapped_user_id = format!("{}:{}", provider_name, external_user_id); + + let user = match self.user_repository.get_user_by_external_id(&mapped_user_id).await { + Ok(existing_user) => existing_user, + Err(_) => { + // User doesn't exist, create if allowed + if !provider.config.auto_create_users { + return Err(DomainError::new( + ErrorKind::AccessDenied, + "OIDC", + "User does not exist and auto-creation is disabled" + )); + } + + // Get user information from claims + let email = claims.email.clone().unwrap_or_else(|| + format!("{}@oidc.oxicloud.local", Uuid::new_v4()) + ); + + let username = claims.preferred_username.clone() + .or_else(|| claims.email.clone()) + .unwrap_or_else(|| format!("user_{}", Uuid::new_v4())); + + // Create the user + let role = match provider.config.default_role.as_str() { + "admin" => UserRole::Admin, + _ => UserRole::User, + }; + + // Default quota + let quota = 1024 * 1024 * 1024; // 1GB + + let mut new_user = User::new( + username, + email, + Uuid::new_v4().to_string(), // Random password, not used for OIDC + role, + quota, + )?; + + // Set external ID + new_user.set_external_id(Some(mapped_user_id)); + + // Save user + self.user_repository.create_user(new_user).await? + } + }; + + Ok((user, token)) + } +} + +#[derive(Clone, Debug, serde::Serialize)] +pub struct OidcProviderInfo { + pub name: String, + pub display_name: String, +} +``` + +### 3. Update User Entity + +Modify `src/domain/entities/user.rs` to support external IDs for OIDC users: + +```rust +#[derive(Debug, Clone)] +pub struct User { + // Existing fields... + external_id: Option, // For OIDC users: "provider:external_id" +} + +impl User { + // Existing methods... + + pub fn external_id(&self) -> Option<&str> { + self.external_id.as_deref() + } + + pub fn set_external_id(&mut self, external_id: Option) { + self.external_id = external_id; + } + + pub fn is_oidc_user(&self) -> bool { + self.external_id.is_some() + } +} +``` + +### 4. Update Database Schema + +Add a new column to the users table in `db/schema.sql`: + +```sql +ALTER TABLE auth.users ADD COLUMN IF NOT EXISTS external_id VARCHAR(255) UNIQUE; +``` + +### 5. Update the Auth Application Service + +Modify `src/application/services/auth_application_service.rs` to add OIDC methods: + +```rust +use crate::domain::services::oidc_service::{OidcService, OidcProviderInfo}; +use crate::application::dtos::user_dto::{OidcAuthUrlDto, OidcCallbackDto, OidcProviderDto}; + +impl AuthApplicationService { + // Add OIDC service + pub fn with_oidc_service(mut self, oidc_service: Arc) -> Self { + self.oidc_service = Some(oidc_service); + self + } + + // Get available OIDC providers + pub fn get_oidc_providers(&self) -> Result, DomainError> { + let oidc_service = self.oidc_service.as_ref() + .ok_or_else(|| DomainError::new( + ErrorKind::UnsupportedOperation, + "Auth", + "OIDC is not configured" + ))?; + + let providers = oidc_service.get_providers_info(); + Ok(providers.into_iter().map(OidcProviderDto::from).collect()) + } + + // Generate authorization URL + pub fn generate_oidc_auth_url(&self, dto: OidcAuthUrlDto) -> Result { + let oidc_service = self.oidc_service.as_ref() + .ok_or_else(|| DomainError::new( + ErrorKind::UnsupportedOperation, + "Auth", + "OIDC is not configured" + ))?; + + oidc_service.generate_authorization_url(&dto.provider, &dto.state) + } + + // Process OIDC callback + pub async fn process_oidc_callback(&self, dto: OidcCallbackDto) -> Result { + let oidc_service = self.oidc_service.as_ref() + .ok_or_else(|| DomainError::new( + ErrorKind::UnsupportedOperation, + "Auth", + "OIDC is not configured" + ))?; + + let (user, _token) = oidc_service.process_callback( + &dto.provider, + &dto.code, + &dto.state + ).await?; + + // Generate access token + let access_token = self.auth_service.generate_access_token(&user) + .map_err(DomainError::from)?; + + // Generate refresh token + let refresh_token = self.auth_service.generate_refresh_token(); + + // Create session + let session = Session::new( + user.id().to_string(), + refresh_token.clone(), + None, + None, + self.auth_service.refresh_token_expiry_days(), + ); + + self.session_storage.create_session(session).await?; + + // Return auth response + Ok(AuthResponseDto { + user: UserDto::from(user), + access_token, + refresh_token, + token_type: "Bearer".to_string(), + expires_in: self.auth_service.refresh_token_expiry_secs(), + }) + } +} +``` + +### 6. Add Auth Handler Routes for OIDC + +Update `src/interfaces/api/handlers/auth_handler.rs`: + +```rust +pub fn auth_routes() -> Router> { + Router::new() + .route("/register", post(register)) + .route("/login", post(login)) + .route("/refresh", post(refresh_token)) + .route("/me", get(get_current_user)) + .route("/change-password", put(change_password)) + .route("/logout", post(logout)) + // Add OIDC routes + .route("/oidc/providers", get(get_oidc_providers)) + .route("/oidc/auth", post(generate_oidc_auth_url)) + .route("/oidc/callback", post(process_oidc_callback)) +} + +// Get available OIDC providers +async fn get_oidc_providers( + State(state): State>, +) -> Result { + let auth_service = state.auth_service.as_ref() + .ok_or_else(|| AppError::internal_error("Servicio de autenticación no configurado"))?; + + match auth_service.auth_application_service.get_oidc_providers() { + Ok(providers) => Ok((StatusCode::OK, Json(providers))), + Err(err) => Err(err.into()), + } +} + +// Generate OIDC authorization URL +async fn generate_oidc_auth_url( + State(state): State>, + Json(dto): Json, +) -> Result { + let auth_service = state.auth_service.as_ref() + .ok_or_else(|| AppError::internal_error("Servicio de autenticación no configurado"))?; + + match auth_service.auth_application_service.generate_oidc_auth_url(dto) { + Ok(url) => Ok((StatusCode::OK, Json(json!({ "url": url })))), + Err(err) => Err(err.into()), + } +} + +// Process OIDC callback +async fn process_oidc_callback( + State(state): State>, + Json(dto): Json, +) -> Result { + let auth_service = state.auth_service.as_ref() + .ok_or_else(|| AppError::internal_error("Servicio de autenticación no configurado"))?; + + match auth_service.auth_application_service.process_oidc_callback(dto).await { + Ok(auth_response) => Ok((StatusCode::OK, Json(auth_response))), + Err(err) => Err(err.into()), + } +} +``` + +### 7. Update DTOs for OIDC + +Create new DTOs in `src/application/dtos/user_dto.rs`: + +```rust +use crate::domain::services::oidc_service::OidcProviderInfo; + +#[derive(Debug, Clone, Serialize)] +pub struct OidcProviderDto { + pub name: String, + pub display_name: String, +} + +impl From for OidcProviderDto { + fn from(info: OidcProviderInfo) -> Self { + Self { + name: info.name, + display_name: info.display_name, + } + } +} + +#[derive(Debug, Clone, Deserialize)] +pub struct OidcAuthUrlDto { + pub provider: String, + pub state: String, + pub redirect_uri: Option, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct OidcCallbackDto { + pub provider: String, + pub code: String, + pub state: String, +} +``` + +### 8. Add Frontend Integration + +Create a new JavaScript file `static/js/oidcAuth.js`: + +```javascript +// OIDC Authentication Module +const oidcAuth = { + // Get available OIDC providers + async getProviders() { + try { + const response = await fetch('/api/auth/oidc/providers'); + if (!response.ok) { + throw new Error(`Failed to get OIDC providers: ${response.statusText}`); + } + return await response.json(); + } catch (error) { + console.error('Error fetching OIDC providers:', error); + return []; + } + }, + + // Generate random state for CSRF protection + generateState() { + const array = new Uint8Array(16); + window.crypto.getRandomValues(array); + return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join(''); + }, + + // Start OIDC authentication flow + async startAuth(providerName) { + try { + // Generate and store state + const state = this.generateState(); + localStorage.setItem('oidc_state', state); + + // Get authorization URL + const response = await fetch('/api/auth/oidc/auth', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + provider: providerName, + state: state, + }), + }); + + if (!response.ok) { + throw new Error(`Failed to get auth URL: ${response.statusText}`); + } + + const data = await response.json(); + + // Redirect to authorization URL + window.location.href = data.url; + } catch (error) { + console.error('Error starting OIDC auth:', error); + alert('Failed to start authentication. Please try again.'); + } + }, + + // Handle OIDC callback + async handleCallback() { + // Parse URL parameters + const urlParams = new URLSearchParams(window.location.search); + const code = urlParams.get('code'); + const state = urlParams.get('state'); + const error = urlParams.get('error'); + + // Check for errors + if (error) { + console.error('OIDC authentication error:', error); + alert(`Authentication failed: ${error}`); + window.location.href = '/login.html'; + return; + } + + // Verify code and state + if (!code || !state) { + console.error('Missing code or state in callback'); + alert('Authentication failed: Invalid response'); + window.location.href = '/login.html'; + return; + } + + // Verify state matches + const savedState = localStorage.getItem('oidc_state'); + if (state !== savedState) { + console.error('State mismatch - potential CSRF attack'); + alert('Authentication failed: Invalid state'); + window.location.href = '/login.html'; + return; + } + + // Clear stored state + localStorage.removeItem('oidc_state'); + + try { + // Extract provider from URL path or from saved data + const pathParts = window.location.pathname.split('/'); + const provider = localStorage.getItem('oidc_provider') || + (pathParts.length > 2 ? pathParts[2] : 'default'); + + // Process callback + const response = await fetch('/api/auth/oidc/callback', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + provider: provider, + code: code, + state: state, + }), + }); + + if (!response.ok) { + throw new Error(`Failed to process callback: ${response.statusText}`); + } + + const authData = await response.json(); + + // Store auth data and redirect to dashboard + localStorage.setItem('auth_token', authData.access_token); + localStorage.setItem('refresh_token', authData.refresh_token); + localStorage.setItem('user', JSON.stringify(authData.user)); + + window.location.href = '/index.html'; + } catch (error) { + console.error('Error handling OIDC callback:', error); + alert('Failed to complete authentication. Please try again.'); + window.location.href = '/login.html'; + } + } +}; + +// Check if current page is callback page +if (window.location.pathname.includes('/oidc/callback')) { + document.addEventListener('DOMContentLoaded', () => { + oidcAuth.handleCallback(); + }); +} +``` + +### 9. Update Login Page + +Add OIDC login buttons to `static/login.html`: + +```html + + + + +``` + +## Configuration Example + +Here's how to configure OxiCloud to use OIDC with KeyCloak: + +```yaml +# docker-compose.yml +version: '3' +services: + oxicloud: + image: oxicloud:latest + environment: + OXICLOUD_ENABLE_OIDC: "true" + OXICLOUD_OIDC_PROVIDER_KEYCLOAK_NAME: "KeyCloak" + OXICLOUD_OIDC_PROVIDER_KEYCLOAK_CLIENT_ID: "oxicloud" + OXICLOUD_OIDC_PROVIDER_KEYCLOAK_CLIENT_SECRET: "your-client-secret" + OXICLOUD_OIDC_PROVIDER_KEYCLOAK_DISCOVERY_URL: "https://keycloak.example.com/realms/your-realm/.well-known/openid-configuration" + OXICLOUD_OIDC_PROVIDER_KEYCLOAK_REDIRECT_URI: "https://oxicloud.example.com/oidc/callback/keycloak" + OXICLOUD_OIDC_PROVIDER_KEYCLOAK_SCOPES: "openid profile email" + OXICLOUD_OIDC_PROVIDER_KEYCLOAK_USER_ID_ATTRIBUTE: "sub" + OXICLOUD_OIDC_PROVIDER_KEYCLOAK_DEFAULT_ROLE: "user" + OXICLOUD_OIDC_PROVIDER_KEYCLOAK_AUTO_CREATE_USERS: "true" + ports: + - "8085:8085" + volumes: + - ./storage:/app/storage +``` + +## Additional Considerations + +1. **Security**: OIDC connections should always use HTTPS. Ensure proper TLS configuration. + +2. **User mapping**: Consider how user attributes from OIDC map to your application (roles, groups, etc.). + +3. **Multiple providers**: The design supports multiple OIDC providers simultaneously. + +4. **Session management**: Implement proper session handling for OIDC users. + +5. **Access control**: Review how OIDC integration affects your application's permission model. + +6. **Testing**: Create separate test IdP configurations for development and testing. \ No newline at end of file