Multi-tenancy: the inbuxa-features crate, the permission ceiling and tenant disk quota (MT-12, MT-13, MT-14, MT-15, MT-16, MT-19, MT-20)
New crate crates/features (inbuxa-features), AGPL-3.0-only, holding the tenancy rules. Hooks in common: a tenant's roles and permission lists cap its people's permissions; a change to a tenant, or to a role a tenant holds, drops its members' cached permissions; delivery and every other write check the tenant's maxDiskQuota; usedDiskQuota reads the tenant usage counter. The default Tenant Administrator role gains sysTenantGet and sysTenantQuery.
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 John Coffey
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
//! Who a change to a tenant affects (MT-16).
|
||||
//!
|
||||
//! A principal's permissions are cached. A change to its tenant's roles,
|
||||
//! permissions or quotas has to reach it on its next request, so the cached
|
||||
//! permissions of everyone in the tenant are dropped with the change.
|
||||
|
||||
use registry::{
|
||||
schema::{
|
||||
prelude::ObjectType,
|
||||
structs::{Roles, Tenant},
|
||||
},
|
||||
types::id::ObjectId,
|
||||
};
|
||||
use store::{RegistryStore, registry::RegistryQuery};
|
||||
use trc::AddContext;
|
||||
use types::id::Id;
|
||||
|
||||
/// The accounts that belong to a tenant.
|
||||
pub async fn accounts(registry: &RegistryStore, tenant_id: u32) -> trc::Result<Vec<u32>> {
|
||||
registry
|
||||
.query::<Vec<Id>>(RegistryQuery::new(ObjectType::Account).with_tenant(Some(tenant_id)))
|
||||
.await
|
||||
.map(|ids| ids.into_iter().map(|id| id.document_id()).collect())
|
||||
.caused_by(trc::location!())
|
||||
}
|
||||
|
||||
/// The tenants whose ceiling a role takes part in, given the objects that
|
||||
/// link to the role. A tenant names the role itself when its roles are
|
||||
/// `Custom`; `Authentication` names it for every tenant whose roles are
|
||||
/// `Default`.
|
||||
pub async fn tenants_using_role(
|
||||
registry: &RegistryStore,
|
||||
linked: &[ObjectId],
|
||||
) -> trc::Result<Vec<u32>> {
|
||||
let mut tenants = Vec::new();
|
||||
let mut by_default = false;
|
||||
|
||||
for object_id in linked {
|
||||
match object_id.object() {
|
||||
ObjectType::Tenant => tenants.push(object_id.id().document_id()),
|
||||
ObjectType::Authentication => by_default = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if by_default {
|
||||
for id in registry
|
||||
.query::<Vec<Id>>(RegistryQuery::new(ObjectType::Tenant))
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
if let Some(tenant) = registry.object::<Tenant>(id).await?
|
||||
&& matches!(tenant.roles, Roles::Default)
|
||||
&& !tenants.contains(&id.document_id())
|
||||
{
|
||||
tenants.push(id.document_id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(tenants)
|
||||
}
|
||||
Reference in New Issue
Block a user