async_smtp/
authentication.rs1use crate::error::Error;
4use std::fmt::{self, Display, Formatter};
5
6pub const DEFAULT_ENCRYPTED_MECHANISMS: &[Mechanism] = &[Mechanism::Plain, Mechanism::Login];
9
10pub const DEFAULT_UNENCRYPTED_MECHANISMS: &[Mechanism] = &[];
12
13pub trait IntoCredentials {
15 fn into_credentials(self) -> Credentials;
17}
18
19impl IntoCredentials for Credentials {
20 fn into_credentials(self) -> Credentials {
21 self
22 }
23}
24
25impl<S: Into<String>, T: Into<String>> IntoCredentials for (S, T) {
26 fn into_credentials(self) -> Credentials {
27 let (username, password) = self;
28 Credentials::new(username.into(), password.into())
29 }
30}
31
32#[derive(PartialEq, Eq, Clone, Hash, Debug)]
34pub struct Credentials {
35 authentication_identity: String,
36 secret: String,
37}
38
39impl Credentials {
40 pub fn new(username: String, password: String) -> Credentials {
42 Credentials {
43 authentication_identity: username,
44 secret: password,
45 }
46 }
47}
48
49#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)]
51pub enum Mechanism {
52 Plain,
55 Login,
59 Xoauth2,
62}
63
64impl Display for Mechanism {
65 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
66 write!(
67 f,
68 "{}",
69 match *self {
70 Mechanism::Plain => "PLAIN",
71 Mechanism::Login => "LOGIN",
72 Mechanism::Xoauth2 => "XOAUTH2",
73 }
74 )
75 }
76}
77
78impl Mechanism {
79 pub fn supports_initial_response(self) -> bool {
81 match self {
82 Mechanism::Plain | Mechanism::Xoauth2 => true,
83 Mechanism::Login => false,
84 }
85 }
86
87 pub fn response(
90 self,
91 credentials: &Credentials,
92 challenge: Option<&str>,
93 ) -> Result<String, Error> {
94 match self {
95 Mechanism::Plain => match challenge {
96 Some(_) => Err(Error::Client("This mechanism does not expect a challenge")),
97 None => Ok(format!(
98 "\u{0}{}\u{0}{}",
99 credentials.authentication_identity, credentials.secret
100 )),
101 },
102 Mechanism::Login => {
103 let decoded_challenge =
104 challenge.ok_or(Error::Client("This mechanism does expect a challenge"))?;
105
106 if ["User Name", "Username:", "Username"].contains(&decoded_challenge) {
107 return Ok(credentials.authentication_identity.to_string());
108 }
109
110 if ["Password", "Password:"].contains(&decoded_challenge) {
111 return Ok(credentials.secret.to_string());
112 }
113
114 Err(Error::Client("Unrecognized challenge"))
115 }
116 Mechanism::Xoauth2 => match challenge {
117 Some(_) => Err(Error::Client("This mechanism does not expect a challenge")),
118 None => Ok(format!(
119 "user={}\x01auth=Bearer {}\x01\x01",
120 credentials.authentication_identity, credentials.secret
121 )),
122 },
123 }
124 }
125}
126
127#[cfg(test)]
128mod test {
129 use super::{Credentials, Mechanism};
130
131 #[test]
132 fn test_plain() {
133 let mechanism = Mechanism::Plain;
134
135 let credentials = Credentials::new("username".to_string(), "password".to_string());
136
137 assert_eq!(
138 mechanism.response(&credentials, None).unwrap(),
139 "\u{0}username\u{0}password"
140 );
141 assert!(mechanism.response(&credentials, Some("test")).is_err());
142 }
143
144 #[test]
145 fn test_login() {
146 let mechanism = Mechanism::Login;
147
148 let credentials = Credentials::new("alice".to_string(), "wonderland".to_string());
149
150 assert_eq!(
151 mechanism.response(&credentials, Some("Username")).unwrap(),
152 "alice"
153 );
154 assert_eq!(
155 mechanism.response(&credentials, Some("Password")).unwrap(),
156 "wonderland"
157 );
158 assert!(mechanism.response(&credentials, None).is_err());
159 }
160
161 #[test]
162 fn test_xoauth2() {
163 let mechanism = Mechanism::Xoauth2;
164
165 let credentials = Credentials::new(
166 "username".to_string(),
167 "vF9dft4qmTc2Nvb3RlckBhdHRhdmlzdGEuY29tCg==".to_string(),
168 );
169
170 assert_eq!(
171 mechanism.response(&credentials, None).unwrap(),
172 "user=username\x01auth=Bearer vF9dft4qmTc2Nvb3RlckBhdHRhdmlzdGEuY29tCg==\x01\x01"
173 );
174 assert!(mechanism.response(&credentials, Some("test")).is_err());
175 }
176}