drasi_lib/identity/
mod.rs1use anyhow::Result;
18use async_trait::async_trait;
19
20#[async_trait]
26pub trait IdentityProvider: Send + Sync {
27 async fn get_credentials(&self) -> Result<Credentials>;
29
30 fn clone_box(&self) -> Box<dyn IdentityProvider>;
32}
33
34impl Clone for Box<dyn IdentityProvider> {
35 fn clone(&self) -> Self {
36 self.clone_box()
37 }
38}
39
40#[derive(Clone, PartialEq, Eq)]
42pub enum Credentials {
43 UsernamePassword { username: String, password: String },
45 Token { username: String, token: String },
47 Certificate {
52 cert_pem: String,
54 key_pem: String,
56 username: Option<String>,
58 },
59}
60
61impl std::fmt::Debug for Credentials {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 match self {
65 Credentials::UsernamePassword { username, .. } => f
66 .debug_struct("UsernamePassword")
67 .field("username", username)
68 .field("password", &"[REDACTED]")
69 .finish(),
70 Credentials::Token { username, .. } => f
71 .debug_struct("Token")
72 .field("username", username)
73 .field("token", &"[REDACTED]")
74 .finish(),
75 Credentials::Certificate { username, .. } => f
76 .debug_struct("Certificate")
77 .field("cert_pem", &"[REDACTED]")
78 .field("key_pem", &"[REDACTED]")
79 .field("username", username)
80 .finish(),
81 }
82 }
83}
84
85impl Credentials {
86 pub fn try_into_auth_pair(self) -> std::result::Result<(String, String), Self> {
90 match self {
91 Credentials::UsernamePassword { username, password } => Ok((username, password)),
92 Credentials::Token { username, token } => Ok((username, token)),
93 other => Err(other),
94 }
95 }
96
97 pub fn try_into_certificate(
102 self,
103 ) -> std::result::Result<(String, String, Option<String>), Self> {
104 match self {
105 Credentials::Certificate {
106 cert_pem,
107 key_pem,
108 username,
109 } => Ok((cert_pem, key_pem, username)),
110 other => Err(other),
111 }
112 }
113
114 #[deprecated(note = "Use try_into_auth_pair() which returns Result instead of panicking")]
122 pub(crate) fn into_auth_pair(self) -> (String, String) {
123 self.try_into_auth_pair()
124 .unwrap_or_else(|_| panic!("Certificate credentials cannot be converted to an auth pair. Use try_into_auth_pair() or try_into_certificate() instead."))
125 }
126
127 #[deprecated(note = "Use try_into_certificate() which returns Result instead of panicking")]
135 pub(crate) fn into_certificate(self) -> (String, String, Option<String>) {
136 self.try_into_certificate()
137 .unwrap_or_else(|_| panic!("Not certificate credentials. Use try_into_certificate() or try_into_auth_pair() instead."))
138 }
139
140 pub fn is_certificate(&self) -> bool {
142 matches!(self, Credentials::Certificate { .. })
143 }
144}
145
146mod password;
147pub use password::PasswordIdentityProvider;
148
149#[cfg(feature = "azure-identity")]
150mod azure;
151#[cfg(feature = "azure-identity")]
152pub use azure::AzureIdentityProvider;
153
154#[cfg(feature = "aws-identity")]
155mod aws;
156#[cfg(feature = "aws-identity")]
157pub use aws::AwsIdentityProvider;
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[tokio::test]
164 async fn test_password_provider() {
165 let provider = PasswordIdentityProvider::new("testuser", "testpass");
166 let credentials = provider.get_credentials().await.unwrap();
167
168 match credentials {
169 Credentials::UsernamePassword { username, password } => {
170 assert_eq!(username, "testuser");
171 assert_eq!(password, "testpass");
172 }
173 _ => panic!("Expected UsernamePassword credentials"),
174 }
175 }
176
177 #[tokio::test]
178 async fn test_provider_clone() {
179 let provider: Box<dyn IdentityProvider> =
180 Box::new(PasswordIdentityProvider::new("user", "pass"));
181 let cloned = provider.clone();
182
183 let credentials = cloned.get_credentials().await.unwrap();
184 assert!(matches!(credentials, Credentials::UsernamePassword { .. }));
185 }
186
187 #[test]
188 fn test_try_into_auth_pair_username_password() {
189 let creds = Credentials::UsernamePassword {
190 username: "user".into(),
191 password: "pass".into(),
192 };
193 let (u, p) = creds.try_into_auth_pair().unwrap();
194 assert_eq!(u, "user");
195 assert_eq!(p, "pass");
196 }
197
198 #[test]
199 fn test_try_into_auth_pair_token() {
200 let creds = Credentials::Token {
201 username: "user".into(),
202 token: "tok".into(),
203 };
204 let (u, t) = creds.try_into_auth_pair().unwrap();
205 assert_eq!(u, "user");
206 assert_eq!(t, "tok");
207 }
208
209 #[test]
210 fn test_try_into_auth_pair_rejects_certificate() {
211 let creds = Credentials::Certificate {
212 cert_pem: "cert".into(),
213 key_pem: "key".into(),
214 username: None,
215 };
216 let result = creds.try_into_auth_pair();
217 assert!(result.is_err());
218 let returned = result.unwrap_err();
220 assert!(returned.is_certificate());
221 }
222
223 #[test]
224 fn test_try_into_certificate_success() {
225 let creds = Credentials::Certificate {
226 cert_pem: "cert".into(),
227 key_pem: "key".into(),
228 username: Some("user".into()),
229 };
230 let (c, k, u) = creds.try_into_certificate().unwrap();
231 assert_eq!(c, "cert");
232 assert_eq!(k, "key");
233 assert_eq!(u, Some("user".into()));
234 }
235
236 #[test]
237 fn test_try_into_certificate_rejects_password() {
238 let creds = Credentials::UsernamePassword {
239 username: "user".into(),
240 password: "pass".into(),
241 };
242 let result = creds.try_into_certificate();
243 assert!(result.is_err());
244 }
245}