Automated translation of german into "Leichte Sprache"
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

281 lines
10 KiB

3 weeks ago
  1. use actix_web::client::Client;
  2. use actix_web::{http, web, HttpRequest, HttpResponse};
  3. use base64::URL_SAFE_NO_PAD;
  4. use percent_encoding::percent_decode_str;
  5. use rand::rngs::OsRng;
  6. use rand::Rng;
  7. use rand::RngCore;
  8. use regex::Regex;
  9. use std::collections::HashMap;
  10. use std::time::Duration;
  11. use crate::config::{ADJ_LIST, NAME_LIST, PROXY_TIMEOUT, USER_AGENT};
  12. use crate::debug;
  13. use crate::errors::{crash, TrainCrash};
  14. use crate::templates::get_lang;
  15. use crate::CONFIG;
  16. #[derive(Serialize)]
  17. struct NCLoginForm<'a> {
  18. pub user: &'a str,
  19. pub password: &'a str,
  20. pub timezone: &'a str,
  21. pub timezone_offset: &'a str,
  22. pub requesttoken: &'a str,
  23. }
  24. // check if the user is connected to Nextcloud
  25. // returns Some(cookie_raw_value) if connected
  26. // returns None if disconnected
  27. pub fn is_logged_in(req: &HttpRequest) -> Option<&str> {
  28. let c = req.headers().get("Cookie")?.to_str().ok()?;
  29. if c.contains("nc_username") {
  30. Some(c)
  31. } else {
  32. None
  33. }
  34. }
  35. // attempts to create the account from Nextcloud's API
  36. // returns the newly created username.
  37. // if it fails (bad return code), returns None.
  38. pub async fn create_account(
  39. client: &web::Data<Client>,
  40. user: &str,
  41. password: &str,
  42. lang: String,
  43. ) -> Result<String, TrainCrash> {
  44. let mut register_query = client
  45. .post(format!(
  46. "{}/{}",
  47. CONFIG.nextcloud_url, "ocs/v1.php/cloud/users"
  48. ))
  49. .timeout(Duration::new(PROXY_TIMEOUT, 0))
  50. .basic_auth(&CONFIG.admin_username, Some(&CONFIG.admin_password))
  51. .header(
  52. http::header::CONTENT_TYPE,
  53. "application/x-www-form-urlencoded",
  54. )
  55. .header("OCS-APIRequest", "true")
  56. .send_form(&NCCreateAccountForm {
  57. userid: user,
  58. password,
  59. quota: "0B",
  60. language: &lang,
  61. })
  62. .await
  63. .map_err(|e| {
  64. eprintln!("error_createaccount_post: {}", e);
  65. crash(lang.clone(), "error_createaccount_post")
  66. })?;
  67. // only 200 http status code is allowed
  68. if register_query.status() != 200 {
  69. eprintln!("error_createaccount_status: {}", register_query.status());
  70. // + extract response body for debugging purposes
  71. let response_body = register_query.body().await.map_err(|e| {
  72. eprintln!("error_createaccount_post_body: {}", e);
  73. crash(lang.clone(), "error_createaccount_post_body")
  74. })?;
  75. debug(&format!("Body: {:#?}", response_body));
  76. return Err(crash(lang.clone(), "error_createaccount_status"));
  77. }
  78. // extract response body
  79. let response_body = register_query.body().await.map_err(|e| {
  80. eprintln!("error_createaccount_post_body: {}", e);
  81. crash(lang.clone(), "error_createaccount_post_body")
  82. })?;
  83. let response_body = String::from_utf8_lossy(&response_body);
  84. // grasp NC status code
  85. let status_start = response_body.find("<statuscode>").ok_or_else(|| {
  86. eprintln!("error_createaccount_ncstatus_parse: start missing");
  87. crash(lang.clone(), "error_createaccount_ncstatus_parse")
  88. })? + 12;
  89. let status_end = response_body.find("</statuscode>").ok_or_else(|| {
  90. eprintln!("error_createaccount_ncstatus_parse: end missing");
  91. crash(lang.clone(), "error_createaccount_ncstatus_parse")
  92. })?;
  93. let code = &response_body[status_start..status_end];
  94. match code.parse::<u16>() {
  95. Ok(100) => Ok(String::from(user)), // success
  96. Ok(r) => {
  97. eprintln!("error_createaccount_ncstatus: {}", r);
  98. Err(crash(lang.clone(), "error_createaccount_ncstatus"))
  99. }
  100. Err(e) => {
  101. eprintln!("error_createaccount_ncstatus_parse: {}", e);
  102. Err(crash(lang.clone(), "error_createaccount_ncstatus_parse"))
  103. }
  104. }
  105. }
  106. #[derive(Serialize)]
  107. struct NCCreateAccountForm<'a> {
  108. pub userid: &'a str,
  109. pub password: &'a str,
  110. pub quota: &'a str,
  111. pub language: &'a str,
  112. }
  113. pub async fn login(
  114. client: &web::Data<Client>,
  115. req: &HttpRequest,
  116. user: &str,
  117. password: &str,
  118. ) -> Result<HttpResponse, TrainCrash> {
  119. debug(&format!("Sending forged login for user {}", user));
  120. // 1. GET /csrftoken
  121. let mut login_get = client
  122. .get(format!("{}/{}", CONFIG.nextcloud_url, "csrftoken"))
  123. .timeout(Duration::new(PROXY_TIMEOUT, 0))
  124. .header("User-Agent", USER_AGENT)
  125. .header("Accept-Language" , "fr" )
  126. .send()
  127. .await
  128. .map_err(|e| {
  129. eprintln!("error_login_get: {}", e);
  130. crash(get_lang(&req), "error_login_get")
  131. })?;
  132. // rewrite cookie headers from GET to POST
  133. let mut str_cookiepair = String::new();
  134. // remove duplicate oc<id> cookie (nextcloud bug)
  135. // leading to sncf being unable to forge logins
  136. let cookie_set = login_get.headers().get_all("set-cookie");
  137. let mut cookie_map: HashMap<String, String> = HashMap::new();
  138. for c in cookie_set {
  139. // get str version of cookie header
  140. let c_str = c.to_str().map_err(|e| {
  141. eprintln!("error_login_cookiepair (1): {}", e);
  142. crash(get_lang(&req), "error_login_cookiepair")
  143. })?;
  144. // percent decode
  145. let c_str = percent_decode_str(c_str).decode_utf8_lossy();
  146. //then remove values after ';'
  147. let c_str_arr = c_str.split(';').collect::<Vec<&str>>();
  148. let c_str = c_str_arr
  149. .first()
  150. .expect("error: cookiepair split does not have a first value. shouldn't happen.");
  151. // split cookie key and cookie value
  152. // split_once would work best but it's nightly-only for now
  153. let c_str_arr = c_str.split('=').collect::<Vec<&str>>();
  154. let c_key = c_str_arr
  155. .first()
  156. .expect("error: cookie key split does not have a first value, shouldn't happen.");
  157. let c_value = c_str.replace(&format!("{}=", c_key), "");
  158. if c_key != c_str {
  159. // if the key already exists in hashmap, replace its value
  160. // else, insert it
  161. if let Some(c_sel) = cookie_map.get_mut(*c_key) {
  162. *c_sel = c_value;
  163. } else {
  164. cookie_map.insert(c_key.to_string(), c_value);
  165. }
  166. } else {
  167. eprintln!("error_login_cookiepair (2)");
  168. return Err(crash(get_lang(&req), "error_login_cookiepair"));
  169. }
  170. }
  171. for (cookie_k, cookie_v) in cookie_map {
  172. str_cookiepair.push_str(&format!("{}={}; ", cookie_k, cookie_v));
  173. }
  174. // load requesttoken regex
  175. lazy_static! {
  176. static ref RE: Regex = Regex::new(r#"\{"token":"(?P<token>[^"]*)"\}"#)
  177. .expect("Error while parsing the requesttoken regex");
  178. }
  179. let post_body = login_get.body().await.map_err(|e| {
  180. eprintln!("error_login_get_body: {}", e);
  181. crash(get_lang(&req), "error_login_get_body")
  182. })?;
  183. let post_body_str = String::from_utf8_lossy(&post_body);
  184. // save requesttoken (CSRF) for POST
  185. let requesttoken = RE
  186. .captures(&post_body_str)
  187. .ok_or_else(|| {
  188. eprintln!("error_login_regex (no capture)");
  189. crash(get_lang(&req), "error_login_regex")
  190. })?
  191. .name("token")
  192. .ok_or_else(|| {
  193. eprintln!("error_login_regex (no capture named token)");
  194. crash(get_lang(&req), "error_login_regex")
  195. })?
  196. .as_str();
  197. // 2. POST /login
  198. let mut login_post = client
  199. .post(format!("{}/{}", CONFIG.nextcloud_url, "login"))
  200. .timeout(Duration::new(PROXY_TIMEOUT, 0))
  201. .header("User-Agent", USER_AGENT)
  202. .header("Accept-Language" , "fr" );
  203. // include all NC cookies in one cookie (cookie pair)
  204. login_post = login_post.header("Cookie", str_cookiepair);
  205. // send the same POST data as you'd log in from a web browser
  206. let response_post = login_post
  207. .send_form(&NCLoginForm {
  208. user,
  209. password,
  210. timezone: "UTC",
  211. timezone_offset: "2",
  212. requesttoken,
  213. })
  214. .await
  215. .map_err(|e| {
  216. eprintln!("error_login_post: {}", e);
  217. crash(get_lang(&req), "error_login_post")
  218. })?;
  219. // 3. set the same cookies in the user's browser
  220. let mut user_response = HttpResponse::SeeOther();
  221. for item in response_post.headers().clone().get_all("set-cookie") {
  222. user_response.header(
  223. "Set-Cookie",
  224. item.to_str().map_err(|e| {
  225. eprintln!("error_login_setcookie: {}", e);
  226. crash(get_lang(&req), "error_login_setcookie")
  227. })?,
  228. );
  229. }
  230. // redirect to forms!
  231. Ok(user_response
  232. .header("Accept-Language", "fr" )
  233. .header(http::header::LOCATION, "/apps/forms")
  234. .finish()
  235. .await
  236. .map_err(|e| {
  237. eprintln!("error_login_redir: {}", e);
  238. crash(get_lang(&req), "error_login_redir")
  239. })?)
  240. }
  241. // checks if the token seems valid before asking the db.
  242. // The token must be 45 bytes long and base64-encoded.
  243. // returns true if the token is valid
  244. pub fn check_token(token: &str) -> bool {
  245. let token_dec = base64::decode_config(token, URL_SAFE_NO_PAD);
  246. if let Ok(token_bytes) = token_dec {
  247. token_bytes.len() == 45
  248. } else {
  249. false
  250. }
  251. }
  252. // generates a new token
  253. pub fn gen_token(size: usize) -> String {
  254. // Using /dev/random to generate random bytes
  255. let mut r = OsRng;
  256. let mut my_secure_bytes = vec![0u8; size];
  257. r.fill_bytes(&mut my_secure_bytes);
  258. base64::encode_config(my_secure_bytes, URL_SAFE_NO_PAD)
  259. }
  260. // generates a random username composed of
  261. // an adjective, a name and a 4-byte base64-encoded token.
  262. // with the default list, that represents:
  263. // 141 * 880 = 124 080
  264. // 255^4 / 2 = 2 114 125 312 (we lose approx. the half because of uppercase)
  265. // 2 114 125 312 * 124 080 = 2.623206687*10^14 possible combinations??
  266. pub fn gen_name() -> String {
  267. // uppercasing gen_token because NC would probably refuse two
  268. // users with the same name but a different case
  269. // and that'd be a pain to debug
  270. format!(
  271. "{}{}-{}",
  272. list_rand(&ADJ_LIST),
  273. list_rand(&NAME_LIST),
  274. gen_token(4).to_uppercase()
  275. )
  276. }
  277. pub fn list_rand(list: &[String]) -> &String {
  278. let mut rng = rand::thread_rng();
  279. let roll = rng.gen_range(0..list.len() - 1);
  280. &list[roll]
  281. }