From 6b7dac0fcbeb3b2a2a6e0a734c18d2b44c41f594 Mon Sep 17 00:00:00 2001 From: mdecimus Date: Tue, 17 Sep 2024 19:33:31 +0200 Subject: [PATCH] Test fixes - part 2 --- crates/common/src/auth/access_token.rs | 14 +- .../directory/src/backend/internal/lookup.rs | 2 +- .../directory/src/backend/internal/manage.rs | 18 +- crates/directory/src/backend/ldap/lookup.rs | 97 ++++++---- crates/directory/src/backend/memory/config.rs | 2 + crates/directory/src/backend/sql/lookup.rs | 53 ++++-- crates/directory/src/core/mod.rs | 176 ++++++++++++++++++ crates/directory/src/core/principal.rs | 82 ++++---- crates/imap/src/core/mailbox.rs | 4 - crates/imap/src/op/append.rs | 4 +- crates/imap/src/op/store.rs | 4 - crates/jmap/src/mailbox/set.rs | 4 +- tests/src/directory/internal.rs | 19 +- tests/src/directory/ldap.rs | 6 +- tests/src/directory/mod.rs | 8 +- tests/src/directory/sql.rs | 32 +++- tests/src/imap/acl.rs | 1 - tests/src/smtp/inbound/auth.rs | 13 +- tests/src/smtp/inbound/vrfy.rs | 13 +- 19 files changed, 423 insertions(+), 129 deletions(-) diff --git a/crates/common/src/auth/access_token.rs b/crates/common/src/auth/access_token.rs index 96ab0e93..e1e731c6 100644 --- a/crates/common/src/auth/access_token.rs +++ b/crates/common/src/auth/access_token.rs @@ -267,19 +267,21 @@ impl AccessToken { } pub fn permissions(&self) -> Vec { - const BYTES_LEN: u32 = (std::mem::size_of::() * 8) as u32 - 1; + const USIZE_BITS: usize = std::mem::size_of::() * 8; + const USIZE_MASK: u32 = USIZE_BITS 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 = BYTES_LEN - bytes.leading_zeros(); + let item = USIZE_MASK - bytes.leading_zeros(); bytes ^= 1 << item; - permissions.push( - Permission::from_id((block_num * std::mem::size_of::()) + item as usize) - .unwrap(), - ); + if let Some(permission) = + Permission::from_id((block_num * USIZE_BITS) + item as usize) + { + permissions.push(permission); + } } } permissions diff --git a/crates/directory/src/backend/internal/lookup.rs b/crates/directory/src/backend/internal/lookup.rs index a847cfa7..7517357f 100644 --- a/crates/directory/src/backend/internal/lookup.rs +++ b/crates/directory/src/backend/internal/lookup.rs @@ -60,7 +60,7 @@ impl DirectoryStore for Store { .await? { if let Some(secret) = secret { - if principal.verify_secret(secret).await? { + if !principal.verify_secret(secret).await? { return Ok(None); } } diff --git a/crates/directory/src/backend/internal/manage.rs b/crates/directory/src/backend/internal/manage.rs index c895e85d..84f4686f 100644 --- a/crates/directory/src/backend/internal/manage.rs +++ b/crates/directory/src/backend/internal/manage.rs @@ -827,7 +827,7 @@ impl ManageDirectory for Store { PrincipalField::Quota, PrincipalValue::IntegerList(quotas), ) if matches!(principal.inner.typ, Type::Tenant) - && quotas.len() <= (Type::Other as usize + 1) => + && quotas.len() <= (Type::Role as usize + 1) => { principal.inner.set(PrincipalField::Quota, quotas); } @@ -1269,8 +1269,11 @@ impl ManageDirectory for Store { .retain_int(change.field, |v| *v != permission); } - _ => { - return Err(trc::StoreEvent::NotSupported.caused_by(trc::location!())); + (_, field, value) => { + return Err(error( + "Invalid parameter", + format!("Invalid value {:?} for {}", value, field.as_str()).into(), + )); } } } @@ -1326,7 +1329,10 @@ impl ManageDirectory for Store { .await .caused_by(trc::location!())?; - if filter.is_none() && fields.iter().all(|f| matches!(f, PrincipalField::Name)) { + if filter.is_none() + && !fields.is_empty() + && fields.iter().all(|f| matches!(f, PrincipalField::Name)) + { return Ok(PrincipalList { total: results.len() as u64, items: results @@ -1350,7 +1356,7 @@ impl ManageDirectory for Store { } }); - let mut offset = limit * page; + let mut offset = limit * page.saturating_sub(1); let mut is_done = false; let map_principals = fields.is_empty() || fields.iter().any(|f| { @@ -1394,7 +1400,7 @@ impl ManageDirectory for Store { .caused_by(trc::location!())?; } result.items.push(principal); - is_done = result.items.len() >= limit; + is_done = limit != 0 && result.items.len() >= limit; } } else { offset -= 1; diff --git a/crates/directory/src/backend/ldap/lookup.rs b/crates/directory/src/backend/ldap/lookup.rs index f7f558cd..5cc2a3d6 100644 --- a/crates/directory/src/backend/ldap/lookup.rs +++ b/crates/directory/src/backend/ldap/lookup.rs @@ -131,50 +131,77 @@ impl LdapDirectory { } principal.append_str(PrincipalField::Name, account_name); - // Obtain groups - if return_member_of && principal.has_field(PrincipalField::MemberOf) { - let mut member_of = Vec::new(); - for mut name in principal - .take_str_array(PrincipalField::MemberOf) - .unwrap_or_default() - { - if name.contains('=') { - let (rs, _res) = conn - .search( - &name, - Scope::Base, - "objectClass=*", - &self.mappings.attr_name, - ) - .await - .map_err(|err| err.into_error().caused_by(trc::location!()))? - .success() - .map_err(|err| err.into_error().caused_by(trc::location!()))?; - for entry in rs { - 'outer: for (attr, value) in SearchEntry::construct(entry).attrs { - if self.mappings.attr_name.contains(&attr) { - if let Some(group) = value.into_iter().next() { - if !group.is_empty() { - name = group; - break 'outer; + if return_member_of { + // Obtain groups + if principal.has_field(PrincipalField::MemberOf) { + let mut member_of = Vec::new(); + for mut name in principal + .take_str_array(PrincipalField::MemberOf) + .unwrap_or_default() + { + if name.contains('=') { + let (rs, _res) = conn + .search( + &name, + Scope::Base, + "objectClass=*", + &self.mappings.attr_name, + ) + .await + .map_err(|err| err.into_error().caused_by(trc::location!()))? + .success() + .map_err(|err| err.into_error().caused_by(trc::location!()))?; + for entry in rs { + 'outer: for (attr, value) in SearchEntry::construct(entry).attrs { + if self.mappings.attr_name.contains(&attr) { + if let Some(group) = value.into_iter().next() { + if !group.is_empty() { + name = group; + break 'outer; + } } } } } } + + member_of.push( + self.data_store + .get_or_create_principal_id(&name, Type::Group) + .await + .caused_by(trc::location!())?, + ); } - member_of.push( - self.data_store - .get_or_create_principal_id(&name, Type::Group) - .await - .caused_by(trc::location!())?, - ); + // Map ids + principal.set(PrincipalField::MemberOf, member_of); } - // Map ids - principal.set(PrincipalField::MemberOf, member_of); - } else { + // Obtain roles + let mut did_role_cleanup = false; + for member in self + .data_store + .get_member_of(principal.id) + .await + .caused_by(trc::location!())? + { + match member.typ { + Type::List => { + principal.append_int(PrincipalField::Lists, member.principal_id); + } + Type::Role => { + if !did_role_cleanup { + principal.remove(PrincipalField::Roles); + did_role_cleanup = true; + } + principal.append_int(PrincipalField::Roles, member.principal_id); + } + _ => { + principal.append_int(PrincipalField::MemberOf, member.principal_id); + } + } + } + } else if principal.has_field(PrincipalField::MemberOf) { principal.remove(PrincipalField::MemberOf); } diff --git a/crates/directory/src/backend/memory/config.rs b/crates/directory/src/backend/memory/config.rs index a4f966d2..5198ad1c 100644 --- a/crates/directory/src/backend/memory/config.rs +++ b/crates/directory/src/backend/memory/config.rs @@ -147,6 +147,8 @@ impl MemoryDirectory { { principal.set(PrincipalField::Quota, quota); } + + directory.principals.push(principal); } Some(directory) diff --git a/crates/directory/src/backend/sql/lookup.rs b/crates/directory/src/backend/sql/lookup.rs index 38a96638..7e2607cb 100644 --- a/crates/directory/src/backend/sql/lookup.rs +++ b/crates/directory/src/backend/sql/lookup.rs @@ -105,22 +105,49 @@ impl SqlDirectory { principal.set(PrincipalField::Name, account_name); // Obtain members - if return_member_of && !self.mappings.query_members.is_empty() { - for row in self - .store - .query::(&self.mappings.query_members, vec![principal.name().into()]) + if return_member_of { + if !self.mappings.query_members.is_empty() { + for row in self + .store + .query::(&self.mappings.query_members, vec![principal.name().into()]) + .await + .caused_by(trc::location!())? + .rows + { + if let Some(Value::Text(account_id)) = row.values.first() { + principal.append_int( + PrincipalField::MemberOf, + self.data_store + .get_or_create_principal_id(account_id, Type::Group) + .await + .caused_by(trc::location!())?, + ); + } + } + } + + // Obtain roles + let mut did_role_cleanup = false; + for member in self + .data_store + .get_member_of(principal.id) .await .caused_by(trc::location!())? - .rows { - if let Some(Value::Text(account_id)) = row.values.first() { - principal.append_int( - PrincipalField::MemberOf, - self.data_store - .get_or_create_principal_id(account_id, Type::Group) - .await - .caused_by(trc::location!())?, - ); + match member.typ { + Type::List => { + principal.append_int(PrincipalField::Lists, member.principal_id); + } + Type::Role => { + if !did_role_cleanup { + principal.remove(PrincipalField::Roles); + did_role_cleanup = true; + } + principal.append_int(PrincipalField::Roles, member.principal_id); + } + _ => { + principal.append_int(PrincipalField::MemberOf, member.principal_id); + } } } } diff --git a/crates/directory/src/core/mod.rs b/crates/directory/src/core/mod.rs index c9ee3326..36c21dc1 100644 --- a/crates/directory/src/core/mod.rs +++ b/crates/directory/src/core/mod.rs @@ -4,8 +4,184 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use crate::Permission; + pub mod cache; pub mod config; pub mod dispatch; pub mod principal; pub mod secret; + +impl Permission { + pub fn description(&self) -> &'static str { + match self { + Permission::Impersonate => "Allows acting on behalf of another user", + Permission::UnlimitedRequests => "Removes request limits or quotas", + Permission::UnlimitedUploads => "Removes upload size or frequency limits", + Permission::DeleteSystemFolders => "Allows deletion of critical system folders", + Permission::MessageQueueList => "View message queue", + Permission::MessageQueueGet => "Retrieve specific messages from the queue", + Permission::MessageQueueUpdate => "Modify queued messages", + Permission::MessageQueueDelete => "Remove messages from the queue", + Permission::OutgoingReportList => "View reports for outgoing emails", + Permission::OutgoingReportGet => "Retrieve specific outgoing email reports", + Permission::OutgoingReportDelete => "Remove outgoing email reports", + Permission::IncomingReportList => "View reports for incoming emails", + Permission::IncomingReportGet => "Retrieve specific incoming email reports", + Permission::IncomingReportDelete => "Remove incoming email reports", + Permission::SettingsList => "View system settings", + Permission::SettingsUpdate => "Modify system settings", + Permission::SettingsDelete => "Remove system settings", + Permission::SettingsReload => "Refresh system settings", + Permission::IndividualList => "View list of individual users", + Permission::IndividualGet => "Retrieve specific user information", + Permission::IndividualUpdate => "Modify user information", + Permission::IndividualDelete => "Remove user accounts", + Permission::IndividualCreate => "Add new user accounts", + Permission::GroupList => "View list of user groups", + Permission::GroupGet => "Retrieve specific group information", + Permission::GroupUpdate => "Modify group information", + Permission::GroupDelete => "Remove user groups", + Permission::GroupCreate => "Add new user groups", + Permission::DomainList => "View list of email domains", + Permission::DomainGet => "Retrieve specific domain information", + Permission::DomainCreate => "Add new email domains", + Permission::DomainUpdate => "Modify domain information", + Permission::DomainDelete => "Remove email domains", + Permission::TenantList => "View list of tenants (in multi-tenant setup)", + Permission::TenantGet => "Retrieve specific tenant information", + Permission::TenantCreate => "Add new tenants", + Permission::TenantUpdate => "Modify tenant information", + Permission::TenantDelete => "Remove tenants", + Permission::MailingListList => "View list of mailing lists", + Permission::MailingListGet => "Retrieve specific mailing list information", + Permission::MailingListCreate => "Create new mailing lists", + Permission::MailingListUpdate => "Modify mailing list information", + Permission::MailingListDelete => "Remove mailing lists", + Permission::RoleList => "View list of roles", + Permission::RoleGet => "Retrieve specific role information", + Permission::RoleCreate => "Create new roles", + Permission::RoleUpdate => "Modify role information", + Permission::RoleDelete => "Remove roles", + Permission::PrincipalList => "View list of principals (users or system entities)", + Permission::PrincipalGet => "Retrieve specific principal information", + Permission::PrincipalCreate => "Create new principals", + Permission::PrincipalUpdate => "Modify principal information", + Permission::PrincipalDelete => "Remove principals", + Permission::BlobFetch => "Retrieve binary large objects", + Permission::PurgeBlobStore => "Clear the blob storage", + Permission::PurgeDataStore => "Clear the data storage", + Permission::PurgeLookupStore => "Clear the lookup storage", + Permission::PurgeAccount => "Completely remove an account and all associated data", + Permission::Undelete => "Restore deleted items", + Permission::DkimSignatureCreate => "Create DKIM signatures for email authentication", + Permission::DkimSignatureGet => "Retrieve DKIM signature information", + Permission::UpdateSpamFilter => "Modify spam filter settings", + Permission::UpdateWebadmin => "Modify web admin interface settings", + Permission::LogsView => "Access system logs", + Permission::SieveRun => "Execute Sieve scripts for email filtering", + Permission::Restart => "Restart the email server", + Permission::TracingList => "View list of system traces", + Permission::TracingGet => "Retrieve specific trace information", + Permission::TracingLive => "View real-time system traces", + Permission::MetricsList => "View list of system metrics", + Permission::MetricsLive => "View real-time system metrics", + Permission::Authenticate => "Perform authentication", + Permission::AuthenticateOauth => "Perform OAuth authentication", + Permission::EmailSend => "Send emails", + Permission::EmailReceive => "Receive emails", + Permission::ManageEncryption => "Handle encryption settings and operations", + Permission::ManagePasswords => "Manage user passwords", + Permission::JmapEmailGet => "Retrieve emails via JMAP", + Permission::JmapMailboxGet => "Retrieve mailboxes via JMAP", + Permission::JmapThreadGet => "Retrieve email threads via JMAP", + Permission::JmapIdentityGet => "Retrieve user identities via JMAP", + Permission::JmapEmailSubmissionGet => "Retrieve email submission info via JMAP", + Permission::JmapPushSubscriptionGet => "Retrieve push subscriptions via JMAP", + Permission::JmapSieveScriptGet => "Retrieve Sieve scripts via JMAP", + Permission::JmapVacationResponseGet => "Retrieve vacation responses via JMAP", + Permission::JmapPrincipalGet => "Retrieve principal information via JMAP", + Permission::JmapQuotaGet => "Retrieve quota information via JMAP", + Permission::JmapBlobGet => "Retrieve blobs via JMAP", + Permission::JmapEmailSet => "Modify emails via JMAP", + Permission::JmapMailboxSet => "Modify mailboxes via JMAP", + Permission::JmapIdentitySet => "Modify user identities via JMAP", + Permission::JmapEmailSubmissionSet => "Modify email submission settings via JMAP", + Permission::JmapPushSubscriptionSet => "Modify push subscriptions via JMAP", + Permission::JmapSieveScriptSet => "Modify Sieve scripts via JMAP", + Permission::JmapVacationResponseSet => "Modify vacation responses via JMAP", + Permission::JmapEmailChanges => "Track email changes via JMAP", + Permission::JmapMailboxChanges => "Track mailbox changes via JMAP", + Permission::JmapThreadChanges => "Track thread changes via JMAP", + Permission::JmapIdentityChanges => "Track identity changes via JMAP", + Permission::JmapEmailSubmissionChanges => "Track email submission changes via JMAP", + Permission::JmapQuotaChanges => "Track quota changes via JMAP", + Permission::JmapEmailCopy => "Copy emails via JMAP", + Permission::JmapBlobCopy => "Copy blobs via JMAP", + Permission::JmapEmailImport => "Import emails via JMAP", + Permission::JmapEmailParse => "Parse emails via JMAP", + Permission::JmapEmailQueryChanges => "Track email query changes via JMAP", + Permission::JmapMailboxQueryChanges => "Track mailbox query changes via JMAP", + Permission::JmapEmailSubmissionQueryChanges => { + "Track email submission query changes via JMAP" + } + Permission::JmapSieveScriptQueryChanges => "Track Sieve script query changes via JMAP", + Permission::JmapPrincipalQueryChanges => "Track principal query changes via JMAP", + Permission::JmapQuotaQueryChanges => "Track quota query changes via JMAP", + Permission::JmapEmailQuery => "Perform email queries via JMAP", + Permission::JmapMailboxQuery => "Perform mailbox queries via JMAP", + Permission::JmapEmailSubmissionQuery => "Perform email submission queries via JMAP", + Permission::JmapSieveScriptQuery => "Perform Sieve script queries via JMAP", + Permission::JmapPrincipalQuery => "Perform principal queries via JMAP", + Permission::JmapQuotaQuery => "Perform quota queries via JMAP", + Permission::JmapSearchSnippet => "Retrieve search snippets via JMAP", + Permission::JmapSieveScriptValidate => "Validate Sieve scripts via JMAP", + Permission::JmapBlobLookup => "Look up blobs via JMAP", + Permission::JmapBlobUpload => "Upload blobs via JMAP", + Permission::JmapEcho => "Perform JMAP echo requests", + Permission::ImapAuthenticate => "Authenticate via IMAP", + Permission::ImapAclGet => "Retrieve ACLs via IMAP", + Permission::ImapAclSet => "Set ACLs via IMAP", + Permission::ImapMyRights => "Retrieve own rights via IMAP", + Permission::ImapListRights => "List rights via IMAP", + Permission::ImapAppend => "Append messages via IMAP", + Permission::ImapCapability => "Retrieve server capabilities via IMAP", + Permission::ImapId => "Retrieve server ID via IMAP", + Permission::ImapCopy => "Copy messages via IMAP", + Permission::ImapMove => "Move messages via IMAP", + Permission::ImapCreate => "Create mailboxes via IMAP", + Permission::ImapDelete => "Delete mailboxes or messages via IMAP", + Permission::ImapEnable => "Enable IMAP extensions", + Permission::ImapExpunge => "Expunge deleted messages via IMAP", + Permission::ImapFetch => "Fetch messages or metadata via IMAP", + Permission::ImapIdle => "Use IMAP IDLE command", + Permission::ImapList => "List mailboxes via IMAP", + Permission::ImapLsub => "List subscribed mailboxes via IMAP", + Permission::ImapNamespace => "Retrieve namespaces via IMAP", + Permission::ImapRename => "Rename mailboxes via IMAP", + Permission::ImapSearch => "Search messages via IMAP", + Permission::ImapSort => "Sort messages via IMAP", + Permission::ImapSelect => "Select mailboxes via IMAP", + Permission::ImapExamine => "Examine mailboxes via IMAP", + Permission::ImapStatus => "Retrieve mailbox status via IMAP", + Permission::ImapStore => "Modify message flags via IMAP", + Permission::ImapSubscribe => "Subscribe to mailboxes via IMAP", + Permission::ImapThread => "Thread messages via IMAP", + Permission::Pop3Authenticate => "Authenticate via POP3", + Permission::Pop3List => "List messages via POP3", + Permission::Pop3Uidl => "Retrieve unique IDs via POP3", + Permission::Pop3Stat => "Retrieve mailbox statistics via POP3", + Permission::Pop3Retr => "Retrieve messages via POP3", + Permission::Pop3Dele => "Mark messages for deletion via POP3", + Permission::SieveAuthenticate => "Authenticate for Sieve script management", + Permission::SieveListScripts => "List Sieve scripts", + Permission::SieveSetActive => "Set active Sieve script", + Permission::SieveGetScript => "Retrieve Sieve scripts", + Permission::SievePutScript => "Upload Sieve scripts", + Permission::SieveDeleteScript => "Delete Sieve scripts", + Permission::SieveRenameScript => "Rename Sieve scripts", + Permission::SieveCheckScript => "Validate Sieve scripts", + Permission::SieveHaveSpace => "Check available space for Sieve scripts", + } + } +} diff --git a/crates/directory/src/core/principal.rs b/crates/directory/src/core/principal.rs index 4116dfbe..f17ba207 100644 --- a/crates/directory/src/core/principal.rs +++ b/crates/directory/src/core/principal.rs @@ -610,7 +610,7 @@ impl<'de> serde::Deserialize<'de> for PrincipalValue { type Value = PrincipalValue; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("an optional u64 or a vector of u64") + formatter.write_str("an optional values or a sequence of values") } fn visit_none(self) -> Result @@ -671,7 +671,7 @@ impl<'de> serde::Deserialize<'de> for PrincipalValue { } } - deserializer.deserialize_map(PrincipalValueVisitor) + deserializer.deserialize_any(PrincipalValueVisitor) } } @@ -748,6 +748,45 @@ impl<'de> serde::Deserialize<'de> for Principal { } } +#[derive(Debug)] +enum StringOrU64 { + String(String), + U64(u64), +} + +impl<'de> serde::Deserialize<'de> for StringOrU64 { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct StringOrU64Visitor; + + impl<'de> Visitor<'de> for StringOrU64Visitor { + type Value = StringOrU64; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string or u64") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Ok(StringOrU64::String(value.to_string())) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + Ok(StringOrU64::U64(value)) + } + } + + deserializer.deserialize_any(StringOrU64Visitor) + } +} + impl Permission { pub const fn is_user_permission(&self) -> bool { matches!( @@ -898,42 +937,3 @@ impl Permission { ) || self.is_user_permission() } } - -#[derive(Debug)] -enum StringOrU64 { - String(String), - U64(u64), -} - -impl<'de> serde::Deserialize<'de> for StringOrU64 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct StringOrU64Visitor; - - impl<'de> Visitor<'de> for StringOrU64Visitor { - type Value = StringOrU64; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a string or u64") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - Ok(StringOrU64::String(value.to_string())) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - Ok(StringOrU64::U64(value)) - } - } - - deserializer.deserialize_any(StringOrU64Visitor) - } -} diff --git a/crates/imap/src/core/mailbox.rs b/crates/imap/src/core/mailbox.rs index 8875f6f4..4a780ee6 100644 --- a/crates/imap/src/core/mailbox.rs +++ b/crates/imap/src/core/mailbox.rs @@ -351,10 +351,6 @@ 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/append.rs b/crates/imap/src/op/append.rs index 404ba9dc..02308ef9 100644 --- a/crates/imap/src/op/append.rs +++ b/crates/imap/src/op/append.rs @@ -89,7 +89,9 @@ impl SessionData { // Obtain quota let resource_token = self - .get_access_token() + .jmap + .core + .get_cached_access_token(mailbox.account_id) .await .imap_ctx(&arguments.tag, trc::location!())? .as_resource_token(); diff --git a/crates/imap/src/op/store.rs b/crates/imap/src/op/store.rs index e3e06743..3a799841 100644 --- a/crates/imap/src/op/store.rs +++ b/crates/imap/src/op/store.rs @@ -68,19 +68,16 @@ 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) @@ -88,7 +85,6 @@ 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/mailbox/set.rs b/crates/jmap/src/mailbox/set.rs index 1a150171..91ee0216 100644 --- a/crates/jmap/src/mailbox/set.rs +++ b/crates/jmap/src/mailbox/set.rs @@ -302,7 +302,9 @@ impl JMAP { } #[cfg(not(feature = "test_mode"))] - if [INBOX_ID, TRASH_ID, JUNK_ID].contains(&document_id) && !access_token.is_super_user() { + if [INBOX_ID, TRASH_ID, JUNK_ID].contains(&document_id) + && !access_token.has_permission(Permission::DeleteSystemFolders) + { return Ok(Err(SetError::forbidden().with_description( "You are not allowed to delete Inbox, Junk or Trash folders.", ))); diff --git a/tests/src/directory/internal.rs b/tests/src/directory/internal.rs index 110f578b..32b9bf90 100644 --- a/tests/src/directory/internal.rs +++ b/tests/src/directory/internal.rs @@ -449,6 +449,7 @@ async fn internal_directory() { quota: 1024, typ: Type::Individual, member_of: vec!["list".to_string(), "sales".to_string()], + ..Default::default() } ); assert_eq!(store.get_principal_id("john").await.unwrap(), None); @@ -530,7 +531,14 @@ async fn internal_directory() { // List accounts assert_eq!( store - .list_principals(None, None, &[], &[], 0, 0) + .list_principals( + None, + None, + &[Type::Individual, Type::Group, Type::List], + &[], + 0, + 0 + ) .await .unwrap() .items @@ -633,7 +641,14 @@ async fn internal_directory() { assert!(!store.rcpt("john.doe@example.org").await.unwrap()); assert_eq!( store - .list_principals(None, None, &[], &[], 0, 0) + .list_principals( + None, + None, + &[Type::Individual, Type::Group, Type::List], + &[], + 0, + 0 + ) .await .unwrap() .items diff --git a/tests/src/directory/ldap.rs b/tests/src/directory/ldap.rs index 7db2313f..ed3a6484 100644 --- a/tests/src/directory/ldap.rs +++ b/tests/src/directory/ldap.rs @@ -6,7 +6,7 @@ use std::fmt::Debug; -use directory::{backend::internal::manage::ManageDirectory, QueryBy, Type}; +use directory::{backend::internal::manage::ManageDirectory, QueryBy, Type, ROLE_USER}; use mail_send::Credentials; use crate::directory::{map_account_ids, DirectoryTest, IntoTestPrincipal, TestPrincipal}; @@ -57,6 +57,7 @@ async fn ldap_directory() { "john@example.org".to_string(), "john.doe@example.org".to_string() ], + roles: vec![ROLE_USER.to_string()], ..Default::default() } .into_sorted() @@ -85,6 +86,7 @@ async fn ldap_directory() { typ: Type::Individual, quota: 500000, emails: vec!["bill@example.org".to_string(),], + roles: vec![ROLE_USER.to_string()], ..Default::default() } .into_sorted() @@ -122,6 +124,7 @@ async fn ldap_directory() { .map(|v| v.to_string()) .collect(), emails: vec!["jane@example.org".to_string(),], + roles: vec![ROLE_USER.to_string()], ..Default::default() } .into_sorted() @@ -140,6 +143,7 @@ async fn ldap_directory() { name: "sales".to_string(), description: "sales".to_string().into(), typ: Type::Group, + roles: vec![ROLE_USER.to_string()], ..Default::default() } ); diff --git a/tests/src/directory/mod.rs b/tests/src/directory/mod.rs index 754a3841..7e34b2bc 100644 --- a/tests/src/directory/mod.rs +++ b/tests/src/directory/mod.rs @@ -266,6 +266,7 @@ pub struct TestPrincipal { pub secrets: Vec, pub emails: Vec, pub member_of: Vec, + pub roles: Vec, pub description: Option, } @@ -457,10 +458,9 @@ impl From for TestPrincipal { member_of: value .take_str_array(PrincipalField::MemberOf) .unwrap_or_default(), - /*member_of: value - .iter_int(PrincipalField::MemberOf) - .map(|v| v as u32) - .collect(),*/ + roles: value + .take_str_array(PrincipalField::Roles) + .unwrap_or_default(), description: value.take_str(PrincipalField::Description), } } diff --git a/tests/src/directory/sql.rs b/tests/src/directory/sql.rs index 1b8ced94..27a86047 100644 --- a/tests/src/directory/sql.rs +++ b/tests/src/directory/sql.rs @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use directory::{backend::internal::manage::ManageDirectory, QueryBy, Type}; +use directory::{backend::internal::manage::ManageDirectory, QueryBy, Type, ROLE_ADMIN, ROLE_USER}; use mail_send::Credentials; use store::{LookupStore, Store}; @@ -39,6 +39,9 @@ async fn sql_directory() { store.create_test_directory().await; // Create test users + store + .create_test_user("admin", "very_secret", "Administrator") + .await; store.create_test_user("john", "12345", "John Doe").await; store.create_test_user("jane", "abcde", "Jane Doe").await; store @@ -128,6 +131,7 @@ async fn sql_directory() { "jdoe@example.org".to_string(), "john.doe@example.org".to_string() ], + roles: vec![ROLE_USER.to_string()], ..Default::default() } ); @@ -154,6 +158,30 @@ async fn sql_directory() { typ: Type::Individual, quota: 500000, emails: vec!["bill@example.org".to_string(),], + roles: vec![ROLE_USER.to_string()], + ..Default::default() + } + ); + assert_eq!( + handle + .query( + QueryBy::Credentials(&Credentials::Plain { + username: "admin".to_string(), + secret: "very_secret".to_string() + }), + true + ) + .await + .unwrap() + .unwrap() + .into_test(), + TestPrincipal { + id: base_store.get_principal_id("admin").await.unwrap().unwrap(), + name: "admin".to_string(), + description: "Administrator".to_string().into(), + secrets: vec!["very_secret".to_string()], + typ: Type::Individual, + roles: vec![ROLE_ADMIN.to_string()], ..Default::default() } ); @@ -189,6 +217,7 @@ async fn sql_directory() { .map(|v| v.to_string()) .collect(), emails: vec!["jane@example.org".to_string(),], + roles: vec![ROLE_USER.to_string()], ..Default::default() } ); @@ -206,6 +235,7 @@ async fn sql_directory() { name: "sales".to_string(), description: "Sales Team".to_string().into(), typ: Type::Group, + roles: vec![ROLE_USER.to_string()], ..Default::default() } ); diff --git a/tests/src/imap/acl.rs b/tests/src/imap/acl.rs index ed5c8f43..156ba1f3 100644 --- a/tests/src/imap/acl.rs +++ b/tests/src/imap/acl.rs @@ -166,7 +166,6 @@ 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/smtp/inbound/auth.rs b/tests/src/smtp/inbound/auth.rs index e44f63a1..037bbe7a 100644 --- a/tests/src/smtp/inbound/auth.rs +++ b/tests/src/smtp/inbound/auth.rs @@ -9,10 +9,13 @@ use common::Core; use store::Stores; use utils::config::Config; -use crate::smtp::{ - build_smtp, - session::{TestSession, VerifyResponse}, - TempDir, +use crate::{ + smtp::{ + build_smtp, + session::{TestSession, VerifyResponse}, + TempDir, + }, + AssertConfig, }; use smtp::core::{Inner, Session, State}; @@ -22,6 +25,7 @@ data = "sqlite" lookup = "sqlite" blob = "sqlite" fts = "sqlite" +directory = "local" [store."sqlite"] type = "sqlite" @@ -74,6 +78,7 @@ async fn auth() { let mut config = Config::new(tmp_dir.update_config(CONFIG)).unwrap(); let stores = Stores::parse_all(&mut config).await; let core = Core::parse(&mut config, stores, Default::default()).await; + config.assert_no_errors(); // EHLO should not advertise plain text auth without TLS let mut session = Session::test(build_smtp(core, Inner::default())); diff --git a/tests/src/smtp/inbound/vrfy.rs b/tests/src/smtp/inbound/vrfy.rs index 705132d5..f431d503 100644 --- a/tests/src/smtp/inbound/vrfy.rs +++ b/tests/src/smtp/inbound/vrfy.rs @@ -11,10 +11,13 @@ use utils::config::Config; use smtp::core::{Inner, Session}; -use crate::smtp::{ - build_smtp, - session::{TestSession, VerifyResponse}, - TempDir, +use crate::{ + smtp::{ + build_smtp, + session::{TestSession, VerifyResponse}, + TempDir, + }, + AssertConfig, }; const CONFIG: &str = r#" @@ -23,6 +26,7 @@ data = "sqlite" lookup = "sqlite" blob = "sqlite" fts = "sqlite" +directory = "local" [store."sqlite"] type = "sqlite" @@ -72,6 +76,7 @@ async fn vrfy_expn() { let mut config = Config::new(tmp_dir.update_config(CONFIG)).unwrap(); let stores = Stores::parse_all(&mut config).await; let core = Core::parse(&mut config, stores, Default::default()).await; + config.assert_no_errors(); // EHLO should not advertise VRFY/EXPN to 10.0.0.2 let mut session = Session::test(build_smtp(core, Inner::default()));