mod revocation;
mod serialisation;
mod share_mdata;
mod utils;
use crate::access_container as access_container_tools;
use crate::config::{self, KEY_APPS};
use crate::errors::{AuthError, ERR_INVALID_MSG, ERR_OPERATION_FORBIDDEN, ERR_UNKNOWN_APP};
use crate::ffi::apps::*;
use crate::ffi::ipc::{
auth_revoke_app, encode_auth_resp, encode_containers_resp, encode_unregistered_resp,
};
use crate::safe_core::ffi::ipc::req::AppExchangeInfo as FfiAppExchangeInfo;
use crate::safe_core::ipc::{
self, AuthReq, ContainersReq, IpcError, IpcMsg, IpcReq, IpcResp, Permission,
};
use crate::std_dirs::{DEFAULT_PRIVATE_DIRS, DEFAULT_PUBLIC_DIRS};
use crate::test_utils::{self, ChannelType};
use crate::{app_container, run};
use ffi_utils::test_utils::{call_1, call_vec, sender_as_user_data};
use ffi_utils::{from_c_str, ErrorCode, ReprC, StringError};
use futures::{future, Future};
use safe_core::config_handler::Config;
use safe_core::{app_container_name, mdata_info, AuthActions, Client};
use safe_nd::PublicKey;
use std::collections::HashMap;
use std::ffi::CString;
use std::sync::mpsc;
use std::time::Duration;
use tiny_keccak::sha3_256;
#[cfg(feature = "mock-network")]
mod mock_routing {
use super::utils;
use crate::access_container as access_container_tools;
use crate::errors::AuthError;
use crate::run;
use crate::std_dirs::{DEFAULT_PRIVATE_DIRS, DEFAULT_PUBLIC_DIRS};
use crate::{test_utils, Authenticator};
use futures::Future;
use safe_core::ipc::AuthReq;
use safe_core::nfs::NfsError;
use safe_core::utils::generate_random_string;
use safe_core::{
app_container_name, test_create_balance, Client, ConnectionManager, CoreError,
};
use safe_nd::{Coins, Error as SndError, PublicKey, Request, RequestType, Response};
use std::str::FromStr;
#[test]
fn std_dirs_recovery() {
let locator = unwrap!(generate_random_string(10));
let password = unwrap!(generate_random_string(10));
let balance_sk = threshold_crypto::SecretKey::random();
unwrap!(test_create_balance(
&balance_sk,
unwrap!(Coins::from_str("10"))
));
{
let cm_hook = move |mut cm: ConnectionManager| -> ConnectionManager {
let mut put_mdata_counter = 0;
cm.set_request_hook(move |req| {
match req {
Request::PutMData(data) if data.tag() == safe_core::DIR_TAG => {
put_mdata_counter += 1;
if put_mdata_counter > 4 {
Some(Response::Mutation(Err(SndError::InsufficientBalance)))
} else {
None
}
}
_ => None,
}
});
cm
};
let authenticator = Authenticator::create_acc_with_hook(
locator.clone(),
password.clone(),
balance_sk,
|| (),
cm_hook,
);
match authenticator {
Err(AuthError::AccountContainersCreation(_)) => (),
Err(x) => panic!("Unexpected error {:?}", x),
Ok(_) => panic!("Unexpected success"),
}
}
let authenticator = unwrap!(Authenticator::login(locator, password, || ()));
let std_dir_names: Vec<_> = DEFAULT_PRIVATE_DIRS
.iter()
.cloned()
.chain(DEFAULT_PUBLIC_DIRS.iter().cloned())
.collect();
let (_entry_version, entries) = unwrap!(run(&authenticator, |client| {
access_container_tools::fetch_authenticator_entry(client).map_err(AuthError::from)
}));
for name in std_dir_names {
assert!(entries.contains_key(name));
}
}
#[test]
fn login_with_low_balance() {
let cm_hook = move |mut cm: ConnectionManager| -> ConnectionManager {
cm.set_request_hook(move |req| {
if req.get_type() == RequestType::Mutation {
Some(Response::Mutation(Err(SndError::InsufficientBalance)))
} else {
None
}
});
cm
};
let _authenticator = test_utils::create_account_and_login_with_hook(cm_hook);
}
#[test]
fn app_authentication_recovery() {
let locator = unwrap!(generate_random_string(10));
let password = unwrap!(generate_random_string(10));
let balance_sk = threshold_crypto::SecretKey::random();
unwrap!(test_create_balance(
&balance_sk,
unwrap!(Coins::from_str("10"))
));
let cm_hook = move |mut cm: ConnectionManager| -> ConnectionManager {
cm.set_request_hook(move |req| {
match *req {
Request::InsAuthKey { .. } => {
Some(Response::Mutation(Err(SndError::InsufficientBalance)))
}
_ => None,
}
});
cm
};
let auth = unwrap!(Authenticator::create_acc_with_hook(
locator.clone(),
password.clone(),
balance_sk,
|| (),
cm_hook,
));
let auth_req = AuthReq {
app: test_utils::rand_app(),
app_container: true,
app_permissions: Default::default(),
containers: utils::create_containers_req(),
};
let app_id = auth_req.app.id.clone();
match test_utils::register_app(&auth, &auth_req) {
Err(AuthError::CoreError(CoreError::DataError(SndError::InsufficientBalance))) => (),
x => panic!("Unexpected {:?}", x),
}
let cm_hook = move |mut cm: ConnectionManager| -> ConnectionManager {
let mut reqs_counter = 0;
cm.set_request_hook(move |req| {
match *req {
Request::SetMDataUserPermissions { .. } => {
reqs_counter += 1;
if reqs_counter == 2 {
Some(Response::Mutation(Err(SndError::InsufficientBalance)))
} else {
None
}
}
_ => None,
}
});
cm
};
let auth = unwrap!(Authenticator::login_with_hook(
locator.clone(),
password.clone(),
|| (),
cm_hook,
));
match test_utils::register_app(&auth, &auth_req) {
Err(AuthError::CoreError(CoreError::DataError(SndError::InsufficientBalance))) => (),
x => panic!("Unexpected {:?}", x),
}
let cm_hook = move |mut cm: ConnectionManager| -> ConnectionManager {
cm.set_request_hook(move |req| {
match *req {
Request::PutMData { .. } => {
Some(Response::Mutation(Err(SndError::InsufficientBalance)))
}
_ => None,
}
});
cm
};
let auth = unwrap!(Authenticator::login_with_hook(
locator.clone(),
password.clone(),
|| (),
cm_hook,
));
match test_utils::register_app(&auth, &auth_req) {
Err(AuthError::NfsError(NfsError::CoreError(CoreError::DataError(
SndError::InsufficientBalance,
)))) => (),
x => panic!("Unexpected {:?}", x),
}
let cm_hook = move |mut cm: ConnectionManager| -> ConnectionManager {
cm.set_request_hook(move |req| {
match *req {
Request::MutateMDataEntries { .. } => {
Some(Response::Mutation(Err(SndError::InsufficientBalance)))
}
_ => None,
}
});
cm
};
let auth = unwrap!(Authenticator::login_with_hook(
locator.clone(),
password.clone(),
|| (),
cm_hook,
));
match test_utils::register_app(&auth, &auth_req) {
Err(AuthError::CoreError(CoreError::DataError(SndError::InsufficientBalance))) => (),
x => panic!("Unexpected {:?}", x),
}
let auth = unwrap!(Authenticator::login(
locator.clone(),
password.clone(),
|| (),
));
let auth_granted = match test_utils::register_app(&auth, &auth_req) {
Ok(auth_granted) => auth_granted,
x => panic!("Unexpected {:?}", x),
};
let mut ac_entries =
test_utils::access_container(&auth, app_id.clone(), auth_granted.clone());
let (_videos_md, _) = unwrap!(ac_entries.remove("_videos"));
let (_documents_md, _) = unwrap!(ac_entries.remove("_documents"));
let (app_container, _) = unwrap!(ac_entries.remove(&app_container_name(&app_id)));
let app_pk = PublicKey::from(auth_granted.app_keys.bls_pk);
unwrap!(run(&auth, move |client| {
let c2 = client.clone();
client
.get_mdata_version(*app_container.address())
.then(move |res| {
let version = unwrap!(res);
assert_eq!(version, 0);
c2.list_mdata_permissions(*app_container.address())
})
.then(move |res| {
let perms = unwrap!(res);
assert!(perms.contains_key(&app_pk));
assert_eq!(perms.len(), 1);
Ok(())
})
}));
}
}
#[test]
fn test_access_container() {
let authenticator = test_utils::create_account_and_login();
let std_dir_names: Vec<_> = DEFAULT_PRIVATE_DIRS
.iter()
.chain(DEFAULT_PUBLIC_DIRS.iter())
.collect();
let entries = unwrap!(run(&authenticator, |client| {
access_container_tools::fetch_authenticator_entry(client).map(|(_version, entries)| entries)
}));
for name in &std_dir_names {
assert!(entries.contains_key(**name));
}
let dirs = unwrap!(run(&authenticator, move |client| {
let fs: Vec<_> = entries
.into_iter()
.map(|(_, dir)| {
let f1 = client.list_seq_mdata_entries(dir.name(), dir.type_tag());
let f2 = client.list_mdata_permissions(*dir.address());
f1.join(f2).map_err(AuthError::from)
})
.collect();
future::join_all(fs)
}));
assert_eq!(dirs.len(), std_dir_names.len());
for (entries, permissions) in dirs {
assert!(entries.is_empty());
assert!(permissions.is_empty());
}
}
#[test]
fn config_root_dir() {
let authenticator = test_utils::create_account_and_login();
let (dir, entries) = unwrap!(run(&authenticator, |client| {
let dir = client.config_root_dir();
client
.list_seq_mdata_entries(dir.name(), dir.type_tag())
.map(move |entries| (dir, entries))
.map_err(AuthError::from)
}));
let entries = unwrap!(mdata_info::decrypt_entries(&dir, &entries));
let config = unwrap!(entries.get(KEY_APPS));
assert!(config.data.is_empty());
}
#[test]
fn app_authentication() {
let authenticator = test_utils::create_account_and_login();
let msg = IpcMsg::Revoked {
app_id: "hello".to_string(),
};
let encoded_msg = unwrap!(ipc::encode_msg(&msg));
match test_utils::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 = test_utils::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,
req: IpcReq::Auth(auth_req.clone()),
};
let encoded_msg = unwrap!(ipc::encode_msg(&msg));
let (received_req_id, received_auth_req) = match unwrap!(
test_utils::auth_decode_ipc_msg_helper(&authenticator, &encoded_msg)
) {
(
IpcMsg::Req {
req_id,
req: 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,
resp: 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());
assert_eq!(access_container.len(), 3);
let app_keys = auth_granted.app_keys;
let app_sign_pk = PublicKey::from(app_keys.bls_pk);
test_utils::compare_access_container_entries(
&authenticator,
app_sign_pk,
access_container.clone(),
expected,
);
let (app_dir_info, _) = unwrap!(access_container.remove(&app_container_name(&app_id)));
let apps = unwrap!(run(&authenticator, |client| {
config::list_apps(client).map(|(_, apps)| apps)
}));
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 received_app_dir_info = unwrap!(run(&authenticator, move |client| {
app_container::fetch(client, &app_id).and_then(move |app_dir| match app_dir {
Some(app_dir) => Ok(app_dir),
None => panic!("App directory not present"),
})
}));
assert_eq!(received_app_dir_info, app_dir_info);
let auth_keys = unwrap!(run(&authenticator, |client| {
client
.list_auth_keys_and_version()
.map(|(keys, _)| keys)
.map_err(AuthError::from)
}));
assert!(auth_keys.contains_key(&app_sign_pk));
}
#[test]
fn invalid_container_authentication() {
let authenticator = test_utils::create_account_and_login();
let req_id = ipc::gen_req_id();
let app_exchange_info = test_utils::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.clone(),
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 == AuthError::NoSuchContainer("_app".into()).error_code() => (),
x => panic!("Unexpected {:?}", x),
};
}
#[test]
fn unregistered_authentication() {
let msg = IpcMsg::Req {
req_id: ipc::gen_req_id(),
req: IpcReq::Auth(AuthReq {
app: test_utils::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,
req: 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,
req: 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,
resp: 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 = test_utils::create_account_and_login();
let (received_req_id, received_data) = match unwrap!(test_utils::auth_decode_ipc_msg_helper(
&authenticator,
&encoded_msg
)) {
(
IpcMsg::Req {
req_id,
req: 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);
}
#[test]
fn authenticated_app_can_be_authenticated_again() {
let authenticator = test_utils::create_account_and_login();
let auth_req = AuthReq {
app: test_utils::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,
req: IpcReq::Auth(auth_req.clone()),
};
let encoded_msg = unwrap!(ipc::encode_msg(&msg));
match unwrap!(test_utils::auth_decode_ipc_msg_helper(
&authenticator,
&encoded_msg
)) {
(
IpcMsg::Req {
req: 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,
req: IpcReq::Auth(auth_req),
};
let encoded_msg = unwrap!(ipc::encode_msg(&msg));
match unwrap!(test_utils::auth_decode_ipc_msg_helper(
&authenticator,
&encoded_msg
)) {
(
IpcMsg::Req {
req: IpcReq::Auth(_),
..
},
_,
) => (),
x => panic!("Unexpected {:?}", x),
};
}
#[test]
fn containers_unknown_app() {
let authenticator = test_utils::create_account_and_login();
let req_id = ipc::gen_req_id();
let msg = IpcMsg::Req {
req_id,
req: IpcReq::Containers(ContainersReq {
app: test_utils::rand_app(),
containers: utils::create_containers_req(),
}),
};
let encoded_msg = unwrap!(ipc::encode_msg(&msg));
match test_utils::auth_decode_ipc_msg_helper(&authenticator, &encoded_msg) {
Err((
code,
Some(IpcMsg::Resp {
resp: IpcResp::Auth(Err(IpcError::UnknownApp)),
..
}),
)) if code == ERR_UNKNOWN_APP => (),
x => panic!("Unexpected {:?}", x),
};
}
#[test]
fn containers_access_request() {
let authenticator = test_utils::create_account_and_login();
let auth_req = AuthReq {
app: test_utils::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 = unwrap!(test_utils::register_app(&authenticator, &auth_req));
let req_id = ipc::gen_req_id();
let cont_req = ContainersReq {
app: auth_req.app.clone(),
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 {
resp: 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_sign_pk = PublicKey::from(auth_granted.app_keys.bls_pk);
let access_container = test_utils::access_container(&authenticator, app_id, auth_granted);
test_utils::compare_access_container_entries(
&authenticator,
app_sign_pk,
access_container,
expected,
);
}
struct RegisteredAppId(String);
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(from_c_str((*repr_c).app_info.id)?))
}
}
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(from_c_str((*repr_c).id)?))
}
}
#[test]
fn lists_of_registered_and_revoked_apps() {
let authenticator = test_utils::create_account_and_login();
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: test_utils::rand_app(),
app_container: false,
app_permissions: Default::default(),
containers: Default::default(),
};
let auth_req2 = AuthReq {
app: test_utils::rand_app(),
app_container: false,
app_permissions: Default::default(),
containers: Default::default(),
};
let _ = unwrap!(test_utils::register_app(&authenticator, &auth_req1));
let _ = unwrap!(test_utils::register_app(&authenticator, &auth_req2));
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 _ = unwrap!(test_utils::register_app(&authenticator, &auth_req1));
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);
}
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),
test_utils::unregistered_cb,
test_utils::err_cb,
);
};
match rx.recv_timeout(Duration::from_secs(15)) {
Ok(r) => r,
Err(_) => Err((-1, None)),
}
}