test(auth): removed DeviceFlowHelper

It's superseded by the more powerful `Authenticator`.
Adjusted library documentation accordingly, as well as `auth` program.
This commit is contained in:
Sebastian Thiel
2015-02-28 12:03:30 +01:00
parent 374804331a
commit dea3e6b3fd
4 changed files with 64 additions and 176 deletions

View File

@@ -9,6 +9,7 @@ extern crate open;
use chrono::{Local};
use getopts::{HasArg,Options,Occur,Fail};
use std::env;
use std::default::Default;
use std::time::Duration;
use std::old_io::timer::sleep;
@@ -46,13 +47,21 @@ fn main() {
return
}
let client_id = m.opt_str("c").unwrap();
let client_secret = m.opt_str("s").unwrap();
let secret = oauth2::ApplicationSecret {
client_id: m.opt_str("c").unwrap(),
client_secret: m.opt_str("s").unwrap(),
token_uri: Default::default(),
auth_uri: Default::default(),
redirect_uris: Default::default(),
client_email: None,
auth_provider_x509_cert_url: None,
client_x509_cert_url: None
};
println!("THIS PROGRAM PRINTS ALL COMMUNICATION TO STDERR !!!");
struct StdoutHandler;
impl oauth2::DeviceFlowHelperDelegate for 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\
given scopes. This is not a test !\n\
@@ -70,8 +79,10 @@ fn main() {
let client = hyper::Client::with_connector(mock::TeeConnector {
connector: hyper::net::HttpConnector(None)
});
if let Some(t) = oauth2::DeviceFlowHelper::new(&mut StdoutHandler)
.retrieve_token(client, &client_id, &client_secret, &m.free) {
if let Some(t) = oauth2::Authenticator::new(&secret, StdoutHandler, client,
oauth2::NullStorage, None)
.token(&m.free) {
println!("Authentication granted !");
println!("You should store the following information for use, or revoke it.");
println!("All dates are given in UTC.");

View File

@@ -1,8 +1,6 @@
use std::iter::IntoIterator;
use std::time::Duration;
use std::default::Default;
use std::cmp::min;
use std::old_io::timer;
use hyper;
use hyper::header::ContentType;
@@ -304,142 +302,6 @@ impl<C, NC> DeviceFlow<C, NC>
}
}
/// A utility type to help executing the `DeviceFlow` correctly.
///
/// This involves polling the authentication server in the given intervals
/// until there is a definitive result.
///
/// These results will be passed the `DeviceFlowHelperDelegate` implementation to deal with
/// * presenting the user code
/// * inform the user about the progress or errors
/// * abort the operation
///
pub struct DeviceFlowHelper<'a> {
delegate: &'a mut (DeviceFlowHelperDelegate + 'a),
}
impl<'a> DeviceFlowHelper<'a> {
/// Initialize a new instance with the given delegate
pub fn new(delegate: &'a mut DeviceFlowHelperDelegate) -> DeviceFlowHelper<'a> {
DeviceFlowHelper {
delegate: delegate,
}
}
/// Blocks until a token was retrieved from the server, or the delegate
/// decided to abort the attempt, or the user decided not to authorize
/// the application.
pub fn retrieve_token<'b, C, NC, T, I>(&mut self,
client: C,
client_id: &str, client_secret: &str, scopes: I)
-> Option<Token>
where T: Str,
I: IntoIterator<Item=&'b T> + Clone,
NC: hyper::net::NetworkConnector,
C: BorrowMut<hyper::Client<NC>> {
let mut flow = DeviceFlow::new(client);
// PHASE 1: REQUEST CODE
loop {
let res = flow.request_code(client_id, client_secret, scopes.clone());
match res {
RequestResult::Error(err) => {
match self.delegate.connection_error(err) {
Retry::Abort => return None,
Retry::After(d) => timer::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) => timer::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) => timer::sleep(min(d, pi.interval)),
}
},
PollResult::AccessGranted(token) => {
return Some(token)
},
}
}
}
}
/// A utility type to indicate how operations DeviceFlowHelper operations should be retried
pub enum Retry {
/// Signal you don't want to retry
Abort,
/// Signals you want to retry after the given duration
After(Duration)
}
/// A partially implemented trait to interact with the `DeviceFlowHelper`
///
/// The only method that needs to be implemented manually is `present_user_code(...)`,
/// as no assumptions are made on how this presentation should happen.
pub trait DeviceFlowHelperDelegate {
/// Called whenever there is an HttpError, usually if there are network problems.
///
/// Return retry information.
fn connection_error(&mut self, hyper::HttpError) -> Retry {
Retry::Abort
}
/// The server denied the attempt to obtain a request code
fn request_failure(&mut self, RequestResult) {}
/// The server has returned a `user_code` which must be shown to the user,
/// along with the `verification_url`.
/// Will be called exactly once, provided we didn't abort during `request_code` phase.
fn present_user_code(&mut self, PollInformation);
/// Called if the request code is expired. You will have to start over in this case.
/// This will be the last call the delegate receives.
fn expired(&mut self, DateTime<UTC>) {}
/// Called if the user denied access. You would have to start over.
/// This will be the last call the delegate receives.
fn denied(&mut self) {}
/// Called as long as we are waiting for the user to authorize us.
/// Can be used to print progress information, or decide to time-out.
///
/// If the returned `Retry` variant is a duration, it will only be used if it
/// is larger than the interval desired by the server.
fn pending(&mut self, &PollInformation) -> Retry {
Retry::After(Duration::seconds(5))
}
}
#[cfg(test)]
pub mod tests {

View File

@@ -1,5 +1,5 @@
use std::iter::IntoIterator;
use std::borrow::{Borrow, BorrowMut};
use std::borrow::BorrowMut;
use std::marker::PhantomData;
use std::collections::HashMap;
use std::hash::{SipHasher, Hash, Hasher};
@@ -9,7 +9,7 @@ use std::cmp::min;
use common::{Token, FlowType, ApplicationSecret};
use device::{PollInformation, RequestResult, DeviceFlow, PollResult};
use refresh::{RefreshResult, RefreshFlow};
use chrono::{DateTime, UTC, Duration};
use chrono::{DateTime, UTC, Duration, Local};
use hyper;
@@ -58,6 +58,18 @@ impl TokenStorage for MemoryStorage {
///
/// It is the go-to helper to deal with any kind of supported authentication flow,
/// which will be kept valid and usable.
///
/// # Device Flow
/// This involves polling the authentication server in the given intervals
/// until there is a definitive result.
///
/// These results will be passed the `DeviceFlowHelperDelegate` implementation to deal with
/// * presenting the user code
/// * inform the user about the progress or errors
/// * abort the operation
///
/// # Usage
/// Please have a look at the library's landing page.
pub struct Authenticator<D, S, C, NC> {
flow_type: FlowType,
delegate: D,
@@ -89,7 +101,7 @@ impl<D, S, C, NC> Authenticator<D, S, C, NC>
/// 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<FlowType>)
delegate: D, client: C, storage: S, flow_type: Option<FlowType>)
-> Authenticator<D, S, C, NC> {
Authenticator {
flow_type: flow_type.unwrap_or(FlowType::Device),
@@ -108,7 +120,7 @@ impl<D, S, C, NC> Authenticator<D, S, C, NC>
pub fn token<'b, I, T>(&mut self, scopes: I) -> Option<Token>
where T: Str + Ord,
I: IntoIterator<Item=&'b T> {
let (scope_key, scope, scopes) = {
let (scope_key, scopes) = {
let mut sv: Vec<&str> = scopes.into_iter()
.map(|s|s.as_slice())
.collect::<Vec<&str>>();
@@ -118,7 +130,7 @@ impl<D, S, C, NC> Authenticator<D, S, C, NC>
let mut sh = SipHasher::new();
s.hash(&mut sh);
let sv = sv;
(sh.finish(), s, sv)
(sh.finish(), sv)
};
// Get cached token. Yes, let's do an explicit return
@@ -263,9 +275,19 @@ pub trait AuthenticatorDelegate {
/// # Notes
/// * 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, PollInformation);
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!("Do not close this application until you either denied or granted access.");
println!("You have time until {}.", pi.expires_at.with_timezone(&Local));
}
}
/// Uses all default implementations by AuthenticatorDelegate, and makes the trait's
/// implementation usable in the first place.
pub struct DefaultAuthenticatorDelegate;
impl AuthenticatorDelegate for DefaultAuthenticatorDelegate {}
/// A utility type to indicate how operations DeviceFlowHelper operations should be retried
pub enum Retry {
/// Signal you don't want to retry
@@ -280,8 +302,7 @@ mod tests {
use super::*;
use super::super::device::tests::MockGoogleAuth;
use super::super::common::tests::SECRET;
use super::super::common::{ConsoleApplicationSecret, Token};
use super::super::device::PollInformation;
use super::super::common::{ConsoleApplicationSecret};
use std::default::Default;
use hyper;
@@ -289,14 +310,8 @@ mod tests {
fn flow() {
use rustc_serialize::json;
struct TestHandler;
impl AuthenticatorDelegate for TestHandler {
fn present_user_code(&mut self, pi: PollInformation) {
println!("{:?}", pi);
}
}
let secret = json::decode::<ConsoleApplicationSecret>(SECRET).unwrap().installed.unwrap();
let res = Authenticator::new(&secret, TestHandler,
let res = Authenticator::new(&secret, DefaultAuthenticatorDelegate,
hyper::Client::with_connector(<MockGoogleAuth as Default>::default()),
<MemoryStorage as Default>::default(), None)
.token(&["https://www.googleapis.com/auth/youtube.upload"]);

View File

@@ -16,27 +16,26 @@
//! ```test_harness,no_run
//! extern crate hyper;
//! extern crate "yup-oauth2" as oauth2;
//! use oauth2::{DeviceFlowHelper, DeviceFlowHelperDelegate, PollInformation};
//! extern crate "rustc-serialize" as rustc_serialize;
//!
//! use oauth2::{Authenticator, DefaultAuthenticatorDelegate, PollInformation, ConsoleApplicationSecret, MemoryStorage};
//! 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\"}}";
//!
//! # #[test] fn device() {
//! struct PrintHandler;
//! impl DeviceFlowHelperDelegate for PrintHandler {
//! 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!("Do not close this application until you either denied or granted access");
//! }
//! }
//! if let Some(t) = DeviceFlowHelper::new(&mut PrintHandler)
//! .retrieve_token(hyper::Client::new(),
//! "your_client_id",
//! "your_secret",
//! &["https://www.googleapis.com/auth/youtube.upload"]) {
//! let secret = json::decode::<ConsoleApplicationSecret>(SECRET).unwrap().installed.unwrap();
//! let res = Authenticator::new(&secret, DefaultAuthenticatorDelegate,
//! hyper::Client::new(),
//! <MemoryStorage as Default>::default(), None)
//! .token(&["https://www.googleapis.com/auth/youtube.upload"]);
//! match res {
//! Some(t) => {
//! // now you can use t.access_token to authenticate API calls within your
//! // given scopes. It will not be valid forever, which is when you have to
//! // refresh it using the `RefreshFlow`
//! } else {
//! println!("user declined");
//! },
//! None => println!("user declined"),
//! }
//! # }
//! ```
@@ -80,7 +79,8 @@ mod refresh;
mod common;
mod helper;
pub use device::{DeviceFlow, PollInformation, PollResult, DeviceFlowHelper,
DeviceFlowHelperDelegate, Retry};
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};