1use std::path::Path;
16
17pub use handlebars_iron::handlebars::to_json;
18use handlebars_iron::{DirectorySource, HandlebarsEngine, Template};
19use iron;
20use iron::mime::Mime;
21use iron::modifiers::Redirect;
22use iron::modifiers::RedirectRaw;
23use iron::prelude::*;
24use iron::{status, AfterMiddleware, AroundMiddleware, Handler};
25use iron_sessionstorage;
26use iron_sessionstorage::backends::SignedCookieBackend;
27use iron_sessionstorage::traits::*;
28use iron_sessionstorage::SessionStorage;
29use mount::Mount;
30use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
31use persistent::{Read, Write};
32use reqwest;
33use router::Router;
34use staticfile::Static;
35use urlencoded::{UrlEncodedBody, UrlEncodedQuery};
36
37#[cfg(feature = "debug")]
38use iron::BeforeMiddleware;
39
40use config::{Config, OauthProvider};
41use core;
42use db_conn::MedalConnection;
43use iron::typemap::Key;
44pub use serde_json::value as json_val;
45
46#[cfg(feature = "signup")]
47use db_conn::SignupResult;
48
49static TASK_DIR: &str = "tasks";
50
51macro_rules! mime {
52 ($top:tt / $sub:tt) => (
53 mime!($top / $sub;)
54 );
55
56 ($top:tt / $sub:tt ; $($attr:tt = $val:tt),*) => (
57 iron::mime::Mime(
58 iron::mime::TopLevel::$top,
59 iron::mime::SubLevel::$sub,
60 vec![ $((Attr::$attr,Value::$val)),* ]
61 )
62 );
63}
64
65macro_rules! with_conn {
66 ( $x:expr , $c:ident, $r:expr , $($y:expr),* ) => {
67 {
68 let mutex = $r.get::<Write<SharedDatabaseConnection<$c>>>().unwrap();
69 let conn = mutex.lock().unwrap_or_else(|e| e.into_inner());
70 $x(&*conn, $($y),*)
71 }
72 };
73}
74
75macro_rules! template_ok {
76 ( $x:expr ) => {{
77 let (template, data) = $x;
78
79 let mut resp = Response::new();
80 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
81 Ok(resp)
82 }};
83}
84
85struct ErrorReporter;
87impl AfterMiddleware for ErrorReporter {
88 fn catch(&self, req: &mut Request, err: IronError) -> IronResult<Response> {
89 if err.response.status != Some(status::Found) || cfg!(feature = "debug") {
90 println!("{} {} {}", err, req.method, req.url);
91 }
92 Err(err)
93 }
94}
95
96struct ErrorShower;
98impl AfterMiddleware for ErrorShower {
99 fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> {
100 let IronError { error, response } = err;
101 if response.body.is_none() {
102 Ok(match response.status {
103 Some(s) => {
104 let n = s.to_u16();
105 if (400..=599).contains(&n) {
106 response.set((mime!(Text / Html),
107 format!("<h1>{} {}</h1>", n, s.canonical_reason().unwrap_or("(Unknown error)"))))
108 } else {
109 response
110 }
111 }
112 _ => response,
113 })
114 } else {
115 Err(IronError { error, response })
116 }
117 }
118}
119
120#[derive(Debug)]
121struct SessionToken {
122 token: String,
123}
124impl iron_sessionstorage::Value for SessionToken {
125 fn get_key() -> &'static str { "medal_session" }
126 fn into_raw(self) -> String { self.token }
127 fn from_raw(value: String) -> Option<Self> {
128 if value.is_empty() {
129 None
130 } else {
131 Some(SessionToken { token: value })
132 }
133 }
134}
135
136pub struct RequestTimeLogger {}
137
138impl AroundMiddleware for RequestTimeLogger {
139 fn around(self, handler: Box<dyn Handler>) -> Box<dyn Handler> {
140 use std::time::{Duration, Instant};
141
142 Box::new(move |req: &mut Request| -> IronResult<Response> {
143 let (threshold, threshold_critical) = match req.url.path().first() {
145 Some(&"save") => (Duration::from_millis(80), Duration::from_millis(120)),
146 Some(&"contest") => (Duration::from_millis(80), Duration::from_millis(120)),
147 Some(&"oauth") => (Duration::from_millis(800), Duration::from_millis(3200)),
148 _ => (Duration::from_millis(20), Duration::from_millis(80)),
149 };
150
151 let start = Instant::now();
153
154 let logtiming = {
156 let config = req.get::<Read<SharedConfiguration>>().unwrap();
157 config.log_timing.unwrap_or(false)
158 };
159
160 let res = handler.handle(req);
162
163 let duration = start.elapsed();
165
166 if logtiming {
167 println!("t:\t{:?}\t{}\t{}", duration, req.method, req.url);
168 } else if duration > threshold_critical {
169 println!("Request took MUCH too long ({:?}) {} {}", duration, req.method, req.url);
170 } else if duration > threshold {
171 println!("Request took too long ({:?}) {} {}", duration, req.method, req.url);
172 }
173
174 res
175 })
176 }
177}
178
179pub struct CookieDistributor {}
180
181impl AroundMiddleware for CookieDistributor {
182 fn around(self, handler: Box<dyn Handler>) -> Box<dyn Handler> {
183 use rand::{distributions::Alphanumeric, thread_rng, Rng};
184
185 Box::new(move |req: &mut Request| -> IronResult<Response> {
186 if req.session().get::<SessionToken>().expect("blub...").is_none() {
187 let session_token: String = thread_rng().sample_iter(&Alphanumeric).take(10).collect();
188 req.session().set(SessionToken { token: session_token }).unwrap();
189 }
190 handler.handle(req)
191 })
192 }
193}
194
195#[cfg(feature = "debug")]
196pub struct RequestLogger {}
197
198#[cfg(feature = "debug")]
199impl BeforeMiddleware for RequestLogger {
200 fn before(&self, req: &mut Request) -> IronResult<()> {
201 println!("{}: {}", req.method, req.url);
202
203 Ok(())
204 }
205}
206
207#[derive(Debug)]
208struct SessionError {
209 message: String,
210}
211impl ::std::error::Error for SessionError {
212 fn description(&self) -> &str { &self.message }
213}
214
215impl ::std::fmt::Display for SessionError {
216 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "{}", self.message) }
217}
218
219trait RequestSession {
220 fn get_session_token(&mut self) -> Option<String>;
221 fn require_session_token(&mut self) -> IronResult<String>;
222 fn expect_session_token(&mut self) -> IronResult<String>;
223}
224
225impl RequestSession for Request<'_, '_> {
226 fn get_session_token(&mut self) -> Option<String> {
227 let session_token = self.session().get::<SessionToken>().unwrap();
228 (|st: Option<SessionToken>| -> Option<String> { Some(st?.token) })(session_token)
229 }
230
231 fn require_session_token(&mut self) -> IronResult<String> {
232 match self.session().get::<SessionToken>().unwrap() {
233 Some(SessionToken { token: session }) => Ok(session),
234 _ => {
235 use rand::{distributions::Alphanumeric, thread_rng, Rng};
236
237 let new_session_key: String = thread_rng().sample_iter(&Alphanumeric).take(28).collect();
238 self.session().set(SessionToken { token: new_session_key }).unwrap();
239 Err(IronError {
240 error: Box::new(SessionError {
241 message: "No valid session found, redirecting to cookie page".to_string(),
242 }),
243 response: Response::with((
244 status::Found,
245 RedirectRaw(format!("/cookie?{}", self.url.path().join("/"))),
246 )),
247 })
248 }
249 }
250 }
251
252 fn expect_session_token(&mut self) -> IronResult<String> {
253 match self.session().get::<SessionToken>().unwrap() {
254 Some(SessionToken { token: session }) => Ok(session),
255 _ => Err(IronError { error: Box::new(SessionError { message:
256 "No valid session found, access denied".to_string() }),
257 response: Response::with(status::Forbidden) }),
258 }
259 }
260}
261
262trait RequestRouterParam {
263 fn get_str(&mut self, key: &str) -> Option<String>;
264 fn get_int<T: ::std::str::FromStr>(&mut self, key: &str) -> Option<T>;
265 fn expect_int<T: ::std::str::FromStr>(&mut self, key: &str) -> IronResult<T>;
266 fn expect_str(&mut self, key: &str) -> IronResult<String>;
267}
268
269impl RequestRouterParam for Request<'_, '_> {
270 fn get_str(&mut self, key: &str) -> Option<String> { Some(self.extensions.get::<Router>()?.find(key)?.to_owned()) }
271
272 fn get_int<T: ::std::str::FromStr>(&mut self, key: &str) -> Option<T> {
273 self.extensions.get::<Router>()?.find(key)?.parse::<T>().ok()
274 }
275
276 fn expect_int<T: ::std::str::FromStr>(&mut self, key: &str) -> IronResult<T> {
277 match self.get_int::<T>(key) {
278 Some(i) => Ok(i),
279 _ => Err(IronError { error: Box::new(SessionError { message:
280 "No valid routing parameter".to_string() }),
281 response: Response::with(status::Forbidden) }),
282 }
283 }
284
285 fn expect_str(&mut self, key: &str) -> IronResult<String> {
286 match self.get_str(key) {
287 Some(s) => Ok(s),
288 _ => Err(IronError { error: Box::new(SessionError { message:
289 "Routing parameter missing".to_string() }),
290 response: Response::with(status::Forbidden) }),
291 }
292 }
293}
294
295struct AugMedalError<'c, 'a: 'c, 'b: 'c + 'a>(core::MedalError, &'c mut Request<'a, 'b>);
296
297impl<'c, 'a, 'b> From<AugMedalError<'c, 'a, 'b>> for IronError {
298 fn from(AugMedalError(me, req): AugMedalError<'c, 'a, 'b>) -> Self {
299 match me {
300 core::MedalError::NotLoggedIn => {
301 IronError { error: Box::new(SessionError { message:
302 "Not Logged in, redirecting to login page".to_string() }),
303 response: Response::with((status::Found,
304 RedirectRaw(format!("/login?{}", req.url.path().join("/"))))) }
305 }
306 core::MedalError::AccessDenied => IronError { error: Box::new(SessionError { message:
307 "Access denied".to_string() }),
308 response: Response::with(status::Unauthorized) },
309 core::MedalError::UnknownId => IronError { error: Box::new(SessionError { message:
310 "Not found".to_string() }),
311 response: Response::with(status::NotFound) },
312 core::MedalError::CsrfCheckFailed => IronError { error: Box::new(SessionError { message:
313 "CSRF Error".to_string() }),
314 response: Response::with(status::Forbidden) },
315 core::MedalError::SessionTimeout => {
316 IronError { error: Box::new(SessionError { message: "Session timed out".to_string() }),
317 response: Response::with(status::Forbidden) }
318 }
319 core::MedalError::DatabaseError => {
320 IronError { error: Box::new(SessionError { message: "Database Error".to_string() }),
321 response: Response::with(status::InternalServerError) }
322 }
323 core::MedalError::ConfigurationError => {
324 IronError { error: Box::new(SessionError { message: "Server misconfiguration. Please contact an administrator!".to_string() }),
325 response: Response::with(status::InternalServerError) }
326 }
327 core::MedalError::DatabaseConnectionError => {
328 IronError { error: Box::new(SessionError { message: "Database Connection Error".to_string() }),
329 response: Response::with(status::InternalServerError) }
330 }
331 core::MedalError::PasswordHashingError => {
332 IronError { error: Box::new(SessionError { message: "Error hashing the passwords".to_string() }),
333 response: Response::with(status::InternalServerError) }
334 }
335 core::MedalError::UnmatchedPasswords => {
336 IronError { error: Box::new(SessionError { message:
337 "The two passwords did not match.".to_string() }),
338 response: Response::with(status::Forbidden) }
339 }
340 core::MedalError::NotFound => IronError { error: Box::new(SessionError { message:
341 "Not found".to_string() }),
342 response: Response::with(status::NotFound) },
343 core::MedalError::AccountIncomplete => IronError { error: Box::new(SessionError { message:
344 "Account incomplete".to_string() }),
345 response: Response::with((status::Found,
346 Redirect(iron::Url::parse(&format!("{}?status=firstlogin",
347 url_for!(req, "myprofile"))).unwrap()))) },
348 core::MedalError::OauthError(errstr) => {
349 IronError { error: Box::new(SessionError { message: format!("Access denied (Error {})", errstr) }),
350 response: Response::with(status::Unauthorized) }
351 }
352 core::MedalError::WebauthnError => {
353 IronError { error: Box::new(SessionError { message: "Webauthn Error. Try again or login with some other login method.".to_string() }),
354 response: Response::with(status::InternalServerError) }
355 }
356 core::MedalError::ErrorWithJson(jsonval) => {
357 IronError { error: Box::new(SessionError { message: "Returned an error to user.".to_string() }),
358 response: Response::with((status::Forbidden, mime!(Application / Json), serde_json::json!(jsonval).to_string())) }
359 }
360 }
361 }
362}
363
364trait RequestAugmentMedalError<'c, 'a: 'c, 'b: 'c + 'a, R> {
365 fn aug(self, req: &'c mut Request<'a, 'b>) -> Result<R, AugMedalError<'c, 'a, 'b>>;
366}
367impl<'c, 'a: 'c, 'b: 'c + 'a, T> RequestAugmentMedalError<'c, 'a, 'b, T> for Result<T, core::MedalError> {
368 fn aug(self, req: &'c mut Request<'a, 'b>) -> Result<T, AugMedalError<'c, 'a, 'b>> {
369 self.map_err(move |me| AugMedalError(me, req))
370 }
371}
372
373fn handle_json_response_simple(jsonval: json_val::Map<String, json_val::Value>) -> Response {
374 Response::with((status::Ok, mime!(Application / Json), serde_json::json!(jsonval).to_string()))
375}
376
377fn handle_json_response<T>(jsonvalresult: Result<json_val::Map<String, json_val::Value>, T>) -> Result<Response, T> {
378 Ok(handle_json_response_simple(jsonvalresult?))
379}
380
381fn login_info(config: &Config) -> core::LoginInfo {
382 core::LoginInfo { password_login: config.enable_password_login == Some(true),
383 self_url: config.self_url.clone(),
384 oauth_providers: config.oauth_providers.clone() }
385}
386
387fn greet_personal<C>(req: &mut Request) -> IronResult<Response>
388 where C: MedalConnection + std::marker::Send + 'static {
389 let session_token = req.get_session_token();
390 let config = req.get::<Read<SharedConfiguration>>().unwrap();
391
392 let (template, mut data) = with_conn![core::index, C, req, session_token, login_info(&config)].aug(req)?;
393 data.insert("config".to_string(), to_json(&config.template_params));
394
395 let mut resp = Response::new();
396 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
397 Ok(resp)
398}
399
400fn dbstatus<C>(req: &mut Request) -> IronResult<Response>
401 where C: MedalConnection + std::marker::Send + 'static {
402 let config = req.get::<Read<SharedConfiguration>>().unwrap();
403 let query_string = req.url.query().map(|s| s.to_string());
404
405 let status = with_conn![core::status, C, req, config.dbstatus_secret.clone(), query_string].aug(req)?;
406
407 let mut resp = Response::new();
408 resp.set_mut(status).set_mut(status::Ok);
409 Ok(resp)
410}
411
412fn debug<C>(req: &mut Request) -> IronResult<Response>
413 where C: MedalConnection + std::marker::Send + 'static {
414 let session_token = req.get_session_token();
415
416 let (template, data) = with_conn![core::debug, C, req, session_token];
417
418 let mut resp = Response::new();
419 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
420 Ok(resp)
421}
422
423fn debug_new_token<C>(req: &mut Request) -> IronResult<Response>
424 where C: MedalConnection + std::marker::Send + 'static {
425 let session_token = req.get_session_token();
426
427 #[cfg(feature = "debug")]
428 println!("Logging out session {:?}", session_token);
429
430 with_conn![core::logout, C, req, session_token];
431
432 Ok(Response::with((status::Found, Redirect(url_for!(req, "debug")))))
433}
434
435fn debug_logout<C>(req: &mut Request) -> IronResult<Response>
436 where C: MedalConnection + std::marker::Send + 'static {
437 let session_token = req.get_session_token();
438
439 #[cfg(feature = "debug")]
440 println!("Logging out session {:?}", session_token);
441
442 with_conn![core::logout, C, req, session_token];
443
444 Ok(Response::with((status::Found, Redirect(url_for!(req, "debug")))))
445}
446
447fn debug_create_session<C>(req: &mut Request) -> IronResult<Response>
448 where C: MedalConnection + std::marker::Send + 'static {
449 let session_token = req.get_session_token();
450
451 with_conn![core::debug_create_session, C, req, session_token];
452
453 Ok(Response::with((status::Found, Redirect(url_for!(req, "debug")))))
454}
455
456fn contests<C>(req: &mut Request) -> IronResult<Response>
457 where C: MedalConnection + std::marker::Send + 'static {
458 let session_token = req.require_session_token()?;
459 let category = req.get_str("category");
460 let query_string = req.url.query().unwrap_or("").to_string();
461
462 let visibility = if let Some(secret) = query_string.strip_prefix("secret=") {
464 core::ContestVisibility::WithSecret(secret.to_string())
465 } else {
466 core::ContestVisibility::All
467 };
468
469 let category = if let Some(category) = category {
470 Some(category)
471 } else {
472 if query_string.contains("open") {
473 Some("training".to_owned())
474 } else if query_string.contains("current") {
475 Some("contest".to_owned())
476 } else if query_string.contains("challenge") {
477 Some("challenge".to_owned())
478 } else if query_string.contains("standalone_task") {
479 Some("standalone_task".to_owned())
480 } else {
481 None
482 }
483 };
484
485 let config = req.get::<Read<SharedConfiguration>>().unwrap();
486
487 let mut category_names: std::collections::BTreeMap<String, String> = std::collections::BTreeMap::new();
488 if let Some(template_params) = config.template_params.as_ref() {
489 if let Some(categories) = template_params.get("categories") {
490 if let Some(map) = categories.as_object() {
491 for (key, value) in map {
493 if let Some(name) = value.get("name") {
494 if let Some(name) = name.as_str() {
495 category_names.insert(key.to_string(), name.to_string());
496 }
497 }
498 }
499 }
500 }
501 }
502
503 let is_results = query_string.contains("results");
504 let res = with_conn![core::show_contests,
505 C,
506 req,
507 &session_token,
508 category.clone(),
509 login_info(&config),
510 visibility,
511 is_results,
512 category_names];
513
514 if res.is_err() {
515 println!("DATABASE CONNECTION LOST! Restarting database connection.");
518 let conn = C::reconnect(&config);
519 let mutex = req.get::<Write<SharedDatabaseConnection<C>>>().unwrap();
520 let mut sharedconn = mutex.lock().unwrap_or_else(|e| e.into_inner());
521 *sharedconn = conn;
522 }
524
525 let (template, mut data) = res.unwrap();
526 data.insert("config".to_string(), to_json(&config.template_params));
527 data.insert("category".to_string(), to_json(&category));
528
529 if let Some(categoryname) = category {
530 if let Some(template_params) = config.template_params.as_ref() {
531 if let Some(categories) = template_params.get("categories") {
532 if let Some(currentcategory) = categories.get(categoryname) {
533 data.insert("current_category".to_string(), currentcategory.clone());
534 }
535 }
536 }
537 }
538
539 if is_results {
540 data.insert("direct_link_to_results".to_string(), to_json(&true));
541 }
542
543 let mut resp = Response::new();
544 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
545 Ok(resp)
546}
547
548fn contest<C>(req: &mut Request) -> IronResult<Response>
549 where C: MedalConnection + std::marker::Send + 'static {
550 let contest_id = req.expect_int::<i64>("contestid")?;
551 let category = req.get_str("category");
552 let secret = req.get_str("secret");
553 let session_token = req.require_session_token()?;
554 let query_string = req.url.query().map(|s| s.to_string());
555
556 let config = req.get::<Read<SharedConfiguration>>().unwrap();
557 let res = with_conn![core::show_contest,
558 C,
559 req,
560 contest_id,
561 &session_token,
562 query_string,
563 login_info(&config),
564 secret].aug(req)?;
565
566 match res {
567 Err(task_id) => {
568 Ok(Response::with((status::Found, Redirect(url_for!(req, "task", "taskid" => format!("{}", task_id))))))
569 }
570 Ok((template, mut data)) => {
571 data.insert("config".to_string(), to_json(&config.template_params));
572 data.insert("category".to_string(), to_json(&category));
573
574 if let Some(categoryname) = category {
575 if let Some(template_params) = config.template_params.as_ref() {
576 if let Some(categories) = template_params.get("categories") {
577 if let Some(currentcategory) = categories.get(categoryname) {
578 data.insert("current_category".to_string(), currentcategory.clone());
579 }
580 }
581 }
582 }
583
584 let mut resp = Response::new();
585 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
586 Ok(resp)
587 }
588 }
589}
590
591fn contests_or_contest<C>(req: &mut Request) -> IronResult<Response>
592 where C: MedalConnection + std::marker::Send + 'static {
593 let firstparam_string = req.get_str("categoryorcontestid");
594 let firstparam_numeric = req.get_int::<i64>("categoryorcontestid");
595
596 let secondparam_string = req.get_str("contestidorsecret");
597 let secondparam_numeric = req.get_int::<i64>("contestidorsecret");
598
599 if firstparam_numeric.is_some() {
601 let router_map = req.extensions.get_mut::<Router>().unwrap();
602
603 router_map.insert("contestid".to_string(), firstparam_string.unwrap());
604
605 if let Some(secondparam_string) = secondparam_string {
606 router_map.insert("secret".to_string(), secondparam_string);
607 }
608
609 contest::<C>(req)
610 } else if secondparam_numeric.is_some() {
611 let router_map = req.extensions.get_mut::<Router>().unwrap();
612
613 router_map.insert("category".to_string(), firstparam_string.unwrap());
614 router_map.insert("contestid".to_string(), secondparam_string.unwrap());
615
616 contest::<C>(req)
617 } else if secondparam_string.is_none() {
618 let router_map = req.extensions.get_mut::<Router>().unwrap();
619
620 router_map.insert("category".to_string(), firstparam_string.unwrap());
621
622 contests::<C>(req)
623 } else {
624 Err(core::MedalError::NotFound).aug(req).map_err(|x| x.into())
625 }
626}
627
628fn contestresults<C>(req: &mut Request) -> IronResult<Response>
629 where C: MedalConnection + std::marker::Send + 'static {
630 let config = req.get::<Read<SharedConfiguration>>().unwrap();
631 let disable_contest_results = config.disable_results_page.unwrap_or(false);
632
633 if disable_contest_results {
634 let mut resp = Response::new();
635 resp.set_mut(Template::new(&"nocontestresults", 2)).set_mut(status::Locked);
636 return Ok(resp);
637 }
638
639 let contest_id = req.expect_int::<i64>("contestid")?;
640 let session_token = req.require_session_token()?;
641
642 let (template, data) = with_conn![core::show_contest_results, C, req, contest_id, &session_token].aug(req)?;
643
644 let mut resp = Response::new();
645 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
646 Ok(resp)
647}
648
649fn contestresults_download<C>(req: &mut Request) -> IronResult<Response>
650 where C: MedalConnection + std::marker::Send + 'static {
651 let config = req.get::<Read<SharedConfiguration>>().unwrap();
652 let disable_contest_results = config.disable_results_page.unwrap_or(false);
653
654 if disable_contest_results {
655 let mut resp = Response::new();
656 resp.set_mut(Template::new(&"nocontestresults", 2)).set_mut(status::Locked);
657 return Ok(resp);
658 }
659
660 let contest_id = req.expect_int::<i64>("contestid")?;
661 let session_token = req.require_session_token()?;
662
663 let (template, data) = with_conn![core::show_contest_results, C, req, contest_id, &session_token].aug(req)?;
664
665 use iron::headers::{Charset, ContentDisposition, DispositionParam, DispositionType};
666
667 let cd = ContentDisposition { disposition: DispositionType::Attachment,
668 parameters: vec![DispositionParam::Filename(
669 Charset::Ext("Utf-8".to_string()), None, format!("{}.csv", utf8_percent_encode(data.get("contestname").unwrap().as_str().unwrap(), NON_ALPHANUMERIC)).as_bytes().to_vec(), )] };
674
675 let mime: Mime = "text/csv; charset=UTF-8".parse().unwrap();
676 let mut resp = Response::new();
677 resp.headers.set(cd);
678 resp.set_mut(Template::new(&format!("{}_download", template), data)).set_mut(status::Ok).set_mut(mime);
679 Ok(resp)
680}
681
682fn contest_post<C>(req: &mut Request) -> IronResult<Response>
683 where C: MedalConnection + std::marker::Send + 'static {
684 let (contest_id, category) = {
685 let potential_contest_id_1 = req.expect_int::<i64>("contestid");
686 let potential_contest_id_2 = req.expect_int::<i64>("categoryorcontestid");
687 let potential_contest_id_3 = req.expect_int::<i64>("contestidorsecret");
688
689 let potential_category_1 = req.get_str("category");
690 let potential_category_3 = req.get_str("categoryorcontestid");
691
692 match (potential_contest_id_1, potential_contest_id_2) {
693 (Ok(contest_id), _) => (contest_id, potential_category_1),
694 (Err(_), Ok(contest_id)) => (contest_id, None),
695 _ => (potential_contest_id_3?, potential_category_3),
696 }
697 };
698
699 let session_token = req.expect_session_token()?;
700
701 let (csrf_token, secret, teampartner_logincode) = {
702 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
703 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
704 formdata.get("secret").map(|x| x[0].to_owned()),
705 formdata.get("teampartner_logincode").map(|x| x[0].to_owned()))
706 };
707
708 let res = with_conn![core::start_contest,
709 C,
710 req,
711 contest_id,
712 &session_token,
713 &csrf_token,
714 secret,
715 teampartner_logincode].aug(req)?;
716
717 if let Err(value) = res {
718 if let Some(category) = category {
719 Ok(Response::with((status::Found,
720 Redirect(iron::Url::parse(&format!("{}?team_participation={}",
721 url_for!(req, "contests_or_contest_secret",
722 "categoryorcontestid" => category.to_string(),
723 "contestidorsecret" => format!("{}", contest_id)),
724 value)).unwrap()))))
725 } else {
726 Ok(Response::with((status::Found, Redirect(
727 iron::Url::parse(&format!("{}?team_participation={}",
728 url_for!(req, "contests_or_contest", "categoryorcontestid" => format!("{}", contest_id)),
729 value
730 )).unwrap()))))
731 }
732 } else if let Some(category) = category {
733 Ok(Response::with((status::Found,
734 Redirect(url_for!(req, "contests_or_contest_secret",
735 "categoryorcontestid" => category.to_string(),
736 "contestidorsecret" => format!("{}", contest_id))))))
737 } else {
738 Ok(Response::with((status::Found,
739 Redirect(url_for!(req, "contests_or_contest", "categoryorcontestid" => format!("{}",contest_id))))))
740 }
741}
742
743fn login<C>(req: &mut Request) -> IronResult<Response>
744 where C: MedalConnection + std::marker::Send + 'static {
745 let session_token = req.get_session_token();
746
747 let config = req.get::<Read<SharedConfiguration>>().unwrap();
748 let (template, mut data) = with_conn![core::show_login, C, req, session_token, login_info(&config)];
749
750 let query_string = req.url.query().map(|s| s.to_string());
751 if let Some(query) = query_string {
752 data.insert("forward".to_string(), to_json(&query));
753 }
754
755 let mut resp = Response::new();
757 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
758 Ok(resp)
759}
760
761fn login_post<C>(req: &mut Request) -> IronResult<Response>
762 where C: MedalConnection + std::marker::Send + 'static {
763 let logindata = {
764 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
765 (iexpect!(formdata.get("username"))[0].to_owned(), iexpect!(formdata.get("password"))[0].to_owned())
766 };
767
768 let config = req.get::<Read<SharedConfiguration>>().unwrap();
769 let loginresult = with_conn![core::login, C, req, logindata, login_info(&config)];
771
772 match loginresult {
773 Ok((sessionkey, profilestatus)) => {
775 if let Some(profilestatus) = profilestatus {
776 req.session().set(SessionToken { token: sessionkey }).unwrap();
777 Ok(Response::with((status::Found,
778 Redirect(iron::Url::parse(&format!("{}?status={}",
779 url_for!(req, "myprofile"),
780 profilestatus)).unwrap()))))
781 } else {
782 req.session().set(SessionToken { token: sessionkey }).unwrap();
783 Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
784 }
785 }
786 Err((template, data)) => {
788 let mut resp = Response::new();
789 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
790 Ok(resp)
791 }
792 }
793}
794
795fn login_code_post<C>(req: &mut Request) -> IronResult<Response>
796 where C: MedalConnection + std::marker::Send + 'static {
797 let code = {
798 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
799 iexpect!(formdata.get("code"))[0].to_owned()
800 };
801
802 let config = req.get::<Read<SharedConfiguration>>().unwrap();
803 let loginresult = with_conn![core::login_with_code, C, req, &code, login_info(&config)];
805
806 match loginresult {
807 Ok((sessionkey, profilestatus)) => {
809 if let Some(profilestatus) = profilestatus {
810 req.session().set(SessionToken { token: sessionkey }).unwrap();
811 Ok(Response::with((status::Found,
812 Redirect(iron::Url::parse(&format!("{}?status={}",
813 url_for!(req, "myprofile"),
814 profilestatus)).unwrap()))))
815 } else {
816 req.session().set(SessionToken { token: sessionkey }).unwrap();
817 Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
818 }
819 }
820 Err((template, data)) => {
822 let mut resp = Response::new();
823 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
824 Ok(resp)
825 }
826 }
827}
828
829fn login_webauthn<C>(req: &mut Request) -> IronResult<Response>
830 where C: MedalConnection + std::marker::Send + 'static {
831 let self_url = {
833 let config = req.get::<Read<SharedConfiguration>>().unwrap();
834 config.self_url.clone().ok_or(core::MedalError::WebauthnError).aug(req)?
835 };
836
837 Ok(handle_json_response_simple(with_conn![core::login_with_key_challenge, C, req, &self_url]))
838}
839
840fn login_webauthn_post<C>(req: &mut Request) -> IronResult<Response>
841 where C: MedalConnection + std::marker::Send + 'static {
842 let (auth_id, credential) = {
843 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
844 (iexpect!(formdata.get("id"))[0].parse::<i64>().unwrap_or(0),
845 iexpect!(formdata.get("credential"))[0].to_owned())
846 };
847
848 let self_url = {
850 let config = req.get::<Read<SharedConfiguration>>().unwrap();
851 config.self_url.clone().ok_or(core::MedalError::WebauthnError).aug(req)?
852 };
853
854 let loginresult = with_conn![core::login_with_key, C, req, &self_url, auth_id, &credential];
856
857 match loginresult {
858 Ok((sessionkey, _profilestatus)) => {
860 req.session().set(SessionToken { token: sessionkey }).unwrap();
861
862 let mut resp = Response::new();
863 resp.set_mut("").set_mut(status::Ok);
864 Ok(resp)
865 }
866 Err(msg) => {
868 let mut resp = Response::new();
869 resp.set_mut(msg).set_mut(status::Forbidden);
870 Ok(resp)
871 }
872 }
873}
874
875fn register_webauthn<C>(req: &mut Request) -> IronResult<Response>
876 where C: MedalConnection + std::marker::Send + 'static {
877 let session_token = req.expect_session_token()?;
878
879 let self_url = {
881 let config = req.get::<Read<SharedConfiguration>>().unwrap();
882 config.self_url.clone().ok_or(core::MedalError::WebauthnError).aug(req)?
883 };
884
885 Ok(handle_json_response(with_conn![core::register_key_challenge, C, req, &self_url, &session_token]).aug(req)?)
886}
887
888fn register_webauthn_post<C>(req: &mut Request) -> IronResult<Response>
889 where C: MedalConnection + std::marker::Send + 'static {
890 let session_token = req.expect_session_token()?;
891
892 let (csrf_token, credential, name) = {
893 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
894 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
895 iexpect!(formdata.get("credential"))[0].to_owned(),
896 iexpect!(formdata.get("name"))[0].to_owned())
897 };
898
899 let self_url = {
901 let config = req.get::<Read<SharedConfiguration>>().unwrap();
902 config.self_url.clone().ok_or(core::MedalError::WebauthnError).aug(req)?
903 };
904
905 Ok(handle_json_response(with_conn![core::register_key,
906 C,
907 req,
908 &self_url,
909 &session_token,
910 &csrf_token,
911 credential,
912 name]).aug(req)?)
913}
914
915fn delete_webauthn_post<C>(req: &mut Request) -> IronResult<Response>
916 where C: MedalConnection + std::marker::Send + 'static {
917 let session_token = req.expect_session_token()?;
918 let (csrf_token, token_id) = {
919 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
920 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
921 iexpect!(formdata.get("id"))[0].parse::<i64>().unwrap_or(0))
922 };
923
924 Ok(handle_json_response(with_conn![core::delete_key, C, req, &session_token, &csrf_token, token_id]).aug(req)?)
925}
926
927fn logout<C>(req: &mut Request) -> IronResult<Response>
928 where C: MedalConnection + std::marker::Send + 'static {
929 let session_token = req.get_session_token();
930
931 #[cfg(feature = "debug")]
932 println!("Logging out session {:?}", session_token);
933
934 with_conn![core::logout, C, req, session_token];
935
936 let query_string = req.url.query().map(|s| s.to_string());
937 if let Some(query) = query_string {
938 if query.contains("redirect=login") {
939 return Ok(Response::with((status::Found, Redirect(url_for!(req, "login")))));
940 }
941 }
942
943 Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
944}
945
946#[cfg(feature = "signup")]
947#[allow(clippy::extra_unused_type_parameters)]
948fn signup<C>(req: &mut Request) -> IronResult<Response>
949 where C: MedalConnection + std::marker::Send + 'static {
950 let query_string = req.url.query().map(|s| s.to_string());
951
952 let data = core::signupdata(query_string);
953 let mut resp = Response::new();
954 resp.set_mut(Template::new("signup", data)).set_mut(status::Ok);
955 Ok(resp)
956}
957
958#[cfg(feature = "signup")]
959fn signup_post<C>(req: &mut Request) -> IronResult<Response>
960 where C: MedalConnection + std::marker::Send + 'static {
961 let session_token = req.get_session_token();
962 let signupdata = {
963 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
964 (iexpect!(formdata.get("username"))[0].to_owned(),
965 iexpect!(formdata.get("email"))[0].to_owned(),
966 iexpect!(formdata.get("password"))[0].to_owned())
967 };
968
969 let signupresult = with_conn![core::signup, C, req, session_token, signupdata].aug(req)?;
970 match signupresult {
971 SignupResult::SignedUp => Ok(Response::with((status::Found,
972 Redirect(iron::Url::parse(&format!("{}?status={:?}",
973 url_for!(req,
974 "myprofile"),
975 signupresult)).unwrap())))),
976 _ => Ok(Response::with((status::Found,
977 Redirect(iron::Url::parse(&format!("{}?status={:?}",
978 url_for!(req, "signup"),
979 signupresult)).unwrap())))),
980 }
981}
982
983#[cfg(not(feature = "signup"))]
984#[allow(clippy::extra_unused_type_parameters)]
985fn signup<C>(req: &mut Request) -> IronResult<Response>
986 where C: MedalConnection + std::marker::Send + 'static {
987 Err(core::MedalError::NotFound).aug(req).map_err(|x| x.into())
988}
989
990#[cfg(not(feature = "signup"))]
991#[allow(clippy::extra_unused_type_parameters)]
992fn signup_post<C>(req: &mut Request) -> IronResult<Response>
993 where C: MedalConnection + std::marker::Send + 'static {
994 Err(core::MedalError::NotFound).aug(req).map_err(|x| x.into())
995}
996
997fn submission<C>(req: &mut Request) -> IronResult<Response>
998 where C: MedalConnection + std::marker::Send + 'static {
999 let task_id = req.expect_int::<i64>("taskid")?;
1000
1001 let session_token = req.expect_session_token()?;
1002 let subtask: Option<String> = (|| -> Option<String> {
1003 req.get_ref::<UrlEncodedQuery>().ok()?.get("subtask")?.first().map(|x| x.to_owned())
1004 })();
1005
1006 let submission: Option<i64> = (|| -> Option<i64> {
1007 req.get_ref::<UrlEncodedQuery>().ok()?.get("submission")?.first().and_then(|x| x.parse::<i64>().ok())
1008 })();
1009
1010 let result = with_conn![core::load_submission, C, req, task_id, &session_token, subtask, submission];
1011
1012 match result {
1013 Ok(data) => Ok(Response::with((status::Ok, mime!(Application / Json), data))),
1014 Err(_) => Ok(Response::with((status::BadRequest, mime!(Application / Json), "{}".to_string()))),
1015 }
1016}
1017
1018fn submission_post<C>(req: &mut Request) -> IronResult<Response>
1019 where C: MedalConnection + std::marker::Send + 'static {
1020 let task_id = req.expect_int::<i64>("taskid")?;
1021 let session_token = req.expect_session_token()?;
1022 let (csrf_token, data, grade, autosave, subtask) = {
1023 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
1024 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1025 iexpect!(formdata.get("data"))[0].to_owned(),
1026 formdata.get("grade").map(|x| x[0].parse::<i32>().unwrap_or(0)).unwrap_or(0),
1027 formdata.get("autosave").map(|x| x[0].parse::<bool>().unwrap_or(false)).unwrap_or(false),
1028 formdata.get("subtask").map(|x| x[0].to_owned()))
1029 };
1030
1031 #[cfg(feature = "debug")]
1032 println!("New submission for task {} (graded {}): {}", task_id, grade, data);
1033
1034 let result = with_conn![core::save_submission,
1035 C,
1036 req,
1037 task_id,
1038 &session_token,
1039 &csrf_token,
1040 data,
1041 grade,
1042 autosave,
1043 subtask].aug(req)?;
1044
1045 Ok(Response::with((status::Ok, mime!(Application / Json), result)))
1046}
1047
1048fn task<C>(req: &mut Request) -> IronResult<Response>
1049 where C: MedalConnection + std::marker::Send + 'static {
1050 let task_id = req.expect_int::<i64>("taskid")?;
1051 let session_token = req.require_session_token()?;
1052
1053 let autosaveinterval = {
1055 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1056 config.auto_save_interval.unwrap_or(10)
1057 };
1058
1059 match with_conn![core::show_task, C, req, task_id, &session_token, autosaveinterval].aug(req)? {
1060 Ok((template, data)) => {
1061 let mut resp = Response::new();
1062 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1063 Ok(resp)
1064 }
1065 Err((contest_id, category)) => {
1066 if let Some(category) = category {
1068 Ok(Response::with((status::Found,
1069 Redirect(url_for!(req, "contests_or_contest_secret",
1070 "categoryorcontestid" => category.to_string(),
1071 "contestidorsecret" => format!("{}", contest_id))))))
1072 } else {
1073 Ok(Response::with((status::Found,
1074 Redirect(url_for!(req, "contests_or_contest", "categoryorcontestid" => format!("{}",contest_id))))))
1075 }
1076 }
1077 }
1078}
1079
1080fn review<C>(req: &mut Request) -> IronResult<Response>
1081 where C: MedalConnection + std::marker::Send + 'static {
1082 let task_id = req.expect_int::<i64>("taskid")?;
1083 let submission_id = req.expect_int::<i64>("submissionid")?;
1084 let session_token = req.require_session_token()?;
1085
1086 match with_conn![core::review_task, C, req, task_id, &session_token, submission_id].aug(req)? {
1087 Ok((template, data)) => {
1088 let mut resp = Response::new();
1089 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1090 Ok(resp)
1091 }
1092 Err(contest_id) => {
1093 Ok(Response::with((status::Found,
1095 Redirect(url_for!(req, "contest", "contestid" => format!("{}",contest_id))))))
1096 }
1097 }
1098}
1099
1100fn preview<C>(req: &mut Request) -> IronResult<Response>
1101 where C: MedalConnection + std::marker::Send + 'static {
1102 let task_id = req.expect_int::<i64>("taskid")?;
1103
1104 match with_conn![core::preview_task, C, req, task_id].aug(req)? {
1105 Ok((template, data)) => {
1106 let mut resp = Response::new();
1107 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1108 Ok(resp)
1109 }
1110 Err(contest_id) => {
1111 Ok(Response::with((status::Found,
1113 Redirect(url_for!(req, "contest", "contestid" => format!("{}",contest_id))))))
1114 }
1115 }
1116}
1117
1118fn groups<C>(req: &mut Request) -> IronResult<Response>
1119 where C: MedalConnection + std::marker::Send + 'static {
1120 let session_token = req.require_session_token()?;
1121 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1122
1123 let (template, mut data) = with_conn![core::show_groups, C, req, &session_token].aug(req)?;
1124 data.insert("config".to_string(), to_json(&config.template_params));
1125
1126 let mut resp = Response::new();
1127 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1128 Ok(resp)
1129}
1130
1131fn group_download<C>(req: &mut Request) -> IronResult<Response>
1132 where C: MedalConnection + std::marker::Send + 'static {
1133 let group_id = req.expect_int::<i64>("groupid")?;
1134 let session_token = req.require_session_token()?;
1135
1136 let (template, data) = with_conn![core::show_group, C, req, group_id, &session_token].aug(req)?;
1137
1138 use iron::headers::{Charset, ContentDisposition, DispositionParam, DispositionType};
1139
1140 let cd = ContentDisposition { disposition: DispositionType::Attachment,
1141 parameters: vec![DispositionParam::Filename(
1142 Charset::Ext("Utf-8".to_string()), None, format!("{}.csv", data.get("groupname").unwrap().as_str().unwrap()).as_bytes().to_vec(), )] };
1147
1148 let mime: Mime = "text/csv".parse().unwrap();
1149 let mut resp = Response::new();
1150 resp.headers.set(cd);
1151 resp.set_mut(Template::new(&format!("{}_download", template), data)).set_mut(status::Ok).set_mut(mime);
1152 Ok(resp)
1153}
1154
1155fn new_group<C>(req: &mut Request) -> IronResult<Response>
1156 where C: MedalConnection + std::marker::Send + 'static {
1157 let session_token = req.require_session_token()?;
1158
1159 let (csrf_token, name, tag) = {
1160 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
1161 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1162 iexpect!(formdata.get("name"))[0].to_owned(),
1163 iexpect!(formdata.get("tag"))[0].to_owned())
1164 };
1165
1166 let group_id = with_conn![core::add_group, C, req, &session_token, &csrf_token, name, tag].aug(req)?;
1167
1168 Ok(Response::with((status::Found, Redirect(url_for!(req, "group", "groupid" => format!("{}",group_id))))))
1169}
1170
1171fn group_csv<C>(req: &mut Request) -> IronResult<Response>
1172 where C: MedalConnection + std::marker::Send + 'static {
1173 let session_token = req.require_session_token()?;
1174
1175 let si = {
1176 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1177 core::SexInformation { require_sex: config.require_sex.unwrap_or(false),
1178 allow_sex_na: config.allow_sex_na.unwrap_or(true),
1179 allow_sex_diverse: config.allow_sex_diverse.unwrap_or(false),
1180 allow_sex_other: config.allow_sex_other.unwrap_or(true) }
1181 };
1182
1183 template_ok!(with_conn![core::group_csv, C, req, &session_token, si].aug(req)?)
1184}
1185
1186fn group_csv_upload<C>(req: &mut Request) -> IronResult<Response>
1187 where C: MedalConnection + std::marker::Send + 'static {
1188 let session_token = req.require_session_token()?;
1189
1190 let (csrf_token, group_data) = {
1191 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
1192 (iexpect!(formdata.get("csrf_token"))[0].to_owned(), iexpect!(formdata.get("group_data"))[0].to_owned())
1193 };
1194
1195 #[cfg(feature = "debug")]
1196 println!("{}", group_data);
1197
1198 with_conn![core::upload_groups, C, req, &session_token, &csrf_token, &group_data].aug(req)?;
1199
1200 Ok(Response::with((status::Found, Redirect(url_for!(req, "groups")))))
1201}
1202
1203fn contest_resultcsv<C>(req: &mut Request) -> IronResult<Response>
1204 where C: MedalConnection + std::marker::Send + 'static {
1205 let session_token = req.require_session_token()?;
1206
1207 template_ok!(with_conn![core::contest_result_csv, C, req, &session_token].aug(req)?)
1208}
1209
1210fn contest_resultcsv_upload<C>(req: &mut Request) -> IronResult<Response>
1211 where C: MedalConnection + std::marker::Send + 'static {
1212 let contest_id = req.expect_int::<i64>("contestid")?;
1213 let session_token = req.require_session_token()?;
1214
1215 let (csrf_token, admission_data) = {
1216 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
1217 (iexpect!(formdata.get("csrf_token"))[0].to_owned(), iexpect!(formdata.get("admission_data"))[0].to_owned())
1218 };
1219
1220 #[cfg(feature = "debug")]
1221 println!("{}", admission_data);
1222
1223 with_conn![core::upload_contest_result_csv, C, req, &session_token, &csrf_token, contest_id, &admission_data].aug(req)?;
1224
1225 Ok(Response::with((status::Found,
1226 Redirect(url_for!(req, "admin_contests", "contestid" => format!("{}",contest_id))))))
1227}
1228
1229fn profile<C>(req: &mut Request) -> IronResult<Response>
1230 where C: MedalConnection + std::marker::Send + 'static {
1231 let session_token = req.require_session_token()?;
1232 let query_string = req.url.query().map(|s| s.to_string());
1233
1234 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1235 let si = core::SexInformation { require_sex: config.require_sex.unwrap_or(false),
1236 allow_sex_na: config.allow_sex_na.unwrap_or(true),
1237 allow_sex_diverse: config.allow_sex_diverse.unwrap_or(false),
1238 allow_sex_other: config.allow_sex_other.unwrap_or(true) };
1239
1240 let mut category_names: std::collections::BTreeMap<String, String> = std::collections::BTreeMap::new();
1241 if let Some(template_params) = config.template_params.as_ref() {
1242 if let Some(categories) = template_params.get("categories") {
1243 if let Some(map) = categories.as_object() {
1244 for (key, value) in map {
1246 if let Some(name) = value.get("name") {
1247 if let Some(name) = name.as_str() {
1248 category_names.insert(key.to_string(), name.to_string());
1249 }
1250 }
1251 }
1252 }
1253 }
1254 }
1255
1256 let (template, mut data) =
1257 with_conn![core::show_profile, C, req, &session_token, None, query_string, si, category_names].aug(req)?;
1258 data.insert("config".to_string(), to_json(&config.template_params));
1259
1260 let mut resp = Response::new();
1261 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1262 Ok(resp)
1263}
1264
1265fn profile_post<C>(req: &mut Request) -> IronResult<Response>
1266 where C: MedalConnection + std::marker::Send + 'static {
1267 let session_token = req.expect_session_token()?;
1268 let (csrf_token, firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex) = {
1269 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1270 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1271 iexpect!(formdata.get("firstname"))[0].to_owned(),
1272 iexpect!(formdata.get("lastname"))[0].to_owned(),
1273 formdata.get("street").map(|x| x[0].to_owned()),
1274 formdata.get("zip").map(|x| x[0].to_owned()),
1275 formdata.get("city").map(|x| x[0].to_owned()),
1276 formdata.get("password").map(|x| x[0].to_owned()),
1277 formdata.get("password_repeat").map(|x| x[0].to_owned()),
1278 iexpect!(formdata.get("grade"))[0].parse::<i32>().unwrap_or(0),
1279 iexpect!(formdata.get("sex"))[0].parse::<i32>().ok())
1280 };
1281
1282 let profilechangeresult =
1283 with_conn![core::edit_profile,
1284 C,
1285 req,
1286 &session_token,
1287 None,
1288 &csrf_token,
1289 (firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex)].aug(req)?;
1290
1291 Ok(Response::with((status::Found,
1292 Redirect(iron::Url::parse(&format!("{}?status={:?}",
1293 url_for!(req, "myprofile"),
1294 profilechangeresult)).unwrap()))))
1295}
1296
1297fn profile_check<C>(req: &mut Request) -> IronResult<Response>
1298 where C: MedalConnection + std::marker::Send + 'static {
1299 let session_token = req.expect_session_token()?;
1300 let (csrf_token, firstname, lastname) = {
1301 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1302 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1303 iexpect!(formdata.get("firstname"))[0].to_owned(),
1304 iexpect!(formdata.get("lastname"))[0].to_owned())
1305 };
1306
1307 Ok(handle_json_response(with_conn![core::check_profile,
1308 C,
1309 req,
1310 &session_token,
1311 &csrf_token,
1312 (firstname, lastname)]).aug(req)?)
1313}
1314
1315fn user<C>(req: &mut Request) -> IronResult<Response>
1316 where C: MedalConnection + std::marker::Send + 'static {
1317 let user_id = req.expect_int::<i64>("userid")?;
1318 let session_token = req.expect_session_token()?;
1319 let query_string = req.url.query().map(|s| s.to_string());
1320
1321 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1322 let si = core::SexInformation { require_sex: config.require_sex.unwrap_or(false),
1323 allow_sex_na: config.allow_sex_na.unwrap_or(true),
1324 allow_sex_diverse: config.allow_sex_diverse.unwrap_or(false),
1325 allow_sex_other: config.allow_sex_other.unwrap_or(true) };
1326
1327 let mut category_names: std::collections::BTreeMap<String, String> = std::collections::BTreeMap::new();
1328 if let Some(template_params) = config.template_params.as_ref() {
1329 if let Some(categories) = template_params.get("categories") {
1330 if let Some(map) = categories.as_object() {
1331 for (key, value) in map {
1333 if let Some(name) = value.get("name") {
1334 if let Some(name) = name.as_str() {
1335 category_names.insert(key.to_string(), name.to_string());
1336 }
1337 }
1338 }
1339 }
1340 }
1341 }
1342
1343 let (template, mut data) = with_conn![core::show_profile,
1344 C,
1345 req,
1346 &session_token,
1347 Some(user_id),
1348 query_string,
1349 si,
1350 category_names].aug(req)?;
1351 data.insert("config".to_string(), to_json(&config.template_params));
1352
1353 let mut resp = Response::new();
1354 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1355 Ok(resp)
1356}
1357
1358fn user_post<C>(req: &mut Request) -> IronResult<Response>
1359 where C: MedalConnection + std::marker::Send + 'static {
1360 let user_id = req.expect_int::<i64>("userid")?;
1361 let session_token = req.expect_session_token()?;
1362 let (csrf_token, firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex) = {
1363 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1364 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1365 iexpect!(formdata.get("firstname"))[0].to_owned(),
1366 iexpect!(formdata.get("lastname"))[0].to_owned(),
1367 formdata.get("street").map(|x| x[0].to_owned()),
1368 formdata.get("zip").map(|x| x[0].to_owned()),
1369 formdata.get("city").map(|x| x[0].to_owned()),
1370 formdata.get("password").map(|x| x[0].to_owned()),
1371 formdata.get("password_repeat").map(|x| x[0].to_owned()),
1372 iexpect!(formdata.get("grade"))[0].parse::<i32>().unwrap_or(0),
1373 iexpect!(formdata.get("sex"))[0].parse::<i32>().ok())
1374 };
1375
1376 let profilechangeresult =
1377 with_conn![core::edit_profile,
1378 C,
1379 req,
1380 &session_token,
1381 Some(user_id),
1382 &csrf_token,
1383 (firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex)].aug(req)?;
1384
1385 Ok(Response::with((status::Found,
1386 Redirect(iron::Url::parse(&format!("{}?status={:?}",
1387 url_for!(req, "user", "userid" => format!("{}",user_id)),
1388 profilechangeresult)).unwrap()))))
1389 }
1391
1392fn teacherinfos<C>(req: &mut Request) -> IronResult<Response>
1393 where C: MedalConnection + std::marker::Send + 'static {
1394 let session_token = req.expect_session_token()?;
1395
1396 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1397
1398 let (template, mut data) = with_conn![core::teacher_infos, C, req, &session_token].aug(req)?;
1399 data.insert("config".to_string(), to_json(&config.template_params));
1400
1401 let mut resp = Response::new();
1402 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1403 Ok(resp)
1404}
1405
1406fn admin<C>(req: &mut Request) -> IronResult<Response>
1407 where C: MedalConnection + std::marker::Send + 'static {
1408 let session_token = req.expect_session_token()?;
1409 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1410
1411 let (template, mut data) = with_conn![core::admin_index, C, req, &session_token].aug(req)?;
1412 data.insert("dbstatus_secret".to_string(), to_json(&config.dbstatus_secret));
1413 data.insert("config".to_string(), to_json(&config.template_params));
1414
1415 let mut resp = Response::new();
1416 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1417 Ok(resp)
1418}
1419
1420fn admin_users<C>(req: &mut Request) -> IronResult<Response>
1421 where C: MedalConnection + std::marker::Send + 'static {
1422 let session_token = req.expect_session_token()?;
1423 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1424
1425 let (s_id, s_firstname, s_lastname, s_logincode, s_groupname, s_groupcode, s_pms_id, s_anything) = {
1426 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1427 (formdata.get("id").map(|x| x[0].parse::<i64>().unwrap_or(0)),
1428 formdata.get("firstname").map(|x| x[0].to_owned()),
1429 formdata.get("lastname").map(|x| x[0].to_owned()),
1430 formdata.get("logincode").map(|x| x[0].to_owned()),
1431 formdata.get("groupname").map(|x| x[0].to_owned()),
1432 formdata.get("groupcode").map(|x| x[0].to_owned()),
1433 formdata.get("pmsid").map(|x| x[0].to_owned()),
1434 formdata.get("anything").map(|x| x[0].to_owned()))
1435 };
1436
1437 let (template, mut data) = with_conn![core::admin_search_users,
1438 C,
1439 req,
1440 &session_token,
1441 (s_id,
1442 s_firstname,
1443 s_lastname,
1444 s_logincode,
1445 s_groupname,
1446 s_groupcode,
1447 s_pms_id,
1448 s_anything)].aug(req)?;
1449 data.insert("config".to_string(), to_json(&config.template_params));
1450
1451 let mut resp = Response::new();
1452 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1453 Ok(resp)
1454}
1455
1456fn admin_user<C>(req: &mut Request) -> IronResult<Response>
1457 where C: MedalConnection + std::marker::Send + 'static {
1458 let user_id = req.expect_int::<i64>("userid")?;
1459 let session_token = req.expect_session_token()?;
1460 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1461
1462 let (csrf_token, group_id, additional_time) = if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
1463 (formdata.get("csrf_token").map(|x| x[0].to_owned()),
1465 formdata.get("group_id").map(|x| x[0].parse::<i64>().unwrap_or(0)),
1466 formdata.get("additional_time").map(|x| x[0].parse::<i32>().unwrap_or(0)))
1467 } else {
1468 (None, None, None)
1469 };
1470
1471 if let Some(csrf_token) = csrf_token {
1472 if let Some(group_id) = group_id {
1473 return Ok(handle_json_response(with_conn![core::admin_move_user_to_group,
1474 C,
1475 req,
1476 user_id,
1477 group_id,
1478 &session_token,
1479 &csrf_token]).aug(req)?);
1480 } else if let Some(additional_time) = additional_time {
1481 return Ok(handle_json_response(with_conn![core::admin_set_user_additional_contest_time,
1482 C,
1483 req,
1484 user_id,
1485 additional_time,
1486 &session_token,
1487 &csrf_token]).aug(req)?);
1488 } else {
1489 return Ok(handle_json_response(with_conn![core::admin_delete_user,
1490 C,
1491 req,
1492 user_id,
1493 &session_token,
1494 &csrf_token]).aug(req)?);
1495 }
1496 }
1497
1498 let (template, mut data) = with_conn![core::admin_show_user, C, req, user_id, &session_token].aug(req)?;
1499 data.insert("config".to_string(), to_json(&config.template_params));
1500
1501 let mut resp = Response::new();
1502 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1503 Ok(resp)
1504}
1505
1506fn admin_group<C>(req: &mut Request) -> IronResult<Response>
1507 where C: MedalConnection + std::marker::Send + 'static {
1508 let group_id = req.expect_int::<i64>("groupid")?;
1509 let session_token = req.expect_session_token()?;
1510 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1511
1512 let (csrf_token, set_clearance, clearance_user_id) = if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
1513 (formdata.get("csrf_token").map(|x| x[0].to_owned()),
1514 formdata.get("enable_clearance").map(|x| x[0].parse::<bool>().unwrap_or(false)),
1515 formdata.get("clearance_user_id").map(|x| x[0].parse::<i64>().unwrap_or(0)))
1516 } else {
1517 (None, None, None)
1518 };
1519
1520 if let Some(csrf_token) = csrf_token {
1521 if let Some(set_clearance) = set_clearance {
1522 return Ok(handle_json_response(with_conn![core::admin_set_data_clearance,
1523 C,
1524 req,
1525 group_id,
1526 set_clearance,
1527 clearance_user_id,
1528 &session_token,
1529 &csrf_token]).aug(req)?);
1530 } else {
1531 return Ok(handle_json_response(with_conn![core::admin_delete_group,
1532 C,
1533 req,
1534 group_id,
1535 &session_token,
1536 &csrf_token]).aug(req)?);
1537 }
1538 }
1539
1540 let (template, mut data) = with_conn![core::admin_show_group, C, req, group_id, &session_token].aug(req)?;
1541 data.insert("config".to_string(), to_json(&config.template_params));
1542
1543 let mut resp = Response::new();
1544 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1545 Ok(resp)
1546}
1547
1548fn admin_group_edit<C>(req: &mut Request) -> IronResult<Response>
1549 where C: MedalConnection + std::marker::Send + 'static {
1550 let group_id = req.expect_int::<i64>("groupid")?;
1551 let session_token = req.expect_session_token()?;
1552 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1553
1554 if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
1555 let csrf_token = iexpect!(formdata.get("csrf_token"))[0].to_owned();
1556 let name = iexpect!(formdata.get("new_groupname"))[0].to_owned();
1557 let tag = iexpect!(formdata.get("new_grouptag"))[0].to_owned();
1558
1559 with_conn![core::admin_edit_group, C, req, group_id, &session_token, &csrf_token, name, tag].aug(req)?;
1560
1561 Ok(Response::with((status::Found, Redirect(url_for!(req, "group", "groupid" => format!("{}",group_id))))))
1562 } else {
1563 let (template, mut data) = with_conn![core::admin_show_edit_group, C, req, group_id, &session_token].aug(req)?;
1564
1565 data.insert("config".to_string(), to_json(&config.template_params));
1566
1567 let mut resp = Response::new();
1568 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1569 Ok(resp)
1570 }
1571}
1572
1573fn group_addadmin<C>(req: &mut Request) -> IronResult<Response>
1574 where C: MedalConnection + std::marker::Send + 'static {
1575 let group_id = req.expect_int::<i64>("groupid")?;
1576 let session_token = req.expect_session_token()?;
1577
1578 let (csrf_token, teacher_id) = {
1579 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
1580 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1581 iexpect!(formdata.get("teacherid").unwrap_or(&vec!["".to_owned()])[0].parse::<i64>().ok()))
1582 };
1583
1584 Ok(handle_json_response(with_conn![core::group_add_admin,
1585 C,
1586 req,
1587 group_id,
1588 teacher_id,
1589 &session_token,
1590 &csrf_token]).aug(req)?)
1591}
1592
1593fn group_deladmin<C>(req: &mut Request) -> IronResult<Response>
1594 where C: MedalConnection + std::marker::Send + 'static {
1595 let group_id = req.expect_int::<i64>("groupid")?;
1596 let user_id = req.expect_int::<i64>("userid")?;
1597 let session_token = req.expect_session_token()?;
1598
1599 let csrf_token = {
1600 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
1601 iexpect!(formdata.get("csrf_token"))[0].to_owned()
1602 };
1603
1604 Ok(handle_json_response(with_conn![core::group_delete_admin,
1605 C,
1606 req,
1607 group_id,
1608 user_id,
1609 &session_token,
1610 &csrf_token]).aug(req)?)
1611}
1612
1613fn admin_participation<C>(req: &mut Request) -> IronResult<Response>
1614 where C: MedalConnection + std::marker::Send + 'static {
1615 let user_id = req.expect_int::<i64>("userid")?;
1616 let contest_id = req.expect_int::<i64>("contestid")?;
1617 let session_token = req.expect_session_token()?;
1618 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1619
1620 let csrf_token = if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
1621 formdata.get("csrf_token").map(|x| x[0].to_owned())
1622 } else {
1623 None
1624 };
1625
1626 if let Some(csrf_token) = csrf_token {
1627 return Ok(handle_json_response(with_conn![core::admin_delete_participation,
1628 C,
1629 req,
1630 user_id,
1631 contest_id,
1632 &session_token,
1633 &csrf_token]).aug(req)?);
1634 }
1635
1636 let (template, mut data) =
1637 with_conn![core::admin_show_participation, C, req, user_id, contest_id, &session_token].aug(req)?;
1638 data.insert("config".to_string(), to_json(&config.template_params));
1639
1640 let mut resp = Response::new();
1641 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1642 Ok(resp)
1643}
1644
1645fn admin_contests<C>(req: &mut Request) -> IronResult<Response>
1646 where C: MedalConnection + std::marker::Send + 'static {
1647 let session_token = req.expect_session_token()?;
1648 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1649
1650 let (template, mut data) = with_conn![core::admin_show_contests, C, req, &session_token].aug(req)?;
1651 data.insert("config".to_string(), to_json(&config.template_params));
1652
1653 let mut resp = Response::new();
1654 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1655 Ok(resp)
1656}
1657
1658fn admin_export_contest<C>(req: &mut Request) -> IronResult<Response>
1659 where C: MedalConnection + std::marker::Send + 'static {
1660 let contest_id = req.expect_int::<i64>("contestid")?;
1661 let session_token = req.expect_session_token()?;
1662
1663 let filename = with_conn![core::admin_contest_export, C, req, contest_id, &session_token].aug(req)?;
1664
1665 Ok(Response::with((status::Found, RedirectRaw(format!("/export/{}", filename)))))
1666}
1667
1668fn admin_cleanup<C>(req: &mut Request) -> IronResult<Response>
1669 where C: MedalConnection + std::marker::Send + 'static {
1670 let session_token = req.expect_session_token()?;
1671 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1672
1673 let csrf_token = if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
1674 formdata.get("csrf_token").map(|x| x[0].to_owned())
1675 } else {
1676 None
1677 };
1678
1679 if let Some(csrf_token) = csrf_token {
1680 let cleanup_type = req.get_str("type");
1681
1682 return Ok(handle_json_response(match cleanup_type.as_deref() {
1683 Some("session") => with_conn![core::do_session_cleanup, C, req,],
1684 _ => with_conn![core::admin_do_cleanup, C, req, &session_token, &csrf_token],
1685 }).aug(req)?);
1686 }
1687
1688 let (template, mut data) = with_conn![core::admin_show_cleanup, C, req, &session_token].aug(req)?;
1689 data.insert("config".to_string(), to_json(&config.template_params));
1690
1691 let mut resp = Response::new();
1692 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1693 Ok(resp)
1694}
1695
1696fn admin_housekeeping_index<C>(req: &mut Request) -> IronResult<Response>
1697 where C: MedalConnection + std::marker::Send + 'static {
1698 let session_token = req.expect_session_token()?;
1699 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1700
1701 let (template, mut data) = with_conn![core::housekeeping, C, req, &session_token].aug(req)?;
1702 data.insert("config".to_string(), to_json(&config.template_params));
1703 data.insert("self_url".to_string(), to_json(&config.self_url));
1704
1705 use iron::headers::Cookie;
1706 if let Some(&Cookie(ref cookies)) = req.headers.get::<Cookie>() {
1707 for cookie in cookies {
1708 if let Some(session_token) = cookie.strip_prefix("medal_session=") {
1709 data.insert("session_token_cookie".to_string(), to_json(&session_token));
1710 }
1711 }
1712 }
1713
1714 let mut resp = Response::new();
1715 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1716 Ok(resp)
1717}
1718
1719fn admin_move_task_location<C>(req: &mut Request) -> IronResult<Response>
1724 where C: MedalConnection + std::marker::Send + 'static {
1725 let session_token = req.expect_session_token()?;
1726
1727 let (csrf_token, old_location, new_location, contest) = {
1728 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1729 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1730 iexpect!(formdata.get("old_task_location"))[0].to_owned(),
1731 iexpect!(formdata.get("new_task_location"))[0].to_owned(),
1732 iexpect!(formdata.get("contest_location"))[0].to_owned())
1733 };
1734
1735 Ok(handle_json_response(with_conn![core::move_task_location,
1736 C,
1737 req,
1738 &session_token,
1739 &csrf_token,
1740 &old_location,
1741 &new_location,
1742 &contest]).aug(req)?)
1743}
1744
1745fn admin_move_task_contest<C>(req: &mut Request) -> IronResult<Response>
1750 where C: MedalConnection + std::marker::Send + 'static {
1751 let session_token = req.expect_session_token()?;
1752
1753 let (csrf_token, old_location, new_location, old_contest, new_contest) = {
1754 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1755 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1756 iexpect!(formdata.get("old_task_location"))[0].to_owned(),
1757 iexpect!(formdata.get("new_task_location"))[0].to_owned(),
1758 iexpect!(formdata.get("old_contest_location"))[0].to_owned(),
1759 iexpect!(formdata.get("new_contest_location"))[0].to_owned())
1760 };
1761
1762 Ok(handle_json_response(with_conn![core::move_task_contest,
1763 C,
1764 req,
1765 &session_token,
1766 &csrf_token,
1767 &old_location,
1768 &new_location,
1769 &old_contest,
1770 &new_contest]).aug(req)?)
1771}
1772
1773fn admin_move_contest_location<C>(req: &mut Request) -> IronResult<Response>
1778 where C: MedalConnection + std::marker::Send + 'static {
1779 let session_token = req.expect_session_token()?;
1780
1781 let (csrf_token, old_location, new_location) = {
1782 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1783 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1784 iexpect!(formdata.get("old_contest_location"))[0].to_owned(),
1785 iexpect!(formdata.get("new_contest_location"))[0].to_owned())
1786 };
1787
1788 Ok(handle_json_response(with_conn![core::move_contest_location,
1789 C,
1790 req,
1791 &session_token,
1792 &csrf_token,
1793 &old_location,
1794 &new_location]).aug(req)?)
1795}
1796
1797fn admin_move_task_submissions_grades<C>(req: &mut Request) -> IronResult<Response>
1802 where C: MedalConnection + std::marker::Send + 'static {
1803 let session_token = req.expect_session_token()?;
1804
1805 let (csrf_token, old_location, new_location, old_contest, new_contest) = {
1806 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1807 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1808 iexpect!(formdata.get("old_task_location"))[0].to_owned(),
1809 iexpect!(formdata.get("new_task_location"))[0].to_owned(),
1810 iexpect!(formdata.get("old_contest_location"))[0].to_owned(),
1811 iexpect!(formdata.get("new_contest_location"))[0].to_owned())
1812 };
1813
1814 Ok(handle_json_response(with_conn![core::move_task_submissions_grades,
1815 C,
1816 req,
1817 &session_token,
1818 &csrf_token,
1819 &old_location,
1820 &new_location,
1821 &old_contest,
1822 &new_contest]).aug(req)?)
1823}
1824
1825fn dbcleanup<C>(req: &mut Request) -> IronResult<Response>
1826 where C: MedalConnection + std::marker::Send + 'static {
1827 Ok(handle_json_response(with_conn![core::do_session_cleanup, C, req,]).aug(req)?)
1828}
1829
1830fn oauth<C>(req: &mut Request) -> IronResult<Response>
1831 where C: MedalConnection + std::marker::Send + 'static {
1832 #[cfg(feature = "debug")]
1833 println!("{:?}", req.url.query().unwrap_or(""));
1834
1835 let oauth_id = req.expect_str("oauthid")?;
1836 let school_id = req.get_str("schoolid");
1837
1838 let (oauth_provider, autoclean_submissions) = {
1839 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1840
1841 let mut result: Option<OauthProvider> = None;
1842
1843 if let Some(ref oauth_providers) = config.oauth_providers {
1844 for oauth_provider in oauth_providers {
1845 if oauth_provider.provider_id == oauth_id {
1846 result = Some(oauth_provider.clone());
1847 break;
1848 }
1849 }
1850
1851 if let Some(result) = result {
1852 (result, config.autoclean_submissions.unwrap_or(false))
1853 } else {
1854 return Ok(Response::with(iron::status::NotFound));
1855 }
1856 } else {
1857 return Ok(Response::with(iron::status::NotFound));
1858 }
1859 };
1860
1861 let user_data_result = match oauth_provider.medal_oauth_type.as_ref() {
1862 "pms" => oauth_pms(req, oauth_provider, school_id.as_ref()).aug(req)?,
1863 _ => return Ok(Response::with(iron::status::NotFound)),
1864 };
1865
1866 let user_data = match user_data_result {
1867 Err(response) => return Ok(response),
1868 Ok(user_data) => user_data,
1869 };
1870 let user_type = user_data.foreign_type;
1871
1872 let oauthloginresult = {
1873 let mutex = req.get::<Write<SharedDatabaseConnection<C>>>().unwrap();
1875 let conn = mutex.lock().unwrap_or_else(|e| e.into_inner());
1876
1877 core::login_oauth(&*conn, user_data, oauth_id, autoclean_submissions)
1879 };
1880
1881 match oauthloginresult {
1882 Ok((sessionkey, redirectprofile)) => {
1884 req.session().set(SessionToken { token: sessionkey }).unwrap();
1885
1886 use core::UserType;
1887 if user_type == UserType::User {
1888 if let Some(redirectprofile) = redirectprofile {
1889 Ok(Response::with((status::Found,
1890 Redirect(iron::Url::parse(&format!("{}?status={}",
1891 url_for!(req, "myprofile"),
1892 redirectprofile)).unwrap()))))
1893 } else {
1894 Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
1895 }
1896 } else {
1897 Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
1898 }
1899 }
1900 Err((template, data)) => {
1902 let mut resp = Response::new();
1903 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1904 Ok(resp)
1905 }
1906 }
1907}
1908
1909#[derive(Deserialize, Debug)]
1910struct OAuthAccess {
1911 access_token: String,
1912 #[allow(dead_code)]
1913 token_type: String,
1914 #[allow(dead_code)]
1915 refresh_token: String,
1916 #[allow(dead_code)]
1917 expires: Option<i32>, #[allow(dead_code)]
1919 expires_in: Option<i32>, }
1921
1922#[derive(Deserialize, Debug)]
1923#[allow(non_snake_case)]
1924#[serde(untagged)]
1925pub enum SchoolIdOrSchoolIds {
1926 #[allow(dead_code)]
1927 None(i32), #[allow(dead_code)]
1929 SchoolId(String),
1930 SchoolIds(Vec<String>),
1931}
1932
1933#[derive(Deserialize, Debug)]
1934#[allow(non_snake_case)]
1935pub struct OAuthUserData {
1936 userID: Option<String>, userId: Option<String>, userType: String,
1939 gender: String,
1940 firstName: String,
1941 lastName: String,
1942 #[allow(dead_code)]
1943 dateOfBirth: Option<String>,
1944 #[allow(dead_code)]
1945 eMail: Option<String>,
1946 schoolId: Option<SchoolIdOrSchoolIds>,
1947}
1948
1949#[derive(Deserialize, Debug)]
1950#[allow(non_snake_case)]
1951pub struct OAuthSchoolData {
1952 name: Option<String>,
1953 city: Option<String>,
1954 error: Option<String>,
1955}
1956
1957fn pms_hash_school(school_id: &str, secret: &str) -> String {
1958 use sha2::{Digest, Sha512};
1959 let mut hasher = Sha512::default();
1960 let string_to_hash = format!("{}{}", school_id, secret);
1961
1962 hasher.input(string_to_hash.as_bytes());
1963 let hashed_string = hasher.result();
1964
1965 format!("{:02X?}", hashed_string).chars().filter(|c| c.is_ascii_alphanumeric()).collect()
1966}
1967
1968fn oauth_pms(req: &mut Request, oauth_provider: OauthProvider, selected_school_id: Option<&String>)
1969 -> Result<Result<core::ForeignUserData, Response>, core::MedalError> {
1970 use core::{UserSex, UserType};
1971 use params::{Params, Value};
1972
1973 fn er(e: &str) -> core::MedalError { core::MedalError::OauthError(e.to_string()) }
1974 fn e<T>(e: &str) -> Result<T, core::MedalError> { Err::<T, _>(er(e)) }
1975
1976 let (_, _, code): (String, String, String) = {
1977 let map = req.get_ref::<Params>().unwrap();
1978
1979 match (map.find(&["state"]), map.find(&["scope"]), map.find(&["code"])) {
1980 (Some(&Value::String(ref state)), Some(&Value::String(ref scope)), Some(&Value::String(ref code)))
1981 if state == "42" =>
1982 {
1983 (state.clone(), scope.clone(), code.clone())
1984 }
1985 _ => return e("#70"),
1986 }
1987 };
1988
1989 let client = reqwest::Client::new();
1990 let params = [("code", code), ("grant_type", "authorization_code".to_string())];
1991
1992 let res = client.post(&oauth_provider.access_token_url)
1994 .basic_auth(oauth_provider.client_id, Some(oauth_provider.client_secret))
1995 .form(¶ms)
1996 .send();
1997 let access: OAuthAccess = res.or(e("#00"))?.json().or(e("#01"))?;
1998
1999 let res = client.get(&oauth_provider.user_data_url).bearer_auth(access.access_token).send();
2000 let mut user_data: OAuthUserData = res.or(e("#10"))?.json().or(e("#11"))?;
2001
2002 user_data.userId = user_data.userID.or(user_data.userId);
2004
2005 let user_type = match user_data.userType.as_ref() {
2006 "a" | "A" => UserType::Admin,
2007 "t" | "T" => UserType::Teacher,
2008 "s" | "S" => UserType::User,
2009 _ => UserType::User,
2010 };
2011 let user_sex = match user_data.gender.as_ref() {
2012 "m" | "M" => UserSex::Male,
2013 "f" | "F" | "w" | "W" => UserSex::Female,
2014 "?" => UserSex::Unknown,
2015 _ => UserSex::Unknown,
2016 };
2017 let mut school_name: Option<String> = None;
2018
2019 match (&user_data.schoolId, user_type) {
2020 (Some(SchoolIdOrSchoolIds::SchoolIds(_)), UserType::User) => return e("#70"),
2022 (Some(SchoolIdOrSchoolIds::SchoolId(_)), UserType::Teacher) => return e("#71"),
2026 (Some(SchoolIdOrSchoolIds::None(_)), UserType::Teacher) => return e("#72"),
2027 (None, UserType::Teacher) => {
2029 user_data.schoolId = Some(SchoolIdOrSchoolIds::SchoolIds(Vec::new()));
2030 }
2031
2032 _ => (),
2034 }
2035
2036 if let Some(SchoolIdOrSchoolIds::SchoolIds(school_ids)) = user_data.schoolId {
2038 if let Some(selected_school_id) = selected_school_id {
2040 if selected_school_id == "none" && oauth_provider.allow_teacher_login_without_school == Some(true) {
2041 }
2043 else if school_ids.contains(&selected_school_id) {
2045 if let Some(mut user_id) = user_data.userId {
2046 user_id.push('/');
2047 user_id.push_str(&selected_school_id);
2048 user_data.userId = Some(user_id);
2049
2050 if let (Some(school_data_url), Some(school_data_secret)) =
2053 (oauth_provider.school_data_url, oauth_provider.school_data_secret)
2054 {
2055 let params = [("schoolId", selected_school_id.clone()),
2056 ("hash", pms_hash_school(&selected_school_id, &school_data_secret))];
2057 let res = client.post(&school_data_url).form(¶ms).send();
2058 if let Ok(mut json) = res {
2059 let school_data_opt: Result<OAuthSchoolData, reqwest::Error> = json.json();
2060 if let Ok(school_data) = school_data_opt {
2061 school_name = school_data.name;
2062 }
2063 }
2064 }
2065 }
2066 } else {
2067 return e("#40");
2068 }
2069 } else {
2070 if let (Some(school_data_url), Some(school_data_secret)) =
2073 (oauth_provider.school_data_url, oauth_provider.school_data_secret)
2074 {
2075 let school_infos: Vec<(String, String)> =
2077 school_ids.iter()
2078 .map(|school_id| -> Result<(String, String), core::MedalError> {
2079 let params = [("schoolId", school_id.clone()),
2080 ("hash", pms_hash_school(&school_id, &school_data_secret))];
2081 let res = client.post(&school_data_url).form(¶ms).send();
2082 let school_data: OAuthSchoolData = res.or(e("#30"))?.json().or(e("#31"))?;
2083
2084 Ok((school_id.clone(),
2085 format!("{}, {}",
2086 school_data.name
2087 .or(school_data.error)
2088 .unwrap_or_else(|| "Information missing".to_string()),
2089 school_data.city.unwrap_or_else(|| "–".to_string()))))
2090 })
2091 .collect::<Result<_, _>>()?;
2092
2093 let mut data = json_val::Map::new();
2094 data.insert("schools".to_string(), to_json(&school_infos));
2095 data.insert("query".to_string(), to_json(&req.url.query().unwrap_or("")));
2096
2097 data.insert("parent".to_string(), to_json(&"base"));
2098 data.insert("disable_login_box".to_string(), to_json(&true));
2099
2100 data.insert("teacher_login_without_school".to_string(),
2101 to_json(&oauth_provider.allow_teacher_login_without_school.unwrap_or(false)));
2102
2103 let mut resp = Response::new();
2104 resp.set_mut(Template::new(&"oauth_school_selector", data)).set_mut(status::Ok);
2105 return Ok(Err(resp));
2106 } else {
2107 return Err(core::MedalError::ConfigurationError);
2109 }
2110 }
2111 } else if selected_school_id.is_some() {
2112 return e("#50");
2114 }
2115
2116 Ok(Ok(core::ForeignUserData { foreign_id: user_data.userId.ok_or(er("#60"))?,
2117 foreign_type: user_type,
2118 sex: user_sex,
2119 firstname: user_data.firstName,
2120 lastname: user_data.lastName,
2121 school_name }))
2122}
2123
2124#[derive(Copy, Clone)]
2126pub struct SharedDatabaseConnection<C>
2127 where C: MedalConnection
2128{
2129 phantom: std::marker::PhantomData<C>,
2130}
2131impl<C> Key for SharedDatabaseConnection<C> where C: MedalConnection + 'static
2132{
2133 type Value = C;
2134}
2135
2136#[derive(Copy, Clone)]
2138pub struct SharedConfiguration;
2139impl Key for SharedConfiguration {
2140 type Value = Config;
2141}
2142
2143#[cfg(feature = "watch")]
2144pub fn get_handlebars_engine(template_name: &str) -> impl AfterMiddleware {
2145 let mut hbse = HandlebarsEngine::new();
2147 hbse.add(Box::new(DirectorySource::new(&format!("./templates/{}/", template_name) as &str, ".hbs")));
2148
2149 if let Err(r) = hbse.reload() {
2151 panic!("{}", r);
2152 }
2153
2154 use handlebars_iron::Watchable;
2155 use std::sync::Arc;
2156
2157 let hbse_ref = Arc::new(hbse);
2158 hbse_ref.watch("./templates/");
2159 hbse_ref
2160}
2161
2162#[cfg(not(feature = "watch"))]
2163pub fn get_handlebars_engine(template_name: &str) -> impl AfterMiddleware {
2164 let mut hbse = HandlebarsEngine::new();
2166 hbse.add(Box::new(DirectorySource::new(&format!("./templates/{}/", template_name) as &str, ".hbs")));
2167
2168 if let Err(r) = hbse.reload() {
2170 panic!("{}", r);
2171 }
2172
2173 hbse
2174}
2175
2176fn cookie_warning(req: &mut Request) -> IronResult<Response> {
2177 match req.get_session_token() {
2178 Some(_session_token) => {
2179 Ok(Response::with((status::Found, RedirectRaw(format!("/{}", req.url.query().unwrap_or(""))))))
2182 }
2183 None => {
2184 let mut resp = Response::new();
2185 resp.set_mut(Template::new("cookie", json_val::Map::new())).set_mut(status::Ok);
2186 Ok(resp)
2187 }
2188 }
2189}
2190
2191pub fn start_server<C>(conn: C, config: Config) -> iron::error::HttpResult<iron::Listening>
2192 where C: MedalConnection + std::marker::Send + 'static {
2193 let router = router!(
2194 greet: get "/" => greet_personal::<C>,
2195 contests: get "/contest/" => contests::<C>,
2196 contests_or_contest: get "/contest/:categoryorcontestid" => contests_or_contest::<C>,
2197 contests_or_contest_secret: get "/contest/:categoryorcontestid/:contestidorsecret" => contests_or_contest::<C>,
2198 contest_secret: get "/contest/:category/:contestid/:secret" => contest::<C>,
2199 contestresults: get "/contest/:contestid/result/" => contestresults::<C>,
2200 contestresults_download: get "/contest/:contestid/result/download" => contestresults_download::<C>,
2201 contestresults_category: get "/contest/:category/:contestid/result/" => contestresults::<C>,
2202 contestresults_category_download: get "/contest/:category/:contestid/result/download" => contestresults_download::<C>,
2203 contest_post: post "/contest/:contestid" => contest_post::<C>,
2204 contest_post_maybesecret: post "/contest/:categoryorcontestid/:contestidorsecret" => contest_post::<C>, contest_post_secret: post "/contest/:category/:contestid/:secret" => contest_post::<C>, login: get "/login" => login::<C>,
2207 login_post: post "/login" => login_post::<C>,
2208 login_code_post: post "/clogin" => login_code_post::<C>,
2209 login_webauthn: get "/webauthn_login" => login_webauthn::<C>,
2210 login_webauthn_post: post "/webauthn_login" => login_webauthn_post::<C>,
2211 register_webauthn: get "/webauthn_register" => register_webauthn::<C>,
2212 register_webauthn_post: post "/webauthn_register" => register_webauthn_post::<C>,
2213 delete_webauthn_post: post "/webauthn_delete" => delete_webauthn_post::<C>,
2214 logout: get "/logout" => logout::<C>,
2215 signup: get "/signup" => signup::<C>,
2216 signup_post: post "/signup" => signup_post::<C>,
2217 subm_load: get "/load/:taskid" => submission::<C>,
2218 subm_save: post "/save/:taskid" => submission_post::<C>,
2219 groups: get "/group/" => groups::<C>,
2220 groups: post "/group/" => new_group::<C>,
2221 group: get "/group/:groupid" => admin_group::<C>,
2222 group_post: post "/group/:groupid" => admin_group::<C>,
2223 group_edit: get "/group/:groupid/edit" => admin_group_edit::<C>,
2224 group_edit_post: post "/group/:groupid/edit" => admin_group_edit::<C>,
2225 group_addadmin: post "/group/:groupid/addadmin" => group_addadmin::<C>,
2226 group_deladmin: post "/group/:groupid/deladmin/:userid" => group_deladmin::<C>,
2227 group_download: get "/group/download/:groupid" => group_download::<C>,
2228 groupcsv: get "/group/csv" => group_csv::<C>,
2230 groupcsv_post: post "/group/csv" => group_csv_upload::<C>,
2231 myprofile: get "/profile" => profile::<C>,
2232 myprofile_post: post "/profile" => profile_post::<C>,
2233 myprofile_check: post "/profile/check" => profile_check::<C>,
2234 user: get "/user/:userid" => admin_user::<C>,
2235 user_post: post "/user/:userid" => admin_user::<C>,
2236 profile: get "/profile/:userid" => user::<C>,
2237 profile_post: post "/profile/:userid" => user_post::<C>,
2238 task: get "/task/:taskid" => task::<C>,
2239 task_review_solution: get "/task/:taskid/:submissionid" => review::<C>,
2240 preview_task: get "/preview/:taskid" => preview::<C>,
2241 teacher: get "/teacher" => teacherinfos::<C>,
2242 admin: get "/admin" => admin::<C>,
2243 admin_users: post "/admin/user/" => admin_users::<C>,
2244 admin_user: get "/admin/user/:userid" => admin_user::<C>,
2245 admin_user_post: post "/admin/user/:userid" => admin_user::<C>,
2246 admin_group: get "/admin/group/:groupid" => admin_group::<C>,
2247 admin_group_post: post "/admin/group/:groupid" => admin_group::<C>,
2248 admin_group_edit: get "/admin/group/:groupid/edit" => admin_group_edit::<C>,
2249 admin_group_edit_post: post "/admin/group/:groupid/edit" => admin_group_edit::<C>,
2250 admin_participation: get "/admin/user/:userid/:contestid" => admin_participation::<C>,
2251 admin_participation_post: post "/admin/user/:userid/:contestid" => admin_participation::<C>,
2252 admin_contests: get "/admin/contest/" => admin_contests::<C>,
2253 admin_contest_admissioncsv: get "/admin/contest/:contestid/csv" => contest_resultcsv::<C>,
2254 admin_contest_admissioncsv_post: post "/admin/contest/:contestid/csv" => contest_resultcsv_upload::<C>,
2255 admin_export_contest: get "/admin/contest/:contestid/export" => admin_export_contest::<C>,
2256 admin_cleanup: get "/admin/cleanup" => admin_cleanup::<C>,
2257 admin_cleanup_post: post "/admin/cleanup/:type" => admin_cleanup::<C>,
2258 admin_housekeeping_index: get "/admin/housekeeping" => admin_housekeeping_index::<C>,
2259 admin_move_task_location_post: post "/admin/housekeeping/move_task_location" => admin_move_task_location::<C>,
2260 admin_move_task_contest_post: post "/admin/housekeeping/move_task_contest" => admin_move_task_contest::<C>,
2261 admin_move_contest_location_post: post "/admin/housekeeping/move_contest_location" => admin_move_contest_location::<C>,
2262 admin_move_task_submissions_grades_post: post "/admin/housekeeping/move_task_submissions_grades" => admin_move_task_submissions_grades::<C>,
2263 oauth: get "/oauth/:oauthid/" => oauth::<C>,
2264 oauth_school: get "/oauth/:oauthid/:schoolid" => oauth::<C>,
2265 check_cookie: get "/cookie" => cookie_warning,
2266 dbstatus: get "/dbstatus" => dbstatus::<C>,
2267 status: get "/status" => dbstatus::<C>,
2268 dbcleanup: get "/cleanup" => dbcleanup::<C>,
2269 debug: get "/debug" => debug::<C>,
2270 debug_reset: get "/debug/reset" => debug_new_token::<C>,
2271 debug_logout: get "/debug/logout" => debug_logout::<C>,
2272 debug_create: get "/debug/create" => debug_create_session::<C>,
2273 );
2274
2275 let mut mount = Mount::new();
2276
2277 mount.mount("/static/", Static::new(Path::new("static")));
2279 mount.mount("/export/", Static::new(Path::new("export")));
2280 mount.mount("/tasks/", Static::new(Path::new(TASK_DIR)));
2281 mount.mount("/", router);
2282
2283 let mut ch = Chain::new(mount);
2284
2285 #[cfg(feature = "debug")]
2286 ch.link_before(RequestLogger {});
2287
2288 ch.link(Write::<SharedDatabaseConnection<C>>::both(conn));
2289 ch.link(Read::<SharedConfiguration>::both(config.clone()));
2290
2291 ch.link_around(RequestTimeLogger {});
2292 ch.link_around(CookieDistributor {});
2293 ch.link_around(SessionStorage::new(SignedCookieBackend::new(config.cookie_signing_secret.expect("Cookie signing secret not found in configuration").into_bytes())));
2294
2295 ch.link_after(get_handlebars_engine(&config.template.unwrap_or_else(|| "default".to_string())));
2296 ch.link_after(ErrorReporter);
2297 ch.link_after(ErrorShower);
2298
2299 let socket_addr = format!("{}:{}", config.host.unwrap(), config.port.unwrap());
2300
2301 let srvr = Iron::new(ch).http(&socket_addr);
2302 print!("Listening on {} … ", socket_addr);
2303 srvr
2304}