From b96cfcd66ad9928ecbc6d5b6c7ff40680154088f Mon Sep 17 00:00:00 2001 From: Guy Taylor Date: Thu, 21 Feb 2019 20:21:18 +0000 Subject: [PATCH] Format to fustfmt defaults and force lint check in Travis --- .travis.yml | 2 +- examples/auth.rs | 65 +++++++--- src/authenticator.rs | 176 +++++++++++++++------------ src/authenticator_delegate.rs | 33 ++--- src/device.rs | 158 +++++++++++++----------- src/helper.rs | 18 +-- src/installed.rs | 222 +++++++++++++++++++--------------- src/lib.rs | 26 ++-- src/refresh.rs | 77 ++++++------ src/service_account.rs | 98 +++++++++------ src/storage.rs | 47 +++---- src/types.rs | 39 +++--- 12 files changed, 553 insertions(+), 408 deletions(-) diff --git a/.travis.yml b/.travis.yml index 66c848f..e0c4d43 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,7 @@ jobs: - rustup component add clippy - rustup component add rustfmt script: - - cargo fmt --all -- --check || true + - cargo fmt --all -- --check - cargo clippy --all-targets --all-features -- -D warnings || true - stage: coverage diff --git a/examples/auth.rs b/examples/auth.rs index 11a96d0..6303104 100644 --- a/examples/auth.rs +++ b/examples/auth.rs @@ -1,12 +1,11 @@ -use yup_oauth2::{self as oauth2, GetToken}; -use yup_hyper_mock as mock; -use chrono::{Local}; -use getopts::{HasArg,Options,Occur,Fail}; -use std::env; +use chrono::Local; +use getopts::{Fail, HasArg, Occur, Options}; use std::default::Default; -use std::time::Duration; +use std::env; use std::thread::sleep; - +use std::time::Duration; +use yup_hyper_mock as mock; +use yup_oauth2::{self as oauth2, GetToken}; fn usage(program: &str, opts: &Options, err: Option) -> ! { if err.is_some() { @@ -14,9 +13,14 @@ fn usage(program: &str, opts: &Options, err: Option) -> ! { std::process::exit(1); } println!("{}", opts.short_usage(program) + " SCOPE [SCOPE ...]"); - println!("{}", opts.usage("A program to authenticate against oauthv2 services.\n\ - See https://developers.google.com/youtube/registering_an_application\n\ - and https://developers.google.com/youtube/v3/guides/authentication#devices")); + println!( + "{}", + opts.usage( + "A program to authenticate against oauthv2 services.\n\ + See https://developers.google.com/youtube/registering_an_application\n\ + and https://developers.google.com/youtube/v3/guides/authentication#devices" + ) + ); std::process::exit(0); } @@ -26,8 +30,22 @@ fn main() { let prog = args[0].clone(); let mut opts = Options::new(); - opts.opt("c", "id", "oauthv2 ID of your application", "CLIENT_ID", HasArg::Yes, Occur::Req) - .opt("s", "secret", "oauthv2 secret of your application", "CLIENT_SECRET", HasArg::Yes, Occur::Req); + opts.opt( + "c", + "id", + "oauthv2 ID of your application", + "CLIENT_ID", + HasArg::Yes, + Occur::Req, + ) + .opt( + "s", + "secret", + "oauthv2 secret of your application", + "CLIENT_SECRET", + HasArg::Yes, + Occur::Req, + ); let m = match opts.parse(&args[1..]) { Ok(m) => m, @@ -37,7 +55,9 @@ fn main() { }; if m.free.len() == 0 { - let msg = Fail::ArgumentMissing("you must provide one or more authorization scopes as free options".to_string()); + let msg = Fail::ArgumentMissing( + "you must provide one or more authorization scopes as free options".to_string(), + ); usage(&prog, &opts, Some(msg)); } @@ -55,11 +75,15 @@ fn main() { struct StdoutHandler; impl oauth2::AuthenticatorDelegate for StdoutHandler { fn present_user_code(&mut self, pi: &oauth2::PollInformation) { - println!("Please enter '{}' at {} and authenticate the application for the\n\ + println!( + "Please enter '{}' at {} and authenticate the application for the\n\ given scopes. This is not a test !\n\ You have time until {} to do that. Do not terminate the program until you deny or grant access !", - pi.user_code, pi.verification_url, pi.expires_at.with_timezone(&Local)); + pi.user_code, + pi.verification_url, + pi.expires_at.with_timezone(&Local) + ); let delay = Duration::from_secs(5); println!("Browser opens automatically in {:?} seconds", delay); sleep(delay); @@ -69,17 +93,18 @@ fn main() { } let client = hyper::Client::with_connector(mock::TeeConnector { - connector: hyper::net::HttpConnector - }); + connector: hyper::net::HttpConnector, + }); - match oauth2::Authenticator::new(&secret, StdoutHandler, client, - oauth2::NullStorage, None).token(&m.free) { + match oauth2::Authenticator::new(&secret, StdoutHandler, client, oauth2::NullStorage, None) + .token(&m.free) + { Ok(t) => { println!("Authentication granted !"); println!("You should store the following information for use, or revoke it."); println!("All dates are given in UTC."); println!("{:?}", t); - }, + } Err(err) => { println!("Access token wasn't obtained: {}", err); std::process::exit(10); diff --git a/src/authenticator.rs b/src/authenticator.rs index fa47897..2791080 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -9,11 +9,11 @@ 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::device::{DeviceFlow, GOOGLE_DEVICE_CODE_URL}; use crate::installed::{InstalledFlow, InstalledFlowReturnMethod}; -use crate::refresh::{RefreshResult, RefreshFlow}; +use crate::refresh::{RefreshFlow, RefreshResult}; use crate::storage::TokenStorage; -use crate::types::{RequestError, StringError, Token, FlowType, ApplicationSecret}; +use crate::types::{ApplicationSecret, FlowType, RequestError, StringError, Token}; use hyper; @@ -46,16 +46,18 @@ pub struct Authenticator { /// if no user is involved. pub trait GetToken { fn token<'b, I, T>(&mut self, scopes: I) -> Result> - where T: AsRef + Ord + 'b, - I: IntoIterator; + where + T: AsRef + Ord + 'b, + I: IntoIterator; fn api_key(&mut self) -> Option; } impl Authenticator - where D: AuthenticatorDelegate, - S: TokenStorage, - C: BorrowMut +where + D: AuthenticatorDelegate, + S: TokenStorage, + C: BorrowMut, { /// Returns a new `Authenticator` instance /// @@ -70,12 +72,13 @@ impl Authenticator /// * `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: C, - storage: S, - flow_type: Option) - -> Authenticator { + pub fn new( + secret: &ApplicationSecret, + delegate: D, + client: C, + storage: S, + flow_type: Option, + ) -> Authenticator { Authenticator { flow_type: flow_type.unwrap_or(FlowType::Device(GOOGLE_DEVICE_CODE_URL.to_string())), delegate: delegate, @@ -85,7 +88,6 @@ impl Authenticator } } - fn do_installed_flow(&mut self, scopes: &Vec<&str>) -> Result> { let installed_type; @@ -103,7 +105,11 @@ impl Authenticator flow.obtain_token(&mut self.delegate, &self.secret, scopes.iter()) } - fn retrieve_device_token(&mut self, scopes: &Vec<&str>, code_url: String) -> Result> { + fn retrieve_device_token( + &mut self, + scopes: &Vec<&str>, + code_url: String, + ) -> Result> { let mut flow = DeviceFlow::new(self.client.borrow_mut(), &self.secret, &code_url); // PHASE 1: REQUEST CODE @@ -117,14 +123,14 @@ impl Authenticator RequestError::HttpError(err) => { match self.delegate.connection_error(&err) { Retry::Abort | Retry::Skip => { - return Err(Box::new(StringError::from(&err as &Error))) + return Err(Box::new(StringError::from(&err as &Error))); } Retry::After(d) => sleep(d), } } - RequestError::InvalidClient | - RequestError::NegativeServerResponse(_, _) | - RequestError::InvalidScope(_) => { + 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)); @@ -149,7 +155,7 @@ impl Authenticator &&PollError::HttpError(ref err) => { match self.delegate.connection_error(err) { Retry::Abort | Retry::Skip => { - return Err(Box::new(StringError::from(err as &Error))) + return Err(Box::new(StringError::from(err as &Error))); } Retry::After(d) => sleep(d), } @@ -164,16 +170,15 @@ impl Authenticator } }; // 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(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), } } @@ -181,9 +186,10 @@ impl Authenticator } impl GetToken for Authenticator - where D: AuthenticatorDelegate, - S: TokenStorage, - C: BorrowMut +where + D: AuthenticatorDelegate, + S: TokenStorage, + C: BorrowMut, { /// 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. @@ -191,11 +197,13 @@ impl GetToken for Authenticator /// 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) -> Result> - where T: AsRef + Ord + 'b, - I: IntoIterator + where + T: AsRef + Ord + 'b, + I: IntoIterator, { let (scope_key, scopes) = { - let mut sv: Vec<&str> = scopes.into_iter() + let mut sv: Vec<&str> = scopes + .into_iter() .map(|s| s.as_ref()) .collect::>(); sv.sort(); @@ -213,33 +221,41 @@ impl GetToken for Authenticator 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) { + 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 => + Retry::Abort | Retry::Skip => { return Err(Box::new(StringError::new( - err.description().to_string(), - None))), + 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()))); + 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())) { + 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)), @@ -253,9 +269,9 @@ impl GetToken for Authenticator } break; // refresh_token loop } - }// RefreshResult handling - }// refresh loop - }// handle expiration + } // RefreshResult handling + } // refresh loop + } // handle expiration Ok(t) } Ok(None) => { @@ -268,8 +284,9 @@ impl GetToken for Authenticator } { Ok(token) => { loop { - if let Err(err) = self.storage - .set(scope_key, &scopes, Some(token.clone())) { + 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)), @@ -280,23 +297,21 @@ impl GetToken for Authenticator } } break; - }// end attempt to save + } // end attempt to save Ok(token) } Err(err) => Err(err), - }// end match token retrieve result + } // end match token retrieve result } - Err(err) => { - match self.delegate.token_storage_failure(false, &err) { - Retry::Abort | Retry::Skip => Err(Box::new(err)), - Retry::After(d) => { - sleep(d); - continue; - } + Err(err) => match self.delegate.token_storage_failure(false, &err) { + Retry::Abort | Retry::Skip => Err(Box::new(err)), + Retry::After(d) => { + sleep(d); + continue; } - } - };// end match - }// end loop + }, + }; // end match + } // end loop } fn api_key(&mut self) -> Option { @@ -307,7 +322,6 @@ impl GetToken for Authenticator } } - /// A utility type to indicate how operations DeviceFlowHelper operations should be retried pub enum Retry { /// Signal you don't want to retry @@ -319,27 +333,33 @@ pub enum Retry { 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 super::*; use crate::authenticator_delegate::DefaultAuthenticatorDelegate; use crate::storage::MemoryStorage; - use std::default::Default; use hyper; + use std::default::Default; #[test] fn flow() { use serde_json as json; - let secret = json::from_str::(SECRET).unwrap().installed.unwrap(); - let res = Authenticator::new(&secret, DefaultAuthenticatorDelegate, - hyper::Client::with_connector(::default()), - ::default(), None) - .token(&["https://www.googleapis.com/auth/youtube.upload"]); + let secret = json::from_str::(SECRET) + .unwrap() + .installed + .unwrap(); + let res = Authenticator::new( + &secret, + DefaultAuthenticatorDelegate, + hyper::Client::with_connector(::default()), + ::default(), + None, + ) + .token(&["https://www.googleapis.com/auth/youtube.upload"]); match res { Ok(t) => assert_eq!(t.access_token, "1/fFAGRNJru1FTz70BzhT3Zg"), diff --git a/src/authenticator_delegate.rs b/src/authenticator_delegate.rs index 78302d9..affb1ca 100644 --- a/src/authenticator_delegate.rs +++ b/src/authenticator_delegate.rs @@ -1,8 +1,8 @@ use hyper; +use std::error::Error; use std::fmt; use std::io; -use std::error::Error; use crate::authenticator::Retry; use crate::types::RequestError; @@ -53,8 +53,6 @@ impl fmt::Display for PollError { } } - - /// A partially implemented trait to interact with the `Authenticator` /// /// The only method that needs to be implemented manually is `present_user_code(...)`, @@ -123,12 +121,15 @@ pub trait AuthenticatorDelegate { /// * Will be called exactly once, provided we didn't abort during `request_code` phase. /// * Will only be called if the Authenticator's flow_type is `FlowType::Device`. fn present_user_code(&mut self, pi: &PollInformation) { - println!("Please enter {} at {} and grant access to this application", - pi.user_code, - pi.verification_url); + println!( + "Please enter {} at {} and grant access to this application", + pi.user_code, pi.verification_url + ); println!("Do not close this application until you either denied or granted access."); - println!("You have time until {}.", - pi.expires_at.with_timezone(&Local)); + println!( + "You have time until {}.", + pi.expires_at.with_timezone(&Local) + ); } /// This method is used by the InstalledFlow. @@ -137,9 +138,11 @@ pub trait AuthenticatorDelegate { /// used. fn present_user_url(&mut self, url: &String, need_code: bool) -> Option { if need_code { - println!("Please direct your browser to {}, follow the instructions and enter the \ - code displayed here: ", - url); + println!( + "Please direct your browser to {}, follow the instructions and enter the \ + code displayed here: ", + url + ); let mut code = String::new(); io::stdin().read_line(&mut code).ok().map(|_| { @@ -148,9 +151,11 @@ pub trait AuthenticatorDelegate { code }) } else { - println!("Please direct your browser to {} and follow the instructions displayed \ - there.", - url); + println!( + "Please direct your browser to {} and follow the instructions displayed \ + there.", + url + ); None } } diff --git a/src/device.rs b/src/device.rs index 1f3a876..ee6ddb1 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,19 +1,19 @@ +use std::default::Default; use std::iter::IntoIterator; use std::time::Duration; -use std::default::Default; +use chrono::{self, Utc}; use hyper; use hyper::header::ContentType; -use url::form_urlencoded; use itertools::Itertools; use serde_json as json; -use chrono::{self, Utc}; use std::borrow::BorrowMut; -use std::io::Read; use std::i64; +use std::io::Read; +use url::form_urlencoded; -use crate::types::{ApplicationSecret, Token, FlowType, Flow, RequestError, JsonError}; use crate::authenticator_delegate::{PollError, PollInformation}; +use crate::types::{ApplicationSecret, Flow, FlowType, JsonError, RequestError, Token}; pub const GOOGLE_DEVICE_CODE_URL: &'static str = "https://accounts.google.com/o/oauth2/device/code"; @@ -46,10 +46,14 @@ impl Flow for DeviceFlow { } } impl DeviceFlow - where C: BorrowMut +where + C: BorrowMut, { - - pub fn new>(client: C, secret: &ApplicationSecret, device_code_url: S) -> DeviceFlow { + pub fn new>( + client: C, + secret: &ApplicationSecret, + device_code_url: S, + ) -> DeviceFlow { DeviceFlow { client: client, device_code: Default::default(), @@ -75,11 +79,10 @@ impl DeviceFlow /// * If called after a successful result was returned at least once. /// # Examples /// See test-cases in source code for a more complete example. - pub fn request_code<'b, T, I>(&mut self, - scopes: I) - -> Result - where T: AsRef + 'b, - I: IntoIterator + pub fn request_code<'b, T, I>(&mut self, scopes: I) -> Result + where + T: AsRef + 'b, + I: IntoIterator, { if self.state.is_some() { panic!("Must not be called after we have obtained a token and have no error"); @@ -88,28 +91,35 @@ impl DeviceFlow // note: cloned() shouldn't be needed, see issue // https://github.com/servo/rust-url/issues/81 let req = form_urlencoded::Serializer::new(String::new()) - .extend_pairs(&[("client_id", &self.application_secret.client_id), - ("scope", &scopes - .into_iter() - .map(|s| s.as_ref()) - .intersperse(" ") - .collect::())]) + .extend_pairs(&[ + ("client_id", &self.application_secret.client_id), + ( + "scope", + &scopes + .into_iter() + .map(|s| s.as_ref()) + .intersperse(" ") + .collect::(), + ), + ]) .finish(); // note: works around bug in rustlang // https://github.com/rust-lang/rust/issues/22252 - let ret = match self.client + let ret = match self + .client .borrow_mut() .post(&self.device_code_url) - .header(ContentType("application/x-www-form-urlencoded".parse().unwrap())) + .header(ContentType( + "application/x-www-form-urlencoded".parse().unwrap(), + )) .body(&*req) - .send() { + .send() + { Err(err) => { return Err(RequestError::HttpError(err)); } Ok(mut res) => { - - #[derive(Deserialize)] struct JsonData { device_code: String, @@ -166,13 +176,11 @@ impl DeviceFlow pub fn poll_token(&mut self) -> Result, &PollError> { // clone, as we may re-assign our state later let pi = match self.state { - Some(ref s) => { - match *s { - DeviceFlowState::Pending(ref pi) => pi.clone(), - DeviceFlowState::Error => return Err(self.error.as_ref().unwrap()), - DeviceFlowState::Success(ref t) => return Ok(Some(t.clone())), - } - } + Some(ref s) => match *s { + DeviceFlowState::Pending(ref pi) => pi.clone(), + DeviceFlowState::Error => return Err(self.error.as_ref().unwrap()), + DeviceFlowState::Success(ref t) => return Ok(Some(t.clone())), + }, _ => panic!("You have to call request_code() beforehand"), }; @@ -184,18 +192,24 @@ impl DeviceFlow // We should be ready for a new request let req = form_urlencoded::Serializer::new(String::new()) - .extend_pairs(&[("client_id", &self.application_secret.client_id[..]), - ("client_secret", &self.application_secret.client_secret), - ("code", &self.device_code), - ("grant_type", "http://oauth.net/grant_type/device/1.0")]) + .extend_pairs(&[ + ("client_id", &self.application_secret.client_id[..]), + ("client_secret", &self.application_secret.client_secret), + ("code", &self.device_code), + ("grant_type", "http://oauth.net/grant_type/device/1.0"), + ]) .finish(); - let json_str: String = match self.client + let json_str: String = match self + .client .borrow_mut() .post(&self.application_secret.token_uri) - .header(ContentType("application/x-www-form-urlencoded".parse().unwrap())) + .header(ContentType( + "application/x-www-form-urlencoded".parse().unwrap(), + )) .body(&*req) - .send() { + .send() + { Err(err) => { self.error = Some(PollError::HttpError(err)); return Err(self.error.as_ref().unwrap()); @@ -237,48 +251,52 @@ impl DeviceFlow } } - #[cfg(test)] pub mod tests { use super::*; + use hyper; use std::default::Default; use std::time::Duration; - use hyper; - use yup_hyper_mock::{SequentialConnector, MockStream}; + use yup_hyper_mock::{MockStream, SequentialConnector}; pub struct MockGoogleAuth(SequentialConnector); impl Default for MockGoogleAuth { fn default() -> MockGoogleAuth { let mut c = MockGoogleAuth(Default::default()); - c.0.content.push("HTTP/1.1 200 OK\r\n\ - Server: BOGUS\r\n\ - \r\n\ - {\r\n\ - \"device_code\" : \"4/L9fTtLrhY96442SEuf1Rl3KLFg3y\",\r\n\ - \"user_code\" : \"a9xfwk9c\",\r\n\ - \"verification_url\" : \"http://www.google.com/device\",\r\n\ - \"expires_in\" : 1800,\r\n\ - \"interval\" : 0\r\n\ - }" - .to_string()); + c.0.content.push( + "HTTP/1.1 200 OK\r\n\ + Server: BOGUS\r\n\ + \r\n\ + {\r\n\ + \"device_code\" : \"4/L9fTtLrhY96442SEuf1Rl3KLFg3y\",\r\n\ + \"user_code\" : \"a9xfwk9c\",\r\n\ + \"verification_url\" : \"http://www.google.com/device\",\r\n\ + \"expires_in\" : 1800,\r\n\ + \"interval\" : 0\r\n\ + }" + .to_string(), + ); - c.0.content.push("HTTP/1.1 200 OK\r\n\ - Server: BOGUS\r\n\ - \r\n\ - {\r\n\ - \"error\" : \"authorization_pending\"\r\n\ - }" - .to_string()); + c.0.content.push( + "HTTP/1.1 200 OK\r\n\ + Server: BOGUS\r\n\ + \r\n\ + {\r\n\ + \"error\" : \"authorization_pending\"\r\n\ + }" + .to_string(), + ); - c.0.content.push("HTTP/1.1 200 OK\r\nServer: \ - BOGUS\r\n\r\n{\r\n\"access_token\":\"1/fFAGRNJru1FTz70BzhT3Zg\",\ - \r\n\"expires_in\":3920,\r\n\"token_type\":\"Bearer\",\ - \r\n\"refresh_token\":\ - \"1/6BMfW9j53gdGImsixUH6kU5RsR4zwI9lUVX-tqf8JXQ\"\r\n}" - .to_string()); + c.0.content.push( + "HTTP/1.1 200 OK\r\nServer: \ + BOGUS\r\n\r\n{\r\n\"access_token\":\"1/fFAGRNJru1FTz70BzhT3Zg\",\ + \r\n\"expires_in\":3920,\r\n\"token_type\":\"Bearer\",\ + \r\n\"refresh_token\":\ + \"1/6BMfW9j53gdGImsixUH6kU5RsR4zwI9lUVX-tqf8JXQ\"\r\n}" + .to_string(), + ); c - } } @@ -298,10 +316,12 @@ pub mod tests { let appsecret = parse_application_secret(&TEST_APP_SECRET.to_string()).unwrap(); let mut flow = DeviceFlow::new( - hyper::Client::with_connector(::default()), &appsecret, GOOGLE_DEVICE_CODE_URL); + hyper::Client::with_connector(::default()), + &appsecret, + GOOGLE_DEVICE_CODE_URL, + ); - match flow.request_code( - &["https://www.googleapis.com/auth/youtube.upload"]) { + match flow.request_code(&["https://www.googleapis.com/auth/youtube.upload"]) { Ok(pi) => assert_eq!(pi.interval, Duration::from_secs(0)), _ => unreachable!(), } diff --git a/src/helper.rs b/src/helper.rs index 00a9bd5..15675ea 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -9,12 +9,12 @@ use serde_json; -use std::io::{self, Read}; use std::fs; +use std::io::{self, Read}; use std::path::Path; use crate::service_account::ServiceAccountKey; -use crate::types::{ConsoleApplicationSecret, ApplicationSecret}; +use crate::types::{ApplicationSecret, ConsoleApplicationSecret}; /// Read an application secret from a file. pub fn read_application_secret(path: &Path) -> io::Result { @@ -31,18 +31,20 @@ pub fn read_application_secret(path: &Path) -> io::Result { pub fn parse_application_secret(secret: &String) -> io::Result { let result: serde_json::Result = serde_json::from_str(secret); match result { - Err(e) => { - Err(io::Error::new(io::ErrorKind::InvalidData, - format!("Bad application secret: {}", e))) - } + Err(e) => Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("Bad application secret: {}", e), + )), Ok(decoded) => { if decoded.web.is_some() { Ok(decoded.web.unwrap()) } else if decoded.installed.is_some() { Ok(decoded.installed.unwrap()) } else { - Err(io::Error::new(io::ErrorKind::InvalidData, - "Unknown application secret format")) + Err(io::Error::new( + io::ErrorKind::InvalidData, + "Unknown application secret format", + )) } } } diff --git a/src/installed.rs b/src/installed.rs index aa54ce3..43037c4 100644 --- a/src/installed.rs +++ b/src/installed.rs @@ -10,8 +10,8 @@ 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 std::sync::Mutex; use hyper; use hyper::{client, header, server, status, uri}; @@ -19,21 +19,23 @@ 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; +use crate::types::{ApplicationSecret, Token}; const OOB_REDIRECT_URI: &'static str = "urn:ietf:wg:oauth:2.0:oob"; /// Assembles a URL to request an authorization token (with user interaction). /// Note that the redirect_uri here has to be either None or some variation of /// http://localhost:{port}, or the authorization won't work (error "redirect_uri_mismatch") -fn build_authentication_request_url<'a, T, I>(auth_uri: &str, - client_id: &str, - scopes: I, - redirect_uri: Option) - -> String - where T: AsRef + 'a, - I: IntoIterator +fn build_authentication_request_url<'a, T, I>( + auth_uri: &str, + client_id: &str, + scopes: I, + redirect_uri: Option, +) -> String +where + T: AsRef + 'a, + I: IntoIterator, { let mut url = String::new(); let mut scopes_string = scopes.into_iter().fold(String::new(), |mut acc, sc| { @@ -45,16 +47,20 @@ fn build_authentication_request_url<'a, T, I>(auth_uri: &str, 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 - }) + 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 { @@ -77,7 +83,8 @@ pub enum InstalledFlowReturnMethod { } impl InstalledFlow - where C: BorrowMut +where + C: BorrowMut, { /// Starts a new Installed App auth flow. /// If HTTPRedirect is chosen as method and the server can't be started, the flow falls @@ -100,19 +107,18 @@ impl InstalledFlow Result::Err(_) => default, Result::Ok(server) => { let (tx, rx) = channel(); - let listening = - server.handle(InstalledFlowHandler { auth_code_snd: Mutex::new(tx) }); + 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), - } - } + Result::Ok(listening) => InstalledFlow { + client: default.client, + server: Some(listening), + port: Some(port), + auth_code_rcv: Some(rx), + }, } } } @@ -126,13 +132,15 @@ impl InstalledFlow /// . Return that token /// /// It's recommended not to use the DefaultAuthenticatorDelegate, but a specialized one. - pub fn obtain_token<'a, AD: AuthenticatorDelegate, S, T>(&mut self, - auth_delegate: &mut AD, - appsecret: &ApplicationSecret, - scopes: S) - -> Result> - where T: AsRef + 'a, - S: Iterator + pub fn obtain_token<'a, AD: AuthenticatorDelegate, S, T>( + &mut self, + auth_delegate: &mut AD, + appsecret: &ApplicationSecret, + scopes: S, + ) -> Result> + where + T: AsRef + 'a, + S: Iterator, { let authcode = self.get_authorization_code(auth_delegate, &appsecret, scopes)?; let tokens = self.request_token(&appsecret, &authcode, auth_delegate.redirect_uri())?; @@ -150,37 +158,44 @@ impl InstalledFlow token.set_expiry_absolute(); Result::Ok(token) } else { - let err = io::Error::new(io::ErrorKind::Other, - format!("Token API error: {} {}", - tokens.error.unwrap_or("".to_string()), - tokens.error_description - .unwrap_or("".to_string())) - .as_str()); + let err = io::Error::new( + io::ErrorKind::Other, + format!( + "Token API error: {} {}", + tokens.error.unwrap_or("".to_string()), + tokens.error_description.unwrap_or("".to_string()) + ) + .as_str(), + ); Result::Err(Box::new(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: &mut AD, - appsecret: &ApplicationSecret, - scopes: S) - -> Result> - where T: AsRef + 'a, - S: Iterator + fn get_authorization_code<'a, AD: AuthenticatorDelegate, S, T>( + &mut self, + auth_delegate: &mut AD, + appsecret: &ApplicationSecret, + scopes: S, + ) -> Result> + where + T: AsRef + 'a, + S: Iterator, { let result: Result> = match self.server { None => { - let url = build_authentication_request_url(&appsecret.auth_uri, - &appsecret.client_id, - scopes, - auth_delegate.redirect_uri()); + 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"))) - } + 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. @@ -196,12 +211,14 @@ impl InstalledFlow Some(_) => { // 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:{}", self.port.unwrap_or(8080))) - })); + let url = build_authentication_request_url( + &appsecret.auth_uri, + &appsecret.client_id, + scopes, + auth_delegate.redirect_uri().or_else(|| { + Some(format!("http://localhost:{}", self.port.unwrap_or(8080))) + }), + ); auth_delegate.present_user_url(&url, false /* need_code */); match self.auth_code_rcv.as_ref().unwrap().recv() { @@ -215,31 +232,35 @@ impl InstalledFlow } /// Sends the authorization code to the provider in order to obtain access and refresh tokens. - fn request_token(&mut self, - appsecret: &ApplicationSecret, - authcode: &str, - custom_redirect_uri: Option) - -> Result> { - let redirect_uri = custom_redirect_uri.unwrap_or_else(|| { - match self.port { - None => OOB_REDIRECT_URI.to_string(), - Some(p) => format!("http://localhost:{}", p), - } + fn request_token( + &mut self, + appsecret: &ApplicationSecret, + authcode: &str, + custom_redirect_uri: Option, + ) -> Result> { + let redirect_uri = custom_redirect_uri.unwrap_or_else(|| match self.port { + None => OOB_REDIRECT_URI.to_string(), + Some(p) => 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())]) + .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 = self.client + let result: Result = self + .client .borrow_mut() .post(&appsecret.token_uri) .body(&body) - .header(header::ContentType("application/x-www-form-urlencoded".parse().unwrap())) + .header(header::ContentType( + "application/x-www-form-urlencoded".parse().unwrap(), + )) .send(); let mut resp = String::new(); @@ -296,10 +317,11 @@ impl server::Handler for InstalledFlowHandler { } else { self.handle_url(url.unwrap()); *rp.status_mut() = status::StatusCode::Ok; - let _ = - rp.send("SuccessYou may now \ - close this window." - .as_ref()); + let _ = rp.send( + "SuccessYou may now \ + close this window." + .as_ref(), + ); } } _ => { @@ -321,7 +343,6 @@ impl InstalledFlowHandler { let _ = self.auth_code_snd.lock().unwrap().send(val); } } - } } @@ -330,29 +351,34 @@ mod tests { use super::build_authentication_request_url; use super::InstalledFlowHandler; - use std::sync::Mutex; use std::sync::mpsc::channel; + use std::sync::Mutex; 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)); + 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 handler = InstalledFlowHandler { + auth_code_snd: Mutex::new(tx), + }; // URLs are usually a bit botched let url = Url::parse("http://example.com:1234/?code=ab/c%2Fd#").unwrap(); handler.handle_url(url); diff --git a/src/lib.rs b/src/lib.rs index a7884af..03e0ff9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,7 +37,7 @@ //! ```test_harness,no_run //! #[macro_use] //! extern crate serde_derive; -//! +//! //! use yup_oauth2::{Authenticator, DefaultAuthenticatorDelegate, PollInformation, ConsoleApplicationSecret, MemoryStorage, GetToken}; //! use serde_json as json; //! use std::default::Default; @@ -73,12 +73,12 @@ extern crate chrono; extern crate hyper; extern crate hyper_native_tls; +extern crate itertools; #[cfg(test)] extern crate log; +extern crate url; #[cfg(test)] extern crate yup_hyper_mock; -extern crate url; -extern crate itertools; mod authenticator; mod authenticator_delegate; @@ -90,14 +90,16 @@ mod service_account; mod storage; mod types; -pub use crate::device::{GOOGLE_DEVICE_CODE_URL, DeviceFlow}; -pub use crate::refresh::{RefreshFlow, RefreshResult}; -pub use crate::types::{Token, FlowType, ApplicationSecret, ConsoleApplicationSecret, Scheme, TokenType}; -pub use crate::installed::{InstalledFlow, InstalledFlowReturnMethod}; -pub use crate::storage::{TokenStorage, NullStorage, MemoryStorage, DiskTokenStorage}; -pub use crate::authenticator::{Authenticator, Retry, GetToken}; -pub use crate::authenticator_delegate::{AuthenticatorDelegate, DefaultAuthenticatorDelegate, PollError, - PollInformation}; +pub use crate::authenticator::{Authenticator, GetToken, Retry}; +pub use crate::authenticator_delegate::{ + AuthenticatorDelegate, DefaultAuthenticatorDelegate, PollError, PollInformation, +}; +pub use crate::device::{DeviceFlow, GOOGLE_DEVICE_CODE_URL}; pub use crate::helper::*; +pub use crate::installed::{InstalledFlow, InstalledFlowReturnMethod}; +pub use crate::refresh::{RefreshFlow, RefreshResult}; pub use crate::service_account::*; - +pub use crate::storage::{DiskTokenStorage, MemoryStorage, NullStorage, TokenStorage}; +pub use crate::types::{ + ApplicationSecret, ConsoleApplicationSecret, FlowType, Scheme, Token, TokenType, +}; diff --git a/src/refresh.rs b/src/refresh.rs index baefb08..2bd20bf 100644 --- a/src/refresh.rs +++ b/src/refresh.rs @@ -1,13 +1,13 @@ use crate::types::{ApplicationSecret, FlowType, JsonError}; +use super::Token; use chrono::Utc; use hyper; use hyper::header::ContentType; use serde_json as json; -use url::form_urlencoded; -use super::Token; use std::borrow::BorrowMut; use std::io::Read; +use url::form_urlencoded; /// Implements the [Outh2 Refresh Token Flow](https://developers.google.com/youtube/v3/guides/authentication#devices). /// @@ -19,7 +19,6 @@ pub struct RefreshFlow { result: RefreshResult, } - /// All possible outcomes of the refresh flow pub enum RefreshResult { /// Indicates connection failure @@ -31,7 +30,8 @@ pub enum RefreshResult { } impl RefreshFlow - where C: BorrowMut +where + C: BorrowMut, { pub fn new(client: C) -> RefreshFlow { RefreshFlow { @@ -54,29 +54,36 @@ impl RefreshFlow /// /// # Examples /// Please see the crate landing page for an example. - pub fn refresh_token(&mut self, - flow_type: FlowType, - client_secret: &ApplicationSecret, - refresh_token: &str) - -> &RefreshResult { + pub fn refresh_token( + &mut self, + flow_type: FlowType, + client_secret: &ApplicationSecret, + refresh_token: &str, + ) -> &RefreshResult { let _ = flow_type; if let RefreshResult::Success(_) = self.result { return &self.result; } let req = form_urlencoded::Serializer::new(String::new()) - .extend_pairs(&[("client_id", client_secret.client_id.as_ref()), - ("client_secret", client_secret.client_secret.as_ref()), - ("refresh_token", refresh_token), - ("grant_type", "refresh_token")]) + .extend_pairs(&[ + ("client_id", client_secret.client_id.as_ref()), + ("client_secret", client_secret.client_secret.as_ref()), + ("refresh_token", refresh_token), + ("grant_type", "refresh_token"), + ]) .finish(); - let json_str: String = match self.client + let json_str: String = match self + .client .borrow_mut() .post(&client_secret.token_uri) - .header(ContentType("application/x-www-form-urlencoded".parse().unwrap())) + .header(ContentType( + "application/x-www-form-urlencoded".parse().unwrap(), + )) .body(&*req) - .send() { + .send() + { Err(err) => { self.result = RefreshResult::Error(err); return &self.result; @@ -116,32 +123,32 @@ impl RefreshFlow } } - - #[cfg(test)] mod tests { + use super::super::FlowType; + use super::*; + use crate::device::GOOGLE_DEVICE_CODE_URL; + use crate::helper::parse_application_secret; use hyper; use std::default::Default; - use super::*; - use super::super::FlowType; use yup_hyper_mock::{MockStream, SequentialConnector}; - use crate::helper::parse_application_secret; - use crate::device::GOOGLE_DEVICE_CODE_URL; struct MockGoogleRefresh(SequentialConnector); impl Default for MockGoogleRefresh { fn default() -> MockGoogleRefresh { let mut c = MockGoogleRefresh(Default::default()); - c.0.content.push("HTTP/1.1 200 OK\r\n\ - Server: BOGUS\r\n\ - \r\n\ - {\r\n\ - \"access_token\":\"1/fFAGRNJru1FTz70BzhT3Zg\",\r\n\ - \"expires_in\":3920,\r\n\ - \"token_type\":\"Bearer\"\r\n\ - }" - .to_string()); + c.0.content.push( + "HTTP/1.1 200 OK\r\n\ + Server: BOGUS\r\n\ + \r\n\ + {\r\n\ + \"access_token\":\"1/fFAGRNJru1FTz70BzhT3Zg\",\r\n\ + \"expires_in\":3920,\r\n\ + \"token_type\":\"Bearer\"\r\n\ + }" + .to_string(), + ); c } @@ -159,14 +166,16 @@ mod tests { #[test] fn refresh_flow() { - let appsecret = parse_application_secret(&TEST_APP_SECRET.to_string()).unwrap(); let mut c = hyper::Client::with_connector(::default()); let mut flow = RefreshFlow::new(&mut c); - - match *flow.refresh_token(FlowType::Device(GOOGLE_DEVICE_CODE_URL.to_string()), &appsecret, "bogus_refresh_token") { + match *flow.refresh_token( + FlowType::Device(GOOGLE_DEVICE_CODE_URL.to_string()), + &appsecret, + "bogus_refresh_token", + ) { RefreshResult::Success(ref t) => { assert_eq!(t.access_token, "1/fFAGRNJru1FTz70BzhT3Zg"); assert!(!t.expired()); diff --git a/src/service_account.rs b/src/service_account.rs index 2cfbade..104d7f9 100644 --- a/src/service_account.rs +++ b/src/service_account.rs @@ -27,18 +27,18 @@ use url::form_urlencoded; #[cfg(not(feature = "no-openssl"))] use openssl::{ - sign::Signer, hash::MessageDigest, pkey::{PKey, Private}, rsa::Padding, + sign::Signer, }; #[cfg(feature = "no-openssl")] use rustls::{ self, - PrivateKey, - sign::{self, SigningKey}, internal::pemfile, + sign::{self, SigningKey}, + PrivateKey, }; #[cfg(feature = "no-openssl")] use std::io; @@ -72,11 +72,16 @@ fn decode_rsa_key(pem_pkcs8: &str) -> Result> { if pk.len() > 0 { Ok(pk[0].clone()) } else { - Err(Box::new(io::Error::new(io::ErrorKind::InvalidInput, - "Not enough private keys in PEM"))) + Err(Box::new(io::Error::new( + io::ErrorKind::InvalidInput, + "Not enough private keys in PEM", + ))) } } else { - Err(Box::new(io::Error::new(io::ErrorKind::InvalidInput, "Error reading key from PEM"))) + Err(Box::new(io::Error::new( + io::ErrorKind::InvalidInput, + "Error reading key from PEM", + ))) } } @@ -87,7 +92,7 @@ fn decode_rsa_key(pem_pkcs8: &str) -> Result> { /// secret into a ServiceAccountKey. #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ServiceAccountKey { - #[serde(rename="type")] + #[serde(rename = "type")] pub key_type: Option, pub project_id: Option, pub private_key_id: Option, @@ -155,8 +160,12 @@ impl JWT { let key = decode_rsa_key(private_key)?; let signing_key = sign::RSASigningKey::new(&key) .map_err(|_| io::Error::new(io::ErrorKind::Other, "Couldn't initialize signer"))?; - let signer = signing_key.choose_scheme(&[rustls::SignatureScheme::RSA_PKCS1_SHA256]) - .ok_or(io::Error::new(io::ErrorKind::Other, "Couldn't choose signing scheme"))?; + let signer = signing_key + .choose_scheme(&[rustls::SignatureScheme::RSA_PKCS1_SHA256]) + .ok_or(io::Error::new( + io::ErrorKind::Other, + "Couldn't choose signing scheme", + ))?; let signature = signer.sign(jwt_head.as_bytes())?; let signature_b64 = encode_base64(signature); @@ -168,8 +177,9 @@ impl JWT { } fn init_claims_from_key<'a, I, T>(key: &ServiceAccountKey, scopes: I) -> Claims - where T: AsRef + 'a, - I: IntoIterator +where + T: AsRef + 'a, + I: IntoIterator, { let iat = chrono::Utc::now().timestamp(); let expiry = iat + 3600 - 5; // Max validity is 1h. @@ -230,7 +240,8 @@ impl TokenResponse { } impl<'a, C> ServiceAccountAccess - where C: BorrowMut +where + C: BorrowMut, { /// Returns a new `ServiceAccountAccess` token source. #[allow(dead_code)] @@ -255,20 +266,24 @@ impl<'a, C> ServiceAccountAccess fn request_token(&mut self, scopes: &Vec<&str>) -> result::Result> { let mut claims = init_claims_from_key(&self.key, scopes); claims.sub = self.sub.clone(); - let signed = JWT::new(claims) - .sign(self.key.private_key.as_ref().unwrap())?; + let signed = JWT::new(claims).sign(self.key.private_key.as_ref().unwrap())?; let body = form_urlencoded::Serializer::new(String::new()) - .extend_pairs(vec![("grant_type".to_string(), GRANT_TYPE.to_string()), - ("assertion".to_string(), signed)]) + .extend_pairs(vec![ + ("grant_type".to_string(), GRANT_TYPE.to_string()), + ("assertion".to_string(), signed), + ]) .finish(); let mut response = String::new(); - let mut result = self.client + let mut result = self + .client .borrow_mut() .post(self.key.token_uri.as_ref().unwrap()) .body(&body) - .header(header::ContentType("application/x-www-form-urlencoded".parse().unwrap())) + .header(header::ContentType( + "application/x-www-form-urlencoded".parse().unwrap(), + )) .send()?; result.read_to_string(&mut response)?; @@ -279,10 +294,14 @@ impl<'a, C> ServiceAccountAccess match token { Err(e) => return Err(Box::new(e)), Ok(token) => { - if token.access_token.is_none() || token.token_type.is_none() || - token.expires_in.is_none() { - Err(Box::new(StringError::new("Token response lacks fields".to_string(), - Some(&format!("{:?}", token))))) + if token.access_token.is_none() + || token.token_type.is_none() + || token.expires_in.is_none() + { + Err(Box::new(StringError::new( + "Token response lacks fields".to_string(), + Some(&format!("{:?}", token)), + ))) } else { Ok(token.to_oauth_token()) } @@ -293,8 +312,9 @@ impl<'a, C> ServiceAccountAccess impl> GetToken for ServiceAccountAccess { fn token<'b, I, T>(&mut self, scopes: I) -> result::Result> - where T: AsRef + Ord + 'b, - I: IntoIterator + where + T: AsRef + Ord + 'b, + I: IntoIterator, { let (hash, scps) = hash_scopes(scopes); @@ -318,11 +338,11 @@ impl> GetToken for ServiceAccountAccess { #[cfg(test)] mod tests { use super::*; + use crate::authenticator::GetToken; use crate::helper::service_account_key_from_file; use hyper; use hyper::net::HttpsConnector; use hyper_native_tls::NativeTlsClient; - use crate::authenticator::GetToken; // This is a valid but deactivated key. const TEST_PRIVATE_KEY_PATH: &'static str = "examples/Sanguine-69411a0c0eea.json"; @@ -332,10 +352,14 @@ mod tests { #[allow(dead_code)] fn test_service_account_e2e() { let key = service_account_key_from_file(&TEST_PRIVATE_KEY_PATH.to_string()).unwrap(); - let client = hyper::Client::with_connector(HttpsConnector::new(NativeTlsClient::new().unwrap())); + let client = + hyper::Client::with_connector(HttpsConnector::new(NativeTlsClient::new().unwrap())); let mut acc = ServiceAccountAccess::new(key, client); - println!("{:?}", - acc.token(vec![&"https://www.googleapis.com/auth/pubsub"]).unwrap()); + println!( + "{:?}", + acc.token(vec![&"https://www.googleapis.com/auth/pubsub"]) + .unwrap() + ); } #[test] @@ -344,11 +368,15 @@ mod tests { let scopes = vec!["scope1", "scope2", "scope3"]; let claims = super::init_claims_from_key(&key, &scopes); - assert_eq!(claims.iss, - "oauth2-public-test@sanguine-rhythm-105020.iam.gserviceaccount.com".to_string()); + assert_eq!( + claims.iss, + "oauth2-public-test@sanguine-rhythm-105020.iam.gserviceaccount.com".to_string() + ); assert_eq!(claims.scope, "scope1 scope2 scope3".to_string()); - assert_eq!(claims.aud, - "https://accounts.google.com/o/oauth2/token".to_string()); + assert_eq!( + claims.aud, + "https://accounts.google.com/o/oauth2/token".to_string() + ); assert!(claims.exp > 1000000000); assert!(claims.iat < claims.exp); assert_eq!(claims.exp - claims.iat, 3595); @@ -365,7 +393,9 @@ mod tests { assert!(signature.is_ok()); let signature = signature.unwrap(); - assert_eq!(signature.split(".").nth(0).unwrap(), - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"); + assert_eq!( + signature.split(".").nth(0).unwrap(), + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9" + ); } } diff --git a/src/storage.rs b/src/storage.rs index a94e832..43b203a 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -5,14 +5,14 @@ extern crate serde_json; -use std::collections::HashMap; use std::collections::hash_map::DefaultHasher; +use std::collections::HashMap; use std::error::Error; use std::fmt; use std::fs; use std::hash::{Hash, Hasher}; -use std::io::{Read, Write}; use std::io; +use std::io::{Read, Write}; use crate::types::Token; @@ -26,21 +26,24 @@ pub trait TokenStorage { /// If `token` is None, it is invalid or revoked and should be removed from storage. /// Otherwise, it should be saved. - fn set(&mut self, - scope_hash: u64, - scopes: &Vec<&str>, - token: Option) - -> Result<(), Self::Error>; + fn set( + &mut self, + scope_hash: u64, + scopes: &Vec<&str>, + token: Option, + ) -> Result<(), Self::Error>; /// A `None` result indicates that there is no token for the given scope_hash. fn get(&self, scope_hash: u64, scopes: &Vec<&str>) -> Result, Self::Error>; } /// Calculate a hash value describing the scopes, and return a sorted Vec of the scopes. pub fn hash_scopes<'a, I, T>(scopes: I) -> (u64, Vec<&'a str>) - where T: AsRef + Ord + 'a, - I: IntoIterator +where + T: AsRef + Ord + 'a, + I: IntoIterator, { - let mut sv: Vec<&str> = scopes.into_iter() + let mut sv: Vec<&str> = scopes + .into_iter() .map(|s| s.as_ref()) .collect::>(); sv.sort(); @@ -87,11 +90,12 @@ pub struct MemoryStorage { impl TokenStorage for MemoryStorage { type Error = NullError; - fn set(&mut self, - scope_hash: u64, - _: &Vec<&str>, - token: Option) - -> Result<(), NullError> { + fn set( + &mut self, + scope_hash: u64, + _: &Vec<&str>, + token: Option, + ) -> Result<(), NullError> { match token { Some(t) => self.tokens.insert(scope_hash, t), None => self.tokens.remove(&scope_hash), @@ -142,7 +146,7 @@ impl DiskTokenStorage { Result::Err(e) => { match e.kind() { io::ErrorKind::NotFound => Result::Ok(dts), // File not found; ignore and create new one - _ => Result::Err(e), // e.g. PermissionDenied + _ => Result::Err(e), // e.g. PermissionDenied } } } @@ -198,11 +202,12 @@ impl DiskTokenStorage { impl TokenStorage for DiskTokenStorage { type Error = io::Error; - fn set(&mut self, - scope_hash: u64, - _: &Vec<&str>, - token: Option) - -> Result<(), Self::Error> { + fn set( + &mut self, + scope_hash: u64, + _: &Vec<&str>, + token: Option, + ) -> Result<(), Self::Error> { match token { None => { self.tokens.remove(&scope_hash); diff --git a/src/types.rs b/src/types.rs index 62c05ae..ba29073 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,8 +1,8 @@ -use chrono::{DateTime, Utc, TimeZone}; +use chrono::{DateTime, TimeZone, Utc}; +use hyper; use std::error::Error; use std::fmt; use std::str::FromStr; -use hyper; /// A marker trait for all Flows pub trait Flow { @@ -34,10 +34,11 @@ impl From for RequestError { fn from(value: JsonError) -> RequestError { match &*value.error { "invalid_client" => RequestError::InvalidClient, - "invalid_scope" => { - RequestError::InvalidScope(value.error_description - .unwrap_or("no description provided".to_string())) - } + "invalid_scope" => RequestError::InvalidScope( + value + .error_description + .unwrap_or("no description provided".to_string()), + ), _ => RequestError::NegativeServerResponse(value.error, value.error_description), } } @@ -101,7 +102,6 @@ impl Error for StringError { } } - /// Represents all implemented token types #[derive(Clone, PartialEq, Debug)] pub enum TokenType { @@ -127,7 +127,6 @@ impl FromStr for TokenType { } } - /// A scheme for use in `hyper::header::Authorization` #[derive(Clone, PartialEq, Debug)] pub struct Scheme { @@ -155,12 +154,10 @@ impl FromStr for Scheme { return Err("Expected two parts: "); } match ::from_str(parts[0]) { - Ok(t) => { - Ok(Scheme { - token_type: t, - access_token: parts[1].to_string(), - }) - } + Ok(t) => Ok(Scheme { + token_type: t, + access_token: parts[1].to_string(), + }), Err(_) => Err("Couldn't parse token type"), } } @@ -208,9 +205,11 @@ impl Token { /// Returns a DateTime object representing our expiry date. pub fn expiry_date(&self) -> DateTime { - Utc.timestamp(self.expires_in_timestamp - .expect("Tokens without an absolute expiry are invalid"), - 0) + Utc.timestamp( + self.expires_in_timestamp + .expect("Tokens without an absolute expiry are invalid"), + 0, + ) } /// Adjust our stored expiry format to be absolute, using the current time. @@ -307,8 +306,10 @@ pub mod tests { }; let mut headers = hyper::header::Headers::new(); headers.set(hyper::header::Authorization(s)); - assert_eq!(headers.to_string(), - "Authorization: Bearer foo\r\n".to_string()); + assert_eq!( + headers.to_string(), + "Authorization: Bearer foo\r\n".to_string() + ); } #[test]