Fixed registry JMAP helpers

This commit is contained in:
Maurus Decimus
2026-04-13 19:51:45 +02:00
parent 7564f51642
commit f3dfb7cb33
32 changed files with 487 additions and 282 deletions

View File

@@ -8,8 +8,8 @@ use super::AccessToken;
use crate::{
Server,
auth::{
AccessScope, AccessTo, AccessTokenInner, AccountTenantIds, FALLBACK_ADMIN_ID, Permissions,
permissions::{BuildPermissions, build_permissions_list},
AccessScope, AccessTo, AccessTokenInner, AccountTenantIds, RECOVERY_ADMIN_ID, Permissions,
permissions::{BuildPermissions, PermissionsListBuilder},
},
network::limiter::{ConcurrencyLimiter, LimiterResult},
};
@@ -286,7 +286,7 @@ impl Server {
self.build_access_token(account, account_id, revision, revision_account)
.await?
.into()
} else if account_id == FALLBACK_ADMIN_ID {
} else if account_id == RECOVERY_ADMIN_ID {
AccessTokenInner::new_admin().into()
} else {
return Err(trc::SecurityEvent::Unauthorized
@@ -627,12 +627,17 @@ impl AccessToken {
pub fn permissions(&self) -> Vec<Permission> {
if let Some(scope) = self.inner.scopes.get(self.scope_idx) {
build_permissions_list(&scope.permissions)
scope.permissions.build_permissions_list()
} else {
vec![]
}
}
#[inline(always)]
pub fn access_scope(&self) -> Option<&AccessScope> {
self.inner.scopes.get(self.scope_idx)
}
pub(crate) fn permissions_bits(&self) -> &Permissions {
&self
.inner
@@ -773,7 +778,7 @@ impl AccessTokenInner {
pub fn new_admin() -> Self {
AccessTokenInner {
account_id: FALLBACK_ADMIN_ID,
account_id: RECOVERY_ADMIN_ID,
tenant_id: Default::default(),
member_of: Default::default(),
access_to: Default::default(),

View File

@@ -32,7 +32,7 @@ pub mod oauth;
pub mod permissions;
pub mod rate_limit;
pub const FALLBACK_ADMIN_ID: u32 = u32::MAX;
pub const RECOVERY_ADMIN_ID: u32 = u32::MAX;
const PERMISSIONS_BITSET_SIZE: usize = Permission::COUNT.div_ceil(std::mem::size_of::<usize>());
pub type Permissions = Bitset<PERMISSIONS_BITSET_SIZE>;
@@ -141,7 +141,7 @@ pub struct AccessTokenInner {
}
#[derive(Debug, Default, Hash, Clone)]
pub(crate) struct AccessScope {
pub struct AccessScope {
pub permissions: Permissions,
pub credential_id: u32,
pub expires_at: u64,

View File

@@ -154,30 +154,36 @@ impl AccessToken {
if requested_permissions.is_empty() {
Ok(())
} else {
Err(build_permissions_list(&requested_permissions))
Err(requested_permissions.build_permissions_list())
}
}
}
pub(crate) fn build_permissions_list(permissions_in: &Permissions) -> Vec<Permission> {
const USIZE_BITS: usize = std::mem::size_of::<usize>() * 8;
const USIZE_MASK: u32 = USIZE_BITS as u32 - 1;
let mut permissions = Vec::new();
pub trait PermissionsListBuilder {
fn build_permissions_list(&self) -> Vec<Permission>;
}
for (block_num, bytes) in permissions_in.inner().iter().enumerate() {
let mut bytes = *bytes;
impl PermissionsListBuilder for Permissions {
fn build_permissions_list(&self) -> Vec<Permission> {
const USIZE_BITS: usize = std::mem::size_of::<usize>() * 8;
const USIZE_MASK: u32 = USIZE_BITS as u32 - 1;
let mut permissions = Vec::new();
while bytes != 0 {
let item = USIZE_MASK - bytes.leading_zeros();
bytes ^= 1 << item;
if let Some(permission) =
Permission::from_id(((block_num * USIZE_BITS) + item as usize) as u16)
{
permissions.push(permission);
for (block_num, bytes) in self.inner().iter().enumerate() {
let mut bytes = *bytes;
while bytes != 0 {
let item = USIZE_MASK - bytes.leading_zeros();
bytes ^= 1 << item;
if let Some(permission) =
Permission::from_id(((block_num * USIZE_BITS) + item as usize) as u16)
{
permissions.push(permission);
}
}
}
permissions
}
permissions
}
pub struct DefaultPermissions {

View File

@@ -11,8 +11,8 @@ use crate::{
ACCOUNT_FLAG_ENCRYPT_APPEND, ACCOUNT_FLAG_ENCRYPT_METHOD_PGP,
ACCOUNT_FLAG_ENCRYPT_METHOD_SMIME, ACCOUNT_FLAG_ENCRYPT_TRAIN_SPAM_FILTER, ACCOUNT_IS_USER,
AccountCache, AccountInfo, AccountTenantIds, DOMAIN_FLAG_RELAY, DOMAIN_FLAG_SUB_ADDRESSING,
DomainCache, EmailAddress, EmailAddressRef, EmailCache, FALLBACK_ADMIN_ID,
MailingListCache, PermissionsGroup, RoleCache, TenantCache, permissions::BuildPermissions,
DomainCache, EmailAddress, EmailAddressRef, EmailCache, MailingListCache, PermissionsGroup,
RECOVERY_ADMIN_ID, RoleCache, TenantCache, permissions::BuildPermissions,
},
config::smtp::auth::DkimSigner,
expr::if_block::BootstrapExprExt,
@@ -281,7 +281,7 @@ impl Server {
pub async fn account(&self, account_id: u32) -> trc::Result<Arc<AccountCache>> {
if let Some(account) = self.try_account(account_id).await? {
Ok(account)
} else if account_id == FALLBACK_ADMIN_ID {
} else if account_id == RECOVERY_ADMIN_ID {
Ok(Arc::new(AccountCache {
name: self
.registry()
@@ -289,7 +289,7 @@ impl Server {
.map(|(name, _)| name.as_str())
.unwrap_or("recovery-admin")
.into(),
id: FALLBACK_ADMIN_ID,
id: RECOVERY_ADMIN_ID,
addresses: Default::default(),
id_tenant: Default::default(),
id_member_of: Default::default(),

View File

@@ -89,8 +89,9 @@ impl EmailConfig {
let auth = bp.setting_infallible::<Authentication>().await;
// Obtain default domain name
let default_domain_name = if let Some(default_domain) =
bp.get_infallible::<Domain>(system.default_domain_id).await
let default_domain_name = if system.default_domain_id.is_valid()
&& let Some(default_domain) =
bp.get_infallible::<Domain>(system.default_domain_id).await
{
default_domain.name
} else {

View File

@@ -136,6 +136,12 @@ impl BootManager {
.await
.failed("⚠️ Startup failed");
let mut bootstrap = Bootstrap::new(registry).await;
if matches!(import_export, StoreOp::None) {
// Add safe defaults if missing
bootstrap.insert_safe_defaults().await;
}
let todo = "implement recovery mode, check env_recovery_mode in RegistryStoreInner";
// Start listeners
@@ -150,9 +156,6 @@ impl BootManager {
match import_export {
StoreOp::None => {
// Add safe defaults if missing
bootstrap.insert_safe_defaults().await;
// Parse components
let core = Box::pin(Core::parse(&mut bootstrap, storage)).await;
let data = Data::parse(&mut bootstrap).await;

View File

@@ -24,6 +24,7 @@ use store::{
bootstrap::Bootstrap,
write::{RegistryWrite, RegistryWriteResult},
},
write::BatchBuilder,
};
use types::id::Id;
@@ -381,88 +382,12 @@ async fn insert_safe_defaults(bp: &mut Bootstrap) -> trc::Result<()> {
}
}
let mut default_domain_id = None;
if bp.registry.count_object(ObjectType::Domain).await? == 0 {
match bp
.registry
.write(RegistryWrite::insert(
&Domain {
name: psl::domain_str(bp.registry.local_hostname())
.unwrap_or("localhost.localdomain")
.to_string(),
is_enabled: true,
..Default::default()
}
.into(),
))
.await?
{
RegistryWriteResult::Success(id) => {
default_domain_id = Some(id);
}
err => {
bp.build_error(
ObjectType::Domain.singleton(),
format!("Failed to insert default domain: {err}"),
);
}
}
let todo = "review";
//#[cfg(not(feature = "test_mode"))]
/*if let Some(domain_id) = default_domain_id {
let now = store::write::now();
let signature_rsa = DkimSignature::Dkim1RsaSha256(Dkim1Signature {
domain_id,
stage: DkimSignatureStage::Testing,
selector: format!("rsa-{now}"),
private_key: DkimPrivateKey::Value(SecretTextValue {
secret: crate::network::dkim::generate_dkim_private_key(
DkimSignatureType::Dkim1RsaSha256,
)
.await?
.map_err(|err| {
trc::EventType::Dkim(trc::DkimEvent::BuildError)
.into_err()
.reason(err)
.caused_by(trc::location!())
})?,
}),
..Default::default()
});
let signature_ed = DkimSignature::Dkim1Ed25519Sha256(Dkim1Signature {
domain_id,
enabled: true,
selector: format!("ed-{now}"),
private_key: DkimPrivateKey::Value(SecretTextValue {
secret: crate::network::dkim::generate_dkim_private_key(
DkimSignatureType::Dkim1Ed25519Sha256,
)
.await?
.map_err(|err| {
trc::EventType::Dkim(trc::DkimEvent::BuildError)
.into_err()
.reason(err)
.caused_by(trc::location!())
})?,
}),
..Default::default()
});
for signature in [signature_rsa, signature_ed] {
bp.registry
.write(RegistryWrite::insert(&signature.into()))
.await?;
}
}*/
}
#[cfg(not(any(feature = "dev_mode", feature = "test_mode")))]
if bp.registry.count_object(ObjectType::SystemSettings).await? == 0 {
bp.registry
.write(RegistryWrite::insert(
&SystemSettings {
default_hostname: bp.registry.local_hostname().to_string(),
default_domain_id: default_domain_id.unwrap_or(Id::new(0)),
..Default::default()
}
.into(),
@@ -548,5 +473,20 @@ async fn insert_safe_defaults(bp: &mut Bootstrap) -> trc::Result<()> {
.await?;
}
if bp.registry.count_object(ObjectType::SpamRule).await? == 0
&& bp
.registry
.object::<SpamSettings>(Id::singleton())
.await?
.is_none_or(|spam| spam.spam_filter_rules_url.is_some())
{
let mut batch = BatchBuilder::new();
batch.schedule_task(Task::SpamFilterMaintenance(TaskSpamFilterMaintenance {
maintenance_type: TaskSpamFilterMaintenanceType::UpdateRules,
status: TaskStatus::now(),
}));
bp.data_store.write(batch.build_all()).await?;
}
Ok(())
}