blob: dcd8f8844a91ea5a3c8a9c7921464465398136cc [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::auth_provider_supplier::AuthProviderSupplier;
use crate::common::AccountLifetime;
use crate::inspect;
use crate::lock_request;
use crate::persona::{Persona, PersonaContext};
use crate::stored_account::StoredAccount;
use crate::TokenManager;
use account_common::{
AccountManagerError, FidlLocalPersonaId, GlobalAccountId, LocalPersonaId, ResultExt,
};
use anyhow::Error;
use fidl::endpoints::{ClientEnd, ServerEnd};
use fidl_fuchsia_auth::{AuthenticationContextProviderProxy, ServiceProviderAccount};
use fidl_fuchsia_identity_account::{
AccountRequest, AccountRequestStream, AuthChangeGranularity, AuthListenerMarker, AuthState,
Error as ApiError, Lifetime, PersonaMarker, Scenario, MAX_ID_SIZE,
};
use fidl_fuchsia_identity_internal::AccountHandlerContextProxy;
use fuchsia_inspect::{Node, NumericProperty};
use futures::prelude::*;
use identity_common::{cancel_or, TaskGroup, TaskGroupCancel};
use identity_key_manager::KeyManager;
use log::{error, info, warn};
use rand::Rng;
use scopeguard;
use std::fs;
use std::sync::Arc;
/// The file name to use for a token manager database. The location is supplied
/// by `AccountHandlerContext.GetAccountPath()`
const TOKEN_DB: &str = "tokens.json";
const GLOBAL_ACCOUNT_ID_SIZE: usize = MAX_ID_SIZE as usize;
/// The context that a particular request to an Account should be executed in, capturing
/// information that was supplied upon creation of the channel.
pub struct AccountContext {
/// An `AuthenticationContextProviderProxy` capable of generating new `AuthenticationUiContext`
/// channels.
pub auth_ui_context_provider: AuthenticationContextProviderProxy,
}
/// Information about the Account that this AccountHandler instance is responsible for.
///
/// This state is only available once the Handler has been initialized to a particular account via
/// the AccountHandlerControl channel.
pub struct Account {
/// A global identifier for this account.
global_id: GlobalAccountId,
/// Lifetime for this account.
lifetime: Arc<AccountLifetime>,
/// The default persona for this account.
default_persona: Arc<Persona>,
/// Collection of tasks that are using this instance.
task_group: TaskGroup,
/// A Sender of a lock request.
lock_request_sender: lock_request::Sender,
/// Helper for outputting account information via fuchsia_inspect.
inspect: inspect::Account,
// TODO(jsankey): Once the system and API surface can support more than a single persona, add
// additional state here to store these personae. This will most likely be a hashmap from
// LocalPersonaId to Persona struct, and changing default_persona from a struct to an ID. We
// will also need to store Arc<TokenManager> at the account level.
}
impl Account {
/// A fixed string returned as the name of all accounts until account names are fully
/// implemented.
const DEFAULT_ACCOUNT_NAME: &'static str = "Unnamed account";
/// Manually construct an account object, shouldn't normally be called directly.
async fn new(
persona_id: LocalPersonaId,
global_account_id: GlobalAccountId,
lifetime: AccountLifetime,
context_proxy: AccountHandlerContextProxy,
lock_request_sender: lock_request::Sender,
inspect_parent: &Node,
) -> Result<Account, AccountManagerError> {
let task_group = TaskGroup::new();
let token_manager_task_group = task_group
.create_child()
.await
.map_err(|_| AccountManagerError::new(ApiError::RemovalInProgress))?;
let key_manager_task_group = task_group
.create_child()
.await
.map_err(|_| AccountManagerError::new(ApiError::RemovalInProgress))?;
let default_persona_task_group = task_group
.create_child()
.await
.map_err(|_| AccountManagerError::new(ApiError::RemovalInProgress))?;
let auth_provider_supplier = AuthProviderSupplier::new(context_proxy);
let token_manager = Arc::new(match &lifetime {
AccountLifetime::Ephemeral => {
TokenManager::new_in_memory(auth_provider_supplier, token_manager_task_group)
}
AccountLifetime::Persistent { account_dir } => {
let token_db_path = account_dir.join(TOKEN_DB);
TokenManager::new(&token_db_path, auth_provider_supplier, token_manager_task_group)
.account_manager_error(ApiError::Unknown)?
}
});
let key_manager = Arc::new(KeyManager::new(key_manager_task_group));
let lifetime = Arc::new(lifetime);
let account_inspect = inspect::Account::new(inspect_parent);
Ok(Self {
global_id: global_account_id,
lifetime: Arc::clone(&lifetime),
default_persona: Arc::new(Persona::new(
persona_id,
lifetime,
token_manager,
key_manager,
default_persona_task_group,
inspect_parent,
)),
task_group,
lock_request_sender,
inspect: account_inspect,
})
}
/// Creates a new Fuchsia account and, if it is persistent, stores it on disk.
pub async fn create(
lifetime: AccountLifetime,
context_proxy: AccountHandlerContextProxy,
lock_request_sender: lock_request::Sender,
inspect_parent: &Node,
) -> Result<Account, AccountManagerError> {
let global_account_id = Self::generate_global_account_id()?;
let local_persona_id = LocalPersonaId::new(rand::random::<u64>());
if let AccountLifetime::Persistent { ref account_dir } = lifetime {
if StoredAccount::path(account_dir).exists() {
info!("Attempting to create account twice");
return Err(AccountManagerError::new(ApiError::Internal));
}
let stored_account =
StoredAccount::new(local_persona_id.clone(), global_account_id.clone());
stored_account.save(account_dir)?;
}
Self::new(
local_persona_id,
global_account_id,
lifetime,
context_proxy,
lock_request_sender,
inspect_parent,
)
.await
}
/// Loads an existing Fuchsia account from disk.
pub async fn load(
lifetime: AccountLifetime,
context_proxy: AccountHandlerContextProxy,
lock_request_sender: lock_request::Sender,
inspect_parent: &Node,
) -> Result<Account, AccountManagerError> {
let account_dir = match lifetime {
AccountLifetime::Persistent { ref account_dir } => account_dir,
AccountLifetime::Ephemeral => {
warn!(concat!(
"Attempting to load an ephemeral account from disk. This is not a ",
"supported operation."
));
return Err(AccountManagerError::new(ApiError::Internal));
}
};
let stored_account = StoredAccount::load(account_dir)?;
let local_persona_id = stored_account.get_default_persona_id().clone();
let global_account_id = stored_account.get_global_account_id().clone();
Self::new(
local_persona_id,
global_account_id,
lifetime,
context_proxy,
lock_request_sender,
inspect_parent,
)
.await
}
/// Removes the account from disk or returns the account and the error.
pub fn remove(self) -> Result<(), (Self, AccountManagerError)> {
self.remove_inner().map_err(|err| (self, err))
}
/// Removes the account from disk.
fn remove_inner(&self) -> Result<(), AccountManagerError> {
match self.lifetime.as_ref() {
AccountLifetime::Ephemeral => Ok(()),
AccountLifetime::Persistent { account_dir } => {
let token_db_path = &account_dir.join(TOKEN_DB);
if token_db_path.exists() {
fs::remove_file(token_db_path).map_err(|err| {
warn!("Failed to delete token db: {:?}", err);
AccountManagerError::new(ApiError::Resource).with_cause(err)
})?;
}
let to_remove = StoredAccount::path(&account_dir.clone());
fs::remove_file(to_remove).map_err(|err| {
warn!("Failed to delete account doc: {:?}", err);
AccountManagerError::new(ApiError::Resource).with_cause(err)
})
}
}
}
/// Returns a task group which can be used to spawn and cancel tasks that use this instance.
pub fn task_group(&self) -> &TaskGroup {
&self.task_group
}
/// A global identifier for this account
pub fn global_id(&self) -> &GlobalAccountId {
&self.global_id
}
/// Asynchronously handles the supplied stream of `AccountRequest` messages.
pub async fn handle_requests_from_stream<'a>(
&'a self,
context: &'a AccountContext,
mut stream: AccountRequestStream,
cancel: TaskGroupCancel,
) -> Result<(), Error> {
self.inspect.open_client_channels.add(1);
scopeguard::defer!(self.inspect.open_client_channels.subtract(1));
while let Some(result) = cancel_or(&cancel, stream.try_next()).await {
if let Some(request) = result? {
self.handle_request(context, request).await?;
} else {
break;
}
}
Ok(())
}
/// Dispatches an `AccountRequest` message to the appropriate handler method
/// based on its type.
pub async fn handle_request<'a>(
&'a self,
context: &'a AccountContext,
req: AccountRequest,
) -> Result<(), fidl::Error> {
match req {
AccountRequest::GetAccountName { responder } => {
let response = self.get_account_name();
responder.send(&response)?;
}
AccountRequest::GetLifetime { responder } => {
let response = self.get_lifetime();
responder.send(response)?;
}
AccountRequest::GetAuthState { scenario, responder } => {
let mut response = self.get_auth_state(scenario);
responder.send(&mut response)?;
}
AccountRequest::RegisterAuthListener {
scenario,
listener,
initial_state,
granularity,
responder,
} => {
let mut response =
self.register_auth_listener(scenario, listener, initial_state, granularity);
responder.send(&mut response)?;
}
AccountRequest::GetPersonaIds { responder } => {
let response = self.get_persona_ids();
responder.send(&response)?;
}
AccountRequest::GetDefaultPersona { persona, responder } => {
let mut response = self.get_default_persona(context, persona).await;
responder.send(&mut response)?;
}
AccountRequest::GetPersona { id, persona, responder } => {
let mut response = self.get_persona(context, id.into(), persona).await;
responder.send(&mut response)?;
}
AccountRequest::GetRecoveryAccount { responder } => {
let mut response =
self.get_recovery_account().map(|option| option.map(|value| Box::new(value)));
responder.send(&mut response)?;
}
AccountRequest::SetRecoveryAccount { account, responder } => {
let mut response = self.set_recovery_account(account);
responder.send(&mut response)?;
}
AccountRequest::GetAuthMechanismEnrollments { responder, .. } => {
responder.send(&mut Err(ApiError::UnsupportedOperation))?;
}
AccountRequest::CreateAuthMechanismEnrollment { responder, .. } => {
responder.send(&mut Err(ApiError::UnsupportedOperation))?;
}
AccountRequest::RemoveAuthMechanismEnrollment { responder, .. } => {
responder.send(&mut Err(ApiError::UnsupportedOperation))?;
}
AccountRequest::Lock { responder } => {
let mut response = self.lock().await;
responder.send(&mut response)?;
}
AccountRequest::GetDataDirectory { responder, .. } => {
responder.send(&mut Err(ApiError::UnsupportedOperation))?;
}
}
Ok(())
}
fn generate_global_account_id() -> Result<GlobalAccountId, AccountManagerError> {
let mut rng = rand::thread_rng();
let mut bytes = vec![0u8; GLOBAL_ACCOUNT_ID_SIZE];
rng.try_fill(bytes.as_mut_slice()).account_manager_error(ApiError::Resource)?;
Ok(GlobalAccountId::new(bytes))
}
fn get_lifetime(&self) -> Lifetime {
Lifetime::from(self.lifetime.as_ref())
}
fn get_account_name(&self) -> String {
// TODO(dnordstrom, jsankey): Implement this method, initially by populating the name from
// an associated service provider account profile name or a randomly assigned string.
Self::DEFAULT_ACCOUNT_NAME.to_string()
}
fn get_auth_state(&self, _scenario: Scenario) -> Result<AuthState, ApiError> {
// TODO(jsankey): Return real authentication state once authenticators exist to create it.
Err(ApiError::UnsupportedOperation)
}
fn register_auth_listener(
&self,
_scenario: Scenario,
_listener: ClientEnd<AuthListenerMarker>,
_initial_state: bool,
_granularity: AuthChangeGranularity,
) -> Result<(), ApiError> {
// TODO(jsankey): Implement this method.
warn!("RegisterAuthListener not yet implemented");
Err(ApiError::UnsupportedOperation)
}
fn get_persona_ids(&self) -> Vec<FidlLocalPersonaId> {
vec![self.default_persona.id().clone().into()]
}
async fn get_default_persona<'a>(
&'a self,
context: &'a AccountContext,
persona_server_end: ServerEnd<PersonaMarker>,
) -> Result<FidlLocalPersonaId, ApiError> {
let persona_clone = Arc::clone(&self.default_persona);
let persona_context =
PersonaContext { auth_ui_context_provider: context.auth_ui_context_provider.clone() };
let stream = persona_server_end.into_stream().map_err(|err| {
error!("Error opening Persona channel: {:?}", err);
ApiError::Resource
})?;
self.default_persona
.task_group()
.spawn(|cancel| async move {
persona_clone
.handle_requests_from_stream(&persona_context, stream, cancel)
.await
.unwrap_or_else(|e| error!("Error handling Persona channel: {:?}", e))
})
.await
.map_err(|_| ApiError::RemovalInProgress)?;
Ok(self.default_persona.id().clone().into())
}
async fn get_persona<'a>(
&'a self,
context: &'a AccountContext,
id: LocalPersonaId,
persona_server_end: ServerEnd<PersonaMarker>,
) -> Result<(), ApiError> {
if &id == self.default_persona.id() {
self.get_default_persona(context, persona_server_end).await.map(|_| ())
} else {
warn!("Requested persona does not exist {:?}", id);
Err(ApiError::NotFound)
}
}
fn get_recovery_account(&self) -> Result<Option<ServiceProviderAccount>, ApiError> {
// TODO(jsankey): Implement this method.
warn!("GetRecoveryAccount not yet implemented");
Err(ApiError::Internal)
}
fn set_recovery_account(&self, _account: ServiceProviderAccount) -> Result<(), ApiError> {
// TODO(jsankey): Implement this method.
warn!("SetRecoveryAccount not yet implemented");
Err(ApiError::Internal)
}
async fn lock(&self) -> Result<(), ApiError> {
match self.lock_request_sender.send().await {
Err(lock_request::SendError::NotSupported) => {
info!("Account lock failure: unsupported account type");
Err(ApiError::FailedPrecondition)
}
Err(lock_request::SendError::UnattendedReceiver) => {
warn!("Account lock failure: unattended listener");
Err(ApiError::Internal)
}
Err(lock_request::SendError::AlreadySent) => {
info!("Received account lock request while existing request in progress");
Ok(())
}
Ok(()) => Ok(()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util::*;
use fidl::endpoints::create_endpoints;
use fidl_fuchsia_auth::AuthenticationContextProviderMarker;
use fidl_fuchsia_identity_account::{AccountMarker, AccountProxy, Scenario, ThreatScenario};
use fidl_fuchsia_identity_internal::AccountHandlerContextMarker;
use fuchsia_async as fasync;
use fuchsia_inspect::Inspector;
use futures::channel::oneshot;
const TEST_SCENARIO: Scenario =
Scenario { include_test: false, threat_scenario: ThreatScenario::BasicAttacker };
const TEST_AUTH_MECHANISM_ID: &str = "<AUTH MECHANISM ID>";
const TEST_ENROLLMENT_ID: u64 = 1337;
/// Type to hold the common state require during construction of test objects and execution
/// of a test, including an async executor and a temporary location in the filesystem.
struct Test {
location: TempLocation,
}
impl Test {
fn new() -> Test {
Test { location: TempLocation::new() }
}
async fn create_persistent_account(&self) -> Result<Account, AccountManagerError> {
let inspector = Inspector::new();
let (account_handler_context_client_end, _) =
create_endpoints::<AccountHandlerContextMarker>().unwrap();
let account_dir = self.location.path.clone();
Account::create(
AccountLifetime::Persistent { account_dir },
account_handler_context_client_end.into_proxy().unwrap(),
lock_request::Sender::NotSupported,
&inspector.root(),
)
.await
}
async fn create_ephemeral_account(&self) -> Result<Account, AccountManagerError> {
let inspector = Inspector::new();
let (account_handler_context_client_end, _) =
create_endpoints::<AccountHandlerContextMarker>().unwrap();
Account::create(
AccountLifetime::Ephemeral,
account_handler_context_client_end.into_proxy().unwrap(),
lock_request::Sender::NotSupported,
&inspector.root(),
)
.await
}
async fn load_account(&self) -> Result<Account, AccountManagerError> {
let inspector = Inspector::new();
let (account_handler_context_client_end, _) =
create_endpoints::<AccountHandlerContextMarker>().unwrap();
Account::load(
AccountLifetime::Persistent { account_dir: self.location.path.clone() },
account_handler_context_client_end.into_proxy().unwrap(),
lock_request::Sender::NotSupported,
&inspector.root(),
)
.await
}
async fn create_persistent_account_with_lock_request(
&self,
) -> Result<(Account, oneshot::Receiver<()>), AccountManagerError> {
let inspector = Inspector::new();
let (account_handler_context_client_end, _) =
create_endpoints::<AccountHandlerContextMarker>().unwrap();
let account_dir = self.location.path.clone();
let (sender, receiver) = lock_request::channel();
let account = Account::create(
AccountLifetime::Persistent { account_dir },
account_handler_context_client_end.into_proxy().unwrap(),
sender,
&inspector.root(),
)
.await?;
Ok((account, receiver))
}
async fn run<TestFn, Fut>(&mut self, test_object: Account, test_fn: TestFn)
where
TestFn: FnOnce(AccountProxy) -> Fut,
Fut: Future<Output = Result<(), Error>>,
{
let (account_client_end, account_server_end) =
create_endpoints::<AccountMarker>().unwrap();
let account_proxy = account_client_end.into_proxy().unwrap();
let request_stream = account_server_end.into_stream().unwrap();
let (ui_context_provider_client_end, _) =
create_endpoints::<AuthenticationContextProviderMarker>().unwrap();
let context = AccountContext {
auth_ui_context_provider: ui_context_provider_client_end.into_proxy().unwrap(),
};
let task_group = TaskGroup::new();
task_group
.spawn(|cancel| async move {
test_object
.handle_requests_from_stream(&context, request_stream, cancel)
.await
.unwrap_or_else(|err| {
panic!("Fatal error handling test request: {:?}", err)
})
})
.await
.expect("Unable to spawn task");
test_fn(account_proxy).await.expect("Test function failed.")
}
}
#[fasync::run_until_stalled(test)]
async fn test_random_identifiers() {
let mut test = Test::new();
// Generating two accounts with the same accountID should lead to two different persona IDs
let account_1 = test.create_persistent_account().await.unwrap();
test.location = TempLocation::new();
let account_2 = test.create_persistent_account().await.unwrap();
assert_ne!(account_1.default_persona.id(), account_2.default_persona.id());
assert_ne!(account_1.global_id(), account_2.global_id());
}
#[fasync::run_until_stalled(test)]
async fn test_get_account_name() {
let mut test = Test::new();
test.run(test.create_persistent_account().await.unwrap(), |proxy| async move {
assert_eq!(proxy.get_account_name().await?, Account::DEFAULT_ACCOUNT_NAME);
Ok(())
})
.await;
}
#[fasync::run_until_stalled(test)]
async fn test_get_lifetime_ephemeral() {
let mut test = Test::new();
test.run(test.create_ephemeral_account().await.unwrap(), |proxy| async move {
assert_eq!(proxy.get_lifetime().await?, Lifetime::Ephemeral);
Ok(())
})
.await;
}
#[fasync::run_until_stalled(test)]
async fn test_get_lifetime_persistent() {
let mut test = Test::new();
test.run(test.create_persistent_account().await.unwrap(), |proxy| async move {
assert_eq!(proxy.get_lifetime().await?, Lifetime::Persistent);
Ok(())
})
.await;
}
#[fasync::run_until_stalled(test)]
async fn test_create_and_load() {
let test = Test::new();
// Persists the account on disk
let account_1 = test.create_persistent_account().await.unwrap();
// Reads from same location
let account_2 = test.load_account().await.unwrap();
// Since persona ids are random, we can check that loading worked successfully here
assert_eq!(account_1.default_persona.id(), account_2.default_persona.id());
}
#[fasync::run_until_stalled(test)]
async fn test_load_non_existing() {
let test = Test::new();
assert!(test.load_account().await.is_err()); // Reads from uninitialized location
}
/// Attempting to load an ephemeral account fails.
#[fasync::run_until_stalled(test)]
async fn test_load_ephemeral() {
let inspector = Inspector::new();
let (account_handler_context_client_end, _) =
create_endpoints::<AccountHandlerContextMarker>().unwrap();
assert!(Account::load(
AccountLifetime::Ephemeral,
account_handler_context_client_end.into_proxy().unwrap(),
lock_request::Sender::NotSupported,
&inspector.root(),
)
.await
.is_err());
}
#[fasync::run_until_stalled(test)]
async fn test_create_twice() {
let test = Test::new();
assert!(test.create_persistent_account().await.is_ok());
assert!(test.create_persistent_account().await.is_err()); // Tries to write to same dir
}
#[fasync::run_until_stalled(test)]
async fn test_get_auth_state() {
let mut test = Test::new();
test.run(test.create_persistent_account().await.unwrap(), |proxy| async move {
assert_eq!(
proxy.get_auth_state(&mut TEST_SCENARIO.clone()).await?,
Err(ApiError::UnsupportedOperation)
);
Ok(())
})
.await;
}
#[fasync::run_until_stalled(test)]
async fn test_register_auth_listener() {
let mut test = Test::new();
test.run(test.create_persistent_account().await.unwrap(), |proxy| {
async move {
let (auth_listener_client_end, _) = create_endpoints().unwrap();
assert_eq!(
proxy
.register_auth_listener(
&mut TEST_SCENARIO.clone(),
auth_listener_client_end,
true, /* include initial state */
&mut AuthChangeGranularity {
presence_changes: false,
engagement_changes: false,
summary_changes: true,
}
)
.await?,
Err(ApiError::UnsupportedOperation)
);
Ok(())
}
})
.await;
}
#[fasync::run_until_stalled(test)]
async fn test_get_persona_ids() {
let mut test = Test::new();
// Note: Persona ID is random. Record the persona_id before starting the test.
let account = test.create_persistent_account().await.unwrap();
let persona_id = &account.default_persona.id().clone();
test.run(account, |proxy| async move {
let response = proxy.get_persona_ids().await?;
assert_eq!(response.len(), 1);
assert_eq!(&LocalPersonaId::new(response[0]), persona_id);
Ok(())
})
.await;
}
#[fasync::run_until_stalled(test)]
async fn test_get_default_persona() {
let mut test = Test::new();
// Note: Persona ID is random. Record the persona_id before starting the test.
let account = test.create_persistent_account().await.unwrap();
let persona_id = &account.default_persona.id().clone();
test.run(account, |account_proxy| {
async move {
let (persona_client_end, persona_server_end) = create_endpoints().unwrap();
let response = account_proxy.get_default_persona(persona_server_end).await?;
assert_eq!(&LocalPersonaId::from(response.unwrap()), persona_id);
// The persona channel should now be usable.
let persona_proxy = persona_client_end.into_proxy().unwrap();
assert_eq!(
persona_proxy.get_auth_state(&mut TEST_SCENARIO.clone()).await?,
Err(ApiError::UnsupportedOperation)
);
assert_eq!(persona_proxy.get_lifetime().await?, Lifetime::Persistent);
Ok(())
}
})
.await;
}
/// When an ephemeral account is created, its default persona is also ephemeral.
#[fasync::run_until_stalled(test)]
async fn test_ephemeral_account_has_ephemeral_persona() {
let mut test = Test::new();
let account = test.create_ephemeral_account().await.unwrap();
test.run(account, |account_proxy| async move {
let (persona_client_end, persona_server_end) = create_endpoints().unwrap();
assert!(account_proxy.get_default_persona(persona_server_end).await?.is_ok());
let persona_proxy = persona_client_end.into_proxy().unwrap();
assert_eq!(persona_proxy.get_lifetime().await?, Lifetime::Ephemeral);
Ok(())
})
.await;
}
#[fasync::run_until_stalled(test)]
async fn test_get_persona_by_correct_id() {
let mut test = Test::new();
let account = test.create_persistent_account().await.unwrap();
let persona_id = account.default_persona.id().clone();
test.run(account, |account_proxy| {
async move {
let (persona_client_end, persona_server_end) = create_endpoints().unwrap();
assert!(account_proxy
.get_persona(FidlLocalPersonaId::from(persona_id), persona_server_end)
.await?
.is_ok());
// The persona channel should now be usable.
let persona_proxy = persona_client_end.into_proxy().unwrap();
assert_eq!(
persona_proxy.get_auth_state(&mut TEST_SCENARIO.clone()).await?,
Err(ApiError::UnsupportedOperation)
);
Ok(())
}
})
.await;
}
#[fasync::run_until_stalled(test)]
async fn test_get_persona_by_incorrect_id() {
let mut test = Test::new();
let account = test.create_persistent_account().await.unwrap();
// Note: This fixed value has a 1 - 2^64 probability of not matching the randomly chosen
// one.
let wrong_id = LocalPersonaId::new(13);
test.run(account, |proxy| async move {
let (_, persona_server_end) = create_endpoints().unwrap();
assert_eq!(
proxy.get_persona(wrong_id.into(), persona_server_end).await?,
Err(ApiError::NotFound)
);
Ok(())
})
.await;
}
#[fasync::run_until_stalled(test)]
async fn test_set_recovery_account() {
let mut test = Test::new();
let mut service_provider_account = ServiceProviderAccount {
identity_provider_domain: "google.com".to_string(),
user_profile_id: "test_obfuscated_gaia_id".to_string(),
};
test.run(test.create_persistent_account().await.unwrap(), |proxy| async move {
assert_eq!(
proxy.set_recovery_account(&mut service_provider_account).await?,
Err(ApiError::Internal)
);
Ok(())
})
.await;
}
#[fasync::run_until_stalled(test)]
async fn test_get_recovery_account() {
let mut test = Test::new();
let expectation = Err(ApiError::Internal);
test.run(test.create_persistent_account().await.unwrap(), |proxy| async move {
assert_eq!(proxy.get_recovery_account().await?, expectation);
Ok(())
})
.await;
}
#[fasync::run_until_stalled(test)]
async fn test_auth_mechanisms() {
let mut test = Test::new();
test.run(test.create_persistent_account().await.unwrap(), |proxy| async move {
assert_eq!(
proxy.get_auth_mechanism_enrollments().await?,
Err(ApiError::UnsupportedOperation)
);
assert_eq!(
proxy.create_auth_mechanism_enrollment(TEST_AUTH_MECHANISM_ID).await?,
Err(ApiError::UnsupportedOperation)
);
assert_eq!(
proxy.remove_auth_mechanism_enrollment(TEST_ENROLLMENT_ID).await?,
Err(ApiError::UnsupportedOperation)
);
Ok(())
})
.await;
}
#[fasync::run_until_stalled(test)]
async fn test_lock() {
let mut test = Test::new();
let (account, mut receiver) =
test.create_persistent_account_with_lock_request().await.unwrap();
test.run(account, |proxy| async move {
assert_eq!(receiver.try_recv(), Ok(None));
assert_eq!(proxy.lock().await?, Ok(()));
assert_eq!(receiver.await, Ok(()));
Ok(())
})
.await;
}
#[fasync::run_until_stalled(test)]
async fn test_lock_not_supported() {
let mut test = Test::new();
let account = test.create_persistent_account().await.unwrap();
test.run(account, |proxy| async move {
assert_eq!(proxy.lock().await?, Err(ApiError::FailedPrecondition));
Ok(())
})
.await;
}
#[fasync::run_until_stalled(test)]
async fn test_lock_unattended_receiver() {
let mut test = Test::new();
let (account, receiver) = test.create_persistent_account_with_lock_request().await.unwrap();
std::mem::drop(receiver);
test.run(account, |proxy| async move {
assert_eq!(proxy.lock().await?, Err(ApiError::Internal));
Ok(())
})
.await;
}
#[fasync::run_until_stalled(test)]
async fn test_lock_twice() {
let mut test = Test::new();
let (account, _receiver) =
test.create_persistent_account_with_lock_request().await.unwrap();
test.run(account, |proxy| async move {
assert_eq!(proxy.lock().await?, Ok(()));
assert_eq!(proxy.lock().await?, Ok(()));
Ok(())
})
.await;
}
}