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.
115 lines
3.3 KiB
Rust
115 lines
3.3 KiB
Rust
/*
|
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
|
*/
|
|
|
|
use crate::{
|
|
Server,
|
|
auth::AccountCache,
|
|
storage::{ObjectQuota, TenantQuota},
|
|
};
|
|
use registry::{
|
|
schema::enums::{StorageQuota, TenantStorageQuota},
|
|
types::EnumImpl,
|
|
};
|
|
use store::{ValueKey, write::ValueClass};
|
|
use trc::AddContext;
|
|
|
|
impl Server {
|
|
pub async fn get_used_quota_account(&self, account_id: u32) -> trc::Result<i64> {
|
|
self.core
|
|
.storage
|
|
.data
|
|
.get_counter(ValueKey {
|
|
account_id,
|
|
collection: 0,
|
|
document_id: 0,
|
|
class: ValueClass::Quota,
|
|
})
|
|
.await
|
|
.add_context(|err| err.caused_by(trc::location!()).account_id(account_id))
|
|
}
|
|
|
|
// inbuxa: MT-20: storage used by all a tenant's members together
|
|
pub async fn get_used_quota_tenant(&self, tenant_id: u32) -> trc::Result<i64> {
|
|
inbuxa_features::tenancy::quota::used(&self.core.storage.data, tenant_id).await
|
|
}
|
|
|
|
pub async fn has_available_quota(
|
|
&self,
|
|
account: &AccountCache,
|
|
item_size: u64,
|
|
) -> trc::Result<()> {
|
|
if account.quota_disk != 0 {
|
|
let used_quota = self.get_used_quota_account(account.id).await?.max(0) as u64;
|
|
|
|
if used_quota + item_size > account.quota_disk {
|
|
return Err(trc::LimitEvent::Quota
|
|
.into_err()
|
|
.ctx(trc::Key::Limit, account.quota_disk)
|
|
.ctx(trc::Key::Size, used_quota));
|
|
}
|
|
}
|
|
|
|
// inbuxa: MT-19: the tenant's limit applies too, whichever is reached first
|
|
if let Some(tenant_id) = account.id_tenant {
|
|
let tenant = self.tenant(tenant_id).await?;
|
|
if tenant.quota_disk != 0 {
|
|
let used_quota = self.get_used_quota_tenant(tenant_id).await?.max(0) as u64;
|
|
|
|
if used_quota + item_size > tenant.quota_disk {
|
|
return Err(trc::LimitEvent::TenantQuota
|
|
.into_err()
|
|
.ctx(trc::Key::Id, tenant_id)
|
|
.ctx(trc::Key::Limit, tenant.quota_disk)
|
|
.ctx(trc::Key::Size, used_quota));
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn object_quota(&self, user_quotas: Option<&ObjectQuota>, object: StorageQuota) -> u32 {
|
|
user_quotas.unwrap_or(&self.core.email.max_objects).0[object as usize]
|
|
}
|
|
}
|
|
|
|
impl ObjectQuota {
|
|
#[inline(always)]
|
|
pub fn set(&mut self, item: StorageQuota, max: u32) {
|
|
self.0[item as usize] = max;
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn get(&self, item: StorageQuota) -> u32 {
|
|
self.0[item as usize]
|
|
}
|
|
}
|
|
|
|
impl TenantQuota {
|
|
#[inline(always)]
|
|
pub fn set(&mut self, item: TenantStorageQuota, max: u32) {
|
|
self.0[item as usize] = max;
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn get(&self, item: TenantStorageQuota) -> u32 {
|
|
self.0[item as usize]
|
|
}
|
|
}
|
|
|
|
impl Default for ObjectQuota {
|
|
fn default() -> Self {
|
|
Self([u32::MAX; StorageQuota::COUNT - 1])
|
|
}
|
|
}
|
|
|
|
impl Default for TenantQuota {
|
|
fn default() -> Self {
|
|
Self([u32::MAX; TenantStorageQuota::COUNT - 1])
|
|
}
|
|
}
|