use super::AuthError;
use crate::access_container;
use crate::client::AuthClient;
use crate::config::{self, AppInfo, RevocationQueue};
use log::trace;
use safe_core::recoverable_apis;
use safe_core::{client::AuthActions, Client, CoreError, MDataInfo};
use safe_nd::{Error as SndError, PublicKey};
use std::collections::HashMap;
type Containers = HashMap<String, MDataInfo>;
pub async fn revoke_app(client: &AuthClient, app_id: &str) -> Result<(), AuthError> {
let app_id = app_id.to_string();
let client = client.clone();
let c2 = client.clone();
let (version, queue) = config::get_app_revocation_queue(&client).await?;
let (version, queue) = config::push_to_app_revocation_queue(
&client,
queue,
config::next_version(version),
&app_id,
)
.await?;
flush_app_revocation_queue_impl(&c2, queue, version + 1).await
}
pub async fn flush_app_revocation_queue(client: &AuthClient) -> Result<(), AuthError> {
let client = client.clone();
let (version, queue) = config::get_app_revocation_queue(&client).await?;
if let Some(version) = version {
flush_app_revocation_queue_impl(&client, queue, version + 1).await
} else {
Ok(())
}
}
async fn flush_app_revocation_queue_impl(
client: &AuthClient,
queue: RevocationQueue,
version: u64,
) -> Result<(), AuthError> {
let client = client.clone();
let mut moved_apps = Vec::new();
let mut queue_to_try = queue;
let mut version_to_try = version;
let mut done_trying = false;
let mut response: Result<(), AuthError> = Ok(());
while !done_trying {
let c2 = client.clone();
let c3 = client.clone();
let fron_of_queue = queue_to_try.front().cloned();
if let Some(app_id) = fron_of_queue {
match revoke_single_app(&c2, &app_id).await {
Ok(_) => {
let (version, queue) = config::remove_from_app_revocation_queue(
&c3,
queue_to_try.clone(),
version_to_try,
&app_id,
)
.await?;
version_to_try = version + 1;
queue_to_try = queue;
}
Err(AuthError::CoreError(CoreError::SymmetricDecipherFailure)) => {
let _ = config::remove_from_app_revocation_queue(
&c3,
queue_to_try.clone(),
version_to_try,
&app_id,
)
.await?;
done_trying = true;
response = Err(AuthError::CoreError(CoreError::SymmetricDecipherFailure));
}
Err(error) => {
if moved_apps.contains(&app_id) {
done_trying = true;
response = Err(error);
} else {
moved_apps.push(app_id.clone());
let (version, queue) = config::repush_to_app_revocation_queue(
&c3,
queue_to_try,
version_to_try,
&app_id,
)
.await?;
version_to_try = version + 1;
queue_to_try = queue;
}
}
}
} else {
done_trying = true;
response = Ok(())
}
}
response
}
async fn revoke_single_app(client: &AuthClient, app_id: &str) -> Result<(), AuthError> {
trace!("Revoking app with ID {}...", app_id);
let c2 = client.clone();
let c3 = client.clone();
let c4 = client.clone();
let app = config::get_app(client, app_id).await?;
delete_app_auth_key(&c2, app.keys.public_key()).await?;
let (version, ac_entry) =
access_container::fetch_entry(c3, app.info.id.clone(), app.keys.clone()).await?;
if let Some(ac_entry) = ac_entry {
let containers: Containers = ac_entry
.into_iter()
.map(|(name, (mdata_info, _))| (name, mdata_info))
.collect();
clear_from_access_container_entry(&c4, app, version, containers).await
} else {
Ok(())
}
}
async fn delete_app_auth_key(client: &AuthClient, key: PublicKey) -> Result<(), AuthError> {
let client = client.clone();
match client.list_auth_keys_and_version().await {
Ok((listed_keys, version)) => {
if listed_keys.contains_key(&key) {
client
.del_auth_key(key, version + 1)
.await
.map_err(AuthError::from)
} else {
Ok(())
}
}
Err(error) => match error {
CoreError::DataError(SndError::NoSuchKey) => Ok(()),
error => Err(AuthError::from(error)),
},
}
}
async fn clear_from_access_container_entry(
client: &AuthClient,
app: AppInfo,
ac_entry_version: u64,
containers: Containers,
) -> Result<(), AuthError> {
let c2 = client.clone();
revoke_container_perms(client, &containers, app.keys.public_key()).await?;
access_container::delete_entry(&c2, &app.info.id, &app.keys, ac_entry_version + 1).await
}
async fn revoke_container_perms(
client: &AuthClient,
containers: &Containers,
pk: PublicKey,
) -> Result<(), AuthError> {
for mdata_info in containers.values() {
let mdata_info = mdata_info.clone();
let c2 = client.clone();
let version = client
.clone()
.get_mdata_version(*mdata_info.address())
.await?;
recoverable_apis::del_mdata_user_permissions(c2, *mdata_info.address(), pk, version + 1)
.await?;
}
Ok(())
}