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
+26
View File
@@ -89,6 +89,11 @@ impl JmapAuthorization for AccessToken {
let MethodObject::Registry(object_type) = object else {
unreachable!()
};
// inbuxa: MT-2: server-level objects are out of a tenant's reach
assert_tenant_reach(
self,
inbuxa_features::tenancy::reach::can_read(object_type),
)?;
object_type.get_permission()
}
},
@@ -203,6 +208,11 @@ impl JmapAuthorization for AccessToken {
let MethodObject::Registry(object_type) = object else {
unreachable!()
};
// inbuxa: MT-2, MT-12: server-level objects are out of a tenant's reach
assert_tenant_reach(
self,
inbuxa_features::tenancy::reach::can_write(object_type),
)?;
let set_permissions = object_type.set_permission();
validate_set(
s,
@@ -295,6 +305,11 @@ impl JmapAuthorization for AccessToken {
let MethodObject::Registry(object_type) = object else {
unreachable!()
};
// inbuxa: MT-2: server-level objects are out of a tenant's reach
assert_tenant_reach(
self,
inbuxa_features::tenancy::reach::can_read(object_type),
)?;
object_type.query_permission()
}
},
@@ -316,6 +331,17 @@ impl JmapAuthorization for AccessToken {
}
}
// inbuxa: MT-2
fn assert_tenant_reach(access_token: &AccessToken, reachable: bool) -> trc::Result<()> {
if reachable || access_token.tenant_id().is_none() {
Ok(())
} else {
Err(trc::JmapEvent::Forbidden
.into_err()
.details("You are not authorized to perform this action"))
}
}
fn validate_set<T: JmapObject>(
set: &SetRequest<'_, T>,
access_token: &AccessToken,