| // 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 anyhow::{format_err, Error}; |
| use fidl_fuchsia_auth::Status; |
| use fidl_fuchsia_identity_external::Error as ExternalApiError; |
| use thiserror::Error; |
| use token_cache::AuthCacheError; |
| use token_store::AuthDbError; |
| |
| /// An extension trait to simplify conversion of results based on general errors to |
| /// TokenManagerErrors. |
| pub trait ResultExt<T, E> { |
| /// Wraps the error in a non-fatal `TokenManagerError` with the supplied `Status`. |
| fn token_manager_status(self, status: Status) -> Result<T, TokenManagerError>; |
| } |
| |
| impl<T, E> ResultExt<T, E> for Result<T, E> |
| where |
| E: Into<Error> + Send + Sync + Sized, |
| { |
| fn token_manager_status(self, status: Status) -> Result<T, TokenManagerError> { |
| self.map_err(|err| TokenManagerError::new(status).with_cause(err)) |
| } |
| } |
| |
| /// An Error type for problems encountered in the token manager. Each error contains the |
| /// `fuchsia.auth.Status` that should be reported back to the client and an indication of whether |
| /// it is fatal. |
| #[derive(Debug, Error)] |
| #[error("TokenManager error, returning {:?}. ({:?})", status, cause)] |
| pub struct TokenManagerError { |
| /// The most appropriate `fuchsia.auth.Status` to describe this problem. |
| pub status: Status, |
| /// Whether this error should be considered fatal, i.e. whether it should terminate processing |
| /// of all requests on the current channel. |
| pub fatal: bool, |
| /// The cause of this error, if available. |
| pub cause: Option<Error>, |
| } |
| |
| impl TokenManagerError { |
| /// Constructs a new non-fatal error based on the supplied `Status`. |
| pub fn new(status: Status) -> Self { |
| TokenManagerError { status, fatal: false, cause: None } |
| } |
| |
| /// Sets a cause on the current error. |
| pub fn with_cause<T: Into<Error>>(mut self, cause: T) -> Self { |
| self.cause = Some(cause.into()); |
| self |
| } |
| } |
| |
| impl From<Status> for TokenManagerError { |
| fn from(status: Status) -> Self { |
| TokenManagerError::new(status) |
| } |
| } |
| |
| impl From<AuthDbError> for TokenManagerError { |
| fn from(auth_db_error: AuthDbError) -> Self { |
| let (status, fatal) = match &auth_db_error { |
| AuthDbError::InvalidArguments => (Status::InvalidRequest, true), |
| AuthDbError::DbInvalid => (Status::InternalError, true), |
| AuthDbError::CredentialNotFound => (Status::UserNotFound, false), |
| AuthDbError::SerializationError => (Status::InternalError, false), |
| _ => (Status::UnknownError, false), |
| }; |
| TokenManagerError { status, fatal, cause: Some(Error::from(auth_db_error)) } |
| } |
| } |
| |
| impl From<AuthCacheError> for TokenManagerError { |
| fn from(auth_cache_error: AuthCacheError) -> Self { |
| TokenManagerError { |
| status: match auth_cache_error { |
| AuthCacheError::InvalidArguments => Status::InvalidRequest, |
| AuthCacheError::KeyNotFound => Status::UserNotFound, |
| }, |
| // No cache failures are persistent and hence none are fatal. |
| fatal: false, |
| cause: Some(Error::from(auth_cache_error)), |
| } |
| } |
| } |
| |
| impl From<ExternalApiError> for TokenManagerError { |
| fn from(external_api_error: ExternalApiError) -> Self { |
| TokenManagerError { |
| status: match external_api_error { |
| ExternalApiError::Unknown => Status::UnknownError, |
| ExternalApiError::Internal => Status::InternalError, |
| ExternalApiError::Config => Status::AuthProviderServiceUnavailable, |
| ExternalApiError::UnsupportedOperation => Status::UnknownError, |
| ExternalApiError::InvalidRequest => Status::InvalidRequest, |
| ExternalApiError::Resource => Status::IoError, |
| ExternalApiError::Network => Status::NetworkError, |
| ExternalApiError::Server => Status::AuthProviderServerError, |
| ExternalApiError::InvalidToken => Status::ReauthRequired, |
| ExternalApiError::InsufficientToken => Status::ReauthRequired, |
| ExternalApiError::Aborted => Status::UserCancelled, |
| }, |
| fatal: false, |
| cause: Some(format_err!("Auth provider error: {:?}", external_api_error)), |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use anyhow::format_err; |
| |
| const TEST_STATUS: Status = Status::UnknownError; |
| |
| fn create_test_error() -> Error { |
| format_err!("Test error") |
| } |
| |
| #[test] |
| fn test_new() { |
| let cause = format_err!("Example cause"); |
| let error = TokenManagerError::new(TEST_STATUS).with_cause(cause); |
| assert_eq!(error.status, TEST_STATUS); |
| assert!(!error.fatal); |
| assert!(error.cause.is_some()); |
| } |
| |
| #[test] |
| fn test_from_status() { |
| let error: TokenManagerError = TEST_STATUS.into(); |
| assert_eq!(error.status, TEST_STATUS); |
| assert!(!error.fatal); |
| assert!(error.cause.is_none()); |
| } |
| |
| #[test] |
| fn test_token_manager_status() { |
| let test_result: Result<(), Error> = Err(create_test_error()); |
| let wrapped_result = test_result.token_manager_status(TEST_STATUS); |
| assert_eq!(wrapped_result.as_ref().unwrap_err().status, TEST_STATUS); |
| assert_eq!( |
| format!("{:?}", wrapped_result.unwrap_err().cause.unwrap()), |
| format!("{:?}", create_test_error()) |
| ); |
| } |
| |
| #[test] |
| fn test_from_auth_db_error() { |
| let err = TokenManagerError::from(AuthDbError::CredentialNotFound); |
| let err_fatal = TokenManagerError::from(AuthDbError::DbInvalid); |
| assert_eq!( |
| (format!("{:?}", err.cause.as_ref().unwrap()), err.fatal, err.status), |
| ("credential not found".to_string(), false, Status::UserNotFound) |
| ); |
| assert_eq!( |
| (format!("{:?}", err_fatal.cause.as_ref().unwrap()), err_fatal.fatal, err_fatal.status), |
| ("database contents could not be parsed".to_string(), true, Status::InternalError) |
| ); |
| } |
| |
| #[test] |
| fn test_from_auth_cache_error() { |
| let err = TokenManagerError::from(AuthCacheError::InvalidArguments); |
| assert_eq!( |
| (format!("{:?}", err.cause.as_ref().unwrap()), err.fatal, err.status), |
| ("invalid argument".to_string(), false, Status::InvalidRequest) |
| ); |
| } |
| } |