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