Skip to main content

aws_config/meta/
token.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Token providers that augment existing token providers to add functionality
7
8use aws_credential_types::provider::{
9    error::TokenError, future, token::ProvideToken, token::Result,
10};
11use aws_smithy_types::error::display::DisplayErrorContext;
12use std::borrow::Cow;
13use tracing::Instrument;
14
15use crate::meta::{ProviderAttempt, ProviderChainError};
16
17/// Access token provider that checks a series of inner providers.
18///
19/// Each provider will be evaluated in order:
20/// * If a provider returns valid [`Token`](aws_credential_types::Token) they will be returned immediately.
21///   No other token providers will be used.
22/// * Otherwise, if a provider returns [`TokenError::TokenNotLoaded`], the next provider will be checked.
23/// * Finally, if a provider returns any other error condition, an error will be returned immediately.
24///
25#[cfg_attr(
26    feature = "sso",
27    doc = r#"
28# Examples
29
30```no_run
31# fn example() {
32use aws_config::meta::token::TokenProviderChain;
33use aws_config::profile::ProfileFileTokenProvider;
34use aws_credential_types::Token;
35
36let provider = TokenProviderChain::first_try("Profile", ProfileFileTokenProvider::builder().build())
37    .or_else("Static", Token::new("example", None));
38# }
39```
40"#
41)]
42#[derive(Debug)]
43pub struct TokenProviderChain {
44    providers: Vec<(Cow<'static, str>, Box<dyn ProvideToken>)>,
45}
46
47impl TokenProviderChain {
48    /// Create a `TokenProviderChain` that begins by evaluating this provider
49    pub fn first_try(
50        name: impl Into<Cow<'static, str>>,
51        provider: impl ProvideToken + 'static,
52    ) -> Self {
53        TokenProviderChain {
54            providers: vec![(name.into(), Box::new(provider))],
55        }
56    }
57
58    /// Add a fallback provider to the token provider chain
59    pub fn or_else(
60        mut self,
61        name: impl Into<Cow<'static, str>>,
62        provider: impl ProvideToken + 'static,
63    ) -> Self {
64        self.providers.push((name.into(), Box::new(provider)));
65        self
66    }
67
68    /// Add a fallback to the default provider chain
69    #[cfg(feature = "sso")]
70    pub async fn or_default_provider(self) -> Self {
71        self.or_else(
72            "DefaultProviderChain",
73            crate::default_provider::token::default_provider().await,
74        )
75    }
76
77    /// Creates a token provider chain that starts with the default provider
78    #[cfg(feature = "sso")]
79    pub async fn default_provider() -> Self {
80        Self::first_try(
81            "DefaultProviderChain",
82            crate::default_provider::token::default_provider().await,
83        )
84    }
85
86    async fn token(&self) -> Result {
87        let mut attempts = Vec::with_capacity(self.providers.len());
88        for (name, provider) in &self.providers {
89            let span = tracing::debug_span!("token_provider_chain", provider = %name);
90            match provider.provide_token().instrument(span).await {
91                Ok(credentials) => {
92                    tracing::debug!(provider = %name, "loaded access token");
93                    return Ok(credentials);
94                }
95                Err(err @ TokenError::TokenNotLoaded(_)) => {
96                    tracing::debug!(provider = %name, context = %DisplayErrorContext(&err), "provider in chain did not provide an access token");
97                    attempts.push(ProviderAttempt::new(name.clone(), err));
98                }
99                Err(err) => {
100                    tracing::warn!(provider = %name, error = %DisplayErrorContext(&err), "provider failed to provide an access token");
101                    return Err(err);
102                }
103            }
104        }
105        Err(TokenError::not_loaded(ProviderChainError::new(attempts)))
106    }
107}
108
109impl ProvideToken for TokenProviderChain {
110    fn provide_token<'a>(&'a self) -> future::ProvideToken<'a>
111    where
112        Self: 'a,
113    {
114        future::ProvideToken::new(self.token())
115    }
116}