use super::{AuthError, AuthFuture};
use futures::Future;
use maidsafe_utilities::serialisation::{deserialise, serialise};
use routing::EntryActions;
use rust_sodium::crypto::secretbox;
use safe_core::{Client, FutureExt, MDataInfo, recovery};
use safe_core::ipc::AppKeys;
use safe_core::ipc::resp::{AccessContainerEntry, access_container_enc_key};
use safe_core::utils::{symmetric_decrypt, symmetric_encrypt};
use std::collections::HashMap;
pub const AUTHENTICATOR_ENTRY: &str = "authenticator";
pub fn enc_key(
access_container: &MDataInfo,
app_id: &str,
secret_key: &secretbox::Key,
) -> Result<Vec<u8>, AuthError> {
let nonce = access_container.nonce().ok_or_else(|| {
AuthError::from("No valid nonce for access container")
})?;
Ok(access_container_enc_key(app_id, secret_key, nonce)?)
}
pub fn decode_authenticator_entry(
encoded: &[u8],
enc_key: &secretbox::Key,
) -> Result<HashMap<String, MDataInfo>, AuthError> {
let plaintext = symmetric_decrypt(encoded, enc_key)?;
Ok(deserialise(&plaintext)?)
}
pub fn encode_authenticator_entry(
decoded: &HashMap<String, MDataInfo>,
enc_key: &secretbox::Key,
) -> Result<Vec<u8>, AuthError> {
let plaintext = serialise(decoded)?;
Ok(symmetric_encrypt(&plaintext, enc_key, None)?)
}
pub fn fetch_authenticator_entry<T: 'static>(
client: &Client<T>,
) -> Box<AuthFuture<(u64, HashMap<String, MDataInfo>)>> {
let c2 = client.clone();
let access_container = fry!(client.access_container());
let key = {
let sk = fry!(client.secret_symmetric_key());
fry!(enc_key(&access_container, AUTHENTICATOR_ENTRY, &sk))
};
client
.get_mdata_value(access_container.name, access_container.type_tag, key)
.map_err(From::from)
.and_then(move |value| {
let enc_key = c2.secret_symmetric_key()?;
decode_authenticator_entry(&value.content, &enc_key).map(
|decoded| (value.entry_version, decoded),
)
})
.into_box()
}
pub fn put_authenticator_entry<T: 'static>(
client: &Client<T>,
new_value: &HashMap<String, MDataInfo>,
version: u64,
) -> Box<AuthFuture<()>> {
let access_container = fry!(client.access_container());
let (key, ciphertext) = {
let sk = fry!(client.secret_symmetric_key());
let key = fry!(enc_key(&access_container, AUTHENTICATOR_ENTRY, &sk));
let ciphertext = fry!(encode_authenticator_entry(new_value, &sk));
(key, ciphertext)
};
let actions = if version == 0 {
EntryActions::new().ins(key, ciphertext, 0)
} else {
EntryActions::new().update(key, ciphertext, version)
};
recovery::mutate_mdata_entries(
client,
access_container.name,
access_container.type_tag,
actions.into(),
).map_err(From::from)
.into_box()
}
pub fn decode_app_entry(
encoded: &[u8],
enc_key: &secretbox::Key,
) -> Result<AccessContainerEntry, AuthError> {
let plaintext = symmetric_decrypt(encoded, enc_key)?;
Ok(deserialise(&plaintext)?)
}
pub fn encode_app_entry(
decoded: &AccessContainerEntry,
enc_key: &secretbox::Key,
) -> Result<Vec<u8>, AuthError> {
let plaintext = serialise(decoded)?;
Ok(symmetric_encrypt(&plaintext, enc_key, None)?)
}
pub fn fetch_entry<T>(
client: &Client<T>,
app_id: &str,
app_keys: AppKeys,
) -> Box<AuthFuture<(u64, Option<AccessContainerEntry>)>>
where
T: 'static,
{
let access_container = fry!(client.access_container());
let key = fry!(enc_key(&access_container, app_id, &app_keys.enc_key));
client
.get_mdata_value(access_container.name, access_container.type_tag, key)
.map_err(From::from)
.and_then(move |value| {
let decoded = if value.content.is_empty() {
None
} else {
Some(decode_app_entry(&value.content, &app_keys.enc_key)?)
};
Ok((value.entry_version, decoded))
})
.into_box()
}
pub fn put_entry<T>(
client: &Client<T>,
app_id: &str,
app_keys: &AppKeys,
permissions: &AccessContainerEntry,
version: u64,
) -> Box<AuthFuture<()>>
where
T: 'static,
{
let access_container = fry!(client.access_container());
let key = fry!(enc_key(&access_container, app_id, &app_keys.enc_key));
let ciphertext = fry!(encode_app_entry(permissions, &app_keys.enc_key));
let actions = if version == 0 {
EntryActions::new().ins(key, ciphertext, 0)
} else {
EntryActions::new().update(key, ciphertext, version)
};
recovery::mutate_mdata_entries(
client,
access_container.name,
access_container.type_tag,
actions.into(),
).map_err(From::from)
.into_box()
}
pub fn delete_entry<T: 'static>(
client: &Client<T>,
app_id: &str,
app_keys: &AppKeys,
version: u64,
) -> Box<AuthFuture<()>> {
let access_container = fry!(client.access_container());
let key = fry!(enc_key(&access_container, app_id, &app_keys.enc_key));
let actions = EntryActions::new().del(key, version);
recovery::mutate_mdata_entries(
client,
access_container.name,
access_container.type_tag,
actions.into(),
).map_err(From::from)
.into_box()
}