BrainGrid

CardDAV Integration Technical Specification

☁️ OxiCloud server, efficient and secure way to save all your data

Used in: 1 reposUpdated: recently

CardDAV Integration Technical Specification

#Overview

This document outlines the technical specification for implementing CardDAV support in OxiCloud, allowing users to synchronize their contacts across various devices and applications.

CardDAV (Card Distributed Authoring and Versioning) is an address book client/server protocol designed to allow users to access and share contact data on a server. It's an extension of WebDAV (RFC 4918) and is defined in RFC 6352.

#Architecture

The CardDAV implementation will follow the established hexagonal architecture pattern used throughout OxiCloud:

┌───────────────────┐     ┌────────────────────┐     ┌────────────────────┐
│                   │     │                    │     │                    │
│  Interfaces       │     │  Application       │     │  Infrastructure    │
│  - CardDAV API    │────▶│  - Contact Service │────▶│  - Contact Repo    │
│  - Contact API    │     │  - CardDAV Adapter │     │  - PG Repository   │
│                   │     │                    │     │                    │
└───────────────────┘     └────────────────────┘     └────────────────────┘
                                    │
                                    ▼
                          ┌────────────────────┐
                          │                    │
                          │  Domain            │
                          │  - Contact Entity  │
                          │  - Address Book    │
                          │                    │
                          └────────────────────┘

#Components

  1. Domain Layer

    • Contact entity - Represents a contact with properties like name, email, phone, etc.
    • AddressBook entity - Represents a collection of contacts
    • Repository interfaces for contact management
  2. Application Layer

    • ContactService - Business logic for managing contacts
    • CardDAVAdapter - Converts between CardDAV protocol requests/responses and domain objects
  3. Infrastructure Layer

    • ContactPgRepository - PostgreSQL implementation of contact repositories
    • AddressBookPgRepository - PostgreSQL implementation of address book repositories
  4. Interface Layer

    • REST API endpoints for contact management
    • CardDAV protocol endpoints (WebDAV extension)

#Database Schema

The following database schema will be used to store contact information:

1-- Address books table
2CREATE TABLE IF NOT EXISTS carddav.address_books (
3    id UUID PRIMARY KEY,
4    name VARCHAR(255) NOT NULL,
5    owner_id VARCHAR(36) NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
6    description TEXT,
7    color VARCHAR(50),
8    is_public BOOLEAN NOT NULL DEFAULT FALSE,
9    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
10    updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
11    UNIQUE(owner_id, name)
12);
13
14-- Address book sharing
15CREATE TABLE IF NOT EXISTS carddav.address_book_shares (
16    address_book_id UUID NOT NULL REFERENCES carddav.address_books(id) ON DELETE CASCADE,
17    user_id VARCHAR(36) NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
18    can_write BOOLEAN NOT NULL DEFAULT FALSE,
19    PRIMARY KEY(address_book_id, user_id)
20);
21
22-- Contacts table
23CREATE TABLE IF NOT EXISTS carddav.contacts (
24    id UUID PRIMARY KEY,
25    address_book_id UUID NOT NULL REFERENCES carddav.address_books(id) ON DELETE CASCADE,
26    uid VARCHAR(255) NOT NULL,
27    full_name VARCHAR(255),
28    first_name VARCHAR(255),
29    last_name VARCHAR(255),
30    nickname VARCHAR(255),
31    email JSONB,
32    phone JSONB,
33    address JSONB,
34    organization VARCHAR(255),
35    title VARCHAR(255),
36    notes TEXT,
37    photo_url TEXT,
38    birthday DATE,
39    anniversary DATE,
40    vcard TEXT NOT NULL,
41    etag VARCHAR(255) NOT NULL,
42    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
43    updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
44    UNIQUE(address_book_id, uid)
45);
46
47-- Contact groups
48CREATE TABLE IF NOT EXISTS carddav.contact_groups (
49    id UUID PRIMARY KEY,
50    address_book_id UUID NOT NULL REFERENCES carddav.address_books(id) ON DELETE CASCADE,
51    name VARCHAR(255) NOT NULL,
52    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
53    updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
54);
55
56-- Group memberships
57CREATE TABLE IF NOT EXISTS carddav.group_memberships (
58    group_id UUID NOT NULL REFERENCES carddav.contact_groups(id) ON DELETE CASCADE,
59    contact_id UUID NOT NULL REFERENCES carddav.contacts(id) ON DELETE CASCADE,
60    PRIMARY KEY(group_id, contact_id)
61);

#API Endpoints

#REST API

The following REST endpoints will be implemented for managing contacts:

MethodEndpointDescription
GET/api/address-booksList all address books
POST/api/address-booksCreate a new address book
GET/api/address-books/:idGet a specific address book
PUT/api/address-books/:idUpdate an address book
DELETE/api/address-books/:idDelete an address book
GET/api/address-books/:id/contactsList contacts in an address book
POST/api/address-books/:id/contactsCreate a new contact
GET/api/address-books/:id/contacts/:contactIdGet a specific contact
PUT/api/address-books/:id/contacts/:contactIdUpdate a contact
DELETE/api/address-books/:id/contacts/:contactIdDelete a contact
GET/api/address-books/:id/groupsList contact groups
POST/api/address-books/:id/groupsCreate a new contact group

#CardDAV Protocol Endpoints

The following CardDAV protocol endpoints will be implemented:

MethodEndpointDescription
PROPFIND/carddav/List all address books
PROPFIND/carddav/:addressBookId/Get address book information
REPORT/carddav/:addressBookId/Query contacts in an address book
GET/carddav/:addressBookId/:contactId.vcfGet a specific contact (vCard)
PUT/carddav/:addressBookId/:contactId.vcfCreate or update a contact
DELETE/carddav/:addressBookId/:contactId.vcfDelete a contact
MKCOL/carddav/:addressBookId/Create a new address book
DELETE/carddav/:addressBookId/Delete an address book

#Data Model

#Contact Entity

1#[derive(Debug, Clone, Serialize, Deserialize)]
2pub struct Contact {
3    pub id: Uuid,
4    pub address_book_id: Uuid,
5    pub uid: String,
6    pub full_name: Option<String>,
7    pub first_name: Option<String>,
8    pub last_name: Option<String>,
9    pub nickname: Option<String>,
10    pub email: Vec<Email>,
11    pub phone: Vec<Phone>,
12    pub address: Vec<Address>,
13    pub organization: Option<String>,
14    pub title: Option<String>,
15    pub notes: Option<String>,
16    pub photo_url: Option<String>,
17    pub birthday: Option<NaiveDate>,
18    pub anniversary: Option<NaiveDate>,
19    pub vcard: String,
20    pub etag: String,
21    pub created_at: DateTime<Utc>,
22    pub updated_at: DateTime<Utc>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct Email {
27    pub email: String,
28    pub r#type: String, // home, work, other
29    pub is_primary: bool,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct Phone {
34    pub number: String,
35    pub r#type: String, // mobile, home, work, fax, other
36    pub is_primary: bool,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct Address {
41    pub street: Option<String>,
42    pub city: Option<String>,
43    pub state: Option<String>,
44    pub postal_code: Option<String>,
45    pub country: Option<String>,
46    pub r#type: String, // home, work, other
47    pub is_primary: bool,
48}

#AddressBook Entity

1#[derive(Debug, Clone, Serialize, Deserialize)]
2pub struct AddressBook {
3    pub id: Uuid,
4    pub name: String,
5    pub owner_id: String,
6    pub description: Option<String>,
7    pub color: Option<String>,
8    pub is_public: bool,
9    pub created_at: DateTime<Utc>,
10    pub updated_at: DateTime<Utc>,
11}

#Repositories

#Contact Repository Interface

1#[async_trait]
2pub trait ContactRepository: Send + Sync + 'static {
3    async fn create_contact(&self, contact: Contact) -> ContactRepositoryResult<Contact>;
4    async fn update_contact(&self, contact: Contact) -> ContactRepositoryResult<Contact>;
5    async fn delete_contact(&self, id: &Uuid) -> ContactRepositoryResult<()>;
6    async fn get_contact_by_id(&self, id: &Uuid) -> ContactRepositoryResult<Option<Contact>>;
7    async fn get_contact_by_uid(&self, address_book_id: &Uuid, uid: &str) -> ContactRepositoryResult<Option<Contact>>;
8    async fn get_contacts_by_address_book(&self, address_book_id: &Uuid) -> ContactRepositoryResult<Vec<Contact>>;
9    async fn get_contacts_by_email(&self, email: &str) -> ContactRepositoryResult<Vec<Contact>>;
10    async fn get_contacts_by_group(&self, group_id: &Uuid) -> ContactRepositoryResult<Vec<Contact>>;
11}

#AddressBook Repository Interface

1#[async_trait]
2pub trait AddressBookRepository: Send + Sync + 'static {
3    async fn create_address_book(&self, address_book: AddressBook) -> AddressBookRepositoryResult<AddressBook>;
4    async fn update_address_book(&self, address_book: AddressBook) -> AddressBookRepositoryResult<AddressBook>;
5    async fn delete_address_book(&self, id: &Uuid) -> AddressBookRepositoryResult<()>;
6    async fn get_address_book_by_id(&self, id: &Uuid) -> AddressBookRepositoryResult<Option<AddressBook>>;
7    async fn get_address_books_by_owner(&self, owner_id: &str) -> AddressBookRepositoryResult<Vec<AddressBook>>;
8    async fn get_shared_address_books(&self, user_id: &str) -> AddressBookRepositoryResult<Vec<AddressBook>>;
9    async fn get_public_address_books(&self) -> AddressBookRepositoryResult<Vec<AddressBook>>;
10    async fn share_address_book(&self, address_book_id: &Uuid, user_id: &str, can_write: bool) -> AddressBookRepositoryResult<()>;
11    async fn unshare_address_book(&self, address_book_id: &Uuid, user_id: &str) -> AddressBookRepositoryResult<()>;
12    async fn get_address_book_shares(&self, address_book_id: &Uuid) -> AddressBookRepositoryResult<Vec<(String, bool)>>;
13}

#CardDAV Protocol Implementation

The CardDAV implementation will support the following features:

  1. Address Book Discovery - Allow clients to discover available address books
  2. Address Book Collection - Manage contacts within address books
  3. vCard Support - Store and retrieve contacts in vCard format (3.0 and 4.0)
  4. Query Support - Filter contacts by properties
  5. Multiget Support - Retrieve multiple contacts in a single request
  6. Sync-Collection - Efficient synchronization of changes

#CardDAV Adapter

The CardDAV adapter will handle:

  1. Parsing CardDAV XML requests
  2. Converting between vCard and Contact entities
  3. Generating CardDAV XML responses
  4. Supporting PROPFIND, REPORT, and other WebDAV methods
  5. Implementing the proper WebDAV properties for CardDAV

#Integration Points

The CardDAV implementation will integrate with:

  1. Authentication System - Reuse existing auth mechanisms
  2. WebDAV Infrastructure - Extend the existing WebDAV implementation
  3. Database Layer - Store contacts in PostgreSQL
  4. User Management - Connect contacts with user accounts

#Client Compatibility

The implementation should be compatible with the following clients:

  • Apple Contacts
  • Google Contacts
  • Thunderbird
  • Outlook
  • Android DAVx⁵
  • iOS native contacts app
  • Evolution

#Implementation Phases

The implementation will be divided into the following phases:

#Phase 1: Core Infrastructure

  • Database schema creation
  • Entity definitions
  • Repository interfaces
  • Basic DTO and port definitions

#Phase 2: Core Business Logic

  • Address book management service
  • Contact management service
  • vCard parsing and generation

#Phase 3: REST API

  • Address book endpoints
  • Contact management endpoints
  • Contact group endpoints

#Phase 4: CardDAV Protocol

  • CardDAV adapter implementation
  • WebDAV method handlers
  • XML parsing and generation
  • Protocol compliance testing

#Phase 5: Testing and Refinement

  • Integration testing with client applications
  • Performance optimization
  • Edge case handling

#Security Considerations

The CardDAV implementation must address the following security concerns:

  1. Authentication - Ensure proper authentication for all operations
  2. Authorization - Verify permissions for each address book operation
  3. Data Validation - Validate vCard input to prevent injection attacks
  4. Resource Limits - Implement limits to prevent abuse
  5. Error Handling - Provide appropriate error responses without revealing sensitive information

#Performance Considerations

To ensure good performance:

  1. Indexing - Proper database indexes for contact queries
  2. Caching - Cache frequently accessed address books and contacts
  3. Pagination - Support pagination for large address books
  4. Incremental Sync - Efficient synchronization with client devices
  5. ETags - Use ETags to prevent unnecessary data transfers

#Testing Strategy

The CardDAV implementation will be tested using:

  1. Unit Tests - Test individual components in isolation
  2. Integration Tests - Test the interaction between components
  3. Protocol Compliance Tests - Verify adherence to the CardDAV specification
  4. Client Compatibility Tests - Test with various CardDAV clients
  5. Performance Tests - Measure performance with large address books