Include OAuth public endpoint in PACC responses
This commit is contained in:
3112
api/v1/openapi.yml
3112
api/v1/openapi.yml
File diff suppressed because it is too large
Load Diff
5
crates/common/src/cache/invalidate.rs
vendored
5
crates/common/src/cache/invalidate.rs
vendored
@@ -238,6 +238,10 @@ impl Server {
|
||||
linked_object.id().document_id(),
|
||||
));
|
||||
}
|
||||
// SPDX-SnippetBegin
|
||||
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
// SPDX-License-Identifier: LicenseRef-SEL
|
||||
#[cfg(feature = "enterprise")]
|
||||
ObjectType::Tenant => {
|
||||
// Invalidate all accounts of the tenant
|
||||
let tenant_id = linked_object.id().document_id();
|
||||
@@ -253,6 +257,7 @@ impl Server {
|
||||
changes.insert(CacheInvalidation::AccessToken(account_id));
|
||||
}
|
||||
}
|
||||
// SPDX-SnippetEnd
|
||||
ObjectType::Role => {
|
||||
role_ids.push(linked_object.id().document_id());
|
||||
}
|
||||
|
||||
@@ -43,11 +43,17 @@ pub struct Network {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NetworkInfo {
|
||||
pub pacc: String,
|
||||
pub pacc: Pacc,
|
||||
pub mxs: Vec<MailExchanger>,
|
||||
pub services: VecMap<ServiceProtocol, Service>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Pacc {
|
||||
pub prefix: String,
|
||||
pub suffix: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Http {
|
||||
pub rate_authenticated: Option<Rate>,
|
||||
@@ -172,10 +178,13 @@ impl Network {
|
||||
has_acme_tls_challenge = true;
|
||||
}
|
||||
|
||||
const SPLIT_HERE: &str = "$$__SPLIT_HERE__$$";
|
||||
let mut pacc = Configuration {
|
||||
protocols: Protocols::default(),
|
||||
authentication: Some(Authentication {
|
||||
oauth_public: None,
|
||||
oauth_public: Some(OAuthPublic {
|
||||
issuer: SPLIT_HERE.to_string(),
|
||||
}),
|
||||
password: true,
|
||||
}),
|
||||
info: Info {
|
||||
@@ -307,6 +316,11 @@ impl Network {
|
||||
}
|
||||
}
|
||||
|
||||
let (prefix, suffix) = serde_json::to_string(&pacc)
|
||||
.unwrap_or_default()
|
||||
.rsplit_once(SPLIT_HERE)
|
||||
.map(|(prefix, suffix)| (prefix.to_string(), suffix.to_string()))
|
||||
.unwrap();
|
||||
let mut network = Network {
|
||||
node_id: bp.node_id() as u64,
|
||||
server_name: system.default_hostname,
|
||||
@@ -321,7 +335,7 @@ impl Network {
|
||||
info: NetworkInfo {
|
||||
mxs: system.mail_exchangers.into_iter().collect(),
|
||||
services: system.services,
|
||||
pacc: serde_json::to_string(&pacc).unwrap_or_default(),
|
||||
pacc: Pacc { prefix, suffix },
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ use registry::schema::{
|
||||
use reqwest::Url;
|
||||
use sha2::{Digest, Sha256};
|
||||
use store::registry::RegistryQuery;
|
||||
use trc::AddContext;
|
||||
use types::id::Id;
|
||||
use x509_parser::parse_x509_certificate;
|
||||
|
||||
@@ -132,7 +133,7 @@ impl Server {
|
||||
}
|
||||
}
|
||||
DnsRecordType::AutoConfig => {
|
||||
let pacc_digest = Sha256::digest(&network.info.pacc);
|
||||
let pacc_digest = Sha256::digest(&self.get_pacc_for_fomain(domain_name).await?);
|
||||
let pacc_digest_encoded = general_purpose::STANDARD.encode(pacc_digest);
|
||||
|
||||
records.push(NamedDnsRecord {
|
||||
@@ -377,6 +378,21 @@ impl Server {
|
||||
.await
|
||||
.map(|records| BindSerializer::serialize(&records))
|
||||
}
|
||||
|
||||
pub async fn get_pacc_for_fomain(&self, domain_name: &str) -> trc::Result<String> {
|
||||
self.get_directory_for_domain(domain_name)
|
||||
.await
|
||||
.caused_by(trc::location!())
|
||||
.map(|directory| {
|
||||
directory
|
||||
.and_then(|directory| {
|
||||
directory
|
||||
.oidc_discovery_document()
|
||||
.map(|doc| doc.url.to_string())
|
||||
})
|
||||
.unwrap_or_else(|| self.core.network.http.url_https.clone())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
||||
@@ -102,6 +102,7 @@ impl DnsUpdater {
|
||||
}
|
||||
};
|
||||
|
||||
#[allow(deprecated)]
|
||||
Ok(DnsUpdater {
|
||||
polling_interval: server.polling_interval.into_inner(),
|
||||
propagation_timeout: server.propagation_timeout.into_inner(),
|
||||
@@ -266,13 +267,8 @@ impl DnsUpdater {
|
||||
.map_err(|err| format!("Failed to build DNS updater: {}", err))?,
|
||||
}),
|
||||
DnsServer::Route53(server) => {
|
||||
let secret_access_key =
|
||||
server.secret_access_key.secret().await?.into_owned();
|
||||
let session_token = server
|
||||
.session_token
|
||||
.secret()
|
||||
.await?
|
||||
.map(|c| c.into_owned());
|
||||
let secret_access_key = server.secret_access_key.secret().await?.into_owned();
|
||||
let session_token = server.session_token.secret().await?.map(|c| c.into_owned());
|
||||
let config = dns_update::providers::route53::Route53Config {
|
||||
access_key_id: server.access_key_id,
|
||||
secret_access_key,
|
||||
@@ -292,8 +288,7 @@ impl DnsUpdater {
|
||||
})
|
||||
}
|
||||
DnsServer::GoogleCloudDns(server) => {
|
||||
let service_account_json =
|
||||
server.service_account_json.secret().await?.into_owned();
|
||||
let service_account_json = server.service_account_json.secret().await?.into_owned();
|
||||
let config = dns_update::providers::google_cloud_dns::GoogleCloudDnsConfig {
|
||||
service_account_json,
|
||||
project_id: server.project_id,
|
||||
|
||||
@@ -30,7 +30,10 @@ impl Server {
|
||||
.await
|
||||
.add_context(|err| err.caused_by(trc::location!()).account_id(account_id))
|
||||
}
|
||||
|
||||
// SPDX-SnippetBegin
|
||||
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
// SPDX-License-Identifier: LicenseRef-SEL
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn get_used_quota_tenant(&self, tenant_id: u32) -> trc::Result<i64> {
|
||||
self.core
|
||||
.storage
|
||||
@@ -39,6 +42,12 @@ impl Server {
|
||||
.await
|
||||
.add_context(|err| err.caused_by(trc::location!()))
|
||||
}
|
||||
// SPDX-SnippetEnd
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
pub async fn get_used_quota_tenant(&self, _tenant_id: u32) -> trc::Result<i64> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
pub async fn has_available_quota(
|
||||
&self,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use crate::Directory;
|
||||
use crate::backend::oidc::lookup::fetch_jwks_keys;
|
||||
use crate::backend::oidc::{
|
||||
DiscoveryDocument, JwksCache, OidcError, OpenIdConfig, OpenIdDirectory,
|
||||
DiscoveryDocument, JwksCache, OidcConfig, OidcDiscovery, OidcError, OpenIdDirectory,
|
||||
};
|
||||
use registry::schema::structs;
|
||||
use reqwest::Client;
|
||||
@@ -17,7 +17,7 @@ use trc::AuthEvent;
|
||||
|
||||
impl OpenIdDirectory {
|
||||
pub async fn open(config: structs::OidcDirectory) -> Result<Directory, String> {
|
||||
Self::new(OpenIdConfig {
|
||||
Self::new(OidcConfig {
|
||||
issue_url: config.issuer_url,
|
||||
require_aud: config.require_audience,
|
||||
require_scopes: config.require_scopes.into_inner(),
|
||||
@@ -31,7 +31,7 @@ impl OpenIdDirectory {
|
||||
.map_err(|err| err.to_string())
|
||||
}
|
||||
|
||||
pub async fn new(config: OpenIdConfig) -> Result<Self, OidcError> {
|
||||
pub async fn new(config: OidcConfig) -> Result<Self, OidcError> {
|
||||
let http = Client::builder()
|
||||
.user_agent("Stalwart/1.0")
|
||||
.timeout(Duration::from_secs(30))
|
||||
@@ -133,8 +133,11 @@ impl OpenIdDirectory {
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
discovery: OidcDiscovery {
|
||||
url: config.issue_url.clone(),
|
||||
document: discovery,
|
||||
},
|
||||
config,
|
||||
discovery,
|
||||
http,
|
||||
cache,
|
||||
})
|
||||
|
||||
@@ -63,7 +63,7 @@ impl OpenIdDirectory {
|
||||
validation.validate_aud = false;
|
||||
}
|
||||
|
||||
validation.set_issuer(&[&self.discovery.issuer]);
|
||||
validation.set_issuer(&[&self.discovery.document.issuer]);
|
||||
validation.leeway = 60;
|
||||
|
||||
match decode::<serde_json::Value>(token, dk, &validation) {
|
||||
@@ -115,7 +115,7 @@ impl OpenIdDirectory {
|
||||
}
|
||||
}
|
||||
|
||||
let new_keys = fetch_jwks_keys(&self.http, &self.discovery.jwks_uri).await?;
|
||||
let new_keys = fetch_jwks_keys(&self.http, &self.discovery.document.jwks_uri).await?;
|
||||
{
|
||||
let mut guard = self.cache.write().await;
|
||||
guard.keys = new_keys;
|
||||
@@ -146,7 +146,7 @@ impl OpenIdDirectory {
|
||||
async fn fetch_userinfo(&self, token: &str) -> Result<serde_json::Value, OidcError> {
|
||||
let resp = self
|
||||
.http
|
||||
.get(&self.discovery.userinfo_endpoint)
|
||||
.get(&self.discovery.document.userinfo_endpoint)
|
||||
.bearer_auth(token)
|
||||
.send()
|
||||
.await
|
||||
|
||||
@@ -14,7 +14,7 @@ use utils::Client;
|
||||
pub mod config;
|
||||
pub mod lookup;
|
||||
|
||||
pub struct OpenIdConfig {
|
||||
pub struct OidcConfig {
|
||||
pub issue_url: String,
|
||||
pub require_aud: Option<String>,
|
||||
pub require_scopes: Vec<String>,
|
||||
@@ -24,6 +24,11 @@ pub struct OpenIdConfig {
|
||||
pub default_domain: Option<String>,
|
||||
}
|
||||
|
||||
pub struct OidcDiscovery {
|
||||
pub url: String,
|
||||
pub document: DiscoveryDocument,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct DiscoveryDocument {
|
||||
pub issuer: String,
|
||||
@@ -52,8 +57,8 @@ struct JwksCache {
|
||||
}
|
||||
|
||||
pub struct OpenIdDirectory {
|
||||
config: OpenIdConfig,
|
||||
pub discovery: DiscoveryDocument,
|
||||
config: OidcConfig,
|
||||
pub discovery: OidcDiscovery,
|
||||
http: Client,
|
||||
cache: RwLock<JwksCache>,
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{Account, Credentials, Directory, Recipient, backend::oidc::DiscoveryDocument};
|
||||
use crate::{Account, Credentials, Directory, Recipient, backend::oidc::OidcDiscovery};
|
||||
use trc::AddContext;
|
||||
|
||||
impl Directory {
|
||||
@@ -34,7 +34,7 @@ impl Directory {
|
||||
!matches!(self, Directory::OpenId(_))
|
||||
}
|
||||
|
||||
pub fn oidc_discovery_document(&self) -> Option<&DiscoveryDocument> {
|
||||
pub fn oidc_discovery_document(&self) -> Option<&OidcDiscovery> {
|
||||
match &self {
|
||||
Directory::OpenId(directory) => Some(&directory.discovery),
|
||||
_ => None,
|
||||
|
||||
@@ -28,6 +28,7 @@ pub enum Credentials {
|
||||
},
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum Directory {
|
||||
Ldap(LdapDirectory),
|
||||
Sql(SqlDirectory),
|
||||
|
||||
@@ -133,7 +133,9 @@ impl OAuthApiHandler for Server {
|
||||
.await?
|
||||
.and_then(|directory| directory.oidc_discovery_document())
|
||||
{
|
||||
Ok(JsonResponse::new(endpoint).no_cache().into_http_response())
|
||||
Ok(JsonResponse::new(&endpoint.document)
|
||||
.no_cache()
|
||||
.into_http_response())
|
||||
} else {
|
||||
self.handle_oidc_metadata(req, session).await
|
||||
}
|
||||
|
||||
@@ -311,7 +311,15 @@ impl ParseHttp for Server {
|
||||
.await?;
|
||||
return Ok(Resource::new(
|
||||
"application/json",
|
||||
self.core.network.info.pacc.clone().into_bytes(),
|
||||
self.get_pacc_for_fomain(
|
||||
req.headers()
|
||||
.get(header::HOST)
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.map(|h| h.rsplit_once(':').map_or(h, |(h, _)| h))
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.await?
|
||||
.into_bytes(),
|
||||
)
|
||||
.into_http_response());
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ pub mod principal;
|
||||
pub mod public_key;
|
||||
pub mod queued_message;
|
||||
pub mod report;
|
||||
pub mod sieve;
|
||||
pub mod spam_sample;
|
||||
pub mod task;
|
||||
pub mod tls;
|
||||
|
||||
@@ -350,6 +350,10 @@ pub(crate) async fn validate_role(
|
||||
}
|
||||
}
|
||||
|
||||
// SPDX-SnippetBegin
|
||||
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
// SPDX-License-Identifier: LicenseRef-SEL
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub(crate) async fn validate_tenant_quota(
|
||||
set: &RegistrySetResponse<'_>,
|
||||
quota: TenantStorageQuota,
|
||||
@@ -411,6 +415,15 @@ pub(crate) async fn validate_tenant_quota(
|
||||
|
||||
Ok(Ok(ObjectResponse::default()))
|
||||
}
|
||||
// SPDX-SnippetEnd
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
pub(crate) async fn validate_tenant_quota(
|
||||
_set: &RegistrySetResponse<'_>,
|
||||
_quota: TenantStorageQuota,
|
||||
) -> ValidationResult {
|
||||
ValidationResult::Ok(Ok(ObjectResponse::default()))
|
||||
}
|
||||
|
||||
pub(crate) async fn schedule_account_destruction(
|
||||
server: &Server,
|
||||
|
||||
@@ -537,6 +537,10 @@ pub(crate) async fn queued_message_query(
|
||||
}
|
||||
}
|
||||
|
||||
// SPDX-SnippetBegin
|
||||
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
// SPDX-License-Identifier: LicenseRef-SEL
|
||||
#[cfg(feature = "enterprise")]
|
||||
async fn tenant_domains(server: &Server, tenant_id: u32) -> trc::Result<AHashSet<String>> {
|
||||
let domain_ids = server
|
||||
.registry()
|
||||
@@ -553,6 +557,12 @@ async fn tenant_domains(server: &Server, tenant_id: u32) -> trc::Result<AHashSet
|
||||
|
||||
Ok(domains)
|
||||
}
|
||||
// SPDX-SnippetEnd
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
async fn tenant_domains(_server: &Server, _tenant_id: u32) -> trc::Result<AHashSet<String>> {
|
||||
Ok(AHashSet::new())
|
||||
}
|
||||
|
||||
fn map_message(message_in: &ArchivedMessage) -> QueuedMessage {
|
||||
let mut message_out = QueuedMessage {
|
||||
|
||||
49
crates/jmap/src/registry/mapping/sieve.rs
Normal file
49
crates/jmap/src/registry/mapping/sieve.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::registry::mapping::{ObjectResponse, ValidationResult};
|
||||
use common::Server;
|
||||
use jmap_proto::error::set::SetError;
|
||||
use registry::schema::prelude::Property;
|
||||
|
||||
pub(crate) async fn validate_sieve_script(
|
||||
server: &Server,
|
||||
script: &str,
|
||||
old_script: Option<&str>,
|
||||
is_system_script: bool,
|
||||
) -> ValidationResult {
|
||||
if old_script.is_none_or(|old_script| old_script != script) {
|
||||
if is_system_script {
|
||||
if let Err(err) = server
|
||||
.core
|
||||
.sieve
|
||||
.untrusted_compiler
|
||||
.compile(script.as_bytes())
|
||||
{
|
||||
return Ok(Err(SetError::invalid_properties()
|
||||
.with_property(Property::Contents)
|
||||
.with_description(format!(
|
||||
"Failed to compile system Sieve script: {err}"
|
||||
))));
|
||||
}
|
||||
} else {
|
||||
if let Err(err) = server
|
||||
.core
|
||||
.sieve
|
||||
.untrusted_compiler
|
||||
.compile(script.as_bytes())
|
||||
{
|
||||
return Ok(Err(SetError::invalid_properties()
|
||||
.with_property(Property::Contents)
|
||||
.with_description(format!(
|
||||
"Failed to compile user Sieve script: {err}"
|
||||
))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Ok(ObjectResponse::default()))
|
||||
}
|
||||
@@ -403,7 +403,7 @@ impl RegistryQueryFilters for QueryRequest<Registry> {
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap_or_else(|| Comparator::ascending(RegistryComparator::Property(Property::Id)));
|
||||
.unwrap_or_else(|| Comparator::descending(RegistryComparator::Property(Property::Id)));
|
||||
|
||||
match comparator.property {
|
||||
RegistryComparator::Property(property) => {
|
||||
|
||||
@@ -21,6 +21,7 @@ use crate::registry::{
|
||||
public_key::validate_public_key,
|
||||
queued_message::queued_message_set,
|
||||
report::report_set,
|
||||
sieve::validate_sieve_script,
|
||||
spam_sample::spam_sample_set,
|
||||
task::task_set,
|
||||
tls::{validate_acme_provider, validate_certificate},
|
||||
@@ -47,7 +48,10 @@ use registry::{
|
||||
OBJ_FILTER_ACCOUNT, OBJ_FILTER_TENANT, OBJ_SINGLETON, Object, ObjectInner, ObjectType,
|
||||
Property,
|
||||
},
|
||||
structs::{Certificate, DkimSignature, DnsServer, Domain, PublicKey, Role, Task},
|
||||
structs::{
|
||||
Certificate, DkimSignature, DnsServer, Domain, PublicKey, Role, SieveSystemScript,
|
||||
SieveUserScript, Task,
|
||||
},
|
||||
},
|
||||
types::id::ObjectId,
|
||||
};
|
||||
@@ -467,6 +471,24 @@ impl RegistrySet for Server {
|
||||
ObjectInner::Certificate(cert) => {
|
||||
validate_certificate(cert, modification.as_certificate()).await?
|
||||
}
|
||||
ObjectInner::SieveUserScript(SieveUserScript { contents, .. }) => {
|
||||
validate_sieve_script(
|
||||
set.server,
|
||||
contents,
|
||||
modification.as_sieve_script(),
|
||||
false,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
ObjectInner::SieveSystemScript(SieveSystemScript { contents, .. }) => {
|
||||
validate_sieve_script(
|
||||
set.server,
|
||||
contents,
|
||||
modification.as_sieve_script(),
|
||||
true,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
_ => Ok(ObjectResponse::default()),
|
||||
};
|
||||
|
||||
@@ -815,6 +837,19 @@ impl Modification {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn as_sieve_script(&self) -> Option<&str> {
|
||||
match self {
|
||||
Modification::Create { .. } => None,
|
||||
Modification::Update { object, .. } => match &object.inner {
|
||||
ObjectInner::SieveUserScript(SieveUserScript { contents, .. })
|
||||
| ObjectInner::SieveSystemScript(SieveSystemScript { contents, .. }) => {
|
||||
Some(contents.as_str())
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_write_error(err: RegistryWriteResult) -> SetError<Property> {
|
||||
|
||||
@@ -359,38 +359,45 @@ async fn store_maintenance(
|
||||
}
|
||||
}
|
||||
TaskStoreMaintenanceType::ResetTenantQuotas => {
|
||||
let mut batch = BatchBuilder::new();
|
||||
let now = now() as i64;
|
||||
|
||||
for tenant_id in server
|
||||
.registry()
|
||||
.query::<RoaringBitmap>(RegistryQuery::new(ObjectType::Tenant))
|
||||
.await?
|
||||
// SPDX-SnippetBegin
|
||||
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
// SPDX-License-Identifier: LicenseRef-SEL
|
||||
#[cfg(feature = "enterprise")]
|
||||
{
|
||||
#[cfg(feature = "test_mode")]
|
||||
let status = TaskStatus::at(now);
|
||||
let mut batch = BatchBuilder::new();
|
||||
let now = now() as i64;
|
||||
|
||||
#[cfg(not(feature = "test_mode"))]
|
||||
let status =
|
||||
TaskStatus::at(now + rand::Rng::random_range(&mut rand::rng(), 0..=300));
|
||||
for tenant_id in server
|
||||
.registry()
|
||||
.query::<RoaringBitmap>(RegistryQuery::new(ObjectType::Tenant))
|
||||
.await?
|
||||
{
|
||||
#[cfg(feature = "test_mode")]
|
||||
let status = TaskStatus::at(now);
|
||||
|
||||
batch.schedule_task(Task::TenantMaintenance(TaskTenantMaintenance {
|
||||
tenant_id: tenant_id.into(),
|
||||
maintenance_type: TaskTenantMaintenanceType::RecalculateQuota,
|
||||
status,
|
||||
}));
|
||||
#[cfg(not(feature = "test_mode"))]
|
||||
let status =
|
||||
TaskStatus::at(now + rand::Rng::random_range(&mut rand::rng(), 0..=300));
|
||||
|
||||
if batch.is_large_batch() {
|
||||
batch.schedule_task(Task::TenantMaintenance(TaskTenantMaintenance {
|
||||
tenant_id: tenant_id.into(),
|
||||
maintenance_type: TaskTenantMaintenanceType::RecalculateQuota,
|
||||
status,
|
||||
}));
|
||||
|
||||
if batch.is_large_batch() {
|
||||
server.core.storage.data.write(batch.build_all()).await?;
|
||||
server.notify_task_queue();
|
||||
batch = BatchBuilder::new();
|
||||
}
|
||||
}
|
||||
|
||||
if !batch.is_empty() {
|
||||
server.core.storage.data.write(batch.build_all()).await?;
|
||||
server.notify_task_queue();
|
||||
batch = BatchBuilder::new();
|
||||
}
|
||||
}
|
||||
|
||||
if !batch.is_empty() {
|
||||
server.core.storage.data.write(batch.build_all()).await?;
|
||||
server.notify_task_queue();
|
||||
}
|
||||
// SPDX-SnippetEnd
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,6 +496,10 @@ async fn recalculate_quota(server: &Server, account_id: u32) -> trc::Result<()>
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
// SPDX-SnippetBegin
|
||||
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
// SPDX-License-Identifier: LicenseRef-SEL
|
||||
#[cfg(feature = "enterprise")]
|
||||
async fn recalculate_tenant_quota(server: &Server, tenant_id: u32) -> trc::Result<()> {
|
||||
let mut quota = 0;
|
||||
for account_id in server
|
||||
@@ -515,6 +526,12 @@ async fn recalculate_tenant_quota(server: &Server, tenant_id: u32) -> trc::Resul
|
||||
.caused_by(trc::location!())
|
||||
.map(|_| ())
|
||||
}
|
||||
// SPDX-SnippetEnd
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
async fn recalculate_tenant_quota(_server: &Server, _tenant_id: u32) -> trc::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn reset_imap_uids(server: &Server, account_id: u32) -> trc::Result<(u32, u32)> {
|
||||
let mut mailbox_count = 0;
|
||||
|
||||
@@ -28,6 +28,8 @@ use std::{borrow::Cow, fmt::Display};
|
||||
use trc::AddContext;
|
||||
use types::id::Id;
|
||||
|
||||
const MAX_OBJECT_PAYLOAD_SIZE: usize = 200_000;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum RegistryWriteResult {
|
||||
Success(Id),
|
||||
@@ -307,6 +309,18 @@ impl RegistryStore {
|
||||
|
||||
// It's pickle time!
|
||||
let out = object.inner.to_pickled_vec();
|
||||
if out.len() > MAX_OBJECT_PAYLOAD_SIZE {
|
||||
return Ok(RegistryWriteResult::ValidationError {
|
||||
errors: vec![ValidationError::Invalid {
|
||||
property: Property::Id,
|
||||
value: format!(
|
||||
"Object size {} exceeds maximum of {}",
|
||||
out.len(),
|
||||
MAX_OBJECT_PAYLOAD_SIZE
|
||||
),
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
// Build batch
|
||||
if write_id {
|
||||
|
||||
@@ -49,7 +49,7 @@ pub async fn test() {
|
||||
|
||||
// Make sure the userinfo endpoint is not being used
|
||||
if let Directory::OpenId(directory) = &mut oidc {
|
||||
directory.discovery.userinfo_endpoint = "http://invalid".to_string();
|
||||
directory.discovery.document.userinfo_endpoint = "http://invalid".to_string();
|
||||
}
|
||||
|
||||
// JWT authentication should still work without the userinfo endpoint
|
||||
@@ -108,7 +108,7 @@ pub async fn test() {
|
||||
assert_eq!(
|
||||
oidc.oidc_discovery_document()
|
||||
.as_ref()
|
||||
.map(|oidc| oidc.authorization_endpoint.as_str()),
|
||||
.map(|oidc| oidc.document.authorization_endpoint.as_str()),
|
||||
Some("http://localhost:9080/realms/stalwart/protocol/openid-connect/auth")
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user