blob: e51cbf07a42f449eba246d31a1e2fca3860ff978 [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 account_common::{AccountManagerError, LocalAccountId, ResultExt};
use failure::{format_err, Error, ResultExt as _};
use fidl::endpoints::{ClientEnd, ServerEnd};
use fidl_fuchsia_auth::{AuthState, AuthStateSummary, AuthenticationContextProviderMarker};
use fidl_fuchsia_auth_account::{AccountMarker, Status};
use fidl_fuchsia_auth_account_internal::{
AccountHandlerContextMarker, AccountHandlerContextProxy, AccountHandlerControlRequest,
AccountHandlerControlRequestStream,
};
use fuchsia_async as fasync;
use futures::prelude::*;
use log::{error, info, warn};
use parking_lot::{RwLock, RwLockWriteGuard};
use std::path::PathBuf;
use std::sync::Arc;
/// 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 optional `Account` that we are handling.
//
// This will be None until a particular Account is established over the control channel. Once
// set, the account will never be cleared or modified.
account: RwLock<Option<Arc<Account>>>,
/// Root directory containing persistent resources for an AccountHandler instance.
data_dir: PathBuf,
// TODO(jsankey): Add TokenManager and AccountHandlerContext.
}
impl AccountHandler {
/// (Temporary) A fixed AuthState that is used for all accounts until authenticators are
/// available.
pub const DEFAULT_AUTH_STATE: AuthState = AuthState { summary: AuthStateSummary::Unknown };
/// Constructs a new AccountHandler.
pub fn new(data_dir: PathBuf) -> AccountHandler {
Self { account: RwLock::new(None), data_dir }
}
/// Asynchronously handles the supplied stream of `AccountHandlerControlRequest` messages.
pub async fn handle_requests_from_stream(
&self,
mut stream: AccountHandlerControlRequestStream,
) -> Result<(), Error> {
while let Some(req) = await!(stream.try_next())? {
await!(self.handle_request(req))?;
}
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 { context, id, responder } => {
let response = await!(self.create_account(id.into(), context));
responder.send(response)?;
}
AccountHandlerControlRequest::LoadAccount { context, id, responder } => {
let response = await!(self.load_account(id.into(), context));
responder.send(response)?;
}
AccountHandlerControlRequest::RemoveAccount { responder } => {
let response = self.remove_account();
responder.send(response)?;
}
AccountHandlerControlRequest::GetAccount {
auth_context_provider,
account,
responder,
} => {
let response = self.get_account(auth_context_provider, account);
responder.send(response)?;
}
AccountHandlerControlRequest::Terminate { control_handle } => {
// TODO(jsankey): Close any open files once we have them and shutdown dependant
// channels on the account, personae, and token manager.
info!("Gracefully shutting down AccountHandler");
control_handle.shutdown();
}
}
Ok(())
}
/// Helper method which prepares an account for being attached, used for both loading and
/// creating accounts. Returns a AccountHandlerContextProxy and a write lock which is
/// pre-checked for existing accounts.
async fn init_account(
&self,
context: ClientEnd<AccountHandlerContextMarker>,
) -> Result<
(AccountHandlerContextProxy, RwLockWriteGuard<Option<Arc<Account>>>),
AccountManagerError,
> {
let context_proxy = context
.into_proxy()
.context("Invalid AccountHandlerContext given")
.account_manager_status(Status::InvalidRequest)?;
let account_lock = self.account.write();
if account_lock.is_some() {
Err(AccountManagerError::new(Status::InternalError)
.with_cause(format_err!("AccountHandler is already initialized")))
} else {
Ok((context_proxy, account_lock))
}
}
/// Creates a new Fuchsia account and attaches it to this handler.
async fn create_account(
&self,
id: LocalAccountId,
context: ClientEnd<AccountHandlerContextMarker>,
) -> Status {
await!(self.init_account(context))
.and_then(|(context_proxy, mut account_lock)| {
let account = Account::create(id, self.data_dir.clone(), context_proxy)?;
Ok(*account_lock = Some(Arc::new(account)))
})
.map_or_else(
|err| {
warn!("Failed creating Fuchsia account: {:?}", err);
err.status
},
|()| Status::Ok,
)
}
/// Loads an existing Fuchsia account and attaches it to this handler.
async fn load_account(
&self,
id: LocalAccountId,
context: ClientEnd<AccountHandlerContextMarker>,
) -> Status {
await!(self.init_account(context))
.and_then(|(context_proxy, mut account_lock)| {
let account = Account::load(id, self.data_dir.clone(), context_proxy)?;
Ok(*account_lock = Some(Arc::new(account)))
})
.map_or_else(
|err| {
warn!("Failed loading Fuchsia account: {:?}", err);
err.status
},
|()| Status::Ok,
)
}
fn remove_account(&self) -> Status {
let mut account_lock = self.account.write();
let account = match &*account_lock {
Some(account) => account,
None => {
warn!("No account is initialized or it has already been removed");
return Status::InvalidRequest;
}
};
match account.remove() {
Ok(()) => {
info!("Deleted Fuchsia account {:?}", &account.id());
*account_lock = None;
Status::Ok
}
Err(err) => {
warn!("Could not remove account: {:?}", err);
err.status
}
}
}
fn get_account(
&self,
auth_context_provider_client_end: ClientEnd<AuthenticationContextProviderMarker>,
account_server_end: ServerEnd<AccountMarker>,
) -> Status {
let account = if let Some(account) = &*self.account.read() {
Arc::clone(account)
} else {
warn!("AccountHandler not yet initialized");
return Status::NotFound;
};
let context = match auth_context_provider_client_end.into_proxy() {
Ok(acp) => AccountContext { auth_ui_context_provider: acp },
Err(err) => {
warn!("Error using AuthenticationContextProvider {:?}", err);
return Status::InvalidRequest;
}
};
let stream = match account_server_end.into_stream() {
Ok(stream) => stream,
Err(e) => {
warn!("Error opening Account channel {:?}", e);
return Status::IoError;
}
};
fasync::spawn(async move {
await!(account.handle_requests_from_stream(&context, stream))
.unwrap_or_else(|e| error!("Error handling Account channel {:?}", e))
});
Status::Ok
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util::*;
use account_common::FidlLocalAccountId;
use fidl::endpoints::create_endpoints;
use fidl_fuchsia_auth_account_internal::{
AccountHandlerControlMarker, AccountHandlerControlProxy,
};
use fuchsia_async as fasync;
use std::path::Path;
use std::sync::Arc;
// Will not match a randomly generated account id with high probability.
const WRONG_ACCOUNT_ID: u64 = 111111;
fn request_stream_test<TestFn, Fut>(tmp_dir: &Path, test_fn: TestFn)
where
TestFn: FnOnce(AccountHandlerControlProxy, ClientEnd<AccountHandlerContextMarker>) -> Fut,
Fut: Future<Output = Result<(), Error>>,
{
let mut executor = fasync::Executor::new().expect("Failed to create executor");
let test_object = AccountHandler::new(tmp_dir.into());
let fake_context = Arc::new(FakeAccountHandlerContext::new());
let ahc_client_end = spawn_context_channel(fake_context.clone());
let (client_end, server_end) = create_endpoints::<AccountHandlerControlMarker>().unwrap();
let proxy = client_end.into_proxy().unwrap();
let request_stream = server_end.into_stream().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, ahc_client_end)).expect("Executor run failed.")
}
#[test]
fn test_get_account_before_initialization() {
let location = TempLocation::new();
request_stream_test(&location.path, async move |proxy, _| {
let (_, account_server_end) = create_endpoints().unwrap();
let (acp_client_end, _) = create_endpoints().unwrap();
assert_eq!(
await!(proxy.get_account(acp_client_end, account_server_end))?,
Status::NotFound
);
Ok(())
});
}
#[test]
fn test_double_initialize() {
let location = TempLocation::new();
let path = &location.path;
request_stream_test(&path, async move |proxy, ahc_client_end| {
let status = await!(
proxy.create_account(ahc_client_end, TEST_ACCOUNT_ID.clone().as_mut().into())
)?;
assert_eq!(status, Status::Ok);
let fake_context_2 = Arc::new(FakeAccountHandlerContext::new());
let ahc_client_end_2 = spawn_context_channel(fake_context_2.clone());
assert_eq!(
await!(
proxy.create_account(ahc_client_end_2, TEST_ACCOUNT_ID.clone().as_mut().into())
)?,
Status::InternalError
);
Ok(())
});
}
#[test]
fn test_create_and_get_account() {
let location = TempLocation::new();
request_stream_test(&location.path, async move |account_handler_proxy, ahc_client_end| {
let status = await!(account_handler_proxy
.create_account(ahc_client_end, TEST_ACCOUNT_ID.clone().as_mut().into()))?;
assert_eq!(status, Status::Ok, "wtf");
let (account_client_end, account_server_end) = create_endpoints().unwrap();
let (acp_client_end, _) = create_endpoints().unwrap();
assert_eq!(
await!(account_handler_proxy.get_account(acp_client_end, account_server_end))?,
Status::Ok
);
// The account channel should now be usable.
let account_proxy = account_client_end.into_proxy().unwrap();
assert_eq!(
await!(account_proxy.get_auth_state())?,
(Status::Ok, Some(Box::new(AccountHandler::DEFAULT_AUTH_STATE)))
);
Ok(())
});
}
#[test]
fn test_create_and_load_account() {
// Check that an account is persisted when account handlers are restarted
let location = TempLocation::new();
request_stream_test(&location.path, async move |proxy, ahc_client_end| {
let status = await!(
proxy.create_account(ahc_client_end, TEST_ACCOUNT_ID.clone().as_mut().into())
)?;
assert_eq!(status, Status::Ok);
Ok(())
});
request_stream_test(&location.path, async move |proxy, ahc_client_end| {
assert_eq!(
await!(proxy.load_account(ahc_client_end, TEST_ACCOUNT_ID.clone().as_mut().into()))?,
Status::Ok
);
Ok(())
});
}
#[test]
fn test_create_and_remove_account() {
let location = TempLocation::new();
request_stream_test(&location.path, async move |proxy, ahc_client_end| {
let status = await!(
proxy.create_account(ahc_client_end, TEST_ACCOUNT_ID.clone().as_mut().into())
)?;
assert_eq!(status, Status::Ok);
assert_eq!(await!(proxy.remove_account())?, Status::Ok);
Ok(())
});
}
#[test]
fn test_remove_account_before_initialization() {
let location = TempLocation::new();
request_stream_test(&location.path, async move |proxy, _| {
assert_eq!(await!(proxy.remove_account())?, Status::InvalidRequest);
Ok(())
});
}
#[test]
fn test_create_and_remove_account_twice() {
let location = TempLocation::new();
request_stream_test(&location.path, async move |proxy, ahc_client_end| {
let status = await!(
proxy.create_account(ahc_client_end, TEST_ACCOUNT_ID.clone().as_mut().into())
)?;
assert_eq!(status, Status::Ok);
assert_eq!(await!(proxy.remove_account())?, Status::Ok);
assert_eq!(
await!(proxy.remove_account())?,
Status::InvalidRequest // You can only remove once
);
Ok(())
});
}
#[test]
fn test_load_account_not_found() {
let location = TempLocation::new();
request_stream_test(&location.path, async move |proxy, ahc_client_end| {
assert_eq!(
await!(proxy.load_account(
ahc_client_end,
&mut FidlLocalAccountId { id: WRONG_ACCOUNT_ID }
))?,
Status::NotFound
);
Ok(())
});
}
}