mirror of
https://github.com/OMGeeky/yup-oauth2.git
synced 2026-01-08 03:57:01 +01:00
Merge branch 'next'
This commit is contained in:
@@ -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 = "*"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
140
src/helper.rs
140
src/helper.rs
@@ -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`
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user