Skip to main content

medal/
webfw_iron.rs

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