#![allow(non_upper_case_globals)]
use std::ops::Add;
use std::sync::{Arc, RwLock};
use lazy_static::lazy_static;
use crate::{DEFAULT_TIMEOUT, GOOGLE_OAUTH_V3_USER_INFO_API, GOOGLE_SA_CERTS_URL, GoogleAccessTokenPayload, GooglePayload, utils};
use std::time::{Duration, Instant};
use log::debug;
use crate::certs::{Cert, Certs};
use crate::jwt_parser::JwtParser;
use crate::validate::id_token;
use crate::MyResult;
lazy_static! {
static ref cb: reqwest::blocking::Client = reqwest::blocking::Client::new();
}
#[derive(Debug, Clone)]
pub struct Client {
client_ids: Vec<String>,
timeout: Duration,
cached_certs: Arc<RwLock<Certs>>,
}
impl Client {
pub fn new<S: ToString>(client_id: S) -> Self {
let client_id = client_id.to_string();
Self::new_with_vec(&[client_id])
}
pub fn new_with_vec<T, V>(client_ids: T) -> Self
where
T: AsRef<[V]>,
V: AsRef<str>
{
Self {
client_ids: client_ids
.as_ref()
.iter()
.map(|c| c.as_ref().to_string())
.collect(),
timeout: Duration::from_secs(DEFAULT_TIMEOUT),
cached_certs: Arc::default(),
}
}
pub fn timeout(mut self, d: Duration) -> Self {
if !d.is_zero() {
self.timeout = d;
}
self
}
pub fn validate_id_token<S>(&self, token: S) -> MyResult<GooglePayload>
where S: AsRef<str>
{
let token = token.as_ref();
let parser: JwtParser<GooglePayload> = JwtParser::parse(token)?;
id_token::validate_info(&self.client_ids, &parser)?;
let cert = self.get_cert(parser.header.alg.as_str(), parser.header.kid.as_str())?;
id_token::do_validate(&cert, &parser)?;
Ok(parser.payload)
}
fn get_cert(&self, alg: &str, kid: &str) -> MyResult<Cert> {
{
let cached_certs = self.cached_certs.read().unwrap();
if !cached_certs.need_refresh() {
debug!("certs: use cache");
return cached_certs.find_cert(alg, kid);
}
}
debug!("certs: try to fetch new certs");
let mut cached_certs = self.cached_certs.write().unwrap();
let resp = cb.get(GOOGLE_SA_CERTS_URL)
.timeout(self.timeout)
.send()?;
let max_age = utils::parse_max_age_from_resp(&resp);
let info = resp.bytes()?;
*cached_certs = serde_json::from_slice(&info)?;
cached_certs.set_cache_until(
Instant::now().add(Duration::from_secs(max_age))
);
cached_certs.find_cert(alg, kid)
}
pub fn validate_access_token<S>(&self, token: S) -> MyResult<GoogleAccessTokenPayload>
where S: AsRef<str>
{
let token = token.as_ref();
let info = cb.get(format!("{}?access_token={}", GOOGLE_OAUTH_V3_USER_INFO_API, token))
.timeout(self.timeout)
.send()?
.bytes()?;
let payload = serde_json::from_slice(&info)?;
Ok(payload)
}
}