From fb0c3ff506a70431112c46f4c4d79f6a9559dd58 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 1 Mar 2015 09:54:15 +0100 Subject: [PATCH 1/3] feat(auth): Authenticator support GetToken trait This will allow its usage in generics, without having to provide the 4 additional template types it usees. --- Cargo.toml | 2 +- examples/auth.rs | 1 + src/helper.rs | 140 ++++++++++++++++++++++++++--------------------- src/lib.rs | 4 +- 4 files changed, 81 insertions(+), 66 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5f33d58..308fd5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "yup-oauth2" -version = "0.2.0" +version = "0.2.1" authors = ["Sebastian Thiel "] repository = "https://github.com/Byron/yup-oauth2" description = "A partial oauth2 implementation, providing the 'device' authorization flow" diff --git a/examples/auth.rs b/examples/auth.rs index 50f808f..84c712c 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/helper.rs b/src/helper.rs index 8df69ba..6327da9 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,69 +120,6 @@ impl Authenticator } } - /// 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 - where T: Str + Ord, - I: IntoIterator { - let (scope_key, scopes) = { - let mut sv: Vec<&str> = scopes.into_iter() - .map(|s|s.as_slice()) - .collect::>(); - sv.sort(); - let s = sv.connect(" "); - - let mut sh = SipHasher::new(); - s.hash(&mut sh); - let sv = sv; - (sh.finish(), sv) - }; - - // Get cached token. Yes, let's do an explicit return - return match self.storage.get(scope_key) { - Some(mut t) => { - // t needs refresh ? - if t.expired() { - let mut rf = RefreshFlow::new(self.client.borrow_mut()); - loop { - match *rf.refresh_token(self.flow_type, - &self.secret.client_id, - &self.secret.client_secret, - &t.refresh_token) { - RefreshResult::Error(ref err) => { - match self.delegate.connection_error(err.clone()) { - Retry::Abort => return None, - Retry::After(d) => sleep(d), - } - }, - RefreshResult::Refused(_) => { - self.delegate.denied(); - return None - }, - RefreshResult::Success(ref new_t) => { - t = new_t.clone(); - self.storage.set(scope_key, Some(t.clone())); - } - }// RefreshResult handling - }// refresh loop - }// handle expiration - Some(t) - } - None => { - // get new token. The respective sub-routine will do all the logic. - let ot = match self.flow_type { - FlowType::Device => self.retrieve_device_token(&scopes), - }; - // store it, no matter what. If tokens have become invalid, it's ok - // to indicate that to the storage. - self.storage.set(scope_key, ot.clone()); - ot - }, - } - } - fn retrieve_device_token(&mut self, scopes: &Vec<&str>) -> Option { let mut flow = DeviceFlow::new(self.client.borrow_mut()); @@ -233,6 +177,76 @@ impl Authenticator } } +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. + fn token<'b, I, T>(&mut self, scopes: I) -> Option + where T: Str + Ord, + I: IntoIterator { + let (scope_key, scopes) = { + let mut sv: Vec<&str> = scopes.into_iter() + .map(|s|s.as_slice()) + .collect::>(); + sv.sort(); + let s = sv.connect(" "); + + let mut sh = SipHasher::new(); + s.hash(&mut sh); + let sv = sv; + (sh.finish(), sv) + }; + + // Get cached token. Yes, let's do an explicit return + return match self.storage.get(scope_key) { + Some(mut t) => { + // t needs refresh ? + if t.expired() { + let mut rf = RefreshFlow::new(self.client.borrow_mut()); + loop { + match *rf.refresh_token(self.flow_type, + &self.secret.client_id, + &self.secret.client_secret, + &t.refresh_token) { + RefreshResult::Error(ref err) => { + match self.delegate.connection_error(err.clone()) { + Retry::Abort => return None, + Retry::After(d) => sleep(d), + } + }, + RefreshResult::Refused(_) => { + self.delegate.denied(); + return None + }, + RefreshResult::Success(ref new_t) => { + t = new_t.clone(); + self.storage.set(scope_key, Some(t.clone())); + } + }// RefreshResult handling + }// refresh loop + }// handle expiration + Some(t) + } + None => { + // get new token. The respective sub-routine will do all the logic. + let ot = match self.flow_type { + FlowType::Device => self.retrieve_device_token(&scopes), + }; + // store it, no matter what. If tokens have become invalid, it's ok + // to indicate that to the storage. + self.storage.set(scope_key, ot.clone()); + ot + }, + } + } +} + /// A partially implemented trait to interact with the `Authenticator` diff --git a/src/lib.rs b/src/lib.rs index bdc715e..92eb24f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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}; From 445675db7f3b34f01a794732a9f254889d1f16b6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 1 Mar 2015 10:14:19 +0100 Subject: [PATCH 2/3] fix(common): Default trait for ApplicationSecret --- src/common.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common.rs b/src/common.rs index 7876bd9..39e6db8 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 From 437a60959b15aae657ad9285fa5ab33580ccd221 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 7 Mar 2015 13:03:12 +0100 Subject: [PATCH 3/3] fix(rustup): switch to using io where possible And adapt to hyper in the same moment --- Cargo.toml | 4 ++-- src/device.rs | 8 ++++++-- src/lib.rs | 2 +- src/refresh.rs | 5 ++++- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 308fd5b..7c9821e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "yup-oauth2" -version = "0.2.1" +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/src/device.rs b/src/device.rs index 6a42bd2..6ea6fc0 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/lib.rs b/src/lib.rs index 92eb24f..645e65f 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) diff --git a/src/refresh.rs b/src/refresh.rs index 4eef64c..5b400ca 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 } };