Merge pull request #97 from kmkaplan/issue-95-authenticate-base64
imap::client::Client::authenticate: Base64 encode the result of the A…
This commit is contained in:
commit
28e4201eb3
5 changed files with 56 additions and 13 deletions
|
|
@ -30,6 +30,4 @@ regex = "1.0"
|
||||||
bufstream = "0.1"
|
bufstream = "0.1"
|
||||||
imap-proto = "0.6"
|
imap-proto = "0.6"
|
||||||
nom = "4.0"
|
nom = "4.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
base64 = "0.10"
|
base64 = "0.10"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ extern crate base64;
|
||||||
extern crate imap;
|
extern crate imap;
|
||||||
extern crate native_tls;
|
extern crate native_tls;
|
||||||
|
|
||||||
use base64::encode;
|
|
||||||
use imap::authenticator::Authenticator;
|
use imap::authenticator::Authenticator;
|
||||||
use native_tls::TlsConnector;
|
use native_tls::TlsConnector;
|
||||||
|
|
||||||
|
|
@ -12,13 +11,12 @@ struct GmailOAuth2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Authenticator for GmailOAuth2 {
|
impl Authenticator for GmailOAuth2 {
|
||||||
|
type Response = String;
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn process(&self, data: String) -> String {
|
fn process(&self, data: &[u8]) -> Self::Response {
|
||||||
encode(
|
|
||||||
format!(
|
format!(
|
||||||
"user={}\x01auth=Bearer {}\x01\x01",
|
"user={}\x01auth=Bearer {}\x01\x01",
|
||||||
self.user, self.access_token
|
self.user, self.access_token
|
||||||
).as_bytes(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,16 @@
|
||||||
/// This will allow plugable authentication mechanisms.
|
/// 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 {
|
pub trait Authenticator {
|
||||||
fn process(&self, String) -> String;
|
/// Type of the response to the challenge. This will usually be a
|
||||||
|
/// `Vec<u8>` 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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
extern crate base64;
|
||||||
use bufstream::BufStream;
|
use bufstream::BufStream;
|
||||||
use native_tls::{TlsConnector, TlsStream};
|
use native_tls::{TlsConnector, TlsStream};
|
||||||
use nom;
|
use nom;
|
||||||
|
|
@ -394,8 +395,15 @@ impl<T: Read + Write> Client<T> {
|
||||||
parse_authenticate_response(String::from_utf8(line).unwrap()),
|
parse_authenticate_response(String::from_utf8(line).unwrap()),
|
||||||
self
|
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!(
|
ok_or_unauth_client_err!(
|
||||||
self.write_line(auth_response.into_bytes().as_slice()),
|
self.write_line(auth_response.into_bytes().as_slice()),
|
||||||
self
|
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<u8>;
|
||||||
|
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]
|
#[test]
|
||||||
fn login() {
|
fn login() {
|
||||||
let response = b"a1 OK Logged in\r\n".to_vec();
|
let response = b"a1 OK Logged in\r\n".to_vec();
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use super::error::{Error, ParseError, Result};
|
||||||
use super::types::*;
|
use super::types::*;
|
||||||
|
|
||||||
pub fn parse_authenticate_response(line: String) -> Result<String> {
|
pub fn parse_authenticate_response(line: String) -> Result<String> {
|
||||||
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() {
|
if let Some(cap) = authenticate_regex.captures_iter(line.as_str()).next() {
|
||||||
let data = cap.get(1).map(|x| x.as_str()).unwrap_or("");
|
let data = cap.get(1).map(|x| x.as_str()).unwrap_or("");
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue