Merge branch 'next'

This commit is contained in:
Sebastian Thiel
2015-03-07 13:36:28 +01:00
7 changed files with 95 additions and 73 deletions

View File

@@ -1,7 +1,7 @@
[package]
name = "yup-oauth2"
version = "0.2.0"
version = "0.2.2"
authors = ["Sebastian Thiel <byronimo@gmail.com>"]
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 = "*"

View File

@@ -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;

View File

@@ -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<ApplicationSecret>,
pub installed: Option<ApplicationSecret>

View File

@@ -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<C, NC> DeviceFlow<C, NC>
// 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::<JsonError>(&json_str) {
@@ -267,7 +269,9 @@ impl<C, NC> DeviceFlow<C, NC>
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
}
};

View File

@@ -80,6 +80,13 @@ pub struct Authenticator<D, S, C, NC> {
_m: PhantomData<NC>
}
/// 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<Token>
where T: Str + Ord,
I: IntoIterator<Item=&'b T>;
}
impl<D, S, C, NC> Authenticator<D, S, C, NC>
where D: AuthenticatorDelegate,
S: TokenStorage,
@@ -113,69 +120,6 @@ impl<D, S, C, NC> Authenticator<D, S, C, NC>
}
}
/// 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<Token>
where T: Str + Ord,
I: IntoIterator<Item=&'b T> {
let (scope_key, scopes) = {
let mut sv: Vec<&str> = scopes.into_iter()
.map(|s|s.as_slice())
.collect::<Vec<&str>>();
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<Token> {
let mut flow = DeviceFlow::new(self.client.borrow_mut());
@@ -233,6 +177,76 @@ impl<D, S, C, NC> Authenticator<D, S, C, NC>
}
}
impl<D, S, C, NC> GetToken for Authenticator<D, S, C, NC>
where D: AuthenticatorDelegate,
S: TokenStorage,
NC: hyper::net::NetworkConnector,
C: BorrowMut<hyper::Client<NC>> {
/// 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<Token>
where T: Str + Ord,
I: IntoIterator<Item=&'b T> {
let (scope_key, scopes) = {
let mut sv: Vec<&str> = scopes.into_iter()
.map(|s|s.as_slice())
.collect::<Vec<&str>>();
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`

View File

@@ -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};

View File

@@ -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<C, NC> RefreshFlow<C, NC>
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
}
};