Enterprise: remove tenant admin permissions when license is invalid

This commit is contained in:
mdecimus
2025-12-15 16:30:12 +01:00
parent 4675b18491
commit fd3736252d
5 changed files with 125 additions and 147 deletions

View File

@@ -97,12 +97,12 @@ impl Server {
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
if self.is_enterprise_edition()
&& let Some(tenant_id) = tenant_id
{
// Limit tenant permissions
use directory::{QueryParams, ROLE_USER};
use directory::QueryParams;
if let Some(tenant_id) = tenant_id {
if self.is_enterprise_edition() {
// Limit tenant permissions
permissions.intersection(&self.get_role_permissions(tenant_id).await?.enabled);
// Obtain tenant quota
@@ -123,6 +123,11 @@ impl Server {
.quota()
.unwrap_or_default(),
});
} else {
// Enterprise edition downgrade, remove any tenant administrator permissions
permissions.intersection(&self.get_role_permissions(ROLE_USER).await?.enabled);
}
}
}
// SPDX-SnippetEnd

View File

@@ -8,10 +8,17 @@
*
*/
use std::{sync::Arc, time::Duration};
use super::{
AlertContent, AlertContentToken, AlertMethod, Enterprise, MetricAlert, MetricStore,
SpamFilterLlmConfig, TraceStore, Undelete, license::LicenseKey, llm::AiApiConfig,
};
use crate::{
expr::{Expression, tokenizer::TokenMap},
manager::config::ConfigManager,
};
use ahash::AHashMap;
use directory::{Type, backend::internal::manage::ManageDirectory};
use std::{sync::Arc, time::Duration};
use store::{Store, Stores};
use trc::{EventType, MetricType, TOTAL_EVENT_COUNT};
use utils::{
@@ -23,16 +30,6 @@ use utils::{
template::Template,
};
use crate::{
expr::{Expression, tokenizer::TokenMap},
manager::config::ConfigManager,
};
use super::{
AlertContent, AlertContentToken, AlertMethod, Enterprise, MetricAlert, MetricStore,
SpamFilterLlmConfig, TraceStore, Undelete, license::LicenseKey, llm::AiApiConfig,
};
impl Enterprise {
pub async fn parse(
config: &mut Config,

View File

@@ -19,20 +19,17 @@
* for copyright infringement, breach of contract, and fraud.
*/
use crate::manager::fetch_resource;
use base64::{Engine, engine::general_purpose::STANDARD};
use hyper::{HeaderMap, header::AUTHORIZATION};
use ring::signature::{ED25519, UnparsedPublicKey};
use std::{
fmt::{Display, Formatter},
time::Duration,
};
use hyper::{HeaderMap, header::AUTHORIZATION};
use ring::signature::{ED25519, UnparsedPublicKey};
use base64::{Engine, engine::general_purpose::STANDARD};
use store::write::now;
use trc::ServerEvent;
use crate::manager::fetch_resource;
//const LICENSING_API: &str = "https://localhost:444/api/license/";
const LICENSING_API: &str = "https://license.stalw.art/api/license/";
const RENEW_THRESHOLD: u64 = 60 * 60 * 24 * 4; // 4 days
@@ -281,73 +278,3 @@ impl Display for LicenseError {
}
}
}
/*
use rustls::sign::CertifiedKey;
use webpki::TrustAnchor;
use x509_parser::{certificate::X509Certificate, prelude::FromDer};
fn validate_certificate(key: &CertifiedKey) -> Result<(), Box<dyn std::error::Error>> {
let cert_der = key.end_entity_cert()?.as_ref();
webpki::EndEntityCert::try_from(cert_der)?.verify_is_valid_tls_server_cert(
&[
&webpki::ECDSA_P256_SHA256,
&webpki::ECDSA_P256_SHA384,
&webpki::ECDSA_P384_SHA256,
&webpki::ECDSA_P384_SHA384,
&webpki::ED25519,
&webpki::RSA_PKCS1_2048_8192_SHA256,
&webpki::RSA_PKCS1_2048_8192_SHA384,
&webpki::RSA_PKCS1_2048_8192_SHA512,
&webpki::RSA_PKCS1_3072_8192_SHA384,
&webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY,
&webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY,
&webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY,
],
&webpki::TlsServerTrustAnchors(
webpki_roots::TLS_SERVER_ROOTS
.iter()
.map(|ta| TrustAnchor {
subject: ta.subject.as_ref(),
spki: ta.subject_public_key_info.as_ref(),
name_constraints: ta.name_constraints.as_ref().map(|nc| nc.as_ref()),
})
.collect::<Vec<_>>()
.as_slice(),
),
&key.cert
.iter()
.skip(1)
.map(|der| der.as_ref())
.collect::<Vec<_>>(),
webpki::Time::try_from(SystemTime::now())?,
)?;
// Additional checks
let x509 = X509Certificate::from_der(cert_der)?.1;
// Check if self-signed
if x509.issuer() == x509.subject() {
return Err("Certificate is self-signed".into());
}
// Check expiration
let not_before = x509.validity().not_before.timestamp();
let not_after = x509.validity().not_after.timestamp();
let now = SystemTime::UNIX_EPOCH
.elapsed()
.unwrap_or_default()
.as_secs() as i64;
if now < not_before || now > not_after {
Err("Certificate is expired or not yet valid".into())
} else {
Ok(())
}
}
*/

View File

@@ -15,7 +15,10 @@ pub mod llm;
pub mod undelete;
use ahash::{AHashMap, AHashSet};
use directory::{QueryParams, Type, backend::internal::lookup::DirectoryStore};
use directory::{
QueryParams, Type,
backend::internal::{lookup::DirectoryStore, manage::ManageDirectory},
};
use license::LicenseKey;
use llm::AiApiConfig;
use mail_parser::DateTime;
@@ -150,6 +153,30 @@ impl Server {
}
}
pub async fn can_create_account(&self) -> trc::Result<bool> {
if let Some(enterprise) = &self.core.enterprise {
let total_accounts = self
.store()
.count_principals(None, Type::Individual.into(), None)
.await
.caused_by(trc::location!())?;
if total_accounts + 1 > enterprise.license.accounts as u64 {
trc::event!(
Server(trc::ServerEvent::Licensing),
Details = "Account creation not possible: license key account limit reached",
Domain = enterprise.license.domain.clone(),
Total = total_accounts,
Limit = enterprise.license.accounts,
);
return Ok(false);
}
}
Ok(true)
}
pub async fn logo_resource(&self, domain: &str) -> trc::Result<Option<Resource<Vec<u8>>>> {
const MAX_IMAGE_SIZE: usize = 1024 * 1024;

View File

@@ -103,6 +103,7 @@ impl PrincipalManager for Server {
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
{
if (matches!(principal.typ(), Type::Tenant)
|| principal.has_field(PrincipalField::Tenant))
&& !self.core.is_enterprise_edition()
@@ -110,6 +111,21 @@ impl PrincipalManager for Server {
return Err(manage::enterprise());
}
if matches!(principal.typ(), Type::Individual)
&& self.core.is_enterprise_edition()
&& !self.can_create_account().await?
{
return Err(manage::error(
"License account limit reached",
format!(
"Enterprise licensed account limit reached: {} accounts licensed.",
self.licensed_accounts()
)
.into(),
));
}
}
// SPDX-SnippetEnd
// Make sure the current directory supports updates
@@ -257,6 +273,7 @@ impl PrincipalManager for Server {
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
{
if self.core.is_enterprise_edition() {
if tenant.is_none() {
// Limit search to current tenant
@@ -274,6 +291,7 @@ impl PrincipalManager for Server {
} else if types.contains(&Type::Tenant) {
return Err(manage::enterprise());
}
}
// SPDX-SnippetEnd
let principals = self
@@ -348,6 +366,7 @@ impl PrincipalManager for Server {
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
{
if self.core.is_enterprise_edition() {
if tenant.is_none() {
// Limit search to current tenant
@@ -365,6 +384,7 @@ impl PrincipalManager for Server {
} else if typ == Type::Tenant {
return Err(manage::enterprise());
}
}
// SPDX-SnippetEnd
let principals = self
@@ -429,9 +449,11 @@ impl PrincipalManager for Server {
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
{
if matches!(typ, Type::Tenant) && !self.core.is_enterprise_edition() {
return Err(manage::enterprise());
}
}
// SPDX-SnippetEnd