Skip to main content

reqsign_oracle/provide_credential/
env.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use crate::{Credential, constants::*};
19use reqsign_core::time::Timestamp;
20use reqsign_core::{Context, ProvideCredential, Result};
21use std::time::Duration;
22
23/// EnvCredentialProvider loads Oracle Cloud credentials from environment variables.
24///
25/// This provider looks for the following environment variables:
26/// - `OCI_USER`: The Oracle Cloud user ID
27/// - `OCI_TENANCY`: The Oracle Cloud tenancy ID
28/// - `OCI_KEY_FILE`: The path to the private key file
29/// - `OCI_FINGERPRINT`: The fingerprint of the key
30#[derive(Debug, Default, Clone)]
31pub struct EnvCredentialProvider {}
32
33impl EnvCredentialProvider {
34    /// Create a new EnvCredentialProvider.
35    pub fn new() -> Self {
36        Self {}
37    }
38}
39impl ProvideCredential for EnvCredentialProvider {
40    type Credential = Credential;
41
42    async fn provide_credential(&self, ctx: &Context) -> Result<Option<Self::Credential>> {
43        let envs = ctx.env_vars();
44
45        let user = envs.get(ORACLE_USER);
46        let tenancy = envs.get(ORACLE_TENANCY);
47        let key_file = envs.get(ORACLE_KEY_FILE);
48        let fingerprint = envs.get(ORACLE_FINGERPRINT);
49
50        match (user, tenancy, key_file, fingerprint) {
51            (Some(user), Some(tenancy), Some(key_file), Some(fingerprint)) => {
52                // Expand key file path if it starts with ~
53                let expanded_key_file = if key_file.starts_with('~') {
54                    ctx.expand_home_dir(key_file).ok_or_else(|| {
55                        reqsign_core::Error::unexpected("Failed to expand home directory")
56                    })?
57                } else {
58                    key_file.clone()
59                };
60
61                Ok(Some(Credential {
62                    user: user.clone(),
63                    tenancy: tenancy.clone(),
64                    key_file: expanded_key_file,
65                    fingerprint: fingerprint.clone(),
66                    expires_in: Some(Timestamp::now() + Duration::from_secs(600)),
67                }))
68            }
69            _ => Ok(None),
70        }
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use reqsign_core::{OsEnv, StaticEnv};
78    use reqsign_file_read_tokio::TokioFileRead;
79    use reqsign_http_send_reqwest::ReqwestHttpSend;
80    use std::collections::HashMap;
81
82    #[tokio::test]
83    async fn test_env_credential_provider() -> anyhow::Result<()> {
84        let envs = HashMap::from([
85            (ORACLE_USER.to_string(), "test_user".to_string()),
86            (ORACLE_TENANCY.to_string(), "test_tenancy".to_string()),
87            (ORACLE_KEY_FILE.to_string(), "/path/to/key".to_string()),
88            (
89                ORACLE_FINGERPRINT.to_string(),
90                "test_fingerprint".to_string(),
91            ),
92        ]);
93
94        let ctx = Context::new()
95            .with_file_read(TokioFileRead)
96            .with_http_send(ReqwestHttpSend::default())
97            .with_env(OsEnv)
98            .with_env(StaticEnv {
99                home_dir: None,
100                envs,
101            });
102
103        let provider = EnvCredentialProvider::new();
104        let cred = provider.provide_credential(&ctx).await?;
105        assert!(cred.is_some());
106        let cred = cred.unwrap();
107        assert_eq!(cred.user, "test_user");
108        assert_eq!(cred.tenancy, "test_tenancy");
109        assert_eq!(cred.key_file, "/path/to/key");
110        assert_eq!(cred.fingerprint, "test_fingerprint");
111
112        Ok(())
113    }
114
115    #[tokio::test]
116    async fn test_env_credential_provider_missing_credentials() -> anyhow::Result<()> {
117        let ctx = Context::new()
118            .with_file_read(TokioFileRead)
119            .with_http_send(ReqwestHttpSend::default())
120            .with_env(OsEnv);
121
122        let provider = EnvCredentialProvider::new();
123        let cred = provider.provide_credential(&ctx).await?;
124        assert!(cred.is_none());
125
126        Ok(())
127    }
128
129    #[tokio::test]
130    async fn test_env_credential_provider_partial_credentials() -> anyhow::Result<()> {
131        // Only user and tenancy
132        let envs = HashMap::from([
133            (ORACLE_USER.to_string(), "test_user".to_string()),
134            (ORACLE_TENANCY.to_string(), "test_tenancy".to_string()),
135        ]);
136
137        let ctx = Context::new()
138            .with_file_read(TokioFileRead)
139            .with_http_send(ReqwestHttpSend::default())
140            .with_env(OsEnv)
141            .with_env(StaticEnv {
142                home_dir: None,
143                envs,
144            });
145
146        let provider = EnvCredentialProvider::new();
147        let cred = provider.provide_credential(&ctx).await?;
148        assert!(cred.is_none());
149
150        Ok(())
151    }
152
153    #[tokio::test]
154    async fn test_env_credential_provider_with_home_expansion() -> anyhow::Result<()> {
155        let envs = HashMap::from([
156            (ORACLE_USER.to_string(), "test_user".to_string()),
157            (ORACLE_TENANCY.to_string(), "test_tenancy".to_string()),
158            (ORACLE_KEY_FILE.to_string(), "~/key.pem".to_string()),
159            (
160                ORACLE_FINGERPRINT.to_string(),
161                "test_fingerprint".to_string(),
162            ),
163        ]);
164
165        let ctx = Context::new()
166            .with_file_read(TokioFileRead)
167            .with_http_send(ReqwestHttpSend::default())
168            .with_env(OsEnv)
169            .with_env(StaticEnv {
170                home_dir: Some("/home/user".into()),
171                envs,
172            });
173
174        let provider = EnvCredentialProvider::new();
175        let cred = provider.provide_credential(&ctx).await?;
176        assert!(cred.is_some());
177        let cred = cred.unwrap();
178        assert_eq!(cred.key_file, "/home/user/key.pem");
179
180        Ok(())
181    }
182}