| // 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. |
| |
| extern crate tempfile; |
| |
| use account_common::{ |
| AccountAuthState, AccountManagerError, FidlAccountAuthState, FidlLocalAccountId, |
| LocalAccountId, ResultExt, |
| }; |
| use failure::{format_err, Error}; |
| use fidl::encoding::OutOfLine; |
| use fidl::endpoints::{create_endpoints, ClientEnd, ServerEnd}; |
| use fidl_fuchsia_auth::{ |
| AppConfig, AuthState, AuthStateSummary, AuthenticationContextProviderMarker, |
| Status as AuthStatus, |
| }; |
| use fidl_fuchsia_auth::{AuthProviderConfig, UserProfileInfo}; |
| use fidl_fuchsia_auth_account::{ |
| AccountListenerMarker, AccountListenerOptions, AccountManagerRequest, |
| AccountManagerRequestStream, AccountMarker, Status, |
| }; |
| use fuchsia_component::fuchsia_single_component_package_url; |
| use fuchsia_inspect::vmo::{Inspector, Metric, Property}; |
| use futures::lock::Mutex; |
| use futures::prelude::*; |
| use lazy_static::lazy_static; |
| use log::{info, warn}; |
| use std::collections::BTreeMap; |
| use std::path::PathBuf; |
| use std::sync::Arc; |
| |
| use crate::account_event_emitter::{AccountEvent, AccountEventEmitter}; |
| use crate::account_handler_connection::AccountHandlerConnection; |
| use crate::account_handler_context::AccountHandlerContext; |
| use crate::inspect; |
| use crate::stored_account_list::{StoredAccountList, StoredAccountMetadata}; |
| |
| const SELF_URL: &str = fuchsia_single_component_package_url!("account_manager"); |
| |
| lazy_static! { |
| |
| /// The Auth scopes used for authorization during service provider-based account provisioning. |
| /// An empty vector means that the auth provider should use its default scopes. |
| static ref APP_SCOPES: Vec<String> = Vec::default(); |
| } |
| |
| /// (Temporary) A fixed AuthState that is used for all accounts until authenticators are |
| /// available. |
| const DEFAULT_AUTH_STATE: AuthState = AuthState { summary: AuthStateSummary::Unknown }; |
| |
| type AccountMap = BTreeMap<LocalAccountId, Option<Arc<AccountHandlerConnection>>>; |
| |
| /// The core component of the account system for Fuchsia. |
| /// |
| /// The AccountManager maintains the set of Fuchsia accounts that are provisioned on the device, |
| /// launches and configures AuthenticationProvider components to perform authentication via |
| /// service providers, and launches and delegates to AccountHandler component instances to |
| /// determine the detailed state and authentication for each account. |
| pub struct AccountManager { |
| /// An ordered map from the `LocalAccountId` of all accounts on the device to an |
| /// `Option` containing the `AcountHandlerConnection` used to communicate with the associated |
| /// AccountHandler if a connecton exists, or None otherwise. |
| ids_to_handlers: Mutex<AccountMap>, |
| |
| /// An object to service requests for contextual information from AccountHandlers. |
| context: Arc<AccountHandlerContext>, |
| |
| /// Contains the client ends of all AccountListeners which are subscribed to account events. |
| event_emitter: AccountEventEmitter, |
| |
| /// Root directory containing persistent resources for an AccountManager instance. |
| data_dir: PathBuf, |
| |
| /// Helper for outputting account information via fuchsia_inspect. |
| accounts_inspect: inspect::Accounts, |
| |
| /// Helper for outputting auth_provider information via fuchsia_inspect. Must be retained |
| /// to avoid dropping the static properties it contains. |
| _auth_providers_inspect: inspect::AuthProviders, |
| } |
| |
| impl AccountManager { |
| /// Constructs a new AccountManager, loading existing set of accounts from `data_dir`, and an |
| /// auth provider configuration. The directory must exist at construction. |
| pub fn new( |
| data_dir: PathBuf, |
| auth_provider_config: &[AuthProviderConfig], |
| inspector: &Inspector, |
| ) -> Result<AccountManager, Error> { |
| let context = Arc::new(AccountHandlerContext::new(auth_provider_config)); |
| |
| // Initialize the map of Account IDs to handlers with IDs read from disk and initially no |
| // handlers. Account handlers will be constructed later when needed. |
| let mut ids_to_handlers = AccountMap::new(); |
| let account_list = StoredAccountList::load(&data_dir)?; |
| for account in account_list.accounts().into_iter() { |
| ids_to_handlers.insert(account.account_id().clone(), None); |
| } |
| |
| // Initialize the structs used to output state through the inspect system. |
| let auth_providers_inspect = inspect::AuthProviders::new(inspector.root())?; |
| let auth_provider_types: Vec<String> = |
| auth_provider_config.iter().map(|apc| apc.auth_provider_type.clone()).collect(); |
| let _ = auth_providers_inspect.types.set(&auth_provider_types.join(",")); |
| let accounts_inspect = inspect::Accounts::new(inspector.root())?; |
| let _ = accounts_inspect.total.set(ids_to_handlers.len() as u64); |
| let event_emitter = AccountEventEmitter::new(inspector.root())?; |
| |
| Ok(Self { |
| ids_to_handlers: Mutex::new(ids_to_handlers), |
| context, |
| event_emitter, |
| data_dir, |
| accounts_inspect, |
| _auth_providers_inspect: auth_providers_inspect, |
| }) |
| } |
| |
| /// Asynchronously handles the supplied stream of `AccountManagerRequest` messages. |
| pub async fn handle_requests_from_stream( |
| &self, |
| mut stream: AccountManagerRequestStream, |
| ) -> Result<(), Error> { |
| while let Some(req) = await!(stream.try_next())? { |
| await!(self.handle_request(req))?; |
| } |
| Ok(()) |
| } |
| |
| /// Handles a single request to the AccountManager. |
| pub async fn handle_request(&self, req: AccountManagerRequest) -> Result<(), fidl::Error> { |
| match req { |
| AccountManagerRequest::GetAccountIds { responder } => { |
| responder.send(&mut await!(self.get_account_ids()).iter_mut()) |
| } |
| AccountManagerRequest::GetAccountAuthStates { responder } => { |
| let mut response = await!(self.get_account_auth_states()); |
| responder.send(response.0, &mut response.1.iter_mut()) |
| } |
| AccountManagerRequest::GetAccount { id, auth_context_provider, account, responder } => { |
| responder.send(await!(self.get_account(id.into(), auth_context_provider, account))) |
| } |
| AccountManagerRequest::RegisterAccountListener { listener, options, responder } => { |
| responder.send(await!(self.register_account_listener(listener, options))) |
| } |
| AccountManagerRequest::RemoveAccount { id, responder } => { |
| responder.send(await!(self.remove_account(id.into()))) |
| } |
| AccountManagerRequest::ProvisionFromAuthProvider { |
| auth_context_provider, |
| auth_provider_type, |
| responder, |
| } => { |
| let mut response = |
| await!(self |
| .provision_from_auth_provider(auth_context_provider, auth_provider_type)); |
| responder.send(response.0, response.1.as_mut().map(OutOfLine)) |
| } |
| AccountManagerRequest::ProvisionNewAccount { responder } => { |
| let mut response = await!(self.provision_new_account()); |
| responder.send(response.0, response.1.as_mut().map(OutOfLine)) |
| } |
| } |
| } |
| |
| /// Returns an `AccountHandlerConnection` for the specified `LocalAccountId`, either by |
| /// returning the existing entry from the map or by creating and adding a new entry to the map. |
| async fn get_handler_for_existing_account<'a>( |
| &'a self, |
| account_id: &'a LocalAccountId, |
| ) -> Result<Arc<AccountHandlerConnection>, AccountManagerError> { |
| let mut ids_to_handlers_lock = await!(self.ids_to_handlers.lock()); |
| match ids_to_handlers_lock.get(account_id) { |
| None => return Err(AccountManagerError::new(Status::NotFound)), |
| Some(Some(existing_handler)) => return Ok(Arc::clone(existing_handler)), |
| Some(None) => { /* ID is valid but a handler doesn't exist yet */ } |
| } |
| |
| let new_handler = Arc::new(await!(AccountHandlerConnection::load_account( |
| account_id, |
| Arc::clone(&self.context) |
| ))?); |
| ids_to_handlers_lock.insert(account_id.clone(), Some(Arc::clone(&new_handler))); |
| let _ = self.accounts_inspect.active.set(count_populated(&ids_to_handlers_lock) as u64); |
| Ok(new_handler) |
| } |
| |
| async fn get_account_ids(&self) -> Vec<FidlLocalAccountId> { |
| await!(self.ids_to_handlers.lock()).keys().map(|id| id.clone().into()).collect() |
| } |
| |
| async fn get_account_auth_states(&self) -> (Status, Vec<FidlAccountAuthState>) { |
| // TODO(jsankey): Collect authentication state from AccountHandler instances rather than |
| // returning a fixed value. This will involve opening account handler connections (in |
| // parallel) for all of the accounts where encryption keys for the account's data partition |
| // are available. |
| let ids_to_handlers_lock = await!(self.ids_to_handlers.lock()); |
| ( |
| Status::Ok, |
| ids_to_handlers_lock |
| .keys() |
| .map(|id| FidlAccountAuthState { |
| account_id: id.clone().into(), |
| auth_state: DEFAULT_AUTH_STATE, |
| }) |
| .collect(), |
| ) |
| } |
| |
| async fn get_account( |
| &self, |
| id: LocalAccountId, |
| auth_context_provider: ClientEnd<AuthenticationContextProviderMarker>, |
| account: ServerEnd<AccountMarker>, |
| ) -> Status { |
| let account_handler = match await!(self.get_handler_for_existing_account(&id)) { |
| Ok(account_handler) => account_handler, |
| Err(err) => { |
| warn!("Failure getting account handler connection: {:?}", err); |
| return err.status; |
| } |
| }; |
| |
| await!(account_handler.proxy().get_account(auth_context_provider, account)).unwrap_or_else( |
| |err| { |
| warn!("Failure calling get account: {:?}", err); |
| Status::IoError |
| }, |
| ) |
| } |
| |
| async fn register_account_listener( |
| &self, |
| listener: ClientEnd<AccountListenerMarker>, |
| options: AccountListenerOptions, |
| ) -> Status { |
| let ids_to_handlers_lock = await!(self.ids_to_handlers.lock()); |
| let account_auth_states: Vec<AccountAuthState> = ids_to_handlers_lock |
| .keys() |
| // TODO(dnordstrom): Get the real auth states |
| .map(|id| AccountAuthState { account_id: id.clone() }) |
| .collect(); |
| std::mem::drop(ids_to_handlers_lock); |
| let proxy = match listener.into_proxy() { |
| Ok(proxy) => proxy, |
| Err(err) => { |
| warn!("Could not convert AccountListener client end to proxy {:?}", err); |
| return Status::InvalidRequest; |
| } |
| }; |
| match await!(self.event_emitter.add_listener(proxy, options, &account_auth_states)) { |
| Ok(()) => Status::Ok, |
| Err(err) => { |
| warn!("Could not instantiate AccountListener client {:?}", err); |
| Status::UnknownError |
| } |
| } |
| } |
| |
| async fn remove_account(&self, id: LocalAccountId) -> Status { |
| // TODO(jsankey): Open an account handler if necessary and ask it to remove persistent |
| // storage for the account. |
| let mut ids_to_handlers = await!(self.ids_to_handlers.lock()); |
| match ids_to_handlers.get(&id) { |
| None => return Status::NotFound, |
| Some(None) => info!("Removing account without open handler: {:?}", id), |
| Some(Some(account_handler)) => { |
| info!("Removing account and terminating its handler: {:?}", id); |
| await!(account_handler.terminate()); |
| } |
| }; |
| let account_ids = ids_to_handlers |
| .keys() |
| .filter(|&x| x != &id) |
| .map(|id| StoredAccountMetadata::new(id.clone())) |
| .collect(); |
| if let Err(err) = StoredAccountList::new(account_ids).save(&self.data_dir) { |
| return err.status; |
| } |
| let event = AccountEvent::AccountRemoved(id.clone()); |
| await!(self.event_emitter.publish(&event)); |
| ids_to_handlers.remove(&id); |
| let _ = self.accounts_inspect.total.set(ids_to_handlers.len() as u64); |
| let _ = self.accounts_inspect.active.set(count_populated(&ids_to_handlers) as u64); |
| Status::Ok |
| } |
| |
| async fn provision_new_account(&self) -> (Status, Option<FidlLocalAccountId>) { |
| // Create an account |
| let (account_handler, account_id) = |
| match await!(AccountHandlerConnection::create_account(Arc::clone(&self.context))) { |
| Ok((connection, account_id)) => (Arc::new(connection), account_id), |
| Err(err) => { |
| warn!("Failure creating account: {:?}", err); |
| return (err.status, None); |
| } |
| }; |
| |
| // Persist the account both in memory and on disk |
| if let Err(err) = await!(self.add_account(account_handler.clone(), account_id.clone())) { |
| warn!("Failure adding account: {:?}", err); |
| await!(account_handler.terminate()); |
| (err.status, None) |
| } else { |
| info!("Adding new local account {:?}", &account_id); |
| (Status::Ok, Some(account_id.into())) |
| } |
| } |
| |
| async fn provision_from_auth_provider( |
| &self, |
| auth_context_provider: ClientEnd<AuthenticationContextProviderMarker>, |
| auth_provider_type: String, |
| ) -> (Status, Option<FidlLocalAccountId>) { |
| // Create an account |
| let (account_handler, account_id) = |
| match await!(AccountHandlerConnection::create_account(Arc::clone(&self.context))) { |
| Ok((connection, account_id)) => (Arc::new(connection), account_id), |
| Err(err) => { |
| warn!("Failure adding account: {:?}", err); |
| return (err.status, None); |
| } |
| }; |
| |
| // Add a service provider to the account |
| let _user_profile = match await!(Self::add_service_provider_account( |
| auth_context_provider, |
| auth_provider_type, |
| account_handler.clone() |
| )) { |
| Ok(user_profile) => user_profile, |
| Err(err) => { |
| // TODO(dnordstrom): Remove the newly created account handler as a cleanup. |
| warn!("Failure adding service provider account: {:?}", err); |
| await!(account_handler.terminate()); |
| return (err.status, None); |
| } |
| }; |
| |
| // Persist the account both in memory and on disk |
| if let Err(err) = await!(self.add_account(account_handler.clone(), account_id.clone())) { |
| warn!("Failure adding service provider account: {:?}", err); |
| await!(account_handler.terminate()); |
| (err.status, None) |
| } else { |
| info!("Adding new account {:?}", &account_id); |
| (Status::Ok, Some(account_id.into())) |
| } |
| } |
| |
| // Attach a service provider account to this Fuchsia account |
| async fn add_service_provider_account( |
| auth_context_provider: ClientEnd<AuthenticationContextProviderMarker>, |
| auth_provider_type: String, |
| account_handler: Arc<AccountHandlerConnection>, |
| ) -> Result<UserProfileInfo, AccountManagerError> { |
| // Use account handler to get a channel to the account |
| let (account_client_end, account_server_end) = |
| create_endpoints().account_manager_status(Status::IoError)?; |
| match await!(account_handler.proxy().get_account(auth_context_provider, account_server_end)) |
| { |
| Ok(Status::Ok) => Ok(()), |
| Ok(status) => Err(AccountManagerError::new(status)), |
| Err(err) => Err(AccountManagerError::new(Status::IoError).with_cause(err)), |
| }?; |
| let account_proxy = |
| account_client_end.into_proxy().account_manager_status(Status::IoError)?; |
| |
| // Use the account to get the persona |
| let (persona_client_end, persona_server_end) = |
| create_endpoints().account_manager_status(Status::IoError)?; |
| match await!(account_proxy.get_default_persona(persona_server_end)) { |
| Ok((Status::Ok, _)) => Ok(()), |
| Ok((status, _)) => Err(AccountManagerError::new(status)), |
| Err(err) => Err(AccountManagerError::new(Status::IoError).with_cause(err)), |
| }?; |
| let persona_proxy = |
| persona_client_end.into_proxy().account_manager_status(Status::IoError)?; |
| |
| // Use the persona to get the token manager |
| let (tm_client_end, tm_server_end) = |
| create_endpoints().account_manager_status(Status::IoError)?; |
| match await!(persona_proxy.get_token_manager(SELF_URL, tm_server_end)) { |
| Ok(Status::Ok) => Ok(()), |
| Ok(status) => Err(AccountManagerError::new(status)), |
| Err(err) => Err(AccountManagerError::new(Status::IoError).with_cause(err)), |
| }?; |
| let tm_proxy = tm_client_end.into_proxy().account_manager_status(Status::IoError)?; |
| |
| // Use the token manager to authorize |
| let mut app_config = AppConfig { |
| auth_provider_type, |
| client_id: None, |
| client_secret: None, |
| redirect_uri: None, |
| }; |
| match await!(tm_proxy.authorize( |
| &mut app_config, |
| None, /* auth_ui_context */ |
| &mut APP_SCOPES.iter().map(|x| &**x), |
| None, /* user_profile_id */ |
| None, /* auth_code */ |
| )) { |
| Ok((AuthStatus::Ok, None)) => Err(AccountManagerError::new(Status::InternalError) |
| .with_cause(format_err!("Invalid response from token manager"))), |
| Ok((AuthStatus::Ok, Some(user_profile))) => Ok(*user_profile), |
| Ok((status, _)) => Err(AccountManagerError::from(status)), |
| Err(err) => Err(AccountManagerError::new(Status::IoError).with_cause(err)), |
| } |
| } |
| |
| // Add the account to the AccountManager, including persistent state. |
| async fn add_account( |
| &self, |
| account_handler: Arc<AccountHandlerConnection>, |
| account_id: LocalAccountId, |
| ) -> Result<(), AccountManagerError> { |
| let mut ids_to_handlers_lock = await!(self.ids_to_handlers.lock()); |
| if ids_to_handlers_lock.get(&account_id).is_some() { |
| // IDs are 64 bit integers that are meant to be random. Its very unlikely we'll create |
| // the same one twice but not impossible. |
| // TODO(dnordstrom): Avoid collision higher up the call chain. |
| return Err(AccountManagerError::new(Status::UnknownError) |
| .with_cause(format_err!("Duplicate ID {:?} creating new account", &account_id))); |
| } |
| let mut account_ids: Vec<StoredAccountMetadata> = |
| ids_to_handlers_lock.keys().map(|id| StoredAccountMetadata::new(id.clone())).collect(); |
| account_ids.push(StoredAccountMetadata::new(account_id.clone())); |
| if let Err(err) = StoredAccountList::new(account_ids).save(&self.data_dir) { |
| // TODO(dnordstrom): When AccountHandler uses persistent storage, clean up its state. |
| return Err(err); |
| } |
| ids_to_handlers_lock.insert(account_id.clone(), Some(account_handler)); |
| let event = AccountEvent::AccountAdded(account_id.clone()); |
| await!(self.event_emitter.publish(&event)); |
| let _ = self.accounts_inspect.total.set(ids_to_handlers_lock.len() as u64); |
| let _ = self.accounts_inspect.active.set(count_populated(&ids_to_handlers_lock) as u64); |
| Ok(()) |
| } |
| } |
| |
| /// Returns the number of values in a BTreeMap of Option<> that are not None. |
| fn count_populated<K, V>(map: &BTreeMap<K, Option<V>>) -> usize { |
| map.values().filter(|v| v.is_some()).count() |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::stored_account_list::{StoredAccountList, StoredAccountMetadata}; |
| use fidl::endpoints::{create_request_stream, RequestStream}; |
| use fidl_fuchsia_auth::AuthChangeGranularity; |
| use fidl_fuchsia_auth_account::{ |
| AccountListenerRequest, AccountManagerProxy, AccountManagerRequestStream, |
| }; |
| use fuchsia_async as fasync; |
| use fuchsia_zircon as zx; |
| use futures::future::join; |
| use lazy_static::lazy_static; |
| use std::path::Path; |
| use tempfile::TempDir; |
| |
| lazy_static! { |
| /// Configuration for a set of fake auth providers used for testing. |
| /// This can be populated later if needed. |
| static ref AUTH_PROVIDER_CONFIG: Vec<AuthProviderConfig> = {vec![]}; |
| } |
| |
| fn request_stream_test<TestFn, Fut>(account_manager: AccountManager, test_fn: TestFn) |
| where |
| TestFn: FnOnce(AccountManagerProxy, Arc<AccountManager>) -> Fut, |
| Fut: Future<Output = Result<(), Error>>, |
| { |
| let mut executor = fasync::Executor::new().expect("Failed to create executor"); |
| let (server_chan, client_chan) = zx::Channel::create().expect("Failed to create channel"); |
| let proxy = AccountManagerProxy::new(fasync::Channel::from_channel(client_chan).unwrap()); |
| let request_stream = AccountManagerRequestStream::from_channel( |
| fasync::Channel::from_channel(server_chan).unwrap(), |
| ); |
| |
| let account_manager_arc = Arc::new(account_manager); |
| let account_manager_clone = Arc::clone(&account_manager_arc); |
| fasync::spawn(async move { |
| await!(account_manager_clone.handle_requests_from_stream(request_stream)) |
| .unwrap_or_else(|err| panic!("Fatal error handling test request: {:?}", err)) |
| }); |
| |
| executor |
| .run_singlethreaded(test_fn(proxy, account_manager_arc)) |
| .expect("Executor run failed.") |
| } |
| |
| // Manually contructs an account manager initialized with the supplied set of accounts. |
| fn create_accounts(existing_ids: Vec<u64>, data_dir: &Path) -> AccountManager { |
| let stored_account_list = existing_ids |
| .iter() |
| .map(|&id| StoredAccountMetadata::new(LocalAccountId::new(id))) |
| .collect(); |
| StoredAccountList::new(stored_account_list) |
| .save(data_dir) |
| .expect("Couldn't write account list"); |
| let inspector = Inspector::new().unwrap(); |
| |
| AccountManager { |
| ids_to_handlers: Mutex::new( |
| existing_ids.into_iter().map(|id| (LocalAccountId::new(id), None)).collect(), |
| ), |
| context: Arc::new(AccountHandlerContext::new(&vec![])), |
| event_emitter: AccountEventEmitter::new(inspector.root()).unwrap(), |
| data_dir: data_dir.to_path_buf(), |
| accounts_inspect: inspect::Accounts::new(inspector.root()).unwrap(), |
| _auth_providers_inspect: inspect::AuthProviders::new(inspector.root()).unwrap(), |
| } |
| } |
| |
| // Contructs an account manager that reads its accounts from the supplied directory. |
| fn read_accounts(data_dir: &Path) -> AccountManager { |
| let inspector = Inspector::new().unwrap(); |
| AccountManager::new(data_dir.to_path_buf(), &AUTH_PROVIDER_CONFIG, &inspector).unwrap() |
| } |
| |
| fn fidl_local_id_vec(ints: Vec<u64>) -> Vec<FidlLocalAccountId> { |
| ints.into_iter().map(|i| FidlLocalAccountId { id: i }).collect() |
| } |
| |
| /// Note: Many AccountManager methods launch instances of an AccountHandler. Since its |
| /// currently not convenient to mock out this component launching in Rust, we rely on the |
| /// hermetic component test to provide coverage for these areas and only cover the in-process |
| /// behavior with this unit-test. |
| |
| #[test] |
| fn test_new() { |
| let inspector = Inspector::new().unwrap(); |
| let data_dir = TempDir::new().unwrap(); |
| request_stream_test( |
| AccountManager::new(data_dir.path().into(), &AUTH_PROVIDER_CONFIG, &inspector).unwrap(), |
| async move |proxy, _| { |
| assert_eq!(await!(proxy.get_account_ids())?, vec![]); |
| assert_eq!(await!(proxy.get_account_auth_states())?, (Status::Ok, vec![])); |
| Ok(()) |
| }, |
| ); |
| } |
| |
| #[test] |
| fn test_initially_empty() { |
| let data_dir = TempDir::new().unwrap(); |
| request_stream_test( |
| create_accounts(vec![], data_dir.path()), |
| async move |proxy, test_object| { |
| assert_eq!(await!(proxy.get_account_ids())?, vec![]); |
| assert_eq!(await!(proxy.get_account_auth_states())?, (Status::Ok, vec![])); |
| assert_eq!(test_object.accounts_inspect.total.get().unwrap(), 0); |
| assert_eq!(test_object.accounts_inspect.active.get().unwrap(), 0); |
| Ok(()) |
| }, |
| ); |
| } |
| |
| #[test] |
| fn test_remove_missing_account() { |
| // Manually create an account manager with one account. |
| let data_dir = TempDir::new().unwrap(); |
| let stored_account_list = |
| StoredAccountList::new(vec![StoredAccountMetadata::new(LocalAccountId::new(1))]); |
| stored_account_list.save(data_dir.path()).unwrap(); |
| request_stream_test(read_accounts(data_dir.path()), async move |proxy, test_object| { |
| // Try to delete a very different account from the one we added. |
| assert_eq!( |
| await!(proxy.remove_account(LocalAccountId::new(42).as_mut()))?, |
| Status::NotFound |
| ); |
| assert_eq!(test_object.accounts_inspect.total.get().unwrap(), 1); |
| Ok(()) |
| }); |
| } |
| |
| #[test] |
| fn test_remove_present_account() { |
| let data_dir = TempDir::new().unwrap(); |
| let stored_account_list = StoredAccountList::new(vec![ |
| StoredAccountMetadata::new(LocalAccountId::new(1)), |
| StoredAccountMetadata::new(LocalAccountId::new(2)), |
| ]); |
| stored_account_list.save(data_dir.path()).unwrap(); |
| // Manually create an account manager from an account_list_dir with two pre-populated dirs. |
| request_stream_test(read_accounts(data_dir.path()), async move |proxy, test_object| { |
| // Try to remove the first account. |
| assert_eq!(test_object.accounts_inspect.total.get().unwrap(), 2); |
| assert_eq!(await!(proxy.remove_account(LocalAccountId::new(1).as_mut()))?, Status::Ok); |
| assert_eq!(test_object.accounts_inspect.total.get().unwrap(), 1); |
| |
| // Verify that the second account is present. |
| assert_eq!(await!(proxy.get_account_ids())?, fidl_local_id_vec(vec![2])); |
| Ok(()) |
| }); |
| // Now create another account manager using the same directory, which should pick up the new |
| // state from the operations of the first account manager. |
| request_stream_test(read_accounts(data_dir.path()), async move |proxy, test_object| { |
| // Verify the only the second account is present. |
| assert_eq!(test_object.accounts_inspect.total.get().unwrap(), 1); |
| assert_eq!(await!(proxy.get_account_ids())?, fidl_local_id_vec(vec![2])); |
| Ok(()) |
| }); |
| } |
| |
| /// Sets up an AccountListener which receives two events, init and remove. |
| #[test] |
| fn test_account_listener() { |
| let mut options = AccountListenerOptions { |
| initial_state: true, |
| add_account: true, |
| remove_account: true, |
| granularity: AuthChangeGranularity { summary_changes: false }, |
| }; |
| |
| let data_dir = TempDir::new().unwrap(); |
| // TODO(dnordstrom): Use run_until_stalled macro instead. |
| request_stream_test(create_accounts(vec![1, 2], data_dir.path()), async move |proxy, _| { |
| let (client_end, mut stream) = |
| create_request_stream::<AccountListenerMarker>().unwrap(); |
| let serve_fut = async move { |
| let request = await!(stream.try_next()).expect("stream error"); |
| if let Some(AccountListenerRequest::OnInitialize { |
| account_auth_states, |
| responder, |
| }) = request |
| { |
| assert_eq!( |
| account_auth_states, |
| vec![ |
| FidlAccountAuthState::from(&AccountAuthState { |
| account_id: LocalAccountId::new(1) |
| }), |
| FidlAccountAuthState::from(&AccountAuthState { |
| account_id: LocalAccountId::new(2) |
| }), |
| ] |
| ); |
| responder.send().unwrap(); |
| } else { |
| panic!("Unexpected message received"); |
| }; |
| let request = await!(stream.try_next()).expect("stream error"); |
| if let Some(AccountListenerRequest::OnAccountRemoved { id, responder }) = request { |
| assert_eq!(LocalAccountId::from(id), LocalAccountId::new(1)); |
| responder.send().unwrap(); |
| } else { |
| panic!("Unexpected message received"); |
| }; |
| if let Some(_) = await!(stream.try_next()).expect("stream error") { |
| panic!("Unexpected message, channel should be closed"); |
| } |
| }; |
| let request_fut = async move { |
| // The registering itself triggers the init event. |
| assert_eq!( |
| await!(proxy.register_account_listener(client_end, &mut options)).unwrap(), |
| Status::Ok |
| ); |
| |
| // Non-existing account removal shouldn't trigger any event. |
| assert_eq!( |
| await!(proxy.remove_account(LocalAccountId::new(42).as_mut())).unwrap(), |
| Status::NotFound |
| ); |
| // Removal of existing account triggers the remove event. |
| assert_eq!( |
| await!(proxy.remove_account(LocalAccountId::new(1).as_mut())).unwrap(), |
| Status::Ok |
| ); |
| }; |
| await!(join(request_fut, serve_fut)); |
| Ok(()) |
| }); |
| } |
| } |