mod revocation;
mod serialisation;
mod share_mdata;
mod utils;
use self::utils::{ChannelType, create_containers_req, decode_ipc_msg, err_cb, unregistered_cb};
use access_container as access_container_tools;
use app_container;
use config::{self, KEY_APPS};
use errors::{AuthError, ERR_INVALID_MSG, ERR_OPERATION_FORBIDDEN, ERR_UNKNOWN_APP};
use ffi::apps::*;
use ffi::ipc::{auth_revoke_app, encode_auth_resp, encode_containers_resp, encode_unregistered_resp};
use ffi_utils::{ReprC, StringError, from_c_str};
use ffi_utils::test_utils::{call_1, call_vec, sender_as_user_data};
use futures::{Future, future};
use safe_core::{app_container_name, mdata_info};
use safe_core::ffi::ipc::req::AppExchangeInfo as FfiAppExchangeInfo;
use safe_core::ipc::{self, AuthReq, BootstrapConfig, ContainersReq, IpcError, IpcMsg, IpcReq,
IpcResp, Permission};
use std::collections::HashMap;
use std::ffi::CString;
use std::sync::mpsc;
use std::time::Duration;
use std_dirs::{DEFAULT_PRIVATE_DIRS, DEFAULT_PUBLIC_DIRS};
use test_utils::{access_container, compare_access_container_entries, create_account_and_login,
rand_app, register_app, run};
use tiny_keccak::sha3_256;
#[cfg(feature = "use-mock-routing")]
mod mock_routing {
use super::utils::create_containers_req;
use Authenticator;
use access_container as access_container_tools;
use errors::AuthError;
use futures::Future;
use routing::{ClientError, Request, Response, User};
use safe_core::{CoreError, MockRouting, app_container_name};
use safe_core::ipc::AuthReq;
use safe_core::nfs::NfsError;
use safe_core::utils::generate_random_string;
use std_dirs::{DEFAULT_PRIVATE_DIRS, DEFAULT_PUBLIC_DIRS};
use test_utils::{access_container, create_account_and_login_with_hook, rand_app, register_app,
run};
#[test]
fn std_dirs_recovery() {
use safe_core::DIR_TAG;
let locator = unwrap!(generate_random_string(10));
let password = unwrap!(generate_random_string(10));
let invitation = unwrap!(generate_random_string(10));
{
let routing_hook = move |mut routing: MockRouting| -> MockRouting {
let mut put_mdata_counter = 0;
routing.set_request_hook(move |req| {
match *req {
Request::PutMData { ref data, msg_id, .. } if data.tag() == DIR_TAG => {
put_mdata_counter += 1;
if put_mdata_counter > 4 {
Some(Response::PutMData {
msg_id,
res: Err(ClientError::LowBalance),
})
} else {
None
}
}
_ => None,
}
});
routing
};
let authenticator = Authenticator::create_acc_with_hook(
locator.clone(),
password.clone(),
invitation,
|| (),
routing_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) = 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 routing_hook = move |mut routing: MockRouting| -> MockRouting {
routing.set_request_hook(move |req| {
match *req {
Request::PutIData { msg_id, .. } => {
Some(Response::PutIData {
res: Err(ClientError::LowBalance),
msg_id,
})
}
Request::PutMData { msg_id, .. } => {
Some(Response::PutMData {
res: Err(ClientError::LowBalance),
msg_id,
})
}
Request::MutateMDataEntries { msg_id, .. } => {
Some(Response::MutateMDataEntries {
res: Err(ClientError::LowBalance),
msg_id,
})
}
Request::SetMDataUserPermissions { msg_id, .. } => {
Some(Response::SetMDataUserPermissions {
res: Err(ClientError::LowBalance),
msg_id,
})
}
Request::DelMDataUserPermissions { msg_id, .. } => {
Some(Response::DelMDataUserPermissions {
res: Err(ClientError::LowBalance),
msg_id,
})
}
Request::ChangeMDataOwner { msg_id, .. } => {
Some(Response::ChangeMDataOwner {
res: Err(ClientError::LowBalance),
msg_id,
})
}
Request::InsAuthKey { msg_id, .. } => {
Some(Response::InsAuthKey {
res: Err(ClientError::LowBalance),
msg_id,
})
}
Request::DelAuthKey { msg_id, .. } => {
Some(Response::DelAuthKey {
res: Err(ClientError::LowBalance),
msg_id,
})
}
_ => None,
}
});
routing
};
let _authenticator = create_account_and_login_with_hook(routing_hook);
}
#[test]
fn app_authentication_recovery() {
let locator = unwrap!(generate_random_string(10));
let password = unwrap!(generate_random_string(10));
let invitation = unwrap!(generate_random_string(10));
let routing_hook = move |mut routing: MockRouting| -> MockRouting {
routing.set_request_hook(move |req| {
match *req {
Request::InsAuthKey { msg_id, .. } => {
Some(Response::InsAuthKey {
res: Err(ClientError::LowBalance),
msg_id,
})
}
_ => None,
}
});
routing
};
let auth = unwrap!(Authenticator::create_acc_with_hook(
locator.clone(),
password.clone(),
invitation,
|| (),
routing_hook,
));
let auth_req = AuthReq {
app: rand_app(),
app_container: true,
containers: create_containers_req(),
};
let app_id = auth_req.app.id.clone();
match register_app(&auth, &auth_req) {
Err(AuthError::CoreError(CoreError::RoutingClientError(ClientError::LowBalance))) => (),
x => panic!("Unexpected {:?}", x),
}
let routing_hook = move |mut routing: MockRouting| -> MockRouting {
let mut reqs_counter = 0;
routing.set_request_hook(move |req| {
match *req {
Request::SetMDataUserPermissions { msg_id, .. } => {
reqs_counter += 1;
if reqs_counter == 2 {
Some(Response::SetMDataUserPermissions {
res: Err(ClientError::LowBalance),
msg_id,
})
} else {
None
}
}
_ => None,
}
});
routing
};
let auth = unwrap!(Authenticator::login_with_hook(
locator.clone(),
password.clone(),
|| (),
routing_hook,
));
match register_app(&auth, &auth_req) {
Err(AuthError::CoreError(CoreError::RoutingClientError(ClientError::LowBalance))) => (),
x => panic!("Unexpected {:?}", x),
}
let routing_hook = move |mut routing: MockRouting| -> MockRouting {
routing.set_request_hook(move |req| {
match *req {
Request::PutMData { msg_id, .. } => {
Some(Response::PutMData {
res: Err(ClientError::LowBalance),
msg_id,
})
}
_ => None,
}
});
routing
};
let auth = unwrap!(Authenticator::login_with_hook(
locator.clone(),
password.clone(),
|| (),
routing_hook,
));
match register_app(&auth, &auth_req) {
Err(AuthError::NfsError(NfsError::CoreError(
CoreError::RoutingClientError(ClientError::LowBalance)))) => (),
x => panic!("Unexpected {:?}", x),
}
let routing_hook = move |mut routing: MockRouting| -> MockRouting {
routing.set_request_hook(move |req| {
match *req {
Request::MutateMDataEntries { msg_id, .. } => {
Some(Response::SetMDataUserPermissions {
res: Err(ClientError::LowBalance),
msg_id,
})
}
_ => None,
}
});
routing
};
let auth = unwrap!(Authenticator::login_with_hook(
locator.clone(),
password.clone(),
|| (),
routing_hook,
));
match register_app(&auth, &auth_req) {
Err(AuthError::CoreError(CoreError::RoutingClientError(ClientError::LowBalance))) => (),
x => panic!("Unexpected {:?}", x),
}
let auth = unwrap!(Authenticator::login(
locator.clone(),
password.clone(),
|| (),
));
let auth_granted = match register_app(&auth, &auth_req) {
Ok(auth_granted) => auth_granted,
x => panic!("Unexpected {:?}", x),
};
let mut ac_entries = 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_md, _) = unwrap!(ac_entries.remove(&app_container_name(&app_id)));
let app_pk = auth_granted.app_keys.sign_pk;
run(&auth, move |client| {
let c2 = client.clone();
client
.get_mdata_version(app_container_md.name, app_container_md.type_tag)
.then(move |res| {
let version = unwrap!(res);
assert_eq!(version, 0);
c2.list_mdata_permissions(app_container_md.name, app_container_md.type_tag)
})
.then(move |res| {
let perms = unwrap!(res);
assert!(perms.contains_key(&User::Key(app_pk)));
assert_eq!(perms.len(), 1);
Ok(())
})
});
}
}
#[test]
fn test_access_container() {
let authenticator = create_account_and_login();
let std_dir_names: Vec<_> = DEFAULT_PRIVATE_DIRS
.iter()
.chain(DEFAULT_PUBLIC_DIRS.iter())
.collect();
let entries = 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 = run(&authenticator, move |client| {
let fs: Vec<_> = entries
.into_iter()
.map(|(_, dir)| {
let f1 = client.list_mdata_entries(dir.name, dir.type_tag);
let f2 = client.list_mdata_permissions(dir.name, dir.type_tag);
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 = create_account_and_login();
let (dir, entries) = run(&authenticator, |client| {
let dir = unwrap!(client.config_root_dir());
client
.list_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.content.is_empty());
}
#[test]
fn app_authentication() {
let authenticator = create_account_and_login();
let msg = IpcMsg::Revoked { app_id: "hello".to_string() };
let encoded_msg = unwrap!(ipc::encode_msg(&msg));
match decode_ipc_msg(&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 auth_req = AuthReq {
app: app_exchange_info.clone(),
app_container: true,
containers: create_containers_req(),
};
let msg = IpcMsg::Req {
req_id: 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!(decode_ipc_msg(&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 = create_containers_req();
let _ = expected.insert(
app_container_name(&app_id),
btree_set![
Permission::Read,
Permission::Insert,
Permission::Update,
Permission::Delete,
Permission::ManagePermissions,
],
);
let mut access_container =
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 = app_keys.sign_pk;
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 = 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 = 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 = run(&authenticator, |client| {
client
.list_auth_keys_and_version()
.map(|(keys, _)| keys)
.map_err(AuthError::from)
});
assert!(auth_keys.contains(&app_sign_pk));
}
#[test]
fn unregistered_authentication() {
let msg = IpcMsg::Req {
req_id: ipc::gen_req_id(),
req: IpcReq::Auth(AuthReq {
app: rand_app(),
app_container: true,
containers: 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_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, BootstrapConfig::default());
let authenticator = create_account_and_login();
let (received_req_id, received_data) =
match unwrap!(decode_ipc_msg(&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 = create_account_and_login();
let auth_req = AuthReq {
app: rand_app(),
app_container: false,
containers: Default::default(),
};
let req_id = ipc::gen_req_id();
let msg = IpcMsg::Req {
req_id: req_id,
req: IpcReq::Auth(auth_req.clone()),
};
let encoded_msg = unwrap!(ipc::encode_msg(&msg));
match unwrap!(decode_ipc_msg(&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_id,
req: IpcReq::Auth(auth_req),
};
let encoded_msg = unwrap!(ipc::encode_msg(&msg));
match unwrap!(decode_ipc_msg(&authenticator, &encoded_msg)) {
(IpcMsg::Req { req: IpcReq::Auth(_), .. }, _) => (),
x => panic!("Unexpected {:?}", x),
};
}
#[test]
fn containers_unknown_app() {
let authenticator = create_account_and_login();
let req_id = ipc::gen_req_id();
let msg = IpcMsg::Req {
req_id: req_id,
req: IpcReq::Containers(ContainersReq {
app: rand_app(),
containers: create_containers_req(),
}),
};
let encoded_msg = unwrap!(ipc::encode_msg(&msg));
match decode_ipc_msg(&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 = create_account_and_login();
let auth_req = AuthReq {
app: rand_app(),
app_container: true,
containers: create_containers_req(),
};
let app_id = auth_req.app.id.clone();
let auth_granted = unwrap!(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 = create_containers_req();
let _ = expected.insert("_downloads".to_owned(), btree_set![Permission::Update]);
let app_sign_pk = auth_granted.app_keys.sign_pk;
let access_container = access_container(&authenticator, app_id, auth_granted);
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(c_repr: Self::C) -> Result<Self, Self::Error> {
Ok(RegisteredAppId(from_c_str((*c_repr).app_info.id)?))
}
}
struct RevokedAppId(String);
impl ReprC for RevokedAppId {
type C = *const FfiAppExchangeInfo;
type Error = StringError;
unsafe fn clone_from_repr_c(
app_info: *const FfiAppExchangeInfo,
) -> Result<RevokedAppId, StringError> {
Ok(RevokedAppId(from_c_str((*app_info).id)?))
}
}
#[test]
fn lists_of_registered_and_revoked_apps() {
let authenticator = 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: rand_app(),
app_container: false,
containers: Default::default(),
};
let auth_req2 = AuthReq {
app: rand_app(),
app_container: false,
containers: Default::default(),
};
let _ = unwrap!(register_app(&authenticator, &auth_req1));
let _ = unwrap!(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!(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 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)),
}
}