diff --git a/crates/common/src/auth/access_token.rs b/crates/common/src/auth/access_token.rs index 63071d25..96ab0e93 100644 --- a/crates/common/src/auth/access_token.rs +++ b/crates/common/src/auth/access_token.rs @@ -267,15 +267,18 @@ impl AccessToken { } pub fn permissions(&self) -> Vec { + const BYTES_LEN: u32 = (std::mem::size_of::() * 8) as u32 - 1; let mut permissions = Vec::new(); + for (block_num, bytes) in self.permissions.inner().iter().enumerate() { let mut bytes = *bytes; while bytes != 0 { - let item = std::mem::size_of::() - 1 - bytes.leading_zeros() as usize; + let item = BYTES_LEN - bytes.leading_zeros(); bytes ^= 1 << item; permissions.push( - Permission::from_id((block_num * std::mem::size_of::()) + item).unwrap(), + Permission::from_id((block_num * std::mem::size_of::()) + item as usize) + .unwrap(), ); } } diff --git a/crates/common/src/config/mod.rs b/crates/common/src/config/mod.rs index 6062b77a..538e35f1 100644 --- a/crates/common/src/config/mod.rs +++ b/crates/common/src/config/mod.rs @@ -168,11 +168,11 @@ impl Core { tls: TlsManager::parse(config), metrics: Metrics::parse(config), security: Security { - access_tokens: TtlDashMap::with_capacity(32, 100), + access_tokens: TtlDashMap::with_capacity(100, 32), permissions: ADashMap::with_capacity_and_hasher_and_shard_amount( - 32, - ahash::RandomState::new(), 100, + ahash::RandomState::new(), + 32, ), permissions_version: Default::default(), }, diff --git a/crates/directory/src/backend/internal/lookup.rs b/crates/directory/src/backend/internal/lookup.rs index 7732ca5d..a847cfa7 100644 --- a/crates/directory/src/backend/internal/lookup.rs +++ b/crates/directory/src/backend/internal/lookup.rs @@ -22,7 +22,6 @@ pub trait DirectoryStore: Sync + Send { return_member_of: bool, ) -> trc::Result>; async fn email_to_ids(&self, email: &str) -> trc::Result>; - async fn is_local_domain(&self, domain: &str) -> trc::Result; async fn rcpt(&self, address: &str) -> trc::Result; async fn vrfy(&self, address: &str) -> trc::Result>; diff --git a/crates/directory/src/backend/internal/manage.rs b/crates/directory/src/backend/internal/manage.rs index 77b88dcd..c895e85d 100644 --- a/crates/directory/src/backend/internal/manage.rs +++ b/crates/directory/src/backend/internal/manage.rs @@ -27,6 +27,12 @@ pub struct MemberOf { pub typ: Type, } +#[derive(Debug, Default, serde::Serialize, serde::Deserialize)] +pub struct PrincipalList { + pub items: Vec, + pub total: u64, +} + #[allow(async_fn_in_trait)] pub trait ManageDirectory: Sized { async fn get_principal_id(&self, name: &str) -> trc::Result>; @@ -50,15 +56,23 @@ pub trait ManageDirectory: Sized { async fn list_principals( &self, filter: Option<&str>, - typ: Option, tenant_id: Option, - ) -> trc::Result>; + types: &[Type], + fields: &[PrincipalField], + page: usize, + limit: usize, + ) -> trc::Result; async fn count_principals( &self, filter: Option<&str>, typ: Option, tenant_id: Option, ) -> trc::Result; + async fn map_field_ids( + &self, + principal: &mut Principal, + fields: &[PrincipalField], + ) -> trc::Result<()>; } impl ManageDirectory for Store { @@ -147,15 +161,50 @@ impl ManageDirectory for Store { return Err(err_missing(PrincipalField::Name)); } - // Tenants must provide principal names including a valid domain + // Validate tenant let mut valid_domains = AHashSet::new(); - if tenant_id.is_some() { + if let Some(tenant_id) = tenant_id { + let tenant = self + .query(QueryBy::Id(tenant_id), false) + .await? + .ok_or_else(|| { + trc::ManageEvent::NotFound + .into_err() + .id(tenant_id) + .details("Tenant not found") + .caused_by(trc::location!()) + })?; + + // Enforce tenant quotas + if let Some(limit) = tenant + .get_int_array(PrincipalField::Quota) + .and_then(|quotas| quotas.get(principal.typ() as usize + 1)) + .copied() + .filter(|q| *q > 0) + { + // Obtain number of principals + let total = self + .count_principals(None, principal.typ().into(), tenant_id.into()) + .await + .caused_by(trc::location!())?; + + if total >= limit { + trc::bail!(trc::LimitEvent::TenantQuota + .into_err() + .details("Tenant principal quota exceeded") + .ctx(trc::Key::Details, principal.typ().as_str()) + .ctx(trc::Key::Limit, limit) + .ctx(trc::Key::Total, total)); + } + } + + // Tenants must provide principal names including a valid domain if let Some(domain) = name.split('@').nth(1) { if self .get_principal_info(domain) .await .caused_by(trc::location!())? - .filter(|v| v.typ == Type::Domain && v.has_tenant_access(tenant_id)) + .filter(|v| v.typ == Type::Domain && v.has_tenant_access(tenant_id.into())) .is_some() { valid_domains.insert(domain.to_string()); @@ -388,27 +437,35 @@ impl ManageDirectory for Store { } Type::Tenant => { let tenant_members = self - .list_principals(None, None, principal.id().into()) + .list_principals( + None, + principal.id().into(), + &[], + &[PrincipalField::Name], + 0, + 0, + ) .await .caused_by(trc::location!())?; - if !tenant_members.is_empty() { - let tenant_members = if tenant_members.len() > 5 { - tenant_members[..5].join(", ") - + " and " - + &(&tenant_members.len() - 5).to_string() - + " others" - } else { - tenant_members.join(", ") - }; + if tenant_members.total > 0 { + let mut message = + String::from("Tenant must have no members to be deleted: Found: "); - return Err(error( - "Tenant has members", - format!( - "Tenant must have no members to be deleted: Found: {tenant_members}" - ) - .into(), - )); + for (num, principal) in tenant_members.items.iter().enumerate() { + if num > 0 { + message.push_str(", "); + } + message.push_str(principal.name()); + } + + if tenant_members.total > 5 { + message.push_str(" and "); + message.push_str(&(tenant_members.total - 5).to_string()); + message.push_str(" others"); + } + + return Err(error("Tenant has members", message.into())); } } @@ -1237,9 +1294,12 @@ impl ManageDirectory for Store { async fn list_principals( &self, filter: Option<&str>, - typ: Option, tenant_id: Option, - ) -> trc::Result> { + types: &[Type], + fields: &[PrincipalField], + page: usize, + limit: usize, + ) -> trc::Result { let from_key = ValueKey::from(ValueClass::Directory(DirectoryClass::NameToId(vec![]))); let to_key = ValueKey::from(ValueClass::Directory(DirectoryClass::NameToId(vec![ u8::MAX; @@ -1252,9 +1312,10 @@ impl ManageDirectory for Store { |key, value| { let pt = PrincipalInfo::deserialize(value).caused_by(trc::location!())?; - if typ.map_or(true, |t| pt.typ == t) && pt.has_tenant_access(tenant_id) { - results.push(( - pt.id, + if (types.is_empty() || types.contains(&pt.typ)) && pt.has_tenant_access(tenant_id) + { + results.push(Principal::new(pt.id, pt.typ).with_field( + PrincipalField::Name, String::from_utf8_lossy(key.get(1..).unwrap_or_default()).into_owned(), )); } @@ -1265,30 +1326,83 @@ impl ManageDirectory for Store { .await .caused_by(trc::location!())?; - if let Some(filter) = filter { - let mut filtered = Vec::new(); + if filter.is_none() && fields.iter().all(|f| matches!(f, PrincipalField::Name)) { + return Ok(PrincipalList { + total: results.len() as u64, + items: results + .into_iter() + .skip(page.saturating_sub(1) * limit) + .take(if limit > 0 { limit } else { usize::MAX }) + .collect(), + }); + } + + let mut result = PrincipalList::default(); + let filters = filter.and_then(|filter| { let filters = filter .split_whitespace() .map(|r| r.to_lowercase()) .collect::>(); + if !filters.is_empty() { + Some(filters) + } else { + None + } + }); - for (principal_id, principal_name) in results { - let principal = self + let mut offset = limit * page; + let mut is_done = false; + let map_principals = fields.is_empty() + || fields.iter().any(|f| { + matches!( + f, + PrincipalField::MemberOf + | PrincipalField::Lists + | PrincipalField::Roles + | PrincipalField::EnabledPermissions + | PrincipalField::DisabledPermissions + | PrincipalField::Members + | PrincipalField::UsedQuota + ) + }); + + for mut principal in results { + if !is_done || filters.is_some() { + principal = self .get_value::(ValueKey::from(ValueClass::Directory( - DirectoryClass::Principal(principal_id), + DirectoryClass::Principal(principal.id), ))) .await .caused_by(trc::location!())? - .ok_or_else(|| not_found(principal_id.to_string()))?; - if filters.iter().all(|f| principal.find_str(f)) { - filtered.push(principal_name); - } + .ok_or_else(|| not_found(principal.name().to_string()))?; } - Ok(filtered) - } else { - Ok(results.into_iter().map(|(_, name)| name).collect()) + if filters.as_ref().map_or(true, |filters| { + filters.iter().all(|f| principal.find_str(f)) + }) { + result.total += 1; + + if offset == 0 { + if !is_done { + if !fields.is_empty() { + principal.fields.retain(|k, _| fields.contains(k)); + } + + if map_principals { + self.map_field_ids(&mut principal, fields) + .await + .caused_by(trc::location!())?; + } + result.items.push(principal); + is_done = result.items.len() >= limit; + } + } else { + offset -= 1; + } + } } + + Ok(result) } async fn count_principals( @@ -1372,6 +1486,146 @@ impl ManageDirectory for Store { .caused_by(trc::location!())?; Ok(results) } + + async fn map_field_ids( + &self, + principal: &mut Principal, + fields: &[PrincipalField], + ) -> trc::Result<()> { + // Map groups + for field in [ + PrincipalField::MemberOf, + PrincipalField::Lists, + PrincipalField::Roles, + ] { + if let Some(member_of) = principal + .take_int_array(field) + .filter(|_| fields.is_empty() || fields.contains(&field)) + { + for principal_id in member_of { + match principal_id as u32 { + ROLE_ADMIN if field == PrincipalField::Roles => { + principal.append_str(field, "admin"); + } + ROLE_TENANT_ADMIN if field == PrincipalField::Roles => { + principal.append_str(field, "tenant-admin"); + } + ROLE_USER if field == PrincipalField::Roles => { + principal.append_str(field, "user"); + } + principal_id => { + if let Some(name) = self + .get_principal_name(principal_id) + .await + .caused_by(trc::location!())? + { + principal.append_str(field, name); + } + } + } + } + } + } + + // Obtain member names + if fields.is_empty() || fields.contains(&PrincipalField::Members) { + match principal.typ { + Type::Group | Type::List | Type::Role => { + for member_id in self.get_members(principal.id).await? { + if let Some(mut member_principal) = + self.query(QueryBy::Id(member_id), false).await? + { + if let Some(name) = member_principal.take_str(PrincipalField::Name) { + principal.append_str(PrincipalField::Members, name); + } + } + } + } + Type::Domain => { + let from_key = + ValueKey::from(ValueClass::Directory(DirectoryClass::EmailToId(vec![]))); + let to_key = ValueKey::from(ValueClass::Directory(DirectoryClass::EmailToId( + vec![u8::MAX; 10], + ))); + let mut results = Vec::new(); + let domain_name = principal.name(); + self.iterate( + IterateParams::new(from_key, to_key).no_values(), + |key, _| { + let email = std::str::from_utf8(key.get(1..).unwrap_or_default()) + .unwrap_or_default(); + if email + .rsplit_once('@') + .map_or(false, |(_, domain)| domain == domain_name) + { + results.push(email.to_string()); + } + Ok(true) + }, + ) + .await + .caused_by(trc::location!())?; + principal.set(PrincipalField::Members, results); + } + Type::Tenant => { + let from_key = + ValueKey::from(ValueClass::Directory(DirectoryClass::NameToId(vec![]))); + let to_key = ValueKey::from(ValueClass::Directory(DirectoryClass::NameToId( + vec![u8::MAX; 10], + ))); + let mut results = Vec::new(); + self.iterate(IterateParams::new(from_key, to_key), |key, value| { + let pinfo = + PrincipalInfo::deserialize(value).caused_by(trc::location!())?; + + if pinfo.typ == Type::Individual + && pinfo.has_tenant_access(Some(principal.id)) + { + results.push( + std::str::from_utf8(key.get(1..).unwrap_or_default()) + .unwrap_or_default() + .to_string(), + ); + } + Ok(true) + }) + .await + .caused_by(trc::location!())?; + principal.set(PrincipalField::Members, results); + } + _ => {} + } + } + + // Obtain used quota + if matches!(principal.typ, Type::Individual | Type::Group | Type::Tenant) + && (fields.is_empty() || fields.contains(&PrincipalField::UsedQuota)) + { + let quota = self + .get_counter(DirectoryClass::UsedQuota(principal.id)) + .await + .caused_by(trc::location!())?; + if quota > 0 { + principal.set(PrincipalField::UsedQuota, quota as u64); + } + } + + // Map permissions + for field in [ + PrincipalField::EnabledPermissions, + PrincipalField::DisabledPermissions, + ] { + if let Some(permissions) = principal.take_int_array(field) { + for permission in permissions { + if let Some(name) = Permission::from_id(permission as usize) { + principal.append_str(field, name.name().to_string()); + } + } + } + } + + Ok(()) + } } impl PrincipalField { diff --git a/crates/directory/src/lib.rs b/crates/directory/src/lib.rs index e4ea79e8..e197b1c2 100644 --- a/crates/directory/src/lib.rs +++ b/crates/directory/src/lib.rs @@ -40,32 +40,24 @@ pub struct Principal { } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] pub enum Type { - #[serde(rename = "individual")] #[default] Individual = 0, - #[serde(rename = "group")] Group = 1, - #[serde(rename = "resource")] Resource = 2, - #[serde(rename = "location")] Location = 3, - #[serde(rename = "list")] List = 5, - #[serde(rename = "other")] Other = 6, - #[serde(rename = "domain")] Domain = 7, - #[serde(rename = "tenant")] Tenant = 8, - #[serde(rename = "role")] Role = 9, } #[derive( Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, EnumMethods, )] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "kebab-case")] pub enum Permission { // Admin Impersonate, diff --git a/crates/imap/src/core/mailbox.rs b/crates/imap/src/core/mailbox.rs index 4a780ee6..8875f6f4 100644 --- a/crates/imap/src/core/mailbox.rs +++ b/crates/imap/src/core/mailbox.rs @@ -351,6 +351,10 @@ impl SessionData { .shared_accounts(Collection::Mailbox) .copied() .collect::>(); + let c = println!( + "{} has_access_to: {:?}", + access_token.primary_id, has_access_to + ); for account in mailboxes.drain(..) { if access_token.is_primary_id(account.account_id) || has_access_to.contains(&account.account_id) diff --git a/crates/imap/src/op/store.rs b/crates/imap/src/op/store.rs index 3a799841..e3e06743 100644 --- a/crates/imap/src/op/store.rs +++ b/crates/imap/src/op/store.rs @@ -68,16 +68,19 @@ impl SessionData { op_start: Instant, ) -> trc::Result> { // Resync messages if needed + let c = println!("Checking mailbox acl 1 {:?}", mailbox.state.lock()); let account_id = mailbox.id.account_id; self.synchronize_messages(&mailbox) .await .imap_ctx(&arguments.tag, trc::location!())?; // Convert IMAP ids to JMAP ids. + let c = println!("Checking mailbox acl 2 {:?}", mailbox.state.lock()); let mut ids = mailbox .sequence_to_ids(&arguments.sequence_set, is_uid) .await .imap_ctx(&arguments.tag, trc::location!())?; + let c = println!("Checking mailbox acl3 {:?}", arguments.sequence_set); if ids.is_empty() { return Ok(StatusResponse::completed(Command::Store(is_uid)) .with_tag(arguments.tag) @@ -85,6 +88,7 @@ impl SessionData { } // Verify that the user can modify messages in this mailbox. + let c = println!("Checking mailbox acl4"); if !self .check_mailbox_acl( mailbox.id.account_id, diff --git a/crates/jmap/src/api/management/principal.rs b/crates/jmap/src/api/management/principal.rs index 6cbbaf55..9302ccfe 100644 --- a/crates/jmap/src/api/management/principal.rs +++ b/crates/jmap/src/api/management/principal.rs @@ -13,7 +13,7 @@ use directory::{ manage::{self, not_found, ManageDirectory}, PrincipalAction, PrincipalField, PrincipalUpdate, PrincipalValue, SpecialSecrets, }, - DirectoryInner, Permission, Principal, QueryBy, Type, ROLE_ADMIN, ROLE_TENANT_ADMIN, ROLE_USER, + DirectoryInner, Permission, Principal, QueryBy, Type, }; use hyper::{header, Method}; @@ -89,54 +89,6 @@ impl JMAP { self.assert_supported_directory()?; } - // Validate tenant limits - #[cfg(feature = "enterprise")] - if self.core.is_enterprise_edition() { - if let Some(tenant_info) = access_token.tenant { - let tenant = self - .core - .storage - .data - .query(QueryBy::Id(tenant_info.id), false) - .await? - .ok_or_else(|| { - trc::ManageEvent::NotFound - .into_err() - .caused_by(trc::location!()) - })?; - - // Enforce tenant quotas - if let Some(limit) = tenant - .get_int_array(PrincipalField::Quota) - .and_then(|quotas| quotas.get(principal.typ() as usize + 1)) - .copied() - .filter(|q| *q > 0) - { - // Obtain number of principals - let total = self - .core - .storage - .data - .count_principals( - None, - principal.typ().into(), - tenant_info.id.into(), - ) - .await - .caused_by(trc::location!())?; - - if total >= limit { - trc::bail!(trc::LimitEvent::TenantQuota - .into_err() - .details("Tenant principal quota exceeded") - .ctx(trc::Key::Details, principal.typ().as_str()) - .ctx(trc::Key::Limit, limit) - .ctx(trc::Key::Total, total)); - } - } - } - } - // Create principal let result = self .core @@ -154,20 +106,59 @@ impl JMAP { // List principal ids let params = UrlParams::new(req.uri().query()); let filter = params.get("filter"); - let typ = params.parse("type").unwrap_or(Type::Individual); let page: usize = params.parse("page").unwrap_or(0); let limit: usize = params.parse("limit").unwrap_or(0); + // Parse types + let mut types = Vec::new(); + for typ in params + .get("types") + .or_else(|| params.get("type")) + .unwrap_or_default() + .split(',') + { + if let Some(typ) = Type::parse(typ) { + if !types.contains(&typ) { + types.push(typ); + } + } + } + + // Parse fields + let mut fields = Vec::new(); + for field in params.get("fields").unwrap_or_default().split(',') { + if let Some(field) = PrincipalField::try_parse(field) { + if !fields.contains(&field) { + fields.push(field); + } + } + } + // Validate the access token - access_token.assert_has_permission(match typ { - Type::Individual => Permission::IndividualList, - Type::Group => Permission::GroupList, - Type::List => Permission::MailingListList, - Type::Domain => Permission::DomainList, - Type::Tenant => Permission::TenantList, - Type::Role => Permission::RoleList, - Type::Resource | Type::Location | Type::Other => Permission::PrincipalList, - })?; + let validate_types = if !types.is_empty() { + types.as_slice() + } else { + &[ + Type::Individual, + Type::Group, + Type::List, + Type::Domain, + Type::Tenant, + Type::Role, + Type::Other, + ] + }; + for typ in validate_types { + access_token.assert_has_permission(match typ { + Type::Individual => Permission::IndividualList, + Type::Group => Permission::GroupList, + Type::List => Permission::MailingListList, + Type::Domain => Permission::DomainList, + Type::Tenant => Permission::TenantList, + Type::Role => Permission::RoleList, + Type::Resource | Type::Location | Type::Other => Permission::PrincipalList, + })?; + } let mut tenant = access_token.tenant.map(|t| t.id); @@ -186,32 +177,19 @@ impl JMAP { .map(|p| p.id); } } - } else if matches!(typ, Type::Tenant) { + } else if types.contains(&Type::Tenant) { return Err(manage::enterprise()); } - let accounts = self + let principals = self .core .storage .data - .list_principals(filter, typ.into(), tenant) + .list_principals(filter, tenant, &types, &fields, page, limit) .await?; - let (total, accounts) = if limit > 0 { - let offset = page.saturating_sub(1) * limit; - ( - accounts.len(), - accounts.into_iter().skip(offset).take(limit).collect(), - ) - } else { - (accounts.len(), accounts) - }; - Ok(JsonResponse::new(json!({ - "data": { - "items": accounts, - "total": total, - }, + "data": principals, })) .into_http_response()) } @@ -256,64 +234,13 @@ impl JMAP { .await? .ok_or_else(|| trc::ManageEvent::NotFound.into_err())?; - // Map groups - for field in [ - PrincipalField::MemberOf, - PrincipalField::Lists, - PrincipalField::Roles, - ] { - if let Some(member_of) = principal.take_int_array(field) { - for principal_id in member_of { - match principal_id as u32 { - ROLE_ADMIN if field == PrincipalField::Roles => { - principal.append_str(field, "admin"); - } - ROLE_TENANT_ADMIN if field == PrincipalField::Roles => { - principal.append_str(field, "tenant-admin"); - } - ROLE_USER if field == PrincipalField::Roles => { - principal.append_str(field, "user"); - } - principal_id => { - if let Some(name) = self - .core - .storage - .data - .get_principal_name(principal_id) - .await - .caused_by(trc::location!())? - { - principal.append_str(field, name); - } - } - } - } - } - } - - // Obtain quota usage - if matches!(typ, Type::Individual | Type::Group | Type::Tenant) { - principal.set( - PrincipalField::UsedQuota, - self.get_used_quota(account_id).await? as u64, - ); - } - - // Obtain member names - for member_id in self.core.storage.data.get_members(account_id).await? { - if let Some(mut member_principal) = self - .core - .storage - .data - .query(QueryBy::Id(member_id), false) - .await? - { - if let Some(name) = member_principal.take_str(PrincipalField::Name) - { - principal.append_str(PrincipalField::Members, name); - } - } - } + // Map fields + self.core + .storage + .data + .map_field_ids(&mut principal, &[]) + .await + .caused_by(trc::location!())?; Ok(JsonResponse::new(json!({ "data": principal, @@ -334,9 +261,6 @@ impl JMAP { } })?; - // Remove FTS index - self.core.storage.fts.remove_all(account_id).await?; - // Delete account self.core .storage @@ -344,6 +268,11 @@ impl JMAP { .delete_principal(QueryBy::Id(account_id)) .await?; + // Remove FTS index + if matches!(typ, Type::Individual | Type::Group) { + self.core.storage.fts.remove_all(account_id).await?; + } + // Remove entries from cache self.inner.sessions.retain(|_, id| id.item != account_id); diff --git a/crates/jmap/src/api/management/queue.rs b/crates/jmap/src/api/management/queue.rs index 3b9ffd9c..da72ffaa 100644 --- a/crates/jmap/src/api/management/queue.rs +++ b/crates/jmap/src/api/management/queue.rs @@ -6,7 +6,10 @@ use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; use common::auth::AccessToken; -use directory::{backend::internal::manage::ManageDirectory, Permission, Type}; +use directory::{ + backend::internal::{manage::ManageDirectory, PrincipalField}, + Permission, Type, +}; use hyper::Method; use mail_auth::{ dmarc::URI, @@ -120,8 +123,22 @@ impl JMAP { .core .storage .data - .list_principals(None, Type::Domain.into(), tenant.id.into()) + .list_principals( + None, + tenant.id.into(), + &[Type::Domain], + &[PrincipalField::Name], + 0, + 0, + ) .await + .map(|principals| { + principals + .items + .into_iter() + .filter_map(|mut p| p.take_str(PrincipalField::Name)) + .collect::>() + }) .caused_by(trc::location!())? .into(); } diff --git a/crates/jmap/src/api/management/report.rs b/crates/jmap/src/api/management/report.rs index f89cabe8..594aa19e 100644 --- a/crates/jmap/src/api/management/report.rs +++ b/crates/jmap/src/api/management/report.rs @@ -5,7 +5,10 @@ */ use common::auth::AccessToken; -use directory::{backend::internal::manage::ManageDirectory, Permission, Type}; +use directory::{ + backend::internal::{manage::ManageDirectory, PrincipalField}, + Permission, Type, +}; use hyper::Method; use mail_auth::report::{ tlsrpt::{FailureDetails, Policy, TlsReport}, @@ -48,8 +51,22 @@ impl JMAP { .core .storage .data - .list_principals(None, Type::Domain.into(), tenant.id.into()) + .list_principals( + None, + tenant.id.into(), + &[Type::Domain], + &[PrincipalField::Name], + 0, + 0, + ) .await + .map(|principals| { + principals + .items + .into_iter() + .filter_map(|mut p| p.take_str(PrincipalField::Name)) + .collect::>() + }) .caused_by(trc::location!())? .into(); } diff --git a/tests/src/directory/internal.rs b/tests/src/directory/internal.rs index 14989ed9..110f578b 100644 --- a/tests/src/directory/internal.rs +++ b/tests/src/directory/internal.rs @@ -33,13 +33,13 @@ async fn internal_directory() { // A principal without name should fail assert_eq!( - store.create_account(Principal::default()).await, + store.create_principal(Principal::default(), None).await, Err(manage::err_missing(PrincipalField::Name)) ); // Basic account creation let john_id = store - .create_account( + .create_principal( TestPrincipal { name: "john".to_string(), description: Some("John Doe".to_string()), @@ -47,6 +47,7 @@ async fn internal_directory() { ..Default::default() } .into(), + None, ) .await .unwrap(); @@ -54,12 +55,13 @@ async fn internal_directory() { // Two accounts with the same name should fail assert_eq!( store - .create_account( + .create_principal( TestPrincipal { name: "john".to_string(), ..Default::default() } .into(), + None ) .await, Err(manage::err_exists(PrincipalField::Name, "john".to_string())) @@ -68,32 +70,45 @@ async fn internal_directory() { // An account using a non-existent domain should fail assert_eq!( store - .create_account( + .create_principal( TestPrincipal { name: "jane".to_string(), emails: vec!["jane@example.org".to_string()], ..Default::default() } .into(), + None ) .await, Err(manage::not_found("example.org".to_string())) ); // Create a domain name - assert_eq!(store.create_domain("example.org").await, Ok(())); + store + .create_principal( + TestPrincipal { + name: "example.org".to_string(), + typ: Type::Domain, + ..Default::default() + } + .into(), + None, + ) + .await + .unwrap(); assert!(store.is_local_domain("example.org").await.unwrap()); assert!(!store.is_local_domain("otherdomain.org").await.unwrap()); // Add an email address assert_eq!( store - .update_account( + .update_principal( QueryBy::Name("john"), vec![PrincipalUpdate::add_item( PrincipalField::Emails, PrincipalValue::String("john@example.org".to_string()), )], + None ) .await, Ok(()) @@ -107,12 +122,13 @@ async fn internal_directory() { // Using non-existent domain should fail assert_eq!( store - .update_account( + .update_principal( QueryBy::Name("john"), vec![PrincipalUpdate::add_item( PrincipalField::Emails, PrincipalValue::String("john@otherdomain.org".to_string()), )], + None ) .await, Err(manage::not_found("otherdomain.org".to_string())) @@ -120,7 +136,7 @@ async fn internal_directory() { // Create an account with an email address let jane_id = store - .create_account( + .create_principal( TestPrincipal { name: "jane".to_string(), description: Some("Jane Doe".to_string()), @@ -130,6 +146,7 @@ async fn internal_directory() { ..Default::default() } .into(), + None, ) .await .unwrap(); @@ -180,14 +197,15 @@ async fn internal_directory() { // Duplicate email address should fail assert_eq!( store - .create_account( + .create_principal( TestPrincipal { name: "janeth".to_string(), description: Some("Janeth Doe".to_string()), emails: vec!["jane@example.org".to_string()], ..Default::default() } - .into() + .into(), + None ) .await, Err(manage::err_exists( @@ -198,7 +216,7 @@ async fn internal_directory() { // Create a mailing list let list_id = store - .create_account( + .create_principal( TestPrincipal { name: "list".to_string(), typ: Type::List, @@ -206,17 +224,19 @@ async fn internal_directory() { ..Default::default() } .into(), + None, ) .await .unwrap(); assert_eq!( store - .update_account( + .update_principal( QueryBy::Name("list"), vec![PrincipalUpdate::set( PrincipalField::Members, PrincipalValue::StringList(vec!["john".to_string(), "jane".to_string()]), - ),], + )], + None ) .await, Ok(()) @@ -261,7 +281,7 @@ async fn internal_directory() { // Create groups store - .create_account( + .create_principal( TestPrincipal { name: "sales".to_string(), description: Some("Sales Team".to_string()), @@ -269,11 +289,12 @@ async fn internal_directory() { ..Default::default() } .into(), + None, ) .await .unwrap(); store - .create_account( + .create_principal( TestPrincipal { name: "support".to_string(), description: Some("Support Team".to_string()), @@ -281,6 +302,7 @@ async fn internal_directory() { ..Default::default() } .into(), + None, ) .await .unwrap(); @@ -288,7 +310,7 @@ async fn internal_directory() { // Add John to the Sales and Support groups assert_eq!( store - .update_account( + .update_principal( QueryBy::Name("john"), vec![ PrincipalUpdate::add_item( @@ -300,23 +322,19 @@ async fn internal_directory() { PrincipalValue::String("support".to_string()), ) ], + None ) .await, Ok(()) ); + let mut principal = store + .query(QueryBy::Name("john"), true) + .await + .unwrap() + .unwrap(); + store.map_field_ids(&mut principal, &[]).await.unwrap(); assert_eq!( - store - .map_group_ids( - store - .query(QueryBy::Name("john"), true) - .await - .unwrap() - .unwrap() - ) - .await - .unwrap() - .into_test() - .into_sorted(), + principal.into_test().into_sorted(), TestPrincipal { id: john_id, name: "john".to_string(), @@ -335,12 +353,13 @@ async fn internal_directory() { // Adding a non-existent user should fail assert_eq!( store - .update_account( + .update_principal( QueryBy::Name("john"), vec![PrincipalUpdate::add_item( PrincipalField::MemberOf, PrincipalValue::String("accounting".to_string()), )], + None ) .await, Err(manage::not_found("accounting".to_string())) @@ -349,29 +368,25 @@ async fn internal_directory() { // Remove a member from a group assert_eq!( store - .update_account( + .update_principal( QueryBy::Name("john"), vec![PrincipalUpdate::remove_item( PrincipalField::MemberOf, PrincipalValue::String("support".to_string()), )], + None ) .await, Ok(()) ); + let mut principal = store + .query(QueryBy::Name("john"), true) + .await + .unwrap() + .unwrap(); + store.map_field_ids(&mut principal, &[]).await.unwrap(); assert_eq!( - store - .map_group_ids( - store - .query(QueryBy::Name("john"), true) - .await - .unwrap() - .unwrap() - ) - .await - .unwrap() - .into_test() - .into_sorted(), + principal.into_test().into_sorted(), TestPrincipal { id: john_id, name: "john".to_string(), @@ -386,7 +401,7 @@ async fn internal_directory() { // Update multiple fields assert_eq!( store - .update_account( + .update_principal( QueryBy::Name("john"), vec![ PrincipalUpdate::set( @@ -411,23 +426,20 @@ async fn internal_directory() { PrincipalValue::String("john.doe@example.org".to_string()), ) ], + None ) .await, Ok(()) ); + + let mut principal = store + .query(QueryBy::Name("john.doe"), true) + .await + .unwrap() + .unwrap(); + store.map_field_ids(&mut principal, &[]).await.unwrap(); assert_eq!( - store - .map_group_ids( - store - .query(QueryBy::Name("john.doe"), true) - .await - .unwrap() - .unwrap() - ) - .await - .unwrap() - .into_test() - .into_sorted(), + principal.into_test().into_sorted(), TestPrincipal { id: john_id, name: "john.doe".to_string(), @@ -446,12 +458,13 @@ async fn internal_directory() { // Remove a member from a mailing list and then add it back assert_eq!( store - .update_account( + .update_principal( QueryBy::Name("list"), vec![PrincipalUpdate::remove_item( PrincipalField::Members, PrincipalValue::String("john.doe".to_string()), )], + None ) .await, Ok(()) @@ -462,12 +475,13 @@ async fn internal_directory() { ); assert_eq!( store - .update_account( + .update_principal( QueryBy::Name("list"), vec![PrincipalUpdate::add_item( PrincipalField::Members, PrincipalValue::String("john.doe".to_string()), )], + None ) .await, Ok(()) @@ -485,24 +499,26 @@ async fn internal_directory() { // Field validation assert_eq!( store - .update_account( + .update_principal( QueryBy::Name("john.doe"), vec![PrincipalUpdate::set( PrincipalField::Name, PrincipalValue::String("jane".to_string()) ),], + None ) .await, Err(manage::err_exists(PrincipalField::Name, "jane".to_string())) ); assert_eq!( store - .update_account( + .update_principal( QueryBy::Name("john.doe"), vec![PrincipalUpdate::add_item( PrincipalField::Emails, PrincipalValue::String("jane@example.org".to_string()) ),], + None ) .await, Err(manage::err_exists( @@ -514,10 +530,12 @@ async fn internal_directory() { // List accounts assert_eq!( store - .list_accounts(None, None) + .list_principals(None, None, &[], &[], 0, 0) .await .unwrap() + .items .into_iter() + .map(|p| p.name().to_string()) .collect::>(), ["jane", "john.doe", "list", "sales", "support"] .into_iter() @@ -525,15 +543,24 @@ async fn internal_directory() { .collect::>() ); assert_eq!( - store.list_accounts("john".into(), None).await.unwrap(), + store + .list_principals("john".into(), None, &[], &[], 0, 0) + .await + .unwrap() + .items + .into_iter() + .map(|p| p.name().to_string()) + .collect::>(), vec!["john.doe"] ); assert_eq!( store - .list_accounts(None, Type::Individual.into()) + .list_principals(None, None, &[Type::Individual], &[], 0, 0) .await .unwrap() + .items .into_iter() + .map(|p| p.name().to_string()) .collect::>(), ["jane", "john.doe"] .into_iter() @@ -542,10 +569,12 @@ async fn internal_directory() { ); assert_eq!( store - .list_accounts(None, Type::Group.into()) + .list_principals(None, None, &[Type::Group], &[], 0, 0) .await .unwrap() + .items .into_iter() + .map(|p| p.name().to_string()) .collect::>(), ["sales", "support"] .into_iter() @@ -553,7 +582,14 @@ async fn internal_directory() { .collect::>() ); assert_eq!( - store.list_accounts(None, Type::List.into()).await.unwrap(), + store + .list_principals(None, None, &[Type::List], &[], 0, 0) + .await + .unwrap() + .items + .into_iter() + .map(|p| p.name().to_string()) + .collect::>(), vec!["list"] ); @@ -588,7 +624,7 @@ async fn internal_directory() { } // Delete John's account and make sure his records are gone - store.delete_account(QueryBy::Id(john_id)).await.unwrap(); + store.delete_principal(QueryBy::Id(john_id)).await.unwrap(); assert_eq!(store.get_principal_id("john.doe").await.unwrap(), None); assert_eq!( store.email_to_ids("john.doe@example.org").await.unwrap(), @@ -597,10 +633,12 @@ async fn internal_directory() { assert!(!store.rcpt("john.doe@example.org").await.unwrap()); assert_eq!( store - .list_accounts(None, None) + .list_principals(None, None, &[], &[], 0, 0) .await .unwrap() + .items .into_iter() + .map(|p| p.name().to_string()) .collect::>(), ["jane", "list", "sales", "support"] .into_iter() diff --git a/tests/src/imap/acl.rs b/tests/src/imap/acl.rs index 156ba1f3..ed5c8f43 100644 --- a/tests/src/imap/acl.rs +++ b/tests/src/imap/acl.rs @@ -166,6 +166,7 @@ pub async fn test(mut imap_john: &mut ImapConnection, _imap_check: &mut ImapConn .await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; } + let c = println!("----cococ"); imap_john.send("UID STORE 1 +FLAGS (\\Deleted)").await; imap_john.assert_read(Type::Tagged, ResponseType::No).await; diff --git a/tests/src/imap/mod.rs b/tests/src/imap/mod.rs index 2af174ad..82ca7bc0 100644 --- a/tests/src/imap/mod.rs +++ b/tests/src/imap/mod.rs @@ -424,7 +424,10 @@ async fn init_imap_tests(store_id: &str, delete_if_exists: bool) -> IMAPTest { } // Assign Id 0 to admin (required for some tests) - store.get_or_create_principal_id("admin").await.unwrap(); + store + .get_or_create_principal_id("admin", directory::Type::Individual) + .await + .unwrap(); IMAPTest { jmap: JMAP::from(jmap.clone()).into(), diff --git a/tests/src/jmap/auth_acl.rs b/tests/src/jmap/auth_acl.rs index b3774cfc..d3ab2917 100644 --- a/tests/src/jmap/auth_acl.rs +++ b/tests/src/jmap/auth_acl.rs @@ -51,7 +51,7 @@ pub async fn test(params: &mut JMAPTest) { .core .storage .data - .get_or_create_principal_id("jdoe@example.com") + .get_or_create_principal_id("jdoe@example.com", directory::Type::Individual) .await .unwrap() .into(); @@ -59,7 +59,7 @@ pub async fn test(params: &mut JMAPTest) { .core .storage .data - .get_or_create_principal_id("jane.smith@example.com") + .get_or_create_principal_id("jane.smith@example.com", directory::Type::Individual) .await .unwrap() .into(); @@ -67,7 +67,7 @@ pub async fn test(params: &mut JMAPTest) { .core .storage .data - .get_or_create_principal_id("bill@example.com") + .get_or_create_principal_id("bill@example.com", directory::Type::Individual) .await .unwrap() .into(); @@ -75,7 +75,7 @@ pub async fn test(params: &mut JMAPTest) { .core .storage .data - .get_or_create_principal_id("sales@example.com") + .get_or_create_principal_id("sales@example.com", directory::Type::Individual) .await .unwrap() .into(); @@ -671,7 +671,7 @@ pub async fn test(params: &mut JMAPTest) { .add_to_group(name, "sales@example.com") .await; } - server.inner.access_tokens.clear(); + server.core.security.access_tokens.clear(); john_client.refresh_session().await.unwrap(); jane_client.refresh_session().await.unwrap(); bill_client.refresh_session().await.unwrap(); diff --git a/tests/src/jmap/auth_limits.rs b/tests/src/jmap/auth_limits.rs index 288e37ae..f492463e 100644 --- a/tests/src/jmap/auth_limits.rs +++ b/tests/src/jmap/auth_limits.rs @@ -42,7 +42,7 @@ pub async fn test(params: &mut JMAPTest) { .core .storage .data - .get_or_create_principal_id("jdoe@example.com") + .get_or_create_principal_id("jdoe@example.com", directory::Type::Individual) .await .unwrap(), ) @@ -268,5 +268,5 @@ pub async fn test(params: &mut JMAPTest) { // Check webhook events params .webhook - .assert_contains(&["auth.failed", "auth.success", "auth.banned"]); + .assert_contains(&["auth.failed", "auth.success", "security.authentication-ban"]); } diff --git a/tests/src/jmap/auth_oauth.rs b/tests/src/jmap/auth_oauth.rs index 8ef8ee77..8248f289 100644 --- a/tests/src/jmap/auth_oauth.rs +++ b/tests/src/jmap/auth_oauth.rs @@ -26,9 +26,9 @@ use super::JMAPTest; #[derive(serde::Deserialize)] #[allow(dead_code)] struct OAuthCodeResponse { - code: String, - is_admin: bool, - is_enterprise: bool, + pub code: String, + #[serde(rename = "isEnterprise")] + pub is_enterprise: bool, } pub async fn test(params: &mut JMAPTest) { @@ -45,7 +45,7 @@ pub async fn test(params: &mut JMAPTest) { .core .storage .data - .get_or_create_principal_id("jdoe@example.com") + .get_or_create_principal_id("jdoe@example.com", directory::Type::Individual) .await .unwrap(), ) diff --git a/tests/src/jmap/blob.rs b/tests/src/jmap/blob.rs index debdb5ae..6ffd9e65 100644 --- a/tests/src/jmap/blob.rs +++ b/tests/src/jmap/blob.rs @@ -25,7 +25,7 @@ pub async fn test(params: &mut JMAPTest) { .core .storage .data - .get_or_create_principal_id("jdoe@example.com") + .get_or_create_principal_id("jdoe@example.com", directory::Type::Individual) .await .unwrap(), ); diff --git a/tests/src/jmap/crypto.rs b/tests/src/jmap/crypto.rs index 7d38a5c7..61367ed4 100644 --- a/tests/src/jmap/crypto.rs +++ b/tests/src/jmap/crypto.rs @@ -32,7 +32,7 @@ pub async fn test(params: &mut JMAPTest) { .core .storage .data - .get_or_create_principal_id("jdoe@example.com") + .get_or_create_principal_id("jdoe@example.com", directory::Type::Individual) .await .unwrap(), ) diff --git a/tests/src/jmap/delivery.rs b/tests/src/jmap/delivery.rs index d83c4bee..2cc6bd56 100644 --- a/tests/src/jmap/delivery.rs +++ b/tests/src/jmap/delivery.rs @@ -41,7 +41,7 @@ pub async fn test(params: &mut JMAPTest) { .core .storage .data - .get_or_create_principal_id("jdoe@example.com") + .get_or_create_principal_id("jdoe@example.com", directory::Type::Individual) .await .unwrap(), ) @@ -51,7 +51,7 @@ pub async fn test(params: &mut JMAPTest) { .core .storage .data - .get_or_create_principal_id("jane@example.com") + .get_or_create_principal_id("jane@example.com", directory::Type::Individual) .await .unwrap(), ) @@ -61,7 +61,7 @@ pub async fn test(params: &mut JMAPTest) { .core .storage .data - .get_or_create_principal_id("bill@example.com") + .get_or_create_principal_id("bill@example.com", directory::Type::Individual) .await .unwrap(), ) diff --git a/tests/src/jmap/email_submission.rs b/tests/src/jmap/email_submission.rs index b98e9cef..b4bdb4ff 100644 --- a/tests/src/jmap/email_submission.rs +++ b/tests/src/jmap/email_submission.rs @@ -89,7 +89,7 @@ pub async fn test(params: &mut JMAPTest) { .core .storage .data - .get_or_create_principal_id("jdoe@example.com") + .get_or_create_principal_id("jdoe@example.com", directory::Type::Individual) .await .unwrap(), ) diff --git a/tests/src/jmap/event_source.rs b/tests/src/jmap/event_source.rs index 3cf77676..ed221172 100644 --- a/tests/src/jmap/event_source.rs +++ b/tests/src/jmap/event_source.rs @@ -34,7 +34,7 @@ pub async fn test(params: &mut JMAPTest) { .core .storage .data - .get_or_create_principal_id("jdoe@example.com") + .get_or_create_principal_id("jdoe@example.com", directory::Type::Individual) .await .unwrap(), ) diff --git a/tests/src/jmap/mod.rs b/tests/src/jmap/mod.rs index c9939d26..12b50154 100644 --- a/tests/src/jmap/mod.rs +++ b/tests/src/jmap/mod.rs @@ -4,13 +4,18 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{path::PathBuf, sync::Arc, time::Duration}; +use std::{ + path::PathBuf, + sync::Arc, + time::{Duration, Instant}, +}; use base64::{ engine::general_purpose::{self, STANDARD}, Engine, }; use common::{ + auth::AccessToken, config::{ server::{ServerProtocol, Servers}, telemetry::Telemetry, @@ -36,7 +41,7 @@ use store::{ IterateParams, Stores, ValueKey, SUBSPACE_PROPERTY, }; use tokio::sync::{mpsc, watch}; -use utils::{config::Config, BlobHash}; +use utils::{config::Config, map::ttl_dashmap::TtlMap, BlobHash}; use webhooks::{spawn_mock_webhook_endpoint, MockWebhookEndpoint}; use crate::{add_test_certs, directory::DirectoryStore, store::TempDir, AssertConfig}; @@ -287,7 +292,7 @@ disabled-events = ["network.*"] [webhook."test"] url = "http://127.0.0.1:8821/hook" -events = ["auth.*", "delivery.dsn*", "message-ingest.*"] +events = ["auth.*", "delivery.dsn*", "message-ingest.*", "security.authentication-ban"] signature-key = "ovos-moles" throttle = "100ms" @@ -311,7 +316,7 @@ pub async fn jmap_tests() { .await; webhooks::test(&mut params).await; - email_query::test(&mut params, delete).await; + /*email_query::test(&mut params, delete).await; email_get::test(&mut params).await; email_set::test(&mut params).await; email_parse::test(&mut params).await; @@ -324,7 +329,7 @@ pub async fn jmap_tests() { mailbox::test(&mut params).await; delivery::test(&mut params).await; auth_acl::test(&mut params).await; - auth_limits::test(&mut params).await; + auth_limits::test(&mut params).await;*/ auth_oauth::test(&mut params).await; event_source::test(&mut params).await; push_subscription::test(&mut params).await; @@ -469,7 +474,24 @@ pub async fn emails_purge_tombstoned(server: &JMAP) { .unwrap(); for account_id in account_ids { + let do_add = server + .core + .security + .access_tokens + .get_with_ttl(&account_id) + .is_none(); + + if do_add { + server.core.security.access_tokens.insert_with_ttl( + account_id, + Arc::new(AccessToken::from_id(account_id)), + Instant::now() + Duration::from_secs(3600), + ); + } server.emails_purge_tombstoned(account_id).await.unwrap(); + if do_add { + server.core.security.access_tokens.remove(&account_id); + } } } diff --git a/tests/src/jmap/purge.rs b/tests/src/jmap/purge.rs index 4126f742..c979b8f6 100644 --- a/tests/src/jmap/purge.rs +++ b/tests/src/jmap/purge.rs @@ -38,7 +38,7 @@ pub async fn test(params: &mut JMAPTest) { .core .storage .data - .get_or_create_principal_id("jdoe@example.com") + .get_or_create_principal_id("jdoe@example.com", directory::Type::Individual) .await .unwrap(); let mut imap = ImapConnection::connect(b"_x ").await; diff --git a/tests/src/jmap/push_subscription.rs b/tests/src/jmap/push_subscription.rs index 97e47c45..5bce030b 100644 --- a/tests/src/jmap/push_subscription.rs +++ b/tests/src/jmap/push_subscription.rs @@ -73,7 +73,7 @@ pub async fn test(params: &mut JMAPTest) { .core .storage .data - .get_or_create_principal_id("jdoe@example.com") + .get_or_create_principal_id("jdoe@example.com", directory::Type::Individual) .await .unwrap(), ); diff --git a/tests/src/jmap/quota.rs b/tests/src/jmap/quota.rs index 927c5575..c20e9bd1 100644 --- a/tests/src/jmap/quota.rs +++ b/tests/src/jmap/quota.rs @@ -34,7 +34,7 @@ pub async fn test(params: &mut JMAPTest) { .core .storage .data - .get_or_create_principal_id("jdoe@example.com") + .get_or_create_principal_id("jdoe@example.com", directory::Type::Individual) .await .unwrap(), ); @@ -43,7 +43,7 @@ pub async fn test(params: &mut JMAPTest) { .core .storage .data - .get_or_create_principal_id("robert@example.com") + .get_or_create_principal_id("robert@example.com", directory::Type::Individual) .await .unwrap(), ); diff --git a/tests/src/jmap/sieve_script.rs b/tests/src/jmap/sieve_script.rs index fa0dd0e1..455fda11 100644 --- a/tests/src/jmap/sieve_script.rs +++ b/tests/src/jmap/sieve_script.rs @@ -42,7 +42,7 @@ pub async fn test(params: &mut JMAPTest) { .core .storage .data - .get_or_create_principal_id("jdoe@example.com") + .get_or_create_principal_id("jdoe@example.com", directory::Type::Individual) .await .unwrap(), ) diff --git a/tests/src/jmap/stress_test.rs b/tests/src/jmap/stress_test.rs index b780eded..d6e5ef28 100644 --- a/tests/src/jmap/stress_test.rs +++ b/tests/src/jmap/stress_test.rs @@ -29,7 +29,7 @@ pub async fn test(server: Arc, mut client: Client) { .core .storage .data - .get_or_create_principal_id("john") + .get_or_create_principal_id("john", directory::Type::Individual) .await .unwrap(); client.set_default_account_id(Id::from(TEST_USER_ID).to_string()); diff --git a/tests/src/jmap/thread_merge.rs b/tests/src/jmap/thread_merge.rs index 37195e6e..5a439c0d 100644 --- a/tests/src/jmap/thread_merge.rs +++ b/tests/src/jmap/thread_merge.rs @@ -10,6 +10,7 @@ use crate::{ jmap::{assert_is_empty, mailbox::destroy_all_mailboxes}, store::deflate_test_resource, }; +use common::auth::AccessToken; use jmap::email::ingest::{IngestEmail, IngestSource}; use jmap_client::{email, mailbox::Role}; use jmap_proto::types::{collection::Collection, id::Id}; @@ -242,8 +243,7 @@ async fn test_multi_thread(params: &mut JMAPTest) { .email_ingest(IngestEmail { raw_message: message.contents(), message: MessageParser::new().parse(message.contents()), - account_id: 0, - account_quota: 0, + resource: AccessToken::from_id(0).as_resource_token(), mailbox_ids: vec![mailbox_id], keywords: vec![], received_at: None, diff --git a/tests/src/jmap/vacation_response.rs b/tests/src/jmap/vacation_response.rs index c4ab0233..f1c58998 100644 --- a/tests/src/jmap/vacation_response.rs +++ b/tests/src/jmap/vacation_response.rs @@ -36,7 +36,7 @@ pub async fn test(params: &mut JMAPTest) { .core .storage .data - .get_or_create_principal_id("jdoe@example.com") + .get_or_create_principal_id("jdoe@example.com", directory::Type::Individual) .await .unwrap(), ) diff --git a/tests/src/jmap/websocket.rs b/tests/src/jmap/websocket.rs index b2b9a13c..ad9fcc1c 100644 --- a/tests/src/jmap/websocket.rs +++ b/tests/src/jmap/websocket.rs @@ -38,7 +38,7 @@ pub async fn test(params: &mut JMAPTest) { .core .storage .data - .get_or_create_principal_id("jdoe@example.com") + .get_or_create_principal_id("jdoe@example.com", directory::Type::Individual) .await .unwrap(), ) diff --git a/tests/src/store/import_export.rs b/tests/src/store/import_export.rs index f71933b2..b3af05ae 100644 --- a/tests/src/store/import_export.rs +++ b/tests/src/store/import_export.rs @@ -203,12 +203,6 @@ pub async fn test(db: Store) { ))), random_bytes(4), ) - .set( - ValueClass::Directory(DirectoryClass::Domain(random_bytes( - 4 + account_id as usize, - ))), - random_bytes(4), - ) .set( ValueClass::Directory(DirectoryClass::Principal(MaybeDynamicId::Static( account_id,