#![allow(unsafe_code)]
mod revocation;
mod share_mdata;
mod utils;
use crate::ffi::apps::AppPermissions as FfiAppPermissions;
use crate::ffi::apps::*;
use crate::ffi::errors::{ERR_INVALID_MSG, ERR_OPERATION_FORBIDDEN, ERR_UNKNOWN_APP};
use crate::ffi::ipc::{
auth_revoke_app, encode_auth_resp, encode_containers_resp, encode_unregistered_resp,
};
use crate::test_utils::{auth_decode_ipc_msg_helper, err_cb, unregistered_cb};
use ffi_utils::test_utils::{call_1, call_vec, sender_as_user_data};
use ffi_utils::{ReprC, StringError};
use safe_authenticator::app_container;
use safe_authenticator::config;
use safe_authenticator::errors::AuthError;
use safe_authenticator::test_utils::{
self, create_account_and_login, rand_app, register_app, ChannelType,
};
use safe_core::btree_set;
use safe_core::config_handler::Config;
use safe_core::ffi::error_codes::ERR_NO_SUCH_CONTAINER;
use safe_core::ffi::ipc::req::AppExchangeInfo as FfiAppExchangeInfo;
use safe_core::ipc::{self, AuthReq, ContainersReq, IpcError, IpcMsg, IpcReq, IpcResp, Permission};
use safe_core::{app_container_name, AuthActions};
use safe_nd::AppPermissions;
use std::collections::HashMap;
use std::ffi::CString;
use std::sync::mpsc;
use std::time::Duration;
use tiny_keccak::sha3_256;
use unwrap::unwrap;
#[tokio::test]
async fn app_authentication() -> Result<(), AuthError> {
let authenticator = create_account_and_login().await;
let client = &authenticator.client;
let msg = IpcMsg::Revoked {
app_id: "hello".to_string(),
};
let encoded_msg = unwrap!(ipc::encode_msg(&msg));
match auth_decode_ipc_msg_helper(&authenticator, &encoded_msg) {
Err((ERR_INVALID_MSG, None)) => (),
x => panic!("Unexpected {:?}", x),
}
let req_id = ipc::gen_req_id();
let app_exchange_info = rand_app();
let app_id = app_exchange_info.id.clone();
let containers = utils::create_containers_req();
let auth_req = AuthReq {
app: app_exchange_info.clone(),
app_container: true,
app_permissions: Default::default(),
containers,
};
let msg = IpcMsg::Req {
req_id,
request: IpcReq::Auth(auth_req.clone()),
};
let encoded_msg = unwrap!(ipc::encode_msg(&msg));
let (received_req_id, received_auth_req) =
match unwrap!(auth_decode_ipc_msg_helper(&authenticator, &encoded_msg)) {
(
IpcMsg::Req {
req_id,
request: IpcReq::Auth(req),
},
_,
) => (req_id, req),
x => panic!("Unexpected {:?}", x),
};
assert_eq!(received_req_id, req_id);
assert_eq!(received_auth_req, auth_req);
let encoded_auth_resp: String = unsafe {
unwrap!(call_1(|ud, cb| {
let auth_req = unwrap!(auth_req.into_repr_c());
encode_auth_resp(
&authenticator,
&auth_req,
req_id,
true, ud,
cb,
)
}))
};
let auth_granted = match unwrap!(ipc::decode_msg(&encoded_auth_resp)) {
IpcMsg::Resp {
req_id: received_req_id,
response: IpcResp::Auth(Ok(auth_granted)),
} => {
assert_eq!(received_req_id, req_id);
auth_granted
}
x => panic!("Unexpected {:?}", x),
};
let mut expected = utils::create_containers_req();
let _ = expected.insert(
app_container_name(&app_id),
btree_set![
Permission::Read,
Permission::Insert,
Permission::Update,
Permission::Delete,
Permission::ManagePermissions,
],
);
for (container, permissions) in expected.clone() {
let perms = unwrap!(auth_granted.access_container_entry.get(&container));
assert_eq!((*perms).1, permissions);
}
let mut access_container =
test_utils::access_container(&authenticator, app_id.clone(), auth_granted.clone()).await?;
assert_eq!(access_container.len(), 3);
let app_keys = auth_granted.app_keys;
let app_pk = app_keys.public_key();
test_utils::compare_access_container_entries(
&authenticator,
app_pk,
access_container.clone(),
expected,
)
.await;
let (app_dir_info, _) = unwrap!(access_container.remove(&app_container_name(&app_id)));
let (_, apps) = config::list_apps(client).await?;
let app_config_key = sha3_256(app_id.as_bytes());
let app_info = unwrap!(apps.get(&app_config_key));
assert_eq!(app_info.info, app_exchange_info);
assert_eq!(app_info.keys, app_keys);
let app_dir = app_container::fetch(client, &app_id).await?;
let received_app_dir_info = match app_dir {
Some(app_dir) => app_dir,
None => panic!("App directory not present"),
};
assert_eq!(received_app_dir_info, app_dir_info);
let (keys, _) = client.list_auth_keys_and_version().await?;
assert!(keys.contains_key(&app_pk));
Ok(())
}
#[tokio::test]
async fn invalid_container_authentication() -> Result<(), AuthError> {
let authenticator = create_account_and_login().await;
let req_id = ipc::gen_req_id();
let app_exchange_info = rand_app();
let mut containers = HashMap::new();
let _ = containers.insert(
"_app".to_owned(),
btree_set![
Permission::Read,
Permission::Insert,
Permission::Update,
Permission::Delete,
Permission::ManagePermissions,
],
);
let auth_req = AuthReq {
app: app_exchange_info,
app_container: true,
app_permissions: Default::default(),
containers,
};
let result: Result<String, i32> = unsafe {
call_1(|ud, cb| {
let auth_req = unwrap!(auth_req.into_repr_c());
encode_auth_resp(
&authenticator,
&auth_req,
req_id,
true, ud,
cb,
)
})
};
match result {
Err(error) if error == ERR_NO_SUCH_CONTAINER => Ok(()),
x => panic!("Unexpected {:?}", x),
}
}
#[tokio::test]
async fn unregistered_authentication() -> Result<(), AuthError> {
let msg = IpcMsg::Req {
req_id: ipc::gen_req_id(),
request: IpcReq::Auth(AuthReq {
app: rand_app(),
app_container: true,
app_permissions: Default::default(),
containers: utils::create_containers_req(),
}),
};
let encoded_msg = unwrap!(ipc::encode_msg(&msg));
match unregistered_decode_ipc_msg(&encoded_msg) {
Err((ERR_OPERATION_FORBIDDEN, None)) => (),
x => panic!("Unexpected {:?}", x),
}
let test_data = vec![0u8; 10];
let req_id = ipc::gen_req_id();
let msg = IpcMsg::Req {
req_id,
request: IpcReq::Unregistered(test_data.clone()),
};
let encoded_msg = unwrap!(ipc::encode_msg(&msg));
let (received_req_id, received_data) = match unwrap!(unregistered_decode_ipc_msg(&encoded_msg))
{
(
IpcMsg::Req {
req_id,
request: IpcReq::Unregistered(extra_data),
},
_,
) => (req_id, extra_data),
x => panic!("Unexpected {:?}", x),
};
assert_eq!(received_req_id, req_id);
assert_eq!(received_data, test_data);
let encoded_resp: String = unsafe {
unwrap!(call_1(|ud, cb| {
encode_unregistered_resp(
req_id, true, ud, cb,
)
}))
};
let bootstrap_cfg = match unwrap!(ipc::decode_msg(&encoded_resp)) {
IpcMsg::Resp {
req_id: received_req_id,
response: IpcResp::Unregistered(Ok(bootstrap_cfg)),
} => {
assert_eq!(received_req_id, req_id);
bootstrap_cfg
}
x => panic!("Unexpected {:?}", x),
};
assert_eq!(bootstrap_cfg, Config::new().quic_p2p.hard_coded_contacts);
let authenticator = create_account_and_login().await;
let (received_req_id, received_data) =
match unwrap!(auth_decode_ipc_msg_helper(&authenticator, &encoded_msg)) {
(
IpcMsg::Req {
req_id,
request: IpcReq::Unregistered(extra_data),
},
_,
) => (req_id, extra_data),
x => panic!("Unexpected {:?}", x),
};
assert_eq!(received_req_id, req_id);
assert_eq!(received_data, test_data);
Ok(())
}
#[tokio::test]
async fn authenticated_app_can_be_authenticated_again() -> Result<(), AuthError> {
let authenticator = create_account_and_login().await;
let auth_req = AuthReq {
app: rand_app(),
app_container: false,
app_permissions: Default::default(),
containers: Default::default(),
};
let req_id = ipc::gen_req_id();
let msg = IpcMsg::Req {
req_id,
request: IpcReq::Auth(auth_req.clone()),
};
let encoded_msg = unwrap!(ipc::encode_msg(&msg));
match unwrap!(auth_decode_ipc_msg_helper(&authenticator, &encoded_msg)) {
(
IpcMsg::Req {
request: IpcReq::Auth(_),
..
},
_,
) => (),
x => panic!("Unexpected {:?}", x),
};
let _resp: String = unsafe {
unwrap!(call_1(|ud, cb| {
let auth_req = unwrap!(auth_req.clone().into_repr_c());
encode_auth_resp(
&authenticator,
&auth_req,
req_id,
true, ud,
cb,
)
}))
};
let req_id = ipc::gen_req_id();
let msg = IpcMsg::Req {
req_id,
request: IpcReq::Auth(auth_req),
};
let encoded_msg = unwrap!(ipc::encode_msg(&msg));
match unwrap!(auth_decode_ipc_msg_helper(&authenticator, &encoded_msg)) {
(
IpcMsg::Req {
request: IpcReq::Auth(_),
..
},
_,
) => (),
x => panic!("Unexpected {:?}", x),
};
Ok(())
}
#[tokio::test]
async fn containers_unknown_app() -> Result<(), AuthError> {
let authenticator = create_account_and_login().await;
let req_id = ipc::gen_req_id();
let msg = IpcMsg::Req {
req_id,
request: IpcReq::Containers(ContainersReq {
app: rand_app(),
containers: utils::create_containers_req(),
}),
};
let encoded_msg = unwrap!(ipc::encode_msg(&msg));
match auth_decode_ipc_msg_helper(&authenticator, &encoded_msg) {
Err((
code,
Some(IpcMsg::Resp {
response: IpcResp::Auth(Err(IpcError::UnknownApp)),
..
}),
)) if code == ERR_UNKNOWN_APP => (),
x => panic!("Unexpected {:?}", x),
};
Ok(())
}
#[tokio::test]
async fn containers_access_request() -> Result<(), AuthError> {
let authenticator = create_account_and_login().await;
let auth_req = AuthReq {
app: rand_app(),
app_container: true,
app_permissions: Default::default(),
containers: utils::create_containers_req(),
};
let app_id = auth_req.app.id.clone();
let auth_granted = register_app(&authenticator, &auth_req).await?;
let req_id = ipc::gen_req_id();
let cont_req = ContainersReq {
app: auth_req.app,
containers: {
let mut containers = HashMap::new();
let _ = containers.insert("_downloads".to_string(), btree_set![Permission::Update]);
containers
},
};
let encoded_containers_resp: String = unsafe {
unwrap!(call_1(|ud, cb| {
let cont_req = unwrap!(cont_req.into_repr_c());
encode_containers_resp(
&authenticator,
&cont_req,
req_id,
true, ud,
cb,
)
}))
};
match ipc::decode_msg(&encoded_containers_resp) {
Ok(IpcMsg::Resp {
response: IpcResp::Containers(Ok(())),
..
}) => (),
x => panic!("Unexpected {:?}", x),
}
let mut expected = utils::create_containers_req();
let _ = expected.insert("_downloads".to_owned(), btree_set![Permission::Update]);
let app_pk = auth_granted.app_keys.public_key();
let access_container =
test_utils::access_container(&authenticator, app_id, auth_granted).await?;
test_utils::compare_access_container_entries(
&authenticator,
app_pk,
access_container,
expected,
)
.await;
Ok(())
}
struct RegisteredAppId {
id: String,
perms: FfiAppPermissions,
}
impl ReprC for RegisteredAppId {
type C = *const RegisteredApp;
type Error = StringError;
unsafe fn clone_from_repr_c(repr_c: Self::C) -> Result<Self, Self::Error> {
Ok(RegisteredAppId {
id: String::clone_from_repr_c((*repr_c).app_info.id)?,
perms: (*repr_c).app_permissions,
})
}
}
struct RevokedAppId(String);
impl ReprC for RevokedAppId {
type C = *const FfiAppExchangeInfo;
type Error = StringError;
unsafe fn clone_from_repr_c(repr_c: Self::C) -> Result<Self, Self::Error> {
Ok(RevokedAppId(String::clone_from_repr_c((*repr_c).id)?))
}
}
#[tokio::test]
async fn lists_of_registered_and_revoked_apps() -> Result<(), AuthError> {
let authenticator = create_account_and_login().await;
let registered: Vec<RegisteredAppId> = unsafe {
unwrap!(call_vec(|ud, cb| auth_registered_apps(
&authenticator,
ud,
cb
),))
};
let revoked: Vec<RevokedAppId> =
unsafe { unwrap!(call_vec(|ud, cb| auth_revoked_apps(&authenticator, ud, cb))) };
assert!(registered.is_empty());
assert!(revoked.is_empty());
let auth_req1 = AuthReq {
app: rand_app(),
app_container: false,
app_permissions: Default::default(),
containers: Default::default(),
};
let auth_req2 = AuthReq {
app: rand_app(),
app_container: false,
app_permissions: Default::default(),
containers: Default::default(),
};
let _ = register_app(&authenticator, &auth_req1).await?;
let _ = register_app(&authenticator, &auth_req2).await?;
let registered: Vec<RegisteredAppId> = unsafe {
unwrap!(call_vec(|ud, cb| auth_registered_apps(
&authenticator,
ud,
cb
),))
};
let revoked: Vec<RevokedAppId> =
unsafe { unwrap!(call_vec(|ud, cb| auth_revoked_apps(&authenticator, ud, cb))) };
assert_eq!(registered.len(), 2);
assert!(revoked.is_empty());
let id_str = unwrap!(CString::new(auth_req1.app.id.clone()));
let _: String = unsafe {
unwrap!(call_1(|ud, cb| auth_revoke_app(
&authenticator,
id_str.as_ptr(),
ud,
cb
)))
};
let registered: Vec<RegisteredAppId> = unsafe {
unwrap!(call_vec(|ud, cb| auth_registered_apps(
&authenticator,
ud,
cb
),))
};
let revoked: Vec<RevokedAppId> =
unsafe { unwrap!(call_vec(|ud, cb| auth_revoked_apps(&authenticator, ud, cb))) };
assert_eq!(registered.len(), 1);
assert_eq!(revoked.len(), 1);
let _ = register_app(&authenticator, &auth_req1).await?;
let registered: Vec<RegisteredAppId> = unsafe {
unwrap!(call_vec(|ud, cb| auth_registered_apps(
&authenticator,
ud,
cb
),))
};
let revoked: Vec<RevokedAppId> =
unsafe { unwrap!(call_vec(|ud, cb| auth_revoked_apps(&authenticator, ud, cb))) };
assert_eq!(registered.len(), 2);
assert_eq!(revoked.len(), 0);
Ok(())
}
#[tokio::test]
async fn test_registered_apps() -> Result<(), AuthError> {
let authenticator = create_account_and_login().await;
let app1 = rand_app();
let app1_id = app1.clone().id;
let app1_perms = AppPermissions {
transfer_coins: true,
perform_mutations: false,
get_balance: true,
};
let auth_req1 = AuthReq {
app: app1,
app_container: false,
app_permissions: app1_perms,
containers: Default::default(),
};
let app2_perms = AppPermissions {
transfer_coins: false,
perform_mutations: true,
get_balance: false,
};
let auth_req2 = AuthReq {
app: rand_app(),
app_container: false,
app_permissions: app2_perms,
containers: Default::default(),
};
let _ = register_app(&authenticator, &auth_req1).await?;
let _ = register_app(&authenticator, &auth_req2).await?;
let registered: Vec<RegisteredAppId> = unsafe {
unwrap!(call_vec(|ud, cb| auth_registered_apps(
&authenticator,
ud,
cb
),))
};
assert_eq!(registered.len(), 2);
for app in registered {
if app1_id == app.id {
assert_eq!(app.perms.transfer_coins, app1_perms.transfer_coins);
assert_eq!(app.perms.get_balance, app1_perms.get_balance);
assert_eq!(app.perms.perform_mutations, app1_perms.perform_mutations);
} else {
assert_eq!(app.perms.transfer_coins, app2_perms.transfer_coins);
assert_eq!(app.perms.get_balance, app2_perms.get_balance);
assert_eq!(app.perms.perform_mutations, app2_perms.perform_mutations);
}
}
Ok(())
}
fn unregistered_decode_ipc_msg(msg: &str) -> ChannelType {
let (tx, rx) = mpsc::channel::<ChannelType>();
let ffi_msg = unwrap!(CString::new(msg));
let mut ud = Default::default();
unsafe {
use crate::ffi::ipc::auth_unregistered_decode_ipc_msg;
auth_unregistered_decode_ipc_msg(
ffi_msg.as_ptr(),
sender_as_user_data(&tx, &mut ud),
unregistered_cb,
err_cb,
);
};
match rx.recv_timeout(Duration::from_secs(15)) {
Ok(r) => r,
Err(_) => Err((-1, None)),
}
}