Skip to main content

oci_rust_sdk/auth/
config.rs

1use crate::auth::provider::AuthProvider;
2use crate::core::region::Region;
3use configparser::ini::Ini;
4use std::fs;
5use std::path::{Path, PathBuf};
6use std::str::FromStr;
7
8/// Configuration file based authentication provider
9///
10/// Reads credentials from OCI configuration file (default: ~/.oci/config)
11#[derive(Debug, Clone)]
12pub struct ConfigFileAuthProvider {
13    tenancy: String,
14    user: String,
15    fingerprint: String,
16    key_content: String,
17    passphrase: Option<String>,
18    region: Option<Region>,
19}
20
21impl ConfigFileAuthProvider {
22    /// Create a new ConfigFileAuthProvider from a configuration file
23    ///
24    /// # Arguments
25    /// * `config_path` - Path to the OCI config file (default: ~/.oci/config)
26    /// * `profile` - Profile name to use (default: DEFAULT)
27    pub fn from_file(config_path: impl AsRef<Path>, profile: &str) -> crate::core::Result<Self> {
28        let config_path = expand_tilde(config_path.as_ref());
29        let mut ini = Ini::new();
30        ini.load(&config_path).map_err(|e| {
31            crate::core::OciError::ConfigError(format!("Failed to load config file: {}", e))
32        })?;
33
34        let tenancy = ini.get(profile, "tenancy").ok_or_else(|| {
35            crate::core::OciError::ConfigError(format!(
36                "Missing 'tenancy' in profile '{}'",
37                profile
38            ))
39        })?;
40
41        let user = ini.get(profile, "user").ok_or_else(|| {
42            crate::core::OciError::ConfigError(format!("Missing 'user' in profile '{}'", profile))
43        })?;
44
45        let fingerprint = ini.get(profile, "fingerprint").ok_or_else(|| {
46            crate::core::OciError::ConfigError(format!(
47                "Missing 'fingerprint' in profile '{}'",
48                profile
49            ))
50        })?;
51
52        let key_file = ini.get(profile, "key_file").ok_or_else(|| {
53            crate::core::OciError::ConfigError(format!(
54                "Missing 'key_file' in profile '{}'",
55                profile
56            ))
57        })?;
58
59        let key_path = expand_tilde(Path::new(&key_file));
60        let key_content = fs::read_to_string(&key_path).map_err(|e| {
61            crate::core::OciError::ConfigError(format!("Failed to read key file: {}", e))
62        })?;
63
64        let passphrase = ini.get(profile, "pass_phrase");
65
66        let region = ini
67            .get(profile, "region")
68            .and_then(|r| Region::from_str(&r).ok());
69
70        Ok(Self {
71            tenancy,
72            user,
73            fingerprint,
74            key_content,
75            passphrase,
76            region,
77        })
78    }
79
80    /// Create from default config file path (~/.oci/config) and DEFAULT profile
81    pub fn from_default() -> crate::core::Result<Self> {
82        let config_path = dirs::home_dir()
83            .ok_or_else(|| {
84                crate::core::OciError::ConfigError("Could not determine home directory".to_string())
85            })?
86            .join(".oci")
87            .join("config");
88
89        Self::from_file(config_path, "DEFAULT")
90    }
91
92    /// Get the region (if specified in config)
93    pub fn region(&self) -> Option<Region> {
94        self.region
95    }
96}
97
98impl AuthProvider for ConfigFileAuthProvider {
99    fn get_key_id(&self) -> String {
100        format!("{}/{}/{}", self.tenancy, self.user, self.fingerprint)
101    }
102
103    fn get_private_key(&self) -> &str {
104        &self.key_content
105    }
106
107    fn get_passphrase(&self) -> Option<&str> {
108        self.passphrase.as_deref()
109    }
110}
111
112/// Expand tilde (~) in paths to home directory
113fn expand_tilde(path: &Path) -> PathBuf {
114    if let Ok(p) = path.strip_prefix("~")
115        && let Some(home) = dirs::home_dir()
116    {
117        return home.join(p);
118    }
119    path.to_path_buf()
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_expand_tilde() {
128        let path = Path::new("~/.oci/config");
129        let expanded = expand_tilde(path);
130        assert!(!expanded.to_str().unwrap().contains('~'));
131    }
132}