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 core::MedalError::InvalidGrade => {
361 IronError { error: Box::new(SessionError { message: "Invalid grade".to_string() }),
362 response: Response::with(status::Forbidden) }
363 },
364 }
365 }
366}
367
368trait RequestAugmentMedalError<'c, 'a: 'c, 'b: 'c + 'a, R> {
369 fn aug(self, req: &'c mut Request<'a, 'b>) -> Result<R, AugMedalError<'c, 'a, 'b>>;
370}
371impl<'c, 'a: 'c, 'b: 'c + 'a, T> RequestAugmentMedalError<'c, 'a, 'b, T> for Result<T, core::MedalError> {
372 fn aug(self, req: &'c mut Request<'a, 'b>) -> Result<T, AugMedalError<'c, 'a, 'b>> {
373 self.map_err(move |me| AugMedalError(me, req))
374 }
375}
376
377fn handle_json_response_simple(jsonval: json_val::Map<String, json_val::Value>) -> Response {
378 Response::with((status::Ok, mime!(Application / Json), serde_json::json!(jsonval).to_string()))
379}
380
381fn handle_json_response<T>(jsonvalresult: Result<json_val::Map<String, json_val::Value>, T>) -> Result<Response, T> {
382 Ok(handle_json_response_simple(jsonvalresult?))
383}
384
385fn login_info(config: &Config) -> core::LoginInfo {
386 core::LoginInfo { password_login: config.enable_password_login == Some(true),
387 self_url: config.self_url.clone(),
388 oauth_providers: config.oauth_providers.clone() }
389}
390
391fn greet_personal<C>(req: &mut Request) -> IronResult<Response>
392 where C: MedalConnection + std::marker::Send + 'static {
393 let session_token = req.get_session_token();
394 let config = req.get::<Read<SharedConfiguration>>().unwrap();
395
396 let (template, mut data) = with_conn![core::index, C, req, session_token, login_info(&config)].aug(req)?;
397 data.insert("config".to_string(), to_json(&config.template_params));
398
399 let mut resp = Response::new();
400 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
401 Ok(resp)
402}
403
404fn dbstatus<C>(req: &mut Request) -> IronResult<Response>
405 where C: MedalConnection + std::marker::Send + 'static {
406 let config = req.get::<Read<SharedConfiguration>>().unwrap();
407 let query_string = req.url.query().map(|s| s.to_string());
408
409 let status = with_conn![core::status, C, req, config.dbstatus_secret.clone(), query_string].aug(req)?;
410
411 let mut resp = Response::new();
412 resp.set_mut(status).set_mut(status::Ok);
413 Ok(resp)
414}
415
416fn debug<C>(req: &mut Request) -> IronResult<Response>
417 where C: MedalConnection + std::marker::Send + 'static {
418 let session_token = req.get_session_token();
419
420 let (template, data) = with_conn![core::debug, C, req, session_token];
421
422 let mut resp = Response::new();
423 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
424 Ok(resp)
425}
426
427fn debug_new_token<C>(req: &mut Request) -> IronResult<Response>
428 where C: MedalConnection + std::marker::Send + 'static {
429 let session_token = req.get_session_token();
430
431 #[cfg(feature = "debug")]
432 println!("Logging out session {:?}", session_token);
433
434 with_conn![core::logout, C, req, session_token];
435
436 Ok(Response::with((status::Found, Redirect(url_for!(req, "debug")))))
437}
438
439fn debug_logout<C>(req: &mut Request) -> IronResult<Response>
440 where C: MedalConnection + std::marker::Send + 'static {
441 let session_token = req.get_session_token();
442
443 #[cfg(feature = "debug")]
444 println!("Logging out session {:?}", session_token);
445
446 with_conn![core::logout, C, req, session_token];
447
448 Ok(Response::with((status::Found, Redirect(url_for!(req, "debug")))))
449}
450
451fn debug_create_session<C>(req: &mut Request) -> IronResult<Response>
452 where C: MedalConnection + std::marker::Send + 'static {
453 let session_token = req.get_session_token();
454
455 with_conn![core::debug_create_session, C, req, session_token];
456
457 Ok(Response::with((status::Found, Redirect(url_for!(req, "debug")))))
458}
459
460fn contests<C>(req: &mut Request) -> IronResult<Response>
461 where C: MedalConnection + std::marker::Send + 'static {
462 let session_token = req.require_session_token()?;
463 let category = req.get_str("category");
464 let query_string = req.url.query().unwrap_or("").to_string();
465
466 let visibility = if let Some(secret) = query_string.strip_prefix("secret=") {
468 core::ContestVisibility::WithSecret(secret.to_string())
469 } else {
470 core::ContestVisibility::All
471 };
472
473 let category = if let Some(category) = category {
474 Some(category)
475 } else {
476 if query_string.contains("open") {
477 Some("training".to_owned())
478 } else if query_string.contains("current") {
479 Some("contest".to_owned())
480 } else if query_string.contains("challenge") {
481 Some("challenge".to_owned())
482 } else if query_string.contains("standalone_task") {
483 Some("standalone_task".to_owned())
484 } else {
485 None
486 }
487 };
488
489 let config = req.get::<Read<SharedConfiguration>>().unwrap();
490
491 let mut category_names: std::collections::BTreeMap<String, String> = std::collections::BTreeMap::new();
492 if let Some(template_params) = config.template_params.as_ref() {
493 if let Some(categories) = template_params.get("categories") {
494 if let Some(map) = categories.as_object() {
495 for (key, value) in map {
497 if let Some(name) = value.get("name") {
498 if let Some(name) = name.as_str() {
499 category_names.insert(key.to_string(), name.to_string());
500 }
501 }
502 }
503 }
504 }
505 }
506
507 let is_results = query_string.contains("results");
508 let res = with_conn![core::show_contests,
509 C,
510 req,
511 &session_token,
512 category.clone(),
513 login_info(&config),
514 visibility,
515 is_results,
516 category_names];
517
518 if res.is_err() {
519 println!("DATABASE CONNECTION LOST! Restarting database connection.");
522 let conn = C::reconnect(&config);
523 let mutex = req.get::<Write<SharedDatabaseConnection<C>>>().unwrap();
524 let mut sharedconn = mutex.lock().unwrap_or_else(|e| e.into_inner());
525 *sharedconn = conn;
526 }
528
529 let (template, mut data) = res.unwrap();
530 data.insert("config".to_string(), to_json(&config.template_params));
531 data.insert("category".to_string(), to_json(&category));
532
533 if let Some(categoryname) = category {
534 if let Some(template_params) = config.template_params.as_ref() {
535 if let Some(categories) = template_params.get("categories") {
536 if let Some(currentcategory) = categories.get(categoryname) {
537 data.insert("current_category".to_string(), currentcategory.clone());
538 }
539 }
540 }
541 }
542
543 if is_results {
544 data.insert("direct_link_to_results".to_string(), to_json(&true));
545 }
546
547 let mut resp = Response::new();
548 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
549 Ok(resp)
550}
551
552fn contest<C>(req: &mut Request) -> IronResult<Response>
553 where C: MedalConnection + std::marker::Send + 'static {
554 let contest_id = req.expect_int::<i64>("contestid")?;
555 let category = req.get_str("category");
556 let secret = req.get_str("secret");
557 let session_token = req.require_session_token()?;
558 let query_string = req.url.query().map(|s| s.to_string());
559
560 let config = req.get::<Read<SharedConfiguration>>().unwrap();
561 let res = with_conn![core::show_contest,
562 C,
563 req,
564 contest_id,
565 &session_token,
566 query_string,
567 login_info(&config),
568 secret].aug(req)?;
569
570 match res {
571 Err(task_id) => {
572 Ok(Response::with((status::Found, Redirect(url_for!(req, "task", "taskid" => format!("{}", task_id))))))
573 }
574 Ok((template, mut data)) => {
575 data.insert("config".to_string(), to_json(&config.template_params));
576 data.insert("category".to_string(), to_json(&category));
577
578 if let Some(categoryname) = category {
579 if let Some(template_params) = config.template_params.as_ref() {
580 if let Some(categories) = template_params.get("categories") {
581 if let Some(currentcategory) = categories.get(categoryname) {
582 data.insert("current_category".to_string(), currentcategory.clone());
583 }
584 }
585 }
586 }
587
588 let mut resp = Response::new();
589 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
590 Ok(resp)
591 }
592 }
593}
594
595fn contests_or_contest<C>(req: &mut Request) -> IronResult<Response>
596 where C: MedalConnection + std::marker::Send + 'static {
597 let firstparam_string = req.get_str("categoryorcontestid");
598 let firstparam_numeric = req.get_int::<i64>("categoryorcontestid");
599
600 let secondparam_string = req.get_str("contestidorsecret");
601 let secondparam_numeric = req.get_int::<i64>("contestidorsecret");
602
603 if firstparam_numeric.is_some() {
605 let router_map = req.extensions.get_mut::<Router>().unwrap();
606
607 router_map.insert("contestid".to_string(), firstparam_string.unwrap());
608
609 if let Some(secondparam_string) = secondparam_string {
610 router_map.insert("secret".to_string(), secondparam_string);
611 }
612
613 contest::<C>(req)
614 } else if secondparam_numeric.is_some() {
615 let router_map = req.extensions.get_mut::<Router>().unwrap();
616
617 router_map.insert("category".to_string(), firstparam_string.unwrap());
618 router_map.insert("contestid".to_string(), secondparam_string.unwrap());
619
620 contest::<C>(req)
621 } else if secondparam_string.is_none() {
622 let router_map = req.extensions.get_mut::<Router>().unwrap();
623
624 router_map.insert("category".to_string(), firstparam_string.unwrap());
625
626 contests::<C>(req)
627 } else {
628 Err(core::MedalError::NotFound).aug(req).map_err(|x| x.into())
629 }
630}
631
632fn contestresults<C>(req: &mut Request) -> IronResult<Response>
633 where C: MedalConnection + std::marker::Send + 'static {
634 let config = req.get::<Read<SharedConfiguration>>().unwrap();
635 let disable_contest_results = config.disable_results_page.unwrap_or(false);
636
637 if disable_contest_results {
638 let mut resp = Response::new();
639 resp.set_mut(Template::new(&"nocontestresults", 2)).set_mut(status::Locked);
640 return Ok(resp);
641 }
642
643 let contest_id = req.expect_int::<i64>("contestid")?;
644 let session_token = req.require_session_token()?;
645
646 let (template, data) = with_conn![core::show_contest_results, C, req, contest_id, &session_token].aug(req)?;
647
648 let mut resp = Response::new();
649 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
650 Ok(resp)
651}
652
653fn contestresults_download<C>(req: &mut Request) -> IronResult<Response>
654 where C: MedalConnection + std::marker::Send + 'static {
655 let config = req.get::<Read<SharedConfiguration>>().unwrap();
656 let disable_contest_results = config.disable_results_page.unwrap_or(false);
657
658 if disable_contest_results {
659 let mut resp = Response::new();
660 resp.set_mut(Template::new(&"nocontestresults", 2)).set_mut(status::Locked);
661 return Ok(resp);
662 }
663
664 let contest_id = req.expect_int::<i64>("contestid")?;
665 let session_token = req.require_session_token()?;
666
667 let (template, data) = with_conn![core::show_contest_results, C, req, contest_id, &session_token].aug(req)?;
668
669 use iron::headers::{Charset, ContentDisposition, DispositionParam, DispositionType};
670
671 let cd = ContentDisposition { disposition: DispositionType::Attachment,
672 parameters: vec![DispositionParam::Filename(
673 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(), )] };
678
679 let mime: Mime = "text/csv; charset=UTF-8".parse().unwrap();
680 let mut resp = Response::new();
681 resp.headers.set(cd);
682 resp.set_mut(Template::new(&format!("{}_download", template), data)).set_mut(status::Ok).set_mut(mime);
683 Ok(resp)
684}
685
686fn contest_post<C>(req: &mut Request) -> IronResult<Response>
687 where C: MedalConnection + std::marker::Send + 'static {
688 let (contest_id, category) = {
689 let potential_contest_id_1 = req.expect_int::<i64>("contestid");
690 let potential_contest_id_2 = req.expect_int::<i64>("categoryorcontestid");
691 let potential_contest_id_3 = req.expect_int::<i64>("contestidorsecret");
692
693 let potential_category_1 = req.get_str("category");
694 let potential_category_3 = req.get_str("categoryorcontestid");
695
696 match (potential_contest_id_1, potential_contest_id_2) {
697 (Ok(contest_id), _) => (contest_id, potential_category_1),
698 (Err(_), Ok(contest_id)) => (contest_id, None),
699 _ => (potential_contest_id_3?, potential_category_3),
700 }
701 };
702
703 let session_token = req.expect_session_token()?;
704
705 let (csrf_token, secret, teampartner_logincode) = {
706 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
707 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
708 formdata.get("secret").map(|x| x[0].to_owned()),
709 formdata.get("teampartner_logincode").map(|x| x[0].to_owned()))
710 };
711
712 let res = with_conn![core::start_contest,
713 C,
714 req,
715 contest_id,
716 &session_token,
717 &csrf_token,
718 secret,
719 teampartner_logincode].aug(req)?;
720
721 if let Err(value) = res {
722 if let Some(category) = category {
723 Ok(Response::with((status::Found,
724 Redirect(iron::Url::parse(&format!("{}?team_participation={}",
725 url_for!(req, "contests_or_contest_secret",
726 "categoryorcontestid" => category.to_string(),
727 "contestidorsecret" => format!("{}", contest_id)),
728 value)).unwrap()))))
729 } else {
730 Ok(Response::with((status::Found, Redirect(
731 iron::Url::parse(&format!("{}?team_participation={}",
732 url_for!(req, "contests_or_contest", "categoryorcontestid" => format!("{}", contest_id)),
733 value
734 )).unwrap()))))
735 }
736 } else if let Some(category) = category {
737 Ok(Response::with((status::Found,
738 Redirect(url_for!(req, "contests_or_contest_secret",
739 "categoryorcontestid" => category.to_string(),
740 "contestidorsecret" => format!("{}", contest_id))))))
741 } else {
742 Ok(Response::with((status::Found,
743 Redirect(url_for!(req, "contests_or_contest", "categoryorcontestid" => format!("{}",contest_id))))))
744 }
745}
746
747fn login<C>(req: &mut Request) -> IronResult<Response>
748 where C: MedalConnection + std::marker::Send + 'static {
749 let session_token = req.get_session_token();
750
751 let config = req.get::<Read<SharedConfiguration>>().unwrap();
752 let (template, mut data) = with_conn![core::show_login, C, req, session_token, login_info(&config)];
753
754 let query_string = req.url.query().map(|s| s.to_string());
755 if let Some(query) = query_string {
756 data.insert("forward".to_string(), to_json(&query));
757 }
758
759 let mut resp = Response::new();
761 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
762 Ok(resp)
763}
764
765fn login_post<C>(req: &mut Request) -> IronResult<Response>
766 where C: MedalConnection + std::marker::Send + 'static {
767 let logindata = {
768 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
769 (iexpect!(formdata.get("username"))[0].to_owned(), iexpect!(formdata.get("password"))[0].to_owned())
770 };
771
772 let config = req.get::<Read<SharedConfiguration>>().unwrap();
773 let loginresult = with_conn![core::login, C, req, logindata, login_info(&config)];
775
776 match loginresult {
777 Ok((sessionkey, profilestatus)) => {
779 if let Some(profilestatus) = profilestatus {
780 req.session().set(SessionToken { token: sessionkey }).unwrap();
781 Ok(Response::with((status::Found,
782 Redirect(iron::Url::parse(&format!("{}?status={}",
783 url_for!(req, "myprofile"),
784 profilestatus)).unwrap()))))
785 } else {
786 req.session().set(SessionToken { token: sessionkey }).unwrap();
787 Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
788 }
789 }
790 Err((template, data)) => {
792 let mut resp = Response::new();
793 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
794 Ok(resp)
795 }
796 }
797}
798
799fn login_code_post<C>(req: &mut Request) -> IronResult<Response>
800 where C: MedalConnection + std::marker::Send + 'static {
801 let code = {
802 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
803 iexpect!(formdata.get("code"))[0].to_owned()
804 };
805
806 let config = req.get::<Read<SharedConfiguration>>().unwrap();
807 let loginresult = with_conn![core::login_with_code, C, req, &code, login_info(&config)];
809
810 match loginresult {
811 Ok((sessionkey, profilestatus)) => {
813 if let Some(profilestatus) = profilestatus {
814 req.session().set(SessionToken { token: sessionkey }).unwrap();
815 Ok(Response::with((status::Found,
816 Redirect(iron::Url::parse(&format!("{}?status={}",
817 url_for!(req, "myprofile"),
818 profilestatus)).unwrap()))))
819 } else {
820 req.session().set(SessionToken { token: sessionkey }).unwrap();
821 Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
822 }
823 }
824 Err((template, data)) => {
826 let mut resp = Response::new();
827 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
828 Ok(resp)
829 }
830 }
831}
832
833fn login_webauthn<C>(req: &mut Request) -> IronResult<Response>
834 where C: MedalConnection + std::marker::Send + 'static {
835 let self_url = {
837 let config = req.get::<Read<SharedConfiguration>>().unwrap();
838 config.self_url.clone().ok_or(core::MedalError::WebauthnError).aug(req)?
839 };
840
841 Ok(handle_json_response_simple(with_conn![core::login_with_key_challenge, C, req, &self_url]))
842}
843
844fn login_webauthn_post<C>(req: &mut Request) -> IronResult<Response>
845 where C: MedalConnection + std::marker::Send + 'static {
846 let (auth_id, credential) = {
847 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
848 (iexpect!(formdata.get("id"))[0].parse::<i64>().unwrap_or(0),
849 iexpect!(formdata.get("credential"))[0].to_owned())
850 };
851
852 let self_url = {
854 let config = req.get::<Read<SharedConfiguration>>().unwrap();
855 config.self_url.clone().ok_or(core::MedalError::WebauthnError).aug(req)?
856 };
857
858 let loginresult = with_conn![core::login_with_key, C, req, &self_url, auth_id, &credential];
860
861 match loginresult {
862 Ok((sessionkey, _profilestatus)) => {
864 req.session().set(SessionToken { token: sessionkey }).unwrap();
865
866 let mut resp = Response::new();
867 resp.set_mut("").set_mut(status::Ok);
868 Ok(resp)
869 }
870 Err(msg) => {
872 let mut resp = Response::new();
873 resp.set_mut(msg).set_mut(status::Forbidden);
874 Ok(resp)
875 }
876 }
877}
878
879fn register_webauthn<C>(req: &mut Request) -> IronResult<Response>
880 where C: MedalConnection + std::marker::Send + 'static {
881 let session_token = req.expect_session_token()?;
882
883 let self_url = {
885 let config = req.get::<Read<SharedConfiguration>>().unwrap();
886 config.self_url.clone().ok_or(core::MedalError::WebauthnError).aug(req)?
887 };
888
889 Ok(handle_json_response(with_conn![core::register_key_challenge, C, req, &self_url, &session_token]).aug(req)?)
890}
891
892fn register_webauthn_post<C>(req: &mut Request) -> IronResult<Response>
893 where C: MedalConnection + std::marker::Send + 'static {
894 let session_token = req.expect_session_token()?;
895
896 let (csrf_token, credential, name) = {
897 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
898 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
899 iexpect!(formdata.get("credential"))[0].to_owned(),
900 iexpect!(formdata.get("name"))[0].to_owned())
901 };
902
903 let self_url = {
905 let config = req.get::<Read<SharedConfiguration>>().unwrap();
906 config.self_url.clone().ok_or(core::MedalError::WebauthnError).aug(req)?
907 };
908
909 Ok(handle_json_response(with_conn![core::register_key,
910 C,
911 req,
912 &self_url,
913 &session_token,
914 &csrf_token,
915 credential,
916 name]).aug(req)?)
917}
918
919fn delete_webauthn_post<C>(req: &mut Request) -> IronResult<Response>
920 where C: MedalConnection + std::marker::Send + 'static {
921 let session_token = req.expect_session_token()?;
922 let (csrf_token, token_id) = {
923 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
924 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
925 iexpect!(formdata.get("id"))[0].parse::<i64>().unwrap_or(0))
926 };
927
928 Ok(handle_json_response(with_conn![core::delete_key, C, req, &session_token, &csrf_token, token_id]).aug(req)?)
929}
930
931fn logout<C>(req: &mut Request) -> IronResult<Response>
932 where C: MedalConnection + std::marker::Send + 'static {
933 let session_token = req.get_session_token();
934
935 #[cfg(feature = "debug")]
936 println!("Logging out session {:?}", session_token);
937
938 with_conn![core::logout, C, req, session_token];
939
940 let query_string = req.url.query().map(|s| s.to_string());
941 if let Some(query) = query_string {
942 if query.contains("redirect=login") {
943 return Ok(Response::with((status::Found, Redirect(url_for!(req, "login")))));
944 }
945 }
946
947 Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
948}
949
950#[cfg(feature = "signup")]
951#[allow(clippy::extra_unused_type_parameters)]
952fn signup<C>(req: &mut Request) -> IronResult<Response>
953 where C: MedalConnection + std::marker::Send + 'static {
954 let query_string = req.url.query().map(|s| s.to_string());
955
956 let data = core::signupdata(query_string);
957 let mut resp = Response::new();
958 resp.set_mut(Template::new("signup", data)).set_mut(status::Ok);
959 Ok(resp)
960}
961
962#[cfg(feature = "signup")]
963fn signup_post<C>(req: &mut Request) -> IronResult<Response>
964 where C: MedalConnection + std::marker::Send + 'static {
965 let session_token = req.get_session_token();
966 let signupdata = {
967 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
968 (iexpect!(formdata.get("username"))[0].to_owned(),
969 iexpect!(formdata.get("email"))[0].to_owned(),
970 iexpect!(formdata.get("password"))[0].to_owned())
971 };
972
973 let signupresult = with_conn![core::signup, C, req, session_token, signupdata].aug(req)?;
974 match signupresult {
975 SignupResult::SignedUp => Ok(Response::with((status::Found,
976 Redirect(iron::Url::parse(&format!("{}?status={:?}",
977 url_for!(req,
978 "myprofile"),
979 signupresult)).unwrap())))),
980 _ => Ok(Response::with((status::Found,
981 Redirect(iron::Url::parse(&format!("{}?status={:?}",
982 url_for!(req, "signup"),
983 signupresult)).unwrap())))),
984 }
985}
986
987#[cfg(not(feature = "signup"))]
988#[allow(clippy::extra_unused_type_parameters)]
989fn signup<C>(req: &mut Request) -> IronResult<Response>
990 where C: MedalConnection + std::marker::Send + 'static {
991 Err(core::MedalError::NotFound).aug(req).map_err(|x| x.into())
992}
993
994#[cfg(not(feature = "signup"))]
995#[allow(clippy::extra_unused_type_parameters)]
996fn signup_post<C>(req: &mut Request) -> IronResult<Response>
997 where C: MedalConnection + std::marker::Send + 'static {
998 Err(core::MedalError::NotFound).aug(req).map_err(|x| x.into())
999}
1000
1001fn submission<C>(req: &mut Request) -> IronResult<Response>
1002 where C: MedalConnection + std::marker::Send + 'static {
1003 let task_id = req.expect_int::<i64>("taskid")?;
1004
1005 let session_token = req.expect_session_token()?;
1006 let subtask: Option<String> = (|| -> Option<String> {
1007 req.get_ref::<UrlEncodedQuery>().ok()?.get("subtask")?.first().map(|x| x.to_owned())
1008 })();
1009
1010 let submission: Option<i64> = (|| -> Option<i64> {
1011 req.get_ref::<UrlEncodedQuery>().ok()?.get("submission")?.first().and_then(|x| x.parse::<i64>().ok())
1012 })();
1013
1014 let result = with_conn![core::load_submission, C, req, task_id, &session_token, subtask, submission];
1015
1016 match result {
1017 Ok(data) => Ok(Response::with((status::Ok, mime!(Application / Json), data))),
1018 Err(_) => Ok(Response::with((status::BadRequest, mime!(Application / Json), "{}".to_string()))),
1019 }
1020}
1021
1022fn submission_post<C>(req: &mut Request) -> IronResult<Response>
1023 where C: MedalConnection + std::marker::Send + 'static {
1024 let task_id = req.expect_int::<i64>("taskid")?;
1025 let session_token = req.expect_session_token()?;
1026 let (csrf_token, data, grade, autosave, subtask) = {
1027 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
1028 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1029 iexpect!(formdata.get("data"))[0].to_owned(),
1030 formdata.get("grade").map(|x| x[0].parse::<i32>().unwrap_or(0)).unwrap_or(0),
1031 formdata.get("autosave").map(|x| x[0].parse::<bool>().unwrap_or(false)).unwrap_or(false),
1032 formdata.get("subtask").map(|x| x[0].to_owned()))
1033 };
1034
1035 #[cfg(feature = "debug")]
1036 println!("New submission for task {} (graded {}): {}", task_id, grade, data);
1037
1038 let result = with_conn![core::save_submission,
1039 C,
1040 req,
1041 task_id,
1042 &session_token,
1043 &csrf_token,
1044 data,
1045 grade,
1046 autosave,
1047 subtask].aug(req)?;
1048
1049 Ok(Response::with((status::Ok, mime!(Application / Json), result)))
1050}
1051
1052fn task<C>(req: &mut Request) -> IronResult<Response>
1053 where C: MedalConnection + std::marker::Send + 'static {
1054 let task_id = req.expect_int::<i64>("taskid")?;
1055 let session_token = req.require_session_token()?;
1056
1057 let autosaveinterval = {
1059 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1060 config.auto_save_interval.unwrap_or(10)
1061 };
1062
1063 match with_conn![core::show_task, C, req, task_id, &session_token, autosaveinterval].aug(req)? {
1064 Ok((template, data)) => {
1065 let mut resp = Response::new();
1066 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1067 Ok(resp)
1068 }
1069 Err((contest_id, category)) => {
1070 if let Some(category) = category {
1072 Ok(Response::with((status::Found,
1073 Redirect(url_for!(req, "contests_or_contest_secret",
1074 "categoryorcontestid" => category.to_string(),
1075 "contestidorsecret" => format!("{}", contest_id))))))
1076 } else {
1077 Ok(Response::with((status::Found,
1078 Redirect(url_for!(req, "contests_or_contest", "categoryorcontestid" => format!("{}",contest_id))))))
1079 }
1080 }
1081 }
1082}
1083
1084fn review<C>(req: &mut Request) -> IronResult<Response>
1085 where C: MedalConnection + std::marker::Send + 'static {
1086 let task_id = req.expect_int::<i64>("taskid")?;
1087 let submission_id = req.expect_int::<i64>("submissionid")?;
1088 let session_token = req.require_session_token()?;
1089
1090 match with_conn![core::review_task, C, req, task_id, &session_token, submission_id].aug(req)? {
1091 Ok((template, data)) => {
1092 let mut resp = Response::new();
1093 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1094 Ok(resp)
1095 }
1096 Err(contest_id) => {
1097 Ok(Response::with((status::Found,
1099 Redirect(url_for!(req, "contest", "contestid" => format!("{}",contest_id))))))
1100 }
1101 }
1102}
1103
1104fn preview<C>(req: &mut Request) -> IronResult<Response>
1105 where C: MedalConnection + std::marker::Send + 'static {
1106 let task_id = req.expect_int::<i64>("taskid")?;
1107
1108 match with_conn![core::preview_task, C, req, task_id].aug(req)? {
1109 Ok((template, data)) => {
1110 let mut resp = Response::new();
1111 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1112 Ok(resp)
1113 }
1114 Err(contest_id) => {
1115 Ok(Response::with((status::Found,
1117 Redirect(url_for!(req, "contest", "contestid" => format!("{}",contest_id))))))
1118 }
1119 }
1120}
1121
1122fn groups<C>(req: &mut Request) -> IronResult<Response>
1123 where C: MedalConnection + std::marker::Send + 'static {
1124 let session_token = req.require_session_token()?;
1125 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1126
1127 let (template, mut data) = with_conn![core::show_groups, C, req, &session_token].aug(req)?;
1128 data.insert("config".to_string(), to_json(&config.template_params));
1129
1130 let mut resp = Response::new();
1131 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1132 Ok(resp)
1133}
1134
1135fn group_download<C>(req: &mut Request) -> IronResult<Response>
1136 where C: MedalConnection + std::marker::Send + 'static {
1137 let group_id = req.expect_int::<i64>("groupid")?;
1138 let session_token = req.require_session_token()?;
1139
1140 let (template, data) = with_conn![core::show_group, C, req, group_id, &session_token].aug(req)?;
1141
1142 use iron::headers::{Charset, ContentDisposition, DispositionParam, DispositionType};
1143
1144 let cd = ContentDisposition { disposition: DispositionType::Attachment,
1145 parameters: vec![DispositionParam::Filename(
1146 Charset::Ext("Utf-8".to_string()), None, format!("{}.csv", data.get("groupname").unwrap().as_str().unwrap()).as_bytes().to_vec(), )] };
1151
1152 let mime: Mime = "text/csv".parse().unwrap();
1153 let mut resp = Response::new();
1154 resp.headers.set(cd);
1155 resp.set_mut(Template::new(&format!("{}_download", template), data)).set_mut(status::Ok).set_mut(mime);
1156 Ok(resp)
1157}
1158
1159fn new_group<C>(req: &mut Request) -> IronResult<Response>
1160 where C: MedalConnection + std::marker::Send + 'static {
1161 let session_token = req.require_session_token()?;
1162
1163 let (csrf_token, name, tag) = {
1164 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
1165 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1166 iexpect!(formdata.get("name"))[0].to_owned(),
1167 iexpect!(formdata.get("tag"))[0].to_owned())
1168 };
1169
1170 let group_id = with_conn![core::add_group, C, req, &session_token, &csrf_token, name, tag].aug(req)?;
1171
1172 Ok(Response::with((status::Found, Redirect(url_for!(req, "group", "groupid" => format!("{}",group_id))))))
1173}
1174
1175fn group_csv<C>(req: &mut Request) -> IronResult<Response>
1176 where C: MedalConnection + std::marker::Send + 'static {
1177 let session_token = req.require_session_token()?;
1178
1179 let si = {
1180 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1181 core::SexInformation { require_sex: config.require_sex.unwrap_or(false),
1182 allow_sex_na: config.allow_sex_na.unwrap_or(true),
1183 allow_sex_diverse: config.allow_sex_diverse.unwrap_or(false),
1184 allow_sex_other: config.allow_sex_other.unwrap_or(true) }
1185 };
1186
1187 template_ok!(with_conn![core::group_csv, C, req, &session_token, si].aug(req)?)
1188}
1189
1190fn group_csv_upload<C>(req: &mut Request) -> IronResult<Response>
1191 where C: MedalConnection + std::marker::Send + 'static {
1192 let session_token = req.require_session_token()?;
1193
1194 let (csrf_token, group_data) = {
1195 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
1196 (iexpect!(formdata.get("csrf_token"))[0].to_owned(), iexpect!(formdata.get("group_data"))[0].to_owned())
1197 };
1198
1199 #[cfg(feature = "debug")]
1200 println!("{}", group_data);
1201
1202 with_conn![core::upload_groups, C, req, &session_token, &csrf_token, &group_data].aug(req)?;
1203
1204 Ok(Response::with((status::Found, Redirect(url_for!(req, "groups")))))
1205}
1206
1207fn contest_resultcsv<C>(req: &mut Request) -> IronResult<Response>
1208 where C: MedalConnection + std::marker::Send + 'static {
1209 let session_token = req.require_session_token()?;
1210
1211 template_ok!(with_conn![core::contest_result_csv, C, req, &session_token].aug(req)?)
1212}
1213
1214fn contest_resultcsv_upload<C>(req: &mut Request) -> IronResult<Response>
1215 where C: MedalConnection + std::marker::Send + 'static {
1216 let contest_id = req.expect_int::<i64>("contestid")?;
1217 let session_token = req.require_session_token()?;
1218
1219 let (csrf_token, admission_data) = {
1220 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
1221 (iexpect!(formdata.get("csrf_token"))[0].to_owned(), iexpect!(formdata.get("admission_data"))[0].to_owned())
1222 };
1223
1224 #[cfg(feature = "debug")]
1225 println!("{}", admission_data);
1226
1227 with_conn![core::upload_contest_result_csv, C, req, &session_token, &csrf_token, contest_id, &admission_data].aug(req)?;
1228
1229 Ok(Response::with((status::Found,
1230 Redirect(url_for!(req, "admin_contests", "contestid" => format!("{}",contest_id))))))
1231}
1232
1233fn profile<C>(req: &mut Request) -> IronResult<Response>
1234 where C: MedalConnection + std::marker::Send + 'static {
1235 let session_token = req.require_session_token()?;
1236 let query_string = req.url.query().map(|s| s.to_string());
1237
1238 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1239 let si = core::SexInformation { require_sex: config.require_sex.unwrap_or(false),
1240 allow_sex_na: config.allow_sex_na.unwrap_or(true),
1241 allow_sex_diverse: config.allow_sex_diverse.unwrap_or(false),
1242 allow_sex_other: config.allow_sex_other.unwrap_or(true) };
1243
1244 let mut category_names: std::collections::BTreeMap<String, String> = std::collections::BTreeMap::new();
1245 if let Some(template_params) = config.template_params.as_ref() {
1246 if let Some(categories) = template_params.get("categories") {
1247 if let Some(map) = categories.as_object() {
1248 for (key, value) in map {
1250 if let Some(name) = value.get("name") {
1251 if let Some(name) = name.as_str() {
1252 category_names.insert(key.to_string(), name.to_string());
1253 }
1254 }
1255 }
1256 }
1257 }
1258 }
1259
1260 let (template, mut data) =
1261 with_conn![core::show_profile, C, req, &session_token, None, query_string, si, category_names].aug(req)?;
1262 data.insert("config".to_string(), to_json(&config.template_params));
1263
1264 let mut resp = Response::new();
1265 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1266 Ok(resp)
1267}
1268
1269fn profile_post<C>(req: &mut Request) -> IronResult<Response>
1270 where C: MedalConnection + std::marker::Send + 'static {
1271 let session_token = req.expect_session_token()?;
1272 let (csrf_token, firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex) = {
1273 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1274 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1275 iexpect!(formdata.get("firstname"))[0].to_owned(),
1276 iexpect!(formdata.get("lastname"))[0].to_owned(),
1277 formdata.get("street").map(|x| x[0].to_owned()),
1278 formdata.get("zip").map(|x| x[0].to_owned()),
1279 formdata.get("city").map(|x| x[0].to_owned()),
1280 formdata.get("password").map(|x| x[0].to_owned()),
1281 formdata.get("password_repeat").map(|x| x[0].to_owned()),
1282 iexpect!(formdata.get("grade"))[0].parse::<i32>().unwrap_or(0),
1283 iexpect!(formdata.get("sex"))[0].parse::<i32>().ok())
1284 };
1285
1286 let profilechangeresult =
1287 with_conn![core::edit_profile,
1288 C,
1289 req,
1290 &session_token,
1291 None,
1292 &csrf_token,
1293 (firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex)].aug(req)?;
1294
1295 Ok(Response::with((status::Found,
1296 Redirect(iron::Url::parse(&format!("{}?status={:?}",
1297 url_for!(req, "myprofile"),
1298 profilechangeresult)).unwrap()))))
1299}
1300
1301fn profile_check<C>(req: &mut Request) -> IronResult<Response>
1302 where C: MedalConnection + std::marker::Send + 'static {
1303 let session_token = req.expect_session_token()?;
1304 let (csrf_token, firstname, lastname) = {
1305 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1306 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1307 iexpect!(formdata.get("firstname"))[0].to_owned(),
1308 iexpect!(formdata.get("lastname"))[0].to_owned())
1309 };
1310
1311 Ok(handle_json_response(with_conn![core::check_profile,
1312 C,
1313 req,
1314 &session_token,
1315 &csrf_token,
1316 (firstname, lastname)]).aug(req)?)
1317}
1318
1319fn user<C>(req: &mut Request) -> IronResult<Response>
1320 where C: MedalConnection + std::marker::Send + 'static {
1321 let user_id = req.expect_int::<i64>("userid")?;
1322 let session_token = req.expect_session_token()?;
1323 let query_string = req.url.query().map(|s| s.to_string());
1324
1325 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1326 let si = core::SexInformation { require_sex: config.require_sex.unwrap_or(false),
1327 allow_sex_na: config.allow_sex_na.unwrap_or(true),
1328 allow_sex_diverse: config.allow_sex_diverse.unwrap_or(false),
1329 allow_sex_other: config.allow_sex_other.unwrap_or(true) };
1330
1331 let mut category_names: std::collections::BTreeMap<String, String> = std::collections::BTreeMap::new();
1332 if let Some(template_params) = config.template_params.as_ref() {
1333 if let Some(categories) = template_params.get("categories") {
1334 if let Some(map) = categories.as_object() {
1335 for (key, value) in map {
1337 if let Some(name) = value.get("name") {
1338 if let Some(name) = name.as_str() {
1339 category_names.insert(key.to_string(), name.to_string());
1340 }
1341 }
1342 }
1343 }
1344 }
1345 }
1346
1347 let (template, mut data) = with_conn![core::show_profile,
1348 C,
1349 req,
1350 &session_token,
1351 Some(user_id),
1352 query_string,
1353 si,
1354 category_names].aug(req)?;
1355 data.insert("config".to_string(), to_json(&config.template_params));
1356
1357 let mut resp = Response::new();
1358 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1359 Ok(resp)
1360}
1361
1362fn user_post<C>(req: &mut Request) -> IronResult<Response>
1363 where C: MedalConnection + std::marker::Send + 'static {
1364 let user_id = req.expect_int::<i64>("userid")?;
1365 let session_token = req.expect_session_token()?;
1366 let (csrf_token, firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex) = {
1367 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1368 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1369 iexpect!(formdata.get("firstname"))[0].to_owned(),
1370 iexpect!(formdata.get("lastname"))[0].to_owned(),
1371 formdata.get("street").map(|x| x[0].to_owned()),
1372 formdata.get("zip").map(|x| x[0].to_owned()),
1373 formdata.get("city").map(|x| x[0].to_owned()),
1374 formdata.get("password").map(|x| x[0].to_owned()),
1375 formdata.get("password_repeat").map(|x| x[0].to_owned()),
1376 iexpect!(formdata.get("grade"))[0].parse::<i32>().unwrap_or(0),
1377 iexpect!(formdata.get("sex"))[0].parse::<i32>().ok())
1378 };
1379
1380 let profilechangeresult =
1381 with_conn![core::edit_profile,
1382 C,
1383 req,
1384 &session_token,
1385 Some(user_id),
1386 &csrf_token,
1387 (firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex)].aug(req)?;
1388
1389 Ok(Response::with((status::Found,
1390 Redirect(iron::Url::parse(&format!("{}?status={:?}",
1391 url_for!(req, "user", "userid" => format!("{}",user_id)),
1392 profilechangeresult)).unwrap()))))
1393 }
1395
1396fn teacherinfos<C>(req: &mut Request) -> IronResult<Response>
1397 where C: MedalConnection + std::marker::Send + 'static {
1398 let session_token = req.expect_session_token()?;
1399
1400 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1401
1402 let (template, mut data) = with_conn![core::teacher_infos, C, req, &session_token].aug(req)?;
1403 data.insert("config".to_string(), to_json(&config.template_params));
1404
1405 let mut resp = Response::new();
1406 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1407 Ok(resp)
1408}
1409
1410fn admin<C>(req: &mut Request) -> IronResult<Response>
1411 where C: MedalConnection + std::marker::Send + 'static {
1412 let session_token = req.expect_session_token()?;
1413 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1414
1415 let (template, mut data) = with_conn![core::admin_index, C, req, &session_token].aug(req)?;
1416 data.insert("dbstatus_secret".to_string(), to_json(&config.dbstatus_secret));
1417 data.insert("config".to_string(), to_json(&config.template_params));
1418
1419 let mut resp = Response::new();
1420 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1421 Ok(resp)
1422}
1423
1424fn admin_users<C>(req: &mut Request) -> IronResult<Response>
1425 where C: MedalConnection + std::marker::Send + 'static {
1426 let session_token = req.expect_session_token()?;
1427 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1428
1429 let (s_id, s_firstname, s_lastname, s_logincode, s_groupname, s_groupcode, s_pms_id, s_anything) = {
1430 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1431 (formdata.get("id").map(|x| x[0].parse::<i64>().unwrap_or(0)),
1432 formdata.get("firstname").map(|x| x[0].to_owned()),
1433 formdata.get("lastname").map(|x| x[0].to_owned()),
1434 formdata.get("logincode").map(|x| x[0].to_owned()),
1435 formdata.get("groupname").map(|x| x[0].to_owned()),
1436 formdata.get("groupcode").map(|x| x[0].to_owned()),
1437 formdata.get("pmsid").map(|x| x[0].to_owned()),
1438 formdata.get("anything").map(|x| x[0].to_owned()))
1439 };
1440
1441 let (template, mut data) = with_conn![core::admin_search_users,
1442 C,
1443 req,
1444 &session_token,
1445 (s_id,
1446 s_firstname,
1447 s_lastname,
1448 s_logincode,
1449 s_groupname,
1450 s_groupcode,
1451 s_pms_id,
1452 s_anything)].aug(req)?;
1453 data.insert("config".to_string(), to_json(&config.template_params));
1454
1455 let mut resp = Response::new();
1456 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1457 Ok(resp)
1458}
1459
1460fn admin_user<C>(req: &mut Request) -> IronResult<Response>
1461 where C: MedalConnection + std::marker::Send + 'static {
1462 let user_id = req.expect_int::<i64>("userid")?;
1463 let session_token = req.expect_session_token()?;
1464 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1465
1466 let (csrf_token, group_id, additional_time) = if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
1467 (formdata.get("csrf_token").map(|x| x[0].to_owned()),
1469 formdata.get("group_id").map(|x| x[0].parse::<i64>().unwrap_or(0)),
1470 formdata.get("additional_time").map(|x| x[0].parse::<i32>().unwrap_or(0)))
1471 } else {
1472 (None, None, None)
1473 };
1474
1475 if let Some(csrf_token) = csrf_token {
1476 if let Some(group_id) = group_id {
1477 return Ok(handle_json_response(with_conn![core::admin_move_user_to_group,
1478 C,
1479 req,
1480 user_id,
1481 group_id,
1482 &session_token,
1483 &csrf_token]).aug(req)?);
1484 } else if let Some(additional_time) = additional_time {
1485 return Ok(handle_json_response(with_conn![core::admin_set_user_additional_contest_time,
1486 C,
1487 req,
1488 user_id,
1489 additional_time,
1490 &session_token,
1491 &csrf_token]).aug(req)?);
1492 } else {
1493 return Ok(handle_json_response(with_conn![core::admin_delete_user,
1494 C,
1495 req,
1496 user_id,
1497 &session_token,
1498 &csrf_token]).aug(req)?);
1499 }
1500 }
1501
1502 let (template, mut data) = with_conn![core::admin_show_user, C, req, user_id, &session_token].aug(req)?;
1503 data.insert("config".to_string(), to_json(&config.template_params));
1504
1505 let mut resp = Response::new();
1506 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1507 Ok(resp)
1508}
1509
1510fn admin_user_new_logincode<C>(req: &mut Request) -> IronResult<Response>
1511 where C: MedalConnection + std::marker::Send + 'static {
1512 let user_id = req.expect_int::<i64>("userid")?;
1513 let session_token = req.expect_session_token()?;
1514
1515 let csrf_token = {
1516 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1517 iexpect!(formdata.get("csrf_token"))[0].to_owned()
1518 };
1519
1520 Ok(handle_json_response(with_conn![core::admin_user_new_logincode,
1521 C,
1522 req,
1523 user_id,
1524 &session_token,
1525 &csrf_token]).aug(req)?)
1526}
1527
1528fn admin_group<C>(req: &mut Request) -> IronResult<Response>
1529 where C: MedalConnection + std::marker::Send + 'static {
1530 let group_id = req.expect_int::<i64>("groupid")?;
1531 let session_token = req.expect_session_token()?;
1532 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1533
1534 let (csrf_token, set_clearance, clearance_user_id) = if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
1535 (formdata.get("csrf_token").map(|x| x[0].to_owned()),
1536 formdata.get("enable_clearance").map(|x| x[0].parse::<bool>().unwrap_or(false)),
1537 formdata.get("clearance_user_id").map(|x| x[0].parse::<i64>().unwrap_or(0)))
1538 } else {
1539 (None, None, None)
1540 };
1541
1542 if let Some(csrf_token) = csrf_token {
1543 if let Some(set_clearance) = set_clearance {
1544 return Ok(handle_json_response(with_conn![core::admin_set_data_clearance,
1545 C,
1546 req,
1547 group_id,
1548 set_clearance,
1549 clearance_user_id,
1550 &session_token,
1551 &csrf_token]).aug(req)?);
1552 } else {
1553 return Ok(handle_json_response(with_conn![core::admin_delete_group,
1554 C,
1555 req,
1556 group_id,
1557 &session_token,
1558 &csrf_token]).aug(req)?);
1559 }
1560 }
1561
1562 let (template, mut data) = with_conn![core::admin_show_group, C, req, group_id, &session_token].aug(req)?;
1563 data.insert("config".to_string(), to_json(&config.template_params));
1564
1565 let mut resp = Response::new();
1566 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1567 Ok(resp)
1568}
1569
1570fn admin_group_edit<C>(req: &mut Request) -> IronResult<Response>
1571 where C: MedalConnection + std::marker::Send + 'static {
1572 let group_id = req.expect_int::<i64>("groupid")?;
1573 let session_token = req.expect_session_token()?;
1574 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1575
1576 if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
1577 let csrf_token = iexpect!(formdata.get("csrf_token"))[0].to_owned();
1578 let name = iexpect!(formdata.get("new_groupname"))[0].to_owned();
1579 let tag = iexpect!(formdata.get("new_grouptag"))[0].to_owned();
1580
1581 with_conn![core::admin_edit_group, C, req, group_id, &session_token, &csrf_token, name, tag].aug(req)?;
1582
1583 Ok(Response::with((status::Found, Redirect(url_for!(req, "group", "groupid" => format!("{}",group_id))))))
1584 } else {
1585 let (template, mut data) = with_conn![core::admin_show_edit_group, C, req, group_id, &session_token].aug(req)?;
1586
1587 data.insert("config".to_string(), to_json(&config.template_params));
1588
1589 let mut resp = Response::new();
1590 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1591 Ok(resp)
1592 }
1593}
1594
1595fn admin_group_new_groupcode<C>(req: &mut Request) -> IronResult<Response>
1596 where C: MedalConnection + std::marker::Send + 'static {
1597 let group_id = req.expect_int::<i64>("groupid")?;
1598 let session_token = req.expect_session_token()?;
1599
1600 let csrf_token = {
1601 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1602 iexpect!(formdata.get("csrf_token"))[0].to_owned()
1603 };
1604
1605 Ok(handle_json_response(with_conn![core::admin_group_new_groupcode,
1606 C,
1607 req,
1608 group_id,
1609 &session_token,
1610 &csrf_token]).aug(req)?)
1611}
1612
1613fn group_addadmin<C>(req: &mut Request) -> IronResult<Response>
1614 where C: MedalConnection + std::marker::Send + 'static {
1615 let group_id = req.expect_int::<i64>("groupid")?;
1616 let session_token = req.expect_session_token()?;
1617
1618 let (csrf_token, teacher_id) = {
1619 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
1620 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1621 iexpect!(formdata.get("teacherid").unwrap_or(&vec!["".to_owned()])[0].parse::<i64>().ok()))
1622 };
1623
1624 Ok(handle_json_response(with_conn![core::group_add_admin,
1625 C,
1626 req,
1627 group_id,
1628 teacher_id,
1629 &session_token,
1630 &csrf_token]).aug(req)?)
1631}
1632
1633fn group_deladmin<C>(req: &mut Request) -> IronResult<Response>
1634 where C: MedalConnection + std::marker::Send + 'static {
1635 let group_id = req.expect_int::<i64>("groupid")?;
1636 let user_id = req.expect_int::<i64>("userid")?;
1637 let session_token = req.expect_session_token()?;
1638
1639 let csrf_token = {
1640 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
1641 iexpect!(formdata.get("csrf_token"))[0].to_owned()
1642 };
1643
1644 Ok(handle_json_response(with_conn![core::group_delete_admin,
1645 C,
1646 req,
1647 group_id,
1648 user_id,
1649 &session_token,
1650 &csrf_token]).aug(req)?)
1651}
1652
1653fn admin_participation<C>(req: &mut Request) -> IronResult<Response>
1654 where C: MedalConnection + std::marker::Send + 'static {
1655 let user_id = req.expect_int::<i64>("userid")?;
1656 let contest_id = req.expect_int::<i64>("contestid")?;
1657 let session_token = req.expect_session_token()?;
1658 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1659
1660 let (csrf_token, new_user_id) = if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
1661 (formdata.get("csrf_token").map(|x| x[0].to_owned()),
1662 formdata.get("user_id").map(|x| x[0].parse::<i64>().unwrap_or(0)))
1663 } else {
1664 (None, None)
1665 };
1666
1667 if let Some(csrf_token) = csrf_token {
1668 if let Some(new_user_id) = new_user_id {
1669 return Ok(handle_json_response(with_conn![core::admin_move_participation_to_user,
1670 C,
1671 req,
1672 contest_id,
1673 user_id,
1674 new_user_id,
1675 &session_token,
1676 &csrf_token]).aug(req)?);
1677 } else {
1678 return Ok(handle_json_response(with_conn![core::admin_delete_participation,
1679 C,
1680 req,
1681 user_id,
1682 contest_id,
1683 &session_token,
1684 &csrf_token]).aug(req)?);
1685 }
1686 }
1687
1688 let (template, mut data) =
1689 with_conn![core::admin_show_participation, C, req, user_id, contest_id, &session_token].aug(req)?;
1690 data.insert("config".to_string(), to_json(&config.template_params));
1691
1692 let mut resp = Response::new();
1693 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1694 Ok(resp)
1695}
1696
1697fn admin_contests<C>(req: &mut Request) -> IronResult<Response>
1698 where C: MedalConnection + std::marker::Send + 'static {
1699 let session_token = req.expect_session_token()?;
1700 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1701
1702 let (template, mut data) = with_conn![core::admin_show_contests, C, req, &session_token].aug(req)?;
1703 data.insert("config".to_string(), to_json(&config.template_params));
1704
1705 let mut resp = Response::new();
1706 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1707 Ok(resp)
1708}
1709
1710fn admin_export_contest<C>(req: &mut Request) -> IronResult<Response>
1711 where C: MedalConnection + std::marker::Send + 'static {
1712 let contest_id = req.expect_int::<i64>("contestid")?;
1713 let session_token = req.expect_session_token()?;
1714
1715 let filename = with_conn![core::admin_contest_export, C, req, contest_id, &session_token].aug(req)?;
1716
1717 Ok(Response::with((status::Found, RedirectRaw(format!("/export/{}", filename)))))
1718}
1719
1720fn admin_cleanup<C>(req: &mut Request) -> IronResult<Response>
1721 where C: MedalConnection + std::marker::Send + 'static {
1722 let session_token = req.expect_session_token()?;
1723 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1724
1725 let csrf_token = if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
1726 formdata.get("csrf_token").map(|x| x[0].to_owned())
1727 } else {
1728 None
1729 };
1730
1731 if let Some(csrf_token) = csrf_token {
1732 let cleanup_type = req.get_str("type");
1733
1734 return Ok(handle_json_response(match cleanup_type.as_deref() {
1735 Some("session") => with_conn![core::do_session_cleanup, C, req,],
1736 _ => with_conn![core::admin_do_cleanup, C, req, &session_token, &csrf_token],
1737 }).aug(req)?);
1738 }
1739
1740 let (template, mut data) = with_conn![core::admin_show_cleanup, C, req, &session_token].aug(req)?;
1741 data.insert("config".to_string(), to_json(&config.template_params));
1742
1743 let mut resp = Response::new();
1744 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1745 Ok(resp)
1746}
1747
1748fn admin_housekeeping_index<C>(req: &mut Request) -> IronResult<Response>
1749 where C: MedalConnection + std::marker::Send + 'static {
1750 let session_token = req.expect_session_token()?;
1751 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1752
1753 let (template, mut data) = with_conn![core::housekeeping, C, req, &session_token].aug(req)?;
1754 data.insert("config".to_string(), to_json(&config.template_params));
1755 data.insert("self_url".to_string(), to_json(&config.self_url));
1756
1757 use iron::headers::Cookie;
1758 if let Some(&Cookie(ref cookies)) = req.headers.get::<Cookie>() {
1759 for cookie in cookies {
1760 if let Some(session_token) = cookie.strip_prefix("medal_session=") {
1761 data.insert("session_token_cookie".to_string(), to_json(&session_token));
1762 }
1763 }
1764 }
1765
1766 let mut resp = Response::new();
1767 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1768 Ok(resp)
1769}
1770
1771fn admin_move_task_location<C>(req: &mut Request) -> IronResult<Response>
1776 where C: MedalConnection + std::marker::Send + 'static {
1777 let session_token = req.expect_session_token()?;
1778
1779 let (csrf_token, old_location, new_location, contest) = {
1780 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1781 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1782 iexpect!(formdata.get("old_task_location"))[0].to_owned(),
1783 iexpect!(formdata.get("new_task_location"))[0].to_owned(),
1784 iexpect!(formdata.get("contest_location"))[0].to_owned())
1785 };
1786
1787 Ok(handle_json_response(with_conn![core::move_task_location,
1788 C,
1789 req,
1790 &session_token,
1791 &csrf_token,
1792 &old_location,
1793 &new_location,
1794 &contest]).aug(req)?)
1795}
1796
1797fn admin_move_task_contest<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_contest,
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 admin_move_contest_location<C>(req: &mut Request) -> IronResult<Response>
1830 where C: MedalConnection + std::marker::Send + 'static {
1831 let session_token = req.expect_session_token()?;
1832
1833 let (csrf_token, old_location, new_location) = {
1834 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1835 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1836 iexpect!(formdata.get("old_contest_location"))[0].to_owned(),
1837 iexpect!(formdata.get("new_contest_location"))[0].to_owned())
1838 };
1839
1840 Ok(handle_json_response(with_conn![core::move_contest_location,
1841 C,
1842 req,
1843 &session_token,
1844 &csrf_token,
1845 &old_location,
1846 &new_location]).aug(req)?)
1847}
1848
1849fn admin_move_task_submissions_grades<C>(req: &mut Request) -> IronResult<Response>
1854 where C: MedalConnection + std::marker::Send + 'static {
1855 let session_token = req.expect_session_token()?;
1856
1857 let (csrf_token, old_location, new_location, old_contest, new_contest) = {
1858 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1859 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1860 iexpect!(formdata.get("old_task_location"))[0].to_owned(),
1861 iexpect!(formdata.get("new_task_location"))[0].to_owned(),
1862 iexpect!(formdata.get("old_contest_location"))[0].to_owned(),
1863 iexpect!(formdata.get("new_contest_location"))[0].to_owned())
1864 };
1865
1866 Ok(handle_json_response(with_conn![core::move_task_submissions_grades,
1867 C,
1868 req,
1869 &session_token,
1870 &csrf_token,
1871 &old_location,
1872 &new_location,
1873 &old_contest,
1874 &new_contest]).aug(req)?)
1875}
1876
1877fn dbcleanup<C>(req: &mut Request) -> IronResult<Response>
1878 where C: MedalConnection + std::marker::Send + 'static {
1879 Ok(handle_json_response(with_conn![core::do_session_cleanup, C, req,]).aug(req)?)
1880}
1881
1882fn oauth<C>(req: &mut Request) -> IronResult<Response>
1883 where C: MedalConnection + std::marker::Send + 'static {
1884 #[cfg(feature = "debug")]
1885 println!("{:?}", req.url.query().unwrap_or(""));
1886
1887 let oauth_id = req.expect_str("oauthid")?;
1888 let school_id = req.get_str("schoolid");
1889
1890 let (oauth_provider, autoclean_submissions) = {
1891 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1892
1893 let mut result: Option<OauthProvider> = None;
1894
1895 if let Some(ref oauth_providers) = config.oauth_providers {
1896 for oauth_provider in oauth_providers {
1897 if oauth_provider.provider_id == oauth_id {
1898 result = Some(oauth_provider.clone());
1899 break;
1900 }
1901 }
1902
1903 if let Some(result) = result {
1904 (result, config.autoclean_submissions.unwrap_or(false))
1905 } else {
1906 return Ok(Response::with(iron::status::NotFound));
1907 }
1908 } else {
1909 return Ok(Response::with(iron::status::NotFound));
1910 }
1911 };
1912
1913 let user_data_result = match oauth_provider.medal_oauth_type.as_ref() {
1914 "pms" => oauth_pms(req, oauth_provider, school_id.as_ref()).aug(req)?,
1915 _ => return Ok(Response::with(iron::status::NotFound)),
1916 };
1917
1918 let user_data = match user_data_result {
1919 Err(response) => return Ok(response),
1920 Ok(user_data) => user_data,
1921 };
1922 let user_type = user_data.foreign_type;
1923
1924 let oauthloginresult = {
1925 let mutex = req.get::<Write<SharedDatabaseConnection<C>>>().unwrap();
1927 let conn = mutex.lock().unwrap_or_else(|e| e.into_inner());
1928
1929 core::login_oauth(&*conn, user_data, oauth_id, autoclean_submissions)
1931 };
1932
1933 match oauthloginresult {
1934 Ok((sessionkey, redirectprofile)) => {
1936 req.session().set(SessionToken { token: sessionkey }).unwrap();
1937
1938 use core::UserType;
1939 if user_type == UserType::User {
1940 if let Some(redirectprofile) = redirectprofile {
1941 Ok(Response::with((status::Found,
1942 Redirect(iron::Url::parse(&format!("{}?status={}",
1943 url_for!(req, "myprofile"),
1944 redirectprofile)).unwrap()))))
1945 } else {
1946 Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
1947 }
1948 } else {
1949 Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
1950 }
1951 }
1952 Err((template, data)) => {
1954 let mut resp = Response::new();
1955 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1956 Ok(resp)
1957 }
1958 }
1959}
1960
1961#[derive(Deserialize, Debug)]
1962struct OAuthAccess {
1963 access_token: String,
1964 #[allow(dead_code)]
1965 token_type: String,
1966 #[allow(dead_code)]
1967 refresh_token: String,
1968 #[allow(dead_code)]
1969 expires: Option<i32>, #[allow(dead_code)]
1971 expires_in: Option<i32>, }
1973
1974#[derive(Deserialize, Debug)]
1975#[allow(non_snake_case)]
1976#[serde(untagged)]
1977pub enum SchoolIdOrSchoolIds {
1978 #[allow(dead_code)]
1979 None(i32), #[allow(dead_code)]
1981 SchoolId(String),
1982 SchoolIds(Vec<String>),
1983}
1984
1985#[derive(Deserialize, Debug)]
1986#[allow(non_snake_case)]
1987pub struct OAuthUserData {
1988 userID: Option<String>, userId: Option<String>, userType: String,
1991 gender: String,
1992 firstName: String,
1993 lastName: String,
1994 #[allow(dead_code)]
1995 dateOfBirth: Option<String>,
1996 #[allow(dead_code)]
1997 eMail: Option<String>,
1998 schoolId: Option<SchoolIdOrSchoolIds>,
1999}
2000
2001#[derive(Deserialize, Debug)]
2002#[allow(non_snake_case)]
2003pub struct OAuthSchoolData {
2004 name: Option<String>,
2005 city: Option<String>,
2006 error: Option<String>,
2007}
2008
2009fn pms_hash_school(school_id: &str, secret: &str) -> String {
2010 use sha2::{Digest, Sha512};
2011 let mut hasher = Sha512::default();
2012 let string_to_hash = format!("{}{}", school_id, secret);
2013
2014 hasher.input(string_to_hash.as_bytes());
2015 let hashed_string = hasher.result();
2016
2017 format!("{:02X?}", hashed_string).chars().filter(|c| c.is_ascii_alphanumeric()).collect()
2018}
2019
2020fn oauth_pms(req: &mut Request, oauth_provider: OauthProvider, selected_school_id: Option<&String>)
2021 -> Result<Result<core::ForeignUserData, Response>, core::MedalError> {
2022 use core::{UserSex, UserType};
2023 use params::{Params, Value};
2024
2025 fn er(e: &str) -> core::MedalError { core::MedalError::OauthError(e.to_string()) }
2026 fn e<T>(e: &str) -> Result<T, core::MedalError> { Err::<T, _>(er(e)) }
2027
2028 let (_, _, code): (String, String, String) = {
2029 let map = req.get_ref::<Params>().unwrap();
2030
2031 match (map.find(&["state"]), map.find(&["scope"]), map.find(&["code"])) {
2032 (Some(&Value::String(ref state)), Some(&Value::String(ref scope)), Some(&Value::String(ref code)))
2033 if state == "42" =>
2034 {
2035 (state.clone(), scope.clone(), code.clone())
2036 }
2037 _ => return e("#70"),
2038 }
2039 };
2040
2041 let client = reqwest::Client::new();
2042 let params = [("code", code), ("grant_type", "authorization_code".to_string())];
2043
2044 let res = client.post(&oauth_provider.access_token_url)
2046 .basic_auth(oauth_provider.client_id, Some(oauth_provider.client_secret))
2047 .form(¶ms)
2048 .send();
2049 let access: OAuthAccess = res.or(e("#00"))?.json().or(e("#01"))?;
2050
2051 let res = client.get(&oauth_provider.user_data_url).bearer_auth(access.access_token).send();
2052 let mut user_data: OAuthUserData = res.or(e("#10"))?.json().or(e("#11"))?;
2053
2054 user_data.userId = user_data.userID.or(user_data.userId);
2056
2057 let user_type = match user_data.userType.as_ref() {
2058 "a" | "A" => UserType::Admin,
2059 "t" | "T" => UserType::Teacher,
2060 "s" | "S" => UserType::User,
2061 _ => UserType::User,
2062 };
2063 let user_sex = match user_data.gender.as_ref() {
2064 "m" | "M" => UserSex::Male,
2065 "f" | "F" | "w" | "W" => UserSex::Female,
2066 "?" => UserSex::Unknown,
2067 _ => UserSex::Unknown,
2068 };
2069 let mut school_name: Option<String> = None;
2070
2071 match (&user_data.schoolId, user_type) {
2072 (Some(SchoolIdOrSchoolIds::SchoolIds(_)), UserType::User) => return e("#70"),
2074 (Some(SchoolIdOrSchoolIds::SchoolId(_)), UserType::Teacher) => return e("#71"),
2078 (Some(SchoolIdOrSchoolIds::None(_)), UserType::Teacher) => return e("#72"),
2079 (None, UserType::Teacher) => {
2081 user_data.schoolId = Some(SchoolIdOrSchoolIds::SchoolIds(Vec::new()));
2082 }
2083
2084 _ => (),
2086 }
2087
2088 if let Some(SchoolIdOrSchoolIds::SchoolIds(school_ids)) = user_data.schoolId {
2090 if let Some(selected_school_id) = selected_school_id {
2092 if selected_school_id == "none" && oauth_provider.allow_teacher_login_without_school == Some(true) {
2093 }
2095 else if school_ids.contains(&selected_school_id) {
2097 if let Some(mut user_id) = user_data.userId {
2098 user_id.push('/');
2099 user_id.push_str(&selected_school_id);
2100 user_data.userId = Some(user_id);
2101
2102 if let (Some(school_data_url), Some(school_data_secret)) =
2105 (oauth_provider.school_data_url, oauth_provider.school_data_secret)
2106 {
2107 let params = [("schoolId", selected_school_id.clone()),
2108 ("hash", pms_hash_school(&selected_school_id, &school_data_secret))];
2109 let res = client.post(&school_data_url).form(¶ms).send();
2110 if let Ok(mut json) = res {
2111 let school_data_opt: Result<OAuthSchoolData, reqwest::Error> = json.json();
2112 if let Ok(school_data) = school_data_opt {
2113 school_name = school_data.name;
2114 }
2115 }
2116 }
2117 }
2118 } else {
2119 return e("#40");
2120 }
2121 } else {
2122 if let (Some(school_data_url), Some(school_data_secret)) =
2125 (oauth_provider.school_data_url, oauth_provider.school_data_secret)
2126 {
2127 let school_infos: Vec<(String, String)> =
2129 school_ids.iter()
2130 .map(|school_id| -> Result<(String, String), core::MedalError> {
2131 let params = [("schoolId", school_id.clone()),
2132 ("hash", pms_hash_school(&school_id, &school_data_secret))];
2133 let res = client.post(&school_data_url).form(¶ms).send();
2134 let school_data: OAuthSchoolData = res.or(e("#30"))?.json().or(e("#31"))?;
2135
2136 Ok((school_id.clone(),
2137 format!("{}, {}",
2138 school_data.name
2139 .or(school_data.error)
2140 .unwrap_or_else(|| "Information missing".to_string()),
2141 school_data.city.unwrap_or_else(|| "–".to_string()))))
2142 })
2143 .collect::<Result<_, _>>()?;
2144
2145 let mut data = json_val::Map::new();
2146 data.insert("schools".to_string(), to_json(&school_infos));
2147 data.insert("query".to_string(), to_json(&req.url.query().unwrap_or("")));
2148
2149 data.insert("parent".to_string(), to_json(&"base"));
2150 data.insert("disable_login_box".to_string(), to_json(&true));
2151
2152 data.insert("teacher_login_without_school".to_string(),
2153 to_json(&oauth_provider.allow_teacher_login_without_school.unwrap_or(false)));
2154
2155 let mut resp = Response::new();
2156 resp.set_mut(Template::new(&"oauth_school_selector", data)).set_mut(status::Ok);
2157 return Ok(Err(resp));
2158 } else {
2159 return Err(core::MedalError::ConfigurationError);
2161 }
2162 }
2163 } else if selected_school_id.is_some() {
2164 return e("#50");
2166 }
2167
2168 Ok(Ok(core::ForeignUserData { foreign_id: user_data.userId.ok_or(er("#60"))?,
2169 foreign_type: user_type,
2170 sex: user_sex,
2171 firstname: user_data.firstName,
2172 lastname: user_data.lastName,
2173 school_name }))
2174}
2175
2176#[derive(Copy, Clone)]
2178pub struct SharedDatabaseConnection<C>
2179 where C: MedalConnection
2180{
2181 phantom: std::marker::PhantomData<C>,
2182}
2183impl<C> Key for SharedDatabaseConnection<C> where C: MedalConnection + 'static
2184{
2185 type Value = C;
2186}
2187
2188#[derive(Copy, Clone)]
2190pub struct SharedConfiguration;
2191impl Key for SharedConfiguration {
2192 type Value = Config;
2193}
2194
2195#[cfg(feature = "watch")]
2196pub fn get_handlebars_engine(template_name: &str) -> impl AfterMiddleware {
2197 let mut hbse = HandlebarsEngine::new();
2199 hbse.add(Box::new(DirectorySource::new(&format!("./templates/{}/", template_name) as &str, ".hbs")));
2200
2201 if let Err(r) = hbse.reload() {
2203 panic!("{}", r);
2204 }
2205
2206 use handlebars_iron::Watchable;
2207 use std::sync::Arc;
2208
2209 let hbse_ref = Arc::new(hbse);
2210 hbse_ref.watch("./templates/");
2211 hbse_ref
2212}
2213
2214#[cfg(not(feature = "watch"))]
2215pub fn get_handlebars_engine(template_name: &str) -> impl AfterMiddleware {
2216 let mut hbse = HandlebarsEngine::new();
2218 hbse.add(Box::new(DirectorySource::new(&format!("./templates/{}/", template_name) as &str, ".hbs")));
2219
2220 if let Err(r) = hbse.reload() {
2222 panic!("{}", r);
2223 }
2224
2225 hbse
2226}
2227
2228fn cookie_warning(req: &mut Request) -> IronResult<Response> {
2229 match req.get_session_token() {
2230 Some(_session_token) => {
2231 Ok(Response::with((status::Found, RedirectRaw(format!("/{}", req.url.query().unwrap_or(""))))))
2234 }
2235 None => {
2236 let mut resp = Response::new();
2237 resp.set_mut(Template::new("cookie", json_val::Map::new())).set_mut(status::Ok);
2238 Ok(resp)
2239 }
2240 }
2241}
2242
2243pub fn start_server<C>(conn: C, config: Config) -> iron::error::HttpResult<iron::Listening>
2244 where C: MedalConnection + std::marker::Send + 'static {
2245 let router = router!(
2246 greet: get "/" => greet_personal::<C>,
2247 contests: get "/contest/" => contests::<C>,
2248 contests_or_contest: get "/contest/:categoryorcontestid" => contests_or_contest::<C>,
2249 contests_or_contest_secret: get "/contest/:categoryorcontestid/:contestidorsecret" => contests_or_contest::<C>,
2250 contest_secret: get "/contest/:category/:contestid/:secret" => contest::<C>,
2251 contestresults: get "/contest/:contestid/result/" => contestresults::<C>,
2252 contestresults_download: get "/contest/:contestid/result/download" => contestresults_download::<C>,
2253 contestresults_category: get "/contest/:category/:contestid/result/" => contestresults::<C>,
2254 contestresults_category_download: get "/contest/:category/:contestid/result/download" => contestresults_download::<C>,
2255 contest_post: post "/contest/:contestid" => contest_post::<C>,
2256 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>,
2259 login_post: post "/login" => login_post::<C>,
2260 login_code_post: post "/clogin" => login_code_post::<C>,
2261 login_webauthn: get "/webauthn_login" => login_webauthn::<C>,
2262 login_webauthn_post: post "/webauthn_login" => login_webauthn_post::<C>,
2263 register_webauthn: get "/webauthn_register" => register_webauthn::<C>,
2264 register_webauthn_post: post "/webauthn_register" => register_webauthn_post::<C>,
2265 delete_webauthn_post: post "/webauthn_delete" => delete_webauthn_post::<C>,
2266 logout: get "/logout" => logout::<C>,
2267 signup: get "/signup" => signup::<C>,
2268 signup_post: post "/signup" => signup_post::<C>,
2269 subm_load: get "/load/:taskid" => submission::<C>,
2270 subm_save: post "/save/:taskid" => submission_post::<C>,
2271 groups: get "/group/" => groups::<C>,
2272 groups: post "/group/" => new_group::<C>,
2273 group: get "/group/:groupid" => admin_group::<C>,
2274 group_post: post "/group/:groupid" => admin_group::<C>,
2275 group_edit: get "/group/:groupid/edit" => admin_group_edit::<C>,
2276 group_edit_post: post "/group/:groupid/edit" => admin_group_edit::<C>,
2277 group_addadmin: post "/group/:groupid/addadmin" => group_addadmin::<C>,
2278 group_deladmin: post "/group/:groupid/deladmin/:userid" => group_deladmin::<C>,
2279 group_download: get "/group/download/:groupid" => group_download::<C>,
2280 groupcsv: get "/group/csv" => group_csv::<C>,
2282 groupcsv_post: post "/group/csv" => group_csv_upload::<C>,
2283 myprofile: get "/profile" => profile::<C>,
2284 myprofile_post: post "/profile" => profile_post::<C>,
2285 myprofile_check: post "/profile/check" => profile_check::<C>,
2286 user: get "/user/:userid" => admin_user::<C>,
2287 user_post: post "/user/:userid" => admin_user::<C>,
2288 profile: get "/profile/:userid" => user::<C>,
2289 profile_post: post "/profile/:userid" => user_post::<C>,
2290 task: get "/task/:taskid" => task::<C>,
2291 task_review_solution: get "/task/:taskid/:submissionid" => review::<C>,
2292 preview_task: get "/preview/:taskid" => preview::<C>,
2293 teacher: get "/teacher" => teacherinfos::<C>,
2294 admin: get "/admin" => admin::<C>,
2295 admin_users: post "/admin/user/" => admin_users::<C>,
2296 admin_user: get "/admin/user/:userid" => admin_user::<C>,
2297 admin_user_post: post "/admin/user/:userid" => admin_user::<C>,
2298 admin_user_new_logincode: post "/admin/user/:userid/new_logincode" => admin_user_new_logincode::<C>,
2299 admin_group: get "/admin/group/:groupid" => admin_group::<C>,
2300 admin_group_post: post "/admin/group/:groupid" => admin_group::<C>,
2301 admin_group_edit: get "/admin/group/:groupid/edit" => admin_group_edit::<C>,
2302 admin_group_edit_post: post "/admin/group/:groupid/edit" => admin_group_edit::<C>,
2303 admin_group_new_groupcode: post "/admin/group/:groupid/new_groupcode" => admin_group_new_groupcode::<C>,
2304 admin_participation: get "/admin/user/:userid/:contestid" => admin_participation::<C>,
2305 admin_participation_post: post "/admin/user/:userid/:contestid" => admin_participation::<C>,
2306 admin_contests: get "/admin/contest/" => admin_contests::<C>,
2307 admin_contest_admissioncsv: get "/admin/contest/:contestid/csv" => contest_resultcsv::<C>,
2308 admin_contest_admissioncsv_post: post "/admin/contest/:contestid/csv" => contest_resultcsv_upload::<C>,
2309 admin_export_contest: get "/admin/contest/:contestid/export" => admin_export_contest::<C>,
2310 admin_cleanup: get "/admin/cleanup" => admin_cleanup::<C>,
2311 admin_cleanup_post: post "/admin/cleanup/:type" => admin_cleanup::<C>,
2312 admin_housekeeping_index: get "/admin/housekeeping" => admin_housekeeping_index::<C>,
2313 admin_move_task_location_post: post "/admin/housekeeping/move_task_location" => admin_move_task_location::<C>,
2314 admin_move_task_contest_post: post "/admin/housekeeping/move_task_contest" => admin_move_task_contest::<C>,
2315 admin_move_contest_location_post: post "/admin/housekeeping/move_contest_location" => admin_move_contest_location::<C>,
2316 admin_move_task_submissions_grades_post: post "/admin/housekeeping/move_task_submissions_grades" => admin_move_task_submissions_grades::<C>,
2317 oauth: get "/oauth/:oauthid/" => oauth::<C>,
2318 oauth_school: get "/oauth/:oauthid/:schoolid" => oauth::<C>,
2319 check_cookie: get "/cookie" => cookie_warning,
2320 dbstatus: get "/dbstatus" => dbstatus::<C>,
2321 status: get "/status" => dbstatus::<C>,
2322 dbcleanup: get "/cleanup" => dbcleanup::<C>,
2323 debug: get "/debug" => debug::<C>,
2324 debug_reset: get "/debug/reset" => debug_new_token::<C>,
2325 debug_logout: get "/debug/logout" => debug_logout::<C>,
2326 debug_create: get "/debug/create" => debug_create_session::<C>,
2327 );
2328
2329 let mut mount = Mount::new();
2330
2331 mount.mount("/static/", Static::new(Path::new("static")));
2333 mount.mount("/export/", Static::new(Path::new("export")));
2334 mount.mount("/tasks/", Static::new(Path::new(TASK_DIR)));
2335 mount.mount("/", router);
2336
2337 let mut ch = Chain::new(mount);
2338
2339 #[cfg(feature = "debug")]
2340 ch.link_before(RequestLogger {});
2341
2342 ch.link(Write::<SharedDatabaseConnection<C>>::both(conn));
2343 ch.link(Read::<SharedConfiguration>::both(config.clone()));
2344
2345 ch.link_around(RequestTimeLogger {});
2346 ch.link_around(CookieDistributor {});
2347 ch.link_around(SessionStorage::new(SignedCookieBackend::new(config.cookie_signing_secret.expect("Cookie signing secret not found in configuration").into_bytes())));
2348
2349 ch.link_after(get_handlebars_engine(&config.template.unwrap_or_else(|| "default".to_string())));
2350 ch.link_after(ErrorReporter);
2351 ch.link_after(ErrorShower);
2352
2353 let socket_addr = format!("{}:{}", config.host.unwrap(), config.port.unwrap());
2354
2355 let srvr = Iron::new(ch).http(&socket_addr);
2356 print!("Listening on {} … ", socket_addr);
2357 srvr
2358}