diff --git a/Cargo.toml b/Cargo.toml index 5f33d5803..7c9821e0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "yup-oauth2" -version = "0.2.0" +version = "0.2.2" authors = ["Sebastian Thiel "] repository = "https://github.com/Byron/yup-oauth2" description = "A partial oauth2 implementation, providing the 'device' authorization flow" @@ -20,5 +20,5 @@ rustc-serialize = "*" [dev-dependencies] getopts = "*" -yup-hyper-mock = "*" +yup-hyper-mock = ">= 0.1.3" open = "*" diff --git a/examples/auth.rs b/examples/auth.rs index 50f808f55..84c712cc7 100644 --- a/examples/auth.rs +++ b/examples/auth.rs @@ -6,6 +6,7 @@ extern crate chrono; extern crate getopts; extern crate open; +use oauth2::GetToken; use chrono::{Local}; use getopts::{HasArg,Options,Occur,Fail}; use std::env; diff --git a/src/common.rs b/src/common.rs index 7876bd9a6..39e6db80d 100644 --- a/src/common.rs +++ b/src/common.rs @@ -83,7 +83,7 @@ impl Str for FlowType { /// Represents either 'installed' or 'web' applications in a json secrets file. /// See `ConsoleApplicationSecret` for more information -#[derive(RustcDecodable, RustcEncodable, Clone)] +#[derive(RustcDecodable, RustcEncodable, Clone, Default)] pub struct ApplicationSecret { /// The client ID. pub client_id: String, @@ -106,7 +106,7 @@ pub struct ApplicationSecret { /// A type to facilitate reading and writing the json secret file /// as returned by the [google developer console](https://code.google.com/apis/console) -#[derive(RustcDecodable, RustcEncodable)] +#[derive(RustcDecodable, RustcEncodable, Default)] pub struct ConsoleApplicationSecret { pub web: Option, pub installed: Option diff --git a/src/device.rs b/src/device.rs index 6a42bd299..6ea6fc0a0 100644 --- a/src/device.rs +++ b/src/device.rs @@ -10,6 +10,7 @@ use rustc_serialize::json; use chrono::{DateTime,UTC}; use std::borrow::BorrowMut; use std::marker::PhantomData; +use std::io::Read; use common::{Token, FlowType, Flow}; @@ -192,7 +193,8 @@ impl DeviceFlow // json::Json::from_reader(&mut res) // .ok() // .expect("decode must work!"))).unwrap(); - let json_str = String::from_utf8(res.read_to_end().unwrap()).unwrap(); + let mut json_str = String::new(); + res.read_to_string(&mut json_str).ok().expect("string decode must work"); // check for error match json::decode::(&json_str) { @@ -267,7 +269,9 @@ impl DeviceFlow return PollResult::Error(err); } Ok(mut res) => { - String::from_utf8(res.read_to_end().unwrap()).unwrap() + let mut json_str = String::new(); + res.read_to_string(&mut json_str).ok().expect("string decode must work"); + json_str } }; diff --git a/src/helper.rs b/src/helper.rs index 8df69bac3..6327da915 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -80,6 +80,13 @@ pub struct Authenticator { _m: PhantomData } +/// A provider for authorization tokens, yielding tokens valid for a given scope. +pub trait GetToken { + fn token<'b, I, T>(&mut self, scopes: I) -> Option + where T: Str + Ord, + I: IntoIterator; +} + impl Authenticator where D: AuthenticatorDelegate, S: TokenStorage, @@ -113,11 +120,74 @@ impl Authenticator } } + fn retrieve_device_token(&mut self, scopes: &Vec<&str>) -> Option { + let mut flow = DeviceFlow::new(self.client.borrow_mut()); + + // PHASE 1: REQUEST CODE + loop { + let res = flow.request_code(&self.secret.client_id, + &self.secret.client_secret, scopes.iter()); + match res { + RequestResult::Error(err) => { + match self.delegate.connection_error(err) { + Retry::Abort => return None, + Retry::After(d) => sleep(d), + } + }, + RequestResult::InvalidClient + |RequestResult::InvalidScope(_) => { + self.delegate.request_failure(res); + return None + } + RequestResult::ProceedWithPolling(pi) => { + self.delegate.present_user_code(pi); + break + } + } + } + + // PHASE 1: POLL TOKEN + loop { + match flow.poll_token() { + PollResult::Error(err) => { + match self.delegate.connection_error(err) { + Retry::Abort => return None, + Retry::After(d) => sleep(d), + } + }, + PollResult::Expired(t) => { + self.delegate.expired(t); + return None + }, + PollResult::AccessDenied => { + self.delegate.denied(); + return None + }, + PollResult::AuthorizationPending(pi) => { + match self.delegate.pending(&pi) { + Retry::Abort => return None, + Retry::After(d) => sleep(min(d, pi.interval)), + } + }, + PollResult::AccessGranted(token) => { + return Some(token) + }, + } + } + } +} + +impl GetToken for Authenticator + where D: AuthenticatorDelegate, + S: TokenStorage, + NC: hyper::net::NetworkConnector, + 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. /// In any failure case, the returned token will be None, otherwise it is guaranteed to be /// valid for the given scopes. - pub fn token<'b, I, T>(&mut self, scopes: I) -> Option + fn token<'b, I, T>(&mut self, scopes: I) -> Option where T: Str + Ord, I: IntoIterator { let (scope_key, scopes) = { @@ -175,62 +245,6 @@ impl Authenticator }, } } - - fn retrieve_device_token(&mut self, scopes: &Vec<&str>) -> Option { - let mut flow = DeviceFlow::new(self.client.borrow_mut()); - - // PHASE 1: REQUEST CODE - loop { - let res = flow.request_code(&self.secret.client_id, - &self.secret.client_secret, scopes.iter()); - match res { - RequestResult::Error(err) => { - match self.delegate.connection_error(err) { - Retry::Abort => return None, - Retry::After(d) => sleep(d), - } - }, - RequestResult::InvalidClient - |RequestResult::InvalidScope(_) => { - self.delegate.request_failure(res); - return None - } - RequestResult::ProceedWithPolling(pi) => { - self.delegate.present_user_code(pi); - break - } - } - } - - // PHASE 1: POLL TOKEN - loop { - match flow.poll_token() { - PollResult::Error(err) => { - match self.delegate.connection_error(err) { - Retry::Abort => return None, - Retry::After(d) => sleep(d), - } - }, - PollResult::Expired(t) => { - self.delegate.expired(t); - return None - }, - PollResult::AccessDenied => { - self.delegate.denied(); - return None - }, - PollResult::AuthorizationPending(pi) => { - match self.delegate.pending(&pi) { - Retry::Abort => return None, - Retry::After(d) => sleep(min(d, pi.interval)), - } - }, - PollResult::AccessGranted(token) => { - return Some(token) - }, - } - } - } } diff --git a/src/lib.rs b/src/lib.rs index bdc715eac..645e65f01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(old_io, std_misc, core, hash)] +#![feature(io, old_io, std_misc, core, hash)] //! This library can be used to acquire oauth2.0 authentication for services. //! At the time of writing, only one way of doing so is implemented, the [device flow](https://developers.google.com/youtube/v3/guides/authentication#devices), along with a flow //! for [refreshing tokens](https://developers.google.com/youtube/v3/guides/authentication#devices) @@ -18,7 +18,7 @@ //! extern crate "yup-oauth2" as oauth2; //! extern crate "rustc-serialize" as rustc_serialize; //! -//! use oauth2::{Authenticator, DefaultAuthenticatorDelegate, PollInformation, ConsoleApplicationSecret, MemoryStorage}; +//! use oauth2::{Authenticator, DefaultAuthenticatorDelegate, PollInformation, ConsoleApplicationSecret, MemoryStorage, GetToken}; //! use rustc_serialize::json; //! use std::default::Default; //! # const SECRET: &'static str = "{\"installed\":{\"auth_uri\":\"https://accounts.google.com/o/oauth2/auth\",\"client_secret\":\"UqkDJd5RFwnHoiG5x5Rub8SI\",\"token_uri\":\"https://accounts.google.com/o/oauth2/token\",\"client_email\":\"\",\"redirect_uris\":[\"urn:ietf:wg:oauth:2.0:oob\",\"oob\"],\"client_x509_cert_url\":\"\",\"client_id\":\"14070749909-vgip2f1okm7bkvajhi9jugan6126io9v.apps.googleusercontent.com\",\"auth_provider_x509_cert_url\":\"https://www.googleapis.com/oauth2/v1/certs\"}}"; @@ -83,4 +83,4 @@ pub use device::{DeviceFlow, PollInformation, PollResult}; pub use refresh::{RefreshFlow, RefreshResult}; pub use common::{Token, FlowType, ApplicationSecret, ConsoleApplicationSecret}; pub use helper::{TokenStorage, NullStorage, MemoryStorage, Authenticator, - AuthenticatorDelegate, Retry, DefaultAuthenticatorDelegate}; + AuthenticatorDelegate, Retry, DefaultAuthenticatorDelegate, GetToken}; diff --git a/src/refresh.rs b/src/refresh.rs index 4eef64cbe..5b400ca75 100644 --- a/src/refresh.rs +++ b/src/refresh.rs @@ -8,6 +8,7 @@ use url::form_urlencoded; use super::Token; use std::borrow::BorrowMut; use std::marker::PhantomData; +use std::io::Read; /// Implements the [Outh2 Refresh Token Flow](https://developers.google.com/youtube/v3/guides/authentication#devices). /// @@ -82,7 +83,9 @@ impl RefreshFlow return &self.result; } Ok(mut res) => { - String::from_utf8(res.read_to_end().unwrap()).unwrap() + let mut json_str = String::new(); + res.read_to_string(&mut json_str).ok().expect("string decode must work"); + json_str } };