use super::AuthError;
use crate::access_container;
use crate::access_container::update_container_perms;
use crate::app_container;
use crate::client::AuthClient;
use crate::config::{self, AppInfo, Apps};
use futures_util::future::FutureExt;
use log::trace;
use safe_core::btree_set;
use safe_core::client;
use safe_core::core_structs::{AccessContInfo, AccessContainerEntry, AppKeys};
use safe_core::ipc::req::{AuthReq, ContainerPermissions, Permission};
use safe_core::ipc::resp::AuthGranted;
use safe_core::{app_container_name, client::AuthActions, recoverable_apis, Client, MDataInfo};
use safe_nd::AppPermissions;
use std::collections::HashMap;
use tiny_keccak::sha3_256;
use unwrap::unwrap;
#[derive(Debug, Eq, PartialEq)]
pub enum AppState {
Authenticated,
Revoked,
NotAuthenticated,
}
pub async fn app_state(
client: &AuthClient,
apps: &Apps,
app_id: &str,
) -> Result<AppState, AuthError> {
let app_id_hash = sha3_256(app_id.as_bytes());
if let Some(app) = apps.get(&app_id_hash) {
let app_keys = app.keys.clone();
let res = access_container::fetch_entry(client.clone(), app_id.to_string(), app_keys).await;
match res {
Ok((_version, Some(_))) => Ok(AppState::Authenticated),
Ok((_, None)) => {
Ok(AppState::Revoked)
}
Err(e) => Err(e),
}
} else {
Ok(AppState::NotAuthenticated)
}
}
fn app_container_exists(permissions: &AccessContainerEntry, app_id: &str) -> bool {
match permissions.get(&app_container_name(app_id)) {
Some(&(_, ref access)) => {
*access
== btree_set![
Permission::Read,
Permission::Insert,
Permission::Update,
Permission::Delete,
Permission::ManagePermissions,
]
}
None => false,
}
}
fn insert_app_container(
mut permissions: AccessContainerEntry,
app_id: &str,
app_container_info: MDataInfo,
) -> AccessContainerEntry {
let access = btree_set![
Permission::Read,
Permission::Insert,
Permission::Update,
Permission::Delete,
Permission::ManagePermissions,
];
let _ = permissions.insert(app_container_name(app_id), (app_container_info, access));
permissions
}
async fn update_access_container(
client: &AuthClient,
app: &AppInfo,
permissions: AccessContainerEntry,
) -> Result<(), AuthError> {
let app_id = app.info.id.clone();
let app_keys = app.keys.clone();
trace!("Updating access container entry for app {}...", app_id);
let res = access_container::fetch_entry(client.clone(), app_id.clone(), app_keys.clone()).await;
let version = match res {
Ok((version, Some(_))) => version + 1,
Ok((_, None)) => 0,
Err(e) => return Err(e),
};
access_container::put_entry(&client, &app_id, &app_keys, &permissions, version).await
}
pub async fn authenticate(
client: &AuthClient,
auth_req: AuthReq,
) -> Result<AuthGranted, AuthError> {
let app_id = auth_req.app.id.clone();
let permissions = auth_req.containers.clone();
let AuthReq {
app_container,
app_permissions,
..
} = auth_req;
let (apps_version, mut apps) = config::list_apps(client).await?;
check_revocation(client, app_id.clone()).await?;
let app_state = app_state(&client, &apps, &app_id).await?;
let (app, app_state, app_id) = match app_state {
AppState::NotAuthenticated => {
let public_id = client.public_id().await;
let keys = AppKeys::new(unwrap!(public_id.client_public_id()).clone());
let app = AppInfo {
info: auth_req.app,
keys,
};
let _ = config::insert_app(
&client,
apps,
config::next_version(apps_version),
app.clone(),
)
.await?;
(app, app_state, app_id)
}
AppState::Authenticated | AppState::Revoked => {
let app_entry_name = sha3_256(app_id.as_bytes());
if let Some(app) = apps.remove(&app_entry_name) {
(app, app_state, app_id)
} else {
return Err(AuthError::from(
"Logical error - couldn't find a revoked app in config",
));
}
}
};
match app_state {
AppState::Authenticated => {
authenticated_app(&client, app, app_id, app_container, app_permissions).await
}
AppState::NotAuthenticated | AppState::Revoked => {
authenticate_new_app(&client, app, app_container, app_permissions, permissions).await
}
}
}
async fn authenticated_app(
client: &AuthClient,
app: AppInfo,
app_id: String,
app_container: bool,
_app_permissions: AppPermissions,
) -> Result<AuthGranted, AuthError> {
let app_keys = app.keys.clone();
let app_pk = app.keys.public_key();
let bootstrap_config = client::bootstrap_config()?;
let (_version, perms) =
access_container::fetch_entry(client.clone(), app_id.clone(), app_keys.clone()).await?;
let perms = perms.unwrap_or_else(AccessContainerEntry::default);
if app_container && !app_container_exists(&perms, &app_id) {
let mdata_info = app_container::fetch_or_create(&client, &app_id, app_pk).await?;
let perms = insert_app_container(perms.clone(), &app_id, mdata_info);
let _ = update_access_container(&client, &app, perms.clone())
.map(move |_| perms)
.await;
}
let access_container_info = client.access_container().await;
let access_container_info = AccessContInfo::from_mdata_info(&access_container_info)?;
Ok(AuthGranted {
app_keys,
bootstrap_config,
access_container_info,
access_container_entry: perms,
})
}
async fn authenticate_new_app(
client: &AuthClient,
app: AppInfo,
app_container: bool,
app_permissions: AppPermissions,
permissions: HashMap<String, ContainerPermissions>,
) -> Result<AuthGranted, AuthError> {
let app_pk = app.keys.public_key();
let app_keys = app.keys.clone();
let app_keys_auth = app.keys.clone();
let app_id = app.info.id.clone();
let (_, version) = client.list_auth_keys_and_version().await?;
recoverable_apis::ins_auth_key_to_client_h(
&client.clone(),
app_keys.public_key(),
app_permissions,
version + 1,
)
.await?;
let (mut perms, app_pk) = if permissions.is_empty() {
(Default::default(), app_pk)
} else {
let perms = update_container_perms(&client, permissions, app_pk).await?;
(perms, app_pk)
};
if app_container {
let mdata_info = app_container::fetch_or_create(&client, &app_id, app_pk).await?;
perms = insert_app_container(perms, &app_id, mdata_info);
}
update_access_container(&client, &app, perms.clone()).await?;
let access_container_entry = perms;
let access_container_info = client.access_container().await;
let access_container_info = AccessContInfo::from_mdata_info(&access_container_info)?;
Ok(AuthGranted {
app_keys: app_keys_auth,
bootstrap_config: client::bootstrap_config()?,
access_container_info,
access_container_entry,
})
}
async fn check_revocation(client: &AuthClient, app_id: String) -> Result<(), AuthError> {
let (_, queue) = config::get_app_revocation_queue(client).await?;
if queue.contains(&app_id) {
Err(AuthError::PendingRevocation)
} else {
Ok(())
}
}