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
use models::{AuthResponse, Claims, ServiceAccountKey, ServiceCredentialsInput};
use reqwest::blocking::Client;
use std::{fs, time::{SystemTime, UNIX_EPOCH}};
use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
mod models;
fn create_jwt(key: &ServiceAccountKey, scopes: Vec<String>) -> String {
let iat = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs() as usize;
let exp = iat + 3600;
let scopes = {
let count = scopes.iter().count();
if count > 1 {
let mut scopes_string = scopes.iter().take(count - 1).map(|x| x.to_string() + ",").collect::<String>();
scopes_string.push_str(&scopes[count - 1]);
scopes_string
}
else {
let scope = scopes[0].clone();
drop(scopes);
scope
}
};
let claims = Claims {
iss: key.client_email.clone(),
scope: scopes,
aud: key.token_uri.clone(),
exp,
iat,
};
let encoding_key = EncodingKey::from_rsa_pem(key.private_key.as_bytes()).expect("Invalid RSA Key");
encode(&Header::new(Algorithm::RS256), &claims, &encoding_key).expect("JWT Encoding failed")
}
fn get_access_token(key: &ServiceAccountKey, scopes: Vec<String>) -> AuthResponse {
let jwt = create_jwt(key, scopes);
let params = format!("grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion={}",jwt);
let client = Client::new();
let res = client.post(format!("https://oauth2.googleapis.com/token"))
.header("content-type", "application/x-www-form-urlencoded")
.body(params)
.send()
.expect("Failed to get access token");
let content = res.text();
let token_response: AuthResponse = serde_json::from_str(
&content.expect("Failed to get text out of response.")
).expect("Failed to parse token response");
token_response
}
/// Authentication handler for storing json credentials and requesting new access_token then necessary.
///
/// ```rust
/// //Example if json credentials are stored at the same directory where the program is contained.
/// let mut dir = env::current_exe().unwrap();
/// dir.pop();
/// dir.push("some-name-431008-92e3a679a62f.json");
///
/// let json_string = json!({
/// "type": "service_account",
/// "project_id": "some-name-0000000",
/// "private_key_id": "somerandomuuid000000000",
/// "private_key": "-----BEGIN PRIVATE KEY-----\n SOME CERT DATA \n-----END PRIVATE KEY-----\n",
/// "client_email": "some-name@some-account-0000000.iam.gserviceaccount.com",
/// "client_id": "000000000000000",
/// "auth_uri": "https://accounts.google.com/o/oauth2/auth",
/// "token_uri": "https://oauth2.googleapis.com/token",
/// "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
/// "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/some-account.iam.gserviceaccount.com",
/// "universe_domain": "googleapis.com"
/// }).to_string();
///
/// //Create the handler.
/// let handler = AuthenticationHandler::new(dir.into());
///
/// //Handler using json `String`
/// let handler_2 = AuthenticationHandler::new(json_string.into());
///
/// //Get a token with scoped read / write access to GCP DNS API.
/// let token = handler.get_access_token_model(
/// vec!["https://www.googleapis.com/auth/ndev.clouddns.readwrite".into()]);
///
/// println!("Access Token: {}", token.access_token);
/// ```
pub struct AuthenticationHandler {
service_credentials: ServiceAccountKey
}
impl AuthenticationHandler {
/// Creates new `AuthenticationHandler`. Requires a `PathBuf` or json `String` containing the service account credentials (key).
pub fn new(creds: ServiceCredentialsInput) -> AuthenticationHandler {
match creds {
ServiceCredentialsInput::PathBuf(creds) => {
let key_data = fs::read_to_string(creds)
.expect("Failed to read service account key file");
let service_account_key: ServiceAccountKey = serde_json::from_str(&key_data)
.expect("Failed to parse service account key");
AuthenticationHandler {
service_credentials: service_account_key
}
},
ServiceCredentialsInput::String(creds) => {
let service_account_key: ServiceAccountKey = serde_json::from_str(&creds)
.expect("Failed to parse service account key");
AuthenticationHandler {
service_credentials: service_account_key
}
}
}
}
/// Creates new `access_token` with specific access. Please for complete scopes list refer to: `https://developers.google.com/identity/protocols/oauth2/scopes`. Make sure to give the respective access /role to the service account.
pub fn get_access_token_model(&self, scopes: Vec<String>) -> AuthResponse {
let token = get_access_token(&self.service_credentials, scopes);
token
}
}