blob: ac2d3bec56f9e8a7ebb1e7b900dcbbb2d4a6df64 [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::{ResultExt as TokenManagerResultExt, TokenManagerError};
use failure::{format_err, ResultExt};
use fidl::endpoints::{ClientEnd, ServerEnd};
use fidl_fuchsia_auth::{
AuthProviderConfig, AuthProviderFactoryMarker, AuthProviderFactoryProxy, AuthProviderMarker,
AuthProviderStatus, Status,
};
use fuchsia_app::client::{App, Launcher};
use fuchsia_zircon as zx;
use log::info;
use parking_lot::Mutex;
use std::sync::Arc;
/// The information neccesary to retain an open connection to the `AuthProviderFactory` interface
/// of a launched component.
struct ConnectionState {
// Note: The app must remain in scope for the connection to be retained, but never needs to be
// read.
_app: App,
factory_proxy: Arc<AuthProviderFactoryProxy>,
}
/// A type capable of launching a particular component implementing the `AuthProviderFactory`
/// interface and acquiring `AuthProvider` connections from that component. Launching is
/// performed on demand.
///
/// Note: This type is not used by TokenManager directly, but is helpful for clients needing to
/// implement the `AuthProviderSupplier` trait.
pub struct AuthProviderConnection {
/// The URL that should be used to launch the component.
component_url: String,
/// Optional params to be passed to the component at launch.
params: Option<Vec<String>>,
/// The state needed to retain a connection, once one has been created.
connection_state: Mutex<Option<ConnectionState>>,
}
impl AuthProviderConnection {
/// Creates a new `AuthProviderConnection` from the supplied `AuthProviderConfig`.
pub fn from_config(config: AuthProviderConfig) -> Self {
AuthProviderConnection {
component_url: config.url,
params: config.params,
connection_state: Mutex::new(None),
}
}
/// Creates a new `AuthProviderClient` from the supplied `AuthProviderConfig` reference.
pub fn from_config_ref(config: &AuthProviderConfig) -> Self {
AuthProviderConnection {
component_url: config.url.clone(),
params: config.params.clone(),
connection_state: Mutex::new(None),
}
}
/// Returns the component_url supplied at construction
pub fn component_url(&self) -> &str {
&self.component_url
}
/// Returns an `AuthProviderFactoryProxy` for opening new `AuthProvider` connections. If
/// a component has previously been launched this is used, otherwise a fresh component is
/// launched and its proxy is stored for future use.
fn get_factory_proxy(&self) -> Result<Arc<AuthProviderFactoryProxy>, TokenManagerError> {
// If a factory proxy has already been created return it.
let mut connection_state_lock = self.connection_state.lock();
if let Some(connection_state) = &*connection_state_lock {
return Ok(Arc::clone(&connection_state.factory_proxy));
}
// Launch the auth provider and connect to its factory interface.
info!("Launching AuthProvider component: {}", self.component_url);
let launcher = Launcher::new()
.context("Failed to start launcher")
.token_manager_status(Status::UnknownError)?;
let app = launcher
.launch(self.component_url.clone(), self.params.clone())
.context("Failed to launch AuthProviderFactory")
.token_manager_status(Status::AuthProviderServiceUnavailable)?;
let factory_proxy = Arc::new(
app.connect_to_service(AuthProviderFactoryMarker)
.context("Failed to connect to AuthProviderFactory")
.token_manager_status(Status::AuthProviderServiceUnavailable)?,
);
connection_state_lock.get_or_insert(ConnectionState {
_app: app,
factory_proxy: Arc::clone(&factory_proxy),
});
Ok(factory_proxy)
}
/// Connects the supplied `ServerEnd` to the `AuthProvider`. If a component has previously been
/// launched this is used, otherwise a fresh component is launched.
pub async fn connect(
&self, server_end: ServerEnd<AuthProviderMarker>,
) -> Result<(), TokenManagerError> {
let factory_proxy = self.get_factory_proxy()?;
match await!(factory_proxy.get_auth_provider(server_end)) {
Ok(AuthProviderStatus::Ok) => Ok(()),
Ok(status) => Err(
TokenManagerError::new(Status::AuthProviderServiceUnavailable)
.with_cause(format_err!("Error getting auth provider: {:?}", status)),
),
Err(err) => Err(
TokenManagerError::new(Status::AuthProviderServiceUnavailable)
.with_cause(format_err!("GetAuthProvider method failed with {:?}", err)),
),
}
}
/// Returns a `ClientEnd` for communicating with the `AuthProvider`. If a component has
/// previously been launched this is used, otherwise a fresh component is launched.
pub async fn get(&self) -> Result<ClientEnd<AuthProviderMarker>, TokenManagerError> {
let (server_chan, client_chan) =
zx::Channel::create().token_manager_status(Status::UnknownError)?;
await!(self.connect(ServerEnd::new(server_chan)))?;
Ok(ClientEnd::new(client_chan))
}
}