mirror of
https://github.com/OMGeeky/yup-oauth2.git
synced 2025-12-30 08:03:32 +01:00
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:
@@ -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.");
|
||||
|
||||
138
src/device.rs
138
src/device.rs
@@ -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 {
|
||||
|
||||
@@ -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"]);
|
||||
|
||||
36
src/lib.rs
36
src/lib.rs
@@ -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};
|
||||
|
||||
Reference in New Issue
Block a user