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 if query_string.contains("open") {
466 core::ContestVisibility::Open
467 } else if query_string.contains("current") {
468 core::ContestVisibility::Current
469 } else if query_string.contains("challenge") {
470 core::ContestVisibility::LoginRequired
471 } else if query_string.contains("standalone_task") {
472 core::ContestVisibility::StandaloneTask
473 } else {
474 core::ContestVisibility::All
475 };
476
477 let config = req.get::<Read<SharedConfiguration>>().unwrap();
478
479 let is_results = query_string.contains("results");
480 let res = with_conn![core::show_contests,
481 C,
482 req,
483 &session_token,
484 category.clone(),
485 login_info(&config),
486 visibility,
487 is_results];
488
489 if res.is_err() {
490 println!("DATABASE CONNECTION LOST! Restarting database connection.");
493 let conn = C::reconnect(&config);
494 let mutex = req.get::<Write<SharedDatabaseConnection<C>>>().unwrap();
495 let mut sharedconn = mutex.lock().unwrap_or_else(|e| e.into_inner());
496 *sharedconn = conn;
497 }
499
500 let (template, mut data) = res.unwrap();
501 data.insert("config".to_string(), to_json(&config.template_params));
502 data.insert("category".to_string(), to_json(&category));
503
504 if let Some(categoryname) = category {
505 if let Some(template_params) = config.template_params.as_ref() {
506 if let Some(categories) = template_params.get("categories") {
507 if let Some(currentcategory) = categories.get(categoryname) {
508 data.insert("current_category".to_string(), currentcategory.clone());
509 }
510 }
511 }
512 }
513
514 if is_results {
515 data.insert("direct_link_to_results".to_string(), to_json(&true));
516 }
517
518 let mut resp = Response::new();
519 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
520 Ok(resp)
521}
522
523fn contest<C>(req: &mut Request) -> IronResult<Response>
524 where C: MedalConnection + std::marker::Send + 'static {
525 let contest_id = req.expect_int::<i32>("contestid")?;
526 let category = req.get_str("category");
527 let secret = req.get_str("secret");
528 let session_token = req.require_session_token()?;
529 let query_string = req.url.query().map(|s| s.to_string());
530
531 let config = req.get::<Read<SharedConfiguration>>().unwrap();
532 let res = with_conn![core::show_contest,
533 C,
534 req,
535 contest_id,
536 &session_token,
537 query_string,
538 login_info(&config),
539 secret].aug(req)?;
540
541 match res {
542 Err(task_id) => {
543 Ok(Response::with((status::Found, Redirect(url_for!(req, "task", "taskid" => format!("{}",task_id))))))
544 }
545 Ok((template, mut data)) => {
546 data.insert("config".to_string(), to_json(&config.template_params));
547 data.insert("category".to_string(), to_json(&category));
548
549 if let Some(categoryname) = category {
550 if let Some(template_params) = config.template_params.as_ref() {
551 if let Some(categories) = template_params.get("categories") {
552 if let Some(currentcategory) = categories.get(categoryname) {
553 data.insert("current_category".to_string(), currentcategory.clone());
554 }
555 }
556 }
557 }
558
559 let mut resp = Response::new();
560 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
561 Ok(resp)
562 }
563 }
564}
565
566fn contests_or_contest<C>(req: &mut Request) -> IronResult<Response>
567 where C: MedalConnection + std::marker::Send + 'static {
568 let firstparam_string = req.get_str("categoryorcontestid");
569 let firstparam_numeric = req.get_int::<i32>("categoryorcontestid");
570
571 let secondparam_string = req.get_str("contestidorsecret");
572 let secondparam_numeric = req.get_int::<i32>("contestidorsecret");
573
574 if firstparam_numeric.is_some() {
576 let router_map = req.extensions.get_mut::<Router>().unwrap();
577
578 router_map.insert("contestid".to_string(), firstparam_string.unwrap());
579
580 if let Some(secondparam_string) = secondparam_string {
581 router_map.insert("secret".to_string(), secondparam_string);
582 }
583
584 contest::<C>(req)
585 } else if secondparam_numeric.is_some() {
586 let router_map = req.extensions.get_mut::<Router>().unwrap();
587
588 router_map.insert("category".to_string(), firstparam_string.unwrap());
589 router_map.insert("contestid".to_string(), secondparam_string.unwrap());
590
591 contest::<C>(req)
592 } else if secondparam_string.is_none() {
593 let router_map = req.extensions.get_mut::<Router>().unwrap();
594
595 router_map.insert("category".to_string(), firstparam_string.unwrap());
596
597 contests::<C>(req)
598 } else {
599 Err(core::MedalError::NotFound).aug(req).map_err(|x| x.into())
600 }
601}
602
603fn contestresults<C>(req: &mut Request) -> IronResult<Response>
604 where C: MedalConnection + std::marker::Send + 'static {
605 let config = req.get::<Read<SharedConfiguration>>().unwrap();
606 let disable_contest_results = config.disable_results_page.unwrap_or(false);
607
608 if disable_contest_results {
609 let mut resp = Response::new();
610 resp.set_mut(Template::new(&"nocontestresults", 2)).set_mut(status::Locked);
611 return Ok(resp);
612 }
613
614 let contest_id = req.expect_int::<i32>("contestid")?;
615 let session_token = req.require_session_token()?;
616
617 let (template, data) = with_conn![core::show_contest_results, C, req, contest_id, &session_token].aug(req)?;
618
619 let mut resp = Response::new();
620 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
621 Ok(resp)
622}
623
624fn contestresults_download<C>(req: &mut Request) -> IronResult<Response>
625 where C: MedalConnection + std::marker::Send + 'static {
626 let config = req.get::<Read<SharedConfiguration>>().unwrap();
627 let disable_contest_results = config.disable_results_page.unwrap_or(false);
628
629 if disable_contest_results {
630 let mut resp = Response::new();
631 resp.set_mut(Template::new(&"nocontestresults", 2)).set_mut(status::Locked);
632 return Ok(resp);
633 }
634
635 let contest_id = req.expect_int::<i32>("contestid")?;
636 let session_token = req.require_session_token()?;
637
638 let (template, data) = with_conn![core::show_contest_results, C, req, contest_id, &session_token].aug(req)?;
639
640 use iron::headers::{Charset, ContentDisposition, DispositionParam, DispositionType};
641
642 let cd = ContentDisposition { disposition: DispositionType::Attachment,
643 parameters: vec![DispositionParam::Filename(
644 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(), )] };
649
650 let mime: Mime = "text/csv; charset=UTF-8".parse().unwrap();
651 let mut resp = Response::new();
652 resp.headers.set(cd);
653 resp.set_mut(Template::new(&format!("{}_download", template), data)).set_mut(status::Ok).set_mut(mime);
654 Ok(resp)
655}
656
657fn contest_post<C>(req: &mut Request) -> IronResult<Response>
658 where C: MedalConnection + std::marker::Send + 'static {
659 let (contest_id, category) = {
660 let potential_contest_id_1 = req.expect_int::<i32>("contestid");
661 let potential_contest_id_2 = req.expect_int::<i32>("categoryorcontestid");
662 let potential_contest_id_3 = req.expect_int::<i32>("contestidorsecret");
663
664 let potential_category_1 = req.get_str("category");
665 let potential_category_3 = req.get_str("categoryorcontestid");
666
667 match (potential_contest_id_1, potential_contest_id_2) {
668 (Ok(contest_id), _) => (contest_id, potential_category_1),
669 (Err(_), Ok(contest_id)) => (contest_id, None),
670 _ => (potential_contest_id_3?, potential_category_3),
671 }
672 };
673
674 let session_token = req.expect_session_token()?;
675
676 let (csrf_token, secret, teampartner_logincode) = {
677 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
678 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
679 formdata.get("secret").map(|x| x[0].to_owned()),
680 formdata.get("teampartner_logincode").map(|x| x[0].to_owned()))
681 };
682
683 let res = with_conn![core::start_contest,
684 C,
685 req,
686 contest_id,
687 &session_token,
688 &csrf_token,
689 secret,
690 teampartner_logincode].aug(req)?;
691
692 if let Err(value) = res {
693 if let Some(category) = category {
694 Ok(Response::with((status::Found,
695 Redirect(iron::Url::parse(&format!("{}?team_participation={}",
696 &url_for!(req, "contests_or_contest_secret",
697 "categoryorcontestid" => format!("{}",category),
698 "contestidorsecret" => format!("{}",contest_id)),
699 value)).unwrap()))))
700 } else {
701 Ok(Response::with((status::Found, Redirect(
702 iron::Url::parse(&format!("{}?team_participation={}",
703 &url_for!(req, "contests_or_contest", "categoryorcontestid" => format!("{}", contest_id)),
704 value
705 )).unwrap()))))
706 }
707 } else if let Some(category) = category {
708 Ok(Response::with((status::Found,
709 Redirect(url_for!(req, "contests_or_contest_secret",
710 "categoryorcontestid" => format!("{}",category),
711 "contestidorsecret" => format!("{}",contest_id))))))
712 } else {
713 Ok(Response::with((status::Found,
714 Redirect(url_for!(req, "contests_or_contest", "categoryorcontestid" => format!("{}",contest_id))))))
715 }
716}
717
718fn login<C>(req: &mut Request) -> IronResult<Response>
719 where C: MedalConnection + std::marker::Send + 'static {
720 let session_token = req.get_session_token();
721
722 let config = req.get::<Read<SharedConfiguration>>().unwrap();
723 let (template, mut data) = with_conn![core::show_login, C, req, session_token, login_info(&config)];
724
725 let query_string = req.url.query().map(|s| s.to_string());
726 if let Some(query) = query_string {
727 data.insert("forward".to_string(), to_json(&query));
728 }
729
730 let mut resp = Response::new();
732 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
733 Ok(resp)
734}
735
736fn login_post<C>(req: &mut Request) -> IronResult<Response>
737 where C: MedalConnection + std::marker::Send + 'static {
738 let logindata = {
739 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
740 (iexpect!(formdata.get("username"))[0].to_owned(), iexpect!(formdata.get("password"))[0].to_owned())
741 };
742
743 let config = req.get::<Read<SharedConfiguration>>().unwrap();
744 let loginresult = with_conn![core::login, C, req, logindata, login_info(&config)];
746
747 match loginresult {
748 Ok(sessionkey) => {
750 req.session().set(SessionToken { token: sessionkey }).unwrap();
751 Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
752 }
753 Err((template, data)) => {
755 let mut resp = Response::new();
756 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
757 Ok(resp)
758 }
759 }
760}
761
762fn login_code_post<C>(req: &mut Request) -> IronResult<Response>
763 where C: MedalConnection + std::marker::Send + 'static {
764 let code = {
765 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
766 iexpect!(formdata.get("code"))[0].to_owned()
767 };
768
769 let config = req.get::<Read<SharedConfiguration>>().unwrap();
770 let loginresult = with_conn![core::login_with_code, C, req, &code, login_info(&config)];
772
773 match loginresult {
774 Ok(Ok(sessionkey)) => {
776 req.session().set(SessionToken { token: sessionkey }).unwrap();
777 Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
778 }
779 Ok(Err(sessionkey)) => {
780 req.session().set(SessionToken { token: sessionkey }).unwrap();
781 Ok(Response::with((status::Found,
783 Redirect(iron::Url::parse(&format!("{}?status=firstlogin",
784 &url_for!(req, "myprofile"))).unwrap()))))
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_webauthn<C>(req: &mut Request) -> IronResult<Response>
796 where C: MedalConnection + std::marker::Send + 'static {
797 let self_url = {
799 let config = req.get::<Read<SharedConfiguration>>().unwrap();
800 config.self_url.clone().ok_or(core::MedalError::WebauthnError).aug(req)?
801 };
802
803 Ok(handle_json_response_simple(with_conn![core::login_with_key_challenge, C, req, &self_url]))
804}
805
806fn login_webauthn_post<C>(req: &mut Request) -> IronResult<Response>
807 where C: MedalConnection + std::marker::Send + 'static {
808 let (auth_id, credential) = {
809 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
810 (iexpect!(formdata.get("id"))[0].parse::<i32>().unwrap_or(0),
811 iexpect!(formdata.get("credential"))[0].to_owned())
812 };
813
814 let self_url = {
816 let config = req.get::<Read<SharedConfiguration>>().unwrap();
817 config.self_url.clone().ok_or(core::MedalError::WebauthnError).aug(req)?
818 };
819
820 let loginresult = with_conn![core::login_with_key, C, req, &self_url, auth_id, &credential];
822
823 match loginresult {
824 Ok(sessionkey) => {
826 req.session().set(SessionToken { token: sessionkey }).unwrap();
827
828 let mut resp = Response::new();
829 resp.set_mut("").set_mut(status::Ok);
830 Ok(resp)
831 }
832 Err(msg) => {
834 let mut resp = Response::new();
835 resp.set_mut(msg).set_mut(status::Forbidden);
836 Ok(resp)
837 }
838 }
839}
840
841fn register_webauthn<C>(req: &mut Request) -> IronResult<Response>
842 where C: MedalConnection + std::marker::Send + 'static {
843 let session_token = req.expect_session_token()?;
844
845 let self_url = {
847 let config = req.get::<Read<SharedConfiguration>>().unwrap();
848 config.self_url.clone().ok_or(core::MedalError::WebauthnError).aug(req)?
849 };
850
851 Ok(handle_json_response(with_conn![core::register_key_challenge, C, req, &self_url, &session_token]).aug(req)?)
852}
853
854fn register_webauthn_post<C>(req: &mut Request) -> IronResult<Response>
855 where C: MedalConnection + std::marker::Send + 'static {
856 let session_token = req.expect_session_token()?;
857
858 let (csrf_token, credential, name) = {
859 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
860 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
861 iexpect!(formdata.get("credential"))[0].to_owned(),
862 iexpect!(formdata.get("name"))[0].to_owned())
863 };
864
865 let self_url = {
867 let config = req.get::<Read<SharedConfiguration>>().unwrap();
868 config.self_url.clone().ok_or(core::MedalError::WebauthnError).aug(req)?
869 };
870
871 Ok(handle_json_response(with_conn![core::register_key,
872 C,
873 req,
874 &self_url,
875 &session_token,
876 &csrf_token,
877 credential,
878 name]).aug(req)?)
879}
880
881fn delete_webauthn_post<C>(req: &mut Request) -> IronResult<Response>
882 where C: MedalConnection + std::marker::Send + 'static {
883 let session_token = req.expect_session_token()?;
884 let (csrf_token, token_id) = {
885 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
886 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
887 iexpect!(formdata.get("id"))[0].parse::<i32>().unwrap_or(0))
888 };
889
890 Ok(handle_json_response(with_conn![core::delete_key, C, req, &session_token, &csrf_token, token_id]).aug(req)?)
891}
892
893fn logout<C>(req: &mut Request) -> IronResult<Response>
894 where C: MedalConnection + std::marker::Send + 'static {
895 let session_token = req.get_session_token();
896
897 #[cfg(feature = "debug")]
898 println!("Logging out session {:?}", session_token);
899
900 with_conn![core::logout, C, req, session_token];
901
902 let query_string = req.url.query().map(|s| s.to_string());
903 if let Some(query) = query_string {
904 if query.contains("redirect=login") {
905 return Ok(Response::with((status::Found, Redirect(url_for!(req, "login")))));
906 }
907 }
908
909 Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
910}
911
912#[cfg(feature = "signup")]
913#[allow(clippy::extra_unused_type_parameters)]
914fn signup<C>(req: &mut Request) -> IronResult<Response>
915 where C: MedalConnection + std::marker::Send + 'static {
916 let query_string = req.url.query().map(|s| s.to_string());
917
918 let data = core::signupdata(query_string);
919 let mut resp = Response::new();
920 resp.set_mut(Template::new("signup", data)).set_mut(status::Ok);
921 Ok(resp)
922}
923
924#[cfg(feature = "signup")]
925fn signup_post<C>(req: &mut Request) -> IronResult<Response>
926 where C: MedalConnection + std::marker::Send + 'static {
927 let session_token = req.get_session_token();
928 let signupdata = {
929 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
930 (iexpect!(formdata.get("username"))[0].to_owned(),
931 iexpect!(formdata.get("email"))[0].to_owned(),
932 iexpect!(formdata.get("password"))[0].to_owned())
933 };
934
935 let signupresult = with_conn![core::signup, C, req, session_token, signupdata].aug(req)?;
936 match signupresult {
937 SignupResult::SignedUp => Ok(Response::with((status::Found,
938 Redirect(iron::Url::parse(&format!("{}?status={:?}",
939 &url_for!(req,
940 "myprofile"),
941 signupresult)).unwrap())))),
942 _ => Ok(Response::with((status::Found,
943 Redirect(iron::Url::parse(&format!("{}?status={:?}",
944 &url_for!(req, "signup"),
945 signupresult)).unwrap())))),
946 }
947}
948
949#[cfg(not(feature = "signup"))]
950#[allow(clippy::extra_unused_type_parameters)]
951fn signup<C>(req: &mut Request) -> IronResult<Response>
952 where C: MedalConnection + std::marker::Send + 'static {
953 Err(core::MedalError::NotFound).aug(req).map_err(|x| x.into())
954}
955
956#[cfg(not(feature = "signup"))]
957#[allow(clippy::extra_unused_type_parameters)]
958fn signup_post<C>(req: &mut Request) -> IronResult<Response>
959 where C: MedalConnection + std::marker::Send + 'static {
960 Err(core::MedalError::NotFound).aug(req).map_err(|x| x.into())
961}
962
963fn submission<C>(req: &mut Request) -> IronResult<Response>
964 where C: MedalConnection + std::marker::Send + 'static {
965 let task_id = req.expect_int::<i32>("taskid")?;
966
967 let session_token = req.expect_session_token()?;
968 let subtask: Option<String> = (|| -> Option<String> {
969 req.get_ref::<UrlEncodedQuery>().ok()?.get("subtask")?.first().map(|x| x.to_owned())
970 })();
971
972 let submission: Option<i32> = (|| -> Option<i32> {
973 req.get_ref::<UrlEncodedQuery>().ok()?.get("submission")?.first().and_then(|x| x.parse::<i32>().ok())
974 })();
975
976 let result = with_conn![core::load_submission, C, req, task_id, &session_token, subtask, submission];
977
978 match result {
979 Ok(data) => Ok(Response::with((status::Ok, mime!(Application / Json), data))),
980 Err(_) => Ok(Response::with((status::BadRequest, mime!(Application / Json), "{}".to_string()))),
981 }
982}
983
984fn submission_post<C>(req: &mut Request) -> IronResult<Response>
985 where C: MedalConnection + std::marker::Send + 'static {
986 let task_id = req.expect_int::<i32>("taskid")?;
987 let session_token = req.expect_session_token()?;
988 let (csrf_token, data, grade, autosave, subtask) = {
989 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
990 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
991 iexpect!(formdata.get("data"))[0].to_owned(),
992 formdata.get("grade").map(|x| x[0].parse::<i32>().unwrap_or(0)).unwrap_or(0),
993 formdata.get("autosave").map(|x| x[0].parse::<bool>().unwrap_or(false)).unwrap_or(false),
994 formdata.get("subtask").map(|x| x[0].to_owned()))
995 };
996
997 #[cfg(feature = "debug")]
998 println!("New submission for task {} (graded {}): {}", task_id, grade, data);
999
1000 let result = with_conn![core::save_submission,
1001 C,
1002 req,
1003 task_id,
1004 &session_token,
1005 &csrf_token,
1006 data,
1007 grade,
1008 autosave,
1009 subtask].aug(req)?;
1010
1011 Ok(Response::with((status::Ok, mime!(Application / Json), result)))
1012}
1013
1014fn task<C>(req: &mut Request) -> IronResult<Response>
1015 where C: MedalConnection + std::marker::Send + 'static {
1016 let task_id = req.expect_int::<i32>("taskid")?;
1017 let session_token = req.require_session_token()?;
1018
1019 let autosaveinterval = {
1021 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1022 config.auto_save_interval.unwrap_or(10)
1023 };
1024
1025 match with_conn![core::show_task, C, req, task_id, &session_token, autosaveinterval].aug(req)? {
1026 Ok((template, data)) => {
1027 let mut resp = Response::new();
1028 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1029 Ok(resp)
1030 }
1031 Err((contest_id, category)) => {
1032 if let Some(category) = category {
1034 Ok(Response::with((status::Found,
1035 Redirect(url_for!(req, "contests_or_contest_secret",
1036 "categoryorcontestid" => format!("{}",category),
1037 "contestidorsecret" => format!("{}",contest_id))))))
1038 } else {
1039 Ok(Response::with((status::Found,
1040 Redirect(url_for!(req, "contests_or_contest", "categoryorcontestid" => format!("{}",contest_id))))))
1041 }
1042 }
1043 }
1044}
1045
1046fn review<C>(req: &mut Request) -> IronResult<Response>
1047 where C: MedalConnection + std::marker::Send + 'static {
1048 let task_id = req.expect_int::<i32>("taskid")?;
1049 let submission_id = req.expect_int::<i32>("submissionid")?;
1050 let session_token = req.require_session_token()?;
1051
1052 match with_conn![core::review_task, C, req, task_id, &session_token, submission_id].aug(req)? {
1053 Ok((template, data)) => {
1054 let mut resp = Response::new();
1055 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1056 Ok(resp)
1057 }
1058 Err(contest_id) => {
1059 Ok(Response::with((status::Found,
1061 Redirect(url_for!(req, "contest", "contestid" => format!("{}",contest_id))))))
1062 }
1063 }
1064}
1065
1066fn preview<C>(req: &mut Request) -> IronResult<Response>
1067 where C: MedalConnection + std::marker::Send + 'static {
1068 let task_id = req.expect_int::<i32>("taskid")?;
1069
1070 match with_conn![core::preview_task, C, req, task_id].aug(req)? {
1071 Ok((template, data)) => {
1072 let mut resp = Response::new();
1073 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1074 Ok(resp)
1075 }
1076 Err(contest_id) => {
1077 Ok(Response::with((status::Found,
1079 Redirect(url_for!(req, "contest", "contestid" => format!("{}",contest_id))))))
1080 }
1081 }
1082}
1083
1084fn groups<C>(req: &mut Request) -> IronResult<Response>
1085 where C: MedalConnection + std::marker::Send + 'static {
1086 let session_token = req.require_session_token()?;
1087 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1088
1089 let (template, mut data) = with_conn![core::show_groups, C, req, &session_token].aug(req)?;
1090 data.insert("config".to_string(), to_json(&config.template_params));
1091
1092 let mut resp = Response::new();
1093 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1094 Ok(resp)
1095}
1096
1097fn group_download<C>(req: &mut Request) -> IronResult<Response>
1098 where C: MedalConnection + std::marker::Send + 'static {
1099 let group_id = req.expect_int::<i32>("groupid")?;
1100 let session_token = req.require_session_token()?;
1101
1102 let (template, data) = with_conn![core::show_group, C, req, group_id, &session_token].aug(req)?;
1103
1104 use iron::headers::{Charset, ContentDisposition, DispositionParam, DispositionType};
1105
1106 let cd = ContentDisposition { disposition: DispositionType::Attachment,
1107 parameters: vec![DispositionParam::Filename(
1108 Charset::Ext("Utf-8".to_string()), None, format!("{}.csv", data.get("groupname").unwrap().as_str().unwrap()).as_bytes().to_vec(), )] };
1113
1114 let mime: Mime = "text/csv".parse().unwrap();
1115 let mut resp = Response::new();
1116 resp.headers.set(cd);
1117 resp.set_mut(Template::new(&format!("{}_download", template), data)).set_mut(status::Ok).set_mut(mime);
1118 Ok(resp)
1119}
1120
1121fn new_group<C>(req: &mut Request) -> IronResult<Response>
1122 where C: MedalConnection + std::marker::Send + 'static {
1123 let session_token = req.require_session_token()?;
1124
1125 let (csrf_token, name, tag) = {
1126 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
1127 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1128 iexpect!(formdata.get("name"))[0].to_owned(),
1129 iexpect!(formdata.get("tag"))[0].to_owned())
1130 };
1131
1132 let group_id = with_conn![core::add_group, C, req, &session_token, &csrf_token, name, tag].aug(req)?;
1133
1134 Ok(Response::with((status::Found, Redirect(url_for!(req, "group", "groupid" => format!("{}",group_id))))))
1135}
1136
1137fn group_csv<C>(req: &mut Request) -> IronResult<Response>
1138 where C: MedalConnection + std::marker::Send + 'static {
1139 let session_token = req.require_session_token()?;
1140
1141 let si = {
1142 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1143 core::SexInformation { require_sex: config.require_sex.unwrap_or(false),
1144 allow_sex_na: config.allow_sex_na.unwrap_or(true),
1145 allow_sex_diverse: config.allow_sex_diverse.unwrap_or(false),
1146 allow_sex_other: config.allow_sex_other.unwrap_or(true) }
1147 };
1148
1149 template_ok!(with_conn![core::group_csv, C, req, &session_token, si].aug(req)?)
1150}
1151
1152fn group_csv_upload<C>(req: &mut Request) -> IronResult<Response>
1153 where C: MedalConnection + std::marker::Send + 'static {
1154 let session_token = req.require_session_token()?;
1155
1156 let (csrf_token, group_data) = {
1157 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
1158 (iexpect!(formdata.get("csrf_token"))[0].to_owned(), iexpect!(formdata.get("group_data"))[0].to_owned())
1159 };
1160
1161 #[cfg(feature = "debug")]
1162 println!("{}", group_data);
1163
1164 with_conn![core::upload_groups, C, req, &session_token, &csrf_token, &group_data].aug(req)?;
1165
1166 Ok(Response::with((status::Found, Redirect(url_for!(req, "groups")))))
1167}
1168
1169fn contest_resultcsv<C>(req: &mut Request) -> IronResult<Response>
1170 where C: MedalConnection + std::marker::Send + 'static {
1171 let session_token = req.require_session_token()?;
1172
1173 template_ok!(with_conn![core::contest_result_csv, C, req, &session_token].aug(req)?)
1174}
1175
1176fn contest_resultcsv_upload<C>(req: &mut Request) -> IronResult<Response>
1177 where C: MedalConnection + std::marker::Send + 'static {
1178 let contest_id = req.expect_int::<i32>("contestid")?;
1179 let session_token = req.require_session_token()?;
1180
1181 let (csrf_token, admission_data) = {
1182 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
1183 (iexpect!(formdata.get("csrf_token"))[0].to_owned(), iexpect!(formdata.get("admission_data"))[0].to_owned())
1184 };
1185
1186 #[cfg(feature = "debug")]
1187 println!("{}", admission_data);
1188
1189 with_conn![core::upload_contest_result_csv, C, req, &session_token, &csrf_token, contest_id, &admission_data].aug(req)?;
1190
1191 Ok(Response::with((status::Found,
1192 Redirect(url_for!(req, "admin_contests", "contestid" => format!("{}",contest_id))))))
1193}
1194
1195fn profile<C>(req: &mut Request) -> IronResult<Response>
1196 where C: MedalConnection + std::marker::Send + 'static {
1197 let session_token = req.require_session_token()?;
1198 let query_string = req.url.query().map(|s| s.to_string());
1199
1200 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1201 let si = core::SexInformation { require_sex: config.require_sex.unwrap_or(false),
1202 allow_sex_na: config.allow_sex_na.unwrap_or(true),
1203 allow_sex_diverse: config.allow_sex_diverse.unwrap_or(false),
1204 allow_sex_other: config.allow_sex_other.unwrap_or(true) };
1205
1206 let (template, mut data) = with_conn![core::show_profile, C, req, &session_token, None, query_string, si].aug(req)?;
1207 data.insert("config".to_string(), to_json(&config.template_params));
1208
1209 let mut resp = Response::new();
1210 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1211 Ok(resp)
1212}
1213
1214fn profile_post<C>(req: &mut Request) -> IronResult<Response>
1215 where C: MedalConnection + std::marker::Send + 'static {
1216 let session_token = req.expect_session_token()?;
1217 let (csrf_token, firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex) = {
1218 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1219 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1220 iexpect!(formdata.get("firstname"))[0].to_owned(),
1221 iexpect!(formdata.get("lastname"))[0].to_owned(),
1222 formdata.get("street").map(|x| x[0].to_owned()),
1223 formdata.get("zip").map(|x| x[0].to_owned()),
1224 formdata.get("city").map(|x| x[0].to_owned()),
1225 formdata.get("password").map(|x| x[0].to_owned()),
1226 formdata.get("password_repeat").map(|x| x[0].to_owned()),
1227 iexpect!(formdata.get("grade"))[0].parse::<i32>().unwrap_or(0),
1228 iexpect!(formdata.get("sex"))[0].parse::<i32>().ok())
1229 };
1230
1231 let profilechangeresult =
1232 with_conn![core::edit_profile,
1233 C,
1234 req,
1235 &session_token,
1236 None,
1237 &csrf_token,
1238 (firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex)].aug(req)?;
1239
1240 Ok(Response::with((status::Found,
1241 Redirect(iron::Url::parse(&format!("{}?status={:?}",
1242 &url_for!(req, "myprofile"),
1243 profilechangeresult)).unwrap()))))
1244}
1245
1246fn profile_check<C>(req: &mut Request) -> IronResult<Response>
1247 where C: MedalConnection + std::marker::Send + 'static {
1248 let session_token = req.expect_session_token()?;
1249 let (csrf_token, firstname, lastname) = {
1250 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1251 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1252 iexpect!(formdata.get("firstname"))[0].to_owned(),
1253 iexpect!(formdata.get("lastname"))[0].to_owned())
1254 };
1255
1256 Ok(handle_json_response(with_conn![core::check_profile,
1257 C,
1258 req,
1259 &session_token,
1260 &csrf_token,
1261 (firstname, lastname)]).aug(req)?)
1262}
1263
1264fn user<C>(req: &mut Request) -> IronResult<Response>
1265 where C: MedalConnection + std::marker::Send + 'static {
1266 let user_id = req.expect_int::<i32>("userid")?;
1267 let session_token = req.expect_session_token()?;
1268 let query_string = req.url.query().map(|s| s.to_string());
1269
1270 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1271 let si = core::SexInformation { require_sex: config.require_sex.unwrap_or(false),
1272 allow_sex_na: config.allow_sex_na.unwrap_or(true),
1273 allow_sex_diverse: config.allow_sex_diverse.unwrap_or(false),
1274 allow_sex_other: config.allow_sex_other.unwrap_or(true) };
1275
1276 let (template, mut data) =
1277 with_conn![core::show_profile, C, req, &session_token, Some(user_id), query_string, si].aug(req)?;
1278 data.insert("config".to_string(), to_json(&config.template_params));
1279
1280 let mut resp = Response::new();
1281 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1282 Ok(resp)
1283}
1284
1285fn user_post<C>(req: &mut Request) -> IronResult<Response>
1286 where C: MedalConnection + std::marker::Send + 'static {
1287 let user_id = req.expect_int::<i32>("userid")?;
1288 let session_token = req.expect_session_token()?;
1289 let (csrf_token, firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex) = {
1290 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1291 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1292 iexpect!(formdata.get("firstname"))[0].to_owned(),
1293 iexpect!(formdata.get("lastname"))[0].to_owned(),
1294 formdata.get("street").map(|x| x[0].to_owned()),
1295 formdata.get("zip").map(|x| x[0].to_owned()),
1296 formdata.get("city").map(|x| x[0].to_owned()),
1297 formdata.get("password").map(|x| x[0].to_owned()),
1298 formdata.get("password_repeat").map(|x| x[0].to_owned()),
1299 iexpect!(formdata.get("grade"))[0].parse::<i32>().unwrap_or(0),
1300 iexpect!(formdata.get("sex"))[0].parse::<i32>().ok())
1301 };
1302
1303 let profilechangeresult =
1304 with_conn![core::edit_profile,
1305 C,
1306 req,
1307 &session_token,
1308 Some(user_id),
1309 &csrf_token,
1310 (firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex)].aug(req)?;
1311
1312 Ok(Response::with((status::Found,
1313 Redirect(iron::Url::parse(&format!("{}?status={:?}",
1314 &url_for!(req, "user", "userid" => format!("{}",user_id)),
1315 profilechangeresult)).unwrap()))))
1316 }
1318
1319fn teacherinfos<C>(req: &mut Request) -> IronResult<Response>
1320 where C: MedalConnection + std::marker::Send + 'static {
1321 let session_token = req.expect_session_token()?;
1322
1323 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1324
1325 let (template, mut data) = with_conn![core::teacher_infos, C, req, &session_token].aug(req)?;
1326 data.insert("config".to_string(), to_json(&config.template_params));
1327
1328 let mut resp = Response::new();
1329 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1330 Ok(resp)
1331}
1332
1333fn admin<C>(req: &mut Request) -> IronResult<Response>
1334 where C: MedalConnection + std::marker::Send + 'static {
1335 let session_token = req.expect_session_token()?;
1336 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1337
1338 let (template, mut data) = with_conn![core::admin_index, C, req, &session_token].aug(req)?;
1339 data.insert("dbstatus_secret".to_string(), to_json(&config.dbstatus_secret));
1340 data.insert("config".to_string(), to_json(&config.template_params));
1341
1342 let mut resp = Response::new();
1343 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1344 Ok(resp)
1345}
1346
1347fn admin_users<C>(req: &mut Request) -> IronResult<Response>
1348 where C: MedalConnection + std::marker::Send + 'static {
1349 let session_token = req.expect_session_token()?;
1350 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1351
1352 let (s_id, s_firstname, s_lastname, s_logincode, s_groupcode, s_pms_id) = {
1353 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1354 (formdata.get("id").map(|x| x[0].parse::<i32>().unwrap_or(0)),
1355 formdata.get("firstname").map(|x| x[0].to_owned()),
1356 formdata.get("lastname").map(|x| x[0].to_owned()),
1357 formdata.get("logincode").map(|x| x[0].to_owned()),
1358 formdata.get("groupcode").map(|x| x[0].to_owned()),
1359 formdata.get("pmsid").map(|x| x[0].to_owned()))
1360 };
1361
1362 let (template, mut data) =
1363 with_conn![core::admin_search_users,
1364 C,
1365 req,
1366 &session_token,
1367 (s_id, s_firstname, s_lastname, s_logincode, s_groupcode, s_pms_id)].aug(req)?;
1368 data.insert("config".to_string(), to_json(&config.template_params));
1369
1370 let mut resp = Response::new();
1371 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1372 Ok(resp)
1373}
1374
1375fn admin_user<C>(req: &mut Request) -> IronResult<Response>
1376 where C: MedalConnection + std::marker::Send + 'static {
1377 let user_id = req.expect_int::<i32>("userid")?;
1378 let session_token = req.expect_session_token()?;
1379 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1380
1381 let (csrf_token, group_id) = if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
1382 (formdata.get("csrf_token").map(|x| x[0].to_owned()),
1384 formdata.get("group_id").map(|x| x[0].parse::<i32>().unwrap_or(0)))
1385 } else {
1386 (None, None)
1387 };
1388
1389 if let Some(csrf_token) = csrf_token {
1390 if let Some(group_id) = group_id {
1391 return Ok(handle_json_response(with_conn![core::admin_move_user_to_group,
1392 C,
1393 req,
1394 user_id,
1395 group_id,
1396 &session_token,
1397 &csrf_token]).aug(req)?);
1398 } else {
1399 return Ok(handle_json_response(with_conn![core::admin_delete_user,
1400 C,
1401 req,
1402 user_id,
1403 &session_token,
1404 &csrf_token]).aug(req)?);
1405 }
1406 }
1407
1408 let (template, mut data) = with_conn![core::admin_show_user, C, req, user_id, &session_token].aug(req)?;
1409 data.insert("config".to_string(), to_json(&config.template_params));
1410
1411 let mut resp = Response::new();
1412 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1413 Ok(resp)
1414}
1415
1416fn admin_group<C>(req: &mut Request) -> IronResult<Response>
1417 where C: MedalConnection + std::marker::Send + 'static {
1418 let group_id = req.expect_int::<i32>("groupid")?;
1419 let session_token = req.expect_session_token()?;
1420 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1421
1422 let csrf_token = if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
1423 formdata.get("csrf_token").map(|x| x[0].to_owned())
1424 } else {
1425 None
1426 };
1427
1428 if let Some(csrf_token) = csrf_token {
1429 return Ok(handle_json_response(with_conn![core::admin_delete_group,
1430 C,
1431 req,
1432 group_id,
1433 &session_token,
1434 &csrf_token]).aug(req)?);
1435 }
1436
1437 let (template, mut data) = with_conn![core::admin_show_group, C, req, group_id, &session_token].aug(req)?;
1438 data.insert("config".to_string(), to_json(&config.template_params));
1439
1440 let mut resp = Response::new();
1441 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1442 Ok(resp)
1443}
1444
1445fn admin_group_edit<C>(req: &mut Request) -> IronResult<Response>
1446 where C: MedalConnection + std::marker::Send + 'static {
1447 let group_id = req.expect_int::<i32>("groupid")?;
1448 let session_token = req.expect_session_token()?;
1449 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1450
1451 if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
1452 let csrf_token = iexpect!(formdata.get("csrf_token"))[0].to_owned();
1453 let name = iexpect!(formdata.get("new_groupname"))[0].to_owned();
1454 let tag = iexpect!(formdata.get("new_grouptag"))[0].to_owned();
1455
1456 with_conn![core::admin_edit_group, C, req, group_id, &session_token, &csrf_token, name, tag].aug(req)?;
1457
1458 Ok(Response::with((status::Found, Redirect(url_for!(req, "group", "groupid" => format!("{}",group_id))))))
1459 } else {
1460 let (template, mut data) = with_conn![core::admin_show_edit_group, C, req, group_id, &session_token].aug(req)?;
1461
1462 data.insert("config".to_string(), to_json(&config.template_params));
1463
1464 let mut resp = Response::new();
1465 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1466 Ok(resp)
1467 }
1468}
1469
1470fn group_addadmin<C>(req: &mut Request) -> IronResult<Response>
1471 where C: MedalConnection + std::marker::Send + 'static {
1472 let group_id = req.expect_int::<i32>("groupid")?;
1473 let session_token = req.expect_session_token()?;
1474
1475 let (csrf_token, teacher_id) = {
1476 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
1477 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1478 iexpect!(formdata.get("teacherid").unwrap_or(&vec!["".to_owned()])[0].parse::<i32>().ok()))
1479 };
1480
1481 Ok(handle_json_response(with_conn![core::group_add_admin,
1482 C,
1483 req,
1484 group_id,
1485 teacher_id,
1486 &session_token,
1487 &csrf_token]).aug(req)?)
1488}
1489
1490fn group_deladmin<C>(req: &mut Request) -> IronResult<Response>
1491 where C: MedalConnection + std::marker::Send + 'static {
1492 let group_id = req.expect_int::<i32>("groupid")?;
1493 let user_id = req.expect_int::<i32>("userid")?;
1494 let session_token = req.expect_session_token()?;
1495
1496 let csrf_token = {
1497 let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
1498 iexpect!(formdata.get("csrf_token"))[0].to_owned()
1499 };
1500
1501 Ok(handle_json_response(with_conn![core::group_delete_admin,
1502 C,
1503 req,
1504 group_id,
1505 user_id,
1506 &session_token,
1507 &csrf_token]).aug(req)?)
1508}
1509
1510fn admin_participation<C>(req: &mut Request) -> IronResult<Response>
1511 where C: MedalConnection + std::marker::Send + 'static {
1512 let user_id = req.expect_int::<i32>("userid")?;
1513 let contest_id = req.expect_int::<i32>("contestid")?;
1514 let session_token = req.expect_session_token()?;
1515 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1516
1517 let csrf_token = if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
1518 formdata.get("csrf_token").map(|x| x[0].to_owned())
1519 } else {
1520 None
1521 };
1522
1523 if let Some(csrf_token) = csrf_token {
1524 return Ok(handle_json_response(with_conn![core::admin_delete_participation,
1525 C,
1526 req,
1527 user_id,
1528 contest_id,
1529 &session_token,
1530 &csrf_token]).aug(req)?);
1531 }
1532
1533 let (template, mut data) =
1534 with_conn![core::admin_show_participation, C, req, user_id, contest_id, &session_token].aug(req)?;
1535 data.insert("config".to_string(), to_json(&config.template_params));
1536
1537 let mut resp = Response::new();
1538 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1539 Ok(resp)
1540}
1541
1542fn admin_contests<C>(req: &mut Request) -> IronResult<Response>
1543 where C: MedalConnection + std::marker::Send + 'static {
1544 let session_token = req.expect_session_token()?;
1545 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1546
1547 let (template, mut data) = with_conn![core::admin_show_contests, C, req, &session_token].aug(req)?;
1548 data.insert("config".to_string(), to_json(&config.template_params));
1549
1550 let mut resp = Response::new();
1551 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1552 Ok(resp)
1553}
1554
1555fn admin_export_contest<C>(req: &mut Request) -> IronResult<Response>
1556 where C: MedalConnection + std::marker::Send + 'static {
1557 let contest_id = req.expect_int::<i32>("contestid")?;
1558 let session_token = req.expect_session_token()?;
1559
1560 let filename = with_conn![core::admin_contest_export, C, req, contest_id, &session_token].aug(req)?;
1561
1562 Ok(Response::with((status::Found, RedirectRaw(format!("/export/{}", filename)))))
1563}
1564
1565fn admin_cleanup<C>(req: &mut Request) -> IronResult<Response>
1566 where C: MedalConnection + std::marker::Send + 'static {
1567 let session_token = req.expect_session_token()?;
1568 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1569
1570 let csrf_token = if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
1571 formdata.get("csrf_token").map(|x| x[0].to_owned())
1572 } else {
1573 None
1574 };
1575
1576 if let Some(csrf_token) = csrf_token {
1577 let cleanup_type = req.get_str("type");
1578
1579 return Ok(handle_json_response(match cleanup_type.as_deref() {
1580 Some("session") => with_conn![core::do_session_cleanup, C, req,],
1581 _ => with_conn![core::admin_do_cleanup, C, req, &session_token, &csrf_token],
1582 }).aug(req)?);
1583 }
1584
1585 let (template, mut data) = with_conn![core::admin_show_cleanup, C, req, &session_token].aug(req)?;
1586 data.insert("config".to_string(), to_json(&config.template_params));
1587
1588 let mut resp = Response::new();
1589 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1590 Ok(resp)
1591}
1592
1593fn admin_move_task_location<C>(req: &mut Request) -> IronResult<Response>
1598 where C: MedalConnection + std::marker::Send + 'static {
1599 let session_token = req.expect_session_token()?;
1600
1601 let (csrf_token, old_location, new_location, contest) = {
1602 let formdata = itry!(req.get_ref::<UrlEncodedBody>());
1603 (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
1604 iexpect!(formdata.get("old_location"))[0].to_owned(),
1605 iexpect!(formdata.get("new_location"))[0].to_owned(),
1606 formdata.get("contest").map(|x| x[0].parse::<i32>().unwrap_or(0)))
1607 };
1608
1609 Ok(handle_json_response(with_conn![core::move_task_location,
1610 C,
1611 req,
1612 &session_token,
1613 &csrf_token,
1614 &old_location,
1615 &new_location,
1616 contest]).aug(req)?)
1617}
1618
1619fn dbcleanup<C>(req: &mut Request) -> IronResult<Response>
1620 where C: MedalConnection + std::marker::Send + 'static {
1621 Ok(handle_json_response(with_conn![core::do_session_cleanup, C, req,]).aug(req)?)
1622}
1623
1624fn oauth<C>(req: &mut Request) -> IronResult<Response>
1625 where C: MedalConnection + std::marker::Send + 'static {
1626 #[cfg(feature = "debug")]
1627 println!("{:?}", req.url.query().unwrap_or(""));
1628
1629 let oauth_id = req.expect_str("oauthid")?;
1630 let school_id = req.get_str("schoolid");
1631
1632 let (oauth_provider, autoclean_submissions) = {
1633 let config = req.get::<Read<SharedConfiguration>>().unwrap();
1634
1635 let mut result: Option<OauthProvider> = None;
1636
1637 if let Some(ref oauth_providers) = config.oauth_providers {
1638 for oauth_provider in oauth_providers {
1639 if oauth_provider.provider_id == oauth_id {
1640 result = Some(oauth_provider.clone());
1641 break;
1642 }
1643 }
1644
1645 if let Some(result) = result {
1646 (result, config.autoclean_submissions.unwrap_or(false))
1647 } else {
1648 return Ok(Response::with(iron::status::NotFound));
1649 }
1650 } else {
1651 return Ok(Response::with(iron::status::NotFound));
1652 }
1653 };
1654
1655 let user_data_result = match oauth_provider.medal_oauth_type.as_ref() {
1656 "pms" => oauth_pms(req, oauth_provider, school_id.as_ref()).aug(req)?,
1657 _ => return Ok(Response::with(iron::status::NotFound)),
1658 };
1659
1660 let user_data = match user_data_result {
1661 Err(response) => return Ok(response),
1662 Ok(user_data) => user_data,
1663 };
1664 let user_type = user_data.foreign_type;
1665
1666 let oauthloginresult = {
1667 let mutex = req.get::<Write<SharedDatabaseConnection<C>>>().unwrap();
1669 let conn = mutex.lock().unwrap_or_else(|e| e.into_inner());
1670
1671 core::login_oauth(&*conn, user_data, oauth_id, autoclean_submissions)
1673 };
1674
1675 match oauthloginresult {
1676 Ok((sessionkey, redirectprofile)) => {
1678 req.session().set(SessionToken { token: sessionkey }).unwrap();
1679
1680 use core::UserType;
1681 if user_type == UserType::User && redirectprofile {
1682 Ok(Response::with((status::Found,
1683 Redirect(iron::Url::parse(&format!("{}?status=firstlogin",
1684 &url_for!(req, "myprofile"))).unwrap()))))
1685 } else {
1686 Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
1687 }
1688 }
1689 Err((template, data)) => {
1691 let mut resp = Response::new();
1692 resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
1693 Ok(resp)
1694 }
1695 }
1696}
1697
1698#[derive(Deserialize, Debug)]
1699struct OAuthAccess {
1700 access_token: String,
1701 #[allow(dead_code)]
1702 token_type: String,
1703 #[allow(dead_code)]
1704 refresh_token: String,
1705 #[allow(dead_code)]
1706 expires: Option<i32>, #[allow(dead_code)]
1708 expires_in: Option<i32>, }
1710
1711#[derive(Deserialize, Debug)]
1712#[allow(non_snake_case)]
1713#[serde(untagged)]
1714pub enum SchoolIdOrSchoolIds {
1715 #[allow(dead_code)]
1716 None(i32), #[allow(dead_code)]
1718 SchoolId(String),
1719 SchoolIds(Vec<String>),
1720}
1721
1722#[derive(Deserialize, Debug)]
1723#[allow(non_snake_case)]
1724pub struct OAuthUserData {
1725 userID: Option<String>, userId: Option<String>, userType: String,
1728 gender: String,
1729 firstName: String,
1730 lastName: String,
1731 #[allow(dead_code)]
1732 dateOfBirth: Option<String>,
1733 #[allow(dead_code)]
1734 eMail: Option<String>,
1735 schoolId: Option<SchoolIdOrSchoolIds>,
1736}
1737
1738#[derive(Deserialize, Debug)]
1739#[allow(non_snake_case)]
1740pub struct OAuthSchoolData {
1741 name: Option<String>,
1742 city: Option<String>,
1743 error: Option<String>,
1744}
1745
1746fn pms_hash_school(school_id: &str, secret: &str) -> String {
1747 use sha2::{Digest, Sha512};
1748 let mut hasher = Sha512::default();
1749 let string_to_hash = format!("{}{}", school_id, secret);
1750
1751 hasher.input(string_to_hash.as_bytes());
1752 let hashed_string = hasher.result();
1753
1754 format!("{:02X?}", hashed_string).chars().filter(|c| c.is_ascii_alphanumeric()).collect()
1755}
1756
1757fn oauth_pms(req: &mut Request, oauth_provider: OauthProvider, selected_school_id: Option<&String>)
1758 -> Result<Result<core::ForeignUserData, Response>, core::MedalError> {
1759 use core::{UserSex, UserType};
1760 use params::{Params, Value};
1761
1762 fn er(e: &str) -> core::MedalError { core::MedalError::OauthError(e.to_string()) }
1763 fn e<T>(e: &str) -> Result<T, core::MedalError> { Err::<T, _>(er(e)) }
1764
1765 let (_, _, code): (String, String, String) = {
1766 let map = req.get_ref::<Params>().unwrap();
1767
1768 match (map.find(&["state"]), map.find(&["scope"]), map.find(&["code"])) {
1769 (Some(&Value::String(ref state)), Some(&Value::String(ref scope)), Some(&Value::String(ref code)))
1770 if state == "42" =>
1771 {
1772 (state.clone(), scope.clone(), code.clone())
1773 }
1774 _ => return e("#70"),
1775 }
1776 };
1777
1778 let client = reqwest::Client::new();
1779 let params = [("code", code), ("grant_type", "authorization_code".to_string())];
1780
1781 let res = client.post(&oauth_provider.access_token_url)
1783 .basic_auth(oauth_provider.client_id, Some(oauth_provider.client_secret))
1784 .form(¶ms)
1785 .send();
1786 let access: OAuthAccess = res.or(e("#00"))?.json().or(e("#01"))?;
1787
1788 let res = client.get(&oauth_provider.user_data_url).bearer_auth(access.access_token).send();
1789 let mut user_data: OAuthUserData = res.or(e("#10"))?.json().or(e("#11"))?;
1790
1791 user_data.userId = user_data.userID.or(user_data.userId);
1793
1794 let user_type = match user_data.userType.as_ref() {
1795 "a" | "A" => UserType::Admin,
1796 "t" | "T" => UserType::Teacher,
1797 "s" | "S" => UserType::User,
1798 _ => UserType::User,
1799 };
1800 let user_sex = match user_data.gender.as_ref() {
1801 "m" | "M" => UserSex::Male,
1802 "f" | "F" | "w" | "W" => UserSex::Female,
1803 "?" => UserSex::Unknown,
1804 _ => UserSex::Unknown,
1805 };
1806 let mut school_name: Option<String> = None;
1807
1808 match (&user_data.schoolId, user_type) {
1809 (Some(SchoolIdOrSchoolIds::SchoolIds(_)), UserType::User) => return e("#70"),
1811 (Some(SchoolIdOrSchoolIds::SchoolId(_)), UserType::Teacher) => return e("#71"),
1815 (Some(SchoolIdOrSchoolIds::None(_)), UserType::Teacher) => return e("#72"),
1816 (None, UserType::Teacher) => {
1818 user_data.schoolId = Some(SchoolIdOrSchoolIds::SchoolIds(Vec::new()));
1819 }
1820
1821 _ => (),
1823 }
1824
1825 if let Some(SchoolIdOrSchoolIds::SchoolIds(school_ids)) = user_data.schoolId {
1827 if let Some(selected_school_id) = selected_school_id {
1829 if selected_school_id == "none" && oauth_provider.allow_teacher_login_without_school == Some(true) {
1830 }
1832 else if school_ids.contains(&selected_school_id) {
1834 if let Some(mut user_id) = user_data.userId {
1835 user_id.push('/');
1836 user_id.push_str(&selected_school_id);
1837 user_data.userId = Some(user_id);
1838
1839 if let (Some(school_data_url), Some(school_data_secret)) =
1842 (oauth_provider.school_data_url, oauth_provider.school_data_secret)
1843 {
1844 let params = [("schoolId", selected_school_id.clone()),
1845 ("hash", pms_hash_school(&selected_school_id, &school_data_secret))];
1846 let res = client.post(&school_data_url).form(¶ms).send();
1847 if let Ok(mut json) = res {
1848 let school_data_opt: Result<OAuthSchoolData, reqwest::Error> = json.json();
1849 if let Ok(school_data) = school_data_opt {
1850 school_name = school_data.name;
1851 }
1852 }
1853 }
1854 }
1855 } else {
1856 return e("#40");
1857 }
1858 } else {
1859 if let (Some(school_data_url), Some(school_data_secret)) =
1862 (oauth_provider.school_data_url, oauth_provider.school_data_secret)
1863 {
1864 let school_infos: Vec<(String, String)> =
1866 school_ids.iter()
1867 .map(|school_id| -> Result<(String, String), core::MedalError> {
1868 let params = [("schoolId", school_id.clone()),
1869 ("hash", pms_hash_school(&school_id, &school_data_secret))];
1870 let res = client.post(&school_data_url).form(¶ms).send();
1871 let school_data: OAuthSchoolData = res.or(e("#30"))?.json().or(e("#31"))?;
1872
1873 Ok((school_id.clone(),
1874 format!("{}, {}",
1875 school_data.name
1876 .or(school_data.error)
1877 .unwrap_or_else(|| "Information missing".to_string()),
1878 school_data.city.unwrap_or_else(|| "–".to_string()))))
1879 })
1880 .collect::<Result<_, _>>()?;
1881
1882 let mut data = json_val::Map::new();
1883 data.insert("schools".to_string(), to_json(&school_infos));
1884 data.insert("query".to_string(), to_json(&req.url.query().unwrap_or("")));
1885
1886 data.insert("parent".to_string(), to_json(&"base"));
1887 data.insert("disable_login_box".to_string(), to_json(&true));
1888
1889 data.insert("teacher_login_without_school".to_string(),
1890 to_json(&oauth_provider.allow_teacher_login_without_school.unwrap_or(false)));
1891
1892 let mut resp = Response::new();
1893 resp.set_mut(Template::new(&"oauth_school_selector", data)).set_mut(status::Ok);
1894 return Ok(Err(resp));
1895 } else {
1896 return Err(core::MedalError::ConfigurationError);
1898 }
1899 }
1900 } else if selected_school_id.is_some() {
1901 return e("#50");
1903 }
1904
1905 Ok(Ok(core::ForeignUserData { foreign_id: user_data.userId.ok_or(er("#60"))?,
1906 foreign_type: user_type,
1907 sex: user_sex,
1908 firstname: user_data.firstName,
1909 lastname: user_data.lastName,
1910 school_name }))
1911}
1912
1913#[derive(Copy, Clone)]
1915pub struct SharedDatabaseConnection<C>
1916 where C: MedalConnection
1917{
1918 phantom: std::marker::PhantomData<C>,
1919}
1920impl<C> Key for SharedDatabaseConnection<C> where C: MedalConnection + 'static
1921{
1922 type Value = C;
1923}
1924
1925#[derive(Copy, Clone)]
1927pub struct SharedConfiguration;
1928impl Key for SharedConfiguration {
1929 type Value = Config;
1930}
1931
1932#[cfg(feature = "watch")]
1933pub fn get_handlebars_engine(template_name: &str) -> impl AfterMiddleware {
1934 let mut hbse = HandlebarsEngine::new();
1936 hbse.add(Box::new(DirectorySource::new(&format!("./templates/{}/", template_name) as &str, ".hbs")));
1937
1938 if let Err(r) = hbse.reload() {
1940 panic!("{}", r);
1941 }
1942
1943 use handlebars_iron::Watchable;
1944 use std::sync::Arc;
1945
1946 let hbse_ref = Arc::new(hbse);
1947 hbse_ref.watch("./templates/");
1948 hbse_ref
1949}
1950
1951#[cfg(not(feature = "watch"))]
1952pub fn get_handlebars_engine(template_name: &str) -> impl AfterMiddleware {
1953 let mut hbse = HandlebarsEngine::new();
1955 hbse.add(Box::new(DirectorySource::new(&format!("./templates/{}/", template_name) as &str, ".hbs")));
1956
1957 if let Err(r) = hbse.reload() {
1959 panic!("{}", r);
1960 }
1961
1962 hbse
1963}
1964
1965fn cookie_warning(req: &mut Request) -> IronResult<Response> {
1966 match req.get_session_token() {
1967 Some(_session_token) => {
1968 Ok(Response::with((status::Found, RedirectRaw(format!("/{}", req.url.query().unwrap_or(""))))))
1971 }
1972 None => {
1973 let mut resp = Response::new();
1974 resp.set_mut(Template::new("cookie", json_val::Map::new())).set_mut(status::Ok);
1975 Ok(resp)
1976 }
1977 }
1978}
1979
1980pub fn start_server<C>(conn: C, config: Config) -> iron::error::HttpResult<iron::Listening>
1981 where C: MedalConnection + std::marker::Send + 'static {
1982 let router = router!(
1983 greet: get "/" => greet_personal::<C>,
1984 contests: get "/contest/" => contests::<C>,
1985 contests_or_contest: get "/contest/:categoryorcontestid" => contests_or_contest::<C>,
1986 contests_or_contest_secret: get "/contest/:categoryorcontestid/:contestidorsecret" => contests_or_contest::<C>,
1987 contest_secret: get "/contest/:category/:contestid/:secret" => contest::<C>,
1988 contestresults: get "/contest/:contestid/result/" => contestresults::<C>,
1989 contestresults_download: get "/contest/:contestid/result/download" => contestresults_download::<C>,
1990 contestresults_category: get "/contest/:category/:contestid/result/" => contestresults::<C>,
1991 contestresults_category_download: get "/contest/:category/:contestid/result/download" => contestresults_download::<C>,
1992 contest_post: post "/contest/:contestid" => contest_post::<C>,
1993 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>,
1996 login_post: post "/login" => login_post::<C>,
1997 login_code_post: post "/clogin" => login_code_post::<C>,
1998 login_webauthn: get "/webauthn_login" => login_webauthn::<C>,
1999 login_webauthn_post: post "/webauthn_login" => login_webauthn_post::<C>,
2000 register_webauthn: get "/webauthn_register" => register_webauthn::<C>,
2001 register_webauthn_post: post "/webauthn_register" => register_webauthn_post::<C>,
2002 delete_webauthn_post: post "/webauthn_delete" => delete_webauthn_post::<C>,
2003 logout: get "/logout" => logout::<C>,
2004 signup: get "/signup" => signup::<C>,
2005 signup_post: post "/signup" => signup_post::<C>,
2006 subm_load: get "/load/:taskid" => submission::<C>,
2007 subm_save: post "/save/:taskid" => submission_post::<C>,
2008 groups: get "/group/" => groups::<C>,
2009 groups: post "/group/" => new_group::<C>,
2010 group: get "/group/:groupid" => admin_group::<C>,
2011 group_post: post "/group/:groupid" => admin_group::<C>,
2012 group_edit: get "/group/:groupid/edit" => admin_group_edit::<C>,
2013 group_edit_post: post "/group/:groupid/edit" => admin_group_edit::<C>,
2014 group_addadmin: post "/group/:groupid/addadmin" => group_addadmin::<C>,
2015 group_deladmin: post "/group/:groupid/deladmin/:userid" => group_deladmin::<C>,
2016 group_download: get "/group/download/:groupid" => group_download::<C>,
2017 groupcsv: get "/group/csv" => group_csv::<C>,
2019 groupcsv_post: post "/group/csv" => group_csv_upload::<C>,
2020 myprofile: get "/profile" => profile::<C>,
2021 myprofile_post: post "/profile" => profile_post::<C>,
2022 myprofile_check: post "/profile/check" => profile_check::<C>,
2023 user: get "/user/:userid" => admin_user::<C>,
2024 user_post: post "/user/:userid" => admin_user::<C>,
2025 profile: get "/profile/:userid" => user::<C>,
2026 profile_post: post "/profile/:userid" => user_post::<C>,
2027 task: get "/task/:taskid" => task::<C>,
2028 task_review_solution: get "/task/:taskid/:submissionid" => review::<C>,
2029 preview_task: get "/preview/:taskid" => preview::<C>,
2030 teacher: get "/teacher" => teacherinfos::<C>,
2031 admin: get "/admin" => admin::<C>,
2032 admin_users: post "/admin/user/" => admin_users::<C>,
2033 admin_user: get "/admin/user/:userid" => admin_user::<C>,
2034 admin_user_post: post "/admin/user/:userid" => admin_user::<C>,
2035 admin_group: get "/admin/group/:groupid" => admin_group::<C>,
2036 admin_group_post: post "/admin/group/:groupid" => admin_group::<C>,
2037 admin_group_edit: get "/admin/group/:groupid/edit" => admin_group_edit::<C>,
2038 admin_group_edit_post: post "/admin/group/:groupid/edit" => admin_group_edit::<C>,
2039 admin_participation: get "/admin/user/:userid/:contestid" => admin_participation::<C>,
2040 admin_participation_post: post "/admin/user/:userid/:contestid" => admin_participation::<C>,
2041 admin_contests: get "/admin/contest/" => admin_contests::<C>,
2042 admin_contest_admissioncsv: get "/admin/contest/:contestid/csv" => contest_resultcsv::<C>,
2043 admin_contest_admissioncsv_post: post "/admin/contest/:contestid/csv" => contest_resultcsv_upload::<C>,
2044 admin_export_contest: get "/admin/contest/:contestid/export" => admin_export_contest::<C>,
2045 admin_cleanup: get "/admin/cleanup" => admin_cleanup::<C>,
2046 admin_cleanup_post: post "/admin/cleanup/:type" => admin_cleanup::<C>,
2047 admin_move_task_location_post: post "/admin/housekeeping/move_task_location" => admin_move_task_location::<C>,
2048 oauth: get "/oauth/:oauthid/" => oauth::<C>,
2049 oauth_school: get "/oauth/:oauthid/:schoolid" => oauth::<C>,
2050 check_cookie: get "/cookie" => cookie_warning,
2051 dbstatus: get "/dbstatus" => dbstatus::<C>,
2052 status: get "/status" => dbstatus::<C>,
2053 dbcleanup: get "/cleanup" => dbcleanup::<C>,
2054 debug: get "/debug" => debug::<C>,
2055 debug_reset: get "/debug/reset" => debug_new_token::<C>,
2056 debug_logout: get "/debug/logout" => debug_logout::<C>,
2057 debug_create: get "/debug/create" => debug_create_session::<C>,
2058 );
2059
2060 let mut mount = Mount::new();
2061
2062 mount.mount("/static/", Static::new(Path::new("static")));
2064 mount.mount("/export/", Static::new(Path::new("export")));
2065 mount.mount("/tasks/", Static::new(Path::new(TASK_DIR)));
2066 mount.mount("/", router);
2067
2068 let mut ch = Chain::new(mount);
2069
2070 #[cfg(feature = "debug")]
2071 ch.link_before(RequestLogger {});
2072
2073 ch.link(Write::<SharedDatabaseConnection<C>>::both(conn));
2074 ch.link(Read::<SharedConfiguration>::both(config.clone()));
2075
2076 ch.link_around(RequestTimeLogger {});
2077 ch.link_around(CookieDistributor {});
2078 ch.link_around(SessionStorage::new(SignedCookieBackend::new(config.cookie_signing_secret.expect("Cookie signing secret not found in configuration").into_bytes())));
2079
2080 ch.link_after(get_handlebars_engine(&config.template.unwrap_or_else(|| "default".to_string())));
2081 ch.link_after(ErrorReporter);
2082 ch.link_after(ErrorShower);
2083
2084 let socket_addr = format!("{}:{}", config.host.unwrap(), config.port.unwrap());
2085
2086 let srvr = Iron::new(ch).http(&socket_addr);
2087 print!("Listening on {} … ", &socket_addr);
2088 srvr
2089}