extern crate serde_json;
extern crate url;
use std::borrow::BorrowMut;
use std::convert::AsRef;
use std::error::Error;
use std::io;
use std::io::Read;
use std::sync::Mutex;
use std::sync::mpsc::{channel, Receiver, Sender};
use hyper;
use hyper::{client, header, server, status, uri};
use serde_json::error;
use url::form_urlencoded;
use url::percent_encoding::{percent_encode, QUERY_ENCODE_SET};
use crate::types::{ApplicationSecret, Token};
use crate::authenticator_delegate::AuthenticatorDelegate;
const OOB_REDIRECT_URI: &'static str = "urn:ietf:wg:oauth:2.0:oob";
fn build_authentication_request_url<'a, T, I>(auth_uri: &str,
client_id: &str,
scopes: I,
redirect_uri: Option<String>)
-> String
where T: AsRef<str> + 'a,
I: IntoIterator<Item = &'a T>
{
let mut url = String::new();
let mut scopes_string = scopes.into_iter().fold(String::new(), |mut acc, sc| {
acc.push_str(sc.as_ref());
acc.push_str(" ");
acc
});
scopes_string.pop();
url.push_str(auth_uri);
vec![format!("?scope={}", scopes_string),
format!("&redirect_uri={}",
redirect_uri.unwrap_or(OOB_REDIRECT_URI.to_string())),
format!("&response_type=code"),
format!("&client_id={}", client_id)]
.into_iter()
.fold(url, |mut u, param| {
u.push_str(&percent_encode(param.as_ref(), QUERY_ENCODE_SET).to_string());
u
})
}
pub struct InstalledFlow<C> {
client: C,
server: Option<server::Listening>,
port: Option<u32>,
auth_code_rcv: Option<Receiver<String>>,
}
pub enum InstalledFlowReturnMethod {
Interactive,
HTTPRedirect(u32),
}
impl<C> InstalledFlow<C>
where C: BorrowMut<hyper::Client>
{
pub fn new(client: C, method: Option<InstalledFlowReturnMethod>) -> InstalledFlow<C> {
let default = InstalledFlow {
client: client,
server: None,
port: None,
auth_code_rcv: None,
};
match method {
None => default,
Some(InstalledFlowReturnMethod::Interactive) => default,
Some(InstalledFlowReturnMethod::HTTPRedirect(port)) => {
let server = server::Server::http(format!("127.0.0.1:{}", port).as_str());
match server {
Result::Err(_) => default,
Result::Ok(server) => {
let (tx, rx) = channel();
let listening =
server.handle(InstalledFlowHandler { auth_code_snd: Mutex::new(tx) });
match listening {
Result::Err(_) => default,
Result::Ok(listening) => {
InstalledFlow {
client: default.client,
server: Some(listening),
port: Some(port),
auth_code_rcv: Some(rx),
}
}
}
}
}
}
}
}
pub fn obtain_token<'a, AD: AuthenticatorDelegate, S, T>(&mut self,
auth_delegate: &mut AD,
appsecret: &ApplicationSecret,
scopes: S)
-> Result<Token, Box<Error>>
where T: AsRef<str> + 'a,
S: Iterator<Item = &'a T>
{
let authcode = self.get_authorization_code(auth_delegate, &appsecret, scopes)?;
let tokens = self.request_token(&appsecret, &authcode)?;
if tokens.access_token.is_some() {
let mut token = Token {
access_token: tokens.access_token.unwrap(),
refresh_token: tokens.refresh_token.unwrap(),
token_type: tokens.token_type.unwrap(),
expires_in: tokens.expires_in,
expires_in_timestamp: None,
};
token.set_expiry_absolute();
Result::Ok(token)
} else {
let err = io::Error::new(io::ErrorKind::Other,
format!("Token API error: {} {}",
tokens.error.unwrap_or("<unknown err>".to_string()),
tokens.error_description
.unwrap_or("".to_string()))
.as_str());
Result::Err(Box::new(err))
}
}
fn get_authorization_code<'a, AD: AuthenticatorDelegate, S, T>(&mut self,
auth_delegate: &mut AD,
appsecret: &ApplicationSecret,
scopes: S)
-> Result<String, Box<Error>>
where T: AsRef<str> + 'a,
S: Iterator<Item = &'a T>
{
let result: Result<String, Box<Error>> = match self.server {
None => {
let url = build_authentication_request_url(&appsecret.auth_uri,
&appsecret.client_id,
scopes,
None);
match auth_delegate.present_user_url(&url, true ) {
None => {
Result::Err(Box::new(io::Error::new(io::ErrorKind::UnexpectedEof,
"couldn't read code")))
}
Some(mut code) => {
code.pop();
Result::Ok(code)
}
}
}
Some(_) => {
let url = build_authentication_request_url(&appsecret.auth_uri,
&appsecret.client_id,
scopes,
Some(format!("http://localhost:{}",
self.port
.unwrap_or(8080))));
auth_delegate.present_user_url(&url, false );
match self.auth_code_rcv.as_ref().unwrap().recv() {
Result::Err(e) => Result::Err(Box::new(e)),
Result::Ok(s) => Result::Ok(s),
}
}
};
self.server.as_mut().map(|l| l.close()).is_some();
result
}
fn request_token(&mut self,
appsecret: &ApplicationSecret,
authcode: &str)
-> Result<JSONTokenResponse, Box<Error>> {
let redirect_uri;
match self.port {
None => redirect_uri = OOB_REDIRECT_URI.to_string(),
Some(p) => redirect_uri = format!("http://localhost:{}", p),
}
let body = form_urlencoded::Serializer::new(String::new())
.extend_pairs(vec![("code".to_string(), authcode.to_string()),
("client_id".to_string(), appsecret.client_id.clone()),
("client_secret".to_string(), appsecret.client_secret.clone()),
("redirect_uri".to_string(), redirect_uri),
("grant_type".to_string(), "authorization_code".to_string())])
.finish();
let result: Result<client::Response, hyper::Error> = self.client
.borrow_mut()
.post(&appsecret.token_uri)
.body(&body)
.header(header::ContentType("application/x-www-form-urlencoded".parse().unwrap()))
.send();
let mut resp = String::new();
match result {
Result::Err(e) => return Result::Err(Box::new(e)),
Result::Ok(mut response) => {
let result = response.read_to_string(&mut resp);
match result {
Result::Err(e) => return Result::Err(Box::new(e)),
Result::Ok(_) => (),
}
}
}
let token_resp: Result<JSONTokenResponse, error::Error> = serde_json::from_str(&resp);
match token_resp {
Result::Err(e) => return Result::Err(Box::new(e)),
Result::Ok(tok) => Result::Ok(tok) as Result<JSONTokenResponse, Box<Error>>,
}
}
}
#[derive(Deserialize)]
struct JSONTokenResponse {
access_token: Option<String>,
refresh_token: Option<String>,
token_type: Option<String>,
expires_in: Option<i64>,
error: Option<String>,
error_description: Option<String>,
}
struct InstalledFlowHandler {
auth_code_snd: Mutex<Sender<String>>,
}
impl server::Handler for InstalledFlowHandler {
fn handle(&self, rq: server::Request, mut rp: server::Response) {
match rq.uri {
uri::RequestUri::AbsolutePath(path) => {
let url = hyper::Url::parse(&format!("http://example.com{}", path));
if url.is_err() {
*rp.status_mut() = status::StatusCode::BadRequest;
let _ = rp.send("Unparseable URL".as_ref());
} else {
self.handle_url(url.unwrap());
*rp.status_mut() = status::StatusCode::Ok;
let _ =
rp.send("<html><head><title>Success</title></head><body>You may now \
close this window.</body></html>"
.as_ref());
}
}
_ => {
*rp.status_mut() = status::StatusCode::BadRequest;
let _ = rp.send("Invalid Request!".as_ref());
}
}
}
}
impl InstalledFlowHandler {
fn handle_url(&self, url: hyper::Url) {
for (param, val) in url.query_pairs().into_owned() {
if param == "code".to_string() {
let _ = self.auth_code_snd.lock().unwrap().send(val);
}
}
}
}
#[cfg(test)]
mod tests {
use super::build_authentication_request_url;
use super::InstalledFlowHandler;
use std::sync::Mutex;
use std::sync::mpsc::channel;
use hyper::Url;
#[test]
fn test_request_url_builder() {
assert_eq!("https://accounts.google.\
com/o/oauth2/auth?scope=email%20profile&redirect_uri=urn:ietf:wg:oauth:2.0:\
oob&response_type=code&client_id=812741506391-h38jh0j4fv0ce1krdkiq0hfvt6n5amr\
f.apps.googleusercontent.com",
build_authentication_request_url("https://accounts.google.com/o/oauth2/auth",
"812741506391-h38jh0j4fv0ce1krdkiq0hfvt6n5am\
rf.apps.googleusercontent.com",
vec![&"email".to_string(),
&"profile".to_string()],
None));
}
#[test]
fn test_http_handle_url() {
let (tx, rx) = channel();
let handler = InstalledFlowHandler { auth_code_snd: Mutex::new(tx) };
let url = Url::parse("http://example.com:1234/?code=ab/c%2Fd#").unwrap();
handler.handle_url(url);
assert_eq!(rx.recv().unwrap(), "ab/c/d".to_string());
}
}