use super::AuthError;
use crate::client::AuthClient;
use bincode::{deserialize, serialize};
use log::trace;
use safe_core::{
core_structs::{access_container_enc_key, AccessContainerEntry, AppKeys},
ipc::req::{container_perms_into_permission_set, ContainerPermissions},
recoverable_apis,
utils::{symmetric_decrypt, symmetric_encrypt, SymEncKey},
Client, CoreError, MDataInfo,
};
use safe_nd::{
Error as SndError, MDataAction, MDataPermissionSet, MDataSeqEntryActions, PublicKey,
};
use std::collections::HashMap;
pub const AUTHENTICATOR_ENTRY: &str = "authenticator";
#[allow(clippy::implicit_hasher)]
pub async fn update_container_perms(
client: &AuthClient,
permissions: HashMap<String, ContainerPermissions>,
app_pk: PublicKey,
) -> Result<AccessContainerEntry, AuthError> {
let c2 = client.clone();
let (_, mut root_containers) = fetch_authenticator_entry(client).await?;
let mut perms = Vec::new();
let client = c2.clone();
for (container_key, access) in permissions {
let c2 = client.clone();
let mdata_info = root_containers
.remove(&container_key)
.ok_or_else(|| AuthError::NoSuchContainer(container_key.clone()))?;
let perm_set = container_perms_into_permission_set(&access);
let version = client.get_mdata_version(*mdata_info.address()).await?;
recoverable_apis::set_mdata_user_permissions(
c2,
*mdata_info.address(),
app_pk,
perm_set,
version + 1,
)
.await?;
perms.push((container_key, mdata_info, access));
}
Ok(perms
.into_iter()
.map(|(container_key, dir, access)| (container_key, (dir, access)))
.collect::<AccessContainerEntry>())
}
pub fn enc_key(
access_container: &MDataInfo,
app_id: &str,
secret_key: &SymEncKey,
) -> 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: &SymEncKey,
) -> Result<HashMap<String, MDataInfo>, AuthError> {
let plaintext = symmetric_decrypt(encoded, enc_key)?;
Ok(deserialize(&plaintext)?)
}
#[allow(clippy::implicit_hasher)]
pub fn encode_authenticator_entry(
decoded: &HashMap<String, MDataInfo>,
enc_key: &SymEncKey,
) -> Result<Vec<u8>, AuthError> {
let plaintext = serialize(decoded)?;
Ok(symmetric_encrypt(&plaintext, enc_key, None)?)
}
pub async fn fetch_authenticator_entry(
client: &AuthClient,
) -> Result<(u64, HashMap<String, MDataInfo>), AuthError> {
let access_container = client.access_container().await;
let key = {
let sk = client.secret_symmetric_key().await;
enc_key(&access_container, AUTHENTICATOR_ENTRY, &sk)?
};
let value = client
.get_seq_mdata_value(access_container.name(), access_container.type_tag(), key)
.await?;
let enc_key = client.secret_symmetric_key().await;
let decoded = decode_authenticator_entry(&value.data, &enc_key)?;
Ok((value.version, decoded))
}
#[allow(clippy::implicit_hasher)]
pub async fn put_authenticator_entry(
client: &AuthClient,
new_value: &HashMap<String, MDataInfo>,
version: u64,
) -> Result<(), AuthError> {
let access_container = client.access_container().await;
let (key, ciphertext) = {
let sk = client.secret_symmetric_key().await;
let key = enc_key(&access_container, AUTHENTICATOR_ENTRY, &sk)?;
let ciphertext = encode_authenticator_entry(new_value, &sk)?;
(key, ciphertext)
};
let actions = if version == 0 {
MDataSeqEntryActions::new().ins(key, ciphertext, 0)
} else {
MDataSeqEntryActions::new().update(key, ciphertext, version)
};
match recoverable_apis::mutate_mdata_entries(
client.clone(),
*access_container.address(),
actions,
)
.await
{
Ok(_result) => Ok(()),
Err(err) => Err(AuthError::from(err)),
}
}
pub fn decode_app_entry(
encoded: &[u8],
enc_key: &SymEncKey,
) -> Result<AccessContainerEntry, AuthError> {
let plaintext = symmetric_decrypt(encoded, enc_key)?;
Ok(deserialize(&plaintext)?)
}
pub fn encode_app_entry(
decoded: &AccessContainerEntry,
enc_key: &SymEncKey,
) -> Result<Vec<u8>, AuthError> {
let plaintext = serialize(decoded)?;
Ok(symmetric_encrypt(&plaintext, enc_key, None)?)
}
pub async fn fetch_entry(
client: AuthClient,
app_id: String,
app_keys: AppKeys,
) -> Result<(u64, Option<AccessContainerEntry>), AuthError> {
trace!(
"Fetching access container entry for app with ID {}...",
app_id
);
let access_container = client.access_container().await;
let key = enc_key(&access_container, &app_id, &app_keys.enc_key)?;
trace!("Fetching entry using entry key {:?}", key);
let result = client
.get_seq_mdata_value(access_container.name(), access_container.type_tag(), key)
.await;
match result {
Err(CoreError::DataError(SndError::NoSuchEntry)) => Ok((0, None)),
Err(err) => Err(AuthError::from(err)),
Ok(value) => {
let decoded = Some(decode_app_entry(&value.data, &app_keys.enc_key)?);
Ok((value.version, decoded))
}
}
}
pub async fn put_entry(
client: &AuthClient,
app_id: &str,
app_keys: &AppKeys,
permissions: &AccessContainerEntry,
version: u64,
) -> Result<(), AuthError> {
trace!("Putting access container entry for app {}...", app_id);
let access_container = client.access_container().await;
let acc_cont_info = access_container.clone();
let key = enc_key(&access_container, app_id, &app_keys.enc_key)?;
let ciphertext = encode_app_entry(permissions, &app_keys.enc_key)?;
let actions = if version == 0 {
MDataSeqEntryActions::new().ins(key, ciphertext, 0)
} else {
MDataSeqEntryActions::new().update(key, ciphertext, version)
};
let app_pk: PublicKey = app_keys.public_key();
let shell_version = client
.get_mdata_version(*access_container.address())
.await?;
client
.set_mdata_user_permissions(
*acc_cont_info.address(),
app_pk,
MDataPermissionSet::new().allow(MDataAction::Read),
shell_version + 1,
)
.await?;
recoverable_apis::mutate_mdata_entries(client.clone(), *access_container.address(), actions)
.await
.map_err(AuthError::from)
}
pub async fn delete_entry(
client: &AuthClient,
app_id: &str,
app_keys: &AppKeys,
version: u64,
) -> Result<(), AuthError> {
let access_container = client.access_container().await;
let acc_cont_info = access_container.clone();
let key = enc_key(&access_container, app_id, &app_keys.enc_key)?;
let actions = MDataSeqEntryActions::new().del(key, version);
let app_pk: PublicKey = app_keys.public_key();
let shell_version = client
.get_mdata_version(*access_container.address())
.await?;
client
.del_mdata_user_permissions(*acc_cont_info.address(), app_pk, shell_version + 1)
.await?;
recoverable_apis::mutate_mdata_entries(client.clone(), *access_container.address(), actions)
.await
.map_err(AuthError::from)
}