# 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