🏗️(caldav) migrate from davical to sabre/dav

This commit is contained in:
Sylvain Zimmer
2026-01-11 02:28:04 +01:00
parent a36348ead1
commit bc801d3007
35 changed files with 1059 additions and 549 deletions

View File

@@ -2,7 +2,7 @@
## Overview
The Calendar application is a modern, self-hosted calendar solution that combines a Django REST API backend with a separate CalDAV server (DAViCal) for standards-compliant calendar data storage and synchronization. This architecture provides both a modern web interface and full CalDAV protocol support for compatibility with standard calendar clients.
The Calendar application is a modern, self-hosted calendar solution that combines a Django REST API backend with a separate CalDAV server for standards-compliant calendar data storage and synchronization. This architecture provides both a modern web interface and full CalDAV protocol support for compatibility with standard calendar clients.
## System Architecture
@@ -34,7 +34,7 @@ The Calendar application is a modern, self-hosted calendar solution that combine
│ HTTP/CalDAV Protocol
┌────────▼─────────────────────────────────────┐
DAViCal Server │
│ CalDAV Server │
│ (CalDAV Protocol Implementation) │
│ - Calendar storage │
│ - Event storage (iCalendar format) │
@@ -46,7 +46,7 @@ The Calendar application is a modern, self-hosted calendar solution that combine
┌────────▼─────────────────────────────────────┐
│ PostgreSQL Database │
│ - Django models (users, calendars metadata) │
│ - DAViCal schema (calendar data)
│ - CalDAV server schema (calendar data) │
└──────────────────────────────────────────────┘
```
@@ -60,7 +60,7 @@ The Django backend serves as the **orchestration layer** and **business logic en
- **User Management & Authentication**: OIDC authentication via Keycloak, user profiles, sessions, authorization
- **Calendar Metadata Management**: Calendar creation/deletion, sharing, visibility settings, display preferences
- **REST API Layer**: Modern RESTful API for the web frontend (JSON, standard HTTP methods, versioned at `/api/v1.0/`)
- **CalDAV Proxy**: Proxies CalDAV requests to DAViCal, handles authentication translation, URL routing, discovery endpoint
- **CalDAV Proxy**: Proxies CalDAV requests to CalDAV server, handles authentication translation, URL routing, discovery endpoint
- **Business Logic**: Calendar sharing logic, permission checks, data validation, integration coordination
**Data Storage:**
@@ -69,11 +69,11 @@ The Django backend serves as the **orchestration layer** and **business logic en
- Sharing relationships
- Application configuration
**Important**: Django does NOT store actual calendar events. Events are stored in DAViCal.
**Important**: Django does NOT store actual calendar events. Events are stored in the CalDAV server.
### DAViCal CalDAV Server
### CalDAV Server
DAViCal is a **standards-compliant CalDAV server** that handles all calendar data storage and protocol operations.
The CalDAV server is a **standards-compliant CalDAV server** that handles all calendar data storage and protocol operations.
**Primary Responsibilities:**
- **Calendar Data Storage**: Stores actual calendar events in iCalendar format, manages calendar collections
@@ -82,9 +82,9 @@ DAViCal is a **standards-compliant CalDAV server** that handles all calendar dat
- **Database Schema**: Uses PostgreSQL with its own schema for calendar data
**Authentication Integration:**
- Trusts authentication from Django backend via `X-Forwarded-User` header
- Users with password `*` are externally authenticated
- Custom authentication hook validates forwarded user headers
- Uses Apache authentication backend which reads `REMOTE_USER` environment variable
- Django proxy sets `X-Forwarded-User` header, which the CalDAV server converts to `REMOTE_USER`
- All communication is via HTTP - no direct database access from Django
### Frontend (Next.js)
@@ -98,17 +98,19 @@ The frontend provides the user interface and interacts with both REST API and Ca
### Design Decision: CalDAV Server Separation
The decision to use a separate CalDAV server (DAViCal) rather than implementing CalDAV directly in Django was made for several reasons:
The decision to use a separate CalDAV server rather than implementing CalDAV directly in Django was made for several reasons:
1. **Standards Compliance**: DAViCal is a mature, well-tested CalDAV server that fully implements RFC 4791. Implementing CalDAV from scratch would be error-prone and time-consuming.
1. **Standards Compliance**: Using a mature, well-tested CalDAV server that fully implements RFC 4791. Implementing CalDAV from scratch would be error-prone and time-consuming.
2. **Protocol Complexity**: CalDAV is built on WebDAV, involving complex XML handling, property management, and collection hierarchies. DAViCal handles all of this complexity.
2. **Protocol Complexity**: CalDAV is built on WebDAV, involving complex XML handling, property management, and collection hierarchies. A dedicated CalDAV server handles all of this complexity.
3. **Maintenance**: Using a proven, maintained CalDAV server reduces maintenance burden and ensures compatibility with various CalDAV clients.
4. **Focus**: Django backend can focus on business logic, user management, and REST API, while DAViCal handles calendar protocol operations.
4. **Focus**: Django backend can focus on business logic, user management, and REST API, while the CalDAV server handles calendar protocol operations.
5. **Shared database**: DAViCal was specifically selected because it stores its data into Postgres, which use use in all LaSuite projects.
5. **Shared database**: The CalDAV server stores its data into Postgres, which we use in all LaSuite projects.
6. **Clean separation**: All communication between Django and the CalDAV server is via HTTP, ensuring clean separation of concerns and no direct database access.
### Benefits
@@ -119,7 +121,7 @@ The decision to use a separate CalDAV server (DAViCal) rather than implementing
2. **Separation of Concerns**
- Django handles business logic and user management
- DAViCal handles calendar protocol and data storage
- CalDAV server handles calendar protocol and data storage
- Each component focuses on its core competency
3. **Flexibility**
@@ -133,7 +135,7 @@ The decision to use a separate CalDAV server (DAViCal) rather than implementing
- Can update components independently
5. **Performance**
- DAViCal is optimized for CalDAV operations
- CalDAV server is optimized for CalDAV operations
- Django can focus on application logic
- Database can be optimized separately for each use case
@@ -144,55 +146,56 @@ The decision to use a separate CalDAV server (DAViCal) rather than implementing
TODO: should this only be via caldav too?
1. **Frontend** → POST `/api/v1.0/calendars` (REST API)
2. **Django Backend**: Validates request, creates `Calendar` model, calls DAViCal to create calendar collection
3. **DAViCal**: Receives MKCALENDAR request, creates calendar collection, returns calendar path
4. **Django Backend**: Stores DAViCal path in `Calendar.davical_path`, returns calendar data to frontend
2. **Django Backend**: Validates request, creates `Calendar` model, calls CalDAV server to create calendar collection
3. **CalDAV Server**: Receives MKCALENDAR request, creates calendar collection, returns calendar path
4. **Django Backend**: Stores CalDAV server path in `Calendar.caldav_path`, returns calendar data to frontend
### Creating an Event
Events are created directly via CalDAV protocol:
1. **Frontend** → PUT `/api/v1.0/caldav/{user}/{calendar}/{event_uid}.ics` (CalDAV)
2. **Django Backend**: `CalDAVProxyView` authenticates user, forwards request to DAViCal with authentication headers
3. **DAViCal**: Receives PUT request with iCalendar data, stores event in calendar collection
2. **Django Backend**: `CalDAVProxyView` authenticates user, forwards request to CalDAV server with authentication headers
3. **CalDAV Server**: Receives PUT request with iCalendar data, stores event in calendar collection
4. **Django Backend**: Forwards CalDAV response to frontend
### CalDAV Client Access
1. **CalDAV Client** → PROPFIND `/api/v1.0/caldav/` (CalDAV protocol)
2. **Django Backend**: Authenticates user via Django session, forwards request to DAViCal with `X-Forwarded-User` header
3. **DAViCal**: Processes CalDAV request, returns CalDAV response
2. **Django Backend**: Authenticates user via Django session, forwards request to CalDAV server with `X-Forwarded-User` header
3. **CalDAV Server**: Processes CalDAV request, returns CalDAV response
4. **Django Backend**: Forwards response to client
## Integration Points
### User Synchronization
When a user is created in Django, they must also exist in DAViCal. The `ensure_user_exists()` method automatically creates DAViCal users when needed, called before any DAViCal operation.
Users are automatically created in the CalDAV server when they first access it. The CalDAV server's Apache authentication backend reads the `REMOTE_USER` environment variable, which is set from the `X-Forwarded-User` header sent by Django. No explicit user creation is needed - the CalDAV server will create principals on-demand.
### Calendar Creation
When creating a calendar via REST API:
1. Django creates `Calendar` model with metadata
2. Django calls DAViCal via HTTP to create calendar collection
3. Django stores DAViCal path in `Calendar.davical_path`
2. Django calls CalDAV server via HTTP to create calendar collection
3. Django stores CalDAV server path in `Calendar.caldav_path`
### Authentication Translation
Django sessions are translated to DAViCal authentication:
Django sessions are translated to CalDAV server authentication:
- Django adds `X-Forwarded-User` header with user email
- DAViCal's custom authentication hook validates this header
- Users have password `*` indicating external authentication
- CalDAV server converts `X-Forwarded-User` to `REMOTE_USER` environment variable
- CalDAV server's Apache authentication backend reads `REMOTE_USER` for authentication
- All communication is via HTTP - no direct database access
### URL Routing
CalDAV clients expect specific URL patterns. The CalDAV proxy handles path translation:
- Discovery endpoint at `.well-known/caldav` redirects to `/api/v1.0/caldav/`
- Proxy forwards requests to DAViCal with correct paths
- Proxy forwards requests to CalDAV server with correct paths
## Database Schema
Both Django and DAViCal use the same PostgreSQL database in a local Docker install, but maintain separate schemas:
Both Django and the CalDAV server use the same PostgreSQL database in a local Docker install, but maintain separate schemas:
**Django Schema (public schema):**
- `calendars_user` - User accounts
@@ -200,11 +203,13 @@ Both Django and DAViCal use the same PostgreSQL database in a local Docker insta
- `caldav_calendarshare` - Sharing relationships
- Other Django app tables
**DAViCal Schema (public schema, same database):**
- `usr` - DAViCal user records
- `principal` - DAViCal principals
- `collection` - Calendar collections
- `dav_resource` - Calendar resources (events)
- Other DAViCal-specific tables
**CalDAV Server Schema (public schema, same database):**
- `users` - CalDAV server user records (for digest auth, not used with Apache auth)
- `principals` - CalDAV server principals
- `calendars` - Calendar collections
- `calendarinstances` - Calendar instance metadata
- `calendarobjects` - Calendar resources (events)
- `calendarchanges` - Change tracking
- Other CalDAV server-specific tables
This allows them to share the database locally while keeping data organized.
This allows them to share the database locally while keeping data organized. Note that Django never directly accesses CalDAV server tables - all communication is via HTTP.