aws_config/default_provider/
auth_scheme_preference.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use crate::provider_config::ProviderConfig;
7use aws_runtime::env_config::EnvConfigValue;
8use aws_smithy_runtime_api::client::auth::{AuthSchemeId, AuthSchemePreference};
9use aws_smithy_types::error::display::DisplayErrorContext;
10use std::borrow::Cow;
11use std::fmt;
12
13mod env {
14    pub(super) const AUTH_SCHEME_PREFERENCE: &str = "AWS_AUTH_SCHEME_PREFERENCE";
15}
16
17mod profile_key {
18    pub(super) const AUTH_SCHEME_PREFERENCE: &str = "auth_scheme_preference";
19}
20
21/// Load the value for the auth scheme preference
22///
23/// This checks the following sources:
24/// 1. The environment variable `AWS_AUTH_SCHEME_PREFERENCE=scheme1,scheme2,scheme3`
25/// 2. The profile key `auth_scheme_preference=scheme1,scheme2,scheme3`
26///
27/// A scheme name can be either a fully qualified name or a shorthand with the namespace prefix trimmed.
28/// For example, valid scheme names include "aws.auth#sigv4", "smithy.api#httpBasicAuth", "sigv4", and "httpBasicAuth".
29/// Whitespace (spaces or tabs), including leading, trailing, and between names, is ignored.
30///
31/// Returns `None` if a parsed string component is empty when creating an `AuthSchemeId`.
32pub(crate) async fn auth_scheme_preference_provider(
33    provider_config: &ProviderConfig,
34) -> Option<AuthSchemePreference> {
35    let env = provider_config.env();
36    let profiles = provider_config.profile().await;
37
38    EnvConfigValue::new()
39        .env(env::AUTH_SCHEME_PREFERENCE)
40        .profile(profile_key::AUTH_SCHEME_PREFERENCE)
41        .validate(&env, profiles, parse_auth_scheme_names)
42        .map_err(|err| tracing::warn!(err = %DisplayErrorContext(&err), "invalid value for `AuthSchemePreference`"))
43        .unwrap_or(None)
44}
45
46fn parse_auth_scheme_names(csv: &str) -> Result<AuthSchemePreference, InvalidAuthSchemeNamesCsv> {
47    csv.split(',')
48        .map(|s| {
49            let trimmed = s.trim().replace([' ', '\t'], "");
50            if trimmed.is_empty() {
51                return Err(InvalidAuthSchemeNamesCsv {
52                    value: format!("Empty name found in `{csv}`."),
53                });
54            }
55            let scheme_name = trimmed.split('#').next_back().unwrap_or(&trimmed);
56            Ok(AuthSchemeId::from(Cow::Owned(scheme_name.to_owned())))
57        })
58        .collect::<Result<Vec<_>, _>>()
59        .map(AuthSchemePreference::from)
60}
61
62#[derive(Debug)]
63pub(crate) struct InvalidAuthSchemeNamesCsv {
64    value: String,
65}
66
67impl fmt::Display for InvalidAuthSchemeNamesCsv {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        write!(
70            f,
71            "Not a valid comma-separated auth scheme names: {}",
72            self.value
73        )
74    }
75}
76
77impl std::error::Error for InvalidAuthSchemeNamesCsv {}
78
79#[cfg(test)]
80mod test {
81    use super::env;
82    use crate::{
83        default_provider::auth_scheme_preference::auth_scheme_preference_provider,
84        provider_config::ProviderConfig,
85    };
86    use aws_types::os_shim_internal::Env;
87    use tracing_test::traced_test;
88
89    #[tokio::test]
90    #[traced_test]
91    async fn log_error_on_invalid_value() {
92        let conf = ProviderConfig::empty().with_env(Env::from_slice(&[(
93            env::AUTH_SCHEME_PREFERENCE,
94            "scheme1, , \tscheme2",
95        )]));
96        assert_eq!(None, auth_scheme_preference_provider(&conf).await);
97        assert!(logs_contain(
98            "Not a valid comma-separated auth scheme names: Empty name found"
99        ));
100    }
101
102    #[cfg(feature = "sso")] // for aws-smithy-runtime-api/http-auth
103    mod http_auth_tests {
104        use super::env;
105        #[allow(deprecated)]
106        use crate::profile::profile_file::{ProfileFileKind, ProfileFiles};
107        use crate::{
108            default_provider::auth_scheme_preference::auth_scheme_preference_provider,
109            provider_config::ProviderConfig,
110        };
111        use aws_smithy_runtime_api::client::auth::AuthSchemePreference;
112        use aws_types::os_shim_internal::{Env, Fs};
113
114        #[tokio::test]
115        async fn environment_priority() {
116            let conf = ProviderConfig::empty()
117            .with_env(Env::from_slice(&[(
118                env::AUTH_SCHEME_PREFERENCE,
119                "aws.auth#sigv4, smithy.api#httpBasicAuth, smithy.api#httpDigestAuth, smithy.api#httpBearerAuth, smithy.api#httpApiKeyAuth",
120            )]))
121            .with_profile_config(
122                Some(
123                    #[allow(deprecated)]
124                    ProfileFiles::builder()
125                        .with_file(
126                            #[allow(deprecated)]
127                            ProfileFileKind::Config,
128                            "conf",
129                        )
130                        .build(),
131                ),
132                None,
133            )
134            .with_fs(Fs::from_slice(&[(
135                "conf",
136                "[default]\nauth_scheme_preference = scheme1, scheme2 , \tscheme3 \t",
137            )]));
138            assert_eq!(
139                AuthSchemePreference::from([
140                    aws_runtime::auth::sigv4::SCHEME_ID,
141                    aws_smithy_runtime_api::client::auth::http::HTTP_BASIC_AUTH_SCHEME_ID,
142                    aws_smithy_runtime_api::client::auth::http::HTTP_DIGEST_AUTH_SCHEME_ID,
143                    aws_smithy_runtime_api::client::auth::http::HTTP_BEARER_AUTH_SCHEME_ID,
144                    aws_smithy_runtime_api::client::auth::http::HTTP_API_KEY_AUTH_SCHEME_ID,
145                ]),
146                auth_scheme_preference_provider(&conf).await.unwrap()
147            );
148        }
149
150        #[tokio::test]
151        async fn load_from_profile() {
152            let conf = ProviderConfig::empty()
153            .with_profile_config(
154                Some(
155                    #[allow(deprecated)]
156                    ProfileFiles::builder()
157                        .with_file(
158                            #[allow(deprecated)]
159                            ProfileFileKind::Config,
160                            "conf",
161                        )
162                        .build(),
163                ),
164                None,
165            )
166            .with_fs(Fs::from_slice(&[(
167                "conf",
168                "[default]\nauth_scheme_preference = sigv4, httpBasicAuth, httpDigestAuth, \thttpBearerAuth \t, httpApiKeyAuth ",
169            )]));
170            assert_eq!(
171                AuthSchemePreference::from([
172                    aws_runtime::auth::sigv4::SCHEME_ID,
173                    aws_smithy_runtime_api::client::auth::http::HTTP_BASIC_AUTH_SCHEME_ID,
174                    aws_smithy_runtime_api::client::auth::http::HTTP_DIGEST_AUTH_SCHEME_ID,
175                    aws_smithy_runtime_api::client::auth::http::HTTP_BEARER_AUTH_SCHEME_ID,
176                    aws_smithy_runtime_api::client::auth::http::HTTP_API_KEY_AUTH_SCHEME_ID,
177                ]),
178                auth_scheme_preference_provider(&conf).await.unwrap()
179            );
180        }
181    }
182}