rusoto_credential/
profile.rs

1//! The Credentials Provider for Credentials stored in a profile inside of a Credentials file.
2
3use std::collections::HashMap;
4use std::fs;
5use std::fs::File;
6use std::io::{BufRead, BufReader};
7use std::path::{Path, PathBuf};
8
9use async_trait::async_trait;
10use dirs_next::home_dir;
11use serde::Deserialize;
12use tokio::process::Command;
13
14use crate::{non_empty_env_var, AwsCredentials, CredentialsError, ProvideAwsCredentials};
15
16const AWS_CONFIG_FILE: &str = "AWS_CONFIG_FILE";
17const AWS_PROFILE: &str = "AWS_PROFILE";
18const AWS_SHARED_CREDENTIALS_FILE: &str = "AWS_SHARED_CREDENTIALS_FILE";
19const DEFAULT: &str = "default";
20const REGION: &str = "region";
21
22/// Provides AWS credentials from a profile in a credentials file, or from a credential process.
23///
24/// # Warning
25///
26/// This provider allows the [`credential_process`][credential_process] option, a method of
27/// sourcing credentials from an external process. This can potentially be dangerous, so proceed
28/// with caution. Other credential providers should be preferred if at all possible. If using this
29/// option, you should make sure that the config file is as locked down as possible using security
30/// best practices for your operating system.
31///
32/// [credential_process]: https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#sourcing-credentials-from-external-processes
33#[derive(Clone, Debug)]
34pub struct ProfileProvider {
35    /// The File Path the Credentials File is located at.
36    file_path: PathBuf,
37    /// The Profile Path to parse out of the Credentials File.
38    profile: String,
39}
40
41impl ProfileProvider {
42    /// Create a new `ProfileProvider` for the default credentials file path and profile name.
43    pub fn new() -> Result<ProfileProvider, CredentialsError> {
44        let profile_location = ProfileProvider::default_profile_location()?;
45        Ok(ProfileProvider::with_default_configuration(
46            profile_location,
47        ))
48    }
49
50    /// Create a new `ProfileProvider` for the credentials file at the given path, using
51    /// the given profile.
52    pub fn with_configuration<F, P>(file_path: F, profile: P) -> ProfileProvider
53    where
54        F: Into<PathBuf>,
55        P: Into<String>,
56    {
57        ProfileProvider {
58            file_path: file_path.into(),
59            profile: profile.into(),
60        }
61    }
62
63    /// Create a new `ProfileProvider` for the credentials file at the given path, using
64    /// the profile name from environment variable ```AWS_PROFILE``` or fall-back to ```"default"```
65    /// if ```AWS_PROFILE``` is not set.
66    pub fn with_default_configuration<F>(file_path: F) -> ProfileProvider
67    where
68        F: Into<PathBuf>,
69    {
70        ProfileProvider::with_configuration(file_path, ProfileProvider::default_profile_name())
71    }
72
73    /// Create a new `ProfileProvider` for the default credentials file path using
74    /// the given profile.
75    pub fn with_default_credentials<P>(profile: P) -> Result<ProfileProvider, CredentialsError>
76        where
77            P: Into<String>,
78    {
79        let profile_location = ProfileProvider::default_profile_location()?;
80        Ok(ProfileProvider {
81            file_path: profile_location.into(),
82            profile: profile.into(),
83        })
84    }
85
86    /// Attempts to resolve a region value associated with the current default profile from
87    /// `~/.aws/config` or the file associated with the `AWS_CONFIG_FILE` environment variable.
88    /// As these fields do not require a region field to be defined, an `Option` type is returned
89    ///
90    /// For a the ful region resolution chain, use the `Default` impl for `rusoto_core::Region`
91    pub fn region() -> Result<Option<String>, CredentialsError> {
92        let location = ProfileProvider::default_config_location();
93        location.map(|location| {
94            parse_config_file(&location).and_then(|config| {
95                config
96                    .get(&ProfileProvider::default_profile_name())
97                    .and_then(|props| props.get(REGION))
98                    .map(std::borrow::ToOwned::to_owned)
99            })
100        })
101    }
102
103    /// Attempts to resolve the region value associated with the current `ProfileProvider`s
104    /// config file path (`ProfileProvider.file_path`) and profile (`ProfileProvider.profile`).
105    /// As these fields do not require a region field to be defined, an `Option` type is returned
106    pub fn region_from_profile(&self) -> Result<Option<String>, CredentialsError> {
107        Ok(
108            parse_config_file(&self.file_path).and_then(|config| {
109                config
110                    .get(&self.profile)
111                    .and_then(|props| props.get(REGION))
112                    .map(std::borrow::ToOwned::to_owned)
113            }))
114    }
115
116    /// Default config file location:
117    /// 1: if set and not empty, use the value from environment variable ```AWS_CONFIG_FILE```
118    /// 2. otherwise return `~/.aws/config` (Linux/Mac) resp. `%USERPROFILE%\.aws\config` (Windows)
119    fn default_config_location() -> Result<PathBuf, CredentialsError> {
120        let env = non_empty_env_var(AWS_CONFIG_FILE);
121        match env {
122            Some(path) => Ok(PathBuf::from(path)),
123            None => ProfileProvider::hardcoded_config_location(),
124        }
125    }
126
127    fn hardcoded_config_location() -> Result<PathBuf, CredentialsError> {
128        match home_dir() {
129            Some(mut home_path) => {
130                home_path.push(".aws");
131                home_path.push("config");
132                Ok(home_path)
133            }
134            None => Err(CredentialsError::new("Failed to determine home directory.")),
135        }
136    }
137
138    /// Default credentials file location:
139    /// 1. if set and not empty, use value from environment variable ```AWS_SHARED_CREDENTIALS_FILE```
140    /// 2. otherwise return `~/.aws/credentials` (Linux/Mac) resp. `%USERPROFILE%\.aws\credentials` (Windows)
141    fn default_profile_location() -> Result<PathBuf, CredentialsError> {
142        let env = non_empty_env_var(AWS_SHARED_CREDENTIALS_FILE);
143        match env {
144            Some(path) => Ok(PathBuf::from(path)),
145            None => ProfileProvider::hardcoded_profile_location(),
146        }
147    }
148
149    fn hardcoded_profile_location() -> Result<PathBuf, CredentialsError> {
150        match home_dir() {
151            Some(mut home_path) => {
152                home_path.push(".aws");
153                home_path.push("credentials");
154                Ok(home_path)
155            }
156            None => Err(CredentialsError::new("Failed to determine home directory.")),
157        }
158    }
159
160    /// Get the default profile name:
161    /// 1. if set and not empty, use value from environment variable ```AWS_PROFILE```
162    /// 2. otherwise return ```"default"```
163    /// see https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html.
164    fn default_profile_name() -> String {
165        non_empty_env_var(AWS_PROFILE).unwrap_or_else(|| DEFAULT.to_owned())
166    }
167
168    /// Get a reference to the credentials file path.
169    pub fn file_path(&self) -> &Path {
170        self.file_path.as_ref()
171    }
172
173    /// Get a reference to the profile name.
174    pub fn profile(&self) -> &str {
175        &self.profile
176    }
177
178    /// Set the credentials file path.
179    pub fn set_file_path<F>(&mut self, file_path: F)
180    where
181        F: Into<PathBuf>,
182    {
183        self.file_path = file_path.into();
184    }
185
186    /// Set the profile name.
187    pub fn set_profile<P>(&mut self, profile: P)
188    where
189        P: Into<String>,
190    {
191        self.profile = profile.into();
192    }
193}
194
195#[async_trait]
196impl ProvideAwsCredentials for ProfileProvider {
197    async fn credentials(&self) -> Result<AwsCredentials, CredentialsError> {
198        match ProfileProvider::default_config_location().map(|location| {
199            parse_config_file(&location).and_then(|config| {
200                config
201                    .get(&ProfileProvider::default_profile_name())
202                    .and_then(|props| props.get("credential_process"))
203                    .map(std::borrow::ToOwned::to_owned)
204            })
205        }) {
206            Ok(Some(command)) => {
207                // credential_process is set, create the future
208                let mut command = parse_command_str(&command)?;
209                let output = command.output().await.map_err(|e| {
210                    CredentialsError::new(format!("Credential process failed: {:?}", e))
211                })?;
212                if output.status.success() {
213                    parse_credential_process_output(&output.stdout)
214                } else {
215                    Err(CredentialsError::new(format!(
216                        "Credential process failed with {}: {}",
217                        output.status,
218                        String::from_utf8_lossy(&output.stderr)
219                    )))
220                }
221            }
222            Ok(None) => {
223                // credential_process is not set, parse the credentials file
224                parse_credentials_file(self.file_path()).and_then(|mut profiles| {
225                    profiles
226                        .remove(self.profile())
227                        .ok_or_else(|| CredentialsError::new("profile not found"))
228                })
229            }
230            Err(err) => Err(err),
231        }
232    }
233}
234
235#[derive(Deserialize)]
236struct CredentialProcessOutput {
237    #[serde(flatten)]
238    creds: AwsCredentials,
239    #[serde(rename = "Version")]
240    version: u8,
241}
242
243fn parse_credential_process_output(v: &[u8]) -> Result<AwsCredentials, CredentialsError> {
244    let output: CredentialProcessOutput = serde_json::from_slice(v)?;
245    if output.version == 1 {
246        Ok(output.creds)
247    } else {
248        Err(CredentialsError::new(format!(
249            "Unsupported version '{}' for credential process provider, supported versions: 1",
250            output.version
251        )))
252    }
253}
254
255/// Parses a profile header, returning the profile name.
256fn parse_profile_name(line: &str) -> Option<&str> {
257    // NOTE(benesch): should this handle whitespace inside the brackets? Seems
258    // like we should maybe use a proper INI parser here? Someone should
259    // investigate how other AWS SDKs work.
260    if let Some(line) = line.trim().strip_suffix("]") {
261        if let Some(profile_name) = line.strip_prefix("[profile ") {
262            return Some(profile_name);
263        }
264        if let Some(profile_name) = line.strip_prefix("[") {
265            return Some(profile_name);
266        }
267    }
268    None
269}
270
271fn parse_config_file(file_path: &Path) -> Option<HashMap<String, HashMap<String, String>>> {
272    match fs::metadata(file_path) {
273        Err(_) => return None,
274        Ok(metadata) => {
275            if !metadata.is_file() {
276                return None;
277            }
278        }
279    };
280    let file = File::open(file_path).expect("expected file");
281    let file_lines = BufReader::new(&file);
282    let result: (HashMap<String, HashMap<String, String>>, Option<String>) = file_lines
283        .lines()
284        .filter_map(|line| {
285            line.ok()
286                .map(|l| l.trim_matches(' ').to_owned())
287                .into_iter()
288                .find(|l| !l.starts_with('#') && !l.is_empty())
289        })
290        .fold(Default::default(), |(mut result, profile), line| {
291            if let Some(next_profile) = parse_profile_name(&line) {
292                (result, Some(next_profile.to_owned()))
293            } else {
294                match &line
295                    .splitn(2, '=')
296                    .map(|value| value.trim_matches(' '))
297                    .collect::<Vec<&str>>()[..]
298                {
299                    [key, value] if !key.is_empty() && !value.is_empty() => {
300                        if let Some(current) = profile.clone() {
301                            let values = result.entry(current).or_insert_with(HashMap::new);
302                            (*values).insert((*key).to_string(), (*value).to_string());
303                        }
304                        (result, profile)
305                    }
306                    _ => (result, profile),
307                }
308            }
309        });
310    Some(result.0)
311}
312
313/// Parses a Credentials file into a Map of <`ProfileName`, `AwsCredentials`>
314fn parse_credentials_file(
315    file_path: &Path,
316) -> Result<HashMap<String, AwsCredentials>, CredentialsError> {
317    match fs::metadata(file_path) {
318        Err(_) => {
319            return Err(CredentialsError::new(format!(
320                "Couldn't stat credentials file: [ {:?} ]. Non existant, or no permission.",
321                file_path
322            )));
323        }
324        Ok(metadata) => {
325            if !metadata.is_file() {
326                return Err(CredentialsError::new(format!(
327                    "Credentials file: [ {:?} ] is not a file.",
328                    file_path
329                )));
330            }
331        }
332    };
333
334    let file = File::open(file_path)?;
335
336    let mut profiles: HashMap<String, AwsCredentials> = HashMap::new();
337    let mut access_key: Option<String> = None;
338    let mut secret_key: Option<String> = None;
339    let mut token: Option<String> = None;
340    let mut profile_name: Option<String> = None;
341
342    let file_lines = BufReader::new(&file);
343    for (line_no, line) in file_lines.lines().enumerate() {
344        let unwrapped_line: String =
345            line.unwrap_or_else(|_| panic!("Failed to read credentials file, line: {}", line_no));
346
347        // skip empty lines
348        if unwrapped_line.is_empty() {
349            continue;
350        }
351
352        // skip comments
353        if unwrapped_line.starts_with('#') {
354            continue;
355        }
356
357        // handle the opening of named profile blocks
358        if let Some(new_profile_name) = parse_profile_name(&unwrapped_line) {
359            if let (Some(profile), Some(access), Some(secret)) =
360                (profile_name, access_key, secret_key)
361            {
362                let creds = AwsCredentials::new(access, secret, token, None);
363                profiles.insert(profile, creds);
364            }
365
366            access_key = None;
367            secret_key = None;
368            token = None;
369
370            profile_name = Some(new_profile_name.to_owned());
371            continue;
372        }
373
374        // otherwise look for key=value pairs we care about
375        let lower_case_line = unwrapped_line.to_ascii_lowercase().to_string();
376
377        if lower_case_line.contains("aws_access_key_id") && access_key.is_none() {
378            let v: Vec<&str> = unwrapped_line.split('=').collect();
379            if !v.is_empty() {
380                access_key = Some(v[1].trim_matches(' ').to_string());
381            }
382        } else if lower_case_line.contains("aws_secret_access_key") && secret_key.is_none() {
383            let v: Vec<&str> = unwrapped_line.split('=').collect();
384            if !v.is_empty() {
385                secret_key = Some(v[1].trim_matches(' ').to_string());
386            }
387        } else if lower_case_line.contains("aws_session_token") && token.is_none() {
388            let v: Vec<&str> = unwrapped_line.split('=').collect();
389            if !v.is_empty() {
390                token = Some(v[1].trim_matches(' ').to_string());
391            }
392        } else if lower_case_line.contains("aws_security_token") {
393            if token.is_none() {
394                let v: Vec<&str> = unwrapped_line.split('=').collect();
395                if !v.is_empty() {
396                    token = Some(v[1].trim_matches(' ').to_string());
397                }
398            }
399        } else {
400            // Ignore unrecognized fields
401            continue;
402        }
403    }
404
405    if let (Some(profile), Some(access), Some(secret)) = (profile_name, access_key, secret_key) {
406        let creds = AwsCredentials::new(access, secret, token, None);
407        profiles.insert(profile, creds);
408    }
409
410    if profiles.is_empty() {
411        return Err(CredentialsError::new("No credentials found."));
412    }
413
414    Ok(profiles)
415}
416
417fn parse_command_str(s: &str) -> Result<Command, CredentialsError> {
418    let args = shlex::split(s)
419        .ok_or_else(|| CredentialsError::new("Unable to parse credential_process value."))?;
420    let mut iter = args.iter();
421    let mut command = Command::new(
422        iter.next()
423            .ok_or_else(|| CredentialsError::new("credential_process value is empty."))?,
424    );
425    command.args(iter);
426    Ok(command)
427}
428
429#[cfg(test)]
430mod tests {
431    use std::env;
432    use std::path::Path;
433
434    use super::*;
435    use crate::test_utils::lock_env;
436    use crate::{CredentialsError, ProvideAwsCredentials};
437
438    #[test]
439    fn parse_config_file_default_profile() {
440        let result = super::parse_config_file(Path::new("tests/sample-data/default_config"));
441        assert!(result.is_some());
442        let profiles = result.unwrap();
443        assert_eq!(profiles.len(), 1);
444        let default_profile = profiles
445            .get(DEFAULT)
446            .expect("No Default profile in default_profile_credentials");
447        assert_eq!(default_profile.get(REGION), Some(&"us-east-2".to_string()));
448        assert_eq!(default_profile.get("output"), Some(&"json".to_string()));
449    }
450
451    #[test]
452    fn parse_config_file_multiple_profiles() {
453        let result =
454            super::parse_config_file(Path::new("tests/sample-data/multiple_profile_config"));
455        assert!(result.is_some());
456
457        let profiles = result.unwrap();
458        assert_eq!(profiles.len(), 3);
459
460        let foo_profile = profiles
461            .get("foo")
462            .expect("No foo profile in multiple_profile_credentials");
463        assert_eq!(foo_profile.get(REGION), Some(&"us-east-3".to_string()));
464        assert_eq!(foo_profile.get("output"), Some(&"json".to_string()));
465
466        let bar_profile = profiles
467            .get("bar")
468            .expect("No bar profile in multiple_profile_credentials");
469        assert_eq!(bar_profile.get(REGION), Some(&"us-east-4".to_string()));
470        assert_eq!(bar_profile.get("output"), Some(&"json".to_string()));
471        assert_eq!(bar_profile.get("# comments"), None);
472    }
473
474    #[test]
475    fn parse_config_file_credential_process() {
476        let result =
477            super::parse_config_file(Path::new("tests/sample-data/credential_process_config"));
478        assert!(result.is_some());
479        let profiles = result.unwrap();
480        assert_eq!(profiles.len(), 2);
481        let default_profile = profiles
482            .get(DEFAULT)
483            .expect("No Default profile in default_profile_credentials");
484        assert_eq!(default_profile.get(REGION), Some(&"us-east-2".to_string()));
485        assert_eq!(
486            default_profile.get("credential_process"),
487            Some(&"cat tests/sample-data/credential_process_sample_response".to_string())
488        );
489    }
490
491    #[test]
492    fn parse_credentials_file_default_profile() {
493        let result = super::parse_credentials_file(Path::new(
494            "tests/sample-data/default_profile_credentials",
495        ));
496        assert!(result.is_ok());
497
498        let profiles = result.ok().unwrap();
499        assert_eq!(profiles.len(), 1);
500
501        let default_profile = profiles
502            .get(DEFAULT)
503            .expect("No Default profile in default_profile_credentials");
504        assert_eq!(default_profile.aws_access_key_id(), "foo");
505        assert_eq!(default_profile.aws_secret_access_key(), "bar");
506    }
507
508    #[test]
509    fn parse_credentials_file_multiple_profiles() {
510        let result = super::parse_credentials_file(Path::new(
511            "tests/sample-data/multiple_profile_credentials",
512        ));
513        assert!(result.is_ok());
514
515        let profiles = result.ok().unwrap();
516        assert_eq!(profiles.len(), 2);
517
518        let foo_profile = profiles
519            .get("foo")
520            .expect("No foo profile in multiple_profile_credentials");
521        assert_eq!(foo_profile.aws_access_key_id(), "foo_access_key");
522        assert_eq!(foo_profile.aws_secret_access_key(), "foo_secret_key");
523
524        let bar_profile = profiles
525            .get("bar")
526            .expect("No bar profile in multiple_profile_credentials");
527        assert_eq!(bar_profile.aws_access_key_id(), "bar_access_key");
528        assert_eq!(bar_profile.aws_secret_access_key(), "bar_secret_key");
529    }
530
531    #[test]
532    fn parse_all_values_credentials_file() {
533        let result =
534            super::parse_credentials_file(Path::new("tests/sample-data/full_profile_credentials"));
535        assert!(result.is_ok());
536
537        let profiles = result.ok().unwrap();
538        assert_eq!(profiles.len(), 1);
539
540        let default_profile = profiles
541            .get(DEFAULT)
542            .expect("No default profile in full_profile_credentials");
543        assert_eq!(default_profile.aws_access_key_id(), "foo");
544        assert_eq!(default_profile.aws_secret_access_key(), "bar");
545    }
546
547    #[tokio::test]
548    async fn profile_provider_happy_path() {
549        let _guard = lock_env();
550        let provider = ProfileProvider::with_configuration(
551            "tests/sample-data/multiple_profile_credentials",
552            "foo",
553        );
554        let result = provider.credentials().await;
555
556        assert!(result.is_ok());
557
558        let creds = result.ok().unwrap();
559        assert_eq!(creds.aws_access_key_id(), "foo_access_key");
560        assert_eq!(creds.aws_secret_access_key(), "foo_secret_key");
561    }
562
563    #[test]
564    fn profile_provider_via_environment_variable() {
565        let _guard = lock_env();
566        let credentials_path = "tests/sample-data/default_profile_credentials";
567        env::set_var(AWS_SHARED_CREDENTIALS_FILE, credentials_path);
568        let result = ProfileProvider::new();
569        assert!(result.is_ok());
570        let provider = result.unwrap();
571        assert_eq!(provider.file_path().to_str().unwrap(), credentials_path);
572        env::remove_var(AWS_SHARED_CREDENTIALS_FILE);
573    }
574
575    #[tokio::test]
576    async fn profile_provider_profile_name_via_environment_variable() {
577        let _guard = lock_env();
578        let credentials_path = "tests/sample-data/multiple_profile_credentials";
579        env::set_var(AWS_SHARED_CREDENTIALS_FILE, credentials_path);
580        env::set_var(AWS_PROFILE, "bar");
581        let result = ProfileProvider::new();
582        assert!(result.is_ok());
583        let provider = result.unwrap();
584        assert_eq!(provider.file_path().to_str().unwrap(), credentials_path);
585        let creds = provider.credentials().await;
586        assert_eq!(creds.unwrap().aws_access_key_id(), "bar_access_key");
587        env::remove_var(AWS_SHARED_CREDENTIALS_FILE);
588        env::remove_var(AWS_PROFILE);
589    }
590
591    #[tokio::test]
592    async fn profile_provider_bad_profile() {
593        let _guard = lock_env();
594        let provider = ProfileProvider::with_configuration(
595            "tests/sample-data/multiple_profile_credentials",
596            "not_a_profile",
597        );
598        let result = provider.credentials().await;
599
600        assert!(result.is_err());
601        assert_eq!(
602            result.err(),
603            Some(CredentialsError::new("profile not found"))
604        );
605    }
606
607    #[tokio::test]
608    async fn profile_provider_credential_process() {
609        let _guard = lock_env();
610        env::set_var(
611            AWS_CONFIG_FILE,
612            "tests/sample-data/credential_process_config",
613        );
614        let provider = ProfileProvider::new().unwrap();
615        let result = provider.credentials().await;
616
617        assert!(result.is_ok());
618
619        let creds = result.ok().unwrap();
620        assert_eq!(creds.aws_access_key_id(), "baz_access_key");
621        assert_eq!(creds.aws_secret_access_key(), "baz_secret_key");
622        assert_eq!(
623            creds.token().as_ref().expect("session token not parsed"),
624            "baz_session_token"
625        );
626        assert!(creds.expires_at().is_some());
627        env::remove_var(AWS_CONFIG_FILE);
628    }
629
630    #[test]
631    fn profile_provider_profile_name() {
632        let _guard = lock_env();
633        let mut provider = ProfileProvider::new().unwrap();
634        assert_eq!(DEFAULT, provider.profile());
635        provider.set_profile("foo");
636        assert_eq!("foo", provider.profile());
637    }
638
639    #[test]
640    fn existing_file_no_credentials() {
641        let result = super::parse_credentials_file(Path::new("tests/sample-data/no_credentials"));
642        assert_eq!(
643            result.err(),
644            Some(CredentialsError::new("No credentials found."))
645        )
646    }
647
648    #[test]
649    fn parse_credentials_bad_path() {
650        let result = super::parse_credentials_file(Path::new("/bad/file/path"));
651        assert_eq!(
652            result.err(),
653            Some(CredentialsError::new(
654                "Couldn\'t stat credentials file: [ \"/bad/file/path\" ]. Non existant, or no permission.",
655            ))
656        );
657    }
658
659    #[test]
660    fn parse_credentials_directory_path() {
661        let result = super::parse_credentials_file(Path::new("tests/"));
662        assert_eq!(
663            result.err(),
664            Some(CredentialsError::new(
665                "Credentials file: [ \"tests/\" ] is not a file.",
666            ))
667        );
668    }
669
670    #[test]
671    fn parse_credentials_unrecognized_field() {
672        let result = super::parse_credentials_file(Path::new(
673            "tests/sample-data/unrecognized_field_profile_credentials",
674        ));
675        assert!(result.is_ok());
676
677        let profiles = result.ok().unwrap();
678        assert_eq!(profiles.len(), 1);
679
680        let default_profile = profiles
681            .get(DEFAULT)
682            .expect("No default profile in full_profile_credentials");
683        assert_eq!(default_profile.aws_access_key_id(), "foo");
684        assert_eq!(default_profile.aws_secret_access_key(), "bar");
685    }
686
687    #[test]
688    fn default_profile_name_from_env_var() {
689        let _guard = lock_env();
690        env::set_var(AWS_PROFILE, "bar");
691        assert_eq!("bar", ProfileProvider::default_profile_name());
692        env::remove_var(AWS_PROFILE);
693    }
694
695    #[test]
696    fn default_profile_name_from_empty_env_var() {
697        let _guard = lock_env();
698        env::set_var(AWS_PROFILE, "");
699        assert_eq!(DEFAULT, ProfileProvider::default_profile_name());
700        env::remove_var(AWS_PROFILE);
701    }
702
703    #[test]
704    fn default_profile_name() {
705        let _guard = lock_env();
706        env::remove_var(AWS_PROFILE);
707        assert_eq!(DEFAULT, ProfileProvider::default_profile_name());
708    }
709
710    #[test]
711    fn default_profile_location_from_env_var() {
712        let _guard = lock_env();
713        env::set_var(AWS_SHARED_CREDENTIALS_FILE, "bar");
714        assert_eq!(
715            Ok(PathBuf::from("bar")),
716            ProfileProvider::default_profile_location()
717        );
718        env::remove_var(AWS_SHARED_CREDENTIALS_FILE);
719    }
720
721    #[test]
722    fn default_profile_location_from_empty_env_var() {
723        let _guard = lock_env();
724        env::set_var(AWS_SHARED_CREDENTIALS_FILE, "");
725        assert_eq!(
726            ProfileProvider::hardcoded_profile_location(),
727            ProfileProvider::default_profile_location()
728        );
729        env::remove_var(AWS_SHARED_CREDENTIALS_FILE);
730    }
731
732    #[test]
733    fn default_profile_location() {
734        let _guard = lock_env();
735        env::remove_var(AWS_SHARED_CREDENTIALS_FILE);
736        assert_eq!(
737            ProfileProvider::hardcoded_profile_location(),
738            ProfileProvider::default_profile_location()
739        );
740    }
741
742    #[test]
743    fn region_from_profile() {
744        let provider = ProfileProvider::with_configuration(
745            "tests/sample-data/multiple_profile_config",
746            "foo",
747        );
748        let maybe_region = provider.region_from_profile().unwrap();
749
750        assert_eq!(
751            maybe_region,
752            Some("us-east-3".to_string())
753        );
754    }
755
756    #[test]
757    fn region_from_profile_missing_profile() {
758        let provider = ProfileProvider::with_configuration(
759            "tests/sample-data/multiple_profile_config",
760            "foobar",
761        );
762        let maybe_region = provider.region_from_profile().unwrap();
763
764        assert_eq!(
765            maybe_region,
766            None
767        );
768    }
769
770}