drive_adv/
lib.rs

1pub mod auth;
2pub mod schema;
3pub mod db;
4
5#[macro_use]
6extern crate diesel;
7extern crate dotenv;
8
9use diesel::prelude::*;
10use diesel::{debug_query, insert_into};
11use std::env;
12
13use schema::{files, user_files, parents, permissions};
14
15use reqwest;
16use serde::{self, Deserialize, Serialize};
17use serde_json::Value;
18use diesel::pg::upsert::*;
19use db::establish_connection;
20
21// TODO -- implement
22// There are a bunch of different possible log items. this shcema should encompass all of them,
23// since there is a lot of overlap
24// https://developers.google.com/admin-sdk/reports/v1/appendix/activity/drive
25// https://developers.google.com/admin-sdk/reports/v1/reference/activities/list
26#[derive(Debug, Default, Deserialize)]
27pub struct LogEntry {
28    // general
29    ip_address: Option<String>,
30    time: Option<String>,
31    actor: Option<String>,
32    actor_caller_type: Option<String>,
33    // drive specific
34    event_name: Option<String>,
35    destination_folder_id: Option<String>,
36    doc_id: Option<String>,
37    doc_title: Option<String>,
38    owner: Option<String>,
39    originating_app_id: Option<String>,
40    primary_event: Option<bool>,
41    visibility: Option<String>,
42    shared_drive_id: Option<String>,
43    new_value: Option<String>,
44    old_value: Option<String>,
45    old_visibility: Option<String>,
46    visibility_change: Option<String>,
47}
48
49// TODO -- implement
50/// See https://developers.google.com/admin-sdk/reports/v1/reference/activities/list
51/// user should be "all" to list all users
52pub fn list_audit_log(user: &str, start_time: Option<&str>, end_time: Option<&str>) {
53    // TODO -- combine duplicate code
54    // TODO -- add more parameters
55    let mut token = auth::AuthToken::default();
56    let auth_token = token.get_token_string(None);
57    let list_url = format!("https://www.googleapis.com/admin/reports/v1/activity/users/{}/applications/drive", user);
58    let query: Vec<(&str, &str)> = vec![];
59    let client = reqwest::blocking::Client::new();
60    let res = client
61        .get(&list_url)
62        .query(&query)
63        .bearer_auth(&auth_token)
64        .send()
65        .unwrap();
66}
67
68/// Fields -- comma-separated list of fields inside the files() object to get
69/// See https://developers.google.com/drive/api/v3/reference/files/list
70pub fn list_files(user: &str, fields: Option<&str>,  drive_query: Option<&str>,page_token: Option<&str>) {
71    let mut token = auth::AuthToken::default();
72    let conn = establish_connection();
73    let list_url = "https://www.googleapis.com/drive/v3/files";
74    // use succession maybe
75    let mut page_token = match page_token {
76        Some(p) => p.to_string(),
77        None => String::new(),
78    };
79    loop {
80        let auth_token = token.get_token_string(Some(user)); // should auto refresh
81        let mut query = vec![
82            ("pageSize", "1000"), // limited to 100 usually when you get a large number of fields
83        ];
84        // By default -- all fields
85        let f = format!("nextPageToken,files({})", &fields.unwrap_or("*"));
86        query.push(( "fields", &f));
87        let mut qu = String::new();
88        if let Some(q) = drive_query {
89            qu = format!("{}", q);
90            query.push(("q", &qu));
91        }
92        if page_token != "" {
93            query.push(("pageToken", &page_token));
94        }
95        let client = reqwest::blocking::Client::new();
96        let res = client
97            .get(list_url)
98            .query(&query)
99            .bearer_auth(&auth_token)
100            .send()
101            .unwrap();
102        // TODO: handle errors
103        let response: Value = res.json().unwrap();
104        // Look out for https://github.com/diesel-rs/diesel/issues/860
105        // This is a mess because diesel lacks that feature
106        if response.get("error") != None {
107            println!("{:?}", response);
108        }
109        let files: Vec<File> = serde_json::from_value(response["files"].clone()).unwrap();
110        // all this is Horrible.
111        let iterfiles: Vec<Value> = serde_json::from_value(response["files"].clone()).unwrap();
112        insert_into(files::dsl::files)
113            .values(&files)
114            .on_conflict_do_nothing()
115            .execute(&conn)
116            .unwrap();
117        for file in iterfiles {
118            let mut user_file: UserFile = serde_json::from_value(file.clone()).unwrap();
119            user_file.user_email = Some(user.to_string());
120            insert_into(user_files::dsl::user_files)
121                .values(&user_file)
122                .on_conflict_do_nothing()
123                .execute(&conn)
124                .unwrap();
125
126            if file.get("permissions") != None {
127                let mut permissions: Vec<Permission> =
128                    serde_json::from_value(file["permissions"].clone()).unwrap();
129                for mut perm in &mut permissions {
130                    perm.file_id = file["id"].as_str().unwrap().to_string();
131                }
132                insert_into(permissions::dsl::permissions)
133                    .values(&permissions)
134                    .on_conflict_do_nothing() // TODO upsert
135                    .execute(&conn)
136                    .unwrap();
137            }
138            let iterparents: Vec<Value> =
139                serde_json::from_value(file["parents"].clone()).unwrap_or(vec![]);
140            let parents: Vec<Parent> = iterparents
141                .iter()
142                .map(|p| Parent {
143                    file_id: file["id"].as_str().unwrap().to_string(),
144                    parent_id: p.as_str().unwrap().to_string(),
145                })
146                .collect();
147            insert_into(parents::dsl::parents)
148                .values(&parents)
149                .on_conflict_do_nothing() // TODO upsert
150                .execute(&conn)
151                .unwrap();
152        }
153        if let Some(r) = response.get("nextPageToken") {
154            page_token = r.as_str().unwrap().to_string();
155        }
156        else { break };
157    }
158}
159
160#[derive(Debug, Deserialize)]
161struct Response {
162    #[serde(rename = "nextPageToken")]
163    next_page_token: Option<String>,
164    files: Vec<File>,
165}
166
167// File metadata specific to a user
168#[derive(Debug, Default, Deserialize, Insertable)]
169#[table_name = "user_files"]
170pub struct UserFile {
171    id: String,
172    user_email: Option<String>,
173    #[serde(rename = "viewedByMe")]
174    viewed_by_me: Option<bool>,
175    #[serde(rename = "viewedByMeTime")]
176    viewed_by_me_time: Option<String>,
177    #[serde(rename = "modifiedByMeTime")]
178    modified_by_me_time: Option<String>,
179    #[serde(rename = "sharedWithMeTime")]
180    shared_with_me_time: Option<String>,
181    #[serde(rename = "sharingUser")]
182    sharing_user: Option<serde_json::Value>,
183    capabilities: Option<serde_json::Value>
184}
185
186// TODO: find a way to make all fields default maybe
187// https://developers.google.com/drive/api/v3/reference/files
188#[derive(Debug, Default, Deserialize, Insertable)]
189#[table_name = "files"]
190pub struct File {
191    id: String,
192    name: Option<String>,
193    #[serde(rename = "mimeType")]
194    mime_type: Option<String>,
195    description: Option<String>,
196    trashed: Option<bool>,
197    starred: Option<bool>,
198    #[serde(rename = "explicitlyTrashed")]
199    explicitly_trashed: Option<bool>,
200    #[serde(rename = "trashingUser")]
201    trashing_user: Option<serde_json::Value>,
202    #[serde(rename = "trashedTime")]
203    trashed_time: Option<String>,
204    properties: Option<serde_json::Value>,
205    // app properties
206   // spaces: Option<Vec<String>>, broken for some reason   
207    // version: Option<i32>, BROKEN
208    //
209    // web_view_link -> Nullable<Varchar>,
210    #[serde(rename = "webContentLink")]
211    web_content_link: Option<String>,
212    #[serde(rename = "webViewLink")]
213    web_view_link: Option<String>,
214    // iconLink: String,
215    // hasThumbnail: bool,
216    // thumbnailLink: String,
217    // thumbnailVersion: u32,
218    #[serde(rename = "createdTime")]
219    created_time: Option<String>,
220    #[serde(rename = "modifiedTime")]
221    modified_time: Option<String>,
222    owners: Option<serde_json::Value>,
223    #[serde(rename = "driveId")]
224    drive_id: Option<String>,
225    #[serde(rename = "lastModifyingUser")]
226    last_modifying_user: Option<serde_json::Value>,
227    shared: Option<bool>,
228    #[serde(rename = "viewersCanCopyContent")]
229    viewers_can_copy_content: Option<bool>,
230    #[serde(rename = "copyRequiresWriterPermission")]
231    copy_requires_writer_permission: Option<bool>,
232    #[serde(rename = "writersCanShare")]
233    writers_can_share: Option<bool>,
234    #[serde(rename = "hasAugmentedPermissions")]
235    has_augmented_permissions: Option<bool>,
236    #[serde(rename = "folderColorRgb")]
237    folder_color_rgb: Option<String>,
238    #[serde(rename = "originalFilename")]
239    original_filename: Option<String>,
240    #[serde(rename = "fullFileExtension")]
241    full_file_extension: Option<String>,
242    #[serde(rename = "fileExtension")]
243    file_extension: Option<String>,
244    #[serde(rename = "md5Checksum")]
245    md5_checksum: Option<String>,
246    // size: Option<i64>, BROKEN
247    // #[serde(rename = "quotaBytesUsed")]
248    // quota_bytes_used:  Option<i64>, // TBROKEN
249    #[serde(rename = "headRevisionId")]
250    head_revision_id: Option<String>,
251    // "contentHints": {
252    #[serde(rename = "imageMediaMetadata")]
253    image_media_metadata: Option<serde_json::Value>,
254    #[serde(rename = "videoMediaMetadata")]
255    video_media_metadata: Option<serde_json::Value>,
256    #[serde(rename = "isAppAuthorized")]
257    is_app_authorized: Option<bool>,
258    // exportLinks
259}
260
261// https://developers.google.com/drive/api/v3/reference/permissions
262#[derive(Debug, Insertable, Deserialize)]
263#[table_name = "permissions"]
264struct Permission {
265    #[serde(default)]
266    file_id: String,
267    id: String,
268    #[serde(rename = "type")]
269    perm_type: Option<String>,
270    #[serde(rename = "emailAddress")]
271    email_address: Option<String>,
272    domain: Option<String>,
273    role: Option<String>,
274    #[serde(rename = "allowFileDiscovery")]
275    allow_file_discovery: Option<bool>,
276    #[serde(rename = "displayName")]
277    display_name: Option<String>,
278    #[serde(rename = "expirationTime")]
279    expiration_time: Option<String>,
280    deleted: Option<bool>,
281    // permissionDetails[]  
282}
283
284#[derive(Debug, Insertable, Deserialize)]
285#[table_name = "parents"]
286struct Parent {
287    file_id: String,
288    parent_id: String,
289}