use crate::SecretScanner;
use encoding::all::ASCII;
use encoding::{DecoderTrap, Encoding};
use google_drive3::{DriveHub, Scope};
use hyper::Client;
use serde_derive::{Deserialize, Serialize};
use simple_error::SimpleError;
use std::collections::HashSet;
use std::io::Read;
use std::iter::FromIterator;
use yup_oauth2::{Authenticator, DefaultAuthenticatorDelegate, DiskTokenStorage};
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, Default)]
pub struct GDriveFinding {
pub date: String,
pub diff: String,
pub path: String,
#[serde(rename = "stringsFound")]
pub strings_found: Vec<String>,
pub g_drive_id: String,
pub reason: String,
pub web_link: String,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct GDriveScanner {
pub secret_scanner: SecretScanner,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Default)]
pub struct GDriveFileInfo {
pub file_id: String,
pub mime_type: String,
pub modified_time: String,
pub web_link: String,
pub parents: Vec<String>,
pub name: String,
pub path: String,
}
impl GDriveFileInfo {
pub fn new(
file_id: &str,
hub: &DriveHub<
Client,
Authenticator<DefaultAuthenticatorDelegate, DiskTokenStorage, Client>,
>,
) -> Result<Self, SimpleError> {
let fields = "kind, id, name, mimeType, webViewLink, modifiedTime, parents";
let hub_result = hub
.files()
.get(file_id)
.add_scope(Scope::Readonly)
.param("fields", fields)
.doit();
let (_, file_object) = match hub_result {
Ok(x) => x,
Err(e) => {
return Err(SimpleError::new(format!(
"failed accessing Google Metadata API {:?}",
e
)))
}
};
let modified_time = file_object.modified_time.unwrap().clone();
let web_link = file_object.web_view_link.unwrap();
let parents = file_object.parents.unwrap_or_else(Vec::new); let name = file_object.name.unwrap();
let path = format!("{}/{}", parents.join("/"), name);
let mime_type = match file_object.mime_type.unwrap().as_ref() {
"application/vnd.google-apps.spreadsheet" => "text/csv", "application/vnd.google-apps.document" => "text/plain",
u => return Err(SimpleError::new(format!("unknown doc type {}", u))),
};
Ok(Self {
file_id: file_id.to_owned(),
mime_type: mime_type.to_owned(),
modified_time,
web_link,
parents,
name,
path,
})
}
}
impl GDriveScanner {
pub fn new_from_scanner(secret_scanner: SecretScanner) -> Self {
Self { secret_scanner }
}
pub fn new() -> Self { Self { secret_scanner: SecretScanner::default() } }
fn gdrive_file_contents(
gdrivefile: &GDriveFileInfo,
hub: &DriveHub<
Client,
Authenticator<DefaultAuthenticatorDelegate, DiskTokenStorage, Client>,
>,
) -> Result<Vec<u8>, SimpleError> {
let resp_obj = hub
.files()
.export(&gdrivefile.file_id, &gdrivefile.mime_type)
.doit();
let mut resp_obj = match resp_obj {
Ok(r) => r,
Err(e) => return Err(SimpleError::new(e.to_string())),
};
let mut buffer: Vec<u8> = Vec::new();
match resp_obj.read_to_end(&mut buffer) {
Err(e) => return Err(SimpleError::new(e.to_string())),
Ok(s) => s,
};
Ok(buffer)
}
pub fn perform_scan(
&self,
gdrivefile: &GDriveFileInfo,
hub: &DriveHub<
Client,
Authenticator<DefaultAuthenticatorDelegate, DiskTokenStorage, Client>,
>,
scan_entropy: bool,
) -> HashSet<GDriveFinding> {
let buffer = Self::gdrive_file_contents(gdrivefile, hub).unwrap();
let lines = buffer.split(|x| (*x as char) == '\n');
let mut findings: HashSet<GDriveFinding> = HashSet::new();
for new_line in lines {
let matches_map = self.secret_scanner.matches(&new_line);
for (reason, match_iterator) in matches_map {
let mut secrets: Vec<String> = Vec::new();
for matchobj in match_iterator {
secrets.push(
ASCII
.decode(
&new_line[matchobj.start()..matchobj.end()],
DecoderTrap::Ignore,
)
.unwrap_or_else(|_| "<STRING DECODE ERROR>".parse().unwrap()),
);
}
if !secrets.is_empty() {
findings.insert(GDriveFinding {
diff: ASCII
.decode(&new_line, DecoderTrap::Ignore)
.unwrap_or_else(|_| "<STRING DECODE ERROR>".parse().unwrap()),
date: gdrivefile.modified_time.clone(),
strings_found: secrets.clone(),
reason: reason.clone(),
g_drive_id: gdrivefile.file_id.to_string(),
path: gdrivefile.path.clone(),
web_link: gdrivefile.web_link.clone(),
});
}
}
if scan_entropy {
let ef = SecretScanner::entropy_findings(new_line);
if !ef.is_empty() {
findings.insert(GDriveFinding {
diff: ASCII
.decode(&new_line, DecoderTrap::Ignore)
.unwrap_or_else(|_| "<STRING DECODE ERROR>".parse().unwrap()),
date: gdrivefile.modified_time.clone(),
strings_found: ef,
reason: "Entropy".parse().unwrap(),
g_drive_id: gdrivefile.file_id.to_string(),
path: gdrivefile.path.clone(),
web_link: gdrivefile.web_link.clone(),
});
}
}
}
HashSet::from_iter(findings.into_iter())
}
}
impl Default for GDriveScanner {
fn default() -> Self {
Self::new()
}
}