Skip to main content

reqsign_google/provide_credential/
static_provider.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 log::debug;
19
20use reqsign_core::{Context, ProvideCredential, Result, hash::base64_decode};
21
22use crate::credential::Credential;
23
24use super::parse::parse_credential_bytes;
25
26/// StaticCredentialProvider loads credentials from a JSON string provided at construction time.
27#[derive(Debug, Clone)]
28pub struct StaticCredentialProvider {
29    content: String,
30    scope: Option<String>,
31}
32
33impl StaticCredentialProvider {
34    /// Create a new StaticCredentialProvider from JSON content.
35    pub fn new(content: impl Into<String>) -> Self {
36        Self {
37            content: content.into(),
38            scope: None,
39        }
40    }
41
42    /// Create a new StaticCredentialProvider from base64-encoded JSON content.
43    pub fn from_base64(content: impl Into<String>) -> Result<Self> {
44        let content = content.into();
45        let decoded = base64_decode(&content).map_err(|e| {
46            reqsign_core::Error::unexpected("failed to decode base64").with_source(e)
47        })?;
48        let json_content = String::from_utf8(decoded).map_err(|e| {
49            reqsign_core::Error::unexpected("invalid UTF-8 in decoded content").with_source(e)
50        })?;
51        Ok(Self {
52            content: json_content,
53            scope: None,
54        })
55    }
56
57    /// Set the OAuth2 scope.
58    pub fn with_scope(mut self, scope: impl Into<String>) -> Self {
59        self.scope = Some(scope.into());
60        self
61    }
62}
63impl ProvideCredential for StaticCredentialProvider {
64    type Credential = Credential;
65
66    async fn provide_credential(&self, ctx: &Context) -> Result<Option<Self::Credential>> {
67        debug!("loading credential from static content");
68
69        parse_credential_bytes(ctx, self.content.as_bytes(), self.scope.clone())
70            .await
71            .map_err(|err| {
72                debug!("failed to parse credential from content: {err:?}");
73                err
74            })
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81    use reqsign_core::Context;
82
83    #[tokio::test]
84    async fn test_static_service_account() {
85        let content = r#"{
86            "type": "service_account",
87            "private_key": "-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END RSA PRIVATE KEY-----",
88            "client_email": "test@example.iam.gserviceaccount.com"
89        }"#;
90
91        let provider = StaticCredentialProvider::new(content);
92        let ctx = Context::new()
93            .with_file_read(reqsign_file_read_tokio::TokioFileRead)
94            .with_http_send(reqsign_http_send_reqwest::ReqwestHttpSend::default());
95
96        let result = provider.provide_credential(&ctx).await;
97        assert!(result.is_ok());
98
99        let cred = result.unwrap();
100        assert!(cred.is_some());
101
102        let cred = cred.unwrap();
103        assert!(cred.has_service_account());
104    }
105
106    #[tokio::test]
107    async fn test_static_service_account_from_base64() {
108        let content = r#"{
109            "type": "service_account",
110            "private_key": "-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END RSA PRIVATE KEY-----",
111            "client_email": "test@example.iam.gserviceaccount.com"
112        }"#;
113
114        // Base64 encode the content
115        use reqsign_core::hash::base64_encode;
116        let encoded = base64_encode(content.as_bytes());
117
118        let provider =
119            StaticCredentialProvider::from_base64(encoded).expect("should decode base64");
120        let ctx = Context::new()
121            .with_file_read(reqsign_file_read_tokio::TokioFileRead)
122            .with_http_send(reqsign_http_send_reqwest::ReqwestHttpSend::default());
123
124        let result = provider.provide_credential(&ctx).await;
125        assert!(result.is_ok());
126
127        let cred = result.unwrap();
128        assert!(cred.is_some());
129
130        let cred = cred.unwrap();
131        assert!(cred.has_service_account());
132    }
133}