From 2dc0776109066e4610935ccc44f24e84cc2b7916 Mon Sep 17 00:00:00 2001 From: mdecimus <11444311+mdecimus@users.noreply.github.com> Date: Sun, 8 Feb 2026 19:36:26 +0100 Subject: [PATCH] Authenticate using registry - part 5 --- Cargo.lock | 3 + crates/common/src/auth/access_token.rs | 484 +++++++++++------- crates/common/src/auth/authentication.rs | 24 +- crates/common/src/auth/mod.rs | 54 +- crates/common/src/auth/oauth/token.rs | 2 +- crates/common/src/cache/directory.rs | 95 +++- crates/common/src/cache/mod.rs | 43 +- crates/common/src/ipc.rs | 6 + crates/common/src/lib.rs | 12 +- crates/common/src/storage/index.rs | 11 +- crates/dav/Cargo.toml | 1 + crates/dav/src/calendar/copy_move.rs | 88 +++- crates/dav/src/calendar/delete.rs | 15 +- crates/dav/src/calendar/freebusy.rs | 2 +- crates/dav/src/calendar/get.rs | 2 +- crates/dav/src/calendar/mkcol.rs | 13 +- crates/dav/src/calendar/proppatch.rs | 22 +- crates/dav/src/calendar/query.rs | 57 ++- crates/dav/src/calendar/scheduling.rs | 24 +- crates/dav/src/calendar/update.rs | 46 +- crates/dav/src/card/copy_move.rs | 99 +++- crates/dav/src/card/delete.rs | 10 +- crates/dav/src/card/get.rs | 2 +- crates/dav/src/card/mkcol.rs | 15 +- crates/dav/src/card/proppatch.rs | 22 +- crates/dav/src/card/query.rs | 2 +- crates/dav/src/card/update.rs | 34 +- crates/dav/src/common/acl.rs | 23 +- crates/dav/src/common/lock.rs | 6 +- crates/dav/src/common/propfind.rs | 95 ++-- crates/dav/src/common/uri.rs | 20 +- crates/dav/src/file/copy_move.rs | 62 ++- crates/dav/src/file/delete.rs | 13 +- crates/dav/src/file/get.rs | 2 +- crates/dav/src/file/mkcol.rs | 2 +- crates/dav/src/file/proppatch.rs | 8 +- crates/dav/src/file/update.rs | 23 +- crates/dav/src/principal/mod.rs | 6 +- crates/dav/src/principal/propfind.rs | 160 +++--- crates/dav/src/principal/propsearch.rs | 22 +- crates/dav/src/request.rs | 108 ++-- crates/email/Cargo.toml | 1 + crates/email/src/cache/mod.rs | 61 ++- crates/email/src/mailbox/destroy.rs | 4 +- crates/email/src/mailbox/manage.rs | 4 +- crates/email/src/message/copy.rs | 24 +- crates/email/src/message/delete.rs | 53 +- crates/email/src/message/delivery.rs | 16 +- crates/email/src/message/ingest.rs | 20 +- crates/email/src/sieve/delete.rs | 2 +- crates/email/src/sieve/ingest.rs | 29 +- crates/groupware/src/cache/calcard.rs | 40 +- crates/groupware/src/cache/file.rs | 16 +- crates/groupware/src/cache/mod.rs | 113 ++-- crates/groupware/src/calendar/itip.rs | 54 +- crates/groupware/src/calendar/storage.rs | 94 ++-- crates/groupware/src/contact/storage.rs | 50 +- crates/groupware/src/file/storage.rs | 28 +- .../groupware/src/scheduling/event_cancel.rs | 2 +- .../groupware/src/scheduling/event_create.rs | 2 +- .../groupware/src/scheduling/event_update.rs | 2 +- crates/groupware/src/scheduling/mod.rs | 6 +- crates/groupware/src/scheduling/snapshot.rs | 2 +- crates/http-proto/src/lib.rs | 5 +- crates/http/src/management/reload.rs | 2 +- crates/http/src/management/settings.rs | 2 +- crates/http/src/management/stores.rs | 2 +- crates/http/src/request.rs | 2 +- crates/imap/src/op/append.rs | 2 +- crates/imap/src/op/authenticate.rs | 2 +- crates/imap/src/op/capability.rs | 2 +- crates/imap/src/op/copy_move.rs | 2 +- crates/imap/src/op/create.rs | 2 +- crates/imap/src/op/delete.rs | 2 +- crates/imap/src/op/enable.rs | 2 +- crates/imap/src/op/expunge.rs | 2 +- crates/imap/src/op/fetch.rs | 2 +- crates/imap/src/op/idle.rs | 2 +- crates/imap/src/op/list.rs | 2 +- crates/imap/src/op/namespace.rs | 2 +- crates/imap/src/op/quota.rs | 2 +- crates/imap/src/op/rename.rs | 2 +- crates/imap/src/op/search.rs | 2 +- crates/imap/src/op/select.rs | 2 +- crates/imap/src/op/status.rs | 2 +- crates/imap/src/op/store.rs | 2 +- crates/imap/src/op/subscribe.rs | 2 +- crates/imap/src/op/thread.rs | 2 +- crates/jmap/src/addressbook/get.rs | 2 +- crates/jmap/src/addressbook/set.rs | 2 +- crates/jmap/src/api/auth.rs | 2 +- crates/jmap/src/api/session.rs | 2 +- crates/jmap/src/blob/copy.rs | 2 +- crates/jmap/src/blob/upload.rs | 2 +- crates/jmap/src/calendar/get.rs | 2 +- crates/jmap/src/calendar/set.rs | 2 +- crates/jmap/src/calendar_event/copy.rs | 4 +- crates/jmap/src/calendar_event/get.rs | 2 +- crates/jmap/src/calendar_event/query.rs | 2 +- crates/jmap/src/calendar_event/set.rs | 8 +- crates/jmap/src/changes/get.rs | 2 +- crates/jmap/src/contact/copy.rs | 4 +- crates/jmap/src/contact/get.rs | 2 +- crates/jmap/src/contact/query.rs | 2 +- crates/jmap/src/contact/set.rs | 6 +- crates/jmap/src/file/get.rs | 2 +- crates/jmap/src/file/query.rs | 2 +- crates/jmap/src/file/set.rs | 2 +- crates/jmap/src/principal/availability.rs | 4 +- crates/jmap/src/vacation/set.rs | 2 +- crates/managesieve/src/op/authenticate.rs | 2 +- crates/managesieve/src/op/checkscript.rs | 2 +- crates/managesieve/src/op/deletescript.rs | 2 +- crates/managesieve/src/op/getscript.rs | 2 +- crates/managesieve/src/op/havespace.rs | 2 +- crates/managesieve/src/op/listscripts.rs | 2 +- crates/managesieve/src/op/mod.rs | 2 +- crates/managesieve/src/op/putscript.rs | 6 +- crates/managesieve/src/op/renamescript.rs | 2 +- crates/managesieve/src/op/setactive.rs | 2 +- crates/pop3/src/op/authenticate.rs | 2 +- crates/pop3/src/op/delete.rs | 2 +- crates/pop3/src/op/fetch.rs | 2 +- crates/pop3/src/op/list.rs | 2 +- crates/registry/src/schema/mod.rs | 12 +- crates/services/src/task_manager/alarm.rs | 2 +- crates/smtp/src/inbound/auth.rs | 2 +- crates/smtp/src/outbound/dane/dnssec.rs | 2 +- crates/smtp/src/scripts/event_loop.rs | 2 +- crates/spam-filter/Cargo.toml | 1 + crates/spam-filter/src/analysis/classifier.rs | 2 +- crates/spam-filter/src/analysis/domain.rs | 2 +- crates/spam-filter/src/analysis/ip.rs | 2 +- crates/spam-filter/src/analysis/mod.rs | 10 +- crates/spam-filter/src/analysis/rules.rs | 2 +- crates/spam-filter/src/analysis/score.rs | 2 +- crates/spam-filter/src/analysis/url.rs | 2 +- crates/spam-filter/src/modules/classifier.rs | 23 +- crates/spam-filter/src/modules/dnsbl.rs | 26 +- crates/spam-filter/src/modules/expression.rs | 61 ++- crates/spam-filter/src/modules/pyzor.rs | 2 +- crates/store/src/registry/mod.rs | 33 +- crates/store/src/registry/query.rs | 198 +++++++ crates/utils/src/cache.rs | 8 + 144 files changed, 1828 insertions(+), 1072 deletions(-) create mode 100644 crates/store/src/registry/query.rs diff --git a/Cargo.lock b/Cargo.lock index 43caee0f..59ba803a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1578,6 +1578,7 @@ dependencies = [ "http_proto", "hyper 1.8.1", "percent-encoding", + "registry", "rkyv", "store", "trc", @@ -2023,6 +2024,7 @@ dependencies = [ "rasn", "rasn-cms", "rasn-pkix", + "registry", "rkyv", "rsa", "sequoia-openpgp", @@ -7280,6 +7282,7 @@ dependencies = [ "mail-send", "nlp", "psl", + "registry", "reqwest", "rkyv", "serde", diff --git a/crates/common/src/auth/access_token.rs b/crates/common/src/auth/access_token.rs index b6b74134..e38f277b 100644 --- a/crates/common/src/auth/access_token.rs +++ b/crates/common/src/auth/access_token.rs @@ -8,7 +8,8 @@ use super::AccessToken; use crate::{ Server, auth::{ - AccessScope, AccessTo, AccessTokenInner, FALLBACK_ADMIN_ID, Permissions, PermissionsGroup, + AccessScope, AccessTo, AccessTokenInner, AccountTenantIds, FALLBACK_ADMIN_ID, Permissions, + PermissionsGroup, }, network::limiter::{ConcurrencyLimiter, LimiterResult}, }; @@ -38,178 +39,281 @@ impl Server { revision: u64, revision_account: u64, ) -> trc::Result { - // Calculate effective permissions - let (mut permissions, roles) = match account.permissions { - structs::Permissions::Inherit => { - (PermissionsGroup::default(), account.role_ids.as_slice()) - } - structs::Permissions::Merge(permissions) => ( - PermissionsGroup::from(permissions), - account.role_ids.as_slice(), - ), - structs::Permissions::Replace(permissions) => { - (PermissionsGroup::from(permissions), &[][..]) - } - }; - if !roles.is_empty() { - permissions = self - .add_role_permissions(permissions, roles.into_iter().map(|v| v.id() as u32)) - .await - .caused_by(trc::location!())? - } - - let tenant_id = account.member_tenant_id.map(|t| t.id() as u32); - - // SPDX-SnippetBegin - // SPDX-FileCopyrightText: 2020 Stalwart Labs LLC - // SPDX-License-Identifier: LicenseRef-SEL - - #[cfg(feature = "enterprise")] - { - if let Some(tenant_id) = tenant_id { - if self.is_enterprise_edition() { - // Limit tenant permissions - let tenant = self.tenant(tenant_id).await.caused_by(trc::location!())?; - let (mut tenant_permissions, tenant_roles) = - if let Some(permissions) = &tenant.permissions { - if permissions.merge { - ((**permissions).clone(), tenant.id_roles.as_slice()) - } else { - ((**permissions).clone(), &[][..]) - } - } else { - (PermissionsGroup::default(), tenant.id_roles.as_slice()) - }; - if !tenant_roles.is_empty() { - tenant_permissions = self - .add_role_permissions(tenant_permissions, tenant_roles.iter().copied()) - .await - .caused_by(trc::location!())? + match account { + Account::User(account) => { + // Calculate effective permissions + let (mut permissions, roles) = match account.permissions { + structs::Permissions::Inherit => { + (PermissionsGroup::default(), account.role_ids.as_slice()) } - - permissions.restrict(&tenant_permissions); - } else { - // Enterprise edition downgrade, remove any tenant administrator permissions - permissions.restrict(&PermissionsGroup::user()); + structs::Permissions::Merge(permissions) => ( + PermissionsGroup::from(permissions), + account.role_ids.as_slice(), + ), + structs::Permissions::Replace(permissions) => { + (PermissionsGroup::from(permissions), &[][..]) + } + }; + if !roles.is_empty() { + permissions = self + .add_role_permissions(permissions, roles.iter().map(|v| v.id() as u32)) + .await + .caused_by(trc::location!())? } - } - } - // SPDX-SnippetEnd + let tenant_id = account.member_tenant_id.map(|t| t.id() as u32); - let can_impersonate = permissions.enabled.get(Permission::Impersonate as usize) - && !permissions.disabled.get(Permission::Impersonate as usize); - let member_of = account - .role_ids - .iter() - .map(|m| m.id() as u32) - .collect::>(); - let mut access_to: Vec = Vec::new(); - for grant_account_id in [account_id].into_iter().chain(member_of.iter().copied()) { - for acl_item in self - .store() - .acl_query(AclQuery::HasAccess { grant_account_id }) - .await - .caused_by(trc::location!())? - { - if acl_item.to_account_id != account_id - && !member_of.contains(&acl_item.to_account_id) - && !can_impersonate + // SPDX-SnippetBegin + // SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + // SPDX-License-Identifier: LicenseRef-SEL + + #[cfg(feature = "enterprise")] { - let acl = Bitmap::::from(acl_item.permissions); - let collection = acl_item.to_collection; - if !collection.is_valid() { - return Err(trc::StoreEvent::DataCorruption - .ctx(trc::Key::Reason, "Corrupted collection found in ACL key.") - .details(format!("{acl_item:?}")) - .account_id(grant_account_id) - .caused_by(trc::location!())); - } + if let Some(tenant_id) = tenant_id { + if self.is_enterprise_edition() { + // Limit tenant permissions + let tenant = + self.tenant(tenant_id).await.caused_by(trc::location!())?; + let (mut tenant_permissions, tenant_roles) = + if let Some(permissions) = &tenant.permissions { + if permissions.merge { + ((**permissions).clone(), tenant.id_roles.as_slice()) + } else { + ((**permissions).clone(), &[][..]) + } + } else { + (PermissionsGroup::default(), tenant.id_roles.as_slice()) + }; + if !tenant_roles.is_empty() { + tenant_permissions = self + .add_role_permissions( + tenant_permissions, + tenant_roles.iter().copied(), + ) + .await + .caused_by(trc::location!())? + } - let mut collections: Bitmap = Bitmap::new(); - if acl.contains(Acl::Read) { - collections.insert(collection); - } - if acl.contains(Acl::ReadItems) - && let Some(child_col) = collection.child_collection() - { - collections.insert(child_col); - } - - if !collections.is_empty() { - if let Some(idx) = access_to - .iter() - .position(|a| a.account_id == acl_item.to_account_id) - { - access_to[idx].collections.union(&collections); + permissions.restrict(&tenant_permissions); } else { - access_to.push(AccessTo { - account_id: acl_item.to_account_id, - collections, - }); + // Enterprise edition downgrade, remove any tenant administrator permissions + permissions.restrict(&PermissionsGroup::user()); } } } + + // SPDX-SnippetEnd + + let can_impersonate = permissions.enabled.get(Permission::Impersonate as usize) + && !permissions.disabled.get(Permission::Impersonate as usize); + let member_of = account + .member_group_ids + .iter() + .map(|m| m.id() as u32) + .collect::>(); + let mut access_to: Vec = Vec::new(); + for grant_account_id in [account_id].into_iter().chain(member_of.iter().copied()) { + for acl_item in self + .store() + .acl_query(AclQuery::HasAccess { grant_account_id }) + .await + .caused_by(trc::location!())? + { + if acl_item.to_account_id != account_id + && !member_of.contains(&acl_item.to_account_id) + && !can_impersonate + { + let acl = Bitmap::::from(acl_item.permissions); + let collection = acl_item.to_collection; + if !collection.is_valid() { + return Err(trc::StoreEvent::DataCorruption + .ctx(trc::Key::Reason, "Corrupted collection found in ACL key.") + .details(format!("{acl_item:?}")) + .account_id(grant_account_id) + .caused_by(trc::location!())); + } + + let mut collections: Bitmap = Bitmap::new(); + if acl.contains(Acl::Read) { + collections.insert(collection); + } + if acl.contains(Acl::ReadItems) + && let Some(child_col) = collection.child_collection() + { + collections.insert(child_col); + } + + if !collections.is_empty() { + if let Some(idx) = access_to + .iter() + .position(|a| a.account_id == acl_item.to_account_id) + { + access_to[idx].collections.union(&collections); + } else { + access_to.push(AccessTo { + account_id: acl_item.to_account_id, + collections, + }); + } + } + } + } + } + + let now = now(); + let credential_scopes = account + .credentials + .into_iter() + .filter_map(|pass| { + let expires_at = pass + .expires_at + .map(|v| v.timestamp() as u64) + .unwrap_or(u64::MAX); + if expires_at > now { + let permissions = match pass.permissions { + structs::Permissions::Inherit => permissions.clone().finalize(), + structs::Permissions::Merge(merge) => { + let mut permissions = permissions.clone(); + permissions.union(&PermissionsGroup::from(merge)); + permissions.finalize() + } + structs::Permissions::Replace(replace) => { + PermissionsGroup::from(replace).finalize() + } + }; + Some(AccessScope { + credential_id: pass.credential_id as u32, + permissions, + expires_at, + }) + } else { + None + } + }) + .collect::>(); + + Ok(AccessTokenInner { + concurrent_imap_requests: self + .core + .imap + .rate_concurrent + .map(ConcurrencyLimiter::new), + concurrent_http_requests: self + .core + .jmap + .request_max_concurrent + .map(ConcurrencyLimiter::new), + concurrent_uploads: self + .core + .jmap + .upload_max_concurrent + .map(ConcurrencyLimiter::new), + obj_size: 0, + revision, + revision_account, + account_id, + tenant_id, + member_of, + access_to: access_to.into_boxed_slice(), + scopes: [AccessScope::new(permissions.finalize(), u32::MAX)] + .into_iter() + .chain(credential_scopes) + .collect::>(), + } + .update_size()) + } + Account::Group(account) => { + // Calculate effective permissions + let (mut permissions, roles) = match account.permissions { + structs::Permissions::Inherit => { + (PermissionsGroup::default(), account.role_ids.as_slice()) + } + structs::Permissions::Merge(permissions) => ( + PermissionsGroup::from(permissions), + account.role_ids.as_slice(), + ), + structs::Permissions::Replace(permissions) => { + (PermissionsGroup::from(permissions), &[][..]) + } + }; + if !roles.is_empty() { + permissions = self + .add_role_permissions(permissions, roles.iter().map(|v| v.id() as u32)) + .await + .caused_by(trc::location!())? + } + + let tenant_id = account.member_tenant_id.map(|t| t.id() as u32); + + // SPDX-SnippetBegin + // SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + // SPDX-License-Identifier: LicenseRef-SEL + + #[cfg(feature = "enterprise")] + { + if let Some(tenant_id) = tenant_id { + if self.is_enterprise_edition() { + // Limit tenant permissions + let tenant = + self.tenant(tenant_id).await.caused_by(trc::location!())?; + let (mut tenant_permissions, tenant_roles) = + if let Some(permissions) = &tenant.permissions { + if permissions.merge { + ((**permissions).clone(), tenant.id_roles.as_slice()) + } else { + ((**permissions).clone(), &[][..]) + } + } else { + (PermissionsGroup::default(), tenant.id_roles.as_slice()) + }; + if !tenant_roles.is_empty() { + tenant_permissions = self + .add_role_permissions( + tenant_permissions, + tenant_roles.iter().copied(), + ) + .await + .caused_by(trc::location!())? + } + + permissions.restrict(&tenant_permissions); + } else { + // Enterprise edition downgrade, remove any tenant administrator permissions + permissions.restrict(&PermissionsGroup::user()); + } + } + } + + // SPDX-SnippetEnd + + Ok(AccessTokenInner { + concurrent_imap_requests: self + .core + .imap + .rate_concurrent + .map(ConcurrencyLimiter::new), + concurrent_http_requests: self + .core + .jmap + .request_max_concurrent + .map(ConcurrencyLimiter::new), + concurrent_uploads: self + .core + .jmap + .upload_max_concurrent + .map(ConcurrencyLimiter::new), + obj_size: 0, + revision, + revision_account, + account_id, + tenant_id, + member_of: Default::default(), + access_to: Default::default(), + scopes: Box::new([AccessScope::new(permissions.finalize(), u32::MAX)]), + } + .update_size()) } } - - let now = now(); - let credential_scopes = account - .credentials - .into_iter() - .filter_map(|pass| { - let expires_at = pass - .expires_at - .map(|v| v.timestamp() as u64) - .unwrap_or(u64::MAX); - if expires_at > now { - let permissions = match pass.permissions { - structs::Permissions::Inherit => permissions.clone().finalize(), - structs::Permissions::Merge(merge) => { - let mut permissions = permissions.clone(); - permissions.union(&PermissionsGroup::from(merge)); - permissions.finalize() - } - structs::Permissions::Replace(replace) => { - PermissionsGroup::from(replace).finalize() - } - }; - Some(AccessScope { - credential_id: pass.credential_id as u32, - permissions, - expires_at, - }) - } else { - None - } - }) - .collect::>(); - - Ok(AccessTokenInner { - concurrent_imap_requests: self.core.imap.rate_concurrent.map(ConcurrencyLimiter::new), - concurrent_http_requests: self - .core - .jmap - .request_max_concurrent - .map(ConcurrencyLimiter::new), - concurrent_uploads: self - .core - .jmap - .upload_max_concurrent - .map(ConcurrencyLimiter::new), - obj_size: 0, - revision, - revision_account, - account_id, - tenant_id, - member_of, - access_to: access_to.into_boxed_slice(), - scopes: [AccessScope::new(permissions.finalize(), u32::MAX)] - .into_iter() - .chain(credential_scopes) - .collect::>(), - } - .update_size()) } pub async fn access_token(&self, account_id: u32) -> trc::Result> { @@ -370,7 +474,7 @@ impl AccessToken { self.inner .scopes .get(self.scope_idx) - .map_or(false, |scope| scope.permissions.get(permission as usize)) + .is_some_and(|scope| scope.permissions.get(permission as usize)) } pub fn is_valid(&self) -> bool { @@ -378,7 +482,7 @@ impl AccessToken { self.inner .scopes .get(self.scope_idx) - .map_or(false, |scope| scope.expires_at > now()) + .is_some_and(|scope| scope.expires_at > now()) } pub fn assert_has_permissions(self, permissions: &[Permission]) -> trc::Result { @@ -405,6 +509,17 @@ impl AccessToken { } } + pub fn enforce_permission(&self, permission: Permission) -> trc::Result<()> { + if self.has_permission(permission) { + Ok(()) + } else { + Err(trc::SecurityEvent::Unauthorized + .into_err() + .details(permission.as_str()) + .account_id(self.account_id())) + } + } + pub fn permissions(&self) -> Vec { const USIZE_BITS: usize = std::mem::size_of::() * 8; const USIZE_MASK: u32 = USIZE_BITS as u32 - 1; @@ -492,6 +607,13 @@ impl AccessToken { .map_or(LimiterResult::Disabled, |limiter| limiter.is_allowed()) } + pub fn account_tenant_ids(&self) -> AccountTenantIds { + AccountTenantIds { + account_id: self.account_id(), + tenant_id: self.tenant_id(), + } + } + pub fn new_admin() -> AccessToken { AccessToken { scope_idx: 0, @@ -527,13 +649,6 @@ impl AccessTokenInner { } } - pub fn with_scopes(self, scopes: impl IntoIterator) -> Self { - Self { - scopes: scopes.into_iter().collect(), - ..self - } - } - pub fn with_tenant_id(mut self, tenant_id: Option) -> Self { self.tenant_id = tenant_id; self @@ -547,10 +662,6 @@ impl AccessTokenInner { as u64; self } - - pub fn is_fresh(&self, account: &Account) -> bool { - self.member_of.len() == account.member_group_ids.len() - } } impl AccessScope { @@ -570,14 +681,25 @@ impl AccessScope { fn hash_account(account: &Account) -> u64 { let mut s = AHasher::default(); - account.member_tenant_id.hash(&mut s); - account.role_ids.hash(&mut s); - hash_permissions(&mut s, &account.permissions); - for credential in &account.credentials { - credential.credential_id.hash(&mut s); - credential.expires_at.hash(&mut s); - hash_permissions(&mut s, &credential.permissions); + + match account { + Account::User(account) => { + account.member_tenant_id.hash(&mut s); + account.role_ids.hash(&mut s); + hash_permissions(&mut s, &account.permissions); + for credential in &account.credentials { + credential.credential_id.hash(&mut s); + credential.expires_at.hash(&mut s); + hash_permissions(&mut s, &credential.permissions); + } + } + Account::Group(account) => { + account.member_tenant_id.hash(&mut s); + account.role_ids.hash(&mut s); + hash_permissions(&mut s, &account.permissions); + } } + s.finish() } diff --git a/crates/common/src/auth/authentication.rs b/crates/common/src/auth/authentication.rs index 7fa8b8c3..bcef094a 100644 --- a/crates/common/src/auth/authentication.rs +++ b/crates/common/src/auth/authentication.rs @@ -74,9 +74,7 @@ impl Server { return if verify_secret_hash(fallback_hash, secret.as_bytes()).await? { if username.is_master() { let address = username.account().address(); - if let Some(EmailCache::Account(account_id)) = - self.email(address).await? - { + if let Some(account_id) = self.account_id(address).await? { trc::event!( Auth(trc::AuthEvent::Success), AccountName = address.to_string(), @@ -115,9 +113,7 @@ impl Server { // Authenticate app passwords if let Some(app_pass) = AppPassword::parse(secret) { let account_name = auth_as.address(); - return if let Some(EmailCache::Account(account_id)) = - self.email(account_name).await? - { + return if let Some(account_id) = self.account_id(account_name).await? { self.validate_credential( account_id, app_pass.credential_id, @@ -144,12 +140,13 @@ impl Server { let token = if let Some(directory) = directory { let directory_account = directory.authenticate(&req.credentials).await?; is_alias_login = directory_account.email != address; - self.update_registry(directory_account).await - } else if let Some(EmailCache::Account(account_id)) = self.email(address).await? { + self.synchronize_directory(directory_account).await + } else if let Some(account_id) = self.account_id(address).await? { if let Some(account) = self .registry() .object::(account_id) .await? + .and_then(|account| account.into_user()) { if verify_mfa_secret_hash( account.otp_auth.as_deref(), @@ -200,7 +197,7 @@ impl Server { ])?; let address = username.account().address(); let master_address = username.account().address(); - if let Some(EmailCache::Account(account_id)) = self.email(address).await? { + if let Some(account_id) = self.account_id(address).await? { trc::event!( Auth(trc::AuthEvent::Success), AccountName = address.to_string(), @@ -256,7 +253,7 @@ impl Server { { match directory.authenticate(&req.credentials).await { Ok(result) => { - return self.update_registry(result).await; + return self.synchronize_directory(result).await; } Err(err) => { if !err.matches(trc::EventType::Auth(trc::AuthEvent::Failed)) { @@ -288,6 +285,7 @@ impl Server { .registry() .object::(account_id) .await? + .and_then(|account| account.into_user()) { // Find credential by credential_id for credential in account.credentials.iter() { @@ -328,7 +326,9 @@ impl Server { } ); - let token = self.access_token_from_account(account_id, account).await?; + let token = self + .access_token_from_account(account_id, structs::Account::User(account)) + .await?; let scope_idx = token .scopes .iter() @@ -408,7 +408,7 @@ impl Server { Ok(self.get_default_directory()) } - async fn update_registry(&self, account: directory::Account) -> trc::Result { + async fn synchronize_directory(&self, account: directory::Account) -> trc::Result { todo!() } } diff --git a/crates/common/src/auth/mod.rs b/crates/common/src/auth/mod.rs index 3da8c9c3..7d1a4f6d 100644 --- a/crates/common/src/auth/mod.rs +++ b/crates/common/src/auth/mod.rs @@ -38,7 +38,6 @@ pub type Permissions = Bitset; #[derive(Debug, Clone, Copy)] pub enum EmailCache { Account(u32), - Group(u32), MailingList(u32), } @@ -69,6 +68,7 @@ pub struct AccountCache { pub quota_objects: Option>, pub description: Option>, pub locale: Locale, + pub is_user: bool, } #[derive(Debug, Clone)] @@ -113,32 +113,45 @@ pub struct AccessToken { #[derive(Debug, Default)] pub struct AccessTokenInner { - pub account_id: u32, - pub tenant_id: Option, - pub member_of: TinyVec<[u32; 3]>, - pub access_to: Box<[AccessTo]>, - pub scopes: Box<[AccessScope]>, - pub concurrent_http_requests: Option, - pub concurrent_imap_requests: Option, - pub concurrent_uploads: Option, - pub revision_account: u64, - pub revision: u64, - pub obj_size: u64, + pub(crate) account_id: u32, + pub(crate) tenant_id: Option, + pub(crate) member_of: TinyVec<[u32; 3]>, + pub(crate) access_to: Box<[AccessTo]>, + pub(crate) scopes: Box<[AccessScope]>, + pub(crate) concurrent_http_requests: Option, + pub(crate) concurrent_imap_requests: Option, + pub(crate) concurrent_uploads: Option, + pub(crate) revision_account: u64, + pub(crate) revision: u64, + pub(crate) obj_size: u64, } #[derive(Debug, Default, Hash)] -struct AccessScope { +pub(crate) struct AccessScope { pub permissions: Permissions, pub credential_id: u32, pub expires_at: u64, } #[derive(Debug, Default, Hash, PartialEq, Eq)] -pub struct AccessTo { +pub(crate) struct AccessTo { pub account_id: u32, pub collections: Bitmap, } +#[derive(Clone)] +pub struct AccountInfo { + pub(crate) account_id: u32, + pub(crate) account: Arc, + pub(crate) member_of: Vec>, +} + +#[derive(Clone, Copy)] +pub struct AccountTenantIds { + pub account_id: u32, + pub tenant_id: Option, +} + pub struct AuthRequest { credentials: Credentials, session_id: u64, @@ -203,3 +216,16 @@ impl CacheItemWeight for PermissionsGroup { std::mem::size_of::() as u64 } } + +pub trait BuildAccessToken { + fn build(self) -> AccessToken; +} + +impl BuildAccessToken for Arc { + fn build(self) -> AccessToken { + AccessToken { + scope_idx: 0, + inner: self, + } + } +} diff --git a/crates/common/src/auth/oauth/token.rs b/crates/common/src/auth/oauth/token.rs index c36eaacf..05c81646 100644 --- a/crates/common/src/auth/oauth/token.rs +++ b/crates/common/src/auth/oauth/token.rs @@ -221,7 +221,7 @@ impl Server { .object::(account_id) .await .caused_by(trc::location!())? - .map(|account| account.secret) + .and_then(|account| account.into_user().map(|account| account.secret)) .ok_or_else(|| { trc::AuthEvent::Error .into_err() diff --git a/crates/common/src/cache/directory.rs b/crates/common/src/cache/directory.rs index aa459ad7..5682aeb8 100644 --- a/crates/common/src/cache/directory.rs +++ b/crates/common/src/cache/directory.rs @@ -4,9 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use store::write::now; + use crate::{ Server, - auth::{AccountCache, DomainCache, EmailCache, RoleCache, TenantCache}, + auth::{ + AccountCache, AccountInfo, AccountTenantIds, DomainCache, EmailCache, RoleCache, + TemporaryAddress, TenantCache, + }, config::smtp::auth::DkimSigner, }; use std::sync::Arc; @@ -16,10 +21,6 @@ impl Server { todo!() } - pub async fn email(&self, address: &str) -> trc::Result> { - todo!() - } - pub async fn account(&self, id: u32) -> trc::Result> { /* @@ -31,6 +32,24 @@ impl Server { todo!() } + pub async fn account_id(&self, address: &str) -> trc::Result> { + todo!() + } + + pub async fn account_info(&self, id: u32) -> trc::Result { + let account = self.account(id).await?; + let mut member_of = Vec::with_capacity(account.id_member_of.len()); + for &group_id in &account.id_member_of { + member_of.push(self.account(group_id).await?); + } + + Ok(AccountInfo { + account_id: id, + account, + member_of, + }) + } + pub async fn role(&self, id: u32) -> trc::Result> { todo!() } @@ -43,3 +62,69 @@ impl Server { todo!() } } + +impl AccountInfo { + pub fn account_id(&self) -> u32 { + self.account_id + } + + pub fn name(&self) -> &str { + self.account + .addresses + .first() + .map(|s| s.as_ref()) + .unwrap_or("") + } + + pub fn description(&self) -> Option<&str> { + self.account.description.as_deref() + } + + pub fn tenant_id(&self) -> Option { + if self.account.id_tenant != u32::MAX { + Some(self.account.id_tenant) + } else { + None + } + } + + pub fn account_tenant_ids(&self) -> AccountTenantIds { + AccountTenantIds { + account_id: self.account_id, + tenant_id: self.tenant_id(), + } + } + + pub fn addresses(&self) -> impl Iterator { + let now = now(); + self.account.addresses(now).chain( + self.member_of + .iter() + .flat_map(move |member| member.addresses(now)), + ) + } + + pub fn is_user_account(&self) -> bool { + self.account.is_user + } +} + +impl AccountCache { + fn addresses(&self, now: u64) -> impl Iterator { + self.addresses.iter().map(|s| s.as_ref()).chain( + self.addresses_temporary + .iter() + .filter_map(move |a| a.validate(now)), + ) + } +} + +impl TemporaryAddress { + pub fn validate(&self, now: u64) -> Option<&str> { + if self.expires_at > now { + Some(self.address.as_ref()) + } else { + None + } + } +} diff --git a/crates/common/src/cache/mod.rs b/crates/common/src/cache/mod.rs index f44912a4..c225a897 100644 --- a/crates/common/src/cache/mod.rs +++ b/crates/common/src/cache/mod.rs @@ -4,7 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use crate::{DavResources, HttpAuthCache, MailboxCache, MessageStoreCache}; +use crate::{DavResources, HttpAuthCache, MailboxCache, MessageStoreCache, UpdateLock}; +use std::sync::atomic::{AtomicU64, Ordering}; +use tokio::sync::{Semaphore, SemaphorePermit}; use utils::cache::CacheItemWeight; pub mod directory; @@ -33,6 +35,45 @@ impl MailboxCache { } } +pub enum LockResult<'x> { + Acquired(SemaphorePermit<'x>), + Stale(SemaphorePermit<'x>), +} + +impl UpdateLock { + pub fn new() -> Self { + Self { + semaphore: Semaphore::new(1), + revision: AtomicU64::new(0), + } + } + + pub async fn acquire(&self, current_revision: u64) -> trc::Result> { + let permit = self.semaphore.acquire().await.map_err(|err| { + trc::EventType::Server(trc::ServerEvent::ThreadError) + .reason(err) + .caused_by(trc::location!()) + .details("Failed to acquire semaphore permit") + })?; + + if self.revision.load(Ordering::Acquire) == current_revision { + Ok(LockResult::Acquired(permit)) + } else { + Ok(LockResult::Stale(permit)) + } + } + + pub fn set_revision(&self, revision: u64) { + self.revision.store(revision, Ordering::Release); + } +} + +impl Default for UpdateLock { + fn default() -> Self { + Self::new() + } +} + impl CacheItemWeight for MessageStoreCache { fn weight(&self) -> u64 { self.size diff --git a/crates/common/src/ipc.rs b/crates/common/src/ipc.rs index 0b2218ea..7b3bdc75 100644 --- a/crates/common/src/ipc.rs +++ b/crates/common/src/ipc.rs @@ -213,6 +213,12 @@ impl TrainTaskController { } } +impl BroadcastEvent { + pub fn reload(object: Object) -> Self { + BroadcastEvent::RegistryChange(RegistryChange::Reload(object)) + } +} + pub trait ToHash { fn to_hash(&self) -> u64; } diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index b029aa24..07c4b88a 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -43,6 +43,7 @@ use mail_auth::{MX, Txt}; use manager::webadmin::{Resource, WebAdminManager}; use parking_lot::{Mutex, RwLock}; use rustls::sign::CertifiedKey; +use std::sync::atomic::AtomicU64; use std::{ net::{IpAddr, Ipv4Addr, Ipv6Addr}, sync::{Arc, atomic::AtomicBool}, @@ -141,6 +142,7 @@ pub struct Inner { pub ipc: Ipc, } +#[allow(clippy::type_complexity)] pub struct Data { pub spam_classifier: ArcSwap, @@ -202,7 +204,7 @@ pub struct Caches { pub struct MessageStoreCache { pub emails: Arc, pub mailboxes: Arc, - pub update_lock: Arc, + pub update_lock: Arc, pub last_change_id: u64, pub size: u64, } @@ -286,7 +288,13 @@ pub struct DavResources { pub container_change_id: u64, pub highest_change_id: u64, pub size: u64, - pub update_lock: Arc, + pub update_lock: Arc, +} + +#[derive(Debug)] +pub struct UpdateLock { + pub semaphore: Semaphore, + pub revision: AtomicU64, } #[derive(Debug, Clone)] diff --git a/crates/common/src/storage/index.rs b/crates/common/src/storage/index.rs index f9a545fc..d70d6647 100644 --- a/crates/common/src/storage/index.rs +++ b/crates/common/src/storage/index.rs @@ -4,7 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use crate::{auth::AccessToken, sharing::notification::ShareNotification}; +use crate::{ + auth::{AccessToken, AccountInfo, AccountTenantIds}, + sharing::notification::ShareNotification, +}; use rkyv::{ option::ArchivedOption, primitive::{ArchivedU32, ArchivedU64}, @@ -299,9 +302,9 @@ impl ObjectIndexBuilder Self { - self.tenant_id = access_token.tenant_id(); - self.changed_by = access_token.account_id(); + pub fn with_changed_by(mut self, ids: AccountTenantIds) -> Self { + self.tenant_id = ids.tenant_id; + self.changed_by = ids.account_id; self } diff --git a/crates/dav/Cargo.toml b/crates/dav/Cargo.toml index 5d7ed046..67b14e2d 100644 --- a/crates/dav/Cargo.toml +++ b/crates/dav/Cargo.toml @@ -10,6 +10,7 @@ store = { path = "../store" } utils = { path = "../utils" } groupware = { path = "../groupware" } directory = { path = "../directory" } +registry = { path = "../registry" } http_proto = { path = "../http-proto" } types = { path = "../types" } trc = { path = "../trc" } diff --git a/crates/dav/src/calendar/copy_move.rs b/crates/dav/src/calendar/copy_move.rs index 924b91cd..949be675 100644 --- a/crates/dav/src/calendar/copy_move.rs +++ b/crates/dav/src/calendar/copy_move.rs @@ -57,7 +57,11 @@ impl CalendarCopyMoveRequestHandler for Server { .into_owned_uri()?; let from_account_id = from_resource_.account_id; let from_resources = self - .fetch_dav_resources(access_token, from_account_id, SyncCollection::Calendar) + .fetch_dav_resources( + access_token.account_id(), + from_account_id, + SyncCollection::Calendar, + ) .await .caused_by(trc::location!())?; let from_resource_name = from_resource_ @@ -116,9 +120,13 @@ impl CalendarCopyMoveRequestHandler for Server { let to_resources = if to_account_id == from_account_id { from_resources.clone() } else { - self.fetch_dav_resources(access_token, to_account_id, SyncCollection::Calendar) - .await - .caused_by(trc::location!())? + self.fetch_dav_resources( + access_token.account_id(), + to_account_id, + SyncCollection::Calendar, + ) + .await + .caused_by(trc::location!())? }; // Validate headers @@ -477,7 +485,11 @@ async fn copy_event( assert_is_unique_uid( server, server - .fetch_dav_resources(access_token, to_account_id, SyncCollection::Calendar) + .fetch_dav_resources( + access_token.account_id(), + to_account_id, + SyncCollection::Calendar, + ) .await .caused_by(trc::location!())? .as_ref(), @@ -487,6 +499,7 @@ async fn copy_event( ) .await?; + let changed_by = access_token.account_tenant_ids(); if from_account_id == to_account_id { let mut new_event = event .deserialize::() @@ -497,7 +510,7 @@ async fn copy_event( }); new_event .update( - access_token, + changed_by, event, from_account_id, from_document_id, @@ -520,7 +533,7 @@ async fn copy_event( .caused_by(trc::location!())?; new_event .insert( - access_token, + changed_by, to_account_id, to_document_id, next_email_alarm, @@ -544,10 +557,14 @@ async fn copy_event( let event = event_ .to_unarchived::() .caused_by(trc::location!())?; + let account_info = server + .account_info(access_token.account_id()) + .await + .caused_by(trc::location!())?; DestroyArchive(event) .delete( - access_token, + &account_info, to_account_id, to_document_id, to_calendar_id, @@ -616,7 +633,11 @@ async fn move_event( assert_is_unique_uid( server, server - .fetch_dav_resources(access_token, to_account_id, SyncCollection::Calendar) + .fetch_dav_resources( + access_token.account_id(), + to_account_id, + SyncCollection::Calendar, + ) .await .caused_by(trc::location!())? .as_ref(), @@ -627,6 +648,10 @@ async fn move_event( .await?; } + let account_info = server + .account_info(access_token.account_id()) + .await + .caused_by(trc::location!())?; let mut batch = BatchBuilder::new(); if from_account_id == to_account_id { let mut name_idx = None; @@ -653,7 +678,7 @@ async fn move_event( }); new_event .update( - access_token, + access_token.account_tenant_ids(), event.clone(), from_account_id, from_document_id, @@ -673,7 +698,7 @@ async fn move_event( DestroyArchive(event) .delete( - access_token, + &account_info, from_account_id, from_document_id, from_calendar_id, @@ -690,7 +715,7 @@ async fn move_event( .caused_by(trc::location!())?; new_event .insert( - access_token, + access_token.account_tenant_ids(), to_account_id, to_document_id, next_email_alarm, @@ -717,7 +742,7 @@ async fn move_event( DestroyArchive(event) .delete( - access_token, + &account_info, to_account_id, to_document_id, to_calendar_id, @@ -780,7 +805,13 @@ async fn rename_event( let mut batch = BatchBuilder::new(); new_event - .update(access_token, event, account_id, document_id, &mut batch) + .update( + access_token.account_tenant_ids(), + event, + account_id, + document_id, + &mut batch, + ) .caused_by(trc::location!())?; batch.log_vanished_item(VanishedCollection::Calendar, from_resource_path); server @@ -830,7 +861,7 @@ async fn copy_container( if remove_source { DestroyArchive(old_calendar) .delete( - access_token, + access_token.account_tenant_ids(), from_account_id, from_document_id, from_resource_path.into(), @@ -853,6 +884,10 @@ async fn copy_container( time_zone: Timezone::Default, }]; + let account_info = server + .account_info(access_token.account_id()) + .await + .caused_by(trc::location!())?; let is_overwrite = to_document_id.is_some(); let to_document_id = if let Some(to_document_id) = to_document_id { // Overwrite destination @@ -873,7 +908,7 @@ async fn copy_container( DestroyArchive(calendar) .delete_with_events( server, - access_token, + &account_info, to_account_id, to_document_id, to_children_ids, @@ -894,7 +929,12 @@ async fn copy_container( .caused_by(trc::location!())? }; calendar - .insert(access_token, to_account_id, to_document_id, &mut batch) + .insert( + access_token.account_tenant_ids(), + to_account_id, + to_document_id, + &mut batch, + ) .caused_by(trc::location!())?; // Copy children @@ -946,7 +986,7 @@ async fn copy_container( new_event.names.push(new_name); new_event .update( - access_token, + access_token.account_tenant_ids(), event, from_account_id, from_child_document_id, @@ -958,7 +998,7 @@ async fn copy_container( if remove_source { DestroyArchive(event) .delete( - access_token, + &account_info, from_account_id, from_child_document_id, from_document_id, @@ -977,7 +1017,7 @@ async fn copy_container( required_space += new_event.size as u64; new_event .insert( - access_token, + access_token.account_tenant_ids(), to_account_id, to_document_id, next_email_alarm, @@ -1037,7 +1077,13 @@ async fn rename_container( let mut batch = BatchBuilder::new(); new_calendar - .update(access_token, calendar, account_id, document_id, &mut batch) + .update( + access_token.account_tenant_ids(), + calendar, + account_id, + document_id, + &mut batch, + ) .caused_by(trc::location!())?; batch.log_vanished_item(VanishedCollection::Calendar, from_resource_path); server diff --git a/crates/dav/src/calendar/delete.rs b/crates/dav/src/calendar/delete.rs index 26e18f21..6c74ecea 100644 --- a/crates/dav/src/calendar/delete.rs +++ b/crates/dav/src/calendar/delete.rs @@ -14,7 +14,6 @@ use crate::{ }; use common::{Server, auth::AccessToken, sharing::EffectiveAcl}; use dav_proto::RequestHeaders; -use directory::Permission; use groupware::{ DestroyArchive, cache::GroupwareCache, @@ -22,6 +21,7 @@ use groupware::{ }; use http_proto::HttpResponse; use hyper::StatusCode; +use registry::schema::enums::Permission; use store::write::{BatchBuilder, ValueClass}; use store::{ ValueKey, @@ -59,7 +59,11 @@ impl CalendarDeleteRequestHandler for Server { .filter(|r| !r.is_empty()) .ok_or(DavError::Code(StatusCode::FORBIDDEN))?; let resources = self - .fetch_dav_resources(access_token, account_id, SyncCollection::Calendar) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::Calendar, + ) .await .caused_by(trc::location!())?; @@ -68,9 +72,10 @@ impl CalendarDeleteRequestHandler for Server { .by_path(delete_path) .ok_or(DavError::Code(StatusCode::NOT_FOUND))?; let document_id = delete_resource.document_id(); + let account_info = self.account_info(access_token.account_id()).await?; let send_itip = self.core.groupware.itip_enabled && !headers.no_schedule_reply - && !access_token.emails.is_empty() + && account_info.addresses().next().is_some() && access_token.has_permission(Permission::CalendarSchedulingSend); // Fetch entry @@ -138,7 +143,7 @@ impl CalendarDeleteRequestHandler for Server { DestroyArchive(calendar) .delete_with_events( self, - access_token, + &account_info, account_id, document_id, resources @@ -222,7 +227,7 @@ impl CalendarDeleteRequestHandler for Server { // Delete event DestroyArchive(event) .delete( - access_token, + &account_info, account_id, document_id, calendar_id, diff --git a/crates/dav/src/calendar/freebusy.rs b/crates/dav/src/calendar/freebusy.rs index 141c2b61..56e6592e 100644 --- a/crates/dav/src/calendar/freebusy.rs +++ b/crates/dav/src/calendar/freebusy.rs @@ -69,7 +69,7 @@ impl CalendarFreebusyRequestHandler for Server { .into_owned_uri()?; let account_id = resource_.account_id; let resources = self - .fetch_dav_resources(access_token, account_id, SyncCollection::Calendar) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::Calendar) .await .caused_by(trc::location!())?; let resource = resources diff --git a/crates/dav/src/calendar/get.rs b/crates/dav/src/calendar/get.rs index d10826a6..b61cba5c 100644 --- a/crates/dav/src/calendar/get.rs +++ b/crates/dav/src/calendar/get.rs @@ -50,7 +50,7 @@ impl CalendarGetRequestHandler for Server { .into_owned_uri()?; let account_id = resource_.account_id; let resources = self - .fetch_dav_resources(access_token, account_id, SyncCollection::Calendar) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::Calendar) .await .caused_by(trc::location!())?; let resource = resources diff --git a/crates/dav/src/calendar/mkcol.rs b/crates/dav/src/calendar/mkcol.rs index 3676a745..89d42316 100644 --- a/crates/dav/src/calendar/mkcol.rs +++ b/crates/dav/src/calendar/mkcol.rs @@ -57,7 +57,11 @@ impl CalendarMkColRequestHandler for Server { return Err(DavError::Code(StatusCode::FORBIDDEN)); } else if name.contains('/') || self - .fetch_dav_resources(access_token, account_id, SyncCollection::Calendar) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::Calendar, + ) .await .caused_by(trc::location!())? .by_path(name) @@ -126,7 +130,12 @@ impl CalendarMkColRequestHandler for Server { .await .caused_by(trc::location!())?; calendar - .insert(access_token, account_id, document_id, &mut batch) + .insert( + access_token.account_tenant_ids(), + account_id, + document_id, + &mut batch, + ) .caused_by(trc::location!())?; let etag = batch.etag(); self.commit_batch(batch).await.caused_by(trc::location!())?; diff --git a/crates/dav/src/calendar/proppatch.rs b/crates/dav/src/calendar/proppatch.rs index 1304663c..6047beaa 100644 --- a/crates/dav/src/calendar/proppatch.rs +++ b/crates/dav/src/calendar/proppatch.rs @@ -83,7 +83,11 @@ impl CalendarPropPatchRequestHandler for Server { let uri = headers.uri; let account_id = resource_.account_id; let resources = self - .fetch_dav_resources(access_token, account_id, SyncCollection::Calendar) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::Calendar, + ) .await .caused_by(trc::location!())?; let resource = resource_ @@ -187,7 +191,13 @@ impl CalendarPropPatchRequestHandler for Server { if is_success { new_calendar - .update(access_token, calendar, account_id, document_id, &mut batch) + .update( + access_token.account_tenant_ids(), + calendar, + account_id, + document_id, + &mut batch, + ) .caused_by(trc::location!())? .etag() } else { @@ -221,7 +231,13 @@ impl CalendarPropPatchRequestHandler for Server { if is_success { new_event - .update(access_token, event, account_id, document_id, &mut batch) + .update( + access_token.account_tenant_ids(), + event, + account_id, + document_id, + &mut batch, + ) .caused_by(trc::location!())? .etag() } else { diff --git a/crates/dav/src/calendar/query.rs b/crates/dav/src/calendar/query.rs index a24a7a9b..76e8a7c4 100644 --- a/crates/dav/src/calendar/query.rs +++ b/crates/dav/src/calendar/query.rs @@ -68,7 +68,11 @@ impl CalendarQueryRequestHandler for Server { .into_owned_uri()?; let account_id = resource_.account_id; let resources = self - .fetch_dav_resources(access_token, account_id, SyncCollection::Calendar) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::Calendar, + ) .await .caused_by(trc::location!())?; let Some(resource) = resources.by_path( @@ -562,32 +566,35 @@ impl CalendarQueryHandler { } else if entries.peek().is_some() { let _ = write!(&mut out, "BEGIN:{component_name}\r\n"); - if data.limit_freebusy.is_none() - || component.component_type != ICalendarComponentType::VFreebusy - { - for (entry, with_value) in entries { - let _ = entry.write_to(&mut out, with_value); - } - } else { - // Filter freebusy - let range = data.limit_freebusy.unwrap(); - for (entry, with_value) in entries { - if matches!(entry.name, ArchivedICalendarProperty::Freebusy) { - let mut fb_in_range = - freebusy_in_range(entry, &range, self.default_tz).peekable(); - if fb_in_range.peek().is_none() { - continue; - } else { - let _ = ICalendarEntry { - name: ICalendarProperty::Freebusy, - params: rkyv_deserialize(&entry.params) - .ok() - .unwrap_or_default(), - values: fb_in_range.collect(), + match data.limit_freebusy { + Some(range) + if component.component_type == ICalendarComponentType::VFreebusy => + { + // Filter freebusy + for (entry, with_value) in entries { + if matches!(entry.name, ArchivedICalendarProperty::Freebusy) { + let mut fb_in_range = + freebusy_in_range(entry, &range, self.default_tz) + .peekable(); + if fb_in_range.peek().is_none() { + continue; + } else { + let _ = ICalendarEntry { + name: ICalendarProperty::Freebusy, + params: rkyv_deserialize(&entry.params) + .ok() + .unwrap_or_default(), + values: fb_in_range.collect(), + } + .write_to(&mut out); } - .write_to(&mut out); + } else { + let _ = entry.write_to(&mut out, with_value); } - } else { + } + } + _ => { + for (entry, with_value) in entries { let _ = entry.write_to(&mut out, with_value); } } diff --git a/crates/dav/src/calendar/scheduling.rs b/crates/dav/src/calendar/scheduling.rs index 7162671f..0779ab73 100644 --- a/crates/dav/src/calendar/scheduling.rs +++ b/crates/dav/src/calendar/scheduling.rs @@ -78,7 +78,7 @@ impl CalendarEventNotificationHandler for Server { let account_id = resource_.account_id; let resources = self .fetch_dav_resources( - access_token, + access_token.account_id(), account_id, SyncCollection::CalendarEventNotification, ) @@ -164,7 +164,7 @@ impl CalendarEventNotificationHandler for Server { .ok_or(DavError::Code(StatusCode::FORBIDDEN))?; let resources = self .fetch_dav_resources( - access_token, + access_token.account_id(), account_id, SyncCollection::CalendarEventNotification, ) @@ -220,7 +220,12 @@ impl CalendarEventNotificationHandler for Server { // Delete event let mut batch = BatchBuilder::new(); DestroyArchive(event) - .delete(access_token, account_id, document_id, &mut batch) + .delete( + access_token.account_tenant_ids(), + account_id, + document_id, + &mut batch, + ) .caused_by(trc::location!())?; self.commit_batch(batch).await.caused_by(trc::location!())?; @@ -362,14 +367,13 @@ impl CalendarEventNotificationHandler for Server { let mut response = ScheduleResponse::default(); for (email, attendee) in attendees { - if let Some(account_id) = self - .directory() - .email_to_id(&email) - .await - .caused_by(trc::location!())? - { + if let Some(account_id) = self.account_id(&email).await.caused_by(trc::location!())? { let resources = self - .fetch_dav_resources(access_token, account_id, SyncCollection::Calendar) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::Calendar, + ) .await .caused_by(trc::location!())?; if let Some(resource) = self diff --git a/crates/dav/src/calendar/update.rs b/crates/dav/src/calendar/update.rs index d454bf8e..be92920e 100644 --- a/crates/dav/src/calendar/update.rs +++ b/crates/dav/src/calendar/update.rs @@ -26,7 +26,6 @@ use dav_proto::{ RequestHeaders, Return, schema::{property::Rfc1123DateTime, response::CalCondition}, }; -use directory::Permission; use groupware::{ cache::GroupwareCache, calendar::{CalendarEvent, CalendarEventData}, @@ -34,6 +33,7 @@ use groupware::{ }; use http_proto::HttpResponse; use hyper::StatusCode; +use registry::schema::enums::Permission; use std::collections::HashSet; use store::write::{BatchBuilder, now}; use store::{ @@ -71,7 +71,11 @@ impl CalendarUpdateRequestHandler for Server { .into_owned_uri()?; let account_id = resource.account_id; let resources = self - .fetch_dav_resources(access_token, account_id, SyncCollection::Calendar) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::Calendar, + ) .await .caused_by(trc::location!())?; let resource_name = fix_percent_encoding( @@ -108,6 +112,11 @@ impl CalendarUpdateRequestHandler for Server { )); } }; + let account_info = self + .account_info(access_token.account_id()) + .await + .caused_by(trc::location!())?; + let account_emails = account_info.addresses().collect::>(); if let Some(resource) = resources.by_path(resource_name.as_ref()) { if resource.is_container() { @@ -214,7 +223,7 @@ impl CalendarUpdateRequestHandler for Server { // Scheduling let mut itip_messages = None; if self.core.groupware.itip_enabled - && !access_token.emails.is_empty() + && !account_emails.is_empty() && access_token.has_permission(Permission::CalendarSchedulingSend) && new_event.data.event_range_end() > now { @@ -222,10 +231,10 @@ impl CalendarUpdateRequestHandler for Server { itip_update( &mut new_event.data.event, &old_ical, - access_token.emails.as_slice(), + account_emails.as_slice(), ) } else { - itip_create(&mut new_event.data.event, access_token.emails.as_slice()) + itip_create(&mut new_event.data.event, account_emails.as_slice()) }; match result { @@ -279,18 +288,20 @@ impl CalendarUpdateRequestHandler for Server { let extra_bytes = (bytes.len() as u64).saturating_sub(u32::from(event.inner.size) as u64); if extra_bytes > 0 { - self.has_available_quota( - &self.get_resource_token(access_token, account_id).await?, - extra_bytes, - ) - .await?; + self.has_available_quota(account_id, extra_bytes).await?; } // Prepare write batch let mut batch = BatchBuilder::new(); let schedule_tag = new_event.schedule_tag; let etag = new_event - .update(access_token, event, account_id, document_id, &mut batch) + .update( + access_token.account_tenant_ids(), + event, + account_id, + document_id, + &mut batch, + ) .caused_by(trc::location!())? .etag(); if prev_email_alarm != next_email_alarm { @@ -374,11 +385,11 @@ impl CalendarUpdateRequestHandler for Server { // Scheduling let mut itip_messages = None; if self.core.groupware.itip_enabled - && !access_token.emails.is_empty() + && !account_emails.is_empty() && access_token.has_permission(Permission::CalendarSchedulingSend) && event.data.event_range_end() > now() as i64 { - match itip_create(&mut event.data.event, access_token.emails.as_slice()) { + match itip_create(&mut event.data.event, account_emails.as_slice()) { Ok(messages) => { if messages.iter().map(|r| r.to.len()).sum::() < self.core.groupware.itip_outbound_max_recipients @@ -408,11 +419,8 @@ impl CalendarUpdateRequestHandler for Server { // Validate quota if !bytes.is_empty() { - self.has_available_quota( - &self.get_resource_token(access_token, account_id).await?, - bytes.len() as u64, - ) - .await?; + self.has_available_quota(account_id, bytes.len() as u64) + .await?; } // Prepare write batch @@ -425,7 +433,7 @@ impl CalendarUpdateRequestHandler for Server { let schedule_tag = event.schedule_tag; let etag = event .insert( - access_token, + access_token.account_tenant_ids(), account_id, document_id, next_email_alarm, diff --git a/crates/dav/src/card/copy_move.rs b/crates/dav/src/card/copy_move.rs index dcd54114..820706ab 100644 --- a/crates/dav/src/card/copy_move.rs +++ b/crates/dav/src/card/copy_move.rs @@ -56,7 +56,11 @@ impl CardCopyMoveRequestHandler for Server { .into_owned_uri()?; let from_account_id = from_resource_.account_id; let from_resources = self - .fetch_dav_resources(access_token, from_account_id, SyncCollection::AddressBook) + .fetch_dav_resources( + access_token.account_id(), + from_account_id, + SyncCollection::AddressBook, + ) .await .caused_by(trc::location!())?; let from_resource_name = from_resource_ @@ -100,9 +104,13 @@ impl CardCopyMoveRequestHandler for Server { let to_resources = if to_account_id == from_account_id { from_resources.clone() } else { - self.fetch_dav_resources(access_token, to_account_id, SyncCollection::AddressBook) - .await - .caused_by(trc::location!())? + self.fetch_dav_resources( + access_token.account_id(), + to_account_id, + SyncCollection::AddressBook, + ) + .await + .caused_by(trc::location!())? }; // Validate headers @@ -459,7 +467,11 @@ async fn copy_card( assert_is_unique_uid( server, server - .fetch_dav_resources(access_token, to_account_id, SyncCollection::AddressBook) + .fetch_dav_resources( + access_token.account_id(), + to_account_id, + SyncCollection::AddressBook, + ) .await .caused_by(trc::location!())? .as_ref(), @@ -479,7 +491,7 @@ async fn copy_card( }); new_card .update( - access_token, + access_token.account_tenant_ids(), card, from_account_id, from_document_id, @@ -500,7 +512,12 @@ async fn copy_card( .await .caused_by(trc::location!())?; new_card - .insert(access_token, to_account_id, to_document_id, &mut batch) + .insert( + access_token.account_tenant_ids(), + to_account_id, + to_document_id, + &mut batch, + ) .caused_by(trc::location!())?; } @@ -522,7 +539,7 @@ async fn copy_card( DestroyArchive(card) .delete( - access_token, + access_token.account_tenant_ids(), to_account_id, to_document_id, to_addressbook_id, @@ -582,7 +599,11 @@ async fn move_card( assert_is_unique_uid( server, server - .fetch_dav_resources(access_token, to_account_id, SyncCollection::AddressBook) + .fetch_dav_resources( + access_token.account_id(), + to_account_id, + SyncCollection::AddressBook, + ) .await .caused_by(trc::location!())? .as_ref(), @@ -619,7 +640,7 @@ async fn move_card( }); new_card .update( - access_token, + access_token.account_tenant_ids(), card.clone(), from_account_id, from_document_id, @@ -638,7 +659,7 @@ async fn move_card( DestroyArchive(card) .delete( - access_token, + access_token.account_tenant_ids(), from_account_id, from_document_id, from_addressbook_id, @@ -653,7 +674,12 @@ async fn move_card( .await .caused_by(trc::location!())?; new_card - .insert(access_token, to_account_id, to_document_id, &mut batch) + .insert( + access_token.account_tenant_ids(), + to_account_id, + to_document_id, + &mut batch, + ) .caused_by(trc::location!())?; } @@ -675,7 +701,7 @@ async fn move_card( DestroyArchive(card) .delete( - access_token, + access_token.account_tenant_ids(), to_account_id, to_document_id, to_addressbook_id, @@ -737,7 +763,13 @@ async fn rename_card( let mut batch = BatchBuilder::new(); new_card - .update(access_token, card, account_id, document_id, &mut batch) + .update( + access_token.account_tenant_ids(), + card, + account_id, + document_id, + &mut batch, + ) .caused_by(trc::location!())?; batch.log_vanished_item(VanishedCollection::AddressBook, from_resource_path); server @@ -787,7 +819,7 @@ async fn copy_container( if remove_source { DestroyArchive(old_book) .delete( - access_token, + access_token.account_tenant_ids(), from_account_id, from_document_id, from_resource_path.into(), @@ -827,7 +859,7 @@ async fn copy_container( DestroyArchive(book) .delete_with_cards( server, - access_token, + access_token.account_tenant_ids(), to_account_id, to_document_id, to_children_ids, @@ -846,8 +878,13 @@ async fn copy_container( .await .caused_by(trc::location!())? }; - book.insert(access_token, to_account_id, to_document_id, &mut batch) - .caused_by(trc::location!())?; + book.insert( + access_token.account_tenant_ids(), + to_account_id, + to_document_id, + &mut batch, + ) + .caused_by(trc::location!())?; // Copy children let mut required_space = 0; @@ -898,7 +935,7 @@ async fn copy_container( new_card.names.push(new_name); new_card .update( - access_token, + access_token.account_tenant_ids(), card, from_account_id, from_child_document_id, @@ -909,7 +946,7 @@ async fn copy_container( if remove_source { DestroyArchive(card) .delete( - access_token, + access_token.account_tenant_ids(), from_account_id, from_child_document_id, from_document_id, @@ -927,7 +964,12 @@ async fn copy_container( new_card.names = vec![new_name]; required_space += new_card.size as u64; new_card - .insert(access_token, to_account_id, to_document_id, &mut batch) + .insert( + access_token.account_tenant_ids(), + to_account_id, + to_document_id, + &mut batch, + ) .caused_by(trc::location!())?; } } @@ -935,12 +977,7 @@ async fn copy_container( if from_account_id != to_account_id && required_space > 0 { server - .has_available_quota( - &server - .get_resource_token(access_token, to_account_id) - .await?, - required_space, - ) + .has_available_quota(to_account_id, required_space) .await?; } @@ -987,7 +1024,13 @@ async fn rename_container( let mut batch = BatchBuilder::new(); new_book - .update(access_token, book, account_id, document_id, &mut batch) + .update( + access_token.account_tenant_ids(), + book, + account_id, + document_id, + &mut batch, + ) .caused_by(trc::location!())?; batch.log_vanished_item(VanishedCollection::AddressBook, from_resource_path); server diff --git a/crates/dav/src/card/delete.rs b/crates/dav/src/card/delete.rs index d07f040f..8a25b48d 100644 --- a/crates/dav/src/card/delete.rs +++ b/crates/dav/src/card/delete.rs @@ -58,7 +58,11 @@ impl CardDeleteRequestHandler for Server { .filter(|r| !r.is_empty()) .ok_or(DavError::Code(StatusCode::FORBIDDEN))?; let resources = self - .fetch_dav_resources(access_token, account_id, SyncCollection::AddressBook) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::AddressBook, + ) .await .caused_by(trc::location!())?; @@ -118,7 +122,7 @@ impl CardDeleteRequestHandler for Server { DestroyArchive(book) .delete_with_cards( self, - access_token, + access_token.account_tenant_ids(), account_id, document_id, resources @@ -198,7 +202,7 @@ impl CardDeleteRequestHandler for Server { .caused_by(trc::location!())?, ) .delete( - access_token, + access_token.account_tenant_ids(), account_id, document_id, addressbook_id, diff --git a/crates/dav/src/card/get.rs b/crates/dav/src/card/get.rs index 1c755efc..1a68dfd1 100644 --- a/crates/dav/src/card/get.rs +++ b/crates/dav/src/card/get.rs @@ -50,7 +50,7 @@ impl CardGetRequestHandler for Server { .into_owned_uri()?; let account_id = resource_.account_id; let resources = self - .fetch_dav_resources(access_token, account_id, SyncCollection::AddressBook) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::AddressBook) .await .caused_by(trc::location!())?; let resource = resources diff --git a/crates/dav/src/card/mkcol.rs b/crates/dav/src/card/mkcol.rs index 70756758..f4619393 100644 --- a/crates/dav/src/card/mkcol.rs +++ b/crates/dav/src/card/mkcol.rs @@ -57,7 +57,11 @@ impl CardMkColRequestHandler for Server { return Err(DavError::Code(StatusCode::FORBIDDEN)); } else if name.contains('/') || self - .fetch_dav_resources(access_token, account_id, SyncCollection::AddressBook) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::AddressBook, + ) .await .caused_by(trc::location!())? .by_path(name) @@ -122,8 +126,13 @@ impl CardMkColRequestHandler for Server { .assign_document_ids(account_id, Collection::AddressBook, 1) .await .caused_by(trc::location!())?; - book.insert(access_token, account_id, document_id, &mut batch) - .caused_by(trc::location!())?; + book.insert( + access_token.account_tenant_ids(), + account_id, + document_id, + &mut batch, + ) + .caused_by(trc::location!())?; let etag = batch.etag(); self.commit_batch(batch).await.caused_by(trc::location!())?; diff --git a/crates/dav/src/card/proppatch.rs b/crates/dav/src/card/proppatch.rs index a79ed2fe..72a22789 100644 --- a/crates/dav/src/card/proppatch.rs +++ b/crates/dav/src/card/proppatch.rs @@ -80,7 +80,11 @@ impl CardPropPatchRequestHandler for Server { let uri = headers.uri; let account_id = resource_.account_id; let resources = self - .fetch_dav_resources(access_token, account_id, SyncCollection::AddressBook) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::AddressBook, + ) .await .caused_by(trc::location!())?; let resource = resource_ @@ -184,7 +188,13 @@ impl CardPropPatchRequestHandler for Server { if is_success { new_book - .update(access_token, book, account_id, document_id, &mut batch) + .update( + access_token.account_tenant_ids(), + book, + account_id, + document_id, + &mut batch, + ) .caused_by(trc::location!())? .etag() } else { @@ -218,7 +228,13 @@ impl CardPropPatchRequestHandler for Server { if is_success { new_card - .update(access_token, card, account_id, document_id, &mut batch) + .update( + access_token.account_tenant_ids(), + card, + account_id, + document_id, + &mut batch, + ) .caused_by(trc::location!())? .etag() } else { diff --git a/crates/dav/src/card/query.rs b/crates/dav/src/card/query.rs index 6ddac964..6d8030e0 100644 --- a/crates/dav/src/card/query.rs +++ b/crates/dav/src/card/query.rs @@ -55,7 +55,7 @@ impl CardQueryRequestHandler for Server { .into_owned_uri()?; let account_id = resource_.account_id; let resources = self - .fetch_dav_resources(access_token, account_id, SyncCollection::AddressBook) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::AddressBook) .await .caused_by(trc::location!())?; let Some(resource) = resources.by_path( diff --git a/crates/dav/src/card/update.rs b/crates/dav/src/card/update.rs index 01b33b94..5bca1e34 100644 --- a/crates/dav/src/card/update.rs +++ b/crates/dav/src/card/update.rs @@ -60,7 +60,11 @@ impl CardUpdateRequestHandler for Server { .into_owned_uri()?; let account_id = resource.account_id; let resources = self - .fetch_dav_resources(access_token, account_id, SyncCollection::AddressBook) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::AddressBook, + ) .await .caused_by(trc::location!())?; let resource_name = fix_percent_encoding( @@ -177,11 +181,7 @@ impl CardUpdateRequestHandler for Server { let extra_bytes = (bytes.len() as u64).saturating_sub(u32::from(card.inner.size) as u64); if extra_bytes > 0 { - self.has_available_quota( - &self.get_resource_token(access_token, account_id).await?, - extra_bytes, - ) - .await?; + self.has_available_quota(account_id, extra_bytes).await?; } // Build node @@ -194,7 +194,13 @@ impl CardUpdateRequestHandler for Server { // Prepare write batch let mut batch = BatchBuilder::new(); let etag = new_card - .update(access_token, card, account_id, document_id, &mut batch) + .update( + access_token.account_tenant_ids(), + card, + account_id, + document_id, + &mut batch, + ) .caused_by(trc::location!())? .etag(); self.commit_batch(batch).await.caused_by(trc::location!())?; @@ -245,11 +251,8 @@ impl CardUpdateRequestHandler for Server { // Validate quota if !bytes.is_empty() { - self.has_available_quota( - &self.get_resource_token(access_token, account_id).await?, - bytes.len() as u64, - ) - .await?; + self.has_available_quota(account_id, bytes.len() as u64) + .await?; } // Build node @@ -271,7 +274,12 @@ impl CardUpdateRequestHandler for Server { .await .caused_by(trc::location!())?; let etag = card - .insert(access_token, account_id, document_id, &mut batch) + .insert( + access_token.account_tenant_ids(), + account_id, + document_id, + &mut batch, + ) .caused_by(trc::location!())? .etag(); self.commit_batch(batch).await.caused_by(trc::location!())?; diff --git a/crates/dav/src/common/acl.rs b/crates/dav/src/common/acl.rs index 1c992020..ba37581e 100644 --- a/crates/dav/src/common/acl.rs +++ b/crates/dav/src/common/acl.rs @@ -18,7 +18,6 @@ use dav_proto::{ response::{Ace, BaseCondition, GrantDeny, Href, MultiStatus, Principal}, }, }; -use directory::{QueryParams, Type, backend::internal::manage::ManageDirectory}; use groupware::RFC_3986; use groupware::{cache::GroupwareCache, calendar::Calendar, contact::AddressBook, file::FileNode}; use http_proto::HttpResponse; @@ -99,7 +98,7 @@ impl DavAclHandler for Server { return Err(DavError::Code(StatusCode::FORBIDDEN)); } let resources = self - .fetch_dav_resources(access_token, account_id, collection.into()) + .fetch_dav_resources(access_token.account_id(), account_id, collection.into()) .await .caused_by(trc::location!())?; let resource = resource_ @@ -151,7 +150,7 @@ impl DavAclHandler for Server { new_calendar.acls = grants; new_calendar .update( - access_token, + access_token.account_tenant_ids(), calendar, account_id, resource.document_id(), @@ -166,7 +165,7 @@ impl DavAclHandler for Server { new_book.acls = grants; new_book .update( - access_token, + access_token.account_tenant_ids(), book, account_id, resource.document_id(), @@ -180,7 +179,7 @@ impl DavAclHandler for Server { new_node.acls = grants; new_node .update( - access_token, + access_token.account_tenant_ids(), node, account_id, resource.document_id(), @@ -422,7 +421,7 @@ impl DavAclHandler for Server { })?; // Verify that the principal is a valid principal - let principal = self + /*let principal = self .directory() .query(QueryParams::id(principal_id).with_return_member_of(false)) .await @@ -438,7 +437,7 @@ impl DavAclHandler for Server { StatusCode::FORBIDDEN, BaseCondition::AllowedPrincipal, ))); - } + }*/ grants.push(AclGrant { account_id: principal_id, @@ -473,17 +472,15 @@ impl DavAclHandler for Server { ))) }) } else { - let grant_account_name = self - .store() - .get_principal_name(grant_account_id) + let grant_account = self + .account_info(grant_account_id) .await - .caused_by(trc::location!())? - .unwrap_or_else(|| format!("_{grant_account_id}")); + .caused_by(trc::location!())?; Principal::Href(Href(format!( "{}/{}/", DavResourceName::Principal.base_path(), - percent_encoding::utf8_percent_encode(&grant_account_name, RFC_3986), + percent_encoding::utf8_percent_encode(grant_account.name(), RFC_3986), ))) }; diff --git a/crates/dav/src/common/lock.rs b/crates/dav/src/common/lock.rs index 42cb2e70..fb9916d8 100644 --- a/crates/dav/src/common/lock.rs +++ b/crates/dav/src/common/lock.rs @@ -202,7 +202,7 @@ impl LockRequestHandler for Server { locks .0 .iter() - .filter(|lock| lock.owner == access_token.account_id) + .filter(|lock| lock.owner == access_token.account_id()) }) .count() >= self.core.groupware.max_locks_per_user @@ -270,7 +270,7 @@ impl LockRequestHandler for Server { } lock_item.lock_id = store::rand::random::() ^ expires; - lock_item.owner = access_token.account_id; + lock_item.owner = access_token.account_id(); lock_item.depth_infinity = matches!(headers.depth, Depth::Infinity); lock_item.owner_dav = lock_info.owner; lock_item.exclusive = matches!(lock_info.lock_scope, LockScope::Exclusive); @@ -557,7 +557,7 @@ impl LockRequestHandler for Server { if needs_sync_token && resource_state.sync_token.is_none() { let id = self .fetch_dav_resources( - access_token, + access_token.account_id(), resource_state.account_id, resource_state.collection.into(), ) diff --git a/crates/dav/src/common/propfind.rs b/crates/dav/src/common/propfind.rs index 7d1db777..7fd037a0 100644 --- a/crates/dav/src/common/propfind.rs +++ b/crates/dav/src/common/propfind.rs @@ -28,7 +28,10 @@ use crate::{ }, }; use calcard::{common::timezone::Tz, icalendar::ICalendarComponentType}; -use common::{DavResourcePath, DavResources, Server, auth::AccessToken}; +use common::{ + DavResourcePath, DavResources, Server, + auth::{AccessToken, AccountInfo}, +}; use dav_proto::{ Depth, RequestHeaders, parser::header::dav_base_uri, @@ -47,16 +50,20 @@ use dav_proto::{ }, }, }; -use directory::{Permission, Type, backend::internal::manage::ManageDirectory}; use groupware::calendar::{SCHEDULE_INBOX_ID, SupportedComponent}; use groupware::{ DavCalendarResource, DavResourceName, cache::GroupwareCache, calendar::ArchivedTimezone, }; use http_proto::HttpResponse; use hyper::StatusCode; +use registry::schema::{ + enums::Permission, + prelude::{Object, Property}, +}; use std::sync::Arc; use store::{ ValueKey, + registry::RegistryQuery, write::{AlignedBytes, Archive}, }; use store::{ @@ -88,7 +95,6 @@ pub(crate) trait PropFindRequestHandler: Sync + Send { fn dav_quota( &self, - access_token: &AccessToken, account_id: u32, ) -> impl Future> + Send; } @@ -161,7 +167,7 @@ impl PropFindRequestHandler for Server { | Collection::AddressBook | Collection::CalendarEventNotification => { // Validate permissions - access_token.assert_has_permission(match resource.collection { + access_token.enforce_permission(match resource.collection { Collection::FileNode => Permission::DavFilePropFind, Collection::Calendar | Collection::CalendarEvent @@ -237,7 +243,7 @@ impl PropFindRequestHandler for Server { if return_children { let ids = if !matches!(resource.collection, Collection::Principal) { // Validate permissions - access_token.assert_has_permission(match resource.collection { + access_token.enforce_permission(match resource.collection { Collection::FileNode => Permission::DavFilePropFind, Collection::Calendar | Collection::CalendarEvent @@ -255,20 +261,13 @@ impl PropFindRequestHandler for Server { || access_token.has_permission(Permission::IndividualList) { // Return all principals - let principals = self - .store() - .list_principals( - None, - access_token.tenant_id(), - &[Type::Individual, Type::Group], - false, - 0, - 0, + self.registry() + .query::( + RegistryQuery::new(Object::Account) + .equal_opt(Property::MemberTenantId, access_token.tenant_id()), ) .await - .caused_by(trc::location!())?; - - RoaringBitmap::from_iter(principals.items.into_iter().map(|p| p.id())) + .caused_by(trc::location!())? } else { RoaringBitmap::from_iter(access_token.all_ids()) }; @@ -417,6 +416,10 @@ impl PropFindRequestHandler for Server { }; let is_scheduling = collection_container == Collection::CalendarEventNotification; + let account_info = self + .account_info(access_token.account_id()) + .await + .caused_by(trc::location!())?; 'outer: for item in paths { let account_id = item.account_id; let document_id = item.document_id; @@ -614,7 +617,7 @@ impl PropFindRequestHandler for Server { if !query.expand { fields.push(DavPropertyValue::new( property.clone(), - vec![access_token.current_user_principal()], + vec![account_info.current_user_principal()], )); } else { fields.push(DavPropertyValue::new( @@ -634,7 +637,7 @@ impl PropFindRequestHandler for Server { if item.is_container { fields.push(DavPropertyValue::new( property.clone(), - data.quota(self, access_token, account_id) + data.quota(self, account_id) .await .caused_by(trc::location!())? .available, @@ -647,7 +650,7 @@ impl PropFindRequestHandler for Server { if item.is_container { fields.push(DavPropertyValue::new( property.clone(), - data.quota(self, access_token, account_id) + data.quota(self, account_id) .await .caused_by(trc::location!())? .used, @@ -661,7 +664,7 @@ impl PropFindRequestHandler for Server { fields.push(DavPropertyValue::new( property.clone(), vec![ - data.owner(self, access_token, account_id) + data.owner(self, &account_info, account_id) .await .caused_by(trc::location!())?, ], @@ -1130,19 +1133,20 @@ impl PropFindRequestHandler for Server { } } - async fn dav_quota( - &self, - access_token: &AccessToken, - account_id: u32, - ) -> trc::Result { - let resource_token = self - .get_resource_token(access_token, account_id) - .await - .caused_by(trc::location!())?; - let quota = if resource_token.quota > 0 { - resource_token.quota - } else if let Some(tenant) = resource_token.tenant.filter(|t| t.quota > 0) { - tenant.quota + async fn dav_quota(&self, account_id: u32) -> trc::Result { + let account = self.account(account_id).await.caused_by(trc::location!())?; + let quota = if account.quota_disk > 0 { + account.quota_disk + } else if account.id_tenant != u32::MAX { + let tenant = self + .tenant(account.id_tenant) + .await + .caused_by(trc::location!())?; + if tenant.quota_disk > 0 { + tenant.quota_disk + } else { + u32::MAX as u64 + } } else { u32::MAX as u64 }; @@ -1588,13 +1592,12 @@ impl PropFindData { pub async fn quota( &mut self, server: &Server, - access_token: &AccessToken, account_id: u32, ) -> trc::Result { let data = self.accounts.entry(account_id).or_default(); if data.quota.is_none() { - data.quota = server.dav_quota(access_token, account_id).await?.into(); + data.quota = server.dav_quota(account_id).await?.into(); } Ok(data.quota.clone().unwrap()) @@ -1603,14 +1606,14 @@ impl PropFindData { pub async fn owner( &mut self, server: &Server, - access_token: &AccessToken, + account_info: &AccountInfo, account_id: u32, ) -> trc::Result { let data = self.accounts.entry(account_id).or_default(); if data.owner.is_none() { data.owner = server - .owner_href(access_token, account_id) + .owner_href(account_info, account_id) .await .caused_by(trc::location!())? .into(); @@ -1630,7 +1633,7 @@ impl PropFindData { if data.resources.is_none() { let resources = server - .fetch_dav_resources(access_token, account_id, sync_collection) + .fetch_dav_resources(access_token.account_id(), account_id, sync_collection) .await .caused_by(trc::location!())?; data.resources = resources.into(); @@ -1725,6 +1728,10 @@ async fn add_base_collection_response( let mut fields = Vec::with_capacity(properties.len()); let mut fields_not_found = Vec::new(); + let account_info = server + .account_info(access_token.account_id()) + .await + .caused_by(trc::location!())?; for prop in properties { match &prop { @@ -1737,15 +1744,15 @@ async fn add_base_collection_response( DavProperty::WebDav(WebDavProperty::CurrentUserPrincipal) => { fields.push(DavPropertyValue::new( prop.clone(), - vec![access_token.current_user_principal()], + vec![account_info.current_user_principal()], )); } DavProperty::Principal(PrincipalProperty::CalendarHomeSet) => { let hrefs = build_home_set( server, access_token, - &access_token.name, - access_token.account_id, + account_info.name(), + access_token.account_id(), true, ) .await @@ -1758,8 +1765,8 @@ async fn add_base_collection_response( let hrefs = build_home_set( server, access_token, - &access_token.name, - access_token.account_id, + account_info.name(), + access_token.account_id(), false, ) .await diff --git a/crates/dav/src/common/uri.rs b/crates/dav/src/common/uri.rs index b8d9d6eb..8f952528 100644 --- a/crates/dav/src/common/uri.rs +++ b/crates/dav/src/common/uri.rs @@ -6,7 +6,6 @@ use crate::{DavError, DavResourceName}; use common::{Server, auth::AccessToken}; -use directory::backend::internal::manage::ManageDirectory; use groupware::cache::GroupwareCache; use http_proto::request::decode_path_element; use hyper::StatusCode; @@ -92,15 +91,10 @@ impl DavUriResource for Server { .map_err(|_| DavError::Code(error_status))? } else { let account = decode_path_element(account); - if access_token.name == account { - access_token.account_id - } else { - self.store() - .get_principal_id(&account) - .await - .caused_by(trc::location!())? - .ok_or(DavError::Code(error_status))? - } + self.account_id(&account) + .await + .caused_by(trc::location!())? + .ok_or(DavError::Code(error_status))? }; // Validate access @@ -125,7 +119,11 @@ impl DavUriResource for Server { ) -> trc::Result> { if let Some(resource) = uri.resource { if let Some(resource) = self - .fetch_dav_resources(access_token, uri.account_id, uri.collection.into()) + .fetch_dav_resources( + access_token.account_id(), + uri.account_id, + uri.collection.into(), + ) .await .caused_by(trc::location!())? .by_path(resource) diff --git a/crates/dav/src/file/copy_move.rs b/crates/dav/src/file/copy_move.rs index 90547468..da93dd1b 100644 --- a/crates/dav/src/file/copy_move.rs +++ b/crates/dav/src/file/copy_move.rs @@ -59,7 +59,11 @@ impl FileCopyMoveRequestHandler for Server { .into_owned_uri()?; let from_account_id = from_resource_.account_id; let from_resources = self - .fetch_dav_resources(access_token, from_account_id, SyncCollection::FileNode) + .fetch_dav_resources( + access_token.account_id(), + from_account_id, + SyncCollection::FileNode, + ) .await .caused_by(trc::location!())?; let from_resource = from_resources.map_resource::(&from_resource_)?; @@ -103,9 +107,13 @@ impl FileCopyMoveRequestHandler for Server { let to_resources = if to_account_id == from_account_id { from_resources.clone() } else { - self.fetch_dav_resources(access_token, to_account_id, SyncCollection::FileNode) - .await - .caused_by(trc::location!())? + self.fetch_dav_resources( + access_token.account_id(), + to_account_id, + SyncCollection::FileNode, + ) + .await + .caused_by(trc::location!())? }; // Map file item @@ -232,11 +240,8 @@ impl FileCopyMoveRequestHandler for Server { .subtree(from_resource_name) .map(|a| a.size() as u64) .sum::(); - self.has_available_quota( - &self.get_resource_token(access_token, to_account_id).await?, - space_needed, - ) - .await?; + self.has_available_quota(to_account_id, space_needed) + .await?; } // Delete collection @@ -254,7 +259,12 @@ impl FileCopyMoveRequestHandler for Server { let mut sorted_ids = Vec::with_capacity(ids.len()); sorted_ids.extend(ids.into_iter().map(|a| a.document_id())); DestroyArchive(sorted_ids) - .delete(self, access_token, destination.account_id, None) + .delete( + self, + access_token.account_tenant_ids(), + destination.account_id, + None, + ) .await .caused_by(trc::location!())?; } @@ -382,7 +392,7 @@ async fn move_container( let mut batch = BatchBuilder::new(); let etag = new_node .update( - access_token, + access_token.account_tenant_ids(), node, from_account_id, from_document_id, @@ -509,7 +519,7 @@ async fn copy_container( .custom( ObjectIndexBuilder::<(), _>::new() .with_changes(node) - .with_access_token(access_token), + .with_changed_by(access_token.account_tenant_ids()), ) .caused_by(trc::location!())? .commit_point(); @@ -526,7 +536,7 @@ async fn copy_container( .with_document(document_id) .custom( ObjectIndexBuilder::<_, ()>::new() - .with_access_token(access_token) + .with_changed_by(access_token.account_tenant_ids()) .with_current(node), ) .caused_by(trc::location!())? @@ -605,7 +615,7 @@ async fn overwrite_and_delete_item( let mut batch = BatchBuilder::new(); let etag = source_node .update( - access_token, + access_token.account_tenant_ids(), dest_node, to_account_id, to_document_id, @@ -615,7 +625,7 @@ async fn overwrite_and_delete_item( .etag(); DestroyArchive(source_node_) .delete( - access_token, + access_token.account_tenant_ids(), from_account_id, from_document_id, &mut batch, @@ -680,7 +690,7 @@ async fn overwrite_item( let mut batch = BatchBuilder::new(); let etag = source_node .update( - access_token, + access_token.account_tenant_ids(), dest_node, to_account_id, to_document_id, @@ -734,7 +744,7 @@ async fn move_item( batch.log_vanished_item(VanishedCollection::FileNode, from_resource_path); new_node .update( - access_token, + access_token.account_tenant_ids(), node, from_account_id, from_document_id, @@ -750,12 +760,17 @@ async fn move_item( .await .caused_by(trc::location!())?; let etag = new_node - .insert(access_token, to_account_id, to_document_id, &mut batch) + .insert( + access_token.account_tenant_ids(), + to_account_id, + to_document_id, + &mut batch, + ) .caused_by(trc::location!())? .etag(); DestroyArchive(node) .delete( - access_token, + access_token.account_tenant_ids(), from_account_id, from_document_id, &mut batch, @@ -807,7 +822,12 @@ async fn copy_item( .await .caused_by(trc::location!())?; let etag = node - .insert(access_token, to_account_id, to_document_id, &mut batch) + .insert( + access_token.account_tenant_ids(), + to_account_id, + to_document_id, + &mut batch, + ) .caused_by(trc::location!())? .etag(); server @@ -849,7 +869,7 @@ async fn rename_item( let mut batch = BatchBuilder::new(); let etag = new_node .update( - access_token, + access_token.account_tenant_ids(), node, from_account_id, from_document_id, diff --git a/crates/dav/src/file/delete.rs b/crates/dav/src/file/delete.rs index d9de9b5e..942a8fe0 100644 --- a/crates/dav/src/file/delete.rs +++ b/crates/dav/src/file/delete.rs @@ -44,7 +44,11 @@ impl FileDeleteRequestHandler for Server { .filter(|r| !r.is_empty()) .ok_or(DavError::Code(StatusCode::FORBIDDEN))?; let resources = self - .fetch_dav_resources(access_token, account_id, SyncCollection::FileNode) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::FileNode, + ) .await .caused_by(trc::location!())?; @@ -90,7 +94,12 @@ impl FileDeleteRequestHandler for Server { .await?; DestroyArchive(sorted_ids) - .delete(self, access_token, account_id, full_delete_path.into()) + .delete( + self, + access_token.account_tenant_ids(), + account_id, + full_delete_path.into(), + ) .await?; Ok(HttpResponse::new(StatusCode::NO_CONTENT)) diff --git a/crates/dav/src/file/get.rs b/crates/dav/src/file/get.rs index a49ed4e5..0a04655d 100644 --- a/crates/dav/src/file/get.rs +++ b/crates/dav/src/file/get.rs @@ -51,7 +51,7 @@ impl FileGetRequestHandler for Server { .into_owned_uri()?; let account_id = resource_.account_id; let files = self - .fetch_dav_resources(access_token, account_id, SyncCollection::FileNode) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::FileNode) .await .caused_by(trc::location!())?; let resource = files.map_resource(&resource_)?; diff --git a/crates/dav/src/file/mkcol.rs b/crates/dav/src/file/mkcol.rs index dc3a9ece..85d981a1 100644 --- a/crates/dav/src/file/mkcol.rs +++ b/crates/dav/src/file/mkcol.rs @@ -53,7 +53,7 @@ impl FileMkColRequestHandler for Server { .into_owned_uri()?; let account_id = resource_.account_id; let resources = self - .fetch_dav_resources(access_token, account_id, SyncCollection::FileNode) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::FileNode) .await .caused_by(trc::location!())?; let resource = resources.map_parent_resource(&resource_)?; diff --git a/crates/dav/src/file/proppatch.rs b/crates/dav/src/file/proppatch.rs index 1fa00e8c..9e46258f 100644 --- a/crates/dav/src/file/proppatch.rs +++ b/crates/dav/src/file/proppatch.rs @@ -68,7 +68,11 @@ impl FilePropPatchRequestHandler for Server { let uri = headers.uri; let account_id = resource_.account_id; let files = self - .fetch_dav_resources(access_token, account_id, SyncCollection::FileNode) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::FileNode, + ) .await .caused_by(trc::location!())?; let resource = files.map_resource(&resource_)?; @@ -145,7 +149,7 @@ impl FilePropPatchRequestHandler for Server { let mut batch = BatchBuilder::new(); let etag = new_node .update( - access_token, + access_token.account_tenant_ids(), node, account_id, resource.resource, diff --git a/crates/dav/src/file/update.rs b/crates/dav/src/file/update.rs index 9a93e2da..06275f08 100644 --- a/crates/dav/src/file/update.rs +++ b/crates/dav/src/file/update.rs @@ -61,7 +61,11 @@ impl FileUpdateRequestHandler for Server { .into_owned_uri()?; let account_id = resource.account_id; let resources = self - .fetch_dav_resources(access_token, account_id, SyncCollection::FileNode) + .fetch_dav_resources( + access_token.account_id(), + account_id, + SyncCollection::FileNode, + ) .await .caused_by(trc::location!())?; let resource_name = resource @@ -162,11 +166,7 @@ impl FileUpdateRequestHandler for Server { let extra_bytes = (bytes.len() as u64) .saturating_sub(u32::from(node.inner.file.as_ref().unwrap().size) as u64); if extra_bytes > 0 { - self.has_available_quota( - &self.get_resource_token(access_token, account_id).await?, - extra_bytes, - ) - .await?; + self.has_available_quota(account_id, extra_bytes).await?; } // Write blob @@ -197,7 +197,7 @@ impl FileUpdateRequestHandler for Server { ObjectIndexBuilder::new() .with_current(node) .with_changes(new_node) - .with_access_token(access_token), + .with_changed_by(access_token.account_tenant_ids()), ) .caused_by(trc::location!())?; let etag = batch.etag(); @@ -242,11 +242,8 @@ impl FileUpdateRequestHandler for Server { // Validate quota if !bytes.is_empty() { - self.has_available_quota( - &self.get_resource_token(access_token, account_id).await?, - bytes.len() as u64, - ) - .await?; + self.has_available_quota(account_id, bytes.len() as u64) + .await?; } // Write blob @@ -292,7 +289,7 @@ impl FileUpdateRequestHandler for Server { .custom( ObjectIndexBuilder::<(), _>::new() .with_changes(node) - .with_access_token(access_token), + .with_changed_by(access_token.account_tenant_ids()), ) .caused_by(trc::location!())?; let etag = batch.etag(); diff --git a/crates/dav/src/principal/mod.rs b/crates/dav/src/principal/mod.rs index f3f7ab1c..ddec88b1 100644 --- a/crates/dav/src/principal/mod.rs +++ b/crates/dav/src/principal/mod.rs @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use common::auth::AccessToken; +use common::auth::AccountInfo; use dav_proto::schema::response::Href; use groupware::RFC_3986; @@ -18,12 +18,12 @@ pub trait CurrentUserPrincipal { fn current_user_principal(&self) -> Href; } -impl CurrentUserPrincipal for AccessToken { +impl CurrentUserPrincipal for AccountInfo { fn current_user_principal(&self) -> Href { Href(format!( "{}/{}/", DavResourceName::Principal.base_path(), - percent_encoding::utf8_percent_encode(&self.name, RFC_3986) + percent_encoding::utf8_percent_encode(self.name(), RFC_3986) )) } } diff --git a/crates/dav/src/principal/propfind.rs b/crates/dav/src/principal/propfind.rs index 49bcac6e..a79755e8 100644 --- a/crates/dav/src/principal/propfind.rs +++ b/crates/dav/src/principal/propfind.rs @@ -9,7 +9,10 @@ use crate::{ DavResourceName, common::propfind::{PropFindRequestHandler, SyncTokenUrn}, }; -use common::{Server, auth::AccessToken}; +use common::{ + Server, + auth::{AccessToken, AccountInfo}, +}; use dav_proto::schema::{ Namespace, property::{ @@ -19,7 +22,6 @@ use dav_proto::schema::{ request::{DavPropertyValue, PropFind}, response::{Href, MultiStatus, PropStat, Response}, }; -use directory::{PrincipalData, QueryParams, Type, backend::internal::manage::ManageDirectory}; use groupware::RFC_3986; use groupware::cache::GroupwareCache; use hyper::StatusCode; @@ -46,7 +48,7 @@ pub(crate) trait PrincipalPropFind: Sync + Send { fn owner_href( &self, - access_token: &AccessToken, + account_info: &AccountInfo, account_id: u32, ) -> impl Future> + Send; } @@ -60,12 +62,16 @@ impl PrincipalPropFind for Server { request: &PropFind, response: &mut MultiStatus, ) -> crate::Result<()> { + let access_account_info = self + .account_info(access_token.account_id()) + .await + .caused_by(trc::location!())?; let properties = match request { PropFind::PropName => { let props = all_props(collection, None); for account_id in account_ids { response.add_response(Response::new_propstat( - self.owner_href(access_token, account_id) + self.owner_href(&access_account_info, account_id) .await .caused_by(trc::location!())?, vec![PropStat::new_list( @@ -106,66 +112,14 @@ impl PrincipalPropFind for Server { let mut fields = Vec::with_capacity(properties.len()); let mut fields_not_found = Vec::new(); - let (name, description, emails, typ) = if access_token.account_id() == account_id { - ( - Cow::Borrowed(access_token.name.as_str()), - access_token - .description - .as_deref() - .unwrap_or(&access_token.name) - .to_string(), - Cow::Borrowed(access_token.emails.as_slice()), - Type::Individual, - ) - } else { - self.directory() - .query(QueryParams::id(account_id).with_return_member_of(false)) - .await - .caused_by(trc::location!())? - .map(|p| { - let name = p.name; - let mut description = None; - let mut emails = Vec::new(); - for data in p.data { - match data { - PrincipalData::Description(desc) => { - description = Some(desc); - } - PrincipalData::PrimaryEmail(email) => { - if emails.is_empty() { - emails.push(email); - } else { - emails.insert(0, email); - } - } - PrincipalData::EmailAlias(email) => { - emails.push(email); - } - _ => {} - } - } - - let description = description.unwrap_or_else(|| name.clone()); - ( - Cow::Owned(name.to_string()), - description, - Cow::Owned(emails), - p.typ, - ) - }) - .unwrap_or_else(|| { - ( - Cow::Owned(format!("_{}", account_id)), - format!("_{}", account_id), - Cow::Owned(vec![]), - Type::Individual, - ) - }) - }; + let account = self + .account_info(account_id) + .await + .caused_by(trc::location!())?; // Fetch quota let quota = if needs_quota { - self.dav_quota(access_token, account_id) + self.dav_quota(account_id) .await .caused_by(trc::location!())? } else { @@ -176,8 +130,10 @@ impl PrincipalPropFind for Server { match property { DavProperty::WebDav(dav_property) => match dav_property { WebDavProperty::DisplayName => { - fields - .push(DavPropertyValue::new(property.clone(), description.clone())); + fields.push(DavPropertyValue::new( + property.clone(), + account.description().unwrap_or(account.name()).to_string(), + )); } WebDavProperty::ResourceType => { let resource_type = if !is_principal { @@ -205,7 +161,7 @@ impl PrincipalPropFind for Server { WebDavProperty::CurrentUserPrincipal => { fields.push(DavPropertyValue::new( property.clone(), - vec![access_token.current_user_principal()], + vec![access_account_info.current_user_principal()], )); } WebDavProperty::QuotaAvailableBytes if !is_principal => { @@ -216,7 +172,11 @@ impl PrincipalPropFind for Server { } WebDavProperty::SyncToken if !is_principal => { let sync_token = self - .fetch_dav_resources(access_token, account_id, collection.into()) + .fetch_dav_resources( + access_token.account_id(), + account_id, + collection.into(), + ) .await .caused_by(trc::location!())? .sync_token(); @@ -225,7 +185,11 @@ impl PrincipalPropFind for Server { } WebDavProperty::GetCTag if !is_principal => { let ctag = self - .fetch_dav_resources(access_token, account_id, collection.into()) + .fetch_dav_resources( + access_token.account_id(), + account_id, + collection.into(), + ) .await .caused_by(trc::location!())? .highest_change_id; @@ -241,7 +205,7 @@ impl PrincipalPropFind for Server { vec![Href(format!( "{}/{}/", DavResourceName::Principal.base_path(), - percent_encoding::utf8_percent_encode(&name, RFC_3986), + percent_encoding::utf8_percent_encode(account.name(), RFC_3986), ))], )); } @@ -286,15 +250,20 @@ impl PrincipalPropFind for Server { vec![Href(format!( "{}/{}/", DavResourceName::Principal.base_path(), - percent_encoding::utf8_percent_encode(&name, RFC_3986), + percent_encoding::utf8_percent_encode(account.name(), RFC_3986), ))], )); } PrincipalProperty::CalendarHomeSet => { - let hrefs = - build_home_set(self, access_token, name.as_ref(), account_id, true) - .await - .caused_by(trc::location!())?; + let hrefs = build_home_set( + self, + access_token, + account.name(), + account_id, + true, + ) + .await + .caused_by(trc::location!())?; fields.push(DavPropertyValue::new(property.clone(), hrefs)); response.set_namespace(Namespace::CalDav); @@ -303,7 +272,7 @@ impl PrincipalPropFind for Server { let hrefs = build_home_set( self, access_token, - name.as_ref(), + account.name(), account_id, false, ) @@ -321,19 +290,18 @@ impl PrincipalPropFind for Server { PrincipalProperty::CalendarUserAddressSet => { fields.push(DavPropertyValue::new( property.clone(), - emails - .iter() - .filter(|email| !email.starts_with("@")) - .take(1) - .map(|email| Href(format!("mailto:{email}",))) - .collect::>(), + vec![Href(format!("mailto:{}", account.name()))], )); response.set_namespace(Namespace::CalDav); } PrincipalProperty::CalendarUserType => { fields.push(DavPropertyValue::new( property.clone(), - typ.as_str().to_uppercase(), + if account.is_user_account() { + DavValue::String("INDIVIDUAL".to_string()) + } else { + DavValue::String("GROUP".to_string()) + }, )); response.set_namespace(Namespace::CalDav); } @@ -343,7 +311,7 @@ impl PrincipalPropFind for Server { vec![Href(format!( "{}/{}/inbox/", DavResourceName::Scheduling.base_path(), - percent_encoding::utf8_percent_encode(&name, RFC_3986), + percent_encoding::utf8_percent_encode(account.name(), RFC_3986), ))], )); response.set_namespace(Namespace::CalDav); @@ -354,7 +322,7 @@ impl PrincipalPropFind for Server { vec![Href(format!( "{}/{}/outbox/", DavResourceName::Scheduling.base_path(), - percent_encoding::utf8_percent_encode(&name, RFC_3986), + percent_encoding::utf8_percent_encode(account.name(), RFC_3986), ))], )); response.set_namespace(Namespace::CalDav); @@ -382,7 +350,7 @@ impl PrincipalPropFind for Server { Href(format!( "{}/{}/", base_path, - percent_encoding::utf8_percent_encode(&name, RFC_3986), + percent_encoding::utf8_percent_encode(account.name(), RFC_3986), )), prop_stats, )); @@ -410,20 +378,18 @@ impl PrincipalPropFind for Server { Ok(status.response.0.into_iter().next()) } - async fn owner_href(&self, access_token: &AccessToken, account_id: u32) -> trc::Result { - if access_token.account_id() == account_id { - Ok(access_token.current_user_principal()) + async fn owner_href(&self, account_info: &AccountInfo, account_id: u32) -> trc::Result { + if account_info.account_id() == account_id { + Ok(account_info.current_user_principal()) } else { - let name = self - .store() - .get_principal_name(account_id) + let account_info = self + .account_info(account_id) .await - .caused_by(trc::location!())? - .unwrap_or_else(|| format!("_{account_id}")); + .caused_by(trc::location!())?; Ok(Href(format!( "{}/{}/", DavResourceName::Principal.base_path(), - percent_encoding::utf8_percent_encode(&name, RFC_3986), + percent_encoding::utf8_percent_encode(account_info.name(), RFC_3986), ))) } } @@ -452,17 +418,15 @@ pub(crate) async fn build_home_set( if !server.core.groupware.assisted_discovery && account_id == access_token.account_id() { for account_id in access_token.all_ids_by_collection(collection) { if account_id != access_token.account_id() { - let other_name = server - .store() - .get_principal_name(account_id) + let other = server + .account_info(account_id) .await - .caused_by(trc::location!())? - .unwrap_or_else(|| format!("_{account_id}")); + .caused_by(trc::location!())?; hrefs.push(Href(format!( "{}/{}/", resource_name.base_path(), - percent_encoding::utf8_percent_encode(&other_name, RFC_3986), + percent_encoding::utf8_percent_encode(other.name(), RFC_3986), ))); } } diff --git a/crates/dav/src/principal/propsearch.rs b/crates/dav/src/principal/propsearch.rs index f29071f4..1669bfa5 100644 --- a/crates/dav/src/principal/propsearch.rs +++ b/crates/dav/src/principal/propsearch.rs @@ -11,10 +11,10 @@ use dav_proto::schema::{ request::{PrincipalPropertySearch, PropFind}, response::MultiStatus, }; -use directory::{Type, backend::internal::manage::ManageDirectory}; use http_proto::HttpResponse; use hyper::StatusCode; -use store::roaring::RoaringBitmap; +use registry::schema::prelude::{Object, Property}; +use store::{registry::RegistryQuery, roaring::RoaringBitmap}; use trc::AddContext; use types::collection::Collection; @@ -46,22 +46,16 @@ impl PrincipalPropSearch for Server { let mut response = MultiStatus::new(Vec::with_capacity(16)); if let Some(search_for) = search_for { - // Return all principals - let principals = self - .store() - .list_principals( - search_for.as_str().into(), - access_token.tenant_id(), - &[Type::Individual, Type::Group], - false, - 0, - 0, + let ids = self + .registry() + .query::( + RegistryQuery::new(Object::Account) + .equal_opt(Property::MemberTenantId, access_token.tenant_id()) + .text(search_for), ) .await .caused_by(trc::location!())?; - let ids = RoaringBitmap::from_iter(principals.items.into_iter().map(|p| p.id())); - if !ids.is_empty() { if request.properties.is_empty() { request diff --git a/crates/dav/src/request.rs b/crates/dav/src/request.rs index c386b315..2c09a7ce 100644 --- a/crates/dav/src/request.rs +++ b/crates/dav/src/request.rs @@ -47,10 +47,10 @@ use dav_proto::{ }, }, }; -use directory::Permission; use http_proto::{HttpRequest, HttpResponse, HttpSessionData, request::fetch_body}; use hyper::{StatusCode, header}; -use std::{sync::Arc, time::Instant}; +use registry::schema::enums::Permission; +use std::time::Instant; use trc::{EventType, LimitEvent, StoreEvent, WebDavEvent}; use types::collection::Collection; @@ -58,7 +58,7 @@ pub trait DavRequestHandler: Sync + Send { fn handle_dav_request( &self, request: HttpRequest, - access_token: Arc, + access_token: AccessToken, session: &HttpSessionData, resource: DavResourceName, method: DavMethod, @@ -69,7 +69,7 @@ pub(crate) trait DavRequestDispatcher: Sync + Send { fn dispatch_dav_request( &self, headers: &RequestHeaders<'_>, - access_token: Arc, + access_token: AccessToken, resource: DavResourceName, method: DavMethod, body: Vec, @@ -80,7 +80,7 @@ impl DavRequestDispatcher for Server { async fn dispatch_dav_request( &self, headers: &RequestHeaders<'_>, - access_token: Arc, + access_token: AccessToken, resource: DavResourceName, method: DavMethod, body: Vec, @@ -96,7 +96,8 @@ impl DavRequestDispatcher for Server { DavMethod::GET | DavMethod::HEAD => match resource { DavResourceName::Card => { // Validate permissions - access_token.assert_has_permission(Permission::DavCardGet)?; + let access_token = + access_token.assert_has_permission(Permission::DavCardGet)?; self.handle_card_get_request( &access_token, @@ -107,7 +108,7 @@ impl DavRequestDispatcher for Server { } DavResourceName::Cal => { // Validate permissions - access_token.assert_has_permission(Permission::DavCalGet)?; + let access_token = access_token.assert_has_permission(Permission::DavCalGet)?; self.handle_calendar_get_request( &access_token, @@ -118,7 +119,8 @@ impl DavRequestDispatcher for Server { } DavResourceName::File => { // Validate permissions - access_token.assert_has_permission(Permission::DavFileGet)?; + let access_token = + access_token.assert_has_permission(Permission::DavFileGet)?; // Deal with Litmus bug /*self.handle_file_get_request( @@ -137,7 +139,7 @@ impl DavRequestDispatcher for Server { } DavResourceName::Scheduling => { // Validate permissions - access_token.assert_has_permission(Permission::DavCalGet)?; + let access_token = access_token.assert_has_permission(Permission::DavCalGet)?; self.handle_scheduling_get_request( &access_token, @@ -151,7 +153,8 @@ impl DavRequestDispatcher for Server { DavMethod::REPORT => match Report::parse(&mut Tokenizer::new(&body))? { Report::SyncCollection(sync_collection) => { // Validate permissions - access_token.assert_has_permission(Permission::DavSyncCollection)?; + let access_token = + access_token.assert_has_permission(Permission::DavSyncCollection)?; let uri = self .validate_uri(&access_token, headers.uri) @@ -186,7 +189,8 @@ impl DavRequestDispatcher for Server { .with_details("The administrator has disabled directory queries."), )); } - access_token.assert_has_permission(Permission::DavPrincipalAcl)?; + let access_token = + access_token.assert_has_permission(Permission::DavPrincipalAcl)?; self.handle_acl_prop_set(&access_token, headers, report) .await @@ -204,7 +208,8 @@ impl DavRequestDispatcher for Server { .with_details("The administrator has disabled directory queries."), )); } - access_token.assert_has_permission(Permission::DavPrincipalMatch)?; + let access_token = + access_token.assert_has_permission(Permission::DavPrincipalMatch)?; self.handle_principal_match(&access_token, headers, report) .await @@ -224,7 +229,8 @@ impl DavRequestDispatcher for Server { )); } - access_token.assert_has_permission(Permission::DavPrincipalSearch)?; + let access_token = + access_token.assert_has_permission(Permission::DavPrincipalSearch)?; self.handle_principal_property_search(&access_token, report) .await @@ -251,14 +257,16 @@ impl DavRequestDispatcher for Server { } Report::AddressbookQuery(report) => { // Validate permissions - access_token.assert_has_permission(Permission::DavCardQuery)?; + let access_token = + access_token.assert_has_permission(Permission::DavCardQuery)?; self.handle_card_query_request(&access_token, headers, report) .await } Report::AddressbookMultiGet(report) => { // Validate permissions - access_token.assert_has_permission(Permission::DavCardMultiGet)?; + let access_token = + access_token.assert_has_permission(Permission::DavCardMultiGet)?; self.handle_dav_query( &access_token, @@ -268,14 +276,16 @@ impl DavRequestDispatcher for Server { } Report::CalendarQuery(report) => { // Validate permissions - access_token.assert_has_permission(Permission::DavCalQuery)?; + let access_token = + access_token.assert_has_permission(Permission::DavCalQuery)?; self.handle_calendar_query_request(&access_token, headers, report) .await } Report::CalendarMultiGet(report) => { // Validate permissions - access_token.assert_has_permission(Permission::DavCalMultiGet)?; + let access_token = + access_token.assert_has_permission(Permission::DavCalMultiGet)?; self.handle_dav_query( &access_token, @@ -285,7 +295,8 @@ impl DavRequestDispatcher for Server { } Report::FreeBusyQuery(report) => { // Validate permissions - access_token.assert_has_permission(Permission::DavCalFreeBusyQuery)?; + let access_token = + access_token.assert_has_permission(Permission::DavCalFreeBusyQuery)?; self.handle_calendar_freebusy_request(&access_token, headers, report) .await @@ -297,7 +308,8 @@ impl DavRequestDispatcher for Server { .and_then(|d| d.into_owned_uri())?; // Validate permissions - access_token.assert_has_permission(Permission::DavExpandProperty)?; + let access_token = + access_token.assert_has_permission(Permission::DavExpandProperty)?; match resource { DavResourceName::Card | DavResourceName::Cal | DavResourceName::File => { @@ -318,21 +330,24 @@ impl DavRequestDispatcher for Server { match resource { DavResourceName::Card => { // Validate permissions - access_token.assert_has_permission(Permission::DavCardPropPatch)?; + let access_token = + access_token.assert_has_permission(Permission::DavCardPropPatch)?; self.handle_card_proppatch_request(&access_token, headers, request) .await } DavResourceName::Cal => { // Validate permissions - access_token.assert_has_permission(Permission::DavCalPropPatch)?; + let access_token = + access_token.assert_has_permission(Permission::DavCalPropPatch)?; self.handle_calendar_proppatch_request(&access_token, headers, request) .await } DavResourceName::File => { // Validate permissions - access_token.assert_has_permission(Permission::DavFilePropPatch)?; + let access_token = + access_token.assert_has_permission(Permission::DavFilePropPatch)?; self.handle_file_proppatch_request(&access_token, headers, request) .await @@ -352,21 +367,24 @@ impl DavRequestDispatcher for Server { match resource { DavResourceName::Card => { // Validate permissions - access_token.assert_has_permission(Permission::DavCardMkCol)?; + let access_token = + access_token.assert_has_permission(Permission::DavCardMkCol)?; self.handle_card_mkcol_request(&access_token, headers, request) .await } DavResourceName::Cal => { // Validate permissions - access_token.assert_has_permission(Permission::DavCalMkCol)?; + let access_token = + access_token.assert_has_permission(Permission::DavCalMkCol)?; self.handle_calendar_mkcol_request(&access_token, headers, request) .await } DavResourceName::File => { // Validate permissions - access_token.assert_has_permission(Permission::DavFileMkCol)?; + let access_token = + access_token.assert_has_permission(Permission::DavFileMkCol)?; self.handle_file_mkcol_request(&access_token, headers, request) .await @@ -379,28 +397,32 @@ impl DavRequestDispatcher for Server { DavMethod::DELETE => match resource { DavResourceName::Card => { // Validate permissions - access_token.assert_has_permission(Permission::DavCardDelete)?; + let access_token = + access_token.assert_has_permission(Permission::DavCardDelete)?; self.handle_card_delete_request(&access_token, headers) .await } DavResourceName::Cal => { // Validate permissions - access_token.assert_has_permission(Permission::DavCalDelete)?; + let access_token = + access_token.assert_has_permission(Permission::DavCalDelete)?; self.handle_calendar_delete_request(&access_token, headers) .await } DavResourceName::File => { // Validate permissions - access_token.assert_has_permission(Permission::DavFileDelete)?; + let access_token = + access_token.assert_has_permission(Permission::DavFileDelete)?; self.handle_file_delete_request(&access_token, headers) .await } DavResourceName::Scheduling => { // Validate permissions - access_token.assert_has_permission(Permission::DavCalDelete)?; + let access_token = + access_token.assert_has_permission(Permission::DavCalDelete)?; self.handle_scheduling_delete_request(&access_token, headers) .await @@ -410,7 +432,8 @@ impl DavRequestDispatcher for Server { DavMethod::PUT | DavMethod::POST | DavMethod::PATCH => match resource { DavResourceName::Card => { // Validate permissions - access_token.assert_has_permission(Permission::DavCardPut)?; + let access_token = + access_token.assert_has_permission(Permission::DavCardPut)?; self.handle_card_update_request( &access_token, @@ -422,7 +445,7 @@ impl DavRequestDispatcher for Server { } DavResourceName::Cal => { // Validate permissions - access_token.assert_has_permission(Permission::DavCalPut)?; + let access_token = access_token.assert_has_permission(Permission::DavCalPut)?; self.handle_calendar_update_request( &access_token, @@ -434,7 +457,8 @@ impl DavRequestDispatcher for Server { } DavResourceName::File => { // Validate permissions - access_token.assert_has_permission(Permission::DavFilePut)?; + let access_token = + access_token.assert_has_permission(Permission::DavFilePut)?; self.handle_file_update_request( &access_token, @@ -446,7 +470,8 @@ impl DavRequestDispatcher for Server { } DavResourceName::Scheduling => { // Validate permissions - access_token.assert_has_permission(Permission::DavCalFreeBusyQuery)?; + let access_token = + access_token.assert_has_permission(Permission::DavCalFreeBusyQuery)?; self.handle_scheduling_post_request(&access_token, headers, body) .await @@ -458,7 +483,7 @@ impl DavRequestDispatcher for Server { match resource { DavResourceName::Card => { // Validate permissions - access_token.assert_has_permission(if is_move { + let access_token = access_token.assert_has_permission(if is_move { Permission::DavCardMove } else { Permission::DavCardCopy @@ -469,7 +494,7 @@ impl DavRequestDispatcher for Server { } DavResourceName::Cal => { // Validate permissions - access_token.assert_has_permission(if is_move { + let access_token = access_token.assert_has_permission(if is_move { Permission::DavCalMove } else { Permission::DavCalCopy @@ -479,7 +504,7 @@ impl DavRequestDispatcher for Server { } DavResourceName::File => { // Validate permissions - access_token.assert_has_permission(if is_move { + let access_token = access_token.assert_has_permission(if is_move { Permission::DavFileMove } else { Permission::DavFileCopy @@ -496,7 +521,8 @@ impl DavRequestDispatcher for Server { DavMethod::MKCALENDAR => match resource { DavResourceName::Cal => { // Validate permissions - access_token.assert_has_permission(Permission::DavCalMkCol)?; + let access_token = + access_token.assert_has_permission(Permission::DavCalMkCol)?; self.handle_calendar_mkcol_request( &access_token, @@ -509,7 +535,7 @@ impl DavRequestDispatcher for Server { }, DavMethod::LOCK => { // Validate permissions - access_token.assert_has_permission(match resource { + let access_token = access_token.assert_has_permission(match resource { DavResourceName::File => Permission::DavFileLock, DavResourceName::Cal => Permission::DavCalLock, DavResourceName::Card => Permission::DavCardLock, @@ -529,7 +555,7 @@ impl DavRequestDispatcher for Server { } DavMethod::UNLOCK => { // Validate permissions - access_token.assert_has_permission(match resource { + let access_token = access_token.assert_has_permission(match resource { DavResourceName::File => Permission::DavFileLock, DavResourceName::Cal => Permission::DavCalLock, DavResourceName::Card => Permission::DavCardLock, @@ -541,7 +567,7 @@ impl DavRequestDispatcher for Server { } DavMethod::ACL => { // Validate permissions - access_token.assert_has_permission(match resource { + let access_token = access_token.assert_has_permission(match resource { DavResourceName::File => Permission::DavFileAcl, DavResourceName::Cal => Permission::DavCalAcl, DavResourceName::Card => Permission::DavCardAcl, @@ -564,7 +590,7 @@ impl DavRequestHandler for Server { async fn handle_dav_request( &self, mut request: HttpRequest, - access_token: Arc, + access_token: AccessToken, session: &HttpSessionData, resource: DavResourceName, method: DavMethod, diff --git a/crates/email/Cargo.toml b/crates/email/Cargo.toml index 94660c13..bf4cd97a 100644 --- a/crates/email/Cargo.toml +++ b/crates/email/Cargo.toml @@ -13,6 +13,7 @@ common = { path = "../common" } directory = { path = "../directory" } groupware = { path = "../groupware" } spam-filter = { path = "../spam-filter" } +registry = { path = "../registry" } smtp-proto = { version = "0.2", features = ["rkyv"] } mail-parser = { version = "0.11", features = ["full_encoding"] } mail-builder = { version = "0.4" } diff --git a/crates/email/src/cache/mod.rs b/crates/email/src/cache/mod.rs index 924915d4..59d0fa9c 100644 --- a/crates/email/src/cache/mod.rs +++ b/crates/email/src/cache/mod.rs @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use common::{CacheSwap, MessageStoreCache, Server}; +use common::{MessageStoreCache, Server, UpdateLock, cache::LockResult}; use email::{full_email_cache_build, update_email_cache}; use mailbox::{full_mailbox_cache_build, update_mailbox_cache}; use std::{collections::hash_map::Entry, sync::Arc, time::Instant}; @@ -12,7 +12,6 @@ use store::{ ahash::AHashMap, query::log::{Change, Query}, }; -use tokio::sync::Semaphore; use trc::{AddContext, StoreEvent}; use types::collection::SyncCollection; @@ -28,23 +27,15 @@ pub trait MessageCacheFetch: Sync + Send { impl MessageCacheFetch for Server { async fn get_cached_messages(&self, account_id: u32) -> trc::Result> { - let cache_ = match self - .inner - .cache - .messages - .get_value_or_guard_async(&account_id) - .await - { + let cache_store = &self.inner.cache.messages; + let mut cache = match cache_store.get_value_or_guard_async(&account_id).await { Ok(cache) => cache, Err(guard) => { let start_time = Instant::now(); - let cache = full_cache_build(self, account_id, Arc::new(Semaphore::new(1))).await?; + let cache = full_cache_build(self, account_id, Arc::new(UpdateLock::new())).await?; - if guard.insert(CacheSwap::new(cache.clone())).is_err() { - self.inner - .cache - .messages - .insert(account_id, CacheSwap::new(cache.clone())); + if guard.insert(cache.clone()).is_err() { + cache_store.update(account_id, cache.clone()); } trc::event!( @@ -61,7 +52,6 @@ impl MessageCacheFetch for Server { }; // Obtain current state - let cache = cache_.load_full(); let start_time = Instant::now(); let changes = self .core @@ -78,7 +68,7 @@ impl MessageCacheFetch for Server { // Regenerate cache if the change log has been truncated if changes.is_truncated { let cache = full_cache_build(self, account_id, cache.update_lock.clone()).await?; - cache_.update(cache.clone()); + cache_store.update(account_id, cache.clone()); trc::event!( Store(StoreEvent::CacheStale), @@ -106,21 +96,26 @@ impl MessageCacheFetch for Server { } // Lock for updates - let _permit = cache.update_lock.acquire().await; - let cache = cache_.0.load(); - let mut cache = if cache.last_change_id >= changes.to_change_id { - trc::event!( - Store(StoreEvent::CacheHit), - AccountId = account_id, - Collection = SyncCollection::Email.as_str(), - ChangeId = cache.last_change_id, - Elapsed = start_time.elapsed(), - ); + let lock = cache.update_lock.clone(); + let _permit = match lock.acquire(cache.last_change_id).await? { + LockResult::Acquired(permit) => permit, + LockResult::Stale(permit) => { + cache = cache_store.peek(&account_id).unwrap_or(cache.clone()); + if cache.last_change_id >= changes.to_change_id { + trc::event!( + Store(StoreEvent::CacheHit), + AccountId = account_id, + Collection = SyncCollection::Email.as_str(), + ChangeId = cache.last_change_id, + Elapsed = start_time.elapsed(), + ); + return Ok(cache); + } - return Ok(cache.clone()); - } else { - cache.as_ref().clone() + permit + } }; + let mut cache = cache.as_ref().clone(); let mut changed_items: AHashMap = AHashMap::with_capacity(changes.changes.len()); let mut changed_containers: AHashMap = @@ -183,8 +178,9 @@ impl MessageCacheFetch for Server { cache.size = cache.emails.size + cache.mailboxes.size; cache.last_change_id = changes.to_change_id; + cache.update_lock.set_revision(cache.last_change_id); let cache = Arc::new(cache); - cache_.update(cache.clone()); + cache_store.update(account_id, cache.clone()); trc::event!( Store(StoreEvent::CacheUpdate), @@ -203,7 +199,7 @@ impl MessageCacheFetch for Server { async fn full_cache_build( server: &Server, account_id: u32, - update_lock: Arc, + update_lock: Arc, ) -> trc::Result> { let last_change_id = server .core @@ -218,6 +214,7 @@ async fn full_cache_build( let size = emails.size + mailboxes.size; emails.change_id = last_change_id; mailboxes.change_id = last_change_id; + update_lock.set_revision(last_change_id); Ok(Arc::new(MessageStoreCache { update_lock, diff --git a/crates/email/src/mailbox/destroy.rs b/crates/email/src/mailbox/destroy.rs index f609624a..09fca5b2 100644 --- a/crates/email/src/mailbox/destroy.rs +++ b/crates/email/src/mailbox/destroy.rs @@ -119,7 +119,7 @@ impl MailboxDestroy for Server { .with_document(message_id) .custom( ObjectIndexBuilder::<_, ()>::new() - .with_access_token(access_token) + .with_changed_by(access_token.account_tenant_ids()) .with_current(prev_message_data), ) .caused_by(trc::location!())? @@ -157,7 +157,7 @@ impl MailboxDestroy for Server { .with_document(message_id) .custom( ObjectIndexBuilder::new() - .with_access_token(access_token) + .with_changed_by(access_token.account_tenant_ids()) .with_changes(new_message_data) .with_current(prev_message_data), ) diff --git a/crates/email/src/mailbox/manage.rs b/crates/email/src/mailbox/manage.rs index 6b1c6a30..927e8f62 100644 --- a/crates/email/src/mailbox/manage.rs +++ b/crates/email/src/mailbox/manage.rs @@ -39,7 +39,7 @@ impl MailboxFnc for Server { // Create mailboxes let mut last_document_id = ARCHIVE_ID; - for folder in &self.core.jmap.default_folders { + for folder in &self.core.email.default_folders { let document_id = match folder.special_use { SpecialUse::Inbox => INBOX_ID, SpecialUse::Trash => TRASH_ID, @@ -124,7 +124,7 @@ impl MailboxFnc for Server { if !create_paths.is_empty() { if create_paths .iter() - .any(|name| name.len() > self.core.jmap.mailbox_name_max_len) + .any(|name| name.len() > self.core.email.mailbox_name_max_len) { return Ok(None); } diff --git a/crates/email/src/message/copy.rs b/crates/email/src/message/copy.rs index 09dd734a..f8d6ac31 100644 --- a/crates/email/src/message/copy.rs +++ b/crates/email/src/message/copy.rs @@ -18,7 +18,7 @@ use crate::{ }, }, }; -use common::{Server, auth::ResourceToken, storage::index::ObjectIndexBuilder}; +use common::{Server, storage::index::ObjectIndexBuilder}; use mail_parser::parsers::fields::thread::thread_name; use store::write::{ BatchBuilder, IndexPropertyClass, SearchIndex, TaskEpoch, TaskQueueClass, ValueClass, @@ -47,7 +47,7 @@ pub trait EmailCopy: Sync + Send { &self, from_account_id: u32, from_message_id: u32, - resource_token: &ResourceToken, + to_account_id: u32, mailboxes: Vec, keywords: Vec, received_at: Option, @@ -61,14 +61,13 @@ impl EmailCopy for Server { &self, from_account_id: u32, from_message_id: u32, - resource_token: &ResourceToken, + to_account_id: u32, mailboxes: Vec, keywords: Vec, received_at: Option, session_id: u64, ) -> trc::Result> { // Obtain metadata - let account_id = resource_token.account_id; let mut metadata = if let Some(metadata) = self .store() .get_value::>(ValueKey::property( @@ -88,13 +87,13 @@ impl EmailCopy for Server { // Check quota let size = metadata.root_part().offset_end; - match self.has_available_quota(resource_token, size as u64).await { + match self.has_available_quota(to_account_id, size as u64).await { Ok(_) => (), Err(err) => { if err.matches(trc::EventType::Limit(trc::LimitEvent::Quota)) || err.matches(trc::EventType::Limit(trc::LimitEvent::TenantQuota)) { - trc::error!(err.account_id(account_id).span_id(session_id)); + trc::error!(err.account_id(to_account_id).span_id(session_id)); return Ok(Err(CopyMessageError::OverQuota)); } else { return Err(err); @@ -144,7 +143,7 @@ impl EmailCopy for Server { // Obtain threadId let thread_result = self - .find_thread_id(account_id, subject, &message_ids) + .find_thread_id(to_account_id, subject, &message_ids) .await .caused_by(trc::location!())?; @@ -159,7 +158,7 @@ impl EmailCopy for Server { let mut mailbox_ids = Vec::with_capacity(mailboxes.len()); email.imap_uids = Vec::with_capacity(mailboxes.len()); let mut ids = self - .assign_email_ids(account_id, mailboxes.iter().copied(), true) + .assign_email_ids(to_account_id, mailboxes.iter().copied(), true) .await .caused_by(trc::location!())?; let document_id = ids.next().unwrap(); @@ -170,9 +169,10 @@ impl EmailCopy for Server { // Prepare batch let mut batch = BatchBuilder::new(); - batch.with_account_id(account_id); + batch.with_account_id(to_account_id); // Determine thread id + let tenant_id = self.account_info(to_account_id).await?.tenant_id(); let thread_id = if let Some(thread_id) = thread_result.thread_id { thread_id } else { @@ -187,7 +187,7 @@ impl EmailCopy for Server { .with_document(document_id) .custom( ObjectIndexBuilder::<(), _>::new() - .with_tenant_id(resource_token.tenant.map(|t| t.id)) + .with_tenant_id(tenant_id) .with_changes(MessageData { mailboxes: mailbox_ids.into_boxed_slice(), keywords: keywords.into_boxed_slice(), @@ -232,7 +232,7 @@ impl EmailCopy for Server { .write(batch.build_all()) .await .caused_by(trc::location!())? - .last_change_id(account_id)?; + .last_change_id(to_account_id)?; // Request indexing self.notify_task_queue(); @@ -244,7 +244,7 @@ impl EmailCopy for Server { email.blob_id = BlobId::new( blob_hash, BlobClass::Linked { - account_id, + account_id: to_account_id, collection: Collection::Email.into(), document_id, }, diff --git a/crates/email/src/message/delete.rs b/crates/email/src/message/delete.rs index e4424fba..c956c0b5 100644 --- a/crates/email/src/message/delete.rs +++ b/crates/email/src/message/delete.rs @@ -6,10 +6,11 @@ use super::metadata::MessageData; use common::{KV_LOCK_PURGE_ACCOUNT, Server, storage::index::ObjectIndexBuilder}; -use directory::backend::internal::manage::ManageDirectory; use groupware::calendar::storage::ItipAutoExpunge; +use registry::schema::prelude::Object; use std::future::Future; use store::rand::prelude::SliceRandom; +use store::registry::RegistryQuery; use store::write::key::DeserializeBigEndian; use store::write::{IndexPropertyClass, SearchIndex, TaskEpoch, TaskQueueClass, now}; use store::{IterateParams, SerializeInfallible, U32_LEN, U64_LEN, ValueKey}; @@ -110,25 +111,30 @@ impl EmailDeletion for Server { } async fn purge_accounts(&self, use_roles: bool) { - if let Ok(account_ids) = self.store().principal_ids(None, None).await { - let mut account_ids: Vec = account_ids - .into_iter() - .filter(|id| { - !use_roles + match self + .registry() + .query::>(RegistryQuery::new(Object::Account)) + .await + { + Ok(mut account_ids) => { + // Shuffle account ids + account_ids.shuffle(&mut store::rand::rng()); + + for account_id in account_ids { + if !use_roles || self .core .network .roles .purge_accounts - .is_enabled_for_integer(*id) - }) - .collect(); - - // Shuffle account ids - account_ids.shuffle(&mut store::rand::rng()); - - for account_id in account_ids { - self.purge_account(account_id).await; + .is_enabled_for_integer(account_id) + { + self.purge_account(account_id).await; + } + } + } + Err(err) => { + trc::error!(err.caused_by(trc::location!())); } } } @@ -136,9 +142,7 @@ impl EmailDeletion for Server { async fn purge_account(&self, account_id: u32) { // Lock account match self - .core - .storage - .lookup + .in_memory_store() .try_lock(KV_LOCK_PURGE_ACCOUNT, &account_id.to_be_bytes(), 3600) .await { @@ -157,7 +161,7 @@ impl EmailDeletion for Server { } // Auto-expunge deleted and junk messages - if let Some(hold_period) = self.core.jmap.mail_autoexpunge_after + if let Some(hold_period) = self.core.email.mail_autoexpunge_after && let Err(err) = self.emails_auto_expunge(account_id, hold_period).await { trc::error!( @@ -177,7 +181,7 @@ impl EmailDeletion for Server { } // Delete old e-mail submissions - if let Some(hold_period) = self.core.jmap.email_submission_autoexpunge_after + if let Some(hold_period) = self.core.email.email_submission_autoexpunge_after && let Err(err) = self.purge_email_submissions(account_id, hold_period).await { trc::error!( @@ -190,8 +194,8 @@ impl EmailDeletion for Server { if let Err(err) = self .delete_changes( account_id, - self.core.jmap.changes_max_history, - self.core.jmap.share_notification_max_history, + self.core.email.changes_max_history, + self.core.email.share_notification_max_history, ) .await { @@ -258,11 +262,10 @@ impl EmailDeletion for Server { // Delete messages let mut batch = BatchBuilder::new(); let tenant_id = self - .store() - .get_principal(account_id) + .account_info(account_id) .await .caused_by(trc::location!())? - .and_then(|p| p.tenant()); + .tenant_id(); self.emails_delete(account_id, tenant_id, &mut batch, destroy_ids) .await?; self.commit_batch(batch).await?; diff --git a/crates/email/src/message/delivery.rs b/crates/email/src/message/delivery.rs index 35e6a125..2f2d5abd 100644 --- a/crates/email/src/message/delivery.rs +++ b/crates/email/src/message/delivery.rs @@ -8,10 +8,11 @@ use super::ingest::{EmailIngest, IngestEmail, IngestSource}; use crate::{mailbox::INBOX_ID, sieve::ingest::SieveScriptIngest}; use common::{ Server, + auth::BuildAccessToken, ipc::{EmailPush, PushNotification}, }; -use directory::Permission; use mail_parser::MessageParser; +use registry::schema::enums::Permission; use std::{borrow::Cow, future::Future}; use store::ahash::AHashMap; use types::blob_hash::BlobHash; @@ -117,14 +118,7 @@ impl MailDelivery for Server { }; for rcpt in message.recipients { - let account_id = match self - .email_to_id( - &self.core.storage.directory, - &rcpt.address, - message.session_id, - ) - .await - { + let account_id = match self.account_id(&rcpt.address).await { Ok(Some(account_id)) => account_id, Ok(None) => { // Something went wrong @@ -156,10 +150,10 @@ impl MailDelivery for Server { } // Obtain access token - let status = match self.get_access_token(account_id).await.and_then(|token| { + let status = match self.access_token(account_id).await.and_then(|token| { token + .build() .assert_has_permission(Permission::EmailReceive) - .map(|_| token) }) { Ok(access_token) => { // Check if there is an active sieve script diff --git a/crates/email/src/message/ingest.rs b/crates/email/src/message/ingest.rs index a0faac41..10f18ae3 100644 --- a/crates/email/src/message/ingest.rs +++ b/crates/email/src/message/ingest.rs @@ -15,7 +15,6 @@ use crate::{ }, }; use common::{Server, auth::AccessToken}; -use directory::Permission; use groupware::{ calendar::itip::{ItipIngest, ItipIngestError}, scheduling::{ItipError, ItipMessages}, @@ -24,6 +23,7 @@ use mail_parser::{ DateTime, Header, HeaderName, HeaderValue, Message, MessageParser, MimeHeaders, PartType, parsers::fields::thread::thread_name, }; +use registry::schema::prelude::Permission; use std::{borrow::Cow, cmp::Ordering, fmt::Write, time::Instant}; use std::{future::Future, hash::Hasher}; use store::write::{AlignedBytes, Archive}; @@ -131,11 +131,10 @@ impl EmailIngest for Server { async fn email_ingest(&self, mut params: IngestEmail<'_>) -> trc::Result { // Check quota let start_time = Instant::now(); - let account_id = params.access_token.account_id; - let tenant_id = params.access_token.tenant.map(|t| t.id); + let account_id = params.access_token.account_id(); + let tenant_id = params.access_token.tenant_id(); let mut raw_message_len = params.raw_message.len() as u64; - let resource_token = params.access_token.as_resource_token(); - self.has_available_quota(&resource_token, raw_message_len) + self.has_available_quota(account_id, raw_message_len) .await .caused_by(trc::location!())?; @@ -355,6 +354,10 @@ impl EmailIngest for Server { .access_token .has_permission(Permission::CalendarSchedulingReceive) { + let account_info = self + .account_info(account_id) + .await + .caused_by(trc::location!())?; let mut sender = None; for part in &message.parts { if part.content_type().is_some_and(|ct| { @@ -375,8 +378,7 @@ impl EmailIngest for Server { }) { match self .itip_ingest( - params.access_token, - &resource_token, + &account_info, sender, deliver_to, itip_message, @@ -479,9 +481,9 @@ impl EmailIngest for Server { // Encrypt message let do_encrypt = match params.source { IngestSource::Jmap { .. } | IngestSource::Imap { .. } => { - self.core.jmap.encrypt && self.core.jmap.encrypt_append + self.core.email.encrypt && self.core.email.encrypt_append } - IngestSource::Smtp { .. } => self.core.jmap.encrypt, + IngestSource::Smtp { .. } => self.core.email.encrypt, IngestSource::Restore => false, }; let is_encrypted = if do_encrypt diff --git a/crates/email/src/sieve/delete.rs b/crates/email/src/sieve/delete.rs index 49fbe75d..730fb43e 100644 --- a/crates/email/src/sieve/delete.rs +++ b/crates/email/src/sieve/delete.rs @@ -54,7 +54,7 @@ impl SieveScriptDelete for Server { obj_.to_unarchived::() .caused_by(trc::location!())?, ) - .with_access_token(access_token), + .with_changed_by(access_token.account_tenant_ids()), ) .caused_by(trc::location!())? .commit_point(); diff --git a/crates/email/src/sieve/ingest.rs b/crates/email/src/sieve/ingest.rs index 2fed5224..3498eb8c 100644 --- a/crates/email/src/sieve/ingest.rs +++ b/crates/email/src/sieve/ingest.rs @@ -14,7 +14,6 @@ use crate::{ }, }; use common::{Server, auth::AccessToken, scripts::plugins::PluginContext}; -use directory::QueryParams; use mail_parser::MessageParser; use sieve::{Envelope, Event, Input, Mailbox, Recipient, Sieve, SpamStatus}; use std::{borrow::Cow, sync::Arc}; @@ -108,7 +107,7 @@ impl SieveScriptIngest for Server { }; // Obtain mailboxIds - let account_id = access_token.account_id; + let account_id = access_token.account_id(); let mut cache = self .get_cached_messages(account_id) .await @@ -118,20 +117,16 @@ impl SieveScriptIngest for Server { let mut instance = self.core.sieve.untrusted_runtime.filter_parsed(message); // Set account name and email - let mail_from = self - .core - .storage - .directory - .query(QueryParams::id(account_id).with_return_member_of(false)) + let account_info = self + .account_info(account_id) .await - .caused_by(trc::location!())? - .and_then(|p| { - instance.set_user_full_name(p.description().unwrap_or_else(|| p.name())); - p.into_primary_email() - }); - - // Set account address - let mail_from = mail_from.unwrap_or_else(|| envelope_to.address.as_str().into()); + .caused_by(trc::location!())?; + let mail_from = account_info.name().to_string(); + instance.set_user_full_name( + account_info + .description() + .unwrap_or_else(|| account_info.name()), + ); instance.set_user_address(&mail_from); // Set envelope @@ -401,7 +396,7 @@ impl SieveScriptIngest for Server { } }; - if message.raw_message.len() <= self.core.jmap.mail_max_size { + if message.raw_message.len() <= self.core.email.mail_max_size { trc::event!( Sieve(SieveEvent::SendMessage), From = mail_from.clone(), @@ -427,7 +422,7 @@ impl SieveScriptIngest for Server { .map(|r| trc::Value::String(r.as_str().into())) .collect::>(), Size = message.raw_message.len(), - Limit = self.core.jmap.mail_max_size, + Limit = self.core.email.mail_max_size, SpanId = session_id, ); } diff --git a/crates/groupware/src/cache/calcard.rs b/crates/groupware/src/cache/calcard.rs index e43e969d..78ab5833 100644 --- a/crates/groupware/src/cache/calcard.rs +++ b/crates/groupware/src/cache/calcard.rs @@ -16,12 +16,10 @@ use crate::{ use calcard::common::timezone::Tz; use common::{ DavName, DavPath, DavResource, DavResourceMetadata, DavResources, Server, - TinyCalendarPreferences, auth::AccessToken, + TinyCalendarPreferences, UpdateLock, }; -use directory::backend::internal::manage::ManageDirectory; use std::sync::Arc; use store::ahash::{AHashMap, AHashSet}; -use tokio::sync::Semaphore; use trc::AddContext; use types::{ acl::AclGrant, @@ -31,20 +29,20 @@ use utils::map::bitmap::Bitmap; pub(super) async fn build_calcard_resources( server: &Server, - access_token: &AccessToken, + access_account_id: u32, account_id: u32, sync_collection: SyncCollection, container_collection: Collection, item_collection: Collection, - update_lock: Arc, + update_lock: Arc, ) -> trc::Result { let is_calendar = matches!(sync_collection, SyncCollection::Calendar); - let name = server - .store() - .get_principal_name(account_id) - .await - .caused_by(trc::location!())? - .unwrap_or_else(|| format!("_{account_id}")); + let owner_account_info = server.account_info(account_id).await?; + let access_account_info = if account_id == access_account_id { + owner_account_info.clone() + } else { + server.account_info(access_account_id).await? + }; let mut cache = DavResources { base_path: format!( "{}/{}/", @@ -54,7 +52,7 @@ pub(super) async fn build_calcard_resources( DavResourceName::Card } .base_path(), - percent_encoding::utf8_percent_encode(&name, RFC_3986), + percent_encoding::utf8_percent_encode(owner_account_info.name(), RFC_3986), ), paths: AHashSet::with_capacity(16), resources: Vec::with_capacity(16), @@ -78,6 +76,7 @@ pub(super) async fn build_calcard_resources( cache.item_change_id = last_change_id; cache.container_change_id = last_change_id; cache.highest_change_id = last_change_id; + cache.update_lock.set_revision(last_change_id); server .archives( @@ -113,11 +112,11 @@ pub(super) async fn build_calcard_resources( if is_first_check { if is_calendar { server - .create_default_calendar(access_token, account_id, &name) + .create_default_calendar(&access_account_info, &owner_account_info) .await?; } else { server - .create_default_addressbook(access_token, account_id, &name) + .create_default_addressbook(&access_account_info, &owner_account_info) .await?; } is_first_check = false; @@ -171,7 +170,7 @@ pub(super) async fn build_calcard_resources( pub(super) async fn build_scheduling_resources( server: &Server, account_id: u32, - update_lock: Arc, + update_lock: Arc, ) -> trc::Result { let last_change_id = server .core @@ -182,23 +181,18 @@ pub(super) async fn build_scheduling_resources( .caused_by(trc::location!())? .unwrap_or_default(); - let name = server - .store() - .get_principal_name(account_id) - .await - .caused_by(trc::location!())? - .unwrap_or_else(|| format!("_{account_id}")); - + let account_info = server.account_info(account_id).await?; let item_ids = server .itip_ids(account_id) .await .caused_by(trc::location!())?; + update_lock.set_revision(last_change_id); let mut cache = DavResources { base_path: format!( "{}/{}/", DavResourceName::Scheduling.base_path(), - percent_encoding::utf8_percent_encode(&name, RFC_3986), + percent_encoding::utf8_percent_encode(account_info.name(), RFC_3986), ), paths: AHashSet::with_capacity((2 + item_ids.len()) as usize), resources: Vec::with_capacity((2 + item_ids.len()) as usize), diff --git a/crates/groupware/src/cache/file.rs b/crates/groupware/src/cache/file.rs index a64088be..422981c1 100644 --- a/crates/groupware/src/cache/file.rs +++ b/crates/groupware/src/cache/file.rs @@ -8,11 +8,9 @@ use crate::{ DavResourceName, RFC_3986, file::{ArchivedFileNode, FileNode}, }; -use common::{DavPath, DavResource, DavResourceMetadata, DavResources, Server}; -use directory::backend::internal::manage::ManageDirectory; +use common::{DavPath, DavResource, DavResourceMetadata, DavResources, Server, UpdateLock}; use std::sync::Arc; use store::ahash::{AHashMap, AHashSet}; -use tokio::sync::Semaphore; use trc::AddContext; use types::{ acl::AclGrant, @@ -23,7 +21,7 @@ use utils::{map::bitmap::Bitmap, topological::TopologicalSort}; pub(super) async fn build_file_resources( server: &Server, account_id: u32, - update_lock: Arc, + update_lock: Arc, ) -> trc::Result { let last_change_id = server .core @@ -33,12 +31,7 @@ pub(super) async fn build_file_resources( .await .caused_by(trc::location!())? .unwrap_or_default(); - let name = server - .store() - .get_principal_name(account_id) - .await - .caused_by(trc::location!())? - .unwrap_or_else(|| format!("_{account_id}")); + let account_info = server.account_info(account_id).await?; let mut resources = Vec::with_capacity(16); server @@ -58,11 +51,12 @@ pub(super) async fn build_file_resources( .await .caused_by(trc::location!())?; + update_lock.set_revision(last_change_id); let mut files = DavResources { base_path: format!( "{}/{}/", DavResourceName::File.base_path(), - percent_encoding::utf8_percent_encode(&name, RFC_3986), + percent_encoding::utf8_percent_encode(account_info.name(), RFC_3986), ), size: std::mem::size_of::() as u64, paths: AHashSet::with_capacity(resources.len()), diff --git a/crates/groupware/src/cache/mod.rs b/crates/groupware/src/cache/mod.rs index 31f6bf46..7fdf8cf1 100644 --- a/crates/groupware/src/cache/mod.rs +++ b/crates/groupware/src/cache/mod.rs @@ -15,7 +15,7 @@ use calcard::{ build_calcard_resources, build_simple_hierarchy, resource_from_addressbook, resource_from_calendar, resource_from_card, resource_from_event, }; -use common::{CacheSwap, DavResource, DavResources, Server, auth::AccessToken}; +use common::{DavResource, DavResources, Server, UpdateLock, auth::AccountInfo, cache::LockResult}; use file::{build_file_resources, build_nested_hierarchy, resource_from_file}; use std::{sync::Arc, time::Instant}; use store::{ @@ -24,7 +24,6 @@ use store::{ query::log::{Change, Query}, write::{AlignedBytes, Archive, BatchBuilder, ValueClass}, }; -use tokio::sync::Semaphore; use trc::{AddContext, StoreEvent}; use types::{ collection::{Collection, SyncCollection}, @@ -37,28 +36,26 @@ pub mod file; pub trait GroupwareCache: Sync + Send { fn fetch_dav_resources( &self, - access_token: &AccessToken, + access_account_id: u32, account_id: u32, collection: SyncCollection, ) -> impl Future>> + Send; fn create_default_addressbook( &self, - access_token: &AccessToken, - account_id: u32, - account_name: &str, + account_info_access: &AccountInfo, + account_info_owner: &AccountInfo, ) -> impl Future>> + Send; fn create_default_calendar( &self, - access_token: &AccessToken, - account_id: u32, - account_name: &str, + account_info_access: &AccountInfo, + account_info_owner: &AccountInfo, ) -> impl Future>> + Send; fn get_or_create_default_calendar( &self, - access_token: &AccessToken, + access_account_id: u32, account_id: u32, ) -> impl Future>> + Send; @@ -72,7 +69,7 @@ pub trait GroupwareCache: Sync + Send { impl GroupwareCache for Server { async fn fetch_dav_resources( &self, - access_token: &AccessToken, + access_account_id: u32, account_id: u32, collection: SyncCollection, ) -> trc::Result> { @@ -83,7 +80,7 @@ impl GroupwareCache for Server { SyncCollection::CalendarEventNotification => &self.inner.cache.scheduling, _ => unreachable!(), }; - let cache_ = match cache_store.get_value_or_guard_async(&account_id).await { + let mut cache = match cache_store.get_value_or_guard_async(&account_id).await { Ok(cache) => cache, Err(guard) => { let start_time = Instant::now(); @@ -91,13 +88,13 @@ impl GroupwareCache for Server { self, account_id, collection, - Arc::new(Semaphore::new(1)), - access_token, + Arc::new(UpdateLock::new()), + access_account_id, ) .await?; - if guard.insert(CacheSwap::new(cache.clone())).is_err() { - cache_store.insert(account_id, CacheSwap::new(cache.clone())); + if guard.insert(cache.clone()).is_err() { + cache_store.update(account_id, cache.clone()); } trc::event!( @@ -114,7 +111,6 @@ impl GroupwareCache for Server { }; // Obtain current state - let cache = cache_.load_full(); let start_time = Instant::now(); let changes = self .core @@ -135,10 +131,10 @@ impl GroupwareCache for Server { account_id, collection, cache.update_lock.clone(), - access_token, + access_account_id, ) .await?; - cache_.update(cache.clone()); + cache_store.update(account_id, cache.clone()); trc::event!( Store(StoreEvent::CacheStale), @@ -166,19 +162,25 @@ impl GroupwareCache for Server { } // Lock for updates - let _permit = cache.update_lock.acquire().await; - let cache = cache_.load_full(); - if cache.highest_change_id >= changes.to_change_id { - trc::event!( - Store(StoreEvent::CacheHit), - AccountId = account_id, - Collection = collection.as_str(), - ChangeId = cache.highest_change_id, - Elapsed = start_time.elapsed(), - ); + let lock = cache.update_lock.clone(); + let _permit = match lock.acquire(cache.highest_change_id).await? { + LockResult::Acquired(permit) => permit, + LockResult::Stale(permit) => { + cache = cache_store.peek(&account_id).unwrap_or(cache.clone()); + if cache.highest_change_id >= changes.to_change_id { + trc::event!( + Store(StoreEvent::CacheHit), + AccountId = account_id, + Collection = collection.as_str(), + ChangeId = cache.highest_change_id, + Elapsed = start_time.elapsed(), + ); + return Ok(cache); + } - return Ok(cache); - } + permit + } + }; let num_changes = changes.changes.len(); let cache = if !matches!(collection, SyncCollection::CalendarEventNotification) { @@ -233,7 +235,7 @@ impl GroupwareCache for Server { .unwrap_or(cache.container_change_id), highest_change_id: changes.to_change_id, size: std::mem::size_of::() as u64, - update_lock: cache.update_lock.clone(), + update_lock: lock.clone(), }; if matches!(collection, SyncCollection::FileNode) { @@ -253,7 +255,7 @@ impl GroupwareCache for Server { .unwrap_or(cache.container_change_id), highest_change_id: changes.to_change_id, size: cache.size, - update_lock: cache.update_lock.clone(), + update_lock: lock.clone(), } } } else { @@ -300,8 +302,9 @@ impl GroupwareCache for Server { } }; + cache.update_lock.set_revision(cache.highest_change_id); let cache = Arc::new(cache); - cache_.update(cache.clone()); + cache_store.update(account_id, cache.clone()); trc::event!( Store(StoreEvent::CacheUpdate), @@ -318,12 +321,13 @@ impl GroupwareCache for Server { async fn create_default_addressbook( &self, - access_token: &AccessToken, - account_id: u32, - account_name: &str, + account_info_access: &AccountInfo, + account_info_owner: &AccountInfo, ) -> trc::Result> { if let Some(name) = &self.core.groupware.default_addressbook_name { let mut batch = BatchBuilder::new(); + let account_id = account_info_owner.account_id(); + let account_name = account_info_owner.name(); let document_id = self .store() .assign_document_ids(account_id, Collection::AddressBook, 1) @@ -345,7 +349,12 @@ impl GroupwareCache for Server { }], ..Default::default() } - .insert(access_token, account_id, document_id, &mut batch)?; + .insert( + account_info_access.account_tenant_ids(), + account_id, + document_id, + &mut batch, + )?; self.commit_batch(batch).await?; Ok(Some(document_id)) } else { @@ -355,12 +364,13 @@ impl GroupwareCache for Server { async fn create_default_calendar( &self, - access_token: &AccessToken, - account_id: u32, - account_name: &str, + account_info_access: &AccountInfo, + account_info_owner: &AccountInfo, ) -> trc::Result> { if let Some(name) = &self.core.groupware.default_calendar_name { let mut batch = BatchBuilder::new(); + let account_id = account_info_owner.account_id(); + let account_name = account_info_owner.name(); let document_id = self .store() .assign_document_ids(account_id, Collection::Calendar, 1) @@ -382,7 +392,12 @@ impl GroupwareCache for Server { }], ..Default::default() } - .insert(access_token, account_id, document_id, &mut batch)?; + .insert( + account_info_access.account_tenant_ids(), + account_id, + document_id, + &mut batch, + )?; // Set default calendar batch @@ -399,7 +414,7 @@ impl GroupwareCache for Server { async fn get_or_create_default_calendar( &self, - access_token: &AccessToken, + access_account_id: u32, account_id: u32, ) -> trc::Result> { let default_calendar_id = self @@ -415,12 +430,13 @@ impl GroupwareCache for Server { if default_calendar_id.is_some() { Ok(default_calendar_id) } else { - self.fetch_dav_resources(access_token, account_id, SyncCollection::Calendar) + self.fetch_dav_resources(access_account_id, account_id, SyncCollection::Calendar) .await .map(|c| c.document_ids(true).next()) } } + #[inline(always)] fn cached_dav_resources( &self, account_id: u32, @@ -433,7 +449,6 @@ impl GroupwareCache for Server { _ => unreachable!(), }) .get(&account_id) - .map(|cache| cache.load_full()) } } @@ -513,14 +528,14 @@ async fn full_cache_build( server: &Server, account_id: u32, collection: SyncCollection, - update_lock: Arc, - access_token: &AccessToken, + update_lock: Arc, + access_account_id: u32, ) -> trc::Result> { match collection { SyncCollection::Calendar => { build_calcard_resources( server, - access_token, + access_account_id, account_id, SyncCollection::Calendar, Collection::Calendar, @@ -532,7 +547,7 @@ async fn full_cache_build( SyncCollection::AddressBook => { build_calcard_resources( server, - access_token, + access_account_id, account_id, SyncCollection::AddressBook, Collection::AddressBook, diff --git a/crates/groupware/src/calendar/itip.rs b/crates/groupware/src/calendar/itip.rs index 8cada62e..3680c0ef 100644 --- a/crates/groupware/src/calendar/itip.rs +++ b/crates/groupware/src/calendar/itip.rs @@ -29,7 +29,7 @@ use calcard::{ }; use common::{ DavName, Server, - auth::{AccessToken, ResourceToken, oauth::GrantType}, + auth::{AccountInfo, oauth::GrantType}, config::groupware::CalendarTemplateVariable, i18n, }; @@ -55,8 +55,7 @@ pub struct ItipRsvpUrl(String); pub trait ItipIngest: Sync + Send { fn itip_ingest( &self, - access_token: &AccessToken, - resource_token: &ResourceToken, + account_info: &AccountInfo, sender: &str, recipient: &str, itip_message: &str, @@ -79,8 +78,7 @@ pub trait ItipIngest: Sync + Send { impl ItipIngest for Server { async fn itip_ingest( &self, - access_token: &AccessToken, - resource_token: &ResourceToken, + account_info: &AccountInfo, sender: &str, recipient: &str, itip_message: &str, @@ -134,7 +132,8 @@ impl ItipIngest for Server { } } - let itip_snapshots = itip_snapshot(&itip, access_token.emails.as_slice(), false)?; + let emails = account_info.addresses().collect::>(); + let itip_snapshots = itip_snapshot(&itip, emails.as_slice(), false)?; if !itip_snapshots.sender_is_organizer_or_attendee(sender) { return Err(ItipIngestError::Message( ItipError::SenderIsNotOrganizerNorAttendee, @@ -142,14 +141,14 @@ impl ItipIngest for Server { } // Obtain changedBy - let changed_by = if let Some(id) = self.email_to_id(self.directory(), sender, 0).await? { + let changed_by = if let Some(id) = self.account_id(sender).await? { ChangedBy::PrincipalId(id) } else { ChangedBy::CalendarAddress(sender.into()) }; // Find event by UID - let account_id = access_token.account_id; + let account_id = account_info.account_id(); let document_id = self .document_ids_matching( account_id, @@ -181,8 +180,7 @@ impl ItipIngest for Server { .caused_by(trc::location!())?; // Process the iTIP message - let snapshots = - itip_snapshot(&event.data.event, access_token.emails.as_slice(), false)?; + let snapshots = itip_snapshot(&event.data.event, emails.as_slice(), false)?; let is_organizer_update = !itip_snapshots.organizer.email.is_local; match itip_process_message( &event.data.event, @@ -206,7 +204,7 @@ impl ItipIngest for Server { .saturating_sub(event_.inner.size.to_native() as u64); if extra_bytes > 0 && self - .has_available_quota(resource_token, extra_bytes) + .has_available_quota(account_id, extra_bytes) .await .is_err() { @@ -253,7 +251,13 @@ impl ItipIngest for Server { // Prepare write batch let mut batch = BatchBuilder::new(); event - .update(access_token, event_, account_id, document_id, &mut batch) + .update( + account_info.account_tenant_ids(), + event_, + account_id, + document_id, + &mut batch, + ) .caused_by(trc::location!())?; if prev_email_alarm != next_email_alarm { if let Some(prev_alarm) = prev_email_alarm { @@ -264,7 +268,12 @@ impl ItipIngest for Server { } } itip_message - .insert(access_token, account_id, itip_document_id, &mut batch) + .insert( + account_info.account_tenant_ids(), + account_id, + itip_document_id, + &mut batch, + ) .caused_by(trc::location!())?; self.commit_batch(batch).await.caused_by(trc::location!())?; @@ -301,7 +310,7 @@ impl ItipIngest for Server { // Validate quota if self - .has_available_quota(resource_token, itip_message.len() as u64) + .has_available_quota(account_id, itip_message.len() as u64) .await .is_err() { @@ -310,7 +319,7 @@ impl ItipIngest for Server { // Obtain parent calendar let Some(parent_id) = self - .get_or_create_default_calendar(access_token, account_id) + .get_or_create_default_calendar(account_id, account_id) .await .caused_by(trc::location!())? else { @@ -359,7 +368,7 @@ impl ItipIngest for Server { let mut batch = BatchBuilder::new(); event .insert( - access_token, + account_info.account_tenant_ids(), account_id, document_id, next_email_alarm, @@ -367,7 +376,12 @@ impl ItipIngest for Server { ) .caused_by(trc::location!())?; itip_message - .insert(access_token, account_id, itip_document_id, &mut batch) + .insert( + account_info.account_tenant_ids(), + account_id, + itip_document_id, + &mut batch, + ) .caused_by(trc::location!())?; self.commit_batch(batch).await.caused_by(trc::location!())?; @@ -481,14 +495,14 @@ impl ItipIngest for Server { if did_change { // Prepare write batch - let access_token = self - .get_access_token(rsvp.account_id) + let account_info = self + .account_info(rsvp.account_id) .await .caused_by(trc::location!())?; let mut batch = BatchBuilder::new(); new_event .update( - &access_token, + account_info.account_tenant_ids(), event, rsvp.account_id, rsvp.document_id, diff --git a/crates/groupware/src/calendar/storage.rs b/crates/groupware/src/calendar/storage.rs index 8ebd956b..f3feb568 100644 --- a/crates/groupware/src/calendar/storage.rs +++ b/crates/groupware/src/calendar/storage.rs @@ -16,7 +16,11 @@ use crate::{ scheduling::{ItipMessages, event_cancel::itip_cancel}, }; use calcard::common::timezone::Tz; -use common::{Server, auth::AccessToken, storage::index::ObjectIndexBuilder}; +use common::{ + Server, + auth::{AccountInfo, AccountTenantIds}, + storage::index::ObjectIndexBuilder, +}; use store::{ IterateParams, U16_LEN, U32_LEN, U64_LEN, ValueKey, roaring::RoaringBitmap, @@ -129,10 +133,11 @@ impl ItipAutoExpunge for Server { // Tombstone messages let mut batch = BatchBuilder::new(); - let access_token = self - .get_access_token(account_id) + let changed_by = self + .account_info(account_id) .await - .caused_by(trc::location!())?; + .caused_by(trc::location!())? + .account_tenant_ids(); for document_id in destroy_ids { // Fetch event @@ -150,7 +155,7 @@ impl ItipAutoExpunge for Server { .to_unarchived::() .caused_by(trc::location!())?; DestroyArchive(event) - .delete(&access_token, account_id, document_id, &mut batch) + .delete(changed_by, account_id, document_id, &mut batch) .caused_by(trc::location!())?; } } @@ -164,7 +169,7 @@ impl ItipAutoExpunge for Server { impl CalendarEvent { pub fn update<'x>( self, - access_token: &AccessToken, + changed_by: AccountTenantIds, event: Archive<&ArchivedCalendarEvent>, account_id: u32, document_id: u32, @@ -184,19 +189,19 @@ impl CalendarEvent { ObjectIndexBuilder::new() .with_current(event) .with_changes(new_event) - .with_access_token(access_token), + .with_changed_by(changed_by), ) .map(|b| b.commit_point()) } - pub fn insert<'x>( + pub fn insert( self, - access_token: &AccessToken, + changed_by: AccountTenantIds, account_id: u32, document_id: u32, next_alarm: Option, - batch: &'x mut BatchBuilder, - ) -> trc::Result<&'x mut BatchBuilder> { + batch: &mut BatchBuilder, + ) -> trc::Result<&mut BatchBuilder> { // Build event let mut event = self; let now = now() as i64; @@ -211,7 +216,7 @@ impl CalendarEvent { .custom( ObjectIndexBuilder::<(), _>::new() .with_changes(event) - .with_access_token(access_token), + .with_changed_by(changed_by), ) .map(|batch| { if let Some(next_alarm) = next_alarm { @@ -224,13 +229,13 @@ impl CalendarEvent { } impl Calendar { - pub fn insert<'x>( + pub fn insert( self, - access_token: &AccessToken, + changed_by: AccountTenantIds, account_id: u32, document_id: u32, - batch: &'x mut BatchBuilder, - ) -> trc::Result<&'x mut BatchBuilder> { + batch: &mut BatchBuilder, + ) -> trc::Result<&mut BatchBuilder> { // Build address calendar let mut calendar = self; let now = now() as i64; @@ -253,14 +258,14 @@ impl Calendar { .custom( ObjectIndexBuilder::<(), _>::new() .with_changes(calendar) - .with_access_token(access_token), + .with_changed_by(changed_by), ) .map(|b| b.commit_point()) } pub fn update<'x>( self, - access_token: &AccessToken, + changed_by: AccountTenantIds, calendar: Archive<&ArchivedCalendar>, account_id: u32, document_id: u32, @@ -279,20 +284,20 @@ impl Calendar { ObjectIndexBuilder::new() .with_current(calendar) .with_changes(new_calendar) - .with_access_token(access_token), + .with_changed_by(changed_by), ) .map(|b| b.commit_point()) } } impl CalendarEventNotification { - pub fn insert<'x>( + pub fn insert( self, - access_token: &AccessToken, + changed_by: AccountTenantIds, account_id: u32, document_id: u32, - batch: &'x mut BatchBuilder, - ) -> trc::Result<&'x mut BatchBuilder> { + batch: &mut BatchBuilder, + ) -> trc::Result<&mut BatchBuilder> { // Build event let mut event = self; let now = now() as i64; @@ -307,7 +312,7 @@ impl CalendarEventNotification { .custom( ObjectIndexBuilder::<(), _>::new() .with_changes(event) - .with_access_token(access_token), + .with_changed_by(changed_by), ) .map(|batch| batch.commit_point()) } @@ -318,7 +323,7 @@ impl DestroyArchive> { pub async fn delete_with_events( self, server: &Server, - access_token: &AccessToken, + account_info: &AccountInfo, account_id: u32, document_id: u32, children_ids: Vec, @@ -344,7 +349,7 @@ impl DestroyArchive> { .caused_by(trc::location!())?, ) .delete( - access_token, + account_info, account_id, document_id, calendar_id, @@ -355,12 +360,18 @@ impl DestroyArchive> { } } - self.delete(access_token, account_id, document_id, delete_path, batch) + self.delete( + account_info.account_tenant_ids(), + account_id, + document_id, + delete_path, + batch, + ) } pub fn delete( self, - access_token: &AccessToken, + changed_by: AccountTenantIds, account_id: u32, document_id: u32, delete_path: Option, @@ -374,7 +385,7 @@ impl DestroyArchive> { .with_document(document_id) .custom( ObjectIndexBuilder::<_, ()>::new() - .with_access_token(access_token) + .with_changed_by(changed_by) .with_current(calendar), ) .caused_by(trc::location!())?; @@ -391,7 +402,7 @@ impl DestroyArchive> { #[allow(clippy::too_many_arguments)] pub fn delete( self, - access_token: &AccessToken, + account_info: &AccountInfo, account_id: u32, document_id: u32, calendar_id: u32, @@ -419,13 +430,13 @@ impl DestroyArchive> { .with_document(document_id) .custom( ObjectIndexBuilder::new() - .with_access_token(access_token) + .with_changed_by(account_info.account_tenant_ids()) .with_current(event) .with_changes(new_event), ) .caused_by(trc::location!())?; } else { - self.delete_all(access_token, account_id, document_id, send_itip, batch)?; + self.delete_all(account_info, account_id, document_id, send_itip, batch)?; } if let Some(delete_path) = delete_path { @@ -441,7 +452,7 @@ impl DestroyArchive> { #[allow(clippy::too_many_arguments)] pub fn delete_all( self, - access_token: &AccessToken, + account_info: &AccountInfo, account_id: u32, document_id: u32, send_itip: bool, @@ -469,9 +480,8 @@ impl DestroyArchive> { .deserialize::() .caused_by(trc::location!())?; - if let Ok(messages) = - itip_cancel(&event.data.event, access_token.emails.as_slice(), true) - { + let emails = account_info.addresses().collect::>(); + if let Ok(messages) = itip_cancel(&event.data.event, emails.as_slice(), true) { ItipMessages::new(vec![messages]) .queue(batch) .caused_by(trc::location!())?; @@ -481,7 +491,7 @@ impl DestroyArchive> { batch .custom( ObjectIndexBuilder::<_, ()>::new() - .with_access_token(access_token) + .with_changed_by(account_info.account_tenant_ids()) .with_current(event), ) .caused_by(trc::location!())?; @@ -494,7 +504,7 @@ impl DestroyArchive> { #[allow(clippy::too_many_arguments)] pub fn delete( self, - access_token: &AccessToken, + changed_by: AccountTenantIds, account_id: u32, document_id: u32, batch: &mut BatchBuilder, @@ -506,7 +516,7 @@ impl DestroyArchive> { .with_document(document_id) .custom( ObjectIndexBuilder::<_, ()>::new() - .with_access_token(access_token) + .with_changed_by(changed_by) .with_current(self.0), ) .caused_by(trc::location!())? @@ -570,13 +580,13 @@ impl ArchivedCalendarEvent { pub async fn webcal_uri( &self, server: &Server, - access_token: &AccessToken, + account_info: &AccountInfo, ) -> trc::Result { for event_name in self.names.iter() { if let Some(calendar_) = server .store() .get_value::>(ValueKey::archive( - access_token.account_id, + account_info.account_id(), Collection::Calendar, event_name.parent_id.to_native(), )) @@ -590,7 +600,7 @@ impl ArchivedCalendarEvent { "webcal://{}{}/{}/{}/{}", server.core.network.server_name, DavResourceName::Cal.base_path(), - percent_encoding::utf8_percent_encode(&access_token.name, RFC_3986), + percent_encoding::utf8_percent_encode(account_info.name(), RFC_3986), calendar.name, event_name.name )); diff --git a/crates/groupware/src/contact/storage.rs b/crates/groupware/src/contact/storage.rs index 0cd692da..01cfb696 100644 --- a/crates/groupware/src/contact/storage.rs +++ b/crates/groupware/src/contact/storage.rs @@ -6,7 +6,7 @@ use super::{AddressBook, ArchivedAddressBook, ArchivedContactCard, ContactCard}; use crate::DestroyArchive; -use common::{Server, auth::AccessToken, storage::index::ObjectIndexBuilder}; +use common::{Server, auth::AccountTenantIds, storage::index::ObjectIndexBuilder}; use store::{ ValueKey, write::{AlignedBytes, Archive, BatchBuilder, now}, @@ -17,7 +17,7 @@ use types::collection::{Collection, VanishedCollection}; impl ContactCard { pub fn update<'x>( self, - access_token: &AccessToken, + changed_by: AccountTenantIds, card: Archive<&ArchivedContactCard>, account_id: u32, document_id: u32, @@ -37,18 +37,18 @@ impl ContactCard { ObjectIndexBuilder::new() .with_current(card) .with_changes(new_card) - .with_access_token(access_token), + .with_changed_by(changed_by), ) .map(|b| b.commit_point()) } - pub fn insert<'x>( + pub fn insert( self, - access_token: &AccessToken, + changed_by: AccountTenantIds, account_id: u32, document_id: u32, - batch: &'x mut BatchBuilder, - ) -> trc::Result<&'x mut BatchBuilder> { + batch: &mut BatchBuilder, + ) -> trc::Result<&mut BatchBuilder> { // Build card let mut card = self; let now = now() as i64; @@ -63,20 +63,20 @@ impl ContactCard { .custom( ObjectIndexBuilder::<(), _>::new() .with_changes(card) - .with_access_token(access_token), + .with_changed_by(changed_by), ) .map(|b| b.commit_point()) } } impl AddressBook { - pub fn insert<'x>( + pub fn insert( self, - access_token: &AccessToken, + changed_by: AccountTenantIds, account_id: u32, document_id: u32, - batch: &'x mut BatchBuilder, - ) -> trc::Result<&'x mut BatchBuilder> { + batch: &mut BatchBuilder, + ) -> trc::Result<&mut BatchBuilder> { // Build address book let mut book = self; let now = now() as i64; @@ -91,14 +91,14 @@ impl AddressBook { .custom( ObjectIndexBuilder::<(), _>::new() .with_changes(book) - .with_access_token(access_token), + .with_changed_by(changed_by), ) .map(|b| b.commit_point()) } pub fn update<'x>( self, - access_token: &AccessToken, + changed_by: AccountTenantIds, book: Archive<&ArchivedAddressBook>, account_id: u32, document_id: u32, @@ -117,7 +117,7 @@ impl AddressBook { ObjectIndexBuilder::new() .with_current(book) .with_changes(new_book) - .with_access_token(access_token), + .with_changed_by(changed_by), ) .map(|b| b.commit_point()) } @@ -128,7 +128,7 @@ impl DestroyArchive> { pub async fn delete_with_cards( self, server: &Server, - access_token: &AccessToken, + changed_by: AccountTenantIds, account_id: u32, document_id: u32, children_ids: Vec, @@ -153,7 +153,7 @@ impl DestroyArchive> { .caused_by(trc::location!())?, ) .delete( - access_token, + changed_by, account_id, document_id, addressbook_id, @@ -163,12 +163,12 @@ impl DestroyArchive> { } } - self.delete(access_token, account_id, document_id, delete_path, batch) + self.delete(changed_by, account_id, document_id, delete_path, batch) } pub fn delete( self, - access_token: &AccessToken, + changed_by: AccountTenantIds, account_id: u32, document_id: u32, delete_path: Option, @@ -182,7 +182,7 @@ impl DestroyArchive> { .with_document(document_id) .custom( ObjectIndexBuilder::<_, ()>::new() - .with_access_token(access_token) + .with_changed_by(changed_by) .with_current(book), ) .caused_by(trc::location!())?; @@ -200,7 +200,7 @@ impl DestroyArchive> { impl DestroyArchive> { pub fn delete( self, - access_token: &AccessToken, + changed_by: AccountTenantIds, account_id: u32, document_id: u32, addressbook_id: u32, @@ -228,7 +228,7 @@ impl DestroyArchive> { .with_document(document_id) .custom( ObjectIndexBuilder::new() - .with_access_token(access_token) + .with_changed_by(changed_by) .with_current(card) .with_changes(new_card), ) @@ -239,7 +239,7 @@ impl DestroyArchive> { .with_document(document_id) .custom( ObjectIndexBuilder::<_, ()>::new() - .with_access_token(access_token) + .with_changed_by(changed_by) .with_current(card), ) .caused_by(trc::location!())?; @@ -257,7 +257,7 @@ impl DestroyArchive> { pub fn delete_all( self, - access_token: &AccessToken, + changed_by: AccountTenantIds, account_id: u32, document_id: u32, batch: &mut BatchBuilder, @@ -268,7 +268,7 @@ impl DestroyArchive> { .with_document(document_id) .custom( ObjectIndexBuilder::<_, ()>::new() - .with_access_token(access_token) + .with_changed_by(changed_by) .with_current(self.0), ) .caused_by(trc::location!()) diff --git a/crates/groupware/src/file/storage.rs b/crates/groupware/src/file/storage.rs index 4746cefc..187e151e 100644 --- a/crates/groupware/src/file/storage.rs +++ b/crates/groupware/src/file/storage.rs @@ -6,7 +6,7 @@ use super::{ArchivedFileNode, FileNode}; use crate::DestroyArchive; -use common::{Server, auth::AccessToken, storage::index::ObjectIndexBuilder}; +use common::{Server, auth::AccountTenantIds, storage::index::ObjectIndexBuilder}; use store::{ ValueKey, write::{AlignedBytes, Archive, BatchBuilder, now}, @@ -15,13 +15,13 @@ use trc::AddContext; use types::collection::{Collection, VanishedCollection}; impl FileNode { - pub fn insert<'x>( + pub fn insert( self, - access_token: &AccessToken, + changed_by: AccountTenantIds, account_id: u32, document_id: u32, - batch: &'x mut BatchBuilder, - ) -> trc::Result<&'x mut BatchBuilder> { + batch: &mut BatchBuilder, + ) -> trc::Result<&mut BatchBuilder> { // Build node let mut node = self; let now = now() as i64; @@ -36,13 +36,13 @@ impl FileNode { .custom( ObjectIndexBuilder::<(), _>::new() .with_changes(node) - .with_access_token(access_token), + .with_changed_by(changed_by), ) .map(|b| b.commit_point()) } pub fn update<'x>( self, - access_token: &AccessToken, + changed_by: AccountTenantIds, node: Archive<&ArchivedFileNode>, account_id: u32, document_id: u32, @@ -59,7 +59,7 @@ impl FileNode { ObjectIndexBuilder::new() .with_current(node) .with_changes(new_node) - .with_access_token(access_token), + .with_changed_by(changed_by), ) .map(|b| b.commit_point()) } @@ -68,7 +68,7 @@ impl FileNode { impl DestroyArchive> { pub fn delete( self, - access_token: &AccessToken, + changed_by: AccountTenantIds, account_id: u32, document_id: u32, batch: &mut BatchBuilder, @@ -82,7 +82,7 @@ impl DestroyArchive> { .custom( ObjectIndexBuilder::<_, ()>::new() .with_current(self.0) - .with_access_token(access_token), + .with_changed_by(changed_by), )? .log_vanished_item(VanishedCollection::FileNode, path) .commit_point(); @@ -94,13 +94,13 @@ impl DestroyArchive> { pub async fn delete( self, server: &Server, - access_token: &AccessToken, + changed_by: AccountTenantIds, account_id: u32, delete_path: Option, ) -> trc::Result<()> { // Process deletions let mut batch = BatchBuilder::new(); - self.delete_batch(server, access_token, account_id, delete_path, &mut batch) + self.delete_batch(server, changed_by, account_id, delete_path, &mut batch) .await?; // Write changes if !batch.is_empty() { @@ -116,7 +116,7 @@ impl DestroyArchive> { pub async fn delete_batch( self, server: &Server, - access_token: &AccessToken, + changed_by: AccountTenantIds, account_id: u32, delete_path: Option, batch: &mut BatchBuilder, @@ -140,7 +140,7 @@ impl DestroyArchive> { .with_document(document_id) .custom( ObjectIndexBuilder::<_, ()>::new() - .with_access_token(access_token) + .with_changed_by(changed_by) .with_current( node.to_unarchived::() .caused_by(trc::location!())?, diff --git a/crates/groupware/src/scheduling/event_cancel.rs b/crates/groupware/src/scheduling/event_cancel.rs index 27eac0a7..76f5dbcd 100644 --- a/crates/groupware/src/scheduling/event_cancel.rs +++ b/crates/groupware/src/scheduling/event_cancel.rs @@ -21,7 +21,7 @@ use calcard::{ pub fn itip_cancel( ical: &ICalendar, - account_emails: &[String], + account_emails: &[&str], is_deletion: bool, ) -> Result, ItipError> { // Prepare iTIP message diff --git a/crates/groupware/src/scheduling/event_create.rs b/crates/groupware/src/scheduling/event_create.rs index 6dd0b57b..1b656eca 100644 --- a/crates/groupware/src/scheduling/event_create.rs +++ b/crates/groupware/src/scheduling/event_create.rs @@ -12,7 +12,7 @@ use calcard::icalendar::ICalendar; pub fn itip_create( ical: &mut ICalendar, - account_emails: &[String], + account_emails: &[&str], ) -> Result>, ItipError> { let itip = itip_snapshot(ical, account_emails, false)?; if !itip.organizer.is_server_scheduling { diff --git a/crates/groupware/src/scheduling/event_update.rs b/crates/groupware/src/scheduling/event_update.rs index 5e97950f..df89e85c 100644 --- a/crates/groupware/src/scheduling/event_update.rs +++ b/crates/groupware/src/scheduling/event_update.rs @@ -13,7 +13,7 @@ use calcard::icalendar::ICalendar; pub fn itip_update( ical: &mut ICalendar, old_ical: &ICalendar, - account_emails: &[String], + account_emails: &[&str], ) -> Result>, ItipError> { let old_itip = itip_snapshot(old_ical, account_emails, false)?; match itip_snapshot(ical, account_emails, false) { diff --git a/crates/groupware/src/scheduling/mod.rs b/crates/groupware/src/scheduling/mod.rs index 2e40031b..e3ad2ba3 100644 --- a/crates/groupware/src/scheduling/mod.rs +++ b/crates/groupware/src/scheduling/mod.rs @@ -235,15 +235,15 @@ impl Attendee<'_> { } impl Email { - pub fn new(email: &str, local_addresses: &[String]) -> Option { + pub fn new(email: &str, local_addresses: &[&str]) -> Option { email.contains('@').then(|| { let email = email.trim().trim_start_matches("mailto:").to_lowercase(); - let is_local = local_addresses.contains(&email); + let is_local = local_addresses.contains(&email.as_str()); Email { email, is_local } }) } - pub fn from_uri(uri: &Uri, local_addresses: &[String]) -> Option { + pub fn from_uri(uri: &Uri, local_addresses: &[&str]) -> Option { if let Uri::Location(uri) = uri { Email::new(uri.as_str(), local_addresses) } else { diff --git a/crates/groupware/src/scheduling/snapshot.rs b/crates/groupware/src/scheduling/snapshot.rs index e887eb81..fd66b3ff 100644 --- a/crates/groupware/src/scheduling/snapshot.rs +++ b/crates/groupware/src/scheduling/snapshot.rs @@ -16,7 +16,7 @@ use calcard::icalendar::{ pub fn itip_snapshot<'x, 'y>( ical: &'x ICalendar, - account_emails: &'y [String], + account_emails: &'y [&str], force_add_client_scheduling: bool, ) -> Result, ItipError> { if !ical.components.iter().any(|comp| { diff --git a/crates/http-proto/src/lib.rs b/crates/http-proto/src/lib.rs index 8dd7a807..19062a4a 100644 --- a/crates/http-proto/src/lib.rs +++ b/crates/http-proto/src/lib.rs @@ -10,10 +10,9 @@ pub mod response; pub use form_urlencoded; -use std::{net::IpAddr, sync::Arc}; - -use common::listener::ServerInstance; +use common::network::ServerInstance; use hyper::StatusCode; +use std::{net::IpAddr, sync::Arc}; pub type HttpRequest = hyper::Request; diff --git a/crates/http/src/management/reload.rs b/crates/http/src/management/reload.rs index 1763f32d..a0c64bc3 100644 --- a/crates/http/src/management/reload.rs +++ b/crates/http/src/management/reload.rs @@ -9,7 +9,7 @@ use common::{ auth::AccessToken, ipc::{BroadcastEvent, HousekeeperEvent}, }; -use directory::Permission; +use registry::schema::enums::Permission; use hyper::Method; use serde_json::json; use std::future::Future; diff --git a/crates/http/src/management/settings.rs b/crates/http/src/management/settings.rs index 87168290..866b8e1c 100644 --- a/crates/http/src/management/settings.rs +++ b/crates/http/src/management/settings.rs @@ -5,7 +5,7 @@ */ use common::{Server, auth::AccessToken}; -use directory::Permission; +use registry::schema::enums::Permission; use hyper::Method; use serde_json::json; use store::ahash::AHashMap; diff --git a/crates/http/src/management/stores.rs b/crates/http/src/management/stores.rs index 8b87be47..5dffeafa 100644 --- a/crates/http/src/management/stores.rs +++ b/crates/http/src/management/stores.rs @@ -151,7 +151,7 @@ impl ManageStore for Server { access_token.assert_has_permission(Permission::PurgeInMemoryStore)?; let store = if let Some(id) = id.filter(|id| *id != "default") { - if let Some(store) = self.core.storage.lookups.get(id) { + if let Some(store) = self.get_lookup_store(id) { store.clone() } else { return Err(trc::ResourceEvent::NotFound.into_err()); diff --git a/crates/http/src/request.rs b/crates/http/src/request.rs index 4006f3cb..349d8281 100644 --- a/crates/http/src/request.rs +++ b/crates/http/src/request.rs @@ -28,7 +28,7 @@ use common::{ manager::webadmin::Resource, }; use dav::{DavMethod, request::DavRequestHandler}; -use directory::Permission; +use registry::schema::enums::Permission; use groupware::{DavResourceName, calendar::itip::ItipIngest}; use http_proto::{ DownloadResponse, HtmlResponse, HttpContext, HttpRequest, HttpResponse, HttpResponseBody, diff --git a/crates/imap/src/op/append.rs b/crates/imap/src/op/append.rs index 159c53bf..7a2a2e02 100644 --- a/crates/imap/src/op/append.rs +++ b/crates/imap/src/op/append.rs @@ -10,7 +10,7 @@ use crate::{ spawn_op, }; use common::{ipc::PushNotification, listener::SessionStream}; -use directory::Permission; +use registry::schema::enums::Permission; use email::message::ingest::{EmailIngest, IngestEmail, IngestSource}; use imap_proto::{ Command, ResponseCode, StatusResponse, diff --git a/crates/imap/src/op/authenticate.rs b/crates/imap/src/op/authenticate.rs index 577cd91f..edaaac13 100644 --- a/crates/imap/src/op/authenticate.rs +++ b/crates/imap/src/op/authenticate.rs @@ -12,7 +12,7 @@ use common::{ listener::{SessionStream, limiter::LimiterResult}, }; -use directory::Permission; +use registry::schema::enums::Permission; use imap_proto::{ Command, ResponseCode, StatusResponse, protocol::{authenticate::Mechanism, capability::Capability}, diff --git a/crates/imap/src/op/capability.rs b/crates/imap/src/op/capability.rs index 46c110fa..450e82ed 100644 --- a/crates/imap/src/op/capability.rs +++ b/crates/imap/src/op/capability.rs @@ -8,7 +8,7 @@ use std::time::Instant; use crate::core::Session; use common::listener::SessionStream; -use directory::Permission; +use registry::schema::enums::Permission; use imap_proto::{ Command, StatusResponse, protocol::{ diff --git a/crates/imap/src/op/copy_move.rs b/crates/imap/src/op/copy_move.rs index 1a49b2dd..d26a5656 100644 --- a/crates/imap/src/op/copy_move.rs +++ b/crates/imap/src/op/copy_move.rs @@ -10,7 +10,7 @@ use crate::{ spawn_op, }; use common::{ipc::PushNotification, listener::SessionStream, storage::index::ObjectIndexBuilder}; -use directory::Permission; +use registry::schema::enums::Permission; use email::{ cache::{MessageCacheFetch, email::MessageCacheAccess}, mailbox::{JUNK_ID, TRASH_ID, UidMailbox}, diff --git a/crates/imap/src/op/create.rs b/crates/imap/src/op/create.rs index 92648eb2..3c29edb4 100644 --- a/crates/imap/src/op/create.rs +++ b/crates/imap/src/op/create.rs @@ -10,7 +10,7 @@ use crate::{ spawn_op, }; use common::{listener::SessionStream, storage::index::ObjectIndexBuilder}; -use directory::Permission; +use registry::schema::enums::Permission; use email::cache::{MessageCacheFetch, mailbox::MailboxCacheAccess}; use imap_proto::{ Command, ResponseCode, StatusResponse, diff --git a/crates/imap/src/op/delete.rs b/crates/imap/src/op/delete.rs index ab39630f..ad06ca67 100644 --- a/crates/imap/src/op/delete.rs +++ b/crates/imap/src/op/delete.rs @@ -10,7 +10,7 @@ use crate::{ spawn_op, }; use common::listener::SessionStream; -use directory::Permission; +use registry::schema::enums::Permission; use email::mailbox::destroy::{MailboxDestroy, MailboxDestroyError}; use imap_proto::{ Command, ResponseCode, StatusResponse, protocol::delete::Arguments, receiver::Request, diff --git a/crates/imap/src/op/enable.rs b/crates/imap/src/op/enable.rs index 8ff7ebb8..9b824597 100644 --- a/crates/imap/src/op/enable.rs +++ b/crates/imap/src/op/enable.rs @@ -8,7 +8,7 @@ use std::time::Instant; use crate::core::Session; use common::listener::SessionStream; -use directory::Permission; +use registry::schema::enums::Permission; use imap_proto::{ Command, StatusResponse, protocol::{ImapResponse, ProtocolVersion, capability::Capability, enable}, diff --git a/crates/imap/src/op/expunge.rs b/crates/imap/src/op/expunge.rs index 601fddff..6ade7a5f 100644 --- a/crates/imap/src/op/expunge.rs +++ b/crates/imap/src/op/expunge.rs @@ -8,7 +8,7 @@ use super::{ImapContext, ToModSeq}; use crate::core::{ImapId, SavedSearch, SelectedMailbox, Session, SessionData}; use ahash::AHashMap; use common::{listener::SessionStream, storage::index::ObjectIndexBuilder}; -use directory::Permission; +use registry::schema::enums::Permission; use email::{ cache::{MessageCacheFetch, email::MessageCacheAccess}, message::metadata::MessageData, diff --git a/crates/imap/src/op/fetch.rs b/crates/imap/src/op/fetch.rs index d02ac59f..ffd9542b 100644 --- a/crates/imap/src/op/fetch.rs +++ b/crates/imap/src/op/fetch.rs @@ -11,7 +11,7 @@ use crate::{ }; use ahash::AHashMap; use common::{listener::SessionStream, storage::index::ObjectIndexBuilder}; -use directory::Permission; +use registry::schema::enums::Permission; use email::{ cache::{MessageCacheFetch, email::MessageCacheAccess}, message::metadata::{ diff --git a/crates/imap/src/op/idle.rs b/crates/imap/src/op/idle.rs index 450258bf..3358d2b6 100644 --- a/crates/imap/src/op/idle.rs +++ b/crates/imap/src/op/idle.rs @@ -10,7 +10,7 @@ use crate::{ }; use ahash::AHashSet; use common::{ipc::PushNotification, listener::SessionStream}; -use directory::Permission; +use registry::schema::enums::Permission; use imap_proto::{ Command, StatusResponse, protocol::{ diff --git a/crates/imap/src/op/list.rs b/crates/imap/src/op/list.rs index 6c0e005c..baf2f401 100644 --- a/crates/imap/src/op/list.rs +++ b/crates/imap/src/op/list.rs @@ -12,7 +12,7 @@ use crate::{ }; use common::listener::SessionStream; -use directory::Permission; +use registry::schema::enums::Permission; use imap_proto::{ Command, StatusResponse, protocol::{ diff --git a/crates/imap/src/op/namespace.rs b/crates/imap/src/op/namespace.rs index c30de674..f8fb45f6 100644 --- a/crates/imap/src/op/namespace.rs +++ b/crates/imap/src/op/namespace.rs @@ -6,7 +6,7 @@ use crate::core::Session; use common::listener::SessionStream; -use directory::Permission; +use registry::schema::enums::Permission; use imap_proto::{ Command, StatusResponse, protocol::{ImapResponse, namespace::Response}, diff --git a/crates/imap/src/op/quota.rs b/crates/imap/src/op/quota.rs index cd41269d..6a4fa1e7 100644 --- a/crates/imap/src/op/quota.rs +++ b/crates/imap/src/op/quota.rs @@ -16,7 +16,7 @@ use crate::{ spawn_op, }; use common::listener::SessionStream; -use directory::Permission; +use registry::schema::enums::Permission; use imap_proto::{ Command, ResponseCode, StatusResponse, protocol::{ diff --git a/crates/imap/src/op/rename.rs b/crates/imap/src/op/rename.rs index 2b6b0bdf..b73a6b18 100644 --- a/crates/imap/src/op/rename.rs +++ b/crates/imap/src/op/rename.rs @@ -9,7 +9,7 @@ use crate::{ spawn_op, }; use common::{listener::SessionStream, sharing::EffectiveAcl, storage::index::ObjectIndexBuilder}; -use directory::Permission; +use registry::schema::enums::Permission; use imap_proto::{ Command, ResponseCode, StatusResponse, protocol::rename::Arguments, receiver::Request, }; diff --git a/crates/imap/src/op/search.rs b/crates/imap/src/op/search.rs index 3d4cf783..9e3597c9 100644 --- a/crates/imap/src/op/search.rs +++ b/crates/imap/src/op/search.rs @@ -10,7 +10,7 @@ use crate::{ spawn_op, }; use common::listener::SessionStream; -use directory::Permission; +use registry::schema::enums::Permission; use email::cache::{MessageCacheFetch, email::MessageCacheAccess}; use imap_proto::{ Command, StatusResponse, diff --git a/crates/imap/src/op/select.rs b/crates/imap/src/op/select.rs index 5f15e519..ffbbe672 100644 --- a/crates/imap/src/op/select.rs +++ b/crates/imap/src/op/select.rs @@ -7,7 +7,7 @@ use super::{ImapContext, ToModSeq}; use crate::core::{SavedSearch, SelectedMailbox, Session, State}; use common::listener::SessionStream; -use directory::Permission; +use registry::schema::enums::Permission; use imap_proto::{ Command, ResponseCode, StatusResponse, protocol::{ diff --git a/crates/imap/src/op/status.rs b/crates/imap/src/op/status.rs index 016f1384..749c818d 100644 --- a/crates/imap/src/op/status.rs +++ b/crates/imap/src/op/status.rs @@ -11,7 +11,7 @@ use crate::{ spawn_op, }; use common::listener::SessionStream; -use directory::Permission; +use registry::schema::enums::Permission; use email::cache::{MessageCacheFetch, email::MessageCacheAccess}; use imap_proto::{ Command, ResponseCode, StatusResponse, diff --git a/crates/imap/src/op/store.rs b/crates/imap/src/op/store.rs index 72b81dc3..48788a75 100644 --- a/crates/imap/src/op/store.rs +++ b/crates/imap/src/op/store.rs @@ -11,7 +11,7 @@ use crate::{ }; use ahash::AHashSet; use common::{listener::SessionStream, storage::index::ObjectIndexBuilder}; -use directory::Permission; +use registry::schema::enums::Permission; use email::{ mailbox::TRASH_ID, message::{ingest::EmailIngest, metadata::MessageData}, diff --git a/crates/imap/src/op/subscribe.rs b/crates/imap/src/op/subscribe.rs index 18420683..84cb6929 100644 --- a/crates/imap/src/op/subscribe.rs +++ b/crates/imap/src/op/subscribe.rs @@ -10,7 +10,7 @@ use crate::{ spawn_op, }; use common::{listener::SessionStream, storage::index::ObjectIndexBuilder}; -use directory::Permission; +use registry::schema::enums::Permission; use imap_proto::{Command, ResponseCode, StatusResponse, receiver::Request}; use std::time::Instant; use store::{ diff --git a/crates/imap/src/op/thread.rs b/crates/imap/src/op/thread.rs index 22e258c4..ec854c80 100644 --- a/crates/imap/src/op/thread.rs +++ b/crates/imap/src/op/thread.rs @@ -10,7 +10,7 @@ use crate::{ }; use ahash::AHashMap; use common::listener::SessionStream; -use directory::Permission; +use registry::schema::enums::Permission; use email::cache::{MessageCacheFetch, email::MessageCacheAccess}; use imap_proto::{ Command, StatusResponse, diff --git a/crates/jmap/src/addressbook/get.rs b/crates/jmap/src/addressbook/get.rs index a80f9e25..d5e1465d 100644 --- a/crates/jmap/src/addressbook/get.rs +++ b/crates/jmap/src/addressbook/get.rs @@ -50,7 +50,7 @@ impl AddressBookGet for Server { ]); let account_id = request.account_id.document_id(); let cache = self - .fetch_dav_resources(access_token, account_id, SyncCollection::AddressBook) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::AddressBook) .await?; let address_book_ids = if access_token.is_member(account_id) { cache.document_ids(true).collect::() diff --git a/crates/jmap/src/addressbook/set.rs b/crates/jmap/src/addressbook/set.rs index 58268cce..c22e3173 100644 --- a/crates/jmap/src/addressbook/set.rs +++ b/crates/jmap/src/addressbook/set.rs @@ -51,7 +51,7 @@ impl AddressBookSet for Server { ) -> trc::Result> { let account_id = request.account_id.document_id(); let cache = self - .fetch_dav_resources(access_token, account_id, SyncCollection::AddressBook) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::AddressBook) .await?; let mut response = SetResponse::from_request(&request, self.core.jmap.set_max_objects)?; let will_destroy = request.unwrap_destroy().into_valid().collect::>(); diff --git a/crates/jmap/src/api/auth.rs b/crates/jmap/src/api/auth.rs index 569435e6..5f587165 100644 --- a/crates/jmap/src/api/auth.rs +++ b/crates/jmap/src/api/auth.rs @@ -5,7 +5,7 @@ */ use common::auth::AccessToken; -use directory::Permission; +use registry::schema::enums::Permission; use jmap_proto::request::{ CopyRequestMethod, GetRequestMethod, ParseRequestMethod, QueryChangesRequestMethod, QueryRequestMethod, RequestMethod, SetRequestMethod, method::MethodObject, diff --git a/crates/jmap/src/api/session.rs b/crates/jmap/src/api/session.rs index 4249e0af..eea03b3d 100644 --- a/crates/jmap/src/api/session.rs +++ b/crates/jmap/src/api/session.rs @@ -5,7 +5,7 @@ */ use common::{Server, auth::AccessToken}; -use directory::Permission; +use registry::schema::enums::Permission; use jmap_proto::request::capability::{ Account, Capabilities, Capability, EmptyCapabilities, Session, }; diff --git a/crates/jmap/src/blob/copy.rs b/crates/jmap/src/blob/copy.rs index 47ffbece..7a80fd0c 100644 --- a/crates/jmap/src/blob/copy.rs +++ b/crates/jmap/src/blob/copy.rs @@ -6,7 +6,7 @@ use super::download::BlobDownload; use common::{Server, auth::AccessToken}; -use directory::Permission; +use registry::schema::enums::Permission; use jmap_proto::{ error::set::{SetError, SetErrorType}, method::copy::{CopyBlobRequest, CopyBlobResponse}, diff --git a/crates/jmap/src/blob/upload.rs b/crates/jmap/src/blob/upload.rs index e8f9ff53..52940210 100644 --- a/crates/jmap/src/blob/upload.rs +++ b/crates/jmap/src/blob/upload.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use super::{UploadResponse, download::BlobDownload}; use common::{Server, auth::AccessToken}; -use directory::Permission; +use registry::schema::enums::Permission; use jmap_proto::{ error::set::SetError, method::upload::{ diff --git a/crates/jmap/src/calendar/get.rs b/crates/jmap/src/calendar/get.rs index 40cf5872..9d844c9e 100644 --- a/crates/jmap/src/calendar/get.rs +++ b/crates/jmap/src/calendar/get.rs @@ -55,7 +55,7 @@ impl CalendarGet for Server { ]); let account_id = request.account_id.document_id(); let cache = self - .fetch_dav_resources(access_token, account_id, SyncCollection::Calendar) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::Calendar) .await?; let is_owner = access_token.is_member(account_id); let calendar_ids = if is_owner { diff --git a/crates/jmap/src/calendar/set.rs b/crates/jmap/src/calendar/set.rs index 08b3042c..7823fb01 100644 --- a/crates/jmap/src/calendar/set.rs +++ b/crates/jmap/src/calendar/set.rs @@ -56,7 +56,7 @@ impl CalendarSet for Server { ) -> trc::Result> { let account_id = request.account_id.document_id(); let cache = self - .fetch_dav_resources(access_token, account_id, SyncCollection::Calendar) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::Calendar) .await?; let mut response = SetResponse::from_request(&request, self.core.jmap.set_max_objects)?; let will_destroy = request.unwrap_destroy().into_valid().collect::>(); diff --git a/crates/jmap/src/calendar_event/copy.rs b/crates/jmap/src/calendar_event/copy.rs index e6e3c823..8d0a8c00 100644 --- a/crates/jmap/src/calendar_event/copy.rs +++ b/crates/jmap/src/calendar_event/copy.rs @@ -61,7 +61,7 @@ impl JmapCalendarEventCopy for Server { .details("From accountId is equal to fromAccountId")); } let cache = self - .fetch_dav_resources(access_token, account_id, SyncCollection::Calendar) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::Calendar) .await .caused_by(trc::location!())?; let old_state = cache.assert_state(false, &request.if_in_state)?; @@ -75,7 +75,7 @@ impl JmapCalendarEventCopy for Server { }; let from_cache = self - .fetch_dav_resources(access_token, from_account_id, SyncCollection::Calendar) + .fetch_dav_resources(access_token.account_id(), from_account_id, SyncCollection::Calendar) .await .caused_by(trc::location!())?; let from_calendar_event_ids = if access_token.is_member(from_account_id) { diff --git a/crates/jmap/src/calendar_event/get.rs b/crates/jmap/src/calendar_event/get.rs index 4d626578..1321ab56 100644 --- a/crates/jmap/src/calendar_event/get.rs +++ b/crates/jmap/src/calendar_event/get.rs @@ -67,7 +67,7 @@ impl CalendarEventGet for Server { let properties = request.unwrap_properties(&[]); let account_id = request.account_id.document_id(); let cache = self - .fetch_dav_resources(access_token, account_id, SyncCollection::Calendar) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::Calendar) .await?; let calendar_event_ids = if access_token.is_member(account_id) { cache.document_ids(false).collect::() diff --git a/crates/jmap/src/calendar_event/query.rs b/crates/jmap/src/calendar_event/query.rs index e1bc79d9..ca0cb4d1 100644 --- a/crates/jmap/src/calendar_event/query.rs +++ b/crates/jmap/src/calendar_event/query.rs @@ -43,7 +43,7 @@ impl CalendarEventQuery for Server { let account_id = request.account_id.document_id(); let mut filters = Vec::with_capacity(request.filter.len()); let cache = self - .fetch_dav_resources(access_token, account_id, SyncCollection::Calendar) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::Calendar) .await?; let default_tz = request.arguments.time_zone.unwrap_or(Tz::UTC); let mut filter: Option = None; diff --git a/crates/jmap/src/calendar_event/set.rs b/crates/jmap/src/calendar_event/set.rs index 74b74119..f160c4ea 100644 --- a/crates/jmap/src/calendar_event/set.rs +++ b/crates/jmap/src/calendar_event/set.rs @@ -16,7 +16,7 @@ use calcard::{ }; use chrono::DateTime; use common::{DavName, DavResources, Server, auth::AccessToken}; -use directory::Permission; +use registry::schema::enums::Permission; use groupware::{ DestroyArchive, cache::GroupwareCache, @@ -82,7 +82,7 @@ impl CalendarEventSet for Server { ) -> trc::Result> { let account_id = request.account_id.document_id(); let cache = self - .fetch_dav_resources(access_token, account_id, SyncCollection::Calendar) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::Calendar) .await?; let mut response = SetResponse::from_request(&request, self.core.jmap.set_max_objects)?; let will_destroy = request.unwrap_destroy().into_valid().collect::>(); @@ -383,7 +383,7 @@ impl CalendarEventSet for Server { if extra_bytes > 0 { match self .has_available_quota( - &self.get_resource_token(access_token, account_id).await?, + account_id, extra_bytes, ) .await @@ -650,7 +650,7 @@ impl CalendarEventSet for Server { // Validate quota match self .has_available_quota( - &self.get_resource_token(access_token, account_id).await?, + account_id, size as u64, ) .await diff --git a/crates/jmap/src/changes/get.rs b/crates/jmap/src/changes/get.rs index 605fb16f..69c9300b 100644 --- a/crates/jmap/src/changes/get.rs +++ b/crates/jmap/src/changes/get.rs @@ -145,7 +145,7 @@ impl ChangesLookup for Server { State::Exact(change_id) => { let last_state = match collection { SyncCollection::Calendar | SyncCollection::AddressBook => self - .fetch_dav_resources(access_token, account_id, collection) + .fetch_dav_resources(access_token.account_id(), account_id, collection) .await .caused_by(trc::location!())? .get_state(is_container) diff --git a/crates/jmap/src/contact/copy.rs b/crates/jmap/src/contact/copy.rs index 0ef99856..97545e4e 100644 --- a/crates/jmap/src/contact/copy.rs +++ b/crates/jmap/src/contact/copy.rs @@ -57,7 +57,7 @@ impl JmapContactCardCopy for Server { .details("From accountId is equal to fromAccountId")); } let cache = self - .fetch_dav_resources(access_token, account_id, SyncCollection::AddressBook) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::AddressBook) .await .caused_by(trc::location!())?; let old_state = cache.assert_state(false, &request.if_in_state)?; @@ -71,7 +71,7 @@ impl JmapContactCardCopy for Server { }; let from_cache = self - .fetch_dav_resources(access_token, from_account_id, SyncCollection::AddressBook) + .fetch_dav_resources(access_token.account_id(), from_account_id, SyncCollection::AddressBook) .await .caused_by(trc::location!())?; let from_contact_ids = if access_token.is_member(from_account_id) { diff --git a/crates/jmap/src/contact/get.rs b/crates/jmap/src/contact/get.rs index e5208ddc..bf312189 100644 --- a/crates/jmap/src/contact/get.rs +++ b/crates/jmap/src/contact/get.rs @@ -46,7 +46,7 @@ impl ContactCardGet for Server { request.unwrap_properties(&[JSContactProperty::Id, JSContactProperty::AddressBookIds]); let account_id = request.account_id.document_id(); let cache = self - .fetch_dav_resources(access_token, account_id, SyncCollection::AddressBook) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::AddressBook) .await?; let contact_ids = if access_token.is_member(account_id) { cache.document_ids(false).collect::() diff --git a/crates/jmap/src/contact/query.rs b/crates/jmap/src/contact/query.rs index e8c9d1e9..be38c4d0 100644 --- a/crates/jmap/src/contact/query.rs +++ b/crates/jmap/src/contact/query.rs @@ -50,7 +50,7 @@ impl ContactCardQuery for Server { let account_id = request.account_id.document_id(); let mut filters = Vec::with_capacity(request.filter.len()); let cache = self - .fetch_dav_resources(access_token, account_id, SyncCollection::AddressBook) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::AddressBook) .await?; let mut created_to_updated = Vec::new(); diff --git a/crates/jmap/src/contact/set.rs b/crates/jmap/src/contact/set.rs index b4b8e416..d7cf63c8 100644 --- a/crates/jmap/src/contact/set.rs +++ b/crates/jmap/src/contact/set.rs @@ -61,7 +61,7 @@ impl ContactCardSet for Server { ) -> trc::Result> { let account_id = request.account_id.document_id(); let cache = self - .fetch_dav_resources(access_token, account_id, SyncCollection::AddressBook) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::AddressBook) .await?; let mut response = SetResponse::from_request(&request, self.core.jmap.set_max_objects)?; let will_destroy = request.unwrap_destroy().into_valid().collect::>(); @@ -254,7 +254,7 @@ impl ContactCardSet for Server { if extra_bytes > 0 { match self .has_available_quota( - &self.get_resource_token(access_token, account_id).await?, + account_id, extra_bytes, ) .await @@ -408,7 +408,7 @@ impl ContactCardSet for Server { } match self .has_available_quota( - &self.get_resource_token(access_token, account_id).await?, + account_id, size as u64, ) .await diff --git a/crates/jmap/src/file/get.rs b/crates/jmap/src/file/get.rs index badafde0..8b4e44ac 100644 --- a/crates/jmap/src/file/get.rs +++ b/crates/jmap/src/file/get.rs @@ -45,7 +45,7 @@ impl FileNodeGet for Server { ]); let account_id = request.account_id.document_id(); let cache = self - .fetch_dav_resources(access_token, account_id, SyncCollection::FileNode) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::FileNode) .await?; let file_node_ids = if access_token.is_member(account_id) { cache diff --git a/crates/jmap/src/file/query.rs b/crates/jmap/src/file/query.rs index bcbf85fe..7e1a1b40 100644 --- a/crates/jmap/src/file/query.rs +++ b/crates/jmap/src/file/query.rs @@ -36,7 +36,7 @@ impl FileNodeQuery for Server { let account_id = request.account_id.document_id(); let mut filters = Vec::with_capacity(request.filter.len()); let cache = self - .fetch_dav_resources(access_token, account_id, SyncCollection::FileNode) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::FileNode) .await?; for cond in std::mem::take(&mut request.filter) { diff --git a/crates/jmap/src/file/set.rs b/crates/jmap/src/file/set.rs index 968d8d49..e93957b5 100644 --- a/crates/jmap/src/file/set.rs +++ b/crates/jmap/src/file/set.rs @@ -51,7 +51,7 @@ impl FileNodeSet for Server { ) -> trc::Result> { let account_id = request.account_id.document_id(); let cache = self - .fetch_dav_resources(access_token, account_id, SyncCollection::FileNode) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::FileNode) .await?; let mut response = SetResponse::from_request(&request, self.core.jmap.set_max_objects)?; let will_destroy = request.unwrap_destroy().into_valid().collect::>(); diff --git a/crates/jmap/src/principal/availability.rs b/crates/jmap/src/principal/availability.rs index 1cbc241f..4fbc35ba 100644 --- a/crates/jmap/src/principal/availability.rs +++ b/crates/jmap/src/principal/availability.rs @@ -15,7 +15,7 @@ use calcard::{ jscalendar::{JSCalendar, JSCalendarProperty, JSCalendarValue}, }; use common::{Server, TinyCalendarPreferences, auth::AccessToken}; -use directory::Permission; +use registry::schema::enums::Permission; use groupware::{ cache::GroupwareCache, calendar::{CALENDAR_SUBSCRIBED, CalendarEvent}, @@ -95,7 +95,7 @@ impl PrincipalGetAvailability for Server { for account_id in principal.all_ids_by_collection(Collection::Calendar) { let resources = self - .fetch_dav_resources(access_token, account_id, SyncCollection::Calendar) + .fetch_dav_resources(access_token.account_id(), account_id, SyncCollection::Calendar) .await .caused_by(trc::location!())?; diff --git a/crates/jmap/src/vacation/set.rs b/crates/jmap/src/vacation/set.rs index 9080c678..c3a7d12d 100644 --- a/crates/jmap/src/vacation/set.rs +++ b/crates/jmap/src/vacation/set.rs @@ -260,7 +260,7 @@ impl VacationResponseSet for Server { let mut obj = ObjectIndexBuilder::new() .with_current_opt(prev_sieve) .with_changes(sieve) - .with_access_token(access_token); + .with_account_info(&account_info); // Update id let document_id = if let Some(document_id) = document_id { diff --git a/crates/managesieve/src/op/authenticate.rs b/crates/managesieve/src/op/authenticate.rs index 588b8312..6af63e4b 100644 --- a/crates/managesieve/src/op/authenticate.rs +++ b/crates/managesieve/src/op/authenticate.rs @@ -12,7 +12,7 @@ use common::{ listener::{SessionStream, limiter::LimiterResult}, }; -use directory::Permission; +use registry::schema::enums::Permission; use imap_proto::{ protocol::authenticate::Mechanism, receiver::{self, Request}, diff --git a/crates/managesieve/src/op/checkscript.rs b/crates/managesieve/src/op/checkscript.rs index dbea0d52..852f2e67 100644 --- a/crates/managesieve/src/op/checkscript.rs +++ b/crates/managesieve/src/op/checkscript.rs @@ -7,7 +7,7 @@ use std::time::Instant; use common::listener::SessionStream; -use directory::Permission; +use registry::schema::enums::Permission; use imap_proto::receiver::Request; use crate::core::{Command, Session, StatusResponse}; diff --git a/crates/managesieve/src/op/deletescript.rs b/crates/managesieve/src/op/deletescript.rs index facfff99..ecfbde8f 100644 --- a/crates/managesieve/src/op/deletescript.rs +++ b/crates/managesieve/src/op/deletescript.rs @@ -6,7 +6,7 @@ use crate::core::{Command, ResponseCode, Session, StatusResponse}; use common::listener::SessionStream; -use directory::Permission; +use registry::schema::enums::Permission; use email::sieve::{delete::SieveScriptDelete, ingest::SieveScriptIngest}; use imap_proto::receiver::Request; use std::time::Instant; diff --git a/crates/managesieve/src/op/getscript.rs b/crates/managesieve/src/op/getscript.rs index 5d04abec..c8ba9605 100644 --- a/crates/managesieve/src/op/getscript.rs +++ b/crates/managesieve/src/op/getscript.rs @@ -6,7 +6,7 @@ use crate::core::{Command, ResponseCode, Session, StatusResponse}; use common::listener::SessionStream; -use directory::Permission; +use registry::schema::enums::Permission; use email::sieve::SieveScript; use imap_proto::receiver::Request; use std::time::Instant; diff --git a/crates/managesieve/src/op/havespace.rs b/crates/managesieve/src/op/havespace.rs index e586ddb6..a86f4c47 100644 --- a/crates/managesieve/src/op/havespace.rs +++ b/crates/managesieve/src/op/havespace.rs @@ -7,7 +7,7 @@ use std::time::Instant; use common::listener::SessionStream; -use directory::Permission; +use registry::schema::enums::Permission; use imap_proto::receiver::Request; use trc::AddContext; diff --git a/crates/managesieve/src/op/listscripts.rs b/crates/managesieve/src/op/listscripts.rs index ee0b76af..6db99a95 100644 --- a/crates/managesieve/src/op/listscripts.rs +++ b/crates/managesieve/src/op/listscripts.rs @@ -6,7 +6,7 @@ use crate::core::{Session, StatusResponse}; use common::listener::SessionStream; -use directory::Permission; +use registry::schema::enums::Permission; use email::sieve::{SieveScript, ingest::SieveScriptIngest}; use std::time::Instant; use store::{ diff --git a/crates/managesieve/src/op/mod.rs b/crates/managesieve/src/op/mod.rs index 3bc8ff3e..e01a0bae 100644 --- a/crates/managesieve/src/op/mod.rs +++ b/crates/managesieve/src/op/mod.rs @@ -6,7 +6,7 @@ use crate::core::{Session, State, StatusResponse}; use common::listener::SessionStream; -use directory::Permission; +use registry::schema::enums::Permission; pub mod authenticate; pub mod capability; diff --git a/crates/managesieve/src/op/putscript.rs b/crates/managesieve/src/op/putscript.rs index 893d5636..315e00be 100644 --- a/crates/managesieve/src/op/putscript.rs +++ b/crates/managesieve/src/op/putscript.rs @@ -6,7 +6,7 @@ use crate::core::{Command, ResponseCode, Session, StatusResponse}; use common::{listener::SessionStream, storage::index::ObjectIndexBuilder}; -use directory::Permission; +use registry::schema::enums::Permission; use email::sieve::SieveScript; use imap_proto::receiver::Request; use sieve::compiler::ErrorType; @@ -142,7 +142,7 @@ impl Session { .with_blob_hash(blob_hash.clone()), ) .with_current(script) - .with_access_token(access_token), + .with_account_info(&account_info), ) .caused_by(trc::location!())? .clear(blob_hold); @@ -185,7 +185,7 @@ impl Session { SieveScript::new(name.clone(), blob_hash.clone()) .with_size(script_size as u32), ) - .with_access_token(access_token), + .with_account_info(&account_info), ) .caused_by(trc::location!())? .clear(blob_hold); diff --git a/crates/managesieve/src/op/renamescript.rs b/crates/managesieve/src/op/renamescript.rs index 682f4aa1..5d828a7d 100644 --- a/crates/managesieve/src/op/renamescript.rs +++ b/crates/managesieve/src/op/renamescript.rs @@ -6,7 +6,7 @@ use crate::core::{Command, ResponseCode, Session, StatusResponse}; use common::{listener::SessionStream, storage::index::ObjectIndexBuilder}; -use directory::Permission; +use registry::schema::enums::Permission; use email::sieve::SieveScript; use imap_proto::receiver::Request; use std::time::Instant; diff --git a/crates/managesieve/src/op/setactive.rs b/crates/managesieve/src/op/setactive.rs index 2720ce47..07d9863a 100644 --- a/crates/managesieve/src/op/setactive.rs +++ b/crates/managesieve/src/op/setactive.rs @@ -7,7 +7,7 @@ use std::time::Instant; use common::listener::SessionStream; -use directory::Permission; +use registry::schema::enums::Permission; use imap_proto::receiver::Request; use store::{SerializeInfallible, write::BatchBuilder}; use trc::AddContext; diff --git a/crates/pop3/src/op/authenticate.rs b/crates/pop3/src/op/authenticate.rs index d104146e..fa69798a 100644 --- a/crates/pop3/src/op/authenticate.rs +++ b/crates/pop3/src/op/authenticate.rs @@ -11,7 +11,7 @@ use common::{ }, listener::{SessionStream, limiter::LimiterResult}, }; -use directory::Permission; +use registry::schema::enums::Permission; use mail_parser::decoders::base64::base64_decode; use mail_send::Credentials; diff --git a/crates/pop3/src/op/delete.rs b/crates/pop3/src/op/delete.rs index bfedd314..442468e4 100644 --- a/crates/pop3/src/op/delete.rs +++ b/crates/pop3/src/op/delete.rs @@ -7,7 +7,7 @@ use std::time::Instant; use common::listener::SessionStream; -use directory::Permission; +use registry::schema::enums::Permission; use email::message::delete::EmailDeletion; use store::{roaring::RoaringBitmap, write::BatchBuilder}; use trc::AddContext; diff --git a/crates/pop3/src/op/fetch.rs b/crates/pop3/src/op/fetch.rs index 4e396075..5101dd84 100644 --- a/crates/pop3/src/op/fetch.rs +++ b/crates/pop3/src/op/fetch.rs @@ -6,7 +6,7 @@ use crate::{Session, protocol::response::Response}; use common::listener::SessionStream; -use directory::Permission; +use registry::schema::enums::Permission; use email::message::metadata::MessageMetadata; use std::time::Instant; use store::{ diff --git a/crates/pop3/src/op/list.rs b/crates/pop3/src/op/list.rs index 7c7dcb74..ebddc2c3 100644 --- a/crates/pop3/src/op/list.rs +++ b/crates/pop3/src/op/list.rs @@ -7,7 +7,7 @@ use std::time::Instant; use common::listener::SessionStream; -use directory::Permission; +use registry::schema::enums::Permission; use crate::{Session, protocol::response::Response}; diff --git a/crates/registry/src/schema/mod.rs b/crates/registry/src/schema/mod.rs index 39cdd08f..2b278451 100644 --- a/crates/registry/src/schema/mod.rs +++ b/crates/registry/src/schema/mod.rs @@ -7,7 +7,7 @@ use crate::{ schema::{ enums::{TracingLevel, TracingLevelOpt}, - prelude::{Duration, HttpAuth, NodeRange, Property}, + prelude::{Account, Duration, HttpAuth, NodeRange, Property, UserAccount}, }, types::EnumType, }; @@ -54,6 +54,16 @@ impl NodeRange { } } +impl Account { + pub fn into_user(self) -> Option { + if let Account::User(user) = self { + Some(user) + } else { + None + } + } +} + impl HttpAuth { pub fn build_headers( &self, diff --git a/crates/services/src/task_manager/alarm.rs b/crates/services/src/task_manager/alarm.rs index 0954eb86..ba0d6982 100644 --- a/crates/services/src/task_manager/alarm.rs +++ b/crates/services/src/task_manager/alarm.rs @@ -17,7 +17,7 @@ use common::{ ipc::{CalendarAlert, PushNotification}, listener::{ServerInstance, stream::NullIo}, }; -use directory::Permission; +use registry::schema::enums::Permission; use groupware::calendar::{ ArchivedCalendarEvent, CalendarEvent, alarm::{CalendarAlarm, CalendarAlarmType}, diff --git a/crates/smtp/src/inbound/auth.rs b/crates/smtp/src/inbound/auth.rs index ced966a1..3e16ceba 100644 --- a/crates/smtp/src/inbound/auth.rs +++ b/crates/smtp/src/inbound/auth.rs @@ -12,7 +12,7 @@ use common::{ listener::SessionStream, }; -use directory::Permission; +use registry::schema::enums::Permission; use mail_parser::decoders::base64::base64_decode; use mail_send::Credentials; use smtp_proto::{AUTH_LOGIN, AUTH_OAUTHBEARER, AUTH_PLAIN, AUTH_XOAUTH2, IntoString}; diff --git a/crates/smtp/src/outbound/dane/dnssec.rs b/crates/smtp/src/outbound/dane/dnssec.rs index 9e81202f..1d83a1e6 100644 --- a/crates/smtp/src/outbound/dane/dnssec.rs +++ b/crates/smtp/src/outbound/dane/dnssec.rs @@ -29,7 +29,7 @@ impl TlsaLookup for Server { &self, key: impl IntoFqdn<'x> + Sync + Send, ) -> mail_auth::Result>> { - let key = key.into_fqdn(); + let key = key.to_fqdn(); if let Some(value) = self.inner.cache.dns_tlsa.get(key.as_ref()) { return Ok(Some(value)); } diff --git a/crates/smtp/src/scripts/event_loop.rs b/crates/smtp/src/scripts/event_loop.rs index c788abe4..bfa565d3 100644 --- a/crates/smtp/src/scripts/event_loop.rs +++ b/crates/smtp/src/scripts/event_loop.rs @@ -98,7 +98,7 @@ impl RunScript for Server { } => { input = false.into(); 'outer: for list in lists { - if let Some(store) = self.core.storage.lookups.get(&list) { + if let Some(store) = self.get_lookup_store(&list) { for value in &values { if let Ok(true) = store .key_exists(if !matches!(match_as, MatchAs::Lowercase) { diff --git a/crates/spam-filter/Cargo.toml b/crates/spam-filter/Cargo.toml index 99210e00..009b67a6 100644 --- a/crates/spam-filter/Cargo.toml +++ b/crates/spam-filter/Cargo.toml @@ -10,6 +10,7 @@ nlp = { path = "../nlp" } store = { path = "../store" } trc = { path = "../trc" } common = { path = "../common" } +registry = { path = "../registry" } smtp-proto = { version = "0.2", features = ["rkyv"] } mail-parser = { version = "0.11", features = ["full_encoding"] } mail-builder = { version = "0.4" } diff --git a/crates/spam-filter/src/analysis/classifier.rs b/crates/spam-filter/src/analysis/classifier.rs index 783a8eaa..f34fafac 100644 --- a/crates/spam-filter/src/analysis/classifier.rs +++ b/crates/spam-filter/src/analysis/classifier.rs @@ -31,7 +31,7 @@ impl SpamFilterAnalyzeClassify for Server { } async fn spam_filter_analyze_spam_trap(&self, ctx: &mut SpamFilterContext<'_>) -> bool { - if let Some(store) = self.get_in_memory_store("spam-traps") { + if let Some(store) = self.get_lookup_store("spam-traps") { for addr in &ctx.output.env_to_addr { match store.key_exists(addr.address.as_str()).await { Ok(true) => { diff --git a/crates/spam-filter/src/analysis/domain.rs b/crates/spam-filter/src/analysis/domain.rs index 6a3d6c70..47ea0a34 100644 --- a/crates/spam-filter/src/analysis/domain.rs +++ b/crates/spam-filter/src/analysis/domain.rs @@ -15,7 +15,7 @@ use crate::{ }; use common::{ Server, - config::spamfilter::{Element, Location}, + config::mailstore::spamfilter::{Element, Location}, }; use mail_auth::DkimResult; use mail_parser::{HeaderName, HeaderValue, Host, parsers::MessageStream}; diff --git a/crates/spam-filter/src/analysis/ip.rs b/crates/spam-filter/src/analysis/ip.rs index 3280726c..79f1ed34 100644 --- a/crates/spam-filter/src/analysis/ip.rs +++ b/crates/spam-filter/src/analysis/ip.rs @@ -8,7 +8,7 @@ use super::ElementLocation; use crate::{IpParts, SpamFilterContext, TextPart, modules::dnsbl::check_dnsbl}; use common::{ Server, - config::spamfilter::{Element, IpResolver, Location}, + config::mailstore::spamfilter::{Element, IpResolver, Location}, }; use mail_auth::IprevResult; use mail_parser::{HeaderName, HeaderValue, Host}; diff --git a/crates/spam-filter/src/analysis/mod.rs b/crates/spam-filter/src/analysis/mod.rs index 6f2ce58d..54b41096 100644 --- a/crates/spam-filter/src/analysis/mod.rs +++ b/crates/spam-filter/src/analysis/mod.rs @@ -7,7 +7,7 @@ use crate::{ Recipient, SpamFilterContext, SpamFilterInput, SpamFilterOutput, SpamFilterResult, TextPart, }; -use common::{Server, config::spamfilter::Location}; +use common::{Server, config::mailstore::spamfilter::Location}; use mail_parser::{Header, parsers::MessageStream}; use std::{ borrow::Cow, @@ -119,7 +119,7 @@ impl ElementLocation { } pub(crate) async fn is_trusted_domain(server: &Server, domain: &str, span_id: u64) -> bool { - if let Some(store) = server.core.storage.lookups.get("trusted-domains") { + if let Some(store) = server.get_lookup_store("trusted-domains") { match store.key_exists(domain).await { Ok(true) => return true, Ok(false) => (), @@ -129,8 +129,8 @@ pub(crate) async fn is_trusted_domain(server: &Server, domain: &str, span_id: u6 } } - match server.core.storage.directory.is_local_domain(domain).await { - Ok(result) => result, + match server.domain(domain).await { + Ok(result) => result.is_some(), Err(err) => { trc::error!(err.span_id(span_id).caused_by(trc::location!())); false @@ -139,7 +139,7 @@ pub(crate) async fn is_trusted_domain(server: &Server, domain: &str, span_id: u6 } pub(crate) async fn is_url_redirector(server: &Server, url: &str, span_id: u64) -> bool { - if let Some(store) = server.core.storage.lookups.get("url-redirectors") { + if let Some(store) = server.get_lookup_store("url-redirectors") { match store.key_exists(url).await { Ok(result) => result, Err(err) => { diff --git a/crates/spam-filter/src/analysis/rules.rs b/crates/spam-filter/src/analysis/rules.rs index 6f65712c..5dc005a2 100644 --- a/crates/spam-filter/src/analysis/rules.rs +++ b/crates/spam-filter/src/analysis/rules.rs @@ -8,7 +8,7 @@ use std::future::Future; use common::{ Server, - config::spamfilter::{IpResolver, Location}, + config::mailstore::spamfilter::{IpResolver, Location}, }; use crate::{ diff --git a/crates/spam-filter/src/analysis/score.rs b/crates/spam-filter/src/analysis/score.rs index c1c1bcab..ac00f6a3 100644 --- a/crates/spam-filter/src/analysis/score.rs +++ b/crates/spam-filter/src/analysis/score.rs @@ -18,7 +18,7 @@ use crate::{ url::SpamFilterAnalyzeUrl, }, }; -use common::{Server, config::spamfilter::SpamFilterAction}; +use common::{Server, config::mailstore::spamfilter::SpamFilterAction}; use std::{fmt::Write, future::Future, vec}; // SPDX-SnippetBegin diff --git a/crates/spam-filter/src/analysis/url.rs b/crates/spam-filter/src/analysis/url.rs index b227dbda..9a09f34c 100644 --- a/crates/spam-filter/src/analysis/url.rs +++ b/crates/spam-filter/src/analysis/url.rs @@ -13,7 +13,7 @@ use crate::{ modules::html::{A, HREF, HtmlToken}, }; use common::Server; -use common::config::spamfilter::{Element, IpResolver, Location}; +use common::config::mailstore::spamfilter::{Element, IpResolver, Location}; use common::scripts::IsMixedCharset; use common::scripts::functions::unicode::CharUtils; use hyper::{Uri, header::LOCATION}; diff --git a/crates/spam-filter/src/modules/classifier.rs b/crates/spam-filter/src/modules/classifier.rs index 2b8b0748..c6d94e76 100644 --- a/crates/spam-filter/src/modules/classifier.rs +++ b/crates/spam-filter/src/modules/classifier.rs @@ -11,9 +11,9 @@ use crate::analysis::url::SpamFilterAnalyzeUrl; use crate::modules::html::{A, ALT, HREF, HtmlToken, IMG, SRC, TITLE}; use crate::{Email, SpamFilterContext, TextPart}; use crate::{Hostname, SpamFilterInput}; -use common::config::spamfilter; +use common::config::mailstore::spamfilter; use common::manager::{SPAM_CLASSIFIER_KEY, SPAM_TRAINER_KEY}; -use common::{Server, config::spamfilter::Location, ipc::BroadcastEvent}; +use common::{Server, config::mailstore::spamfilter::Location, ipc::BroadcastEvent}; use mail_auth::DmarcResult; use mail_parser::{MessageParser, MimeHeaders}; use nlp::classifier::feature::{ @@ -25,6 +25,7 @@ use nlp::classifier::reservoir::SampleReservoir; use nlp::classifier::train::{CcfhTrainer, FhTrainer}; use nlp::tokenizers::types::TypesTokenizer; use nlp::tokenizers::{stream::WordStemTokenizer, types::TokenType}; +use registry::schema::prelude::Object; use std::time::Instant; use std::{ borrow::Cow, @@ -505,6 +506,7 @@ impl SpamClassifier for Server { &Archiver::new(trainer) .serialize() .caused_by(trc::location!())?, + self.core.storage.compression, ) .await .caused_by(trc::location!())?; @@ -512,6 +514,7 @@ impl SpamClassifier for Server { .put_blob( SPAM_CLASSIFIER_KEY, &classifier.serialize().caused_by(trc::location!())?, + self.core.storage.compression, ) .await .caused_by(trc::location!())?; @@ -520,7 +523,7 @@ impl SpamClassifier for Server { .data .spam_classifier .store(Arc::new(classifier.inner)); - self.cluster_broadcast(BroadcastEvent::ReloadSpamFilter) + self.cluster_broadcast(BroadcastEvent::reload(Object::SpamClassifier)) .await; trc::event!( @@ -583,11 +586,8 @@ impl SpamClassifier for Server { } for rcpt in &ctx.input.env_rcpt_to { - let prediction = if let Some(account_id) = self - .directory() - .email_to_id(rcpt) - .await - .caused_by(trc::location!())? + let prediction = if let Some(account_id) = + self.account_id(rcpt).await.caused_by(trc::location!())? { has_prediction = true; classifier @@ -626,11 +626,8 @@ impl SpamClassifier for Server { } for rcpt in &ctx.input.env_rcpt_to { - let prediction = if let Some(account_id) = self - .directory() - .email_to_id(rcpt) - .await - .caused_by(trc::location!())? + let prediction = if let Some(account_id) = + self.account_id(rcpt).await.caused_by(trc::location!())? { has_prediction = true; classifier diff --git a/crates/spam-filter/src/modules/dnsbl.rs b/crates/spam-filter/src/modules/dnsbl.rs index 24ed05b9..ab90d763 100644 --- a/crates/spam-filter/src/modules/dnsbl.rs +++ b/crates/spam-filter/src/modules/dnsbl.rs @@ -4,25 +4,21 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use super::expression::SpamFilterResolver; +use crate::SpamFilterContext; +use common::{ + Server, + config::mailstore::spamfilter::{DnsBlServer, Element, IpResolver, Location}, + expr::functions::ResolveVariable, +}; +use mail_auth::{Error, common::resolver::ToFqdn}; use std::{ net::Ipv4Addr, sync::Arc, time::{Duration, Instant}, }; - -use common::{ - Server, - config::spamfilter::{DnsBlServer, Element, IpResolver, Location}, - expr::functions::ResolveVariable, -}; - -use mail_auth::{Error, common::resolver::IntoFqdn}; use trc::SpamEvent; -use crate::SpamFilterContext; - -use super::expression::SpamFilterResolver; - pub(crate) async fn check_dnsbl( server: &Server, ctx: &mut SpamFilterContext<'_>, @@ -124,7 +120,7 @@ async fn is_dnsbl( .smtp .resolvers .dns - .ipv4_lookup_raw((&zone).into_fqdn().as_ref()) + .ipv4_lookup_raw(zone.to_fqdn().as_ref()) .await { Ok(result) => { @@ -151,7 +147,7 @@ async fn is_dnsbl( )); server.inner.cache.dns_rbl.insert_with_expiry( - zone.to_string(), + zone.into(), Some(entry.clone()), result.expires, ); @@ -168,7 +164,7 @@ async fn is_dnsbl( ); server.inner.cache.dns_rbl.insert( - zone.to_string(), + zone.into(), None, Duration::from_secs(86400), ); diff --git a/crates/spam-filter/src/modules/expression.rs b/crates/spam-filter/src/modules/expression.rs index 514f130c..2a738fb9 100644 --- a/crates/spam-filter/src/modules/expression.rs +++ b/crates/spam-filter/src/modules/expression.rs @@ -5,12 +5,13 @@ */ use common::{ - config::spamfilter::*, - expr::{Expression, StringCow, Variable, functions::ResolveVariable}, + config::mailstore::spamfilter::*, + expr::{StringCow, Variable, functions::ResolveVariable}, }; use compact_str::{CompactString, ToCompactString, format_compact}; use mail_parser::{Header, HeaderValue}; use nlp::tokenizers::types::TokenType; +use registry::schema::enums::ExpressionVariable; use crate::{Recipient, SpamFilterContext, TextPart, analysis::url::UrlParts}; @@ -312,33 +313,43 @@ impl ResolveVariable for EmailHeader<'_> { }) .collect(), ), - HeaderValue::Address(address) => Variable::Array(if variable == 1 { - address - .iter() - .filter_map(|a| { - a.address.as_ref().map(|text| { - Variable::String(if variable == ExpressionVariable::ValueLower { - StringCow::Owned(CompactString::from_str_to_lowercase(text)) - } else { - StringCow::Borrowed(text.as_ref()) + HeaderValue::Address(address) => { + Variable::Array(if matches!(variable, ExpressionVariable::ValueLower) { + address + .iter() + .filter_map(|a| { + a.address.as_ref().map(|text| { + Variable::String( + if variable == ExpressionVariable::ValueLower { + StringCow::Owned(CompactString::from_str_to_lowercase( + text, + )) + } else { + StringCow::Borrowed(text.as_ref()) + }, + ) }) }) - }) - .collect() - } else { - address - .iter() - .filter_map(|a| { - a.name.as_ref().map(|text| { - Variable::String(if variable == ExpressionVariable::ValueLower { - StringCow::Owned(CompactString::from_str_to_lowercase(text)) - } else { - StringCow::Borrowed(text.as_ref()) + .collect() + } else { + address + .iter() + .filter_map(|a| { + a.name.as_ref().map(|text| { + Variable::String( + if variable == ExpressionVariable::ValueLower { + StringCow::Owned(CompactString::from_str_to_lowercase( + text, + )) + } else { + StringCow::Borrowed(text.as_ref()) + }, + ) }) }) - }) - .collect() - }), + .collect() + }) + } HeaderValue::DateTime(date_time) => { CompactString::new(date_time.to_rfc3339()).into() } diff --git a/crates/spam-filter/src/modules/pyzor.rs b/crates/spam-filter/src/modules/pyzor.rs index de74845c..0d8e2593 100644 --- a/crates/spam-filter/src/modules/pyzor.rs +++ b/crates/spam-filter/src/modules/pyzor.rs @@ -11,7 +11,7 @@ use std::{ time::{Duration, SystemTime}, }; -use common::config::spamfilter::PyzorConfig; +use common::config::mailstore::spamfilter::PyzorConfig; use mail_parser::{Message, PartType, decoders::html::add_html_token}; use nlp::tokenizers::types::{TokenType, TypesTokenizer}; use sha1::{Digest, Sha1}; diff --git a/crates/store/src/registry/mod.rs b/crates/store/src/registry/mod.rs index 99f089d0..e3993dfb 100644 --- a/crates/store/src/registry/mod.rs +++ b/crates/store/src/registry/mod.rs @@ -5,10 +5,41 @@ */ pub mod bootstrap; +pub mod query; -use registry::types::{ObjectType, id::Id}; +use registry::{ + schema::prelude::{Object, Property}, + types::{ObjectType, id::Id}, +}; pub struct RegistryObject { pub id: Id, pub object: T, } + +pub struct RegistryQuery { + pub object_type: Object, + pub filters: Vec, +} + +pub struct RegistryFilter { + pub property: Property, + pub op: RegistryFilterOp, + pub value: RegistryFilterValue, +} + +pub enum RegistryFilterOp { + Equal, + NotEqual, + GreaterThan, + LessThan, + GreaterThanOrEqual, + LessThanOrEqual, + TextMatch, +} + +pub enum RegistryFilterValue { + String(String), + Integer(u64), + Boolean(bool), +} diff --git a/crates/store/src/registry/query.rs b/crates/store/src/registry/query.rs new file mode 100644 index 00000000..175ebbee --- /dev/null +++ b/crates/store/src/registry/query.rs @@ -0,0 +1,198 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use crate::{ + RegistryStore, + registry::{RegistryFilter, RegistryFilterOp, RegistryFilterValue, RegistryQuery}, +}; +use registry::schema::prelude::{Object, Property}; +use roaring::RoaringBitmap; + +impl RegistryStore { + pub async fn query(&self, query: RegistryQuery) -> trc::Result { + todo!() + } +} + +pub trait RegistryQueryResults: Default { + fn push(&mut self, id: u64); +} + +impl RegistryQueryResults for Vec { + fn push(&mut self, id: u64) { + self.push(id); + } +} + +impl RegistryQueryResults for Vec { + fn push(&mut self, id: u64) { + self.push(id as u32); + } +} + +impl RegistryQueryResults for RoaringBitmap { + fn push(&mut self, id: u64) { + self.insert(id as u32); + } +} + +impl RegistryQuery { + pub fn new(object_type: Object) -> Self { + Self { + object_type, + filters: Vec::new(), + } + } + + pub fn equal(mut self, property: Property, value: impl Into) -> Self { + self.filters.push(RegistryFilter::equal(property, value)); + self + } + + pub fn equal_opt( + mut self, + property: Property, + value: Option>, + ) -> Self { + if let Some(value) = value { + self.filters.push(RegistryFilter::equal(property, value)); + } + self + } + + pub fn not_equal(mut self, property: Property, value: impl Into) -> Self { + self.filters + .push(RegistryFilter::not_equal(property, value)); + self + } + + pub fn greater_than( + mut self, + property: Property, + value: impl Into, + ) -> Self { + self.filters + .push(RegistryFilter::greater_than(property, value)); + self + } + + pub fn less_than(mut self, property: Property, value: impl Into) -> Self { + self.filters + .push(RegistryFilter::less_than(property, value)); + self + } + + pub fn greater_than_or_equal( + mut self, + property: Property, + value: impl Into, + ) -> Self { + self.filters + .push(RegistryFilter::greater_than_or_equal(property, value)); + self + } + + pub fn less_than_or_equal( + mut self, + property: Property, + value: impl Into, + ) -> Self { + self.filters + .push(RegistryFilter::less_than_or_equal(property, value)); + self + } + + pub fn text(mut self, value: impl Into) -> Self { + self.filters.push(RegistryFilter::text(value)); + self + } + + pub fn text_opt(mut self, value: Option>) -> Self { + if let Some(value) = value { + self.filters.push(RegistryFilter::text(value)); + } + self + } +} + +impl RegistryFilter { + pub fn text(value: impl Into) -> Self { + Self { + property: Property::Contents, + op: RegistryFilterOp::TextMatch, + value: RegistryFilterValue::String(value.into()), + } + } + + pub fn equal(property: Property, value: impl Into) -> Self { + Self { + property, + op: RegistryFilterOp::Equal, + value: value.into(), + } + } + + pub fn not_equal(property: Property, value: impl Into) -> Self { + Self { + property, + op: RegistryFilterOp::NotEqual, + value: value.into(), + } + } + + pub fn greater_than(property: Property, value: impl Into) -> Self { + Self { + property, + op: RegistryFilterOp::GreaterThan, + value: value.into(), + } + } + + pub fn less_than(property: Property, value: impl Into) -> Self { + Self { + property, + op: RegistryFilterOp::LessThan, + value: value.into(), + } + } + + pub fn greater_than_or_equal( + property: Property, + value: impl Into, + ) -> Self { + Self { + property, + op: RegistryFilterOp::GreaterThanOrEqual, + value: value.into(), + } + } + + pub fn less_than_or_equal(property: Property, value: impl Into) -> Self { + Self { + property, + op: RegistryFilterOp::LessThanOrEqual, + value: value.into(), + } + } +} + +impl From for RegistryFilterValue { + fn from(value: String) -> Self { + RegistryFilterValue::String(value) + } +} + +impl From for RegistryFilterValue { + fn from(value: u64) -> Self { + RegistryFilterValue::Integer(value) + } +} + +impl From for RegistryFilterValue { + fn from(value: u32) -> Self { + RegistryFilterValue::Integer(value as u64) + } +} diff --git a/crates/utils/src/cache.rs b/crates/utils/src/cache.rs index 801b0eb9..45303460 100644 --- a/crates/utils/src/cache.rs +++ b/crates/utils/src/cache.rs @@ -53,6 +53,14 @@ impl Cache { self.0.get(key) } + #[inline(always)] + pub fn peek(&self, key: &Q) -> Option + where + Q: Hash + Equivalent + ?Sized, + { + self.0.peek(key) + } + #[inline(always)] pub async fn get_value_or_guard_async<'a, Q>( &'a self,