use super::{AuthError, AuthFuture};
use crate::access_container;
use crate::client::AuthClient;
use crate::config::{self, AppInfo, RevocationQueue};
use futures::future::{self, Either, Loop};
use futures::Future;
use safe_core::recovery;
use safe_core::{client::AuthActions, Client, CoreError, FutureExt, MDataInfo};
use safe_nd::{Error as SndError, PublicKey};
use std::collections::HashMap;
type Containers = HashMap<String, MDataInfo>;
pub fn revoke_app(client: &AuthClient, app_id: &str) -> Box<AuthFuture<()>> {
let app_id = app_id.to_string();
let client = client.clone();
let c2 = client.clone();
config::get_app_revocation_queue(&client)
.and_then(move |(version, queue)| {
config::push_to_app_revocation_queue(
&client,
queue,
config::next_version(version),
&app_id,
)
})
.and_then(move |(version, queue)| flush_app_revocation_queue_impl(&c2, queue, version + 1))
.into_box()
}
pub fn flush_app_revocation_queue(client: &AuthClient) -> Box<AuthFuture<()>> {
let client = client.clone();
config::get_app_revocation_queue(&client)
.and_then(move |(version, queue)| {
if let Some(version) = version {
flush_app_revocation_queue_impl(&client, queue, version + 1)
} else {
future::ok(()).into_box()
}
})
.into_box()
}
fn flush_app_revocation_queue_impl(
client: &AuthClient,
queue: RevocationQueue,
version: u64,
) -> Box<AuthFuture<()>> {
let client = client.clone();
let moved_apps = Vec::new();
future::loop_fn(
(queue, version, moved_apps),
move |(queue, version, mut moved_apps)| {
let c2 = client.clone();
let c3 = client.clone();
if let Some(app_id) = queue.front().cloned() {
let f = revoke_single_app(&c2, &app_id)
.then(move |result| match result {
Ok(_) => {
config::remove_from_app_revocation_queue(&c3, queue, version, &app_id)
.map(|(version, queue)| (version, queue, moved_apps))
.into_box()
}
Err(AuthError::CoreError(CoreError::SymmetricDecipherFailure)) => {
config::remove_from_app_revocation_queue(&c3, queue, version, &app_id)
.and_then(|_| {
err!(AuthError::CoreError(CoreError::SymmetricDecipherFailure))
})
.into_box()
}
Err(error) => {
if moved_apps.contains(&app_id) {
err!(error)
} else {
moved_apps.push(app_id.clone());
config::repush_to_app_revocation_queue(&c3, queue, version, &app_id)
.map(|(version, queue)| (version, queue, moved_apps))
.into_box()
}
}
})
.and_then(move |(version, queue, moved_apps)| {
Ok(Loop::Continue((queue, version + 1, moved_apps)))
});
Either::A(f)
} else {
Either::B(future::ok(Loop::Break(())))
}
},
)
.into_box()
}
fn revoke_single_app(client: &AuthClient, app_id: &str) -> Box<AuthFuture<()>> {
trace!("Revoking app with ID {}...", app_id);
let c2 = client.clone();
let c3 = client.clone();
let c4 = client.clone();
config::get_app(client, app_id)
.and_then(move |app| {
delete_app_auth_key(&c2, PublicKey::from(app.keys.bls_pk)).map(move |_| app)
})
.and_then(move |app| {
access_container::fetch_entry(&c3, &app.info.id, app.keys.clone()).and_then(
move |(version, ac_entry)| {
match ac_entry {
Some(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)
}
None => ok!(()),
}
},
)
})
.into_box()
}
fn delete_app_auth_key(client: &AuthClient, key: PublicKey) -> Box<AuthFuture<()>> {
let client = client.clone();
client
.list_auth_keys_and_version()
.and_then(move |(listed_keys, version)| {
if listed_keys.contains_key(&key) {
client.del_auth_key(key, version + 1)
} else {
ok!(())
}
})
.or_else(|error| match error {
CoreError::DataError(SndError::NoSuchKey) => Ok(()),
error => Err(AuthError::from(error)),
})
.into_box()
}
fn clear_from_access_container_entry(
client: &AuthClient,
app: AppInfo,
ac_entry_version: u64,
containers: Containers,
) -> Box<AuthFuture<()>> {
let c2 = client.clone();
revoke_container_perms(client, &containers, PublicKey::from(app.keys.bls_pk))
.map(move |_| (app, ac_entry_version))
.and_then(move |(app, version)| {
access_container::delete_entry(&c2, &app.info.id, &app.keys, version + 1)
})
.into_box()
}
fn revoke_container_perms(
client: &AuthClient,
containers: &Containers,
pk: PublicKey,
) -> Box<AuthFuture<()>> {
let reqs: Vec<_> = containers
.values()
.map(|mdata_info| {
let mdata_info = mdata_info.clone();
let c2 = client.clone();
client
.clone()
.get_mdata_version(*mdata_info.address())
.and_then(move |version| {
recovery::del_mdata_user_permissions(
&c2,
*mdata_info.address(),
pk,
version + 1,
)
})
.map_err(From::from)
})
.collect();
future::join_all(reqs).map(move |_| ()).into_box()
}