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