Skip to main content

adk_rs/auth/
config.rs

1//! [`AuthConfig`] — pairs an [`AuthScheme`] with the raw/exchanged
2//! [`AuthCredential`] state for one tool's auth needs.
3
4use serde::{Deserialize, Serialize};
5use sha2::{Digest, Sha256};
6
7use crate::auth::credential::AuthCredential;
8use crate::auth::scheme::AuthScheme;
9
10/// Per-tool auth descriptor. Carried as `tool.auth_config()` on tools that
11/// need authentication; the runner's `CredentialManager` resolves it before
12/// invoking the tool.
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub struct AuthConfig {
15    /// What the server expects.
16    pub auth_scheme: AuthScheme,
17    /// Initial credential (e.g. client_id + client_secret for OAuth2;
18    /// service account JSON for SA).
19    #[serde(default, skip_serializing_if = "Option::is_none")]
20    pub raw_auth_credential: Option<AuthCredential>,
21    /// Resolved credential (set after exchange / consent / refresh).
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub exchanged_auth_credential: Option<AuthCredential>,
24    /// Stable cache key. Computed deterministically when omitted.
25    #[serde(default, skip_serializing_if = "Option::is_none")]
26    pub credential_key: Option<String>,
27}
28
29impl AuthConfig {
30    /// Construct with just a scheme.
31    #[must_use]
32    pub fn new(auth_scheme: AuthScheme) -> Self {
33        Self {
34            auth_scheme,
35            raw_auth_credential: None,
36            exchanged_auth_credential: None,
37            credential_key: None,
38        }
39    }
40
41    /// Attach a raw credential.
42    #[must_use]
43    pub fn with_raw(mut self, raw: AuthCredential) -> Self {
44        self.raw_auth_credential = Some(raw);
45        self
46    }
47
48    /// Pin the cache key explicitly (otherwise computed from scheme + raw).
49    #[must_use]
50    pub fn with_key(mut self, key: impl Into<String>) -> Self {
51        self.credential_key = Some(key.into());
52        self
53    }
54
55    /// Resolve the cache key. Uses `credential_key` if set, else computes a
56    /// stable digest from the scheme + raw credential. Mirrors Python's
57    /// `_stable_model_digest` (SHA-256 over canonical JSON).
58    #[must_use]
59    pub fn resolve_credential_key(&self) -> String {
60        if let Some(k) = &self.credential_key {
61            return k.clone();
62        }
63        let mut h = Sha256::new();
64        h.update(self.auth_scheme.kind().as_bytes());
65        h.update(b":");
66        if let Ok(j) = serde_json::to_vec(&self.auth_scheme) {
67            h.update(&j);
68        }
69        h.update(b":");
70        if let Some(raw) = &self.raw_auth_credential {
71            if let Ok(j) = serde_json::to_vec(raw) {
72                h.update(&j);
73            }
74        }
75        let digest = h.finalize();
76        let mut hex = String::with_capacity(digest.len() * 2);
77        for b in digest.iter() {
78            use std::fmt::Write as _;
79            let _ = write!(&mut hex, "{b:02x}");
80        }
81        format!("{}_{}", self.auth_scheme.kind(), &hex[..16])
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use crate::auth::credential::AuthCredential;
89    use crate::auth::scheme::ApiKeyLocation;
90
91    #[test]
92    fn key_is_deterministic_for_same_inputs() {
93        let cfg = AuthConfig::new(AuthScheme::ApiKey {
94            location: ApiKeyLocation::Header,
95            name: "X-API-Key".into(),
96            description: None,
97        })
98        .with_raw(AuthCredential::api_key("secret"));
99        let k1 = cfg.resolve_credential_key();
100        let k2 = cfg.resolve_credential_key();
101        assert_eq!(k1, k2);
102        assert!(k1.starts_with("api_key_"));
103    }
104
105    #[test]
106    fn explicit_key_overrides() {
107        let cfg = AuthConfig::new(AuthScheme::ApiKey {
108            location: ApiKeyLocation::Header,
109            name: "X-API-Key".into(),
110            description: None,
111        })
112        .with_key("explicit");
113        assert_eq!(cfg.resolve_credential_key(), "explicit");
114    }
115}