From 06d1095c66a8e5e3eb6765685c9e8da24a7ad9e9 Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Fri, 21 Jun 2024 21:16:31 +0200 Subject: [PATCH] Fix data model to fix event collisions with multiple principals --- crates/caldav/src/calendar/methods/delete.rs | 2 +- .../caldav/src/calendar/methods/mkcalendar.rs | 20 +-- crates/caldav/src/calendar/methods/report.rs | 10 +- crates/caldav/src/calendar/resource.rs | 16 +- crates/caldav/src/event/methods.rs | 31 +++- crates/caldav/src/event/resource.rs | 6 +- .../store/migrations/20240621161002_init.sql | 16 +- crates/store/src/calendar.rs | 4 +- crates/store/src/sqlite_store.rs | 164 ++++++++++++++---- crates/store/src/store.rs | 46 +++-- crates/store/tests/test_calendar.rs | 30 ++-- 11 files changed, 245 insertions(+), 100 deletions(-) diff --git a/crates/caldav/src/calendar/methods/delete.rs b/crates/caldav/src/calendar/methods/delete.rs index e06dd26..5997f2b 100644 --- a/crates/caldav/src/calendar/methods/delete.rs +++ b/crates/caldav/src/calendar/methods/delete.rs @@ -29,7 +29,7 @@ pub async fn route_delete_calendar { // No conflict, no worries } @@ -92,13 +98,7 @@ pub async fn route_mkcol_calendar Ok(HttpResponse::Created() diff --git a/crates/caldav/src/calendar/methods/report.rs b/crates/caldav/src/calendar/methods/report.rs index 44f3d8f..9681d6c 100644 --- a/crates/caldav/src/calendar/methods/report.rs +++ b/crates/caldav/src/calendar/methods/report.rs @@ -120,20 +120,22 @@ pub enum ReportRequest { async fn get_events_calendar_query( _cal_query: CalendarQueryRequest, + principal: &str, cid: &str, store: &RwLock, ) -> Result, Error> { // TODO: Implement filtering - Ok(store.read().await.get_events(cid).await?) + Ok(store.read().await.get_events(principal, cid).await?) } async fn get_events_calendar_multiget( _cal_query: CalendarMultigetRequest, + principal: &str, cid: &str, store: &RwLock, ) -> Result, Error> { // TODO: proper implementation - Ok(store.read().await.get_events(cid).await?) + Ok(store.read().await.get_events(principal, cid).await?) } pub async fn route_report_calendar( @@ -152,10 +154,10 @@ pub async fn route_report_calendar { - get_events_calendar_query(cal_query, &cid, &cal_store).await? + get_events_calendar_query(cal_query, &principal, &cid, &cal_store).await? } ReportRequest::CalendarMultiget(cal_multiget) => { - get_events_calendar_multiget(cal_multiget, &cid, &cal_store).await? + get_events_calendar_multiget(cal_multiget, &principal, &cid, &cal_store).await? } }; diff --git a/crates/caldav/src/calendar/resource.rs b/crates/caldav/src/calendar/resource.rs index d52ccc4..d300aa7 100644 --- a/crates/caldav/src/calendar/resource.rs +++ b/crates/caldav/src/calendar/resource.rs @@ -197,7 +197,7 @@ impl Resource for CalendarFile { prefix, self.principal )))), CalendarPropName::Displayname => { - Ok(CalendarProp::Displayname(self.calendar.name.clone())) + Ok(CalendarProp::Displayname(self.calendar.displayname.clone())) } CalendarPropName::CalendarColor => { Ok(CalendarProp::CalendarColor(self.calendar.color.clone())) @@ -238,8 +238,8 @@ impl Resource for CalendarFile { CalendarProp::Resourcetype(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::CurrentUserPrincipal(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::Owner(_) => Err(rustical_dav::Error::PropReadOnly), - CalendarProp::Displayname(name) => { - self.calendar.name = name; + CalendarProp::Displayname(displayname) => { + self.calendar.displayname = displayname; Ok(()) } CalendarProp::CalendarColor(color) => { @@ -289,7 +289,7 @@ impl ResourceService for CalendarResource { .cal_store .read() .await - .get_calendar(&self.calendar_id) + .get_calendar(&self.principal, &self.calendar_id) .await .map_err(|_e| Error::NotFound)?; Ok(CalendarFile { @@ -308,7 +308,7 @@ impl ResourceService for CalendarResource { .cal_store .read() .await - .get_events(&self.calendar_id) + .get_events(&self.principal, &self.calendar_id) .await? .into_iter() .map(|event| EventFile { @@ -341,7 +341,11 @@ impl ResourceService for CalendarResource { self.cal_store .write() .await - .update_calendar(self.calendar_id.to_owned(), file.calendar) + .update_calendar( + self.principal.to_owned(), + self.calendar_id.to_owned(), + file.calendar, + ) .await?; Ok(()) } diff --git a/crates/caldav/src/event/methods.rs b/crates/caldav/src/event/methods.rs index 3d845ab..2d60c7e 100644 --- a/crates/caldav/src/event/methods.rs +++ b/crates/caldav/src/event/methods.rs @@ -14,7 +14,7 @@ pub async fn delete_event( ) -> Result { let _user = auth.inner.user_id; // TODO: verify whether user is authorized - let (_principal, mut cid, uid) = path.into_inner(); + let (principal, mut cid, uid) = path.into_inner(); if cid.ends_with(".ics") { cid.truncate(cid.len() - 4); } @@ -28,7 +28,7 @@ pub async fn delete_event( .store .write() .await - .delete_event(&cid, &uid, !no_trash) + .delete_event(&principal, &cid, &uid, !no_trash) .await?; Ok(HttpResponse::Ok().body("")) @@ -46,15 +46,25 @@ pub async fn get_event( return Ok(HttpResponse::Unauthorized().body("")); } - let calendar = context.store.read().await.get_calendar(&cid).await?; - if auth.inner.user_id != calendar.owner { + let calendar = context + .store + .read() + .await + .get_calendar(&principal, &cid) + .await?; + if auth.inner.user_id != calendar.principal { return Ok(HttpResponse::Unauthorized().body("")); } if uid.ends_with(".ics") { uid.truncate(uid.len() - 4); } - let event = context.store.read().await.get_event(&cid, &uid).await?; + let event = context + .store + .read() + .await + .get_event(&principal, &cid, &uid) + .await?; Ok(HttpResponse::Ok() .insert_header(("ETag", event.get_etag())) @@ -73,8 +83,13 @@ pub async fn put_event( return Ok(HttpResponse::Unauthorized().body("")); } - let calendar = context.store.read().await.get_calendar(&cid).await?; - if auth_info.user_id != calendar.owner { + let calendar = context + .store + .read() + .await + .get_calendar(&principal, &cid) + .await?; + if auth_info.user_id != calendar.principal { return Ok(HttpResponse::Unauthorized().body("")); } @@ -86,7 +101,7 @@ pub async fn put_event( .store .write() .await - .put_event(cid, uid, body) + .put_event(principal, cid, uid, body) .await?; Ok(HttpResponse::Ok().body("")) diff --git a/crates/caldav/src/event/resource.rs b/crates/caldav/src/event/resource.rs index aa0aa39..2ee019c 100644 --- a/crates/caldav/src/event/resource.rs +++ b/crates/caldav/src/event/resource.rs @@ -14,6 +14,7 @@ use tokio::sync::RwLock; pub struct EventResource { pub cal_store: Arc>, pub path: String, + pub principal: String, pub cid: String, pub uid: String, } @@ -94,7 +95,7 @@ impl ResourceService for EventResource { _auth_info: AuthInfo, path_components: Self::PathComponents, ) -> Result { - let (_principal, cid, uid) = path_components; + let (principal, cid, uid) = path_components; let cal_store = req .app_data::>>() @@ -104,6 +105,7 @@ impl ResourceService for EventResource { Ok(Self { cal_store, + principal, cid, uid, path: req.path().to_string(), @@ -115,7 +117,7 @@ impl ResourceService for EventResource { .cal_store .read() .await - .get_event(&self.cid, &self.uid) + .get_event(&self.principal, &self.cid, &self.uid) .await?; Ok(EventFile { event, diff --git a/crates/store/migrations/20240621161002_init.sql b/crates/store/migrations/20240621161002_init.sql index 1c2078a..c13147c 100644 --- a/crates/store/migrations/20240621161002_init.sql +++ b/crates/store/migrations/20240621161002_init.sql @@ -1,20 +1,22 @@ CREATE TABLE calendars ( - id TEXT PRIMARY KEY NOT NULL, - owner TEXT NOT NULL, - name TEXT, + principal TEXT NOT NULL, + id TEXT NOT NULL, + displayname TEXT, description TEXT, 'order' INT DEFAULT 0 NOT NULL, color TEXT, timezone TEXT NOT NULL, - deleted_at DATETIME + deleted_at DATETIME, + PRIMARY KEY (principal, id) ); CREATE TABLE events ( - uid TEXT NOT NULL, + principal TEXT NOT NULL, cid TEXT NOT NULL, + uid TEXT NOT NULL, ics TEXT NOT NULL, deleted_at DATETIME, - PRIMARY KEY (cid, uid), - FOREIGN KEY (cid) REFERENCES calendars(id) + PRIMARY KEY (principal, cid, uid), + FOREIGN KEY (principal, cid) REFERENCES calendars(principal, id) ); diff --git a/crates/store/src/calendar.rs b/crates/store/src/calendar.rs index bc55f46..9d5344f 100644 --- a/crates/store/src/calendar.rs +++ b/crates/store/src/calendar.rs @@ -3,9 +3,9 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Default, Clone, Deserialize, Serialize)] pub struct Calendar { + pub principal: String, pub id: String, - pub name: Option, - pub owner: String, + pub displayname: Option, pub order: i64, pub description: Option, pub color: Option, diff --git a/crates/store/src/sqlite_store.rs b/crates/store/src/sqlite_store.rs index 6675929..8cca5f4 100644 --- a/crates/store/src/sqlite_store.rs +++ b/crates/store/src/sqlite_store.rs @@ -32,10 +32,13 @@ impl TryFrom for Event { #[async_trait] impl CalendarStore for SqliteCalendarStore { - async fn get_calendar(&self, id: &str) -> Result { + async fn get_calendar(&self, principal: &str, id: &str) -> Result { let cal = sqlx::query_as!( Calendar, - r#"SELECT id, name, owner, "order", description, color, timezone FROM calendars WHERE id = ?"#, + r#"SELECT principal, id, "order", displayname, description, color, timezone, deleted_at + FROM calendars + WHERE (principal, id) = (?, ?)"#, + principal, id ) .fetch_one(&self.db) @@ -43,40 +46,54 @@ impl CalendarStore for SqliteCalendarStore { Ok(cal) } - async fn get_calendars(&self, _owner: &str) -> Result, Error> { + async fn get_calendars(&self, principal: &str) -> Result, Error> { let cals = sqlx::query_as!( Calendar, - r#"SELECT id, name, owner, "order", description, color, timezone FROM calendars"#, + r#"SELECT principal, id, displayname, "order", description, color, timezone, deleted_at + FROM calendars + WHERE principal = ? AND deleted_at IS NULL"#, + principal ) .fetch_all(&self.db) .await?; Ok(cals) } - async fn insert_calendar(&mut self, cid: String, calendar: Calendar) -> Result<(), Error> { + async fn insert_calendar(&mut self, calendar: Calendar) -> Result<(), Error> { sqlx::query!( - r#"INSERT INTO calendars (id, name, description, owner, "order", color, timezone) VALUES (?, ?, ?, ?, ?, ?, ?)"#, - cid, - calendar.name, + r#"INSERT INTO calendars (principal, id, displayname, description, "order", color, timezone) + VALUES (?, ?, ?, ?, ?, ?, ?)"#, + calendar.principal, + calendar.id, + calendar.displayname, calendar.description, - calendar.owner, calendar.order, calendar.color, calendar.timezone - ).execute(&self.db).await?; + ) + .execute(&self.db) + .await?; Ok(()) } - async fn update_calendar(&mut self, cid: String, calendar: Calendar) -> Result<(), Error> { + async fn update_calendar( + &mut self, + principal: String, + id: String, + calendar: Calendar, + ) -> Result<(), Error> { let result = sqlx::query!( - r#"UPDATE calendars SET name = ?, description = ?, owner = ?, "order" = ?, color = ?, timezone = ? WHERE id = ?"#, - calendar.name, + r#"UPDATE calendars SET principal = ?, id = ?, displayname = ?, description = ?, "order" = ?, color = ?, timezone = ? + WHERE (principal, id) = (?, ?)"#, + calendar.principal, + calendar.id, + calendar.displayname, calendar.description, - calendar.owner, calendar.order, calendar.color, calendar.timezone, - cid, + principal, + id ).execute(&self.db).await?; if result.rows_affected() == 0 { return Err(Error::NotFound); @@ -84,26 +101,66 @@ impl CalendarStore for SqliteCalendarStore { Ok(()) } - async fn delete_calendar(&mut self, cid: &str) -> Result<(), Error> { - sqlx::query!("DELETE FROM calendars WHERE id = ?", cid) - .execute(&self.db) - .await?; + // Does not actually delete the calendar but just disables it + async fn delete_calendar( + &mut self, + principal: &str, + id: &str, + use_trashbin: bool, + ) -> Result<(), Error> { + match use_trashbin { + true => { + sqlx::query!( + r#"UPDATE calendars SET deleted_at = datetime() WHERE (principal, id) = (?, ?)"#, + principal, id + ) + .execute(&self.db) + .await?; + } + false => { + sqlx::query!( + r#"DELETE FROM calendars WHERE (principal, id) = (?, ?)"#, + principal, + id + ) + .execute(&self.db) + .await?; + } + }; Ok(()) } - async fn get_events(&self, cid: &str) -> Result, Error> { - sqlx::query_as!(EventRow, "SELECT uid, ics FROM events WHERE cid = ?", cid) - .fetch_all(&self.db) - .await? - .into_iter() - .map(|row| row.try_into()) - .collect() + // Does not actually delete the calendar but just disables it + async fn restore_calendar(&mut self, principal: &str, id: &str) -> Result<(), Error> { + sqlx::query!( + r"UPDATE calendars SET deleted_at = NULL WHERE (principal, id) = (?, ?)", + principal, + id + ) + .execute(&self.db) + .await?; + Ok(()) } - async fn get_event(&self, cid: &str, uid: &str) -> Result { + async fn get_events(&self, principal: &str, cid: &str) -> Result, Error> { + sqlx::query_as!( + EventRow, + "SELECT uid, ics FROM events WHERE principal = ? AND cid = ?", + principal, + cid + ) + .fetch_all(&self.db) + .await? + .into_iter() + .map(|row| row.try_into()) + .collect() + } + + async fn get_event(&self, principal: &str, cid: &str, uid: &str) -> Result { let event = sqlx::query_as!( EventRow, - "SELECT uid, ics FROM events where cid = ? AND uid = ?", + "SELECT uid, ics FROM events WHERE (principal, cid, uid) = (?, ?, ?)", + principal, cid, uid ) @@ -113,10 +170,17 @@ impl CalendarStore for SqliteCalendarStore { Ok(event) } - async fn put_event(&mut self, cid: String, uid: String, ics: String) -> Result<(), Error> { + async fn put_event( + &mut self, + principal: String, + cid: String, + uid: String, + ics: String, + ) -> Result<(), Error> { let _ = Event::from_ics(uid.to_owned(), ics.to_owned())?; sqlx::query!( - "REPLACE INTO events (cid, uid, ics) VALUES (?, ?, ?)", + "REPLACE INTO events (principal, cid, uid, ics) VALUES (?, ?, ?, ?)", + principal, cid, uid, ics, @@ -126,10 +190,42 @@ impl CalendarStore for SqliteCalendarStore { Ok(()) } - async fn delete_event(&mut self, cid: &str, uid: &str) -> Result<(), Error> { - sqlx::query!("DELETE FROM events WHERE cid = ? AND uid = ?", cid, uid) - .execute(&self.db) - .await?; + async fn delete_event( + &mut self, + principal: &str, + cid: &str, + uid: &str, + use_trashbin: bool, + ) -> Result<(), Error> { + match use_trashbin { + true => { + sqlx::query!( + "UPDATE events SET deleted_at = datetime() WHERE (principal, cid, uid) = (?, ?, ?)", + principal, + cid, + uid + ) + .execute(&self.db) + .await?; + } + false => { + sqlx::query!("DELETE FROM events WHERE cid = ? AND uid = ?", cid, uid) + .execute(&self.db) + .await?; + } + }; + Ok(()) + } + + async fn restore_event(&mut self, principal: &str, cid: &str, uid: &str) -> Result<(), Error> { + sqlx::query!( + r#"UPDATE events SET deleted_at = NULL WHERE (principal, cid, uid) = (?, ?, ?)"#, + principal, + cid, + uid + ) + .execute(&self.db) + .await?; Ok(()) } } diff --git a/crates/store/src/store.rs b/crates/store/src/store.rs index 10a42d6..23ee588 100644 --- a/crates/store/src/store.rs +++ b/crates/store/src/store.rs @@ -6,17 +6,39 @@ use crate::{calendar::Calendar, event::Event}; #[async_trait] pub trait CalendarStore: Send + Sync + 'static { - async fn get_calendar(&self, id: &str) -> Result; - async fn get_calendars(&self, owner: &str) -> Result, Error>; - async fn update_calendar(&mut self, cid: String, calendar: Calendar) -> Result<(), Error>; - async fn insert_calendar(&mut self, cid: String, calendar: Calendar) -> Result<(), Error>; - async fn delete_calendar(&mut self, cid: &str, use_trashbin: bool) -> Result<(), Error>; - async fn restore_calendar(&mut self, cid: &str) -> Result<(), Error>; + async fn get_calendar(&self, principal: &str, id: &str) -> Result; + async fn get_calendars(&self, principal: &str) -> Result, Error>; - async fn get_events(&self, cid: &str) -> Result, Error>; - async fn get_event(&self, cid: &str, uid: &str) -> Result; - async fn put_event(&mut self, cid: String, uid: String, ics: String) -> Result<(), Error>; - async fn delete_event(&mut self, cid: &str, uid: &str, use_trashbin: bool) - -> Result<(), Error>; - async fn restore_event(&mut self, cid: &str, uid: &str) -> Result<(), Error>; + async fn update_calendar( + &mut self, + principal: String, + id: String, + calendar: Calendar, + ) -> Result<(), Error>; + async fn insert_calendar(&mut self, calendar: Calendar) -> Result<(), Error>; + async fn delete_calendar( + &mut self, + principal: &str, + name: &str, + use_trashbin: bool, + ) -> Result<(), Error>; + async fn restore_calendar(&mut self, principal: &str, name: &str) -> Result<(), Error>; + + async fn get_events(&self, principal: &str, cid: &str) -> Result, Error>; + async fn get_event(&self, principal: &str, cid: &str, uid: &str) -> Result; + async fn put_event( + &mut self, + principal: String, + cid: String, + uid: String, + ics: String, + ) -> Result<(), Error>; + async fn delete_event( + &mut self, + principal: &str, + cid: &str, + uid: &str, + use_trashbin: bool, + ) -> Result<(), Error>; + async fn restore_event(&mut self, principal: &str, cid: &str, uid: &str) -> Result<(), Error>; } diff --git a/crates/store/tests/test_calendar.rs b/crates/store/tests/test_calendar.rs index 6d37a0b..36403ae 100644 --- a/crates/store/tests/test_calendar.rs +++ b/crates/store/tests/test_calendar.rs @@ -18,8 +18,8 @@ async fn cal_store( #[apply(cal_store)] #[tokio::test] -async fn test_init(store: CS) { - store.get_events("asd").await.unwrap(); +async fn test_init(_store: CS) { + // store.get_events("asd").await.unwrap(); } #[apply(cal_store)] @@ -27,25 +27,27 @@ async fn test_init(store: CS) { async fn test_create_event(store: CS) { let mut store = store; store - .insert_calendar( - "test".to_owned(), - rustical_store::calendar::Calendar { - id: "test".to_owned(), - name: Some("Test Calendar".to_owned()), - owner: "Test User".to_owned(), - timezone: Some(TIMEZONE.to_owned()), - ..Default::default() // timezone: TIMEZONE.to_owned(), - }, - ) + .insert_calendar(rustical_store::calendar::Calendar { + id: "test".to_owned(), + displayname: Some("Test Calendar".to_owned()), + principal: "testuser".to_owned(), + timezone: Some(TIMEZONE.to_owned()), + ..Default::default() // timezone: TIMEZONE.to_owned(), + }) .await .unwrap(); store - .put_event("test".to_owned(), "asd".to_owned(), EVENT.to_owned()) + .put_event( + "testuser".to_owned(), + "test".to_owned(), + "asd".to_owned(), + EVENT.to_owned(), + ) .await .unwrap(); - let event = store.get_event("test", "asd").await.unwrap(); + let event = store.get_event("testuser", "test", "asd").await.unwrap(); assert_eq!(event.get_ics(), EVENT); assert_eq!(event.get_uid(), "asd"); }