1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
use chrono::{DateTime, Utc, TimeZone};
use std::error::Error;
use std::fmt;
use std::str::FromStr;
use hyper;

/// A marker trait for all Flows
pub trait Flow {
    fn type_id() -> FlowType;
}

#[derive(Deserialize)]
pub struct JsonError {
    pub error: String,
    pub error_description: Option<String>,
    pub error_uri: Option<String>,
}

/// Encapsulates all possible results of the `request_token(...)` operation
pub enum RequestError {
    /// Indicates connection failure
    HttpError(hyper::Error),
    /// The OAuth client was not found
    InvalidClient,
    /// Some requested scopes were invalid. String contains the scopes as part of
    /// the server error message
    InvalidScope(String),
    /// A 'catch-all' variant containing the server error and description
    /// First string is the error code, the second may be a more detailed description
    NegativeServerResponse(String, Option<String>),
}

impl From<JsonError> for RequestError {
    fn from(value: JsonError) -> RequestError {
        match &*value.error {
            "invalid_client" => RequestError::InvalidClient,
            "invalid_scope" => {
                RequestError::InvalidScope(value.error_description
                    .unwrap_or("no description provided".to_string()))
            }
            _ => RequestError::NegativeServerResponse(value.error, value.error_description),
        }
    }
}

impl fmt::Display for RequestError {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match *self {
            RequestError::HttpError(ref err) => err.fmt(f),
            RequestError::InvalidClient => "Invalid Client".fmt(f),
            RequestError::InvalidScope(ref scope) => writeln!(f, "Invalid Scope: '{}'", scope),
            RequestError::NegativeServerResponse(ref error, ref desc) => {
                error.fmt(f)?;
                if let &Some(ref desc) = desc {
                    write!(f, ": {}", desc)?;
                }
                "\n".fmt(f)
            }
        }
    }
}

#[derive(Debug)]
pub struct StringError {
    error: String,
}

impl fmt::Display for StringError {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        self.description().fmt(f)
    }
}

impl StringError {
    pub fn new(error: String, desc: Option<&String>) -> StringError {
        let mut error = error;
        if let Some(d) = desc {
            error.push_str(": ");
            error.push_str(&*d);
        }

        StringError { error: error }
    }
}

impl<'a> From<&'a Error> for StringError {
    fn from(err: &'a Error) -> StringError {
        StringError::new(err.description().to_string(), None)
    }
}

impl From<String> for StringError {
    fn from(value: String) -> StringError {
        StringError::new(value, None)
    }
}

impl Error for StringError {
    fn description(&self) -> &str {
        &self.error
    }
}


/// Represents all implemented token types
#[derive(Clone, PartialEq, Debug)]
pub enum TokenType {
    /// Means that whoever bears the access token will be granted access
    Bearer,
}

impl AsRef<str> for TokenType {
    fn as_ref(&self) -> &'static str {
        match *self {
            TokenType::Bearer => "Bearer",
        }
    }
}

impl FromStr for TokenType {
    type Err = ();
    fn from_str(s: &str) -> Result<TokenType, ()> {
        match s {
            "Bearer" => Ok(TokenType::Bearer),
            _ => Err(()),
        }
    }
}


/// A scheme for use in `hyper::header::Authorization`
#[derive(Clone, PartialEq, Debug)]
pub struct Scheme {
    /// The type of our access token
    pub token_type: TokenType,
    /// The token returned by one of the Authorization Flows
    pub access_token: String,
}

impl hyper::header::Scheme for Scheme {
    fn scheme() -> Option<&'static str> {
        None
    }

    fn fmt_scheme(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} {}", self.token_type.as_ref(), self.access_token)
    }
}

impl FromStr for Scheme {
    type Err = &'static str;
    fn from_str(s: &str) -> Result<Scheme, &'static str> {
        let parts: Vec<&str> = s.split(' ').collect();
        if parts.len() != 2 {
            return Err("Expected two parts: <token_type> <token>");
        }
        match <TokenType as FromStr>::from_str(parts[0]) {
            Ok(t) => {
                Ok(Scheme {
                    token_type: t,
                    access_token: parts[1].to_string(),
                })
            }
            Err(_) => Err("Couldn't parse token type"),
        }
    }
}

/// 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 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 `expired()`.
#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
pub struct Token {
    /// used when authenticating calls to oauth2 enabled services.
    pub access_token: String,
    /// used to refresh an expired access_token.
    pub refresh_token: String,
    /// The token type as string - usually 'Bearer'.
    pub token_type: String,
    /// access_token will expire after this amount of time.
    /// Prefer using expiry_date()
    pub expires_in: Option<i64>,
    /// 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<i64>,
}

impl Token {
    /// Returns true if we are expired.
    ///
    /// # Panics
    /// * if our access_token is unset
    pub fn expired(&self) -> bool {
        if self.access_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> {
        Utc.timestamp(self.expires_in_timestamp
                          .expect("Tokens without an absolute expiry are invalid"),
                      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
#[derive(Clone)]
pub enum FlowType {
    /// [device authentication](https://developers.google.com/youtube/v3/guides/authentication#devices). Only works
    /// for certain scopes.
    /// Contains the device token URL; for google, that is
    /// https://accounts.google.com/o/oauth2/device/code (exported as `GOOGLE_DEVICE_CODE_URL`)
    Device(String),
    /// [installed app flow](https://developers.google.com/identity/protocols/OAuth2InstalledApp). Required
    /// for Drive, Calendar, Gmail...; Requires user to paste a code from the browser.
    InstalledInteractive,
    /// Same as InstalledInteractive, but uses a redirect: The OAuth provider redirects the user's
    /// browser to a web server that is running on localhost. This may not work as well with the
    /// Windows Firewall, but is more comfortable otherwise. The integer describes which port to
    /// bind to (default: 8080)
    InstalledRedirect(u32),
}

/// Represents either 'installed' or 'web' applications in a json secrets file.
/// See `ConsoleApplicationSecret` for more information
#[derive(Deserialize, Serialize, Clone, Default)]
pub struct ApplicationSecret {
    /// The client ID.
    pub client_id: String,
    /// The client secret.
    pub client_secret: String,
    /// The token server endpoint URI.
    pub token_uri: String,
    /// The authorization server endpoint URI.
    pub auth_uri: String,
    pub redirect_uris: Vec<String>,

    /// Name of the google project the credentials are associated with
    pub project_id: Option<String>,
    /// The service account email associated with the client.
    pub client_email: Option<String>,
    /// The URL of the public x509 certificate, used to verify the signature on JWTs, such
    /// as ID tokens, signed by the authentication provider.
    pub auth_provider_x509_cert_url: Option<String>,
    ///  The URL of the public x509 certificate, used to verify JWTs signed by the client.
    pub client_x509_cert_url: Option<String>,
}

/// 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(Deserialize, Serialize, Default)]
pub struct ConsoleApplicationSecret {
    pub web: Option<ApplicationSecret>,
    pub installed: Option<ApplicationSecret>,
}

#[cfg(test)]
pub mod tests {
    use super::*;
    use hyper;

    pub 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 console_secret() {
        use serde_json as json;
        match json::from_str::<ConsoleApplicationSecret>(SECRET) {
            Ok(s) => assert!(s.installed.is_some() && s.web.is_none()),
            Err(err) => panic!(err),
        }
    }

    #[test]
    fn schema() {
        let s = Scheme {
            token_type: TokenType::Bearer,
            access_token: "foo".to_string(),
        };
        let mut headers = hyper::header::Headers::new();
        headers.set(hyper::header::Authorization(s));
        assert_eq!(headers.to_string(),
                   "Authorization: Bearer foo\r\n".to_string());
    }

    #[test]
    fn parse_schema() {
        let auth: hyper::header::Authorization<Scheme> =
            hyper::header::Header::parse_header(&[b"Bearer foo".to_vec()]).unwrap();
        assert_eq!(auth.0.token_type, TokenType::Bearer);
        assert_eq!(auth.0.access_token, "foo".to_string());
    }
}