use std::borrow::BorrowMut;
use std::cmp::min;
use std::collections::hash_map::DefaultHasher;
use std::convert::From;
use std::error::Error;
use std::hash::{Hash, Hasher};
use std::iter::IntoIterator;
use std::thread::sleep;
use std::time::Duration;
use crate::authenticator_delegate::{AuthenticatorDelegate, PollError, PollInformation};
use crate::device::{GOOGLE_DEVICE_CODE_URL, DeviceFlow};
use crate::installed::{InstalledFlow, InstalledFlowReturnMethod};
use crate::refresh::{RefreshResult, RefreshFlow};
use crate::storage::TokenStorage;
use crate::types::{RequestError, StringError, Token, FlowType, ApplicationSecret};
use hyper;
pub struct Authenticator<D, S, C> {
flow_type: FlowType,
delegate: D,
storage: S,
client: C,
secret: ApplicationSecret,
}
pub trait GetToken {
fn token<'b, I, T>(&mut self, scopes: I) -> Result<Token, Box<Error>>
where T: AsRef<str> + Ord + 'b,
I: IntoIterator<Item = &'b T>;
fn api_key(&mut self) -> Option<String>;
}
impl<D, S, C> Authenticator<D, S, C>
where D: AuthenticatorDelegate,
S: TokenStorage,
C: BorrowMut<hyper::Client>
{
pub fn new(secret: &ApplicationSecret,
delegate: D,
client: C,
storage: S,
flow_type: Option<FlowType>)
-> Authenticator<D, S, C> {
Authenticator {
flow_type: flow_type.unwrap_or(FlowType::Device(GOOGLE_DEVICE_CODE_URL.to_string())),
delegate: delegate,
storage: storage,
client: client,
secret: secret.clone(),
}
}
fn do_installed_flow(&mut self, scopes: &Vec<&str>) -> Result<Token, Box<Error>> {
let installed_type;
match self.flow_type {
FlowType::InstalledInteractive => {
installed_type = Some(InstalledFlowReturnMethod::Interactive)
}
FlowType::InstalledRedirect(port) => {
installed_type = Some(InstalledFlowReturnMethod::HTTPRedirect(port))
}
_ => installed_type = None,
}
let mut flow = InstalledFlow::new(self.client.borrow_mut(), installed_type);
flow.obtain_token(&mut self.delegate, &self.secret, scopes.iter())
}
fn retrieve_device_token(&mut self, scopes: &Vec<&str>, code_url: String) -> Result<Token, Box<Error>> {
let mut flow = DeviceFlow::new(self.client.borrow_mut(), &self.secret, &code_url);
let pi: PollInformation;
loop {
let res = flow.request_code(scopes.iter());
pi = match res {
Err(res_err) => {
match res_err {
RequestError::HttpError(err) => {
match self.delegate.connection_error(&err) {
Retry::Abort | Retry::Skip => {
return Err(Box::new(StringError::from(&err as &Error)))
}
Retry::After(d) => sleep(d),
}
}
RequestError::InvalidClient |
RequestError::NegativeServerResponse(_, _) |
RequestError::InvalidScope(_) => {
let serr = StringError::from(res_err.to_string());
self.delegate.request_failure(res_err);
return Err(Box::new(serr));
}
};
continue;
}
Ok(pi) => {
self.delegate.present_user_code(&pi);
pi
}
};
break;
}
loop {
match flow.poll_token() {
Err(ref poll_err) => {
let pts = poll_err.to_string();
match poll_err {
&&PollError::HttpError(ref err) => {
match self.delegate.connection_error(err) {
Retry::Abort | Retry::Skip => {
return Err(Box::new(StringError::from(err as &Error)))
}
Retry::After(d) => sleep(d),
}
}
&&PollError::Expired(ref t) => {
self.delegate.expired(t);
return Err(Box::new(StringError::from(pts)));
}
&&PollError::AccessDenied => {
self.delegate.denied();
return Err(Box::new(StringError::from(pts)));
}
};
}
Ok(None) => {
match self.delegate.pending(&pi) {
Retry::Abort | Retry::Skip => {
return Err(Box::new(StringError::new("Pending authentication aborted"
.to_string(),
None)))
}
Retry::After(d) => sleep(min(d, pi.interval)),
}
}
Ok(Some(token)) => return Ok(token),
}
}
}
}
impl<D, S, C> GetToken for Authenticator<D, S, C>
where D: AuthenticatorDelegate,
S: TokenStorage,
C: BorrowMut<hyper::Client>
{
fn token<'b, I, T>(&mut self, scopes: I) -> Result<Token, Box<Error>>
where T: AsRef<str> + Ord + 'b,
I: IntoIterator<Item = &'b T>
{
let (scope_key, scopes) = {
let mut sv: Vec<&str> = scopes.into_iter()
.map(|s| s.as_ref())
.collect::<Vec<&str>>();
sv.sort();
let mut sh = DefaultHasher::new();
&sv.hash(&mut sh);
let sv = sv;
(sh.finish(), sv)
};
loop {
return match self.storage.get(scope_key, &scopes) {
Ok(Some(mut t)) => {
if t.expired() {
let mut rf = RefreshFlow::new(self.client.borrow_mut());
loop {
match *rf.refresh_token(self.flow_type.clone(),
&self.secret,
&t.refresh_token) {
RefreshResult::Error(ref err) => {
match self.delegate.connection_error(err) {
Retry::Abort|Retry::Skip =>
return Err(Box::new(StringError::new(
err.description().to_string(),
None))),
Retry::After(d) => sleep(d),
}
}
RefreshResult::RefreshError(ref err_str, ref err_description) => {
self.delegate.token_refresh_failed(&err_str, &err_description);
let storage_err = match self.storage
.set(scope_key, &scopes, None) {
Ok(_) => String::new(),
Err(err) => err.to_string(),
};
return Err(Box::new(StringError::new(storage_err + err_str,
err_description.as_ref())));
}
RefreshResult::Success(ref new_t) => {
t = new_t.clone();
loop {
if let Err(err) = self.storage
.set(scope_key, &scopes, Some(t.clone())) {
match self.delegate.token_storage_failure(true, &err) {
Retry::Skip => break,
Retry::Abort => return Err(Box::new(err)),
Retry::After(d) => {
sleep(d);
continue;
}
}
}
break;
}
break;
}
}
}
}
Ok(t)
}
Ok(None) => {
match match self.flow_type.clone() {
FlowType::Device(url) => self.retrieve_device_token(&scopes, url),
FlowType::InstalledInteractive => self.do_installed_flow(&scopes),
FlowType::InstalledRedirect(_) => self.do_installed_flow(&scopes),
} {
Ok(token) => {
loop {
if let Err(err) = self.storage
.set(scope_key, &scopes, Some(token.clone())) {
match self.delegate.token_storage_failure(true, &err) {
Retry::Skip => break,
Retry::Abort => return Err(Box::new(err)),
Retry::After(d) => {
sleep(d);
continue;
}
}
}
break;
}
Ok(token)
}
Err(err) => Err(err),
}
}
Err(err) => {
match self.delegate.token_storage_failure(false, &err) {
Retry::Abort | Retry::Skip => Err(Box::new(err)),
Retry::After(d) => {
sleep(d);
continue;
}
}
}
};
}
}
fn api_key(&mut self) -> Option<String> {
if self.secret.client_id.len() == 0 {
return None;
}
Some(self.secret.client_id.clone())
}
}
pub enum Retry {
Abort,
After(Duration),
Skip,
}
#[cfg(test)]
mod tests {
use super::*;
use super::super::device::tests::MockGoogleAuth;
use super::super::types::tests::SECRET;
use super::super::types::ConsoleApplicationSecret;
use crate::authenticator_delegate::DefaultAuthenticatorDelegate;
use crate::storage::MemoryStorage;
use std::default::Default;
use hyper;
#[test]
fn flow() {
use serde_json as json;
let secret = json::from_str::<ConsoleApplicationSecret>(SECRET).unwrap().installed.unwrap();
let res = Authenticator::new(&secret, DefaultAuthenticatorDelegate,
hyper::Client::with_connector(<MockGoogleAuth as Default>::default()),
<MemoryStorage as Default>::default(), None)
.token(&["https://www.googleapis.com/auth/youtube.upload"]);
match res {
Ok(t) => assert_eq!(t.access_token, "1/fFAGRNJru1FTz70BzhT3Zg"),
_ => panic!("Expected to retrieve token in one go"),
}
}
}