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(runtime_api_auth_scheme_id(scheme_name))
57        })
58        .collect::<Result<Vec<_>, _>>()
59        .map(AuthSchemePreference::from)
60}
61
62// This function is not automatically extensible. When a new scheme is introduced,
63// it must be manually updated for the scheme to be recognized in the environment configuration.
64//
65// Furthermore, this function does not return a predefined `AuthSchemeId` like `HTTP_BASIC_AUTH_SCHEME_ID`.
66// The predefined `SchemeId`s are gated behind the `http-auth` feature, but this function returns an `AuthSchemeId`
67// that wraps the parsed string regardless of feature flags. If the feature is disabled, the returned
68// auth scheme preference is simply ignored during auth scheme resolution.
69fn runtime_api_auth_scheme_id(auth_scheme_name: &str) -> AuthSchemeId {
70    let runtime_api_auth_scheme_str = match auth_scheme_name {
71        "httpBasicAuth" => "http-basic-auth",
72        "httpDigestAuth" => "http-digest-auth",
73        "httpBearerAuth" => "http-bearer-auth",
74        "httpApiKeyAuth" => "http-api-key-auth",
75        "noAuth" => "no_auth",
76        otherwise => otherwise,
77    };
78    AuthSchemeId::from(Cow::Owned(runtime_api_auth_scheme_str.to_owned()))
79}
80
81#[derive(Debug)]
82pub(crate) struct InvalidAuthSchemeNamesCsv {
83    value: String,
84}
85
86impl fmt::Display for InvalidAuthSchemeNamesCsv {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        write!(
89            f,
90            "Not a valid comma-separated auth scheme names: {}",
91            self.value
92        )
93    }
94}
95
96impl std::error::Error for InvalidAuthSchemeNamesCsv {}
97
98#[cfg(test)]
99mod test {
100    use super::env;
101    use crate::{
102        default_provider::auth_scheme_preference::auth_scheme_preference_provider,
103        provider_config::ProviderConfig,
104    };
105    use aws_types::os_shim_internal::Env;
106    use tracing_test::traced_test;
107
108    #[tokio::test]
109    #[traced_test]
110    async fn log_error_on_invalid_value() {
111        let conf = ProviderConfig::empty().with_env(Env::from_slice(&[(
112            env::AUTH_SCHEME_PREFERENCE,
113            "scheme1, , \tscheme2",
114        )]));
115        assert_eq!(None, auth_scheme_preference_provider(&conf).await);
116        assert!(logs_contain(
117            "Not a valid comma-separated auth scheme names: Empty name found"
118        ));
119    }
120
121    #[cfg(feature = "sso")] // for aws-smithy-runtime-api/http-auth
122    mod http_auth_tests {
123        use super::env;
124        #[allow(deprecated)]
125        use crate::profile::profile_file::{ProfileFileKind, ProfileFiles};
126        use crate::{
127            default_provider::auth_scheme_preference::auth_scheme_preference_provider,
128            provider_config::ProviderConfig,
129        };
130        use aws_smithy_runtime_api::client::auth::AuthSchemePreference;
131        use aws_types::os_shim_internal::{Env, Fs};
132
133        #[tokio::test]
134        async fn environment_priority() {
135            let conf = ProviderConfig::empty()
136            .with_env(Env::from_slice(&[(
137                env::AUTH_SCHEME_PREFERENCE,
138                "aws.auth#sigv4, smithy.api#httpBasicAuth, smithy.api#httpDigestAuth, smithy.api#httpBearerAuth, smithy.api#httpApiKeyAuth",
139            )]))
140            .with_profile_config(
141                Some(
142                    #[allow(deprecated)]
143                    ProfileFiles::builder()
144                        .with_file(
145                            #[allow(deprecated)]
146                            ProfileFileKind::Config,
147                            "conf",
148                        )
149                        .build(),
150                ),
151                None,
152            )
153            .with_fs(Fs::from_slice(&[(
154                "conf",
155                "[default]\nauth_scheme_preference = scheme1, scheme2 , \tscheme3 \t",
156            )]));
157            assert_eq!(
158                AuthSchemePreference::from([
159                    aws_runtime::auth::sigv4::SCHEME_ID,
160                    aws_smithy_runtime_api::client::auth::http::HTTP_BASIC_AUTH_SCHEME_ID,
161                    aws_smithy_runtime_api::client::auth::http::HTTP_DIGEST_AUTH_SCHEME_ID,
162                    aws_smithy_runtime_api::client::auth::http::HTTP_BEARER_AUTH_SCHEME_ID,
163                    aws_smithy_runtime_api::client::auth::http::HTTP_API_KEY_AUTH_SCHEME_ID,
164                ]),
165                auth_scheme_preference_provider(&conf).await.unwrap()
166            );
167        }
168
169        #[tokio::test]
170        async fn load_from_profile() {
171            let conf = ProviderConfig::empty()
172            .with_profile_config(
173                Some(
174                    #[allow(deprecated)]
175                    ProfileFiles::builder()
176                        .with_file(
177                            #[allow(deprecated)]
178                            ProfileFileKind::Config,
179                            "conf",
180                        )
181                        .build(),
182                ),
183                None,
184            )
185            .with_fs(Fs::from_slice(&[(
186                "conf",
187                "[default]\nauth_scheme_preference = sigv4, httpBasicAuth, httpDigestAuth, \thttpBearerAuth \t, httpApiKeyAuth ",
188            )]));
189            assert_eq!(
190                AuthSchemePreference::from([
191                    aws_runtime::auth::sigv4::SCHEME_ID,
192                    aws_smithy_runtime_api::client::auth::http::HTTP_BASIC_AUTH_SCHEME_ID,
193                    aws_smithy_runtime_api::client::auth::http::HTTP_DIGEST_AUTH_SCHEME_ID,
194                    aws_smithy_runtime_api::client::auth::http::HTTP_BEARER_AUTH_SCHEME_ID,
195                    aws_smithy_runtime_api::client::auth::http::HTTP_API_KEY_AUTH_SCHEME_ID,
196                ]),
197                auth_scheme_preference_provider(&conf).await.unwrap()
198            );
199        }
200    }
201}