Skip to main content

drasi_lib/identity/
mod.rs

1// Copyright 2025 The Drasi Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Identity providers for authentication credentials.
16
17use anyhow::Result;
18use async_trait::async_trait;
19
20/// Trait for identity providers that supply authentication credentials.
21#[async_trait]
22pub trait IdentityProvider: Send + Sync {
23    /// Fetch credentials for authentication.
24    async fn get_credentials(&self) -> Result<Credentials>;
25
26    /// Clone the provider into a boxed trait object.
27    fn clone_box(&self) -> Box<dyn IdentityProvider>;
28}
29
30impl Clone for Box<dyn IdentityProvider> {
31    fn clone(&self) -> Self {
32        self.clone_box()
33    }
34}
35
36/// Credentials returned by an identity provider.
37#[derive(Clone, PartialEq, Eq)]
38pub enum Credentials {
39    /// Traditional username and password authentication.
40    UsernamePassword { username: String, password: String },
41    /// Token-based authentication (Azure AD, AWS IAM, etc.).
42    Token { username: String, token: String },
43    /// Client certificate authentication (mTLS).
44    ///
45    /// Used for database connections that authenticate via TLS client certificates
46    /// instead of passwords or tokens.
47    Certificate {
48        /// PEM-encoded client certificate.
49        cert_pem: String,
50        /// PEM-encoded private key.
51        key_pem: String,
52        /// Optional username (some databases require it alongside certificates).
53        username: Option<String>,
54    },
55}
56
57impl Credentials {
58    /// Extract username and password/token for connection string building.
59    ///
60    /// # Panics
61    /// Panics if called on `Certificate` credentials. Use [`into_certificate`](Self::into_certificate)
62    /// for certificate-based authentication.
63    pub fn into_auth_pair(self) -> (String, String) {
64        match self {
65            Credentials::UsernamePassword { username, password } => (username, password),
66            Credentials::Token { username, token } => (username, token),
67            Credentials::Certificate { .. } => {
68                panic!("Certificate credentials cannot be converted to an auth pair. Use into_certificate() instead.")
69            }
70        }
71    }
72
73    /// Extract certificate and key for TLS client authentication.
74    ///
75    /// Returns `(cert_pem, key_pem, optional_username)`.
76    ///
77    /// # Panics
78    /// Panics if called on non-Certificate credentials.
79    pub fn into_certificate(self) -> (String, String, Option<String>) {
80        match self {
81            Credentials::Certificate {
82                cert_pem,
83                key_pem,
84                username,
85            } => (cert_pem, key_pem, username),
86            _ => panic!("Not certificate credentials. Use into_auth_pair() instead."),
87        }
88    }
89
90    /// Returns `true` if this is a `Certificate` variant.
91    pub fn is_certificate(&self) -> bool {
92        matches!(self, Credentials::Certificate { .. })
93    }
94}
95
96mod password;
97pub use password::PasswordIdentityProvider;
98
99#[cfg(feature = "azure-identity")]
100mod azure;
101#[cfg(feature = "azure-identity")]
102pub use azure::AzureIdentityProvider;
103
104#[cfg(feature = "aws-identity")]
105mod aws;
106#[cfg(feature = "aws-identity")]
107pub use aws::AwsIdentityProvider;
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[tokio::test]
114    async fn test_password_provider() {
115        let provider = PasswordIdentityProvider::new("testuser", "testpass");
116        let credentials = provider.get_credentials().await.unwrap();
117
118        match credentials {
119            Credentials::UsernamePassword { username, password } => {
120                assert_eq!(username, "testuser");
121                assert_eq!(password, "testpass");
122            }
123            _ => panic!("Expected UsernamePassword credentials"),
124        }
125    }
126
127    #[tokio::test]
128    async fn test_provider_clone() {
129        let provider: Box<dyn IdentityProvider> =
130            Box::new(PasswordIdentityProvider::new("user", "pass"));
131        let cloned = provider.clone();
132
133        let credentials = cloned.get_credentials().await.unwrap();
134        assert!(matches!(credentials, Credentials::UsernamePassword { .. }));
135    }
136}