diff --git a/crates/common/src/sharing/resources.rs b/crates/common/src/sharing/resources.rs index 14802173..33e82246 100644 --- a/crates/common/src/sharing/resources.rs +++ b/crates/common/src/sharing/resources.rs @@ -80,4 +80,14 @@ impl DavResources { account_acls } + + pub fn document_ids(&self, is_container: bool) -> impl Iterator { + self.resources.iter().filter_map(move |resource| { + if resource.is_container() == is_container { + Some(resource.document_id) + } else { + None + } + }) + } } diff --git a/crates/dav/src/calendar/mkcol.rs b/crates/dav/src/calendar/mkcol.rs index 0d938e60..3676a745 100644 --- a/crates/dav/src/calendar/mkcol.rs +++ b/crates/dav/src/calendar/mkcol.rs @@ -100,7 +100,7 @@ impl CalendarMkColRequestHandler for Server { let mut prop_stat = PropStatBuilder::default(); is_mkcalendar = mkcol.is_mkcalendar; if !self.apply_calendar_properties( - account_id, + access_token, &mut calendar, false, mkcol.props, diff --git a/crates/dav/src/calendar/proppatch.rs b/crates/dav/src/calendar/proppatch.rs index f302676b..71afcc5a 100644 --- a/crates/dav/src/calendar/proppatch.rs +++ b/crates/dav/src/calendar/proppatch.rs @@ -47,7 +47,7 @@ pub(crate) trait CalendarPropPatchRequestHandler: Sync + Send { fn apply_calendar_properties( &self, - account_id: u32, + access_token: &AccessToken, calendar: &mut Calendar, is_update: bool, properties: Vec, @@ -149,7 +149,7 @@ impl CalendarPropPatchRequestHandler for Server { // Remove properties if !request.set_first && !request.remove.is_empty() { remove_calendar_properties( - account_id, + access_token, &mut new_calendar, std::mem::take(&mut request.remove), &mut items, @@ -158,7 +158,7 @@ impl CalendarPropPatchRequestHandler for Server { // Set properties is_success = self.apply_calendar_properties( - account_id, + access_token, &mut new_calendar, true, request.set, @@ -168,7 +168,7 @@ impl CalendarPropPatchRequestHandler for Server { // Remove properties if is_success && !request.remove.is_empty() { remove_calendar_properties( - account_id, + access_token, &mut new_calendar, request.remove, &mut items, @@ -238,7 +238,7 @@ impl CalendarPropPatchRequestHandler for Server { fn apply_calendar_properties( &self, - account_id: u32, + access_token: &AccessToken, calendar: &mut Calendar, is_update: bool, properties: Vec, @@ -250,7 +250,7 @@ impl CalendarPropPatchRequestHandler for Server { match (&property.property, property.value) { (DavProperty::WebDav(WebDavProperty::DisplayName), DavValue::String(name)) => { if name.len() <= self.core.groupware.live_property_size { - calendar.preferences_mut(account_id).name = name; + calendar.preferences_mut(access_token).name = name; items.insert_ok(property.property); } else { items.insert_error_with_description( @@ -266,7 +266,7 @@ impl CalendarPropPatchRequestHandler for Server { DavValue::String(name), ) => { if name.len() <= self.core.groupware.live_property_size { - calendar.preferences_mut(account_id).description = Some(name); + calendar.preferences_mut(access_token).description = Some(name); items.insert_ok(property.property); } else { items.insert_error_with_description( @@ -298,13 +298,14 @@ impl CalendarPropPatchRequestHandler for Server { ); has_errors = true; } else { - calendar.preferences_mut(account_id).time_zone = Timezone::Custom(ical); + calendar.preferences_mut(access_token).time_zone = Timezone::Custom(ical); items.insert_ok(property.property); } } (DavProperty::CalDav(CalDavProperty::TimezoneId), DavValue::String(tz_id)) => { if let Ok(tz) = Tz::from_str(&tz_id) { - calendar.preferences_mut(account_id).time_zone = Timezone::IANA(tz.as_id()); + calendar.preferences_mut(access_token).time_zone = + Timezone::IANA(tz.as_id()); items.insert_ok(property.property); } else { items.insert_precondition_failed_with_description( @@ -472,7 +473,7 @@ fn remove_event_properties( } fn remove_calendar_properties( - account_id: u32, + access_token: &AccessToken, calendar: &mut Calendar, properties: Vec, items: &mut PropStatBuilder, @@ -480,12 +481,12 @@ fn remove_calendar_properties( for property in properties { match &property { DavProperty::CalDav(CalDavProperty::CalendarDescription) => { - calendar.preferences_mut(account_id).description = None; + calendar.preferences_mut(access_token).description = None; items.insert_with_status(property, StatusCode::NO_CONTENT); } DavProperty::CalDav(CalDavProperty::CalendarTimezone) | DavProperty::CalDav(CalDavProperty::TimezoneId) => { - calendar.preferences_mut(account_id).time_zone = Timezone::Default; + calendar.preferences_mut(access_token).time_zone = Timezone::Default; items.insert_with_status(property, StatusCode::NO_CONTENT); } DavProperty::DeadProperty(dead) => { diff --git a/crates/dav/src/card/copy_move.rs b/crates/dav/src/card/copy_move.rs index 2af11f04..32a54071 100644 --- a/crates/dav/src/card/copy_move.rs +++ b/crates/dav/src/card/copy_move.rs @@ -18,7 +18,7 @@ use dav_proto::{Depth, RequestHeaders}; use groupware::{ DestroyArchive, cache::GroupwareCache, - contact::{AddressBook, ContactCard}, + contact::{AddressBook, AddressBookPreferences, ContactCard}, }; use http_proto::HttpResponse; use hyper::StatusCode; @@ -759,10 +759,17 @@ async fn copy_container( .caused_by(trc::location!())?; } + let preference = book.preferences.into_iter().next().unwrap(); book.name = new_name.to_string(); book.subscribers.clear(); book.acls.clear(); - book.is_default = false; + book.preferences = vec![AddressBookPreferences { + account_id: to_account_id, + name: preference.name, + description: preference.description, + sort_order: 0, + is_default: false, + }]; let is_overwrite = to_document_id.is_some(); let to_document_id = if let Some(to_document_id) = to_document_id { diff --git a/crates/dav/src/card/mkcol.rs b/crates/dav/src/card/mkcol.rs index 527101e4..78a1132a 100644 --- a/crates/dav/src/card/mkcol.rs +++ b/crates/dav/src/card/mkcol.rs @@ -89,7 +89,13 @@ impl CardMkColRequestHandler for Server { let mut return_prop_stat = None; if let Some(mkcol) = request { let mut prop_stat = PropStatBuilder::default(); - if !self.apply_addressbook_properties(&mut book, false, mkcol.props, &mut prop_stat) { + if !self.apply_addressbook_properties( + access_token, + &mut book, + false, + mkcol.props, + &mut prop_stat, + ) { return Ok(HttpResponse::new(StatusCode::FORBIDDEN).with_xml_body( MkColResponse::new(prop_stat.build()) .with_namespace(Namespace::CardDav) diff --git a/crates/dav/src/card/proppatch.rs b/crates/dav/src/card/proppatch.rs index 630d15f8..36fb8313 100644 --- a/crates/dav/src/card/proppatch.rs +++ b/crates/dav/src/card/proppatch.rs @@ -45,6 +45,7 @@ pub(crate) trait CardPropPatchRequestHandler: Sync + Send { fn apply_addressbook_properties( &self, + access_token: &AccessToken, address_book: &mut AddressBook, is_update: bool, properties: Vec, @@ -146,6 +147,7 @@ impl CardPropPatchRequestHandler for Server { // Remove properties if !request.set_first && !request.remove.is_empty() { remove_addressbook_properties( + access_token, &mut new_book, std::mem::take(&mut request.remove), &mut items, @@ -153,12 +155,22 @@ impl CardPropPatchRequestHandler for Server { } // Set properties - is_success = - self.apply_addressbook_properties(&mut new_book, true, request.set, &mut items); + is_success = self.apply_addressbook_properties( + access_token, + &mut new_book, + true, + request.set, + &mut items, + ); // Remove properties if is_success && !request.remove.is_empty() { - remove_addressbook_properties(&mut new_book, request.remove, &mut items); + remove_addressbook_properties( + access_token, + &mut new_book, + request.remove, + &mut items, + ); } if is_success { @@ -224,6 +236,7 @@ impl CardPropPatchRequestHandler for Server { fn apply_addressbook_properties( &self, + access_token: &AccessToken, address_book: &mut AddressBook, is_update: bool, properties: Vec, @@ -235,7 +248,7 @@ impl CardPropPatchRequestHandler for Server { match (&property.property, property.value) { (DavProperty::WebDav(WebDavProperty::DisplayName), DavValue::String(name)) => { if name.len() <= self.core.groupware.live_property_size { - address_book.display_name = Some(name); + address_book.preferences_mut(access_token).name = name; items.insert_ok(property.property); } else { items.insert_error_with_description( @@ -251,7 +264,7 @@ impl CardPropPatchRequestHandler for Server { DavValue::String(name), ) => { if name.len() <= self.core.groupware.live_property_size { - address_book.description = Some(name); + address_book.preferences_mut(access_token).description = Some(name); items.insert_ok(property.property); } else { items.insert_error_with_description( @@ -418,6 +431,7 @@ fn remove_card_properties( } fn remove_addressbook_properties( + access_token: &AccessToken, book: &mut AddressBook, properties: Vec, items: &mut PropStatBuilder, @@ -425,13 +439,13 @@ fn remove_addressbook_properties( for property in properties { match &property { DavProperty::CardDav(CardDavProperty::AddressbookDescription) => { - book.description = None; + book.preferences_mut(access_token).description = None; items.insert_with_status(property, StatusCode::NO_CONTENT); } - DavProperty::WebDav(WebDavProperty::DisplayName) => { + /*DavProperty::WebDav(WebDavProperty::DisplayName) => { book.display_name = None; items.insert_with_status(property, StatusCode::NO_CONTENT); - } + }*/ DavProperty::DeadProperty(dead) => { book.dead_properties.remove_element(dead); items.insert_with_status(property, StatusCode::NO_CONTENT); diff --git a/crates/dav/src/common/mod.rs b/crates/dav/src/common/mod.rs index ab494d59..313d310d 100644 --- a/crates/dav/src/common/mod.rs +++ b/crates/dav/src/common/mod.rs @@ -8,6 +8,7 @@ use calcard::{ icalendar::{ICalendarComponentType, ICalendarParameterName, ICalendarProperty}, vcard::{VCardParameterName, VCardVersion}, }; +use common::auth::AccessToken; use dav_proto::{ Depth, RequestHeaders, Return, schema::{ @@ -418,13 +419,15 @@ impl<'x> ArchivedResource<'x> { } } - pub fn display_name(&self, account_id: u32) -> Option<&str> { + pub fn display_name(&self, access_token: &AccessToken) -> Option<&str> { match self { ArchivedResource::Calendar(archive) => { - Some(archive.inner.preferences(account_id).name.as_str()) + Some(archive.inner.preferences(access_token).name.as_str()) } ArchivedResource::CalendarEvent(archive) => archive.inner.display_name.as_deref(), - ArchivedResource::AddressBook(archive) => archive.inner.display_name.as_deref(), + ArchivedResource::AddressBook(archive) => { + Some(archive.inner.preferences(access_token).name.as_str()) + } ArchivedResource::ContactCard(archive) => archive.inner.display_name.as_deref(), ArchivedResource::FileNode(archive) => archive.inner.display_name.as_deref(), ArchivedResource::CalendarScheduling(_) diff --git a/crates/dav/src/common/propfind.rs b/crates/dav/src/common/propfind.rs index 42d6e8af..a92c04fe 100644 --- a/crates/dav/src/common/propfind.rs +++ b/crates/dav/src/common/propfind.rs @@ -408,7 +408,6 @@ impl PropFindRequestHandler for Server { PropFind::Prop(items) => items.clone(), }; - let view_as_id = access_token.primary_id(); let is_scheduling = collection_container == Collection::CalendarScheduling; 'outer: for item in paths { let account_id = item.account_id; @@ -491,7 +490,7 @@ impl PropFindRequestHandler for Server { )); } WebDavProperty::DisplayName => { - if let Some(name) = archive.display_name(view_as_id) { + if let Some(name) = archive.display_name(access_token) { fields.push(DavPropertyValue::new( property.clone(), DavValue::String(name.to_string()), @@ -777,11 +776,17 @@ impl PropFindRequestHandler for Server { ( CardDavProperty::AddressbookDescription, ArchivedResource::AddressBook(book), - ) if book.inner.description.is_some() => { - fields.push(DavPropertyValue::new( - property.clone(), - book.inner.description.as_ref().unwrap().to_string(), - )); + ) => { + if let Some(desc) = + book.inner.preferences(access_token).description.as_deref() + { + fields.push(DavPropertyValue::new( + property.clone(), + desc.to_string(), + )); + } else { + fields_not_found.push(DavPropertyValue::empty(property.clone())); + } } ( CardDavProperty::SupportedAddressData, @@ -844,7 +849,7 @@ impl PropFindRequestHandler for Server { ) => { if let Some(desc) = calendar .inner - .preferences(account_id) + .preferences(access_token) .description .as_deref() { @@ -861,7 +866,7 @@ impl PropFindRequestHandler for Server { ArchivedResource::Calendar(calendar), ) => { if let ArchivedTimezone::Custom(tz) = - &calendar.inner.preferences(account_id).time_zone + &calendar.inner.preferences(access_token).time_zone { fields.push(DavPropertyValue::new( property.clone(), @@ -873,7 +878,7 @@ impl PropFindRequestHandler for Server { } (CalDavProperty::TimezoneId, ArchivedResource::Calendar(calendar)) => { if let ArchivedTimezone::IANA(tz) = - &calendar.inner.preferences(account_id).time_zone + &calendar.inner.preferences(access_token).time_zone { fields.push(DavPropertyValue::new( property.clone(), diff --git a/crates/groupware/src/cache/mod.rs b/crates/groupware/src/cache/mod.rs index 0d5fc0df..0ba3fa19 100644 --- a/crates/groupware/src/cache/mod.rs +++ b/crates/groupware/src/cache/mod.rs @@ -7,7 +7,7 @@ use crate::{ cache::calcard::{build_scheduling_resources, path_from_scheduling, resource_from_scheduling}, calendar::{Calendar, CalendarEvent, CalendarPreferences}, - contact::{AddressBook, ContactCard}, + contact::{AddressBook, AddressBookPreferences, ContactCard}, file::FileNode, }; use ahash::AHashSet; @@ -327,17 +327,19 @@ impl GroupwareCache for Server { .await?; AddressBook { name: name.clone(), - display_name: format!( - "{} ({})", - self.core - .groupware - .default_addressbook_display_name - .as_ref() - .unwrap_or(name), - account_name - ) - .into(), - is_default: true, + preferences: vec![AddressBookPreferences { + account_id, + name: format!( + "{} ({})", + self.core + .groupware + .default_addressbook_display_name + .as_ref() + .unwrap_or(name), + account_name + ), + ..Default::default() + }], ..Default::default() } .insert(access_token, account_id, document_id, &mut batch)?; diff --git a/crates/groupware/src/calendar/mod.rs b/crates/groupware/src/calendar/mod.rs index 0ef2d2e7..1895affa 100644 --- a/crates/groupware/src/calendar/mod.rs +++ b/crates/groupware/src/calendar/mod.rs @@ -12,9 +12,9 @@ pub mod itip; pub mod storage; use calcard::icalendar::ICalendar; -use common::DavName; +use common::{DavName, auth::AccessToken}; use dav_proto::schema::request::DeadProperty; -use types::acl::{Acl, AclGrant}; +use types::acl::AclGrant; #[derive( rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq, @@ -154,55 +154,12 @@ pub enum Timezone { Default, } -pub enum CalendarRight { - ReadFreeBusy, - ReadItems, - WriteAll, - WriteOwn, - UpdatePrivate, - RSVP, - Share, - Delete, -} - -impl TryFrom for CalendarRight { - type Error = Acl; - - fn try_from(value: Acl) -> Result { - match value { - Acl::SchedulingReadFreeBusy => Ok(CalendarRight::ReadFreeBusy), - Acl::ReadItems => Ok(CalendarRight::ReadItems), - Acl::Modify => Ok(CalendarRight::WriteAll), - Acl::ModifyItemsOwn => Ok(CalendarRight::WriteOwn), - Acl::ModifyPrivateProperties => Ok(CalendarRight::UpdatePrivate), - Acl::SchedulingReply => Ok(CalendarRight::RSVP), - Acl::Administer => Ok(CalendarRight::Share), - Acl::Delete => Ok(CalendarRight::Delete), - _ => Err(value), - } - } -} - -impl From for Acl { - fn from(value: CalendarRight) -> Self { - match value { - CalendarRight::ReadFreeBusy => Acl::SchedulingReadFreeBusy, - CalendarRight::ReadItems => Acl::ReadItems, - CalendarRight::WriteAll => Acl::Modify, - CalendarRight::WriteOwn => Acl::ModifyItemsOwn, - CalendarRight::UpdatePrivate => Acl::ModifyPrivateProperties, - CalendarRight::RSVP => Acl::SchedulingReply, - CalendarRight::Share => Acl::Administer, - CalendarRight::Delete => Acl::Delete, - } - } -} - impl Calendar { - pub fn preferences(&self, account_id: u32) -> &CalendarPreferences { + pub fn preferences(&self, access_token: &AccessToken) -> &CalendarPreferences { if self.preferences.len() == 1 { &self.preferences[0] } else { + let account_id = access_token.primary_id(); self.preferences .iter() .find(|p| p.account_id == account_id) @@ -211,10 +168,11 @@ impl Calendar { } } - pub fn preferences_mut(&mut self, account_id: u32) -> &mut CalendarPreferences { + pub fn preferences_mut(&mut self, access_token: &AccessToken) -> &mut CalendarPreferences { if self.preferences.len() == 1 { &mut self.preferences[0] } else { + let account_id = access_token.primary_id(); let idx = self .preferences .iter() @@ -226,10 +184,11 @@ impl Calendar { } impl ArchivedCalendar { - pub fn preferences(&self, account_id: u32) -> &ArchivedCalendarPreferences { + pub fn preferences(&self, access_token: &AccessToken) -> &ArchivedCalendarPreferences { if self.preferences.len() == 1 { &self.preferences[0] } else { + let account_id = access_token.primary_id(); self.preferences .iter() .find(|p| p.account_id == account_id) diff --git a/crates/groupware/src/contact/index.rs b/crates/groupware/src/contact/index.rs index e295542f..b032cfeb 100644 --- a/crates/groupware/src/contact/index.rs +++ b/crates/groupware/src/contact/index.rs @@ -21,8 +21,14 @@ impl IndexableObject for AddressBook { }, IndexValue::Quota { used: self.dead_properties.size() as u32 - + self.display_name.as_ref().map_or(0, |n| n.len() as u32) - + self.description.as_ref().map_or(0, |n| n.len() as u32) + + self + .preferences + .iter() + .map(|p| { + p.name.len() as u32 + + p.description.as_ref().map_or(0, |n| n.len() as u32) + }) + .sum::() + self.name.len() as u32, }, IndexValue::LogContainer { @@ -46,8 +52,14 @@ impl IndexableObject for &ArchivedAddressBook { }, IndexValue::Quota { used: self.dead_properties.size() as u32 - + self.display_name.as_ref().map_or(0, |n| n.len() as u32) - + self.description.as_ref().map_or(0, |n| n.len() as u32) + + self + .preferences + .iter() + .map(|p| { + p.name.len() as u32 + + p.description.as_ref().map_or(0, |n| n.len() as u32) + }) + .sum::() + self.name.len() as u32, }, IndexValue::LogContainer { diff --git a/crates/groupware/src/contact/mod.rs b/crates/groupware/src/contact/mod.rs index e5c659b0..8d73f6a2 100644 --- a/crates/groupware/src/contact/mod.rs +++ b/crates/groupware/src/contact/mod.rs @@ -8,9 +8,9 @@ pub mod index; pub mod storage; use calcard::vcard::VCard; -use common::DavName; +use common::{DavName, auth::AccessToken}; use dav_proto::schema::request::DeadProperty; -use types::acl::{Acl, AclGrant}; +use types::acl::AclGrant; #[derive( rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq, @@ -18,10 +18,7 @@ use types::acl::{Acl, AclGrant}; #[rkyv(derive(Debug))] pub struct AddressBook { pub name: String, - pub display_name: Option, - pub description: Option, - pub sort_order: u32, - pub is_default: bool, + pub preferences: Vec, pub subscribers: Vec, pub dead_properties: DeadProperty, pub acls: Vec, @@ -29,11 +26,16 @@ pub struct AddressBook { pub modified: i64, } -pub enum AddressBookRight { - Read, - Write, - Share, - Delete, +#[derive( + rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq, +)] +#[rkyv(derive(Debug))] +pub struct AddressBookPreferences { + pub account_id: u32, + pub name: String, + pub description: Option, + pub sort_order: u32, + pub is_default: bool, } #[derive( @@ -49,27 +51,46 @@ pub struct ContactCard { pub size: u32, } -impl TryFrom for AddressBookRight { - type Error = Acl; +impl AddressBook { + pub fn preferences(&self, access_token: &AccessToken) -> &AddressBookPreferences { + if self.preferences.len() == 1 { + &self.preferences[0] + } else { + let account_id = access_token.primary_id(); + self.preferences + .iter() + .find(|p| p.account_id == account_id) + .or_else(|| self.preferences.first()) + .unwrap() + } + } - fn try_from(value: Acl) -> Result { - match value { - Acl::Read => Ok(AddressBookRight::Read), - Acl::Modify => Ok(AddressBookRight::Write), - Acl::Administer => Ok(AddressBookRight::Share), - Acl::Delete => Ok(AddressBookRight::Delete), - _ => Err(value), + pub fn preferences_mut(&mut self, access_token: &AccessToken) -> &mut AddressBookPreferences { + if self.preferences.len() == 1 { + &mut self.preferences[0] + } else { + let account_id = access_token.primary_id(); + let idx = self + .preferences + .iter() + .position(|p| p.account_id == account_id) + .unwrap_or(0); + &mut self.preferences[idx] } } } -impl From for Acl { - fn from(value: AddressBookRight) -> Self { - match value { - AddressBookRight::Read => Acl::Read, - AddressBookRight::Write => Acl::Modify, - AddressBookRight::Share => Acl::Administer, - AddressBookRight::Delete => Acl::Delete, +impl ArchivedAddressBook { + pub fn preferences(&self, access_token: &AccessToken) -> &ArchivedAddressBookPreferences { + if self.preferences.len() == 1 { + &self.preferences[0] + } else { + let account_id = access_token.primary_id(); + self.preferences + .iter() + .find(|p| p.account_id == account_id) + .or_else(|| self.preferences.first()) + .unwrap() } } } diff --git a/crates/migration/src/lib.rs b/crates/migration/src/lib.rs index d16a3dec..8d86360f 100644 --- a/crates/migration/src/lib.rs +++ b/crates/migration/src/lib.rs @@ -46,6 +46,24 @@ const LOCK_WAIT_TIME_ACCOUNT: u64 = 3 * 60; const LOCK_WAIT_TIME_CORE: u64 = 5 * 60; const LOCK_RETRY_TIME: Duration = Duration::from_secs(30); +/* + +pub struct AddressBook { + pub name: String, + pub display_name: Option, + pub description: Option, + pub sort_order: u32, + pub is_default: bool, + pub subscribers: Vec, + pub dead_properties: DeadProperty, + pub acls: Vec, + pub created: i64, + pub modified: i64, +} + + +*/ + pub async fn try_migrate(server: &Server) -> trc::Result<()> { if let Some(version) = std::env::var("FORCE_MIGRATE_QUEUE") .ok()