blob: 051be442e413c121b40a6e57beb2b732203a19ef [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 crate::account::{Account, AccountContext};
use crate::common::AccountLifetime;
use crate::inspect;
use crate::lock_request;
use crate::pre_auth;
use account_common::{AccountManagerError, LocalAccountId};
use anyhow::format_err;
use fidl::endpoints::{create_proxy, ClientEnd, ServerEnd};
use fidl_fuchsia_auth::AuthenticationContextProviderMarker;
use fidl_fuchsia_identity_account::{AccountMarker, Error as ApiError};
use fidl_fuchsia_identity_authentication::{Enrollment, StorageUnlockMechanismProxy};
use fidl_fuchsia_identity_internal::{
AccountHandlerContextProxy, AccountHandlerControlRequest, AccountHandlerControlRequestStream,
HASH_SALT_SIZE, HASH_SIZE,
};
use fuchsia_async as fasync;
use fuchsia_inspect::{Inspector, Property};
use futures::channel::oneshot;
use futures::lock::Mutex;
use futures::prelude::*;
use identity_common::TaskGroupError;
use lazy_static::lazy_static;
use log::{error, info, warn};
use mundane::hash::{Digest, Hasher, Sha256};
use std::fmt;
use std::sync::Arc;
lazy_static! {
/// Temporary pre-key material which constitutes a successful authentication
/// attempt, for manual and automatic tests. This constant is specifically
/// for the developer authenticator implementations
/// (see src/identity/bin/dev_authenticator) and needs to stay in sync.
static ref MAGIC_PREKEY: Vec<u8> = vec![77; 32];
}
// A static enrollment id which represents the only enrollment.
const ENROLLMENT_ID: u64 = 0;
/// The states of an AccountHandler.
enum Lifecycle {
/// An account has not yet been created or loaded.
Uninitialized,
/// The handler is awaiting an account transfer.
PendingTransfer,
/// The handler is holding a transferred account and is awaiting finalization.
Transferred,
/// The account is locked.
Locked,
/// The account is currently loaded and is available.
Initialized { account: Arc<Account> },
/// There is no account present, and initialization is not possible.
Finished,
}
impl fmt::Debug for Lifecycle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
&Lifecycle::Uninitialized => "Uninitialized",
&Lifecycle::PendingTransfer => "PendingTransfer",
&Lifecycle::Transferred => "Transferred",
&Lifecycle::Locked { .. } => "Locked",
&Lifecycle::Initialized { .. } => "Initialized",
&Lifecycle::Finished => "Finished",
};
write!(f, "{}", name)
}
}
type GlobalIdHash = [u8; HASH_SIZE as usize];
type GlobalIdHashSalt = [u8; HASH_SALT_SIZE as usize];
/// The core state of the AccountHandler, i.e. the Account (once it is known) and references to
/// the execution context and a TokenManager.
pub struct AccountHandler {
/// An AccountHandlerContextProxy for this account.
context: AccountHandlerContextProxy,
/// The current state of the AccountHandler state machine, optionally containing
/// a reference to the `Account` and its pre-authentication data, depending on the
/// state. The methods of the AccountHandler drives changes to the state.
state: Arc<Mutex<Lifecycle>>,
/// Lifetime for this account (ephemeral or persistent with a path).
lifetime: AccountLifetime,
/// An implementation of the `Manager` trait that is responsible for retrieving and
/// persisting pre-authentication data of an account. The pre-auth data is cached
/// and available in the Locked and Initialized states.
// TODO(dnordstrom): Consider moving the pre_auth_manager into the AccountLifetime enum.
pre_auth_manager: Arc<dyn pre_auth::Manager>,
/// Helper for outputting account handler information via fuchsia_inspect.
inspect: Arc<inspect::AccountHandler>,
// TODO(jsankey): Add TokenManager and AccountHandlerContext.
}
impl AccountHandler {
/// Constructs a new AccountHandler and puts it in the Uninitialized state.
pub fn new(
context: AccountHandlerContextProxy,
account_id: LocalAccountId,
lifetime: AccountLifetime,
pre_auth_manager: Arc<dyn pre_auth::Manager>,
inspector: &Inspector,
) -> AccountHandler {
let inspect =
Arc::new(inspect::AccountHandler::new(inspector.root(), &account_id, "uninitialized"));
Self {
context,
state: Arc::new(Mutex::new(Lifecycle::Uninitialized)),
lifetime,
pre_auth_manager,
inspect,
}
}
/// Asynchronously handles the supplied stream of `AccountHandlerControlRequest` messages.
pub async fn handle_requests_from_stream(
&self,
mut stream: AccountHandlerControlRequestStream,
) -> Result<(), anyhow::Error> {
while let Some(req) = stream.try_next().await? {
self.handle_request(req).await?;
}
Ok(())
}
/// Dispatches an `AccountHandlerControlRequest` message to the appropriate handler method
/// based on its type.
pub async fn handle_request(
&self,
req: AccountHandlerControlRequest,
) -> Result<(), fidl::Error> {
match req {
AccountHandlerControlRequest::CreateAccount { auth_mechanism_id, responder } => {
let mut response = self.create_account(auth_mechanism_id).await;
responder.send(&mut response)?;
}
AccountHandlerControlRequest::Preload { responder } => {
let mut response = self.preload().await;
responder.send(&mut response)?;
}
AccountHandlerControlRequest::UnlockAccount { responder } => {
let mut response = self.unlock_account().await;
responder.send(&mut response)?;
}
AccountHandlerControlRequest::LockAccount { responder } => {
let mut response = self.lock_account().await;
responder.send(&mut response)?;
}
AccountHandlerControlRequest::PrepareForAccountTransfer { responder } => {
let mut response = self.prepare_for_account_transfer().await;
responder.send(&mut response)?;
}
AccountHandlerControlRequest::PerformAccountTransfer {
encrypted_account_data,
responder,
} => {
let mut response = self.perform_account_transfer(encrypted_account_data).await;
responder.send(&mut response)?;
}
AccountHandlerControlRequest::FinalizeAccountTransfer { responder, .. } => {
responder.send(&mut Err(ApiError::UnsupportedOperation))?;
}
AccountHandlerControlRequest::EncryptAccountData { responder, .. } => {
responder.send(&mut Err(ApiError::UnsupportedOperation))?;
}
AccountHandlerControlRequest::RemoveAccount { force, responder } => {
let mut response = self.remove_account(force).await;
responder.send(&mut response)?;
}
AccountHandlerControlRequest::GetAccount {
auth_context_provider,
account,
responder,
} => {
let mut response = self.get_account(auth_context_provider, account).await;
responder.send(&mut response)?;
}
AccountHandlerControlRequest::GetPublicKey { responder } => {
responder.send(&mut Err(ApiError::UnsupportedOperation))?;
}
AccountHandlerControlRequest::GetGlobalIdHash { salt, responder } => {
let mut response = self.get_global_id_hash(salt).await;
responder.send(&mut response)?;
}
AccountHandlerControlRequest::Terminate { control_handle } => {
self.terminate().await;
control_handle.shutdown();
}
}
Ok(())
}
/// Connects to a specified authentication mechanism and return a proxy to it.
async fn get_auth_mechanism_connection<'a>(
&'a self,
auth_mechanism_id: &'a str,
) -> Result<StorageUnlockMechanismProxy, ApiError> {
let (auth_mechanism_proxy, server_end) = create_proxy().map_err(|_| ApiError::Resource)?;
self.context
.get_storage_unlock_auth_mechanism(&auth_mechanism_id, server_end)
.await
.map_err(|_| ApiError::Resource)?
.map_err(|err| {
warn!("Failed to connect to authenticator: {:?}", err);
err
})?;
Ok(auth_mechanism_proxy)
}
/// Creates a new Fuchsia account and attaches it to this handler. Moves
/// the handler from the `Uninitialized` to the `Initialized` state.
async fn create_account(&self, auth_mechanism_id: Option<String>) -> Result<(), ApiError> {
let mut state_lock = self.state.lock().await;
match *state_lock {
Lifecycle::Uninitialized => {
let pre_auth_state = match (&self.lifetime, auth_mechanism_id) {
(AccountLifetime::Persistent { .. }, Some(auth_mechanism_id)) => {
let auth_mechanism_proxy =
self.get_auth_mechanism_connection(&auth_mechanism_id).await?;
let (data, prekey_material) = auth_mechanism_proxy
.enroll()
.await
.map_err(|err| {
warn!("Error connecting to authenticator: {:?}", err);
ApiError::Unknown
})?
.map_err(|authenticator_err| {
warn!(
"Error enrolling authentication mechanism: {:?}",
authenticator_err
);
AccountManagerError::from(authenticator_err).api_error
})?;
// TODO(fxbug.dev/45041): Use storage manager for key validation
if prekey_material != *MAGIC_PREKEY {
warn!("Received unexpected pre-key material from authenticator");
return Err(ApiError::Internal);
}
pre_auth::State::SingleEnrollment { auth_mechanism_id, data }
}
(AccountLifetime::Ephemeral, Some(_)) => {
warn!(
"CreateAccount called with auth_mechanism_id set on an ephemeral \
account"
);
return Err(ApiError::InvalidRequest);
}
(_, None) => pre_auth::State::NoEnrollments,
};
self.pre_auth_manager.put(pre_auth_state).await.map_err(|err| {
warn!("Could not write pre-auth data: {:?}", err);
err.api_error
})?;
let sender = self.create_lock_request_sender().await.map_err(|err| {
warn!("Error constructing lock request sender: {:?}", err);
err.api_error
})?;
let account = Account::create(
self.lifetime.clone(),
self.context.clone(),
sender,
self.inspect.get_node(),
)
.await
.map_err(|err| err.api_error)?;
*state_lock = Lifecycle::Initialized { account: Arc::new(account) };
self.inspect.lifecycle.set("initialized");
Ok(())
}
ref invalid_state @ _ => {
warn!("CreateAccount was called in the {:?} state", invalid_state);
Err(ApiError::FailedPrecondition)
}
}
}
/// Loads pre-authentication state for an account. Moves the handler from
/// the `Uninitialized` to the `Locked` state.
async fn preload(&self) -> Result<(), ApiError> {
if self.lifetime == AccountLifetime::Ephemeral {
warn!("Preload was called on an ephemeral account");
return Err(ApiError::InvalidRequest);
}
let mut state_lock = self.state.lock().await;
match *state_lock {
Lifecycle::Uninitialized => {
// Fetch the pre-auth state for validation, but ignore it.
let _ = self.pre_auth_manager.get().await.map_err(|err| {
warn!("Error fetching pre-auth state: {:?}", err);
err.api_error
})?;
*state_lock = Lifecycle::Locked;
self.inspect.lifecycle.set("locked");
Ok(())
}
ref invalid_state @ _ => {
warn!("Preload was called in the {:?} state", invalid_state);
Err(ApiError::FailedPrecondition)
}
}
}
/// Unlocks an existing Fuchsia account and attaches it to this handler.
/// If the account is enrolled with an authentication mechanism,
/// authentication will be performed as part of this call. Moves
/// the handler to the `Initialized` state.
async fn unlock_account(&self) -> Result<(), ApiError> {
let mut state_lock = self.state.lock().await;
match &*state_lock {
Lifecycle::Initialized { .. } => {
info!("UnlockAccount was called in the Initialized state, quietly succeeding.");
return Ok(());
}
Lifecycle::Locked => {
let (prekey_material, updated_state) =
self.authenticate().await.map_err(|err| {
warn!("Authentication error: {:?}", err);
err.api_error
})?;
// TODO(fxbug.dev/45041): Use storage manager for key validation
if let Some(prekey_material) = prekey_material {
if prekey_material != *MAGIC_PREKEY {
info!("Encountered a failed authentication attempt");
return Err(ApiError::FailedAuthentication);
}
}
let sender = self.create_lock_request_sender().await.map_err(|err| {
warn!("Error constructing lock request sender: {:?}", err);
err.api_error
})?;
let account = Account::load(
self.lifetime.clone(),
self.context.clone(),
sender,
self.inspect.get_node(),
)
.await
.map_err(|err| err.api_error)?;
if let Some(updated_state) = updated_state {
self.pre_auth_manager.put(updated_state).await.map_err(|err| err.api_error)?;
}
*state_lock = Lifecycle::Initialized { account: Arc::new(account) };
self.inspect.lifecycle.set("initialized");
Ok(())
}
ref invalid_state @ _ => {
warn!("UnlockAccount was called in the {:?} state", invalid_state);
Err(ApiError::FailedPrecondition)
}
}
}
/// Locks the account, terminating all open Account and Persona channels. Moves
/// the handler to the `Locked` state.
async fn lock_account(&self) -> Result<(), ApiError> {
Self::lock_now(Arc::clone(&self.state), Arc::clone(&self.inspect)).await.map_err(|err| {
warn!("LockAccount call failed: {:?}", err);
err.api_error
})
}
/// Prepares the handler for an account transfer. Moves the handler from the
/// `Uninitialized` state to the `PendingTransfer` state.
async fn prepare_for_account_transfer(&self) -> Result<(), ApiError> {
let mut state_lock = self.state.lock().await;
match *state_lock {
Lifecycle::Uninitialized => {
*state_lock = Lifecycle::PendingTransfer;
self.inspect.lifecycle.set("pendingTransfer");
Ok(())
}
ref invalid_state @ _ => {
warn!("PrepareForAccountTransfer was called in the {:?} state", invalid_state);
Err(ApiError::FailedPrecondition)
}
}
}
/// Loads an encrypted account into memory but does not make it available
/// for use yet. Moves the handler from the `PendingTransfer` state to the
/// `Transferred` state.
async fn perform_account_transfer(
&self,
_encrypted_account_data: Vec<u8>,
) -> Result<(), ApiError> {
let mut state_lock = self.state.lock().await;
match *state_lock {
Lifecycle::PendingTransfer => {
*state_lock = Lifecycle::Transferred;
self.inspect.lifecycle.set("transferred");
Ok(())
}
ref invalid_state @ _ => {
warn!("PerformAccountTransfer was called in the {:?} state", invalid_state);
Err(ApiError::FailedPrecondition)
}
}
}
/// Remove the active account. This method should not be retried on failure.
// TODO(fxbug.dev/555): Implement graceful account removal.
async fn remove_account(&self, force: bool) -> Result<(), ApiError> {
if force == false {
warn!("Graceful (non-force) account removal not yet implemented.");
return Err(ApiError::UnsupportedOperation);
}
let old_lifecycle = {
let mut state_lock = self.state.lock().await;
std::mem::replace(&mut *state_lock, Lifecycle::Finished)
};
self.inspect.lifecycle.set("finished");
let account_arc = match old_lifecycle {
Lifecycle::Locked { .. } => {
warn!("Removing a locked account is not yet implemented");
return Err(ApiError::UnsupportedOperation);
}
Lifecycle::Initialized { account, .. } => account,
_ => {
warn!("No account is initialized");
return Err(ApiError::FailedPrecondition);
}
};
// TODO(fxbug.dev/555): After this point, error recovery might include putting the account back
// in the lock.
if let Err(TaskGroupError::AlreadyCancelled) = account_arc.task_group().cancel().await {
warn!("Task group was already cancelled prior to account removal.");
}
// At this point we have exclusive access to the account, so we move it out of the Arc to
// destroy it.
let account = Arc::try_unwrap(account_arc).map_err(|_| {
warn!("Could not acquire exclusive access to account");
ApiError::Internal
})?;
account.remove().map_err(|(_account, err)| {
warn!("Could not delete account: {:?}", err);
err.api_error
})?;
self.pre_auth_manager.remove().await.map_err(|err| {
warn!("Could not remove pre-auth data: {:?}", err);
err.api_error
})?;
info!("Deleted Fuchsia account");
Ok(())
}
/// Connects the provided `account_server_end` to the `Account` protocol
/// served by this handler.
async fn get_account(
&self,
auth_context_provider_client_end: ClientEnd<AuthenticationContextProviderMarker>,
account_server_end: ServerEnd<AccountMarker>,
) -> Result<(), ApiError> {
let account_arc = match &*self.state.lock().await {
Lifecycle::Initialized { account, .. } => Arc::clone(account),
_ => {
warn!("AccountHandler is not initialized");
return Err(ApiError::FailedPrecondition);
}
};
let acp = auth_context_provider_client_end.into_proxy().map_err(|err| {
warn!("Error using AuthenticationContextProvider {:?}", err);
ApiError::InvalidRequest
})?;
let context = AccountContext { auth_ui_context_provider: acp };
let stream = account_server_end.into_stream().map_err(|err| {
warn!("Error opening Account channel {:?}", err);
ApiError::Resource
})?;
let account_arc_clone = Arc::clone(&account_arc);
account_arc
.task_group()
.spawn(|cancel| async move {
account_arc_clone
.handle_requests_from_stream(&context, stream, cancel)
.await
.unwrap_or_else(|e| error!("Error handling Account channel: {:?}", e));
})
.await
.map_err(|_| {
// Since AccountHandler serves only one channel of requests in serial, this is an
// inconsistent state rather than a conflict
ApiError::Internal
})
}
/// Computes a hash of the global account id of the account held by this
/// handler using the provided hash.
async fn get_global_id_hash(&self, salt: GlobalIdHashSalt) -> Result<GlobalIdHash, ApiError> {
let state_lock = self.state.lock().await;
let global_id = match &*state_lock {
Lifecycle::Initialized { account, .. } => account.global_id().as_ref(),
invalid_state @ _ => {
warn!("GetGlobalIdHash was called in the {:?} state", invalid_state);
return Err(ApiError::FailedPrecondition);
}
};
let mut salted_id = Vec::with_capacity(global_id.len() + HASH_SALT_SIZE as usize);
salted_id.extend_from_slice(&salt);
salted_id.extend_from_slice(global_id.as_slice());
Ok(Sha256::hash(&salted_id).bytes())
}
async fn terminate(&self) {
info!("Gracefully shutting down AccountHandler");
let old_state = {
let mut state_lock = self.state.lock().await;
std::mem::replace(&mut *state_lock, Lifecycle::Finished)
};
if let Lifecycle::Initialized { account, .. } = old_state {
if account.task_group().cancel().await.is_err() {
warn!("Task group cancelled but account is still initialized");
}
}
}
/// Performs an authentication attempt if appropriate. Returns pre-key
/// material from the attempt if the account is configured with a key, and
/// optionally a new pre-authentication state, to be written if the
/// attempt is successful.
async fn authenticate(
&self,
) -> Result<(Option<Vec<u8>>, Option<pre_auth::State>), AccountManagerError> {
let pre_auth_state = self.pre_auth_manager.get().await.map_err(|err| {
warn!("Error fetching pre-auth state: {:?}", err);
err.api_error
})?;
if let pre_auth::State::SingleEnrollment { ref auth_mechanism_id, ref data } =
pre_auth_state.as_ref()
{
let auth_mechanism_proxy =
self.get_auth_mechanism_connection(auth_mechanism_id).await?;
let mut enrollments = vec![Enrollment { id: ENROLLMENT_ID, data: data.clone() }];
let fut = auth_mechanism_proxy.authenticate(&mut enrollments.iter_mut());
let auth_attempt = fut.await.map_err(|err| {
AccountManagerError::new(ApiError::Unknown)
.with_cause(format_err!("Error connecting to authenticator: {:?}", err))
})??;
if auth_attempt.enrollment_id != ENROLLMENT_ID {
// TODO(dnordstrom): Error code for unexpected behavior from another component.
return Err(AccountManagerError::new(ApiError::Internal)
.with_cause(format_err!(
"Authenticator returned an unexpected enrollment id {} during authentication.",
auth_attempt.enrollment_id)));
}
// Determine whether pre-auth state should be updated
let updated_pre_auth_state = auth_attempt.updated_enrollment_data.map(|data| {
pre_auth::State::SingleEnrollment {
auth_mechanism_id: auth_mechanism_id.to_string(),
data: data,
}
});
let prekey_material = Some(auth_attempt.prekey_material);
Ok((prekey_material, updated_pre_auth_state))
} else {
Ok((None, None))
}
}
/// Returns a sender which, when dispatched, causes the account handler
/// to transition to the locked state. This method spawns
/// a task monitoring the lock request (which terminates quitely if the
/// sender is dropped). If lock requests are not supported for the account,
/// depending on the pre-auth state, an unsupported lock request sender is
/// returned.
async fn create_lock_request_sender(
&self,
) -> Result<lock_request::Sender, AccountManagerError> {
// Lock requests are only supported for accounts with an enrolled
// storage unlock mechanism
if self.pre_auth_manager.get().await?.as_ref() == &pre_auth::State::NoEnrollments {
return Ok(lock_request::Sender::NotSupported);
}
// Use weak pointers in order to not interfere with destruction of AccountHandler
let state_weak = Arc::downgrade(&self.state);
let inspect_weak = Arc::downgrade(&self.inspect);
let (sender, receiver) = lock_request::channel();
fasync::Task::spawn(async move {
match receiver.await {
Ok(()) => {
if let (Some(state), Some(inspect)) =
(state_weak.upgrade(), inspect_weak.upgrade())
{
if let Err(err) = Self::lock_now(state, inspect).await {
warn!("Lock request failure: {:?}", err);
}
}
}
Err(oneshot::Canceled) => {
// The sender was dropped, which is on the expected path.
}
}
})
.detach();
Ok(sender)
}
/// Moves the provided lifecycle to the lock state, and notifies the inspect
/// node of the change. Succeeds quitely if already locked.
async fn lock_now(
state: Arc<Mutex<Lifecycle>>,
inspect: Arc<inspect::AccountHandler>,
) -> Result<(), AccountManagerError> {
let mut state_lock = state.lock().await;
match &*state_lock {
Lifecycle::Locked { .. } => {
info!("A lock operation was attempted in the locked state, quietly succeeding.");
Ok(())
}
Lifecycle::Initialized { account } => {
let _ = account.task_group().cancel().await; // Ignore AlreadyCancelled error
let new_state = Lifecycle::Locked;
*state_lock = new_state;
inspect.lifecycle.set("locked");
Ok(())
}
ref invalid_state @ _ => Err(AccountManagerError::new(ApiError::FailedPrecondition)
.with_cause(format_err!(
"A lock operation was attempted in the {:?} state",
invalid_state
))),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fake_account_handler_context::{spawn_context_channel, FakeAccountHandlerContext};
use crate::fake_authenticator::{Expected, FakeAuthenticator};
use crate::test_util::*;
use fidl::endpoints::{create_endpoints, create_proxy_and_stream};
use fidl_fuchsia_identity_account::{Scenario, ThreatScenario};
use fidl_fuchsia_identity_authentication::{
AttemptedEvent, Enrollment, Error as AuthenticationApiError,
};
use fidl_fuchsia_identity_internal::{AccountHandlerControlMarker, AccountHandlerControlProxy};
use fuchsia_async as fasync;
use fuchsia_async::DurationExt;
use fuchsia_inspect::testing::AnyProperty;
use fuchsia_inspect::{assert_inspect_tree, Inspector};
use fuchsia_zircon as zx;
use futures::future::join;
use lazy_static::lazy_static;
use std::sync::Arc;
const DEFAULT_SCENARIO: Scenario =
Scenario { include_test: false, threat_scenario: ThreatScenario::None };
const FORCE_REMOVE_ON: bool = true;
const FORCE_REMOVE_OFF: bool = false;
const TEST_AUTH_MECHANISM_ID: &str = "<AUTH MECHANISM ID>";
const TEST_INVALID_AUTH_MECHANISM_ID: &str = "<INVALID AUTH MECHANISM ID>";
lazy_static! {
/// Initial enrollment data
static ref TEST_ENROLLMENT_DATA: Vec<u8> = vec![13, 37];
/// Updated enrollment data
static ref TEST_UPDATED_ENROLLMENT_DATA: Vec<u8> = vec![14, 37];
/// An initial pre-authentication state with a single enrollment
static ref TEST_PRE_AUTH_SINGLE: pre_auth::State = pre_auth::State::SingleEnrollment {
auth_mechanism_id: TEST_AUTH_MECHANISM_ID.to_string(),
data: TEST_ENROLLMENT_DATA.clone(),
};
/// An updated pre-authentication state
static ref TEST_PRE_AUTH_UPDATED: pre_auth::State = pre_auth::State::SingleEnrollment {
auth_mechanism_id: TEST_AUTH_MECHANISM_ID.to_string(),
data: TEST_UPDATED_ENROLLMENT_DATA.clone(),
};
/// Pre-key material that fails authentication.
static ref TEST_NOT_MAGIC_PREKEY: Vec<u8> = vec![80; 32];
/// Assumed time between a lock request and when the account handler is locked
static ref LOCK_REQUEST_DURATION: zx::Duration = zx::Duration::from_millis(20);
}
/// An enum expressing unexpected errors that may occur during a test.
#[derive(Debug)]
enum AccountHandlerTestError {
FidlError(fidl::Error),
AccountError(ApiError),
}
impl From<fidl::Error> for AccountHandlerTestError {
fn from(fidl_error: fidl::Error) -> Self {
AccountHandlerTestError::FidlError(fidl_error)
}
}
impl From<ApiError> for AccountHandlerTestError {
fn from(err: ApiError) -> Self {
AccountHandlerTestError::AccountError(err)
}
}
type TestResult = Result<(), AccountHandlerTestError>;
fn create_account_handler(
lifetime: AccountLifetime,
pre_auth_manager: Arc<dyn pre_auth::Manager>,
context: AccountHandlerContextProxy,
inspector: Arc<Inspector>,
) -> (AccountHandlerControlProxy, impl Future<Output = ()>) {
let test_object = AccountHandler::new(
context,
TEST_ACCOUNT_ID.clone().into(),
lifetime,
pre_auth_manager,
&inspector,
);
let (proxy, request_stream) = create_proxy_and_stream::<AccountHandlerControlMarker>()
.expect("Failed to create proxy and stream");
let server_fut = async move {
test_object
.handle_requests_from_stream(request_stream)
.await
.unwrap_or_else(|err| panic!("Fatal error handling test request: {:?}", err));
// Check that no more objects are lurking in inspect
std::mem::drop(test_object);
assert_inspect_tree!(inspector, root: {});
};
(proxy, server_fut)
}
fn request_stream_test<TestFn, Fut>(
lifetime: AccountLifetime,
pre_auth_manager: Arc<dyn pre_auth::Manager>,
fake_authenticator: Option<FakeAuthenticator>,
inspector: Arc<Inspector>,
test_fn: TestFn,
) where
TestFn: FnOnce(AccountHandlerControlProxy) -> Fut,
Fut: Future<Output = TestResult>,
{
let mut executor = fasync::Executor::new().expect("Failed to create executor");
let mut fake_context = FakeAccountHandlerContext::new();
if let Some(fake_authenticator) = fake_authenticator {
fake_context.insert_authenticator(TEST_AUTH_MECHANISM_ID, fake_authenticator);
}
let ahc_proxy = spawn_context_channel(Arc::new(fake_context));
let (proxy, server_fut) =
create_account_handler(lifetime, pre_auth_manager, ahc_proxy, inspector);
let (test_res, _server_result) =
executor.run_singlethreaded(join(test_fn(proxy), server_fut));
assert!(test_res.is_ok());
}
fn create_clean_pre_auth_manager() -> Arc<dyn pre_auth::Manager> {
Arc::new(pre_auth::InMemoryManager::create(pre_auth::State::NoEnrollments))
}
#[test]
fn test_get_account_before_initialization() {
let location = TempLocation::new();
request_stream_test(
location.to_persistent_lifetime(),
create_clean_pre_auth_manager(),
None,
Arc::new(Inspector::new()),
|proxy| async move {
let (_, account_server_end) = create_endpoints().unwrap();
let (acp_client_end, _) = create_endpoints().unwrap();
assert_eq!(
proxy.get_account(acp_client_end, account_server_end).await?,
Err(ApiError::FailedPrecondition)
);
Ok(())
},
);
}
#[test]
fn test_get_account_when_locked() {
let location = TempLocation::new();
request_stream_test(
location.to_persistent_lifetime(),
create_clean_pre_auth_manager(),
None,
Arc::new(Inspector::new()),
|proxy| async move {
let (_, account_server_end) = create_endpoints().unwrap();
let (acp_client_end, _) = create_endpoints().unwrap();
proxy.preload().await??;
assert_eq!(
proxy.get_account(acp_client_end, account_server_end).await?,
Err(ApiError::FailedPrecondition)
);
Ok(())
},
);
}
#[test]
fn test_double_initialize() {
let location = TempLocation::new();
request_stream_test(
location.to_persistent_lifetime(),
create_clean_pre_auth_manager(),
None,
Arc::new(Inspector::new()),
|proxy| async move {
proxy.create_account(None).await??;
assert_eq!(proxy.create_account(None).await?, Err(ApiError::FailedPrecondition));
Ok(())
},
);
}
#[test]
fn test_create_get_and_lock_account() {
let location = TempLocation::new();
let inspector = Arc::new(Inspector::new());
request_stream_test(
location.to_persistent_lifetime(),
create_clean_pre_auth_manager(),
None,
Arc::clone(&inspector),
|account_handler_proxy| {
async move {
account_handler_proxy.create_account(None).await??;
assert_inspect_tree!(inspector, root: {
account_handler: contains {
account: contains {
open_client_channels: 0u64,
},
}
});
let (account_client_end, account_server_end) = create_endpoints().unwrap();
let (acp_client_end, _) = create_endpoints().unwrap();
account_handler_proxy.get_account(acp_client_end, account_server_end).await??;
assert_inspect_tree!(inspector, root: {
account_handler: contains {
lifecycle: "initialized",
account: contains {
open_client_channels: 1u64,
},
}
});
// The account channel should now be usable.
let account_proxy = account_client_end.into_proxy().unwrap();
assert_eq!(
account_proxy.get_auth_state(&mut DEFAULT_SCENARIO.clone()).await?,
Err(ApiError::UnsupportedOperation)
);
// Lock the account and check that channels are closed
account_handler_proxy.lock_account().await??;
assert_inspect_tree!(inspector, root: {
account_handler: contains {
lifecycle: "locked",
}
});
assert!(account_proxy
.get_auth_state(&mut DEFAULT_SCENARIO.clone())
.await
.is_err());
Ok(())
}
},
);
}
#[test]
fn test_preload_and_unlock_existing_account() {
// Create an account
let location = TempLocation::new();
let pre_auth_manager = create_clean_pre_auth_manager();
let inspector = Arc::new(Inspector::new());
request_stream_test(
location.to_persistent_lifetime(),
Arc::clone(&pre_auth_manager),
None,
Arc::clone(&inspector),
|proxy| async move {
proxy.create_account(None).await??;
assert_inspect_tree!(inspector, root: {
account_handler: contains {
lifecycle: "initialized",
}
});
Ok(())
},
);
// Ensure the account is persisted by unlocking it
let inspector = Arc::new(Inspector::new());
request_stream_test(
location.to_persistent_lifetime(),
pre_auth_manager,
None,
Arc::clone(&inspector),
|proxy| async move {
proxy.preload().await??;
assert_inspect_tree!(inspector, root: {
account_handler: contains {
lifecycle: "locked",
}
});
proxy.unlock_account().await??;
assert_inspect_tree!(inspector, root: {
account_handler: contains {
lifecycle: "initialized",
}
});
Ok(())
},
);
}
#[test]
fn test_multiple_unlocks() {
// Create an account
let location = TempLocation::new();
let pre_auth_manager = create_clean_pre_auth_manager();
let inspector = Arc::new(Inspector::new());
request_stream_test(
location.to_persistent_lifetime(),
Arc::clone(&pre_auth_manager),
None,
Arc::clone(&inspector),
|proxy| async move {
proxy.create_account(None).await??;
proxy.lock_account().await??;
proxy.unlock_account().await??;
proxy.lock_account().await??;
proxy.unlock_account().await??;
Ok(())
},
);
}
#[test]
fn test_unlock_uninitialized_account() {
let location = TempLocation::new();
let pre_auth_manager =
Arc::new(pre_auth::InMemoryManager::create(TEST_PRE_AUTH_SINGLE.clone()));
request_stream_test(
location.to_persistent_lifetime(),
pre_auth_manager,
None,
Arc::new(Inspector::new()),
|proxy| async move {
assert_eq!(proxy.unlock_account().await?, Err(ApiError::FailedPrecondition));
Ok(())
},
);
}
#[test]
fn test_create_account_with_authentication_mechanism() {
let authenticator = FakeAuthenticator::new();
authenticator
.enqueue(Expected::Enroll { resp: Err(AuthenticationApiError::InvalidAuthContext) });
authenticator.enqueue(Expected::Enroll {
resp: Ok((TEST_ENROLLMENT_DATA.clone(), MAGIC_PREKEY.clone())),
});
let location = TempLocation::new();
let inspector = Arc::new(Inspector::new());
let pre_auth_manager = create_clean_pre_auth_manager();
request_stream_test(
location.to_persistent_lifetime(),
Arc::clone(&pre_auth_manager),
Some(authenticator),
Arc::clone(&inspector),
|proxy| async move {
// Non-existing auth mechanism
assert_eq!(
proxy.create_account(Some(TEST_INVALID_AUTH_MECHANISM_ID)).await.unwrap(),
Err(ApiError::NotFound)
);
// An authentication API error was returned and converted to the appropriate error
assert_eq!(
proxy.create_account(Some(TEST_AUTH_MECHANISM_ID)).await.unwrap(),
Err(ApiError::InvalidRequest)
);
// Check that the handler is still uninitialized and that none of the failed calls
// were able to alter the pre-auth state.
assert_inspect_tree!(inspector, root: { account_handler: contains {
lifecycle: "uninitialized",
}});
assert_eq!(
pre_auth_manager.get().await.unwrap().as_ref(),
&pre_auth::State::NoEnrollments
);
// Account creation with enrollment succeeded
proxy.create_account(Some(TEST_AUTH_MECHANISM_ID)).await.unwrap().unwrap();
assert_inspect_tree!(inspector, root: { account_handler: contains {
lifecycle: "initialized",
}});
assert_eq!(pre_auth_manager.get().await.unwrap().as_ref(), &*TEST_PRE_AUTH_SINGLE);
Ok(())
},
);
}
#[test]
fn test_unlock_account_success_without_enrollment_update() {
let authenticator = FakeAuthenticator::new();
authenticator.enqueue(Expected::Enroll {
resp: Ok((TEST_ENROLLMENT_DATA.clone(), MAGIC_PREKEY.clone())),
});
authenticator.enqueue(Expected::Authenticate {
req: vec![Enrollment { id: 0, data: TEST_ENROLLMENT_DATA.clone() }],
resp: Ok(AttemptedEvent {
enrollment_id: 0,
prekey_material: MAGIC_PREKEY.clone(),
timestamp: 1337,
updated_enrollment_data: None,
}),
});
let location = TempLocation::new();
let inspector = Arc::new(Inspector::new());
let pre_auth_manager: Arc<dyn pre_auth::Manager> =
Arc::new(pre_auth::InMemoryManager::create(pre_auth::State::NoEnrollments));
request_stream_test(
location.to_persistent_lifetime(),
Arc::clone(&pre_auth_manager),
Some(authenticator),
Arc::clone(&inspector),
|proxy| async move {
proxy.create_account(Some(TEST_AUTH_MECHANISM_ID)).await.unwrap().unwrap();
proxy.lock_account().await.unwrap().unwrap();
assert_eq!(pre_auth_manager.get().await.unwrap().as_ref(), &*TEST_PRE_AUTH_SINGLE);
// Authentication passed
assert_eq!(proxy.unlock_account().await.unwrap(), Ok(()));
// Lifecycle updated
assert_inspect_tree!(inspector, root: { account_handler: contains {
lifecycle: "initialized",
}});
// Pre-auth state in unaltered
assert_eq!(pre_auth_manager.get().await.unwrap().as_ref(), &*TEST_PRE_AUTH_SINGLE);
Ok(())
},
);
}
#[test]
fn test_unlock_account_success_with_enrollment_update() {
let authenticator = FakeAuthenticator::new();
authenticator.enqueue(Expected::Enroll {
resp: Ok((TEST_ENROLLMENT_DATA.clone(), MAGIC_PREKEY.clone())),
});
authenticator.enqueue(Expected::Authenticate {
req: vec![Enrollment { id: 0, data: TEST_ENROLLMENT_DATA.clone() }],
resp: Ok(AttemptedEvent {
enrollment_id: 0,
prekey_material: MAGIC_PREKEY.clone(),
timestamp: 1337,
updated_enrollment_data: Some(TEST_UPDATED_ENROLLMENT_DATA.clone()),
}),
});
let location = TempLocation::new();
let inspector = Arc::new(Inspector::new());
let pre_auth_manager: Arc<dyn pre_auth::Manager> =
Arc::new(pre_auth::InMemoryManager::create(pre_auth::State::NoEnrollments));
request_stream_test(
location.to_persistent_lifetime(),
Arc::clone(&pre_auth_manager),
Some(authenticator),
Arc::clone(&inspector),
|proxy| async move {
proxy.create_account(Some(TEST_AUTH_MECHANISM_ID)).await.unwrap().unwrap();
proxy.lock_account().await.unwrap().unwrap();
// Authentication passed
assert_eq!(proxy.unlock_account().await.unwrap(), Ok(()));
// Lifecycle updated
assert_inspect_tree!(inspector, root: { account_handler: contains {
lifecycle: "initialized",
}});
// Pre-auth state updated
assert_eq!(pre_auth_manager.get().await.unwrap().as_ref(), &*TEST_PRE_AUTH_UPDATED);
Ok(())
},
);
}
#[test]
fn test_unlock_account_failure() {
let authenticator = FakeAuthenticator::new();
// Required for setup
authenticator.enqueue(Expected::Enroll {
resp: Ok((TEST_ENROLLMENT_DATA.clone(), MAGIC_PREKEY.clone())),
});
// Aborted authentication attempt
authenticator.enqueue(Expected::Authenticate {
req: vec![Enrollment { id: 0, data: TEST_ENROLLMENT_DATA.clone() }],
resp: Err(AuthenticationApiError::Aborted),
});
// Unexpected enrollment id
authenticator.enqueue(Expected::Authenticate {
req: vec![Enrollment { id: 0, data: TEST_ENROLLMENT_DATA.clone() }],
resp: Ok(AttemptedEvent {
enrollment_id: 1,
prekey_material: MAGIC_PREKEY.clone(),
timestamp: 1337,
updated_enrollment_data: Some(TEST_UPDATED_ENROLLMENT_DATA.clone()),
}),
});
// Updated enrollment data, but invalid pre-key material
authenticator.enqueue(Expected::Authenticate {
req: vec![Enrollment { id: 0, data: TEST_ENROLLMENT_DATA.clone() }],
resp: Ok(AttemptedEvent {
enrollment_id: 0,
prekey_material: TEST_NOT_MAGIC_PREKEY.clone(),
timestamp: 1337,
updated_enrollment_data: Some(TEST_UPDATED_ENROLLMENT_DATA.clone()),
}),
});
let location = TempLocation::new();
let inspector = Arc::new(Inspector::new());
let pre_auth_manager: Arc<dyn pre_auth::Manager> =
Arc::new(pre_auth::InMemoryManager::create(pre_auth::State::NoEnrollments));
request_stream_test(
location.to_persistent_lifetime(),
Arc::clone(&pre_auth_manager),
Some(authenticator),
Arc::clone(&inspector),
|proxy| async move {
proxy.create_account(Some(TEST_AUTH_MECHANISM_ID)).await.unwrap().unwrap();
proxy.lock_account().await.unwrap().unwrap();
// Aborted authentication attempt
assert_eq!(proxy.unlock_account().await.unwrap(), Err(ApiError::Unknown));
// Unexpected enrollment id
assert_eq!(proxy.unlock_account().await.unwrap(), Err(ApiError::Internal));
// Unexpected pre-key material
assert_eq!(
proxy.unlock_account().await.unwrap(),
Err(ApiError::FailedAuthentication)
);
// Account handler is still locked
assert_inspect_tree!(inspector, root: { account_handler: contains {
lifecycle: "locked",
}});
// Extra sanity check that account is not retrievable
let (_, account_server_end) = create_endpoints().unwrap();
let (acp_client_end, _) = create_endpoints().unwrap();
assert_eq!(
proxy.get_account(acp_client_end, account_server_end).await.unwrap(),
Err(ApiError::FailedPrecondition)
);
// Pre-auth state unaltered
assert_eq!(pre_auth_manager.get().await.unwrap().as_ref(), &*TEST_PRE_AUTH_SINGLE);
Ok(())
},
);
}
#[test]
fn test_global_id_hashes_unique() {
let location = TempLocation::new();
request_stream_test(
location.to_persistent_lifetime(),
create_clean_pre_auth_manager(),
None,
Arc::new(Inspector::new()),
|proxy| {
async move {
proxy.create_account(None).await??;
// Different given different salts
let mut salt_1 = [0u8; 32];
let hash_1 = proxy.get_global_id_hash(&mut salt_1).await??;
let mut salt_2 = [37u8; 32];
let hash_2 = proxy.get_global_id_hash(&mut salt_2).await??;
assert_ne!(hash_1[..], hash_2[..]);
Ok(())
}
},
);
}
#[test]
fn test_account_transfer_states() {
let location = TempLocation::new();
let inspector = Arc::new(Inspector::new());
request_stream_test(
location.to_persistent_lifetime(),
create_clean_pre_auth_manager(),
None,
Arc::clone(&inspector),
|proxy| {
async move {
// TODO(satsukiu): add more meaningful tests once there's some functionality
// to test
assert_inspect_tree!(inspector, root: {
account_handler: {
local_account_id: TEST_ACCOUNT_ID_UINT,
lifecycle: "uninitialized",
}
});
proxy.prepare_for_account_transfer().await??;
assert_inspect_tree!(inspector, root: {
account_handler: {
local_account_id: TEST_ACCOUNT_ID_UINT,
lifecycle: "pendingTransfer",
}
});
proxy.perform_account_transfer(&[]).await??;
assert_inspect_tree!(inspector, root: {
account_handler: {
local_account_id: TEST_ACCOUNT_ID_UINT,
lifecycle: "transferred",
}
});
Ok(())
}
},
);
}
#[test]
fn test_prepare_for_account_transfer_invalid_states() {
// Handler in `PendingTransfer` state
request_stream_test(
AccountLifetime::Ephemeral,
create_clean_pre_auth_manager(),
None,
Arc::new(Inspector::new()),
|proxy| async move {
proxy.prepare_for_account_transfer().await??;
assert_eq!(
proxy.prepare_for_account_transfer().await?,
Err(ApiError::FailedPrecondition)
);
Ok(())
},
);
// Handler in `Transferred` state
request_stream_test(
AccountLifetime::Ephemeral,
create_clean_pre_auth_manager(),
None,
Arc::new(Inspector::new()),
|proxy| async move {
proxy.prepare_for_account_transfer().await??;
proxy.perform_account_transfer(&[]).await??;
assert_eq!(
proxy.prepare_for_account_transfer().await?,
Err(ApiError::FailedPrecondition)
);
Ok(())
},
);
// Handler in `Initialized` state
request_stream_test(
AccountLifetime::Ephemeral,
create_clean_pre_auth_manager(),
None,
Arc::new(Inspector::new()),
|proxy| async move {
proxy.create_account(None).await??;
assert_eq!(
proxy.prepare_for_account_transfer().await?,
Err(ApiError::FailedPrecondition)
);
Ok(())
},
);
// Handler in `Locked` state
let location = TempLocation::new();
request_stream_test(
location.to_persistent_lifetime(),
create_clean_pre_auth_manager(),
None,
Arc::new(Inspector::new()),
|proxy| async move {
proxy.preload().await??;
proxy.lock_account().await??;
assert_eq!(
proxy.prepare_for_account_transfer().await?,
Err(ApiError::FailedPrecondition)
);
Ok(())
},
);
}
#[test]
fn test_perform_account_transfer_invalid_states() {
// Handler in `Uninitialized` state
request_stream_test(
AccountLifetime::Ephemeral,
create_clean_pre_auth_manager(),
None,
Arc::new(Inspector::new()),
|proxy| async move {
assert_eq!(
proxy.perform_account_transfer(&[]).await?,
Err(ApiError::FailedPrecondition)
);
Ok(())
},
);
// Handler in `Transferred` state
request_stream_test(
AccountLifetime::Ephemeral,
create_clean_pre_auth_manager(),
None,
Arc::new(Inspector::new()),
|proxy| async move {
proxy.prepare_for_account_transfer().await??;
proxy.perform_account_transfer(&[]).await??;
assert_eq!(
proxy.perform_account_transfer(&[]).await?,
Err(ApiError::FailedPrecondition)
);
Ok(())
},
);
// Handler in `Initialized` state
request_stream_test(
AccountLifetime::Ephemeral,
create_clean_pre_auth_manager(),
None,
Arc::new(Inspector::new()),
|proxy| async move {
proxy.create_account(None).await??;
assert_eq!(
proxy.perform_account_transfer(&[]).await?,
Err(ApiError::FailedPrecondition)
);
Ok(())
},
);
// Handler in `Locked` state
let location = TempLocation::new();
request_stream_test(
location.to_persistent_lifetime(),
create_clean_pre_auth_manager(),
None,
Arc::new(Inspector::new()),
|proxy| async move {
proxy.preload().await??;
proxy.lock_account().await??;
assert_eq!(
proxy.perform_account_transfer(&[]).await?,
Err(ApiError::FailedPrecondition)
);
Ok(())
},
);
}
#[test]
fn test_finalize_account_transfer_unimplemented() {
request_stream_test(
AccountLifetime::Ephemeral,
create_clean_pre_auth_manager(),
None,
Arc::new(Inspector::new()),
|proxy| async move {
proxy.prepare_for_account_transfer().await??;
proxy.perform_account_transfer(&[]).await??;
assert_eq!(
proxy.finalize_account_transfer().await?,
Err(ApiError::UnsupportedOperation)
);
Ok(())
},
);
}
#[test]
fn test_remove_account() {
let location = TempLocation::new();
let inspector = Arc::new(Inspector::new());
request_stream_test(
location.to_persistent_lifetime(),
create_clean_pre_auth_manager(),
None,
Arc::clone(&inspector),
|proxy| {
async move {
assert_inspect_tree!(inspector, root: {
account_handler: {
local_account_id: TEST_ACCOUNT_ID_UINT,
lifecycle: "uninitialized",
}
});
proxy.create_account(None).await??;
assert_inspect_tree!(inspector, root: {
account_handler: {
local_account_id: TEST_ACCOUNT_ID_UINT,
lifecycle: "initialized",
account: {
open_client_channels: 0u64,
},
default_persona: {
local_persona_id: AnyProperty,
open_client_channels: 0u64,
},
}
});
// Keep an open channel to an account.
let (account_client_end, account_server_end) = create_endpoints().unwrap();
let (acp_client_end, _) = create_endpoints().unwrap();
proxy.get_account(acp_client_end, account_server_end).await??;
let account_proxy = account_client_end.into_proxy().unwrap();
// Simple check that non-force account removal returns error due to not
// implemented.
assert_eq!(
proxy.remove_account(FORCE_REMOVE_OFF).await?,
Err(ApiError::UnsupportedOperation)
);
// Make sure remove_account() can make progress with an open channel.
proxy.remove_account(FORCE_REMOVE_ON).await??;
assert_inspect_tree!(inspector, root: {
account_handler: {
local_account_id: TEST_ACCOUNT_ID_UINT,
lifecycle: "finished",
}
});
// Make sure that the channel is in fact closed.
assert!(account_proxy
.get_auth_state(&mut DEFAULT_SCENARIO.clone())
.await
.is_err());
// We cannot remove twice.
assert_eq!(
proxy.remove_account(FORCE_REMOVE_ON).await?,
Err(ApiError::FailedPrecondition)
);
Ok(())
}
},
);
}
#[test]
fn test_remove_locked_account() {
let location = TempLocation::new();
request_stream_test(
location.to_persistent_lifetime(),
create_clean_pre_auth_manager(),
None,
Arc::new(Inspector::new()),
|proxy| async move {
proxy.preload().await??; // Preloading a non-existing account will succeed, for now
assert_eq!(proxy.remove_account(false).await?, Err(ApiError::UnsupportedOperation));
Ok(())
},
);
}
#[test]
fn test_remove_account_before_initialization() {
let location = TempLocation::new();
request_stream_test(
location.to_persistent_lifetime(),
create_clean_pre_auth_manager(),
None,
Arc::new(Inspector::new()),
|proxy| async move {
assert_eq!(
proxy.remove_account(FORCE_REMOVE_ON).await?,
Err(ApiError::FailedPrecondition)
);
Ok(())
},
);
}
#[test]
fn test_terminate() {
let location = TempLocation::new();
request_stream_test(
location.to_persistent_lifetime(),
create_clean_pre_auth_manager(),
None,
Arc::new(Inspector::new()),
|proxy| {
async move {
proxy.create_account(None).await??;
// Keep an open channel to an account.
let (account_client_end, account_server_end) = create_endpoints().unwrap();
let (acp_client_end, _) = create_endpoints().unwrap();
proxy.get_account(acp_client_end, account_server_end).await??;
let account_proxy = account_client_end.into_proxy().unwrap();
// Terminate the handler
proxy.terminate()?;
// Check that further operations fail
assert!(proxy.remove_account(FORCE_REMOVE_ON).await.is_err());
assert!(proxy.terminate().is_err());
// Make sure that the channel closed too.
assert!(account_proxy
.get_auth_state(&mut DEFAULT_SCENARIO.clone())
.await
.is_err());
Ok(())
}
},
);
}
#[test]
fn test_terminate_locked_account() {
let location = TempLocation::new();
request_stream_test(
location.to_persistent_lifetime(),
create_clean_pre_auth_manager(),
None,
Arc::new(Inspector::new()),
|proxy| {
async move {
proxy.create_account(None).await??;
proxy.lock_account().await??;
proxy.terminate()?;
// Check that further operations fail
assert!(proxy.unlock_account().await.is_err());
assert!(proxy.terminate().is_err());
Ok(())
}
},
);
}
#[test]
fn test_load_non_existing_account() {
let location = TempLocation::new();
request_stream_test(
location.to_persistent_lifetime(),
create_clean_pre_auth_manager(),
None,
Arc::new(Inspector::new()),
|proxy| async move {
proxy.preload().await??; // Preloading a non-existing account will succeed, for now
assert_eq!(proxy.unlock_account().await?, Err(ApiError::NotFound));
Ok(())
},
);
}
#[test]
fn test_create_account_ephemeral_with_auth_mechanism() {
request_stream_test(
AccountLifetime::Ephemeral,
create_clean_pre_auth_manager(),
None,
Arc::new(Inspector::new()),
|proxy| async move {
assert_eq!(
proxy.create_account(Some(TEST_AUTH_MECHANISM_ID)).await?,
Err(ApiError::InvalidRequest)
);
Ok(())
},
);
}
#[test]
fn test_lock_request_ephemeral_account_failure() {
let inspector = Arc::new(Inspector::new());
request_stream_test(
AccountLifetime::Ephemeral,
create_clean_pre_auth_manager(),
None,
Arc::clone(&inspector),
|account_handler_proxy| async move {
account_handler_proxy.create_account(None).await??;
// Get a proxy to the Account interface
let (account_client_end, account_server_end) = create_endpoints().unwrap();
let (acp_client_end, _) = create_endpoints().unwrap();
account_handler_proxy.get_account(acp_client_end, account_server_end).await??;
let account_proxy = account_client_end.into_proxy().unwrap();
// Send the lock request
assert_eq!(account_proxy.lock().await?, Err(ApiError::FailedPrecondition));
// Wait for a potentitially faulty lock request to propagate
fasync::Timer::new(LOCK_REQUEST_DURATION.clone().after_now()).await;
// The channel is still usable
assert!(account_proxy.get_persona_ids().await.is_ok());
// The state remains initialized
assert_inspect_tree!(inspector, root: {
account_handler: contains {
lifecycle: "initialized",
}
});
Ok(())
},
);
}
#[test]
fn test_lock_request_persistent_account_without_auth_mechanism() {
let location = TempLocation::new();
let inspector = Arc::new(Inspector::new());
request_stream_test(
location.to_persistent_lifetime(),
create_clean_pre_auth_manager(),
None,
Arc::clone(&inspector),
|account_handler_proxy| async move {
account_handler_proxy.create_account(None).await??;
// Get a proxy to the Account interface
let (account_client_end, account_server_end) = create_endpoints().unwrap();
let (acp_client_end, _) = create_endpoints().unwrap();
account_handler_proxy.get_account(acp_client_end, account_server_end).await??;
let account_proxy = account_client_end.into_proxy().unwrap();
// Send the lock request
assert_eq!(account_proxy.lock().await?, Err(ApiError::FailedPrecondition));
Ok(())
},
);
}
#[test]
fn test_lock_request_persistent_account_with_auth_mechanism() {
let authenticator = FakeAuthenticator::new();
authenticator.enqueue(Expected::Enroll {
resp: Ok((TEST_ENROLLMENT_DATA.clone(), MAGIC_PREKEY.clone())),
});
let location = TempLocation::new();
let inspector = Arc::new(Inspector::new());
request_stream_test(
location.to_persistent_lifetime(),
create_clean_pre_auth_manager(),
Some(authenticator),
Arc::clone(&inspector),
|account_handler_proxy| async move {
account_handler_proxy
.create_account(Some(TEST_AUTH_MECHANISM_ID.clone()))
.await??;
// Get a proxy to the Account interface
let (account_client_end, account_server_end) = create_endpoints().unwrap();
let (acp_client_end, _) = create_endpoints().unwrap();
account_handler_proxy.get_account(acp_client_end, account_server_end).await??;
let account_proxy = account_client_end.into_proxy().unwrap();
// Send the lock request
assert_eq!(account_proxy.lock().await?, Ok(()));
// Wait for it to propagate
fasync::Timer::new(LOCK_REQUEST_DURATION.clone().after_now()).await;
// Channel is unusable
assert!(account_proxy.get_persona_ids().await.is_err());
assert_inspect_tree!(inspector, root: {
account_handler: contains {
lifecycle: "locked",
}
});
// Extra check to ensure the account cannot be retrieved
let (_, account_server_end) = create_endpoints().unwrap();
let (acp_client_end, _) = create_endpoints().unwrap();
assert_eq!(
account_handler_proxy.get_account(acp_client_end, account_server_end).await?,
Err(ApiError::FailedPrecondition)
);
Ok(())
},
);
}
}