mirror of
https://github.com/stalwartlabs/stalwart.git
synced 2026-03-17 14:34:03 +00:00
Enterprise: remove tenant admin permissions when license is invalid
This commit is contained in:
@@ -97,32 +97,37 @@ 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;
|
||||
permissions.intersection(&self.get_role_permissions(tenant_id).await?.enabled);
|
||||
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
|
||||
tenant = Some(TenantInfo {
|
||||
id: tenant_id,
|
||||
quota: self
|
||||
.store()
|
||||
.query(QueryParams::id(tenant_id).with_return_member_of(false))
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or_else(|| {
|
||||
trc::SecurityEvent::Unauthorized
|
||||
.into_err()
|
||||
.details("Tenant not found")
|
||||
.id(tenant_id)
|
||||
.caused_by(trc::location!())
|
||||
})?
|
||||
.quota()
|
||||
.unwrap_or_default(),
|
||||
});
|
||||
// Obtain tenant quota
|
||||
tenant = Some(TenantInfo {
|
||||
id: tenant_id,
|
||||
quota: self
|
||||
.store()
|
||||
.query(QueryParams::id(tenant_id).with_return_member_of(false))
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or_else(|| {
|
||||
trc::SecurityEvent::Unauthorized
|
||||
.into_err()
|
||||
.details("Tenant not found")
|
||||
.id(tenant_id)
|
||||
.caused_by(trc::location!())
|
||||
})?
|
||||
.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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -103,11 +103,27 @@ 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()
|
||||
{
|
||||
return Err(manage::enterprise());
|
||||
if (matches!(principal.typ(), Type::Tenant)
|
||||
|| principal.has_field(PrincipalField::Tenant))
|
||||
&& !self.core.is_enterprise_edition()
|
||||
{
|
||||
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
|
||||
@@ -257,22 +273,24 @@ 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
|
||||
if let Some(tenant_name) = params.get("tenant") {
|
||||
tenant = self
|
||||
.core
|
||||
.storage
|
||||
.data
|
||||
.get_principal_info(tenant_name)
|
||||
.await?
|
||||
.filter(|p| p.typ == Type::Tenant)
|
||||
.map(|p| p.id);
|
||||
{
|
||||
if self.core.is_enterprise_edition() {
|
||||
if tenant.is_none() {
|
||||
// Limit search to current tenant
|
||||
if let Some(tenant_name) = params.get("tenant") {
|
||||
tenant = self
|
||||
.core
|
||||
.storage
|
||||
.data
|
||||
.get_principal_info(tenant_name)
|
||||
.await?
|
||||
.filter(|p| p.typ == Type::Tenant)
|
||||
.map(|p| p.id);
|
||||
}
|
||||
}
|
||||
} else if types.contains(&Type::Tenant) {
|
||||
return Err(manage::enterprise());
|
||||
}
|
||||
} else if types.contains(&Type::Tenant) {
|
||||
return Err(manage::enterprise());
|
||||
}
|
||||
// SPDX-SnippetEnd
|
||||
|
||||
@@ -348,22 +366,24 @@ 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
|
||||
if let Some(tenant_name) = params.get("tenant") {
|
||||
tenant = self
|
||||
.core
|
||||
.storage
|
||||
.data
|
||||
.get_principal_info(tenant_name)
|
||||
.await?
|
||||
.filter(|p| p.typ == Type::Tenant)
|
||||
.map(|p| p.id);
|
||||
{
|
||||
if self.core.is_enterprise_edition() {
|
||||
if tenant.is_none() {
|
||||
// Limit search to current tenant
|
||||
if let Some(tenant_name) = params.get("tenant") {
|
||||
tenant = self
|
||||
.core
|
||||
.storage
|
||||
.data
|
||||
.get_principal_info(tenant_name)
|
||||
.await?
|
||||
.filter(|p| p.typ == Type::Tenant)
|
||||
.map(|p| p.id);
|
||||
}
|
||||
}
|
||||
} else if typ == Type::Tenant {
|
||||
return Err(manage::enterprise());
|
||||
}
|
||||
} else if typ == Type::Tenant {
|
||||
return Err(manage::enterprise());
|
||||
}
|
||||
// SPDX-SnippetEnd
|
||||
|
||||
@@ -429,8 +449,10 @@ 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());
|
||||
{
|
||||
if matches!(typ, Type::Tenant) && !self.core.is_enterprise_edition() {
|
||||
return Err(manage::enterprise());
|
||||
}
|
||||
}
|
||||
|
||||
// SPDX-SnippetEnd
|
||||
|
||||
Reference in New Issue
Block a user