medal/
webfw_iron.rs

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