diff --git a/Cargo.toml b/Cargo.toml index 7c133e6..c0c15f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,4 @@ regex = "1.0" bufstream = "0.1" imap-proto = "0.6" nom = "4.0" - -[dev-dependencies] base64 = "0.10" diff --git a/examples/gmail_oauth2.rs b/examples/gmail_oauth2.rs index 051c7e2..769be3d 100644 --- a/examples/gmail_oauth2.rs +++ b/examples/gmail_oauth2.rs @@ -2,7 +2,6 @@ extern crate base64; extern crate imap; extern crate native_tls; -use base64::encode; use imap::authenticator::Authenticator; use native_tls::TlsConnector; @@ -12,13 +11,12 @@ struct GmailOAuth2 { } impl Authenticator for GmailOAuth2 { + type Response = String; #[allow(unused_variables)] - fn process(&self, data: String) -> String { - encode( - format!( - "user={}\x01auth=Bearer {}\x01\x01", - self.user, self.access_token - ).as_bytes(), + fn process(&self, data: &[u8]) -> Self::Response { + format!( + "user={}\x01auth=Bearer {}\x01\x01", + self.user, self.access_token ) } } diff --git a/src/authenticator.rs b/src/authenticator.rs index 825ec9a..1351b7a 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -1,4 +1,16 @@ /// This will allow plugable authentication mechanisms. +/// +/// This trait is used by `Client::authenticate` to [authenticate +/// using SASL](https://tools.ietf.org/html/rfc3501#section-6.2.2). pub trait Authenticator { - fn process(&self, String) -> String; + /// Type of the response to the challenge. This will usually be a + /// `Vec` or `String`. It must not be Base64 encoded: the + /// library will do it. + type Response: AsRef<[u8]>; + /// For each server challenge is passed to `process`. The library + /// has already decoded the Base64 string into bytes. + /// + /// The `process` function should return its response, not Base64 + /// encoded: the library will do it. + fn process(&self, &[u8]) -> Self::Response; } diff --git a/src/client.rs b/src/client.rs index dad95d4..cfdebb8 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,3 +1,4 @@ +extern crate base64; use bufstream::BufStream; use native_tls::{TlsConnector, TlsStream}; use nom; @@ -394,8 +395,15 @@ impl Client { parse_authenticate_response(String::from_utf8(line).unwrap()), self ); - let auth_response = authenticator.process(data); - + let challenge = ok_or_unauth_client_err!( + base64::decode(data.as_str()) + .map_err(|_| + Error::Parse(ParseError::Authentication(data)) + ), + self + ); + let raw_response = &authenticator.process(&challenge); + let auth_response = base64::encode(raw_response); ok_or_unauth_client_err!( self.write_line(auth_response.into_bytes().as_slice()), self @@ -937,6 +945,33 @@ mod tests { ); } + #[test] + fn authenticate() { + let response = b"+YmFy\r\n\ + a1 OK Logged in\r\n".to_vec(); + let command = "a1 AUTHENTICATE PLAIN\r\n\ + Zm9v\r\n"; + let mock_stream = MockStream::new(response); + let client = Client::new(mock_stream); + enum Authenticate { Auth }; + impl Authenticator for Authenticate { + type Response = Vec; + fn process(&self, challenge: &[u8]) -> Self::Response { + assert!( + challenge == b"bar", + "Invalid authenticate challenge" + ); + b"foo".to_vec() + } + } + let auth = Authenticate::Auth; + let session = client.authenticate("PLAIN", auth).unwrap(); + assert!( + session.stream.get_ref().written_buf == command.as_bytes().to_vec(), + "Invalid authenticate command" + ); + } + #[test] fn login() { let response = b"a1 OK Logged in\r\n".to_vec(); diff --git a/src/parse.rs b/src/parse.rs index 194b02a..f198b46 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -7,7 +7,7 @@ use super::error::{Error, ParseError, Result}; use super::types::*; pub fn parse_authenticate_response(line: String) -> Result { - let authenticate_regex = Regex::new("^+(.*)\r\n").unwrap(); + let authenticate_regex = Regex::new("^\\+(.*)\r\n").unwrap(); if let Some(cap) = authenticate_regex.captures_iter(line.as_str()).next() { let data = cap.get(1).map(|x| x.as_str()).unwrap_or("");