| // 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(()) |
| }); |
| } |
| |
| } |