drive_adv/
auth.rs

1extern crate jsonwebtoken as jwt;
2
3use chrono::prelude::*;
4use jwt::{encode, Algorithm, Header};
5use openssl::rsa;
6use reqwest;
7use reqwest::header;
8use serde::{self, Deserialize, Serialize};
9use serde_json::value::Value;
10use std::collections::HashMap;
11use std::env;
12use std::fs;
13use log::{info, debug};
14
15const TOKEN_LIFETIME: i64 = 3600; // 1 hour. Max 1 hour
16
17// See https://developers.google.com/identity/protocols/OAuth2ServiceAccount
18//
19#[derive(Serialize, Deserialize)]
20struct Claims {
21    iss: String,
22    scope: String,
23    aud: String,
24    exp: i64,
25    iat: i64,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    sub: Option<String>,
28}
29
30#[derive(Serialize, Deserialize)]
31struct SublessClaims {
32    iss: String,
33    scope: String,
34    aud: String,
35    exp: i64,
36    iat: i64,
37}
38
39#[derive(Debug, Serialize, Deserialize)]
40struct ServiceAccount {
41    #[serde(rename = "type")]
42    auth_type: String,
43    project_id: String,
44    private_key_id: String,
45    private_key: String,
46    client_email: String,
47    client_id: String,
48    auth_uri: String,
49    token_uri: String,
50    auth_provider_x509_cert_url: String,
51    client_x509_cert_url: String,
52}
53
54// TODO break up
55fn generate_jwt_string(user: Option<&str>, scope: &str) -> String {
56    let service_account_file = env::var("DRIVE_ADV_SERVICE_ACCOUNT").expect(
57        "DRIVE_ADV_SERVICE_ACCOUNT env variable not set. Please reference path to service account",
58    );
59    let service_account_file =
60        fs::File::open(service_account_file).expect("Service account file does not exist at path");
61    let service_account: ServiceAccount = serde_json::from_reader(service_account_file).unwrap();
62    let now = Utc::now().timestamp();
63    let mut scope_url: String = "https://www.googleapis.com/auth/".to_string();
64    scope_url.push_str(scope);
65    let sub_user = match user {
66        Some(u) => Some(u.to_string()),
67        None => None,
68    };
69    let claims = Claims {
70        iss: service_account.client_email,
71        scope: scope_url,
72        iat: now,
73        exp: now + TOKEN_LIFETIME, // Expires 1 hour later
74        sub: sub_user,
75        aud: "https://oauth2.googleapis.com/token".to_string(),
76    };
77    let private_key =
78        rsa::Rsa::private_key_from_pem(service_account.private_key.as_bytes()).unwrap();
79    let mut header = Header::default();
80    header.alg = Algorithm::RS256;
81    let der = &private_key.private_key_to_der().unwrap();
82    let token = encode(&header, &claims, der).unwrap();
83    token
84}
85
86#[derive(Default, Debug)]
87pub struct AuthToken {
88    access_token: String, // TODO: change to &str
89    expiration: i64,
90    initialized: bool,
91}
92
93#[derive(Debug, Serialize, Deserialize)]
94pub struct OfflineToken {
95    access_token: String,
96    client_secret: String,
97    client_id: String,
98    refresh_token: String,
99}
100
101impl AuthToken {
102    pub fn get_token_string(&mut self, user: Option<&str>) -> String {
103        let now = Utc::now().timestamp();
104        // Does not match the jwt token expiration
105        if !self.initialized || now > self.expiration {
106            if let Some(path) = env::var("DRIVE_ADV_OFFLINE_OAUTH").ok() {
107                self.access_token = get_authentication_token_offline(&path);
108            }
109            else {
110                self.access_token = get_authentication_token(user);
111            }
112            self.expiration = now + TOKEN_LIFETIME / 2;
113            self.initialized = true;
114        } else {
115            debug!("Authentication token already exists")
116        }
117        self.access_token.clone() // TODO figure out lfietimes
118    }
119}
120
121pub fn get_authentication_token_offline(path: &str) -> String {
122    let offline_token_file =
123        fs::File::open(path).expect("Service account file does not exist at path");
124    let offline_token: OfflineToken = serde_json::from_reader(offline_token_file).unwrap();
125    let access_token_url = "https://oauth2.googleapis.com/token";
126    let client = reqwest::blocking::Client::new();
127    let body = format!("client_id={}&client_secret={}&refresh_token={}&grant_type=refresh_token", offline_token.client_id, offline_token.client_secret, offline_token.refresh_token);
128
129    let res = client
130        .post(access_token_url)
131        .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
132        .body(body)
133        .send()
134        .unwrap();
135    let result_json: Value = res.json().unwrap();
136    return result_json.get("access_token").unwrap().to_string();
137}
138
139/// Either uses a service account or oauth offline refresh token
140pub fn get_authentication_token(user: Option<&str>) -> String {
141    debug!("Getting authentication token");
142    let scope = env::var("DRIVE_SCOPE").expect("DRIVE_SCOPE not set. Should be drive or drive.readonly");
143    let access_token_url = "https://oauth2.googleapis.com/token";
144    let jwt_string = generate_jwt_string(user, &scope);
145    let client = reqwest::blocking::Client::new();
146    let body = format!(
147        "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion={}",
148        jwt_string
149    ); // No real reason I'm using this over json except it was in the tutorial
150    let res = client
151        .post(access_token_url)
152        .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
153        .body(body)
154        .send()
155        .unwrap();
156    let result_json: Value = res.json().unwrap();
157    return result_json.get("access_token").unwrap().to_string()
158}