Multi-tenancy: registry reach, links, membership and count limits over JMAP (MT-2, MT-3, MT-6, MT-7, MT-8, MT-11, MT-12, MT-17, MT-18)

Inside a tenant, server-level object types are forbidden and x:Tenant reads
return only the caller's own tenant, which it can't change. Registry writes
refuse links across tenant boundaries in both directions, give a new
principal its domain's tenant, move a domain's principals and DKIM keys with
it into a tenant, refuse moves out of a tenant while its people remain, and
refuse creates past a tenant's count limits with overQuota and
limit.tenant-quota.
This commit is contained in:
2026-09-18 15:20:04 -07:00
parent ad0db8b2b0
commit 5a22e79992
5 changed files with 84 additions and 0 deletions
+39
View File
@@ -381,6 +381,15 @@ impl RegistrySet for Server {
new_object.inner.set_member_tenant_id(tenant_id.into());
}
// inbuxa: MT-7: a principal takes its domain's tenant
if can_set_tenant {
inbuxa_features::tenancy::writes::default_tenant(
self.registry(),
&mut new_object,
)
.await?;
}
// Add accountId
if has_account_id {
new_object.inner.set_account_id(set.account_id.into());
@@ -526,6 +535,26 @@ impl RegistrySet for Server {
}
};
// inbuxa: MT-3, MT-8, MT-17
let (stored_id, stored) = match &modification {
Modification::Update { id, object } => (Some(*id), Some(object)),
Modification::Create { .. } => (None, None),
};
let after_save = match inbuxa_features::tenancy::writes::check(
self.registry(),
stored_id,
stored,
&new_object,
)
.await?
{
Ok(after_save) => after_save,
Err(err) => {
set.failed(modification, err);
continue 'outer;
}
};
// Validate expressions
if let Some(expressions) = new_object.inner.expression_ctxs() {
let mut bp = Bootstrap::new_uninitialized(self.registry().clone());
@@ -590,6 +619,16 @@ impl RegistrySet for Server {
let object_id = match (modification, result) {
(Modification::Update { id, object }, RegistryWriteResult::Success(_)) => {
cache_invalidator.process_update(id, &object, &new_object);
// inbuxa: MT-8: what moves with a domain follows it
for (id, old, new) in inbuxa_features::tenancy::writes::after_save(
&self.core.storage.data,
self.registry(),
after_save,
)
.await?
{
cache_invalidator.process_update(id, &old, &new);
}
if let (
ObjectInner::Application(previous),
ObjectInner::Application(updated),