diff --git a/examples/auth.rs b/examples/auth.rs index 9ed9e37db..b1f7cb671 100644 --- a/examples/auth.rs +++ b/examples/auth.rs @@ -1,3 +1,4 @@ +#![feature(env, collections)] extern crate "yup-oauth2" as oauth2; extern crate "yup-hyper-mock" as mock; extern crate hyper; @@ -6,14 +7,12 @@ extern crate getopts; use chrono::{Local}; use getopts::{HasArg,Options,Occur,Fail}; -use std::os; -use std::old_io::{File, FileMode, FileAccess}; -use std::old_path::Path; +use std::env; fn usage(program: &str, opts: &Options, err: Option) { if err.is_some() { println!("{}", err.unwrap()); - os::set_exit_status(1); + env::set_exit_status(1); } println!("{}", opts.short_usage(program) + " SCOPE [SCOPE ...]"); println!("{}", opts.usage("A program to authenticate against oauthv2 services.\n\ @@ -22,7 +21,7 @@ fn usage(program: &str, opts: &Options, err: Option) { } fn main() { - let args = os::args(); + let args: Vec = env::args().collect(); let prog = args[0].clone(); let mut opts = Options::new(); @@ -70,6 +69,6 @@ fn main() { println!("{:?}", t); } else { println!("Invalid client id, invalid scope, user denied access or request expired"); - os::set_exit_status(10); + env::set_exit_status(10); } } \ No newline at end of file diff --git a/src/common.rs b/src/common.rs index 54f624827..4316e8e3e 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,20 +1,70 @@ -use chrono::{DateTime, UTC}; +use chrono::{DateTime, UTC, TimeZone}; /// Represents a token as returned by OAuth2 servers. /// /// It is produced by all authentication flows. /// It authenticates certain operations, and must be refreshed once -/// it reached it's expirey date. -#[derive(Clone, PartialEq, Debug)] +/// it reached it's expiry date. +/// +/// The type is tuned to be suitable for direct de-serialization from server +/// replies, as well as for serialization for later reuse. This is the reason +/// for the two fields dealing with expiry - once in relative in and once in +/// absolute terms. +/// +/// Utility methods make common queries easier, see `invalid()` or `expired()`. +#[derive(Clone, PartialEq, Debug, RustcDecodable, RustcEncodable)] pub struct Token { - /// used when authenticating calls to oauth2 enabled services + /// used when authenticating calls to oauth2 enabled services. pub access_token: String, - /// used to refresh an expired access_token + /// used to refresh an expired access_token. pub refresh_token: String, - /// The token type as string - usually 'Bearer' + /// The token type as string - usually 'Bearer'. pub token_type: String, - /// access_token is not valid for use after this date - pub expires_at: DateTime + /// access_token will expire after this amount of time. + /// Prefer using expiry_date() + pub expires_in: Option, + + /// timestamp is seconds since epoch indicating when the token will expire in absolute terms. + /// use expiry_date() to convert to DateTime. + pub expires_in_timestamp: Option, +} + +impl Token { + + /// Returns true if the access token is expired or unset. + pub fn invalid(&self) -> bool { + self.access_token.len() == 0 + || self.refresh_token.len() == 0 + || self.expired() + } + + /// Returns true if we are expired. + /// + /// # Panics + /// * if our access_token is unset + pub fn expired(&self) -> bool { + if self.access_token.len() == 0 || self.refresh_token.len() == 0 { + panic!("called expired() on unset token"); + } + self.expiry_date() <= UTC::now() + } + + /// Returns a DateTime object representing our expiry date. + pub fn expiry_date(&self) -> DateTime { + UTC.timestamp(self.expires_in_timestamp.unwrap(), 0) + } + + /// Adjust our stored expiry format to be absolute, using the current time. + pub fn set_expiry_absolute(&mut self) -> &mut Token { + if self.expires_in_timestamp.is_some() { + assert!(self.expires_in.is_none()); + return self + } + + self.expires_in_timestamp = Some(UTC::now().timestamp() + self.expires_in.unwrap()); + self.expires_in = None; + self + } } /// All known authentication types, for suitable constants @@ -74,7 +124,7 @@ mod tests { fn console_secret() { use rustc_serialize::json; match json::decode::(SECRET) { - Ok(s) => assert!(s.installed.is_some()), + Ok(s) => assert!(s.installed.is_some() && s.web.is_none()), Err(err) => panic!(err), } } diff --git a/src/device.rs b/src/device.rs index cf400f451..f5d47bf5a 100644 --- a/src/device.rs +++ b/src/device.rs @@ -261,14 +261,6 @@ impl DeviceFlow } }; - #[derive(RustcDecodable)] - struct JsonToken { - access_token: String, - refresh_token: String, - token_type: String, - expires_in: i64, - } - #[derive(RustcDecodable)] struct JsonError { error: String @@ -288,13 +280,9 @@ impl DeviceFlow } // yes, we expect that ! - let t: JsonToken = json::decode(&json_str).unwrap(); - self.state = PollResult::AccessGranted(Token { - access_token: t.access_token, - refresh_token: t.refresh_token, - token_type: t.token_type, - expires_at: UTC::now() + Duration::seconds(t.expires_in) - }); + let mut t: Token = json::decode(&json_str).unwrap(); + t.set_expiry_absolute(); + self.state = PollResult::AccessGranted(t); }, // In any other state, we will bail out and do nothing _ => {} diff --git a/src/refresh.rs b/src/refresh.rs index 90250fdf1..9d883ffcf 100644 --- a/src/refresh.rs +++ b/src/refresh.rs @@ -1,6 +1,5 @@ use common::AuthenticationType; -use std::time::Duration; use chrono::UTC; use hyper; use hyper::header::ContentType; @@ -106,7 +105,8 @@ impl RefreshFlow access_token: t.access_token, token_type: t.token_type, refresh_token: refresh_token.to_string(), - expires_at: UTC::now() + Duration::seconds(t.expires_in) + expires_in: None, + expires_in_timestamp: Some(UTC::now().timestamp() + t.expires_in), }); &self.result @@ -142,8 +142,10 @@ mod tests { match *flow.refresh_token(AuthenticationType::Device, "bogus", "secret", "bogus_refresh_token") { - RefreshResult::Success(ref t) => assert_eq!(t.access_token, - "1/fFAGRNJru1FTz70BzhT3Zg"), + RefreshResult::Success(ref t) => { + assert_eq!(t.access_token, "1/fFAGRNJru1FTz70BzhT3Zg"); + assert!(!t.expired() && !t.invalid()); + }, _ => unreachable!() } }