JMAP Registry API implementation - part 12

This commit is contained in:
mdecimus
2026-03-09 19:21:42 +01:00
parent 455d3e3beb
commit a747239b61
45 changed files with 1718 additions and 883 deletions

View File

@@ -207,7 +207,7 @@ impl Server {
}?;
// Enforce alias login restrictions
if is_alias_login && !token.has_permission(Permission::AuthenticateAlias) {
if is_alias_login && !token.has_permission(Permission::AuthenticateWithAlias) {
return Err(trc::AuthEvent::Failed
.into_err()
.ctx(trc::Key::AccountName, auth_as_address.to_string())

View File

@@ -40,6 +40,7 @@ pub struct JmapConfig {
pub push_timeout: Duration,
pub push_verify_timeout: Duration,
pub push_throttle: Duration,
pub push_total_shards: u32,
pub web_socket_throttle: Duration,
pub web_socket_timeout: Duration,
@@ -79,6 +80,7 @@ impl JmapConfig {
push_timeout: jmap.push_request_timeout.into_inner(),
push_verify_timeout: jmap.push_verify_timeout.into_inner(),
push_throttle: jmap.push_throttle.into_inner(),
push_total_shards: jmap.push_shards_total as u32,
capabilities: BaseCapabilities::default(),
};

View File

@@ -10,12 +10,11 @@ use crate::{
network::security::Security,
};
use registry::schema::{
enums::{ClusterShardedTaskType, ClusterTaskType},
enums::ClusterTaskType,
prelude::ObjectType,
structs::{self, Asn, ClusterTaskGroup, HttpForm, Rate, SystemSettings, TaskManager},
};
use std::{hash::Hasher, str::FromStr, time::Duration};
use xxhash_rust::xxh3::Xxh3Builder;
use std::{str::FromStr, time::Duration};
#[derive(Clone)]
pub struct Network {
@@ -51,33 +50,18 @@ pub struct ContactForm {
pub field_honey_pot: Option<String>,
}
#[derive(Clone, Default)]
#[derive(Clone)]
pub struct ClusterRoles {
pub store_maintenance: ClusterRole,
pub account_maintenance: ClusterRole,
pub push_notifications: ClusterRole,
pub search_indexing: ClusterRole,
pub spam_training: ClusterRole,
pub imip_processing: ClusterRole,
pub merge_threads: ClusterRole,
pub calendar_alerts: ClusterRole,
pub dns_acme: ClusterRole,
pub calculate_metrics: ClusterRole,
pub push_metrics: ClusterRole,
pub outbound_mta: ClusterRole,
pub task_scheduler: ClusterRole,
pub task_manager: ClusterRole,
}
#[derive(Clone, Copy, Default)]
pub enum ClusterRole {
#[default]
Enabled,
Disabled,
Sharded {
shard_id: u32,
total_shards: u32,
},
pub store_maintenance: bool,
pub account_maintenance: bool,
pub push_notifications: bool,
pub search_indexing: bool,
pub spam_training: bool,
pub metrics_calculate: bool,
pub metrics_push: bool,
pub outbound_mta: bool,
pub task_scheduler: bool,
pub task_manager: bool,
}
#[derive(Clone, Default)]
@@ -164,140 +148,20 @@ impl Network {
ClusterTaskGroup::EnableAll => {}
ClusterTaskGroup::DisableAll => {
for network_role in network.roles.all_mut() {
network_role.set_role(false);
*network_role = false;
}
}
ClusterTaskGroup::EnableSome(group) => {
for network_role in network.roles.all_mut() {
network_role.set_role(false);
*network_role = false;
}
for task_type in group.task_types.iter() {
match task_type {
ClusterTaskType::StoreMaintenance => {
network.roles.store_maintenance.set_role(true);
}
ClusterTaskType::AccountMaintenance => {
network.roles.account_maintenance.set_role(true);
}
ClusterTaskType::DnsAndAcme => {
network.roles.dns_acme.set_role(true);
}
ClusterTaskType::CalculateMetrics => {
network.roles.calculate_metrics.set_role(true);
}
ClusterTaskType::PushMetrics => {
network.roles.push_metrics.set_role(true);
}
ClusterTaskType::PushNotifications => {
network.roles.push_notifications.set_role(true);
}
ClusterTaskType::SearchIndexing => {
network.roles.search_indexing.set_role(true);
}
ClusterTaskType::SpamClassifierTraining => {
network.roles.spam_training.set_role(true);
}
ClusterTaskType::ImipProcessing => {
network.roles.imip_processing.set_role(true);
}
ClusterTaskType::CalendarAlerts => {
network.roles.calendar_alerts.set_role(true);
}
ClusterTaskType::MergeThreads => {
network.roles.merge_threads.set_role(true);
}
ClusterTaskType::OutboundMta => {
network.roles.outbound_mta.set_role(true);
}
ClusterTaskType::TaskQueueProcessing => {
network.roles.task_manager.set_role(true);
}
ClusterTaskType::TaskScheduler => {
network.roles.task_scheduler.set_role(true);
}
}
network.roles.set_role(*task_type, true);
}
}
ClusterTaskGroup::DisableSome(group) => {
for task_type in group.task_types.iter() {
match task_type {
ClusterTaskType::StoreMaintenance => {
network.roles.store_maintenance.set_role(true);
}
ClusterTaskType::AccountMaintenance => {
network.roles.account_maintenance.set_role(false);
}
ClusterTaskType::DnsAndAcme => {
network.roles.dns_acme.set_role(false);
}
ClusterTaskType::CalculateMetrics => {
network.roles.calculate_metrics.set_role(false);
}
ClusterTaskType::PushMetrics => {
network.roles.push_metrics.set_role(false);
}
ClusterTaskType::PushNotifications => {
network.roles.push_notifications.set_role(false);
}
ClusterTaskType::SearchIndexing => {
network.roles.search_indexing.set_role(false);
}
ClusterTaskType::SpamClassifierTraining => {
network.roles.spam_training.set_role(false);
}
ClusterTaskType::ImipProcessing => {
network.roles.imip_processing.set_role(false);
}
ClusterTaskType::CalendarAlerts => {
network.roles.calendar_alerts.set_role(false);
}
ClusterTaskType::MergeThreads => {
network.roles.merge_threads.set_role(false);
}
ClusterTaskType::OutboundMta => {
network.roles.outbound_mta.set_role(false);
}
ClusterTaskType::TaskQueueProcessing => {
network.roles.task_manager.set_role(false);
}
ClusterTaskType::TaskScheduler => {
network.roles.task_scheduler.set_role(false);
}
}
}
}
}
if role.shard_size > 1 {
for task_type in role.shard_task_types.iter() {
let network_role = match task_type {
ClusterShardedTaskType::StoreMaintenance => {
&mut network.roles.store_maintenance
}
ClusterShardedTaskType::AccountMaintenance => {
&mut network.roles.account_maintenance
}
ClusterShardedTaskType::DnsAndAcme => &mut network.roles.dns_acme,
ClusterShardedTaskType::PushNotifications => {
&mut network.roles.push_notifications
}
ClusterShardedTaskType::SearchIndexing => {
&mut network.roles.search_indexing
}
ClusterShardedTaskType::ImipProcessing => {
&mut network.roles.imip_processing
}
ClusterShardedTaskType::CalendarAlerts => {
&mut network.roles.calendar_alerts
}
ClusterShardedTaskType::MergeThreads => &mut network.roles.merge_threads,
};
if network_role.is_enabled_or_sharded() {
*network_role = ClusterRole::Sharded {
shard_id: bp.registry.cluster_role_shard() as u32,
total_shards: role.shard_size as u32,
};
network.roles.set_role(*task_type, false);
}
}
}
@@ -408,104 +272,52 @@ impl AsnGeoLookupConfig {
}
}
impl ClusterRole {
pub fn is_enabled_or_sharded(&self) -> bool {
debug_assert!(!self.is_uninit() && !self.is_seen_role());
matches!(self, ClusterRole::Enabled | ClusterRole::Sharded { .. })
}
pub fn is_enabled_for_integer(&self, value: u64) -> bool {
debug_assert!(!self.is_uninit() && !self.is_seen_role());
match self {
ClusterRole::Enabled => true,
ClusterRole::Disabled => false,
ClusterRole::Sharded {
shard_id,
total_shards,
} => (value as u32 % total_shards) == *shard_id,
}
}
pub fn is_enabled_for_hash(&self, item: &impl std::hash::Hash) -> bool {
debug_assert!(!self.is_uninit() && !self.is_seen_role());
match self {
ClusterRole::Enabled => true,
ClusterRole::Disabled => false,
ClusterRole::Sharded {
shard_id,
total_shards,
} => {
let mut hasher = Xxh3Builder::new().with_seed(201179).build();
item.hash(&mut hasher);
hasher.finish() % (*total_shards as u64) == *shard_id as u64
}
}
}
fn set_uninit(&mut self) {
*self = ClusterRole::Sharded {
shard_id: u32::MAX,
total_shards: u32::MAX,
};
}
fn set_role(&mut self, is_member: bool) -> bool {
if self.is_uninit() {
if is_member {
*self = ClusterRole::Enabled;
} else {
*self = ClusterRole::Sharded {
shard_id: u32::MAX,
total_shards: 0,
};
}
true
} else {
false
}
}
fn is_seen_role(&self) -> bool {
matches!(self, ClusterRole::Sharded {
shard_id,
total_shards,
} if *shard_id == u32::MAX && *total_shards == 0)
}
fn is_uninit(&self) -> bool {
matches!(self, ClusterRole::Sharded {
shard_id,
total_shards,
} if *shard_id == u32::MAX && *total_shards == u32::MAX)
}
fn finalize(&mut self) {
if self.is_uninit() {
*self = ClusterRole::Enabled;
} else if self.is_seen_role() {
*self = ClusterRole::Disabled;
}
}
}
impl ClusterRoles {
fn all_mut(&mut self) -> impl Iterator<Item = &mut ClusterRole> {
fn all_mut(&mut self) -> impl Iterator<Item = &mut bool> {
[
&mut self.store_maintenance,
&mut self.account_maintenance,
&mut self.push_notifications,
&mut self.search_indexing,
&mut self.spam_training,
&mut self.imip_processing,
&mut self.merge_threads,
&mut self.calendar_alerts,
&mut self.dns_acme,
&mut self.outbound_mta,
&mut self.calculate_metrics,
&mut self.push_metrics,
&mut self.task_manager,
&mut self.task_scheduler,
&mut self.metrics_calculate,
&mut self.metrics_push,
]
.into_iter()
}
fn set_role(&mut self, role: ClusterTaskType, enabled: bool) {
match role {
ClusterTaskType::StoreMaintenance => self.store_maintenance = enabled,
ClusterTaskType::AccountMaintenance => self.account_maintenance = enabled,
ClusterTaskType::PushNotifications => self.push_notifications = enabled,
ClusterTaskType::SearchIndexing => self.search_indexing = enabled,
ClusterTaskType::SpamClassifierTraining => self.spam_training = enabled,
ClusterTaskType::MetricsCalculate => self.metrics_calculate = enabled,
ClusterTaskType::MetricsPush => self.metrics_push = enabled,
ClusterTaskType::OutboundMta => self.outbound_mta = enabled,
ClusterTaskType::TaskQueueProcessing => self.task_manager = enabled,
ClusterTaskType::TaskScheduler => self.task_scheduler = enabled,
}
}
}
impl Default for ClusterRoles {
fn default() -> Self {
ClusterRoles {
store_maintenance: true,
account_maintenance: true,
push_notifications: true,
search_indexing: true,
spam_training: true,
metrics_calculate: true,
metrics_push: true,
outbound_mta: true,
task_manager: true,
task_scheduler: true,
}
}
}

View File

@@ -14,7 +14,7 @@ use hickory_proto::rr::dnssec::KeyPair;
use rcgen::generate_simple_self_signed;
use registry::schema::{
enums,
structs::{self, Certificate, DnsServer},
structs::{self, Certificate, DnsServer, SystemSettings},
};
use ring::signature::{EcdsaKeyPair, Ed25519KeyPair};
use rustls::{
@@ -268,6 +268,8 @@ pub(crate) async fn parse_certificates(
certificates: &mut AHashMap<Box<str>, Arc<CertifiedKey>>,
subject_names: &mut AHashSet<Box<str>>,
) {
let system = bp.setting_infallible::<SystemSettings>().await;
// Parse certificates
for cert_obj in bp.list_infallible::<Certificate>().await {
let secret = match cert_obj.object.private_key.secret().await {
@@ -347,7 +349,11 @@ pub(crate) async fn parse_certificates(
}
// Add default certificate
if cert_obj.object.default {
if system
.default_certificate_id
.as_ref()
.is_some_and(|id| *id == cert_obj.id.id())
{
certificates.insert("*".into(), cert.clone());
}
}

View File

@@ -81,7 +81,7 @@ impl Expression {
}
}
pub(crate) trait BootstrapExprExt {
pub trait BootstrapExprExt {
fn compile_expr(&mut self, id: ObjectId, expr_ctx: &ExpressionContext<'_>) -> IfBlock;
fn compile_default_expr(&mut self, id: ObjectId, expr_ctx: &ExpressionContext<'_>) -> IfBlock;
fn try_compile_expr(

View File

@@ -284,7 +284,7 @@ impl BootManager {
.cluster_role()
.unwrap_or("[default]")
.to_string(),
Details = bootstrap.registry.cluster_role_shard()
Details = bootstrap.registry.cluster_push_shard()
);
}

View File

@@ -36,7 +36,7 @@ pub async fn exec(ctx: PluginContext<'_>) -> trc::Result<Variable> {
if ctx.access_token.is_none_or(|token| {
use registry::schema::enums::Permission;
if token.has_permission(Permission::AiModelInteract) {
if token.has_permission(Permission::InteractAi) {
true
} else {
use registry::types::EnumImpl;
@@ -44,7 +44,7 @@ pub async fn exec(ctx: PluginContext<'_>) -> trc::Result<Variable> {
trc::event!(
Security(SecurityEvent::Unauthorized),
AccountId = token.account_id(),
Details = Permission::AiModelInteract.as_str(),
Details = Permission::InteractAi.as_str(),
SpanId = ctx.session_id,
);
false