summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormdecimus <mauro@stalw.art>2024-09-15 18:59:36 +0200
committermdecimus <mauro@stalw.art>2024-09-15 18:59:36 +0200
commit1e08e5667207f424d41fe45736a1aa34d76f478f (patch)
tree93065fe1f8196eaec65602bc51948f149a8d8b38
parent49bce9a3dede38c23760588b6b75aa8780d6624e (diff)
Test fixes - part 1
-rw-r--r--crates/common/src/auth/access_token.rs7
-rw-r--r--crates/common/src/config/mod.rs6
-rw-r--r--crates/directory/src/backend/internal/lookup.rs1
-rw-r--r--crates/directory/src/backend/internal/manage.rs334
-rw-r--r--crates/directory/src/lib.rs12
-rw-r--r--crates/imap/src/core/mailbox.rs4
-rw-r--r--crates/imap/src/op/store.rs4
-rw-r--r--crates/jmap/src/api/management/principal.rs203
-rw-r--r--crates/jmap/src/api/management/queue.rs21
-rw-r--r--crates/jmap/src/api/management/report.rs21
-rw-r--r--tests/src/directory/internal.rs170
-rw-r--r--tests/src/imap/acl.rs1
-rw-r--r--tests/src/imap/mod.rs5
-rw-r--r--tests/src/jmap/auth_acl.rs10
-rw-r--r--tests/src/jmap/auth_limits.rs4
-rw-r--r--tests/src/jmap/auth_oauth.rs8
-rw-r--r--tests/src/jmap/blob.rs2
-rw-r--r--tests/src/jmap/crypto.rs2
-rw-r--r--tests/src/jmap/delivery.rs6
-rw-r--r--tests/src/jmap/email_submission.rs2
-rw-r--r--tests/src/jmap/event_source.rs2
-rw-r--r--tests/src/jmap/mod.rs32
-rw-r--r--tests/src/jmap/purge.rs2
-rw-r--r--tests/src/jmap/push_subscription.rs2
-rw-r--r--tests/src/jmap/quota.rs4
-rw-r--r--tests/src/jmap/sieve_script.rs2
-rw-r--r--tests/src/jmap/stress_test.rs2
-rw-r--r--tests/src/jmap/thread_merge.rs4
-rw-r--r--tests/src/jmap/vacation_response.rs2
-rw-r--r--tests/src/jmap/websocket.rs2
-rw-r--r--tests/src/store/import_export.rs6
31 files changed, 580 insertions, 303 deletions
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<Permission> {
+ const BYTES_LEN: u32 = (std::mem::size_of::<usize>() * 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::<usize>() - 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::<usize>()) + item).unwrap(),
+ Permission::from_id((block_num * std::mem::size_of::<usize>()) + 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<Option<Principal>>;
async fn email_to_ids(&self, email: &str) -> trc::Result<Vec<u32>>;
-
async fn is_local_domain(&self, domain: &str) -> trc::Result<bool>;
async fn rcpt(&self, address: &str) -> trc::Result<bool>;
async fn vrfy(&self, address: &str) -> trc::Result<Vec<String>>;
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<Principal>,
+ pub total: u64,
+}
+
#[allow(async_fn_in_trait)]
pub trait ManageDirectory: Sized {
async fn get_principal_id(&self, name: &str) -> trc::Result<Option<u32>>;
@@ -50,15 +56,23 @@ pub trait ManageDirectory: Sized {
async fn list_principals(
&self,
filter: Option<&str>,
- typ: Option<Type>,
tenant_id: Option<u32>,
- ) -> trc::Result<Vec<String>>;
+ types: &[Type],
+ fields: &[PrincipalField],
+ page: usize,
+ limit: usize,
+ ) -> trc::Result<PrincipalList>;
async fn count_principals(
&self,
filter: Option<&str>,
typ: Option<Type>,
tenant_id: Option<u32>,
) -> trc::Result<u64>;
+ 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(", ")
- };
-
- return Err(error(
- "Tenant has members",
- format!(
- "Tenant must have no members to be deleted: Found: {tenant_members}"
- )
- .into(),
- ));
+ if tenant_members.total > 0 {
+ let mut message =
+ String::from("Tenant must have no members to be deleted: Found: ");
+
+ 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<Type>,
tenant_id: Option<u32>,
- ) -> trc::Result<Vec<String>> {
+ types: &[Type],
+ fields: &[PrincipalField],
+ page: usize,
+ limit: usize,
+ ) -> trc::Result<PrincipalList> {
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::<Vec<_>>();
+ 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::<Principal>(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<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/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<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)
@@ -85,6 +88,7 @@ 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/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::<Vec<_>>()
+ })
.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::<Vec<_>>()
+ })
.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::<AHashSet<_>>(),
["jane", "john.doe", "list", "sales", "support"]
.into_iter()
@@ -525,15 +543,24 @@ async fn internal_directory() {
.collect::<AHashSet<_>>()
);
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<_>>(),
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::<AHashSet<_>>(),
["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::<AHashSet<_>>(),
["sales", "support"]
.into_iter()
@@ -553,7 +582,14 @@ async fn internal_directory() {
.collect::<AHashSet<_>>()
);
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<_>>(),
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::<AHashSet<_>>(),
["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<JMAP>, 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
@@ -204,12 +204,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,
))),