[auth] Attach AccountManager to AccountHandler
AUTH-118
Tested: CQ, unit and integration tests included.
Change-Id: I2198a75ac44d66c6412cc4963562a1470801d16b
diff --git a/bin/auth/rust/account_common/src/deferred.rs b/bin/auth/rust/account_common/src/deferred.rs
deleted file mode 100644
index 89eacde..0000000
--- a/bin/auth/rust/account_common/src/deferred.rs
+++ /dev/null
@@ -1,39 +0,0 @@
-// 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.
-
-/// A simple wrapper to set an optional value after instantiation.
-///
-/// The value cannot be unset once set. When wrapped in a synchronization primitive, this type is
-/// helpful as an input for asynchronous functions, letting them lazily create shared state on
-/// first use.
-pub struct DeferredOption<T> {
- value: Option<T>,
-}
-
-impl<T> DeferredOption<T> {
- /// Constructs an empty `DeferredOption`.
- pub fn new() -> DeferredOption<T> {
- DeferredOption { value: None }
- }
-
- /// Returns the current value.
- pub fn get(&self) -> Option<&T> {
- self.value.as_ref()
- }
-
- /// Sets or overrides the current value with `value`.
- pub fn set(&mut self, value: T) {
- self.value = Some(value);
- }
-
- /// Returns the current value if set, or sets and returns if using `setter` otherwise.
- pub fn get_or_create<'a, F>(&'a mut self, setter: F) -> &'a mut T
- where F: FnOnce() -> T
- {
- if self.value.is_none() {
- self.value = Some(setter());
- }
- self.value.as_mut().unwrap()
- }
-}
diff --git a/bin/auth/rust/account_common/src/error.rs b/bin/auth/rust/account_common/src/error.rs
index 0c66273..41b0227 100644
--- a/bin/auth/rust/account_common/src/error.rs
+++ b/bin/auth/rust/account_common/src/error.rs
@@ -35,7 +35,6 @@
}
/// Sets a cause on the current error.
- #[allow(dead_code)] // TODO(jsankey): Use this method in the next CL.
pub fn with_cause<T: Into<Error>>(mut self, cause: T) -> Self {
self.cause = Some(cause.into());
self
diff --git a/bin/auth/rust/account_common/src/identifiers.rs b/bin/auth/rust/account_common/src/identifiers.rs
index c8d1925..91b178f 100644
--- a/bin/auth/rust/account_common/src/identifiers.rs
+++ b/bin/auth/rust/account_common/src/identifiers.rs
@@ -12,6 +12,7 @@
// to be more ergonomic and remove the need for this module.
use std::cmp::Ordering;
+use std::hash::{Hash, Hasher};
use std::fmt::{Debug, Formatter};
/// Implements `$name` as a new wrapper type.
@@ -20,7 +21,7 @@
/// in both directions to a type with the same name in `$fidl_crate`. This type must by a struct
/// contain a single field of type `$type` named 'id'.
///
-/// `$type` must implement the following traits: `Debug`, `Clone`, `Eq`, and `Ord`.
+/// `$type` must implement the following traits: `Debug`, `Clone`, `Hash`, `Eq`, and `Ord`.
///
/// # Examples
///
@@ -53,6 +54,12 @@
}
}
+ impl Hash for $name {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.inner.id.hash(state);
+ }
+ }
+
impl Ord for $name {
fn cmp(&self, other: &$name) -> Ordering {
self.inner.id.cmp(&other.inner.id)
diff --git a/bin/auth/rust/account_common/src/lib.rs b/bin/auth/rust/account_common/src/lib.rs
index 1b5e13d..007f6f3 100644
--- a/bin/auth/rust/account_common/src/lib.rs
+++ b/bin/auth/rust/account_common/src/lib.rs
@@ -10,10 +10,7 @@
mod error;
/// More ergonomic wrapper types for FIDL account and persona identifiers.
mod identifiers;
-/// A simple wrapper to set values after instantiation.
-mod deferred;
pub use crate::error::AccountManagerError;
pub use crate::identifiers::{FidlGlobalAccountId, FidlLocalAccountId, FidlLocalPersonaId,
GlobalAccountId, LocalAccountId, LocalPersonaId};
-pub use crate::deferred::DeferredOption;
diff --git a/bin/auth/rust/account_manager/BUILD.gn b/bin/auth/rust/account_manager/BUILD.gn
index 80bb87d..abf0f25 100644
--- a/bin/auth/rust/account_manager/BUILD.gn
+++ b/bin/auth/rust/account_manager/BUILD.gn
@@ -13,6 +13,7 @@
"//garnet/bin/auth/rust/account_common",
"//garnet/public/fidl/fuchsia.auth:fuchsia.auth-rustc",
"//garnet/public/fidl/fuchsia.auth.account:fuchsia.auth.account-rustc",
+ "//garnet/lib/auth/fidl:account-rustc",
"//garnet/public/lib/fidl/rust/fidl",
"//garnet/public/rust/fuchsia-app",
"//garnet/public/rust/fuchsia-async",
diff --git a/bin/auth/rust/account_manager/src/account_handler_connection.rs b/bin/auth/rust/account_manager/src/account_handler_connection.rs
new file mode 100644
index 0000000..a624b75
--- /dev/null
+++ b/bin/auth/rust/account_manager/src/account_handler_connection.rs
@@ -0,0 +1,82 @@
+// 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 account_common::{AccountManagerError, LocalAccountId};
+use failure::{format_err, ResultExt};
+use fidl_fuchsia_auth_account::Status;
+use fidl_fuchsia_auth_account_internal::{AccountHandlerControlMarker, AccountHandlerControlProxy};
+use fuchsia_app::client::{App, Launcher};
+use futures::prelude::*;
+use log::{info, warn};
+
+/// The information necessary to maintain a connection to an AccountHandler component instance.
+pub struct AccountHandlerConnection {
+ /// An `App` object for the launched AccountHandler.
+ ///
+ /// Note: This must remain in scope for the component to remain running, but never needs to be
+ /// read.
+ _app: App,
+
+ /// A `Proxy` connected to the AccountHandlerControl interface on the launched AccountHandler.
+ proxy: AccountHandlerControlProxy,
+}
+
+/// The url used to launch new AccountHandler component instances.
+const ACCOUNT_HANDLER_URL: &str =
+ "fuchsia-pkg://fuchsia.com/account_handler#meta/account_handler.cmx";
+
+impl AccountHandlerConnection {
+ /// Launches a new AccountHandler component instance and establishes a connection to its
+ /// control channel.
+ pub fn new() -> Result<Self, AccountManagerError> {
+ info!("Launching new AccountHandler instance");
+
+ let launcher = Launcher::new()
+ .context("Failed to start launcher")
+ .map_err(|err| AccountManagerError::new(Status::IoError).with_cause(err))?;
+ let app = launcher
+ .launch(ACCOUNT_HANDLER_URL.to_string(), None)
+ .context("Failed to launch AccountHandler")
+ .map_err(|err| AccountManagerError::new(Status::IoError).with_cause(err))?;
+ let proxy = app
+ .connect_to_service(AccountHandlerControlMarker)
+ .context("Failed to connect to AccountHandlerControl")
+ .map_err(|err| AccountManagerError::new(Status::InternalError).with_cause(err))?;
+ Ok(AccountHandlerConnection { _app: app, proxy })
+ }
+
+ /// Launches a new AccountHandler component instance, establishes a connection to its control
+ /// channel, and requests that it loads an existing account.
+ pub async fn new_for_account(account_id: &LocalAccountId) -> Result<Self, AccountManagerError> {
+ let connection = Self::new()?;
+ let mut fidl_account_id = account_id.clone().into();
+ match await!(connection.proxy.load_account(&mut fidl_account_id))
+ .map_err(|err| AccountManagerError::new(Status::IoError).with_cause(err))?
+ {
+ Status::Ok => Ok(connection),
+ stat => Err(AccountManagerError::new(stat)
+ .with_cause(format_err!("Error loading existing account"))),
+ }
+ }
+
+ /// Returns the AccountHandlerControlProxy for this connection
+ pub fn proxy(&self) -> &AccountHandlerControlProxy {
+ &self.proxy
+ }
+
+ /// Requests that the AccountHandler component instance terminate gracefully.
+ ///
+ /// Any subsequent operations that attempt to use `proxy()` will fail after this call. The
+ /// resources associated with the connection when only be freed once the
+ /// `AccountHandlerConnection` is dropped.
+ pub async fn terminate(&self) {
+ let mut event_stream = self.proxy.take_event_stream();
+ if let Err(err) = self.proxy.terminate() {
+ warn!("Error gracefully terminating account handler {:?}", err);
+ } else {
+ while let Ok(Some(_)) = await!(event_stream.try_next()) {}
+ info!("Gracefully terminated AccountHandler instance");
+ }
+ }
+}
diff --git a/bin/auth/rust/account_manager/src/account_manager.rs b/bin/auth/rust/account_manager/src/account_manager.rs
index 622d37c..bbb1b24 100644
--- a/bin/auth/rust/account_manager/src/account_manager.rs
+++ b/bin/auth/rust/account_manager/src/account_manager.rs
@@ -2,19 +2,23 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use account_common::{FidlLocalAccountId, LocalAccountId};
+use account_common::{AccountManagerError, FidlLocalAccountId, LocalAccountId};
use fidl::encoding::OutOfLine;
use fidl::endpoints::{ClientEnd, ServerEnd};
-use fidl::Error;
use fidl_fuchsia_auth::{AuthState, AuthStateSummary, AuthenticationContextProviderMarker};
use fidl_fuchsia_auth_account::{
AccountAuthState, AccountListenerMarker, AccountListenerOptions, AccountManagerRequest,
AccountManagerRequestStream, AccountMarker, Status,
};
+
+use failure::Error;
+use futures::lock::Mutex;
use futures::prelude::*;
-use log::info;
-use parking_lot::Mutex;
-use std::collections::BTreeSet;
+use log::{info, warn};
+use std::collections::BTreeMap;
+use std::sync::Arc;
+
+use crate::account_handler_connection::AccountHandlerConnection;
/// The core component of the account system for Fuchsia.
///
@@ -23,12 +27,10 @@
/// service providers, and launches and delegates to AccountHandler component instances to
/// determine the detailed state and authentication for each account.
pub struct AccountManager {
- /// (Temporary) The next unused local account identifier.
- // TODO(jsankey): Replace this temporary sequential ID assignment with randomness.
- next_id: Mutex<u64>,
-
- /// An ordered set of LocalAccountIds for accounts provisioned on the current device.
- ids: Mutex<BTreeSet<LocalAccountId>>,
+ /// 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<BTreeMap<LocalAccountId, Option<Arc<AccountHandlerConnection>>>>,
}
impl AccountManager {
@@ -41,8 +43,7 @@
/// Constructs a new AccountManager with no accounts.
pub fn new() -> AccountManager {
AccountManager {
- next_id: Mutex::new(1),
- ids: Mutex::new(BTreeSet::new()),
+ ids_to_handlers: Mutex::new(BTreeMap::new()),
}
}
@@ -51,19 +52,19 @@
&self, mut stream: AccountManagerRequestStream,
) -> Result<(), Error> {
while let Some(req) = await!(stream.try_next())? {
- self.handle_request(req)?;
+ await!(self.handle_request(req))?;
}
Ok(())
}
/// Handles a single request to the AccountManager.
- pub fn handle_request(&self, req: AccountManagerRequest) -> Result<(), Error> {
+ pub async fn handle_request(&self, req: AccountManagerRequest) -> Result<(), fidl::Error> {
match req {
AccountManagerRequest::GetAccountIds { responder } => {
- responder.send(&mut self.get_account_ids().iter_mut())
+ responder.send(&mut await!(self.get_account_ids()).iter_mut())
}
AccountManagerRequest::GetAccountAuthStates { responder } => {
- let mut response = self.get_account_auth_states();
+ let mut response = await!(self.get_account_auth_states());
responder.send(response.0, &mut response.1.iter_mut())
}
AccountManagerRequest::GetAccount {
@@ -71,14 +72,18 @@
auth_context_provider,
account,
responder,
- } => responder.send(self.get_account(id.into(), auth_context_provider, account)),
+ } => responder.send(await!(self.get_account(
+ id.into(),
+ auth_context_provider,
+ account
+ ))),
AccountManagerRequest::RegisterAccountListener {
listener,
options,
responder,
} => responder.send(self.register_account_listener(listener, options)),
AccountManagerRequest::RemoveAccount { id, responder } => {
- responder.send(self.remove_account(id.into()))
+ responder.send(await!(self.remove_account(id.into())))
}
AccountManagerRequest::ProvisionFromAuthProvider {
auth_context_provider,
@@ -90,24 +95,49 @@
responder.send(response.0, response.1.as_mut().map(OutOfLine))
}
AccountManagerRequest::ProvisionNewAccount { responder } => {
- let mut response = self.provision_new_account();
+ let mut response = await!(self.provision_new_account());
responder.send(response.0, response.1.as_mut().map(OutOfLine))
}
}
}
- fn get_account_ids(&self) -> Vec<FidlLocalAccountId> {
- self.ids.lock().iter().map(|id| id.clone().into()).collect()
+ /// 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 */ }
+ }
+
+ // Initialize a new account handler if not.
+ let new_handler = Arc::new(await!(AccountHandlerConnection::new_for_account(
+ account_id
+ ))?);
+ ids_to_handlers_lock.insert(account_id.clone(), Some(Arc::clone(&new_handler)));
+ Ok(new_handler)
}
- fn get_account_auth_states(&self) -> (Status, Vec<AccountAuthState>) {
- // TODO(jsankey): Collect authentication state from AccountHandler instances
- // rather than returning a fixed value.
+ 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<AccountAuthState>) {
+ // 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,
- self.ids
- .lock()
- .iter()
+ ids_to_handlers_lock
+ .keys()
.map(|id| AccountAuthState {
account_id: id.clone().into(),
auth_state: Self::DEFAULT_AUTH_STATE,
@@ -116,13 +146,26 @@
)
}
- fn get_account(
- &self, _id: LocalAccountId,
- _auth_context_provider: ClientEnd<AuthenticationContextProviderMarker>,
- _account: ServerEnd<AccountMarker>,
+ async fn get_account(
+ &self, id: LocalAccountId,
+ auth_context_provider: ClientEnd<AuthenticationContextProviderMarker>,
+ account: ServerEnd<AccountMarker>,
) -> Status {
- // TODO(jsankey): Implement this method
- Status::InternalError
+ 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
+ })
}
fn register_account_listener(
@@ -132,26 +175,61 @@
Status::InternalError
}
- fn remove_account(&self, id: LocalAccountId) -> Status {
- let mut ids_lock = self.ids.lock();
- if ids_lock.contains(&id) {
- ids_lock.remove(&id);
- info!("Removing account {:?}", id);
- // TODO(jsankey): Persist the change in installed accounts.
- Status::Ok
- } else {
- Status::NotFound
+ 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.
+ match await!(self.ids_to_handlers.lock()).remove(&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());
+ }
}
+ Status::Ok
}
- fn provision_new_account(&self) -> (Status, Option<FidlLocalAccountId>) {
- let mut next_id_lock = self.next_id.lock();
- let new_id = LocalAccountId::new(*next_id_lock);
- self.ids.lock().insert(new_id.clone());
- *next_id_lock += 1;
- info!("Adding new local account {:?}", new_id);
- // TODO(jsankey): Persist the change in installed accounts.
- (Status::Ok, Some(new_id.into()))
+ async fn provision_new_account(&self) -> (Status, Option<FidlLocalAccountId>) {
+ let account_handler = match AccountHandlerConnection::new() {
+ Ok(connection) => Arc::new(connection),
+ Err(err) => {
+ warn!("Failure creating account handler connection: {:?}", err);
+ return (err.status, None);
+ }
+ };
+
+ let account_id = match await!(account_handler.proxy().create_account()) {
+ Ok((Status::Ok, Some(account_id))) => LocalAccountId::from(*account_id),
+ Ok((Status::Ok, None)) => {
+ warn!("Failure creating account, handler returned success without ID");
+ return (Status::InternalError, None);
+ }
+ Ok((stat, _)) => {
+ warn!("Failure creating new account, handler returned {:?}", stat);
+ return (stat, None);
+ }
+ Err(err) => {
+ warn!("Failure calling create account: {:?}", err);
+ return (Status::IoError, None);
+ }
+ };
+
+ info!("Adding new local account {:?}", account_id);
+ // TODO(jsankey): Persist the change in installed accounts, ensuring this has succeeded
+ // before adding to our in-memory state.
+
+ 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(jsankey): Once account handler is handling persistent state it may be able to
+ // detect this condition itself, if not it needs to be told to delete any state it has
+ // created for this user we're not going to add.
+ warn!("Duplicate ID creating new account");
+ return (Status::UnknownError, None);
+ }
+ ids_to_handlers_lock.insert(account_id.clone(), Some(account_handler));
+ (Status::Ok, Some(account_id.into()))
}
fn provision_from_auth_provider(
@@ -195,10 +273,14 @@
.expect("Executor run failed.")
}
- fn create_test_object(next_id: u64, existing_ids: Vec<u64>) -> AccountManager {
+ fn create_test_object(existing_ids: Vec<u64>) -> AccountManager {
AccountManager {
- next_id: Mutex::new(next_id),
- ids: Mutex::new(existing_ids.into_iter().map(LocalAccountId::new).collect()),
+ ids_to_handlers: Mutex::new(
+ existing_ids
+ .into_iter()
+ .map(|id| (LocalAccountId::new(id), None))
+ .collect(),
+ ),
}
}
@@ -208,9 +290,10 @@
.collect()
}
- fn fidl_local_id_box(i: u64) -> Box<FidlLocalAccountId> {
- Box::new(FidlLocalAccountId { id: i })
- }
+ /// 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
+ /// hermertic component test to provide coverage for these areas and only cover the in-process
+ /// behavior with this unit-test.
#[test]
fn test_initially_empty() {
@@ -225,44 +308,10 @@
}
#[test]
- fn test_provision_new_account() {
- request_stream_test(AccountManager::new(), async move |proxy| {
- // Add two accounts.
- assert_eq!(
- await!(proxy.provision_new_account())?,
- (Status::Ok, Some(fidl_local_id_box(1)))
- );
- assert_eq!(
- await!(proxy.provision_new_account())?,
- (Status::Ok, Some(fidl_local_id_box(2)))
- );
-
- // Verify both are visible in the ID and AuthState getters.
- assert_eq!(
- await!(proxy.get_account_ids())?,
- fidl_local_id_vec(vec![1, 2])
- );
-
- let expected_account_auth_states = vec![1, 2]
- .into_iter()
- .map(|id| AccountAuthState {
- account_id: FidlLocalAccountId { id },
- auth_state: AccountManager::DEFAULT_AUTH_STATE,
- })
- .collect();
- assert_eq!(
- await!(proxy.get_account_auth_states())?,
- (Status::Ok, expected_account_auth_states)
- );
- Ok(())
- });
- }
-
- #[test]
fn test_remove_missing_account() {
request_stream_test(
// Manually create an account manager with one account.
- create_test_object(2, vec![1]),
+ create_test_object(vec![1]),
async move |proxy| {
// Try to delete a very different account from the one we added.
assert_eq!(
@@ -278,7 +327,7 @@
fn test_remove_present_account() {
request_stream_test(
// Manually create an account manager with two accounts.
- create_test_object(3, vec![1, 2]),
+ create_test_object(vec![1, 2]),
async move |proxy| {
// Try to remove the first one.
assert_eq!(
diff --git a/bin/auth/rust/account_manager/src/main.rs b/bin/auth/rust/account_manager/src/main.rs
index 942a90f..188b814 100644
--- a/bin/auth/rust/account_manager/src/main.rs
+++ b/bin/auth/rust/account_manager/src/main.rs
@@ -14,6 +14,7 @@
#![deny(missing_docs)]
#![feature(async_await, await_macro, futures_api)]
+mod account_handler_connection;
mod account_manager;
use crate::account_manager::AccountManager;
diff --git a/bin/auth/rust/meta/account_handler.cmx b/bin/auth/rust/meta/account_handler.cmx
index 131cc19..ed91ce2 100644
--- a/bin/auth/rust/meta/account_handler.cmx
+++ b/bin/auth/rust/meta/account_handler.cmx
@@ -3,6 +3,9 @@
"binary": "bin/app"
},
"sandbox": {
- "features": [ "persistent-storage" ]
+ "features": [ "persistent-storage" ],
+ "services": [
+ "fuchsia.logger.LogSink"
+ ]
}
}
diff --git a/bin/auth/rust/meta/account_manager.cmx b/bin/auth/rust/meta/account_manager.cmx
index f0c9d6b..8465166 100644
--- a/bin/auth/rust/meta/account_manager.cmx
+++ b/bin/auth/rust/meta/account_manager.cmx
@@ -6,6 +6,7 @@
"features": [ "persistent-storage" ],
"services": [
"fuchsia.logger.LogSink",
+ "fuchsia.sys.Launcher",
"fuchsia.tracelink.Registry"
]
}
diff --git a/bin/auth/testing/account_manager_integration/tests/account.rs b/bin/auth/testing/account_manager_integration/tests/account.rs
index 8b97509..c710544 100644
--- a/bin/auth/testing/account_manager_integration/tests/account.rs
+++ b/bin/auth/testing/account_manager_integration/tests/account.rs
@@ -3,10 +3,16 @@
// found in the LICENSE file.
use failure::{format_err, Error};
-use fidl_fuchsia_auth_account::{AccountManagerMarker, AccountManagerProxy, Status};
+use fidl::endpoints::{ClientEnd, ServerEnd};
+use fidl_fuchsia_auth::AuthStateSummary;
+use fidl_fuchsia_auth_account::{
+ AccountManagerMarker, AccountManagerProxy, AccountProxy, LocalAccountId, Status,
+};
use fuchsia_async as fasync;
+use fuchsia_zircon as zx;
use futures::prelude::*;
+/// Executes the supplied test function against a connected AccountManagerProxy.
fn proxy_test<TestFn, Fut>(test_fn: TestFn)
where
TestFn: FnOnce(AccountManagerProxy) -> Fut,
@@ -21,24 +27,84 @@
.expect("Executor run failed.")
}
-#[test]
-fn test_provision_new_account() {
- proxy_test(async move |account_manager| {
- assert_eq!(await!(account_manager.get_account_ids())?, vec![]);
- match await!(account_manager.provision_new_account())? {
- (Status::Ok, Some(new_account_id)) => {
- assert_eq!(
- await!(account_manager.get_account_ids())?,
- vec![*new_account_id]
- );
- Ok(())
- }
- (status, _) => Err(format_err!(
- "ProvisionNewAccount returned status: {:?}",
- status
- )),
- }
- });
+/// Calls provision_new_account on the supplied account_manager, returning an error on any
+/// non-OK responses, or the account ID on success.
+async fn provision_new_account(
+ account_manager: &AccountManagerProxy,
+) -> Result<LocalAccountId, Error> {
+ match await!(account_manager.provision_new_account())? {
+ (Status::Ok, Some(new_account_id)) => Ok(*new_account_id),
+ (status, _) => Err(format_err!(
+ "ProvisionNewAccount returned status: {:?}",
+ status
+ )),
+ }
}
-// TODO(jsankey): Add additional tests as AccountManager and AccountHandler are connected.
+// TODO(jsankey): Work with ComponentFramework and cramertj@ to develop a nice Rust equivalent of
+// the C++ TestWithEnvironment fixture to provide isolated environments for each test case. For now
+// we verify all functionality in a single test case.
+#[test]
+fn test_account_functionality() {
+ proxy_test(async move |account_manager| {
+ // Verify we initially have no accounts.
+ assert_eq!(await!(account_manager.get_account_ids())?, vec![]);
+
+ // Provision a new account.
+ let mut account_1 = await!(provision_new_account(&account_manager))?;
+ assert_eq!(
+ await!(account_manager.get_account_ids())?,
+ vec![LocalAccountId { id: account_1.id }]
+ );
+
+ // Provision a second new account and verify it has a different ID.
+ let mut account_2 = await!(provision_new_account(&account_manager))?;
+ assert_ne!(account_1.id, account_2.id);
+
+ // Connect a channel to one of these accounts and verify it's usable.
+ let (_, acp_client_chan) = zx::Channel::create()?;
+ let (account_server_chan, account_client_chan) = zx::Channel::create()?;
+ assert_eq!(
+ await!(account_manager.get_account(
+ &mut account_1,
+ ClientEnd::new(acp_client_chan),
+ ServerEnd::new(account_server_chan)
+ ))?,
+ Status::Ok
+ );
+ let account =
+ AccountProxy::new(fasync::Channel::from_channel(account_client_chan).unwrap());
+ let account_auth_state = match await!(account.get_auth_state())? {
+ (Status::Ok, Some(auth_state)) => *auth_state,
+ (status, _) => return Err(format_err!("GetAuthState returned status: {:?}", status)),
+ };
+ assert_eq!(account_auth_state.summary, AuthStateSummary::Unknown);
+
+ // Connect a channel to the account's default persona and verify it's usable.
+ let (persona_server_chan, persona_client_chan) = zx::Channel::create()?;
+ assert_eq!(
+ await!(account.get_default_persona(ServerEnd::new(persona_server_chan)))?.0,
+ Status::Ok
+ );
+ let persona =
+ AccountProxy::new(fasync::Channel::from_channel(persona_client_chan).unwrap());
+ let persona_auth_state = match await!(persona.get_auth_state())? {
+ (Status::Ok, Some(auth_state)) => *auth_state,
+ (status, _) => return Err(format_err!("GetAuthState returned status: {:?}", status)),
+ };
+ assert_eq!(persona_auth_state.summary, AuthStateSummary::Unknown);
+
+ // Delete both accounts and verify they are removed.
+ assert_eq!(
+ await!(account_manager.remove_account(&mut account_1))?,
+ Status::Ok
+ );
+ assert_eq!(
+ await!(account_manager.remove_account(&mut account_2))?,
+ Status::Ok
+ );
+ assert_eq!(await!(account_manager.get_account_ids())?, vec![]);
+
+ Ok(())
+ });
+}