[BE] Correctly get & store online classroom cookie

master
Araozu 2023-09-30 12:22:55 -05:00
parent 495c60c323
commit 4d70e7c428
4 changed files with 234 additions and 116 deletions

244
backend/Cargo.lock generated
View File

@ -59,6 +59,17 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "async-channel"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
dependencies = [
"concurrent-queue",
"event-listener",
"futures-core",
]
[[package]] [[package]]
name = "async-stream" name = "async-stream"
version = "0.3.5" version = "0.3.5"
@ -119,13 +130,14 @@ version = "0.1.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"dotenvy", "dotenvy",
"isahc",
"once_cell", "once_cell",
"reqwest", "reqwest",
"rocket", "rocket",
"scraper", "scraper",
"serde", "serde",
"sqlx", "sqlx",
"ureq", "urlencoding",
] ]
[[package]] [[package]]
@ -203,6 +215,12 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]]
name = "castaway"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.83" version = "1.0.83"
@ -233,6 +251,15 @@ dependencies = [
"windows-targets", "windows-targets",
] ]
[[package]]
name = "concurrent-queue"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400"
dependencies = [
"crossbeam-utils",
]
[[package]] [[package]]
name = "const-oid" name = "const-oid"
version = "0.9.5" version = "0.9.5"
@ -318,15 +345,6 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484"
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "crossbeam-queue" name = "crossbeam-queue"
version = "0.3.8" version = "0.3.8"
@ -379,6 +397,37 @@ dependencies = [
"syn 2.0.29", "syn 2.0.29",
] ]
[[package]]
name = "curl"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22"
dependencies = [
"curl-sys",
"libc",
"openssl-probe",
"openssl-sys",
"schannel",
"socket2 0.4.9",
"winapi",
]
[[package]]
name = "curl-sys"
version = "0.4.66+curl-8.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70c44a72e830f0e40ad90dda8a6ab6ed6314d39776599a58a2e5e37fbc6db5b9"
dependencies = [
"cc",
"libc",
"libnghttp2-sys",
"libz-sys",
"openssl-sys",
"pkg-config",
"vcpkg",
"windows-sys",
]
[[package]] [[package]]
name = "der" name = "der"
version = "0.7.8" version = "0.7.8"
@ -541,6 +590,15 @@ version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
"instant",
]
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.0.0" version = "2.0.0"
@ -561,16 +619,6 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "flate2"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]] [[package]]
name = "flume" name = "flume"
version = "0.10.14" version = "0.10.14"
@ -681,6 +729,21 @@ version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
[[package]]
name = "futures-lite"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
dependencies = [
"fastrand 1.9.0",
"futures-core",
"futures-io",
"memchr",
"parking",
"pin-project-lite",
"waker-fn",
]
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.28" version = "0.3.28"
@ -1032,6 +1095,15 @@ version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.8.0" version = "2.8.0"
@ -1049,6 +1121,34 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "isahc"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9"
dependencies = [
"async-channel",
"castaway",
"crossbeam-utils",
"curl",
"curl-sys",
"encoding_rs",
"event-listener",
"futures-lite",
"http",
"httpdate",
"log",
"mime",
"once_cell",
"polling",
"slab",
"sluice",
"tracing",
"tracing-futures",
"url",
"waker-fn",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.10.5" version = "0.10.5"
@ -1094,6 +1194,16 @@ version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4"
[[package]]
name = "libnghttp2-sys"
version = "0.1.8+1.55.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fae956c192dadcdb5dace96db71fa0b827333cce7c7b38dc71446f024d8a340"
dependencies = [
"cc",
"libc",
]
[[package]] [[package]]
name = "libsqlite3-sys" name = "libsqlite3-sys"
version = "0.26.0" version = "0.26.0"
@ -1105,6 +1215,18 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "libz-sys"
version = "1.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.5" version = "0.4.5"
@ -1411,6 +1533,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "parking"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e52c774a4c39359c1d1c52e43f73dd91a75a614652c825408eec30c95a9b2067"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.1" version = "0.12.1"
@ -1617,6 +1745,22 @@ version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "polling"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
dependencies = [
"autocfg",
"bitflags 1.3.2",
"cfg-if",
"concurrent-queue",
"libc",
"log",
"pin-project-lite",
"windows-sys",
]
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.17" version = "0.2.17"
@ -1987,7 +2131,6 @@ version = "0.21.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36" checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36"
dependencies = [ dependencies = [
"log",
"ring", "ring",
"rustls-webpki", "rustls-webpki",
"sct", "sct",
@ -2240,6 +2383,17 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "sluice"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5"
dependencies = [
"async-channel",
"futures-core",
"futures-io",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.11.0" version = "1.11.0"
@ -2356,7 +2510,7 @@ dependencies = [
"tokio-stream", "tokio-stream",
"tracing", "tracing",
"url", "url",
"webpki-roots 0.24.0", "webpki-roots",
] ]
[[package]] [[package]]
@ -2599,7 +2753,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand 2.0.0",
"redox_syscall", "redox_syscall",
"rustix", "rustix",
"windows-sys", "windows-sys",
@ -2838,6 +2992,16 @@ dependencies = [
"valuable", "valuable",
] ]
[[package]]
name = "tracing-futures"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
dependencies = [
"pin-project",
"tracing",
]
[[package]] [[package]]
name = "tracing-log" name = "tracing-log"
version = "0.1.3" version = "0.1.3"
@ -2949,22 +3113,6 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "ureq"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5ccd538d4a604753ebc2f17cd9946e89b77bf87f6a8e2309667c6f2e87855e3"
dependencies = [
"base64",
"flate2",
"log",
"once_cell",
"rustls",
"rustls-webpki",
"url",
"webpki-roots 0.25.2",
]
[[package]] [[package]]
name = "url" name = "url"
version = "2.4.0" version = "2.4.0"
@ -2976,6 +3124,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]] [[package]]
name = "utf-8" name = "utf-8"
version = "0.7.6" version = "0.7.6"
@ -3009,6 +3163,12 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "waker-fn"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690"
[[package]] [[package]]
name = "want" name = "want"
version = "0.3.1" version = "0.3.1"
@ -3115,12 +3275,6 @@ dependencies = [
"rustls-webpki", "rustls-webpki",
] ]
[[package]]
name = "webpki-roots"
version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc"
[[package]] [[package]]
name = "whoami" name = "whoami"
version = "1.4.1" version = "1.4.1"

View File

@ -14,4 +14,7 @@ dotenvy = "0.15.7"
serde = "1.0.188" serde = "1.0.188"
chrono = "0.4.27" chrono = "0.4.27"
scraper = "0.17.1" scraper = "0.17.1"
ureq = "2.8.0" isahc = { version = "1.7.2", features = ["cookies"] }
urlencoding = "2.1.3"

View File

@ -1,12 +1,10 @@
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use isahc::{cookies::CookieJar, prelude::*, Request};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use reqwest::{cookie::Jar, Client, Url};
use std::io::prelude::*;
/// Stores a client with a persistent cookie store /// Stores a client with a persistent cookie store
static SESSION_COOKIE: OnceCell<Client> = OnceCell::new(); static SESSION_COOKIE: OnceCell<CookieJar> = OnceCell::new();
/// Stores the last time a request was made, in seconds since UNIX epoch /// Stores the last time a request was made, in seconds since UNIX epoch
static SESSION_TIME: OnceCell<u64> = OnceCell::new(); static SESSION_TIME: OnceCell<u64> = OnceCell::new();
@ -17,31 +15,25 @@ pub async fn request(url: String) -> Result<String, String> {
ensure_session().await?; ensure_session().await?;
// Get the stored client // Get the stored client
let client = SESSION_COOKIE let jar = SESSION_COOKIE
.get() .get()
.expect("SESSION_COOKIE was not set, even after calling ensure_session"); .expect("SESSION_COOKIE was not set, even after calling ensure_session");
let uri = format!("{}{}", classroom_url, url);
// Do the request // Do the request
let req_builder = client let response = Request::get(uri)
.get(format!("{}{}", classroom_url, url)) .cookie_jar(jar.clone())
.build() .body(())
.unwrap(); .or_else(|err| Err(format!("Error creating request: {:?}", err)))?
.send();
println!("{:?}", req_builder); let mut response = match response {
let response = client.execute(req_builder).await;
let response = match response {
Ok(r) => r, Ok(r) => r,
Err(err) => return Err(format!("Error sending request: {:?}", err)), Err(err) => return Err(format!("Error sending request: {:?}", err)),
}; };
// Check if there's a new cookie match response.text() {
if let Some(session_cookie) = response.cookies().find(|c| c.name() == "ch_sid") {
println!("new cookie: {}", session_cookie.value());
}
match response.text().await {
Ok(t) => Ok(t), Ok(t) => Ok(t),
Err(err) => Err(format!("Error getting text from response: {:?}", err)), Err(err) => Err(format!("Error getting text from response: {:?}", err)),
} }
@ -81,57 +73,23 @@ async fn login() -> Result<(), String> {
let classroom_password = let classroom_password =
std::env::var("CLASSROOM_PASSWORD").expect("CLASSROOM_PASSWORD env var is not set!"); std::env::var("CLASSROOM_PASSWORD").expect("CLASSROOM_PASSWORD env var is not set!");
let params = [ let jar = CookieJar::new();
("login", classroom_user),
("password", classroom_password),
("submitAuth", "".into()),
("_qf__formLogin", "".into()),
];
let jar = Jar::default();
let response = Request::post(format!("{}/index.php", classroom_url))
let client = Client::builder() .header("Content-Type", "application/x-www-form-urlencoded")
.cookie_store(true) .cookie_jar(jar.clone())
.cookie_provider(jar.into()) .body(format!(
.build() "login={}&password={}&submitAuth=&_qf__formLogin=",
.unwrap(); classroom_user, classroom_password
// let client = Client::new(); ))
.unwrap()
.send();
match response {
let req = client Ok(_) => match SESSION_COOKIE.set(jar.clone()) {
.post(format!("{}/index.php", classroom_url)) Ok(_) => Ok(()),
.form(&params) Err(error) => Err(format!("Error saving client: {:?}", error)),
.build().unwrap(); },
println!("{:?}\n", req);
let body_bytes= req.body().unwrap().as_bytes().unwrap();
println!("{:?}\n", std::str::from_utf8(body_bytes));
let result = client.execute(req)
.await;
match result {
Ok(response) => {
println!("{:?}\n\n", response);
let Some(session_cookie) = response.cookies().find(|c| c.name() == "ch_sid") else {
return Err("Response succeeded, but no session cookie was found".into());
};
// Save the client with the session cookie
println!("Got a cookie: {}", session_cookie.value());
let text = response.text().await.unwrap();
// save text to file
let mut f = std::fs::File::create("scraps/test.html").unwrap();
f.write_all(text.as_bytes()).unwrap();
match SESSION_COOKIE.set(client) {
Ok(_) => Ok(()),
Err(error) => Err(format!("Error saving client: {:?}", error)),
}
}
Err(error) => Err(format!("Error connecting to online classroom: {:?}", error)), Err(error) => Err(format!("Error connecting to online classroom: {:?}", error)),
} }
} }

View File

@ -2,6 +2,7 @@ use super::{json_result::JsonResult, session::request};
use rocket::{http::Status, serde::json::Json}; use rocket::{http::Status, serde::json::Json};
use scraper::{ElementRef, Html, Selector}; use scraper::{ElementRef, Html, Selector};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use urlencoding::encode;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct ClassroomPerson { pub struct ClassroomPerson {
@ -22,7 +23,7 @@ pub struct ClassroomPerson {
pub async fn get_users(full_name: String) -> (Status, Json<JsonResult<Vec<ClassroomPerson>>>) { pub async fn get_users(full_name: String) -> (Status, Json<JsonResult<Vec<ClassroomPerson>>>) {
let html = request(format!( let html = request(format!(
"/main/admin/user_list.php?keyword={}&submit=&_qf__search_simple=", "/main/admin/user_list.php?keyword={}&submit=&_qf__search_simple=",
full_name encode(full_name.as_str())
)) ))
.await; .await;
@ -52,6 +53,8 @@ fn parse_users(file: &str) -> Result<Vec<ClassroomPerson>, String> {
let fragment = Html::parse_document(file); let fragment = Html::parse_document(file);
// TODO: If no users are found, the form is not rendered, and the html is different
// Handle this case
let form_element = match fragment.select(&form_selector).next() { let form_element = match fragment.select(&form_selector).next() {
Some(el) => el, Some(el) => el,
None => return Err("Error selecting form#form_users_id: not found".into()), None => return Err("Error selecting form#form_users_id: not found".into()),