use std::{fs, path::Path};
use serde::{Deserialize, Serialize};
use crate::{Error, ErrorKind};
use crate::AccessToken;
use crate::ClientSecrets;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Credentials {
pub client_secrets: ClientSecrets,
pub access_token: AccessToken,
}
impl Credentials {
pub fn new( client_secrets: &ClientSecrets, access_token: &AccessToken ) -> Self {
Self {
client_secrets: client_secrets.clone(),
access_token: access_token.clone(),
}
}
pub fn get_access_token( &self ) -> String {
self.access_token.access_token.to_string()
}
pub fn store<T: AsRef<Path>> ( &self, path: T ) -> crate::Result<()> {
let contents = serde_json::to_string(&self)?;
fs::write(&path, contents)?;
Ok(())
}
pub fn refresh( &mut self ) -> crate::Result<()> {
self.access_token.refresh( &self.client_secrets )
}
pub fn are_valid( &self ) -> bool {
self.access_token.is_valid()
}
pub fn from_file<T, U> ( file: T, scopes: &[U] ) -> crate::Result<Self>
where
T: AsRef<Path>,
U: AsRef<str>,
{
let contents = fs::read_to_string(&file)?;
let credentials: Credentials = serde_json::from_str(&contents)?;
if !credentials.access_token.has_scopes(scopes) {
return Err(
Error::new(ErrorKind::MismatchedScopes, "stored access token does not contain the requested scopes")
)
}
Ok(credentials)
}
#[cfg(not(tarpaulin_include))]
pub fn from_client_secrets_file<T, U> ( file: T, scopes: &[U] ) -> crate::Result<Self>
where
T: AsRef<Path>,
U: AsRef<str>,
{
let client_secrets = ClientSecrets::from_file(&file)?;
let access_token = AccessToken::request(&client_secrets, scopes)?;
Ok( Self::new(&client_secrets, &access_token) )
}
}
#[cfg(test)]
mod tests {
use serde_json;
use super::ErrorKind;
use super::Credentials;
use std::process::Command;
use testfile::{self, TestFile};
use crate::utils::test::{VALID_CREDENTIALS, VALID_SECRETS};
fn get_test_json() -> String {
String::from("{
\"client_secrets\": {
\"client_id\": \"test\",
\"project_id\": \"test\",
\"auth_uri\": \"test\",
\"token_uri\": \"test\",
\"auth_provider_x509_cert_url\": \"test\",
\"client_secret\": \"test\",
\"redirect_uris\": [\"test\"]
},
\"access_token\": {
\"access_token\": \"test\",
\"expires_in\": 0,
\"refresh_token\": \"test\",
\"scope\": \"test-scope other-test-scope unchecked-scope\",
\"token_type\": \"test\"
}
}")
}
fn get_test_scopes() -> Vec<String> {
vec![
String::from("test-scope"),
String::from("other-test-scope"),
]
}
fn get_test_credentials_file() -> TestFile {
testfile::from( &get_test_json() )
}
#[test]
fn new_test() {
let client_secrets = VALID_SECRETS.clone();
let access_token = VALID_CREDENTIALS.access_token.clone();
let credentials = Credentials::new(&client_secrets, &access_token);
assert_eq!(credentials.client_secrets, client_secrets);
assert_eq!(credentials.access_token, access_token);
}
#[test]
fn get_access_token_test() {
let credentials = VALID_CREDENTIALS.clone();
assert_eq!( credentials.access_token.access_token, credentials.get_access_token() );
}
#[test]
fn refresh_test() {
let mut credentials = VALID_CREDENTIALS.clone();
let result = credentials.refresh();
assert!( result.is_ok() );
}
#[test]
fn store_test() {
let scopes: [&str; 0] = [];
let credentials = VALID_CREDENTIALS.clone();
credentials.store("test-stored-credentials.json").unwrap();
let saved_credentials = Credentials::from_file("test-stored-credentials.json", &scopes).unwrap();
Command::new("rm").arg("test-stored-credentials.json").output().unwrap();
assert_eq!(saved_credentials, credentials);
}
#[test]
fn are_valid_test() {
let scopes = get_test_scopes();
let test_file = get_test_credentials_file();
let credentials = Credentials::from_file(&test_file, &scopes).unwrap();
assert!( !credentials.are_valid() );
}
#[test]
fn from_file_test() {
let scopes = get_test_scopes();
let test_file = get_test_credentials_file();
let expected_credentials = serde_json::from_str( &get_test_json() ).unwrap();
let credentials = Credentials::from_file(&test_file, &scopes).unwrap();
assert_eq!(credentials, expected_credentials);
}
#[test]
fn from_file_invalid_scopes_test() {
let scopes = ["invalid-scope"];
let test_file = get_test_credentials_file();
let result = Credentials::from_file(&test_file, &scopes);
assert!( result.is_err() );
assert_eq!( result.unwrap_err().kind, ErrorKind::MismatchedScopes );
}
#[test]
fn from_file_non_existent_test() {
let scopes = get_test_scopes();
let test_file = "non-existent.json";
let result = Credentials::from_file(&test_file, &scopes);
assert!( result.is_err() );
assert_eq!( result.unwrap_err().kind, ErrorKind::IO );
}
#[test]
fn from_file_invalid_json_test() {
let scopes = get_test_scopes();
let test_file = testfile::from("This is not JSON");
let result = Credentials::from_file(&test_file, &scopes);
assert!( result.is_err() );
assert_eq!( result.unwrap_err().kind, ErrorKind::Json );
}
#[test]
fn from_file_no_longer_valid_test() {
let scopes = get_test_scopes();
let test_file = get_test_credentials_file();
let credentials = Credentials::from_file(&test_file, &scopes).unwrap();
assert_eq!( credentials.are_valid(), false );
}
}