blob: 9a9aa9f796a250e1a36106c9cca6676fbe36125b [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use failure::{format_err, Error};
use fidl::endpoints::{create_endpoints, create_request_stream};
use fidl_fuchsia_auth::{
AuthStateSummary, AuthenticationContextProviderMarker, AuthenticationContextProviderRequest,
};
use fidl_fuchsia_auth_account::{
AccountManagerMarker, AccountManagerProxy, LocalAccountId, Status,
};
use fuchsia_async as fasync;
use fuchsia_component::client::{launch, App};
use fuchsia_component::fuchsia_single_component_package_url;
use fuchsia_component::server::{NestedEnvironment, ServiceFs};
use futures::future::join;
use futures::prelude::*;
use lazy_static::lazy_static;
use std::ops::Deref;
lazy_static! {
/// URL for account manager.
static ref ACCOUNT_MANAGER_URL: String =
String::from(fuchsia_single_component_package_url!("account_manager"));
/// Arguments passed to account manager started in test environment.
static ref ACCOUNT_MANAGER_ARGS: Vec<String> = vec![String::from("--dev-auth-providers")];
}
/// Calls provision_new_account on the supplied account_manager, returning an error on any
/// non-OK responses, or the account ID on success.
async fn provision_new_account(
account_manager: &AccountManagerProxy,
) -> Result<LocalAccountId, Error> {
match await!(account_manager.provision_new_account())? {
(Status::Ok, Some(new_account_id)) => Ok(*new_account_id),
(status, _) => Err(format_err!("ProvisionNewAccount returned status: {:?}", status)),
}
}
/// Calls provision_from_auth_provider on the supplied account_manager using "dev_auth_provider",
/// mocks out the UI context, returning an error on any non-OK responses, or the account ID on
/// success. This requires the account manager to run the dev_auth_provider. See the
/// `account_manager` package for more information on how that is configured.
async fn provision_account_from_dev_auth_provider(
account_manager: &AccountManagerProxy,
) -> Result<LocalAccountId, Error> {
let (acp_client_end, mut acp_request_stream) =
create_request_stream::<AuthenticationContextProviderMarker>()
.expect("failed opening channel");
// This async function mocks out the auth context provider channel, although it supplies no data
// for the test.
let serve_fn = async move {
let request = await!(acp_request_stream.try_next())
.expect("AuthenticationContextProvider failed receiving message");
match request {
Some(AuthenticationContextProviderRequest::GetAuthenticationUiContext { .. }) => Ok(()),
None => Err(format_err!("AuthenticationContextProvider channel closed unexpectedly")),
}
};
let (serve_result, provision_result) = await!(join(
serve_fn,
account_manager.provision_from_auth_provider(acp_client_end, "dev_auth_provider"),
));
serve_result?;
match provision_result? {
(Status::Ok, Some(new_account_id)) => Ok(*new_account_id),
(status, _) => Err(format_err!("ProvisionFromAuthProvider returned status: {:?}", status)),
}
}
/// A proxy to an account manager running in a nested environment.
struct NestedAccountManagerProxy {
/// Proxy to account manager.
account_manager_proxy: AccountManagerProxy,
/// Application object for account manager. Needs to be kept in scope to
/// keep the nested environment alive.
_app: App,
/// The nested environment account manager is running in. Needs to be kept
/// in scope to keep the nested environment alive.
_nested_envronment: NestedEnvironment,
}
impl Deref for NestedAccountManagerProxy {
type Target = AccountManagerProxy;
fn deref(&self) -> &AccountManagerProxy {
&self.account_manager_proxy
}
}
/// Start account manager in an isolated environment and return a proxy to it.
fn create_account_manager() -> Result<NestedAccountManagerProxy, Error> {
let mut service_fs = ServiceFs::new();
// Use a salted environment name to ensure the environment is unique across
// calls.
let nested_environment = service_fs.create_salted_nested_environment("account_test_env")?;
let app = launch(
nested_environment.launcher(),
ACCOUNT_MANAGER_URL.clone(),
Some(ACCOUNT_MANAGER_ARGS.clone()),
)?;
fasync::spawn(service_fs.collect());
let account_manager_proxy = app.connect_to_service::<AccountManagerMarker>()?;
Ok(NestedAccountManagerProxy {
account_manager_proxy,
_app: app,
_nested_envronment: nested_environment,
})
}
// TODO(jsankey): Work with ComponentFramework and cramertj@ to develop a nice Rust equivalent of
// the C++ TestWithEnvironment fixture to provide isolated environments for each test case. We
// are currently creating a new environment for account manager to run in, but the tests
// themselves run in a single environment.
#[fuchsia_async::run_singlethreaded(test)]
async fn test_provision_new_account() -> Result<(), Error> {
let account_manager =
create_account_manager().expect("Failed to launch account manager in nested environment.");
// Verify we initially have no accounts.
assert_eq!(await!(account_manager.get_account_ids())?, vec![]);
// Provision a new account.
let account_1 = await!(provision_new_account(&account_manager))?;
assert_eq!(
await!(account_manager.get_account_ids())?,
vec![LocalAccountId { id: account_1.id }]
);
// Provision a second new account and verify it has a different ID.
let account_2 = await!(provision_new_account(&account_manager))?;
assert_ne!(account_1.id, account_2.id);
let account_ids = await!(account_manager.get_account_ids())?;
assert_eq!(account_ids.len(), 2);
assert!(account_ids.contains(&account_1));
assert!(account_ids.contains(&account_2));
// Auth state should be unknown for the new accounts
let (_, auth_states) = await!(account_manager.get_account_auth_states())?;
assert_eq!(auth_states.len(), 2);
assert!(auth_states.iter().all(|state| state.auth_state.summary == AuthStateSummary::Unknown
&& account_ids.contains(&state.account_id)));
Ok(())
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_provision_new_account_from_auth_provider() -> Result<(), Error> {
let account_manager =
create_account_manager().expect("Failed to launch account manager in nested environment.");
// Verify we initially have no accounts.
assert_eq!(await!(account_manager.get_account_ids())?, vec![]);
// Provision a new account.
let account_1 = await!(provision_account_from_dev_auth_provider(&account_manager))?;
assert_eq!(
await!(account_manager.get_account_ids())?,
vec![LocalAccountId { id: account_1.id }]
);
// Provision a second new account and verify it has a different ID.
let account_2 = await!(provision_account_from_dev_auth_provider(&account_manager))?;
assert_ne!(account_1.id, account_2.id);
let account_ids = await!(account_manager.get_account_ids())?;
assert_eq!(account_ids.len(), 2);
assert!(account_ids.contains(&account_1));
assert!(account_ids.contains(&account_2));
// Auth state should be unknown for the new accounts
let (_, auth_states) = await!(account_manager.get_account_auth_states())?;
assert_eq!(auth_states.len(), 2);
assert!(auth_states.iter().all(|state| state.auth_state.summary == AuthStateSummary::Unknown
&& account_ids.contains(&state.account_id)));
Ok(())
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_get_account_and_persona() -> Result<(), Error> {
let account_manager =
create_account_manager().expect("Failed to launch account manager in nested environment.");
assert_eq!(await!(account_manager.get_account_ids())?, vec![]);
let mut account = await!(provision_new_account(&account_manager))?;
// Connect a channel to the newly created account and verify it's usable.
let (acp_client_end, _) = create_endpoints()?;
let (account_client_end, account_server_end) = create_endpoints()?;
assert_eq!(
await!(account_manager.get_account(&mut account, acp_client_end, account_server_end))?,
Status::Ok
);
let account_proxy = account_client_end.into_proxy()?;
let account_auth_state = match await!(account_proxy.get_auth_state())? {
(Status::Ok, Some(auth_state)) => *auth_state,
(status, _) => return Err(format_err!("GetAuthState returned status: {:?}", status)),
};
assert_eq!(account_auth_state.summary, AuthStateSummary::Unknown);
// Connect a channel to the account's default persona and verify it's usable.
let (persona_client_end, persona_server_end) = create_endpoints()?;
assert_eq!(await!(account_proxy.get_default_persona(persona_server_end))?.0, Status::Ok);
let persona_proxy = persona_client_end.into_proxy()?;
let persona_auth_state = match await!(persona_proxy.get_auth_state())? {
(Status::Ok, Some(auth_state)) => *auth_state,
(status, _) => return Err(format_err!("GetAuthState returned status: {:?}", status)),
};
assert_eq!(persona_auth_state.summary, AuthStateSummary::Unknown);
Ok(())
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_account_deletion() -> Result<(), Error> {
let account_manager =
create_account_manager().expect("Failed to launch account manager in nested environment.");
assert_eq!(await!(account_manager.get_account_ids())?, vec![]);
let mut account_1 = await!(provision_new_account(&account_manager))?;
let account_2 = await!(provision_new_account(&account_manager))?;
let existing_accounts = await!(account_manager.get_account_ids())?;
assert!(existing_accounts.contains(&LocalAccountId { id: account_1.id }));
assert!(existing_accounts.contains(&LocalAccountId { id: account_2.id }));
assert_eq!(existing_accounts.len(), 2);
// Delete an account and verify it is removed.
assert_eq!(await!(account_manager.remove_account(&mut account_1))?, Status::Ok);
assert_eq!(
await!(account_manager.get_account_ids())?,
vec![LocalAccountId { id: account_2.id }]
);
// Connecting to the deleted account should fail.
let (acp_client_end, _acp_server_end) = create_endpoints()?;
let (_account_client_end, account_server_end) = create_endpoints()?;
assert_eq!(
await!(account_manager.get_account(&mut account_1, acp_client_end, account_server_end))?,
Status::NotFound
);
Ok(())
}