| // 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::auth_provider_supplier::AuthProviderSupplier; |
| use failure::{Error, ResultExt}; |
| use fidl_fuchsia_auth::{ |
| AuthProviderConfig, TokenManagerFactoryRequest, TokenManagerFactoryRequestStream, |
| }; |
| use fuchsia_async as fasync; |
| use futures::prelude::*; |
| use log::{info, warn}; |
| use parking_lot::Mutex; |
| use std::collections::HashMap; |
| use std::path::Path; |
| use std::sync::Arc; |
| use token_manager::{TokenManagerContext, TokenManagerError}; |
| |
| // The directory to use for token manager databases |
| const DB_DIR: &str = "/data/auth"; |
| // The file suffix to use for token manager databases. This string is appended to the user id. |
| const DB_SUFFIX: &str = "_token_store.json"; |
| |
| type TokenManager = token_manager::TokenManager<AuthProviderSupplier>; |
| |
| /// A factory to create instances of the TokenManager for individual users. |
| pub struct TokenManagerFactory { |
| /// A map storing the single TokenManager instance that will be used for each account. |
| /// |
| /// Entries are created and added on the first request for a user, and are defined as an `Arc` |
| /// `Mutex` so they can be used in asynchronous closures that require mutable access and static |
| /// lifetime. |
| user_to_token_manager: Mutex<HashMap<String, Arc<TokenManager>>>, |
| |
| /// The auth provider configuration that this token manager was initially created with. |
| /// |
| /// Note: We are migrating to a build time configuration of auth providers, at which time this |
| /// will be unnecessary, but in the meantime a caller could attempt to specify two different |
| /// auth provider configuations in different calls to the factory. We do not support this since |
| /// it would infer separate token databases for each potential caller. Instead, we remember the |
| /// set of auth provider configuration that was used on the first call and return an error if |
| /// this is not constant across future calls. |
| auth_provider_configs: Mutex<Vec<AuthProviderConfig>>, |
| |
| /// An object capable of launching and opening connections on components implementing the |
| /// AuthProviderFactory interface. This is populated on the first call that provides auth |
| /// provider configuration. |
| auth_provider_supplier: Mutex<Option<AuthProviderSupplier>>, |
| } |
| |
| impl TokenManagerFactory { |
| /// Creates a new TokenManagerFactory. |
| pub fn new() -> TokenManagerFactory { |
| TokenManagerFactory { |
| user_to_token_manager: Mutex::new(HashMap::new()), |
| auth_provider_configs: Mutex::new(Vec::new()), |
| auth_provider_supplier: Mutex::new(None), |
| } |
| } |
| |
| /// Asynchronously handles the supplied stream of `TokenManagerFactoryRequest` messages. |
| /// |
| /// This method will only return an Err for errors that should be considered fatal for the |
| /// factory. Not that currently no failure modes meet this condition since requests for |
| /// different users are independant. |
| pub async fn handle_requests_from_stream(&self, mut stream: TokenManagerFactoryRequestStream) { |
| while let Ok(Some(req)) = await!(stream.try_next()) { |
| await!(self.handle_request(req)).unwrap_or_else(|err| { |
| warn!("Error handling TokenManagerFactoryRequest: {:?}", err); |
| }); |
| } |
| } |
| |
| /// Asynchronously handles a single request to the TokenManagerFactory. |
| async fn handle_request(&self, req: TokenManagerFactoryRequest) -> Result<(), Error> { |
| match req { |
| TokenManagerFactoryRequest::GetTokenManager { |
| user_id, |
| application_url, |
| auth_provider_configs, |
| auth_context_provider, |
| token_manager: token_manager_server_end, |
| .. |
| } => { |
| if !self.is_auth_provider_config_consistent(&auth_provider_configs) { |
| warn!("Auth provider config inconsistent with previous request"); |
| return Ok(()); |
| }; |
| |
| let token_manager = self |
| .get_token_manager(user_id, auth_provider_configs) |
| .context("Error creating TokenManager")?; |
| let context = TokenManagerContext { |
| application_url, |
| auth_ui_context_provider: auth_context_provider.into_proxy()?, |
| }; |
| let stream = token_manager_server_end |
| .into_stream() |
| .context("Error creating request stream")?; |
| |
| fasync::spawn( |
| async move { |
| await!(token_manager.handle_requests_from_stream(&context, stream)) |
| .unwrap_or_else(|err| { |
| warn!("Error handling TokenManager channel: {:?}", err); |
| }) |
| }, |
| ); |
| Ok(()) |
| } |
| } |
| } |
| |
| /// Returns true iff the supplied `auth_provider_configs` are equal to any previous invocations |
| /// on this factory. |
| fn is_auth_provider_config_consistent( |
| &self, auth_provider_configs: &Vec<AuthProviderConfig>, |
| ) -> bool { |
| let mut previous_auth_provider_configs = self.auth_provider_configs.lock(); |
| if previous_auth_provider_configs.is_empty() { |
| previous_auth_provider_configs |
| .extend(auth_provider_configs.iter().map(clone_auth_provider_config)); |
| true |
| } else { |
| *previous_auth_provider_configs == *auth_provider_configs |
| } |
| } |
| |
| /// Returns an `AuthProviderSupplier` for the supplied `auth_provider_configs`, or any errors |
| /// encountered while creating one. If an auth provider supplier has been previously created, |
| /// it will be cloned. |
| fn get_auth_provider_supplier( |
| &self, auth_provider_configs: Vec<AuthProviderConfig>, |
| ) -> Result<AuthProviderSupplier, TokenManagerError> { |
| let mut auth_provider_supplier_lock = self.auth_provider_supplier.lock(); |
| match &*auth_provider_supplier_lock { |
| Some(auth_provider_supplier) => Ok(auth_provider_supplier.clone()), |
| None => { |
| let auth_provider_supplier = AuthProviderSupplier::new(auth_provider_configs); |
| auth_provider_supplier_lock.get_or_insert(auth_provider_supplier.clone()); |
| Ok(auth_provider_supplier) |
| } |
| } |
| } |
| |
| /// Returns a Result containing a TokenManager, or any errors encountered while creating one. |
| /// The TokenManager is retrieved from the user map if one already exists, or is created |
| /// and added to the map if not. |
| fn get_token_manager( |
| &self, user_id: String, auth_provider_configs: Vec<AuthProviderConfig>, |
| ) -> Result<Arc<TokenManager>, Error> { |
| let mut user_to_token_manager = self.user_to_token_manager.lock(); |
| if let Some(token_manager) = user_to_token_manager.get(&user_id) { |
| return Ok(Arc::clone(token_manager)); |
| }; |
| |
| info!("Creating token manager for user {}", user_id); |
| let db_path = Path::new(DB_DIR).join(user_id.clone() + DB_SUFFIX); |
| let token_manager = Arc::new(TokenManager::new( |
| &db_path, |
| self.get_auth_provider_supplier(auth_provider_configs)?, |
| )?); |
| user_to_token_manager.insert(user_id, Arc::clone(&token_manager)); |
| Ok(token_manager) |
| } |
| } |
| |
| /// A helper function to clone an `AuthProviderConfig`, currently required since the FIDL bindings |
| /// do not derive clone. |
| fn clone_auth_provider_config(other: &AuthProviderConfig) -> AuthProviderConfig { |
| AuthProviderConfig { |
| auth_provider_type: other.auth_provider_type.clone(), |
| url: other.url.clone(), |
| params: other.params.clone(), |
| } |
| } |