summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/common/src/auth/access_token.rs14
-rw-r--r--crates/directory/src/backend/internal/lookup.rs2
-rw-r--r--crates/directory/src/backend/internal/manage.rs18
-rw-r--r--crates/directory/src/backend/ldap/lookup.rs97
-rw-r--r--crates/directory/src/backend/memory/config.rs2
-rw-r--r--crates/directory/src/backend/sql/lookup.rs53
-rw-r--r--crates/directory/src/core/mod.rs176
-rw-r--r--crates/directory/src/core/principal.rs82
-rw-r--r--crates/imap/src/core/mailbox.rs4
-rw-r--r--crates/imap/src/op/append.rs4
-rw-r--r--crates/imap/src/op/store.rs4
-rw-r--r--crates/jmap/src/mailbox/set.rs4
-rw-r--r--tests/src/directory/internal.rs19
-rw-r--r--tests/src/directory/ldap.rs6
-rw-r--r--tests/src/directory/mod.rs8
-rw-r--r--tests/src/directory/sql.rs32
-rw-r--r--tests/src/imap/acl.rs1
-rw-r--r--tests/src/smtp/inbound/auth.rs13
-rw-r--r--tests/src/smtp/inbound/vrfy.rs13
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<Permission> {
- const BYTES_LEN: u32 = (std::mem::size_of::<usize>() * 8) as u32 - 1;
+ const USIZE_BITS: usize = std::mem::size_of::<usize>() * 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::<usize>()) + 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::<Rows>(&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::<Rows>(&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<E>(self) -> Result<Self::Value, E>
@@ -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<D>(deserializer: D) -> Result<Self, D::Error>
+ 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<E>(self, value: &str) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ Ok(StringOrU64::String(value.to_string()))
+ }
+
+ fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
+ 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<D>(deserializer: D) -> Result<Self, D::Error>
- 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<E>(self, value: &str) -> Result<Self::Value, E>
- where
- E: de::Error,
- {
- Ok(StringOrU64::String(value.to_string()))
- }
-
- fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
- 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<T: SessionStream> SessionData<T> {
.shared_accounts(Collection::Mailbox)
.copied()
.collect::<Vec<_>>();
- 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<T: SessionStream> SessionData<T> {
// 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<T: SessionStream> SessionData<T> {
op_start: Instant,
) -> trc::Result<Vec<u8>> {
// 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<T: SessionStream> SessionData<T> {
}
// 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<String>,
pub emails: Vec<String>,
pub member_of: Vec<String>,
+ pub roles: Vec<String>,
pub description: Option<String>,
}
@@ -457,10 +458,9 @@ impl From<Principal> 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()));