blob: 9a412218ef9ee05a2babd5ef03f54b7aef780842 [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;
use account_common::{FidlLocalAccountId, LocalAccountId};
use failure::Error;
use fidl::encoding::OutOfLine;
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::{
AccountHandlerControlRequest, AccountHandlerControlRequestStream,
};
use fuchsia_async as fasync;
use futures::prelude::*;
use log::{error, info, warn};
use parking_lot::RwLock;
use std::fs;
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>>>,
accounts_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(accounts_dir: PathBuf) -> AccountHandler {
Self {
account: RwLock::new(None),
accounts_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())? {
self.handle_request(req)?;
}
Ok(())
}
/// Dispatches an `AccountHandlerControlRequest` message to the appropriate handler method
/// based on its type.
pub fn handle_request(&self, req: AccountHandlerControlRequest) -> Result<(), fidl::Error> {
match req {
AccountHandlerControlRequest::CreateAccount {
context: _,
responder,
} => {
let response = self.create_account();
responder.send(
response.0,
response
.1
.map(FidlLocalAccountId::from)
.as_mut()
.map(OutOfLine),
)?;
}
AccountHandlerControlRequest::LoadAccount {
context: _,
id,
responder,
} => {
let response = self.load_account(id.into());
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(())
}
fn create_account(&self) -> (Status, Option<LocalAccountId>) {
let mut account_lock = self.account.write();
if account_lock.is_some() {
warn!("AccountHandler is already initialized");
(Status::InvalidRequest, None)
} else {
// TODO(jsankey): Longer term, local ID may need to be related to the global ID rather
// than just a random number.
let local_account_id = LocalAccountId::new(rand::random::<u64>());
*account_lock = Some(Arc::new(Account::new(local_account_id.clone())));
let account_dir = self
.accounts_dir
.join(local_account_id.to_canonical_string());
// TODO: Drop logs that expose the account ID
match fs::create_dir_all(account_dir) {
Err(err) => {
warn!("Could not create account dir: {:?}", err);
(Status::IoError, None)
}
Ok(()) => {
info!("Created new Fuchsia account {:?}", local_account_id);
(Status::Ok, Some(local_account_id))
}
}
}
}
fn load_account(&self, id: LocalAccountId) -> Status {
let mut account_lock = self.account.write();
if account_lock.is_some() {
warn!("AccountHandler is already initialized");
Status::InvalidRequest
} else {
if self.accounts_dir.join(id.to_canonical_string()).exists() {
// TODO(dnordstrom): Implement reading the actual db file. Currently we get a new
// persona id.
*account_lock = Some(Arc::new(Account::new(id.clone())));
Status::Ok
} else {
Status::NotFound
}
}
}
fn remove_account(&self) -> Status {
// TODO(dnordstrom): Implement this method once accounts are persisted on disk.
warn!("RemoveAccount method not yet implemented");
Status::InternalError
}
fn get_account(
&self, _auth_context_provider: 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 stream = match account_server_end.into_stream() {
Ok(stream) => stream,
Err(e) => {
error!("Error opening Account channel {:?}", e);
return Status::IoError;
}
};
fasync::spawn(
async move {
await!(account.handle_requests_from_stream(stream))
.unwrap_or_else(|e| error!("Error handling Account channel {:?}", e))
},
);
Status::Ok
}
}
#[cfg(test)]
mod tests {
use super::*;
use fidl::endpoints::RequestStream;
use fidl_fuchsia_auth_account::AccountProxy;
use fidl_fuchsia_auth_account_internal::{
AccountHandlerControlProxy, AccountHandlerControlRequestStream,
};
use fuchsia_async as fasync;
use fuchsia_zircon as zx;
use std::sync::{Arc, Mutex};
use tempfile::TempDir;
// Will not match a randomly generated account id with high probability.
const WRONG_ACCOUNT_ID: u64 = 111111;
struct TempLocation {
/// A fresh temp directory that will be deleted when this object is dropped.
_dir: TempDir,
/// A path within the temp dir to use for writing the db.
path: PathBuf,
}
impl TempLocation {
/// Return a writable, temporary location and optionally create it as a directory.
fn new() -> TempLocation {
let dir = TempDir::new().unwrap();
let path = dir.path().to_path_buf();
TempLocation { _dir: dir, path }
}
}
fn request_stream_test<TestFn, Fut>(test_object: AccountHandler, test_fn: TestFn)
where
TestFn: FnOnce(AccountHandlerControlProxy) -> 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 =
AccountHandlerControlProxy::new(fasync::Channel::from_channel(client_chan).unwrap());
let request_stream = AccountHandlerControlRequestStream::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.")
}
#[test]
fn test_get_account_before_initialization() {
let location = TempLocation::new();
request_stream_test(AccountHandler::new(location.path), async move |proxy| {
let (account_server_chan, _) = zx::Channel::create().unwrap();
let (_, acp_client_chan) = zx::Channel::create().unwrap();
assert_eq!(
await!(proxy.get_account(
ClientEnd::new(acp_client_chan),
ServerEnd::new(account_server_chan)
))?,
Status::NotFound
);
Ok(())
});
}
#[test]
fn test_double_initialize() {
let location = TempLocation::new();
request_stream_test(AccountHandler::new(location.path), async move |proxy| {
let (_, ahc_client_chan_1) = zx::Channel::create().unwrap();
let (status, account_id_optional) =
await!(proxy.create_account(ClientEnd::new(ahc_client_chan_1)))?;
assert_eq!(status, Status::Ok);
assert!(account_id_optional.is_some());
let (_, ahc_client_chan_2) = zx::Channel::create().unwrap();
assert_eq!(
await!(proxy.create_account(ClientEnd::new(ahc_client_chan_2)))?,
(Status::InvalidRequest, None)
);
Ok(())
});
}
#[test]
fn test_create_and_get_account() {
let location = TempLocation::new();
request_stream_test(
AccountHandler::new(location.path),
async move |account_handler_proxy| {
let (_, ahc_client_chan) = zx::Channel::create().unwrap();
let (status, account_id_optional) =
await!(account_handler_proxy.create_account(ClientEnd::new(ahc_client_chan)))?;
assert_eq!(status, Status::Ok);
assert!(account_id_optional.is_some());
let (account_server_chan, account_client_chan) = zx::Channel::create().unwrap();
let (_, acp_client_chan) = zx::Channel::create().unwrap();
assert_eq!(
await!(account_handler_proxy.get_account(
ClientEnd::new(acp_client_chan),
ServerEnd::new(account_server_chan)
))?,
Status::Ok
);
// The account channel should now be usable.
let account_proxy =
AccountProxy::new(fasync::Channel::from_channel(account_client_chan).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();
let acc_id = Arc::new(Mutex::new(FidlLocalAccountId { id: 0 }));
let acc_id_borrow = acc_id.clone();
request_stream_test(
AccountHandler::new(location.path.clone()),
async move |account_handler_proxy| {
let (_, ahc_client_chan) = zx::Channel::create().unwrap();
let (status, account_id_optional) =
await!(account_handler_proxy.create_account(ClientEnd::new(ahc_client_chan)))?;
assert_eq!(status, Status::Ok);
assert!(account_id_optional.is_some());
let mut acc_id = acc_id_borrow.lock().unwrap();
*acc_id = *account_id_optional.unwrap();
Ok(())
},
);
request_stream_test(AccountHandler::new(location.path), async move |proxy| {
let (_, ahc_client_chan) = zx::Channel::create().unwrap();
assert_eq!(
await!(proxy
.load_account(ClientEnd::new(ahc_client_chan), &mut acc_id.lock().unwrap()))?,
Status::Ok
);
Ok(())
});
}
#[test]
fn test_load_account_not_found() {
let location = TempLocation::new();
request_stream_test(AccountHandler::new(location.path), async move |proxy| {
let (_, ahc_client_chan) = zx::Channel::create().unwrap();
assert_eq!(
await!(proxy.load_account(
ClientEnd::new(ahc_client_chan),
&mut FidlLocalAccountId {
id: WRONG_ACCOUNT_ID
}
))?,
Status::NotFound
);
Ok(())
});
}
}