cassandra_protocol/
authenticators.rs

1use crate::error::Result;
2use crate::types::CBytes;
3
4/// Handles SASL authentication.
5///
6/// The lifecycle of an authenticator consists of:
7/// - The `initial_response` function will be called. The initial return value will be sent to the
8/// server to initiate the handshake.
9/// - The server will respond to each client response by either issuing a challenge or indicating
10///  that the authentication is complete (successfully or not). If a new challenge is issued,
11///  the authenticator's `evaluate_challenge` function will be called to produce a response
12///  that will be sent to the server. This challenge/response negotiation will continue until
13///  the server responds that authentication is successful or an error is raised.
14/// - On success, the `handle_success` will be called with data returned by the server.
15pub trait SaslAuthenticator {
16    fn initial_response(&self) -> CBytes;
17
18    fn evaluate_challenge(&self, challenge: CBytes) -> Result<CBytes>;
19
20    fn handle_success(&self, data: CBytes) -> Result<()>;
21}
22
23/// Provides authenticators per new connection.
24pub trait SaslAuthenticatorProvider {
25    fn name(&self) -> Option<&str>;
26
27    fn create_authenticator(&self) -> Box<dyn SaslAuthenticator + Send>;
28}
29
30#[derive(Debug, Clone)]
31pub struct StaticPasswordAuthenticator {
32    username: String,
33    password: String,
34}
35
36impl StaticPasswordAuthenticator {
37    pub fn new<S: ToString>(username: S, password: S) -> StaticPasswordAuthenticator {
38        StaticPasswordAuthenticator {
39            username: username.to_string(),
40            password: password.to_string(),
41        }
42    }
43}
44
45impl SaslAuthenticator for StaticPasswordAuthenticator {
46    fn initial_response(&self) -> CBytes {
47        let mut token = vec![0];
48        token.extend_from_slice(self.username.as_bytes());
49        token.push(0);
50        token.extend_from_slice(self.password.as_bytes());
51
52        CBytes::new(token)
53    }
54
55    fn evaluate_challenge(&self, _challenge: CBytes) -> Result<CBytes> {
56        Err("Server challenge is not supported for StaticPasswordAuthenticator!".into())
57    }
58
59    fn handle_success(&self, _data: CBytes) -> Result<()> {
60        Ok(())
61    }
62}
63
64/// Authentication provider with a username and password.
65#[derive(Debug, Clone)]
66pub struct StaticPasswordAuthenticatorProvider {
67    username: String,
68    password: String,
69}
70
71impl SaslAuthenticatorProvider for StaticPasswordAuthenticatorProvider {
72    fn name(&self) -> Option<&str> {
73        Some("org.apache.cassandra.auth.PasswordAuthenticator")
74    }
75
76    fn create_authenticator(&self) -> Box<dyn SaslAuthenticator + Send> {
77        Box::new(StaticPasswordAuthenticator::new(
78            self.username.clone(),
79            self.password.clone(),
80        ))
81    }
82}
83
84impl StaticPasswordAuthenticatorProvider {
85    pub fn new<S: ToString>(username: S, password: S) -> Self {
86        StaticPasswordAuthenticatorProvider {
87            username: username.to_string(),
88            password: password.to_string(),
89        }
90    }
91}
92
93#[derive(Debug, Clone)]
94pub struct NoneAuthenticator;
95
96impl SaslAuthenticator for NoneAuthenticator {
97    fn initial_response(&self) -> CBytes {
98        CBytes::new(vec![0])
99    }
100
101    fn evaluate_challenge(&self, _challenge: CBytes) -> Result<CBytes> {
102        Err("Server challenge is not supported for NoneAuthenticator!".into())
103    }
104
105    fn handle_success(&self, _data: CBytes) -> Result<()> {
106        Ok(())
107    }
108}
109
110/// Provider for no authentication.
111#[derive(Debug, Clone)]
112pub struct NoneAuthenticatorProvider;
113
114impl SaslAuthenticatorProvider for NoneAuthenticatorProvider {
115    fn name(&self) -> Option<&str> {
116        None
117    }
118
119    fn create_authenticator(&self) -> Box<dyn SaslAuthenticator + Send> {
120        Box::new(NoneAuthenticator)
121    }
122}
123
124#[cfg(test)]
125#[allow(deprecated)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn test_static_password_authenticator_new() {
131        StaticPasswordAuthenticator::new("foo", "bar");
132    }
133
134    #[test]
135    fn test_static_password_authenticator_cassandra_name() {
136        let auth = StaticPasswordAuthenticatorProvider::new("foo", "bar");
137        assert_eq!(
138            auth.name(),
139            Some("org.apache.cassandra.auth.PasswordAuthenticator")
140        );
141    }
142
143    #[test]
144    fn test_authenticator_none_cassandra_name() {
145        let auth = NoneAuthenticator;
146        let provider = NoneAuthenticatorProvider;
147        assert_eq!(provider.name(), None);
148        assert_eq!(auth.initial_response().into_bytes().unwrap(), vec![0]);
149    }
150}