Import upstream v0.16.22, stripped
Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::task_manager::TaskResult;
|
||||
use common::Server;
|
||||
use email::{message::metadata::MessageMetadata, sieve::SieveScript};
|
||||
use groupware::file::FileNode;
|
||||
use registry::{
|
||||
schema::{
|
||||
prelude::{ObjectType, Property},
|
||||
structs::{ArchivedItem, TaskDestroyAccount},
|
||||
},
|
||||
types::EnumImpl,
|
||||
};
|
||||
use store::{
|
||||
SerializeInfallible, ValueKey,
|
||||
registry::RegistryQuery,
|
||||
search::SearchQuery,
|
||||
write::{BatchBuilder, BlobLink, BlobOp, RegistryClass, SearchIndex, ValueClass},
|
||||
};
|
||||
use trc::AddContext;
|
||||
use types::{
|
||||
blob_hash::BlobHash,
|
||||
collection::Collection,
|
||||
field::{EmailField, Field},
|
||||
id::Id,
|
||||
};
|
||||
|
||||
pub(crate) trait DestroyAccountTask: Sync + Send {
|
||||
fn destroy_account(&self, task: &TaskDestroyAccount)
|
||||
-> impl Future<Output = TaskResult> + Send;
|
||||
}
|
||||
|
||||
impl DestroyAccountTask for Server {
|
||||
async fn destroy_account(&self, task: &TaskDestroyAccount) -> TaskResult {
|
||||
match destroy_account(self, task).await {
|
||||
Ok(result) => result,
|
||||
Err(err) => {
|
||||
let result = TaskResult::temporary(err.to_string());
|
||||
trc::error!(
|
||||
err.account_id(task.account_id.document_id())
|
||||
.details("Failed to destroy account")
|
||||
);
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn destroy_account(server: &Server, task: &TaskDestroyAccount) -> trc::Result<TaskResult> {
|
||||
let account_id = task.account_id.document_id();
|
||||
|
||||
// Destroy public keys and masked emails
|
||||
for object in [ObjectType::PublicKey, ObjectType::MaskedEmail] {
|
||||
let mut batch = BatchBuilder::new();
|
||||
let ids = server
|
||||
.registry()
|
||||
.query::<Vec<Id>>(RegistryQuery::new(object).with_account(account_id))
|
||||
.await?;
|
||||
let object_id = object.to_id();
|
||||
|
||||
for id in ids {
|
||||
batch
|
||||
.clear(ValueClass::Registry(RegistryClass::Item {
|
||||
object_id,
|
||||
item_id: id.id(),
|
||||
}))
|
||||
.clear(ValueClass::Registry(RegistryClass::IndexId {
|
||||
object_id,
|
||||
item_id: id.id(),
|
||||
}))
|
||||
.clear(ValueClass::Registry(RegistryClass::Index {
|
||||
index_id: Property::AccountId as u16,
|
||||
object_id,
|
||||
item_id: id.id(),
|
||||
key: (account_id as u64).serialize(),
|
||||
}))
|
||||
.clear(ValueClass::Registry(RegistryClass::Reference {
|
||||
to_object_id: ObjectType::Account as u16,
|
||||
to_item_id: account_id as u64,
|
||||
from_object_id: object_id,
|
||||
from_item_id: id.id(),
|
||||
}));
|
||||
}
|
||||
|
||||
if !batch.is_empty() {
|
||||
server.store().write(batch.build_all()).await?;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove archived items
|
||||
let mut batch = BatchBuilder::new();
|
||||
let ids = server
|
||||
.registry()
|
||||
.query::<Vec<Id>>(RegistryQuery::new(ObjectType::ArchivedItem).with_account(account_id))
|
||||
.await?;
|
||||
for id in ids {
|
||||
let object_id = ObjectType::ArchivedItem.to_id();
|
||||
let item_id = id.id();
|
||||
|
||||
if let Some(item) = server
|
||||
.store()
|
||||
.get_value::<ArchivedItem>(ValueKey::from(ValueClass::Registry(RegistryClass::Item {
|
||||
object_id,
|
||||
item_id,
|
||||
})))
|
||||
.await?
|
||||
{
|
||||
let until = item.archived_until().timestamp() as u64;
|
||||
let blob_hash = item.into_blob_id().hash;
|
||||
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.clear(BlobOp::Link {
|
||||
hash: blob_hash,
|
||||
to: BlobLink::Temporary { until },
|
||||
})
|
||||
.clear(ValueClass::Registry(RegistryClass::Index {
|
||||
index_id: Property::AccountId.to_id(),
|
||||
object_id,
|
||||
item_id,
|
||||
key: (account_id as u64).serialize(),
|
||||
}))
|
||||
.clear(ValueClass::Registry(RegistryClass::Item {
|
||||
object_id,
|
||||
item_id,
|
||||
}));
|
||||
}
|
||||
}
|
||||
if !batch.is_empty() {
|
||||
server.store().write(batch.build_all()).await?;
|
||||
}
|
||||
|
||||
// Remove search index
|
||||
for index in [
|
||||
SearchIndex::Email,
|
||||
SearchIndex::Contacts,
|
||||
SearchIndex::Calendar,
|
||||
] {
|
||||
server
|
||||
.search_store()
|
||||
.unindex(SearchQuery::new(index).with_account_id(account_id))
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Unlink all accounts's blobs
|
||||
destroy_account_blobs(server, account_id).await?;
|
||||
|
||||
// Destroy account data
|
||||
server
|
||||
.store()
|
||||
.danger_destroy_account(account_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
Ok(TaskResult::Success(vec![]))
|
||||
}
|
||||
|
||||
pub async fn destroy_account_blobs(server: &Server, account_id: u32) -> trc::Result<()> {
|
||||
let mut delete_keys = Vec::new();
|
||||
for (collection, field) in [
|
||||
(Collection::Email, u8::from(EmailField::Metadata)),
|
||||
(Collection::FileNode, u8::from(Field::ARCHIVE)),
|
||||
(Collection::SieveScript, u8::from(Field::ARCHIVE)),
|
||||
] {
|
||||
server
|
||||
.all_archives(account_id, collection, field, |document_id, archive| {
|
||||
match collection {
|
||||
Collection::Email => {
|
||||
let message = archive.unarchive::<MessageMetadata>()?;
|
||||
delete_keys.push((
|
||||
collection,
|
||||
document_id,
|
||||
BlobHash::from(&message.blob_hash),
|
||||
));
|
||||
}
|
||||
Collection::FileNode => {
|
||||
if let Some(file) = archive.unarchive::<FileNode>()?.file.as_ref() {
|
||||
delete_keys.push((
|
||||
collection,
|
||||
document_id,
|
||||
BlobHash::from(&file.blob_hash),
|
||||
));
|
||||
}
|
||||
}
|
||||
Collection::SieveScript => {
|
||||
let sieve = archive.unarchive::<SieveScript>()?;
|
||||
delete_keys.push((
|
||||
collection,
|
||||
document_id,
|
||||
BlobHash::from(&sieve.blob_hash),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
}
|
||||
|
||||
let mut batch = BatchBuilder::new();
|
||||
batch.with_account_id(account_id);
|
||||
|
||||
for (collection, document_id, hash) in delete_keys {
|
||||
if batch.is_large_batch() {
|
||||
server
|
||||
.store()
|
||||
.write(batch.build_all())
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
batch = BatchBuilder::new();
|
||||
batch.with_account_id(account_id);
|
||||
}
|
||||
batch
|
||||
.with_collection(collection)
|
||||
.with_document(document_id)
|
||||
.clear(ValueClass::Blob(BlobOp::Link {
|
||||
hash,
|
||||
to: BlobLink::Document,
|
||||
}));
|
||||
}
|
||||
|
||||
if !batch.is_empty() {
|
||||
server
|
||||
.store()
|
||||
.write(batch.build_all())
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user