blob: 4d0229d85963168dfe4c5bfb7688d76e217c8977 [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 account_common::{AccountManagerError, FidlLocalAccountId, LocalAccountId};
use failure::Error;
use fidl::encoding::OutOfLine;
use fidl::endpoints::{ClientEnd, ServerEnd};
use fidl_fuchsia_auth::AuthProviderConfig;
use fidl_fuchsia_auth::{AuthState, AuthStateSummary, AuthenticationContextProviderMarker};
use fidl_fuchsia_auth_account::{
AccountAuthState, AccountListenerMarker, AccountListenerOptions, AccountManagerRequest,
AccountManagerRequestStream, AccountMarker, Status,
};
use futures::lock::Mutex;
use futures::prelude::*;
use lazy_static::lazy_static;
use log::{info, warn};
use std::collections::BTreeMap;
use std::sync::Arc;
use crate::account_handler_connection::AccountHandlerConnection;
use crate::account_handler_context::AccountHandlerContext;
/// (Temporary) A fixed AuthState that is used for all accounts until authenticators are
/// available.
const DEFAULT_AUTH_STATE: AuthState = AuthState {
summary: AuthStateSummary::Unknown,
};
lazy_static! {
/// (Temporary) Configuration for a fixed set of auth providers used until file-based
/// configuration is available.
static ref DEFAULT_AUTH_PROVIDER_CONFIG: Vec<AuthProviderConfig> = {
vec![AuthProviderConfig {
auth_provider_type: "google".to_string(),
url: "fuchsia-pkg://fuchsia.com/google_auth_provider#meta/google_auth_provider.cmx"
.to_string(),
params: None
}]
};
}
/// 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<BTreeMap<LocalAccountId, Option<Arc<AccountHandlerConnection>>>>,
/// An object to service requests for contextual information from AccountHandlers.
context: Arc<AccountHandlerContext>,
}
impl AccountManager {
/// Constructs a new AccountManager with no accounts.
pub fn new() -> AccountManager {
AccountManager {
ids_to_handlers: Mutex::new(BTreeMap::new()),
context: Arc::new(AccountHandlerContext::new(&DEFAULT_AUTH_PROVIDER_CONFIG)),
}
}
/// 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(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 =
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)));
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<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,
ids_to_handlers_lock
.keys()
.map(|id| AccountAuthState {
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
})
}
fn register_account_listener(
&self, _listener: ClientEnd<AccountListenerMarker>, _options: AccountListenerOptions,
) -> Status {
// TODO(jsankey): Implement this method
Status::InternalError
}
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
}
async fn provision_new_account(&self) -> (Status, Option<FidlLocalAccountId>) {
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);
}
};
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(
&self, _auth_context_provider: ClientEnd<AuthenticationContextProviderMarker>,
_auth_provider_type: String,
) -> (Status, Option<FidlLocalAccountId>) {
// TODO(jsankey): Implement this method
(Status::InternalError, None)
}
}
#[cfg(test)]
mod tests {
use super::*;
use fidl::endpoints::RequestStream;
use fidl_fuchsia_auth_account::{AccountManagerProxy, AccountManagerRequestStream};
use fuchsia_async as fasync;
use fuchsia_zircon as zx;
fn request_stream_test<TestFn, Fut>(test_object: AccountManager, test_fn: TestFn)
where
TestFn: FnOnce(AccountManagerProxy) -> 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(),
);
fasync::spawn(
async move {
await!(test_object.handle_requests_from_stream(request_stream))
.unwrap_or_else(|err| panic!("Fatal error handling test request: {:?}", err))
},
);
executor
.run_singlethreaded(test_fn(proxy))
.expect("Executor run failed.")
}
fn create_test_object(existing_ids: Vec<u64>) -> AccountManager {
AccountManager {
ids_to_handlers: Mutex::new(
existing_ids
.into_iter()
.map(|id| (LocalAccountId::new(id), None))
.collect(),
),
context: Arc::new(AccountHandlerContext::new(&vec![])),
}
}
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_initially_empty() {
request_stream_test(AccountManager::new(), 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_remove_missing_account() {
request_stream_test(
// Manually create an account manager with one account.
create_test_object(vec![1]),
async move |proxy| {
// 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
);
Ok(())
},
);
}
#[test]
fn test_remove_present_account() {
request_stream_test(
// Manually create an account manager with two accounts.
create_test_object(vec![1, 2]),
async move |proxy| {
// Try to remove the first one.
assert_eq!(
await!(proxy.remove_account(LocalAccountId::new(1).as_mut()))?,
Status::Ok
);
// Verify the second account is still present.
assert_eq!(await!(proxy.get_account_ids())?, fidl_local_id_vec(vec![2]));
Ok(())
},
);
}
}