blob: 79383a8a60608fbbf92afa03d0754824a6439910 [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 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)
);
}
}