diff --git a/Cargo.toml b/Cargo.toml index 84c1eb7..d9141ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,9 +24,12 @@ serde_derive = "1.0" url = "1" futures = "0.1" tokio-threadpool = "0.1" +tokio = "0.1" [dev-dependencies] getopts = "0.2" open = "1.1" yup-hyper-mock = "3.14" -tokio = "0.1" + +[workspace] +members = ["examples/test-installed/"] diff --git a/src/authenticator.rs b/src/authenticator.rs deleted file mode 100644 index a48b0f1..0000000 --- a/src/authenticator.rs +++ /dev/null @@ -1,396 +0,0 @@ -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::{DeviceFlow, GOOGLE_DEVICE_CODE_URL}; -use crate::installed::{InstalledFlow, InstalledFlowReturnMethod}; -use crate::refresh::{RefreshFlow, RefreshResult}; -use crate::storage::TokenStorage; -use crate::types::{ApplicationSecret, FlowType, RequestError, StringError, Token}; - -use futures::{future, prelude::*}; -use hyper; - -/// A generalized authenticator which will keep tokens valid and store them. -/// -/// It is the go-to helper to deal with any kind of supported authentication flow, -/// which will be kept valid and usable. -/// -/// # Device Flow -/// This involves polling the authentication server in the given intervals -/// until there is a definitive result. -/// -/// These results will be passed the `DeviceFlowHelperDelegate` implementation to deal with -/// * presenting the user code -/// * inform the user about the progress or errors -/// * abort the operation -/// -/// # Usage -/// Please have a look at the library's landing page. -pub struct Authenticator { - flow_type: FlowType, - delegate: D, - storage: S, - client: hyper::Client, - secret: ApplicationSecret, -} - -/// A provider for authorization tokens, yielding tokens valid for a given scope. -/// The `api_key()` method is an alternative in case there are no scopes or -/// if no user is involved. -pub trait GetToken { - fn token<'b, I, T>(&mut self, scopes: I) -> Box>> - where - T: AsRef + Ord + 'b, - I: IntoIterator; - - fn api_key(&mut self) -> Option; -} - -impl<'a, D, S, C: 'static> Authenticator -where - D: AuthenticatorDelegate, - S: TokenStorage, - C: hyper::client::connect::Connect, -{ - /// Returns a new `Authenticator` instance - /// - /// # Arguments - /// * `secret` - usually obtained from a client secret file produced by the - /// [developer console][dev-con] - /// * `delegate` - Used to further refine the flow of the authentication. - /// * `client` - used for all authentication https requests - /// * `storage` - used to cache authorization tokens tokens permanently. However, - /// the implementation doesn't have any particular semantic requirement, which - /// is why `NullStorage` and `MemoryStorage` can be used as well. - /// * `flow_type` - the kind of authentication to use to obtain a token for the - /// required scopes. If unset, it will be derived from the secret. - /// [dev-con]: https://console.developers.google.com - pub fn new( - secret: &ApplicationSecret, - delegate: D, - client: hyper::Client, - storage: S, - flow_type: Option, - ) -> Authenticator { - 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> { - 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.clone(), installed_type); - flow.obtain_token(self.delegate, self.secret.clone(), scopes.iter()) - } - - fn retrieve_device_token( - &mut self, - scopes: &Vec<&str>, - code_url: String, - ) -> Result> { - let mut flow = DeviceFlow::new(self.client.clone(), &self.secret, &code_url); - - // PHASE 1: REQUEST CODE - let pi: PollInformation; - loop { - let res = flow.request_code(scopes.iter()); - - pi = match res { - Err(res_err) => { - match res_err { - RequestError::ClientError(err) => match self.delegate.client_error(&err) { - Retry::Abort | Retry::Skip => { - return Err(Box::new(StringError::from(&err as &Error))); - } - Retry::After(d) => sleep(d), - }, - 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; - } - - // PHASE 1: POLL TOKEN - 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.client_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))); - } - }; // end match poll_err - } - 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 GetToken for Authenticator -where - D: AuthenticatorDelegate, - S: TokenStorage, - C: hyper::client::connect::Connect, -{ - /// Blocks until a token was retrieved from storage, from the server, or until the delegate - /// decided to abort the attempt, or the user decided not to authorize the application. - /// In any failure case, the delegate will be provided with additional information, and - /// the caller will be informed about storage related errors. - /// Otherwise it is guaranteed to be valid for the given scopes. - fn token<'b, I, T>( - &mut self, - scopes: I, - ) -> Box>> - where - T: AsRef + Ord + 'b, - I: IntoIterator, - { - let (scope_key, scopes) = { - let mut sv: Vec<&str> = scopes - .into_iter() - .map(|s| s.as_ref()) - .collect::>(); - sv.sort(); - let mut sh = DefaultHasher::new(); - &sv.hash(&mut sh); - let sv = sv; - (sh.finish(), sv) - }; - - // Get cached token. Yes, let's do an explicit return - loop { - return match self.storage.get(scope_key, &scopes) { - Ok(Some(mut t)) => { - // t needs refresh ? - if t.expired() { - let mut rf = RefreshFlow::new(self.client.clone()); - loop { - match *rf.refresh_token( - self.flow_type.clone(), - &self.secret, - &t.refresh_token, - ) { - RefreshResult::Uninitialized => { - panic!("Token flow should never get here"); - } - RefreshResult::Error(ref err) => { - match self.delegate.client_error(err) { - Retry::Abort | Retry::Skip => { - return Box::new(future::err( - Box::new(StringError::new( - err.description().to_string(), - None, - )) - as Box, - )); - } - 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 Box::new(future::err(Box::new(StringError::new( - storage_err + err_str, - err_description.as_ref(), - )) - as Box)); - } - 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 Box::new(future::err( - Box::new(err) as Box - )) - } - Retry::After(d) => { - sleep(d); - continue; - } - } - } - break; // .set() - } - break; // refresh_token loop - } - } // RefreshResult handling - } // refresh loop - } // handle expiration - Box::new(future::ok(t)) - } - Ok(None) => { - // Nothing was in storage - get a new token - // get new token. The respective sub-routine will do all the logic. - 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 Box::new(future::err( - Box::new(err) as Box - )) - } - Retry::After(d) => { - sleep(d); - continue; - } - } - } - break; - } // end attempt to save - Box::new(future::ok(token)) - } - Err(err) => Box::new(future::err(err)), - } // end match token retrieve result - } - Err(err) => match self.delegate.token_storage_failure(false, &err) { - Retry::Abort | Retry::Skip => { - Box::new(future::err(Box::new(err) as Box)) - } - Retry::After(d) => { - sleep(d); - continue; - } - }, - }; // end match - } // end loop - } - - fn api_key(&mut self) -> Option { - if self.secret.client_id.len() == 0 { - return None; - } - Some(self.secret.client_id.clone()) - } -} - -/// A utility type to indicate how operations DeviceFlowHelper operations should be retried -pub enum Retry { - /// Signal you don't want to retry - Abort, - /// Signals you want to retry after the given duration - After(Duration), - /// Instruct the caller to attempt to keep going, or choose an alternate path. - /// If this is not supported, it will have the same effect as `Abort` - Skip, -} - -#[cfg(test)] -mod tests { - use super::super::device::tests::MockGoogleAuth; - use super::super::types::tests::SECRET; - use super::super::types::ConsoleApplicationSecret; - use super::*; - use crate::authenticator_delegate::DefaultAuthenticatorDelegate; - use crate::storage::MemoryStorage; - use hyper; - use std::default::Default; - - #[test] - fn test_flow() { - use serde_json as json; - - let runtime = tokio::runtime::Runtime::new().unwrap(); - let secret = json::from_str::(SECRET) - .unwrap() - .installed - .unwrap(); - let client = hyper::Client::builder() - .executor(runtime.executor()) - .build(MockGoogleAuth::default()); - let res = Authenticator::new( - &secret, - DefaultAuthenticatorDelegate, - client, - ::default(), - None, - ) - .token(&["https://www.googleapis.com/auth/youtube.upload"]); - - match res { - Ok(t) => assert_eq!(t.access_token, "1/fFAGRNJru1FTz70BzhT3Zg"), - Err(err) => panic!("Expected to retrieve token in one go: {}", err), - } - } -} diff --git a/src/authenticator_delegate.rs b/src/authenticator_delegate.rs index 0d70d5b..6b9784b 100644 --- a/src/authenticator_delegate.rs +++ b/src/authenticator_delegate.rs @@ -4,12 +4,25 @@ use std::error::Error; use std::fmt; use std::io; -use crate::authenticator::Retry; use crate::types::RequestError; use chrono::{DateTime, Local, Utc}; use std::time::Duration; +use futures::{future, prelude::*}; +use tokio::io as tio; + +/// A utility type to indicate how operations DeviceFlowHelper operations should be retried +pub enum Retry { + /// Signal you don't want to retry + Abort, + /// Signals you want to retry after the given duration + After(Duration), + /// Instruct the caller to attempt to keep going, or choose an alternate path. + /// If this is not supported, it will have the same effect as `Abort` + Skip, +} + /// Contains state of pending authentication requests #[derive(Clone, Debug, PartialEq)] pub struct PollInformation { @@ -151,7 +164,7 @@ pub trait AuthenticatorDelegate { &mut self, url: S, need_code: bool, - ) -> Option { + ) -> Box, Error = Box> + Send> { if need_code { println!( "Please direct your browser to {}, follow the instructions and enter the \ @@ -159,19 +172,24 @@ pub trait AuthenticatorDelegate { url ); - let mut code = String::new(); - io::stdin().read_line(&mut code).ok().map(|_| { - // Remove newline - code.pop(); - code - }) + Box::new( + tio::lines(io::BufReader::new(tio::stdin())) + .into_future() + .map_err(|(e, _)| { + println!("{:?}", e); + Box::new(e) as Box + }) + .and_then(|(l, _)| { + Ok(l) + }), + ) } else { println!( "Please direct your browser to {} and follow the instructions displayed \ there.", url ); - None + Box::new(future::ok(None)) } } } diff --git a/src/installed.rs b/src/installed.rs index f964091..e760cd7 100644 --- a/src/installed.rs +++ b/src/installed.rs @@ -7,9 +7,9 @@ use std::error::Error; use std::io; use std::sync::{Arc, Mutex}; +use futures::prelude::*; use futures::stream::Stream; use futures::sync::oneshot; -use futures::{future, prelude::*}; use hyper; use hyper::{header, StatusCode, Uri}; use serde_json::error; @@ -46,6 +46,7 @@ where url.push_str(auth_uri); vec![ format!("?scope={}", scopes_string), + format!("&access_type=offline"), format!( "&redirect_uri={}", redirect_uri.unwrap_or(OOB_REDIRECT_URI.to_string()) @@ -60,9 +61,9 @@ where }) } -pub struct InstalledFlow { - client: hyper::Client, - server: Option, +pub struct InstalledFlow { + method: InstalledFlowReturnMethod, + client: hyper::client::Client, } /// cf. https://developers.google.com/identity/protocols/OAuth2InstalledApp#choosingredirecturi @@ -76,57 +77,91 @@ pub enum InstalledFlowReturnMethod { HTTPRedirect(u16), } -impl InstalledFlow -where - C: hyper::client::connect::Connect, -{ +impl<'c, C: 'c + hyper::client::connect::Connect> InstalledFlow { /// Starts a new Installed App auth flow. /// If HTTPRedirect is chosen as method and the server can't be started, the flow falls /// back to Interactive. pub fn new( - client: hyper::Client, - method: Option, + client: hyper::client::Client, + method: InstalledFlowReturnMethod, ) -> InstalledFlow { - let default = InstalledFlow { + InstalledFlow { + method: method, client: client, - server: None, - }; - match method { - None => default, - Some(InstalledFlowReturnMethod::Interactive) => default, - // Start server on localhost to accept auth code. - Some(InstalledFlowReturnMethod::HTTPRedirect(port)) => { - match InstalledFlowServer::new(port) { - Result::Err(_) => default, - Result::Ok(server) => InstalledFlow { - client: default.client, - server: Some(server), - }, - } - } } } /// Handles the token request flow; it consists of the following steps: - /// . Obtain a auhorization code with user cooperation or internal redirect. + /// . Obtain a authorization code with user cooperation or internal redirect. /// . Obtain a token and refresh token using that code. /// . Return that token /// /// It's recommended not to use the DefaultAuthenticatorDelegate, but a specialized one. - pub fn obtain_token<'a, AD: AuthenticatorDelegate, S, T>( + pub fn obtain_token<'a, AD: 'a + AuthenticatorDelegate + Send>( &mut self, auth_delegate: AD, appsecret: ApplicationSecret, - scopes: S, - ) -> impl Future> - where - T: AsRef + 'a, - S: Iterator, - { - self.get_authorization_code(auth_delegate, appsecret, scopes) - .and_then(|authcode| { - self.request_token(&appsecret, &authcode, auth_delegate.redirect_uri()) + scopes: Vec, + ) -> impl 'a + Future> + Send { + let rduri = auth_delegate.redirect_uri(); + // Start server on localhost to accept auth code. + let server = if let InstalledFlowReturnMethod::HTTPRedirect(port) = self.method { + match InstalledFlowServer::new(port) { + Result::Err(e) => Err(Box::new(e) as Box), + Result::Ok(server) => Ok(Some(server)), + } + } else { + Ok(None) + }; + let port = if let Ok(Some(ref srv)) = server { + Some(srv.port) + } else { + None + }; + let client = self.client.clone(); + let appsecclone = appsecret.clone(); + server + .into_future() + // First: Obtain authorization code from user. + .and_then(move |server| { + Self::ask_authorization_code(server, auth_delegate, &appsecclone, scopes.iter()) }) + // Exchange the authorization code provided by Google for a refresh and an access + // token. + .and_then(move |authcode| { + let request = Self::request_token(appsecret, authcode, rduri, port); + let result = client.request(request); + // Handle result here, it makes ownership tracking easier. + result + .map_err(|e| Box::new(e) as Box) + .and_then(move |r| { + let result = r + .into_body() + .concat2() + .wait() + .map(|c| String::from_utf8(c.into_bytes().to_vec()).unwrap()); // TODO: error handling + + let resp = match result { + Result::Err(e) => { + return Result::Err(Box::new(e) as Box) + } + Result::Ok(s) => s, + }; + + let token_resp: Result = + serde_json::from_str(&resp); + + match token_resp { + Result::Err(e) => { + return Result::Err(Box::new(e) as Box); + } + Result::Ok(tok) => { + Result::Ok(tok) as Result> + } + } + }) + }) + // Return the combined token. .and_then(|tokens| { // Successful response if tokens.access_token.is_some() { @@ -149,83 +184,82 @@ where tokens.error_description.unwrap_or("".to_string()) ) .as_str(), - )) as Box; + )) as Box; Result::Err(err) } }) } - /// Obtains an authorization code either interactively or via HTTP redirect (see - /// InstalledFlowReturnMethod). - fn get_authorization_code<'a, AD: AuthenticatorDelegate, S, T>( - &mut self, - auth_delegate: AD, - appsecret: ApplicationSecret, + fn ask_authorization_code<'a, AD: AuthenticatorDelegate, S, T>( + server: Option, + mut auth_delegate: AD, + appsecret: &ApplicationSecret, scopes: S, - ) -> impl Future> + ) -> Box> + Send> where T: AsRef + 'a, S: Iterator, { - let server = self.server.take(); // Will shutdown the server if present when goes out of scope - let result: Result> = match server { - None => { - let url = build_authentication_request_url( - &appsecret.auth_uri, - &appsecret.client_id, - scopes, - auth_delegate.redirect_uri(), - ); - match auth_delegate.present_user_url(&url, true /* need_code */) { - None => Result::Err(Box::new(io::Error::new( - io::ErrorKind::UnexpectedEof, - "couldn't read code", - ))), - Some(mut code) => { - // Partial backwards compatibilty in case an implementation adds a new line - // due to previous behaviour. - let ends_with_newline = - code.chars().last().map(|c| c == '\n').unwrap_or(false); - if ends_with_newline { - code.pop(); + if server.is_none() { + let url = build_authentication_request_url( + &appsecret.auth_uri, + &appsecret.client_id, + scopes, + auth_delegate.redirect_uri(), + ); + Box::new( + auth_delegate + .present_user_url(&url, true /* need_code */) + .then(|r| { + match r { + Ok(Some(mut code)) => { + // Partial backwards compatibilty in case an implementation adds a new line + // due to previous behaviour. + let ends_with_newline = + code.chars().last().map(|c| c == '\n').unwrap_or(false); + if ends_with_newline { + code.pop(); + } + Ok(code) + } + _ => Err(Box::new(io::Error::new( + io::ErrorKind::UnexpectedEof, + "couldn't read code", + )) as Box), } - Result::Ok(code) - } - } - } - Some(mut server) => { - // The redirect URI must be this very localhost URL, otherwise Google refuses - // authorization. - let url = build_authentication_request_url( - &appsecret.auth_uri, - &appsecret.client_id, - scopes, - auth_delegate - .redirect_uri() - .or_else(|| Some(format!("http://localhost:{}", server.port))), - ); - auth_delegate.present_user_url(&url, false /* need_code */); - - match server.block_till_auth() { - Result::Err(e) => Result::Err(Box::new(e)), - Result::Ok(s) => Result::Ok(s), - } - } - }; - - result.into_future() + }), + ) + } else { + let mut server = server.unwrap(); + // The redirect URI must be this very localhost URL, otherwise Google refuses + // authorization. + let url = build_authentication_request_url( + &appsecret.auth_uri, + &appsecret.client_id, + scopes, + auth_delegate + .redirect_uri() + .or_else(|| Some(format!("http://localhost:{}", server.port))), + ); + Box::new( + auth_delegate + .present_user_url(&url, false /* need_code */) + .then(move |_| server.block_till_auth()) + .map_err(|e| Box::new(e) as Box), + ) + } } /// Sends the authorization code to the provider in order to obtain access and refresh tokens. - fn request_token( - &self, - appsecret: &ApplicationSecret, - authcode: &str, + fn request_token<'a>( + appsecret: ApplicationSecret, + authcode: String, custom_redirect_uri: Option, - ) -> Result> { - let redirect_uri = custom_redirect_uri.unwrap_or_else(|| match &self.server { + port: Option, + ) -> hyper::Request { + let redirect_uri = custom_redirect_uri.unwrap_or_else(|| match port { None => OOB_REDIRECT_URI.to_string(), - Some(server) => format!("http://localhost:{}", server.port), + Some(port) => format!("http://localhost:{}", port), }); let body = form_urlencoded::Serializer::new(String::new()) @@ -238,37 +272,11 @@ where ]) .finish(); - let request = hyper::Request::post(&appsecret.token_uri) + let request = hyper::Request::post(appsecret.token_uri) .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded") .body(hyper::Body::from(body)) .unwrap(); // TODO: error check - - let result = self.client.request(request).wait(); - - let resp = String::new(); - - match result { - Result::Err(e) => return Result::Err(Box::new(e)), - Result::Ok(res) => { - let result = res - .into_body() - .concat2() - .wait() - .map(|c| String::from_utf8(c.into_bytes().to_vec()).unwrap()); // TODO: error handling - - match result { - Result::Err(e) => return Result::Err(Box::new(e)), - Result::Ok(_) => (), - } - } - } - - let token_resp: Result = 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>, - } + request } } @@ -291,37 +299,32 @@ struct InstalledFlowServer { } impl InstalledFlowServer { - fn new(port: u16) -> Result { - let bound_port = hyper::server::conn::AddrIncoming::bind(&([127, 0, 0, 1], port).into()); - match bound_port { - Result::Err(_) => Result::Err(()), - Result::Ok(bound_port) => { - let port = bound_port.local_addr().port(); + fn new(port: u16) -> Result { + let (auth_code_tx, auth_code_rx) = oneshot::channel::(); + let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>(); - let (auth_code_tx, auth_code_rx) = oneshot::channel::(); - let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>(); + let threadpool = tokio_threadpool::Builder::new() + .pool_size(1) + .name_prefix("InstalledFlowServer-") + .build(); + let service_maker = InstalledFlowServiceMaker::new(auth_code_tx); - let threadpool = tokio_threadpool::Builder::new() - .pool_size(1) - .name_prefix("InstalledFlowServer-") - .build(); - let service_maker = InstalledFlowServiceMaker::new(auth_code_tx); - let server = hyper::server::Server::builder(bound_port) - .http1_only(true) - .serve(service_maker) - .with_graceful_shutdown(shutdown_rx) - .map_err(|err| panic!("Failed badly: {}", err)); // TODO: Error handling + let addr = format!("127.0.0.1:{}", port); + let builder = hyper::server::Server::try_bind(&addr.parse().unwrap())?; + let server = builder + .http1_only(true) + .serve(service_maker) + .with_graceful_shutdown(shutdown_rx) + .map_err(|err| panic!("Failed badly: {}", err)); - threadpool.spawn(server); + threadpool.spawn(server); - Result::Ok(InstalledFlowServer { - port, - shutdown_tx: Option::Some(shutdown_tx), - auth_code_rx: Option::Some(auth_code_rx), - threadpool: Option::Some(threadpool), - }) - } - } + Result::Ok(InstalledFlowServer { + port, + shutdown_tx: Some(shutdown_tx), + auth_code_rx: Some(auth_code_rx), + threadpool: Some(threadpool), + }) } fn block_till_auth(&mut self) -> Result { @@ -471,7 +474,7 @@ impl InstalledFlowService { fn handle_url(&mut self, url: hyper::Uri) { // Google redirects to the specified localhost URL, appending the authorization // code, like this: http://localhost:8080/xyz/?code=4/731fJ3BheyCouCniPufAd280GHNV5Ju35yYcGs - // We take that code and send it to the get_authorization_code() function that + // We take that code and send it to the ask_authorization_code() function that // waits for it. for (param, val) in form_urlencoded::parse(url.query().unwrap_or("").as_bytes()) { if param == "code".to_string() { diff --git a/src/lib.rs b/src/lib.rs index 12d5e46..22b5e63 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,7 +69,6 @@ extern crate serde_derive; #[macro_use] extern crate yup_hyper_mock as hyper_mock; -mod authenticator; mod authenticator_delegate; mod device; mod helper; @@ -79,7 +78,6 @@ mod service_account; mod storage; mod types; -pub use crate::authenticator::{Authenticator, GetToken, Retry}; pub use crate::authenticator_delegate::{ AuthenticatorDelegate, DefaultAuthenticatorDelegate, PollError, PollInformation, }; diff --git a/src/service_account.rs b/src/service_account.rs index 0ede6e5..68b234e 100644 --- a/src/service_account.rs +++ b/src/service_account.rs @@ -17,9 +17,8 @@ use std::result; use std::str; use std::sync::{Arc, RwLock}; -use crate::authenticator::GetToken; use crate::storage::{hash_scopes, MemoryStorage, TokenStorage}; -use crate::types::{StringError, Token}; +use crate::types::{StringError, GetToken, Token}; use futures::stream::Stream; use futures::{future, prelude::*}; diff --git a/src/types.rs b/src/types.rs index 42746c1..ca039b3 100644 --- a/src/types.rs +++ b/src/types.rs @@ -4,6 +4,8 @@ use std::error::Error; use std::fmt; use std::str::FromStr; +use futures::{future, prelude::*}; + /// A marker trait for all Flows pub trait Flow { fn type_id() -> FlowType; @@ -179,6 +181,18 @@ impl FromStr for Scheme { } } +/// A provider for authorization tokens, yielding tokens valid for a given scope. +/// The `api_key()` method is an alternative in case there are no scopes or +/// if no user is involved. +pub trait GetToken { + fn token<'b, I, T>(&mut self, scopes: I) -> Box>> + where + T: AsRef + Ord + 'b, + I: IntoIterator; + + fn api_key(&mut self) -> Option; +} + /// Represents a token as returned by OAuth2 servers. /// /// It is produced by all authentication flows.