webfw_iron.rs 50.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/*  medal                                                                                                            *\
 *  Copyright (C) 2020  Bundesweite Informatikwettbewerbe                                                            *
 *                                                                                                                   *
 *  This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero        *
 *  General Public License as published  by the Free Software Foundation, either version 3 of the License, or (at    *
 *  your option) any later version.                                                                                  *
 *                                                                                                                   *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the       *
 *  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public      *
 *  License for more details.                                                                                        *
 *                                                                                                                   *
 *  You should have received a copy of the GNU Affero General Public License along with this program.  If not, see   *
\*  <http://www.gnu.org/licenses/>.                                                                                  */

Robert Czechowski's avatar
Robert Czechowski committed
15 16
use std::path::Path;

Robert Czechowski's avatar
Robert Czechowski committed
17 18 19
pub use handlebars_iron::handlebars::to_json;
use handlebars_iron::{DirectorySource, HandlebarsEngine, Template};
use iron;
Robert Czechowski's avatar
Robert Czechowski committed
20
use iron::modifiers::Redirect;
21
use iron::modifiers::RedirectRaw;
Robert Czechowski's avatar
Robert Czechowski committed
22 23 24
use iron::prelude::*;
use iron::{status, AfterMiddleware, AroundMiddleware, Handler};
use iron_sessionstorage;
Robert Czechowski's avatar
Robert Czechowski committed
25
use iron_sessionstorage::backends::SignedCookieBackend;
Robert Czechowski's avatar
Robert Czechowski committed
26
use iron_sessionstorage::traits::*;
Robert Czechowski's avatar
Robert Czechowski committed
27
use iron_sessionstorage::SessionStorage;
Robert Czechowski's avatar
Robert Czechowski committed
28
use mount::Mount;
29
use persistent::{Read, Write};
30
use reqwest;
Robert Czechowski's avatar
Robert Czechowski committed
31 32 33
use router::Router;
use staticfile::Static;
use urlencoded::{UrlEncodedBody, UrlEncodedQuery};
34

Robert Czechowski's avatar
Robert Czechowski committed
35 36
#[cfg(feature = "debug")]
use iron::BeforeMiddleware;
Robert Czechowski's avatar
Robert Czechowski committed
37

38
use config::Config;
39
use core;
Robert Czechowski's avatar
Robert Czechowski committed
40 41 42
use db_conn::MedalConnection;
use iron::typemap::Key;
pub use serde_json::value as json_val;
43

44
static TASK_DIR: &str = "tasks";
Robert Czechowski's avatar
Robert Czechowski committed
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59

macro_rules! mime {
    ($top:tt / $sub:tt) => (
        mime!($top / $sub;)
    );

    ($top:tt / $sub:tt ; $($attr:tt = $val:tt),*) => (
        iron::mime::Mime(
            iron::mime::TopLevel::$top,
            iron::mime::SubLevel::$sub,
            vec![ $((Attr::$attr,Value::$val)),* ]
        )
    );
}

60
macro_rules! with_conn {
61
    ( $x:expr , $c:ident, $r:expr , $($y:expr),* ) => {
62
        {
63
            let mutex = $r.get::<Write<SharedDatabaseConnection<$c>>>().unwrap();
64 65 66 67 68
            let conn = mutex.lock().unwrap_or_else(|e| e.into_inner());
            $x(&*conn, $($y),*)
        }
    };
}
Robert Czechowski's avatar
Robert Czechowski committed
69

70
macro_rules! template_ok {
Robert Czechowski's avatar
rustfmt  
Robert Czechowski committed
71 72 73 74 75 76 77
    ( $x:expr ) => {{
        let (template, data) = $x;

        let mut resp = Response::new();
        resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
        Ok(resp)
    }};
78 79
}

80
/** Show error messages on commandline */
Robert Czechowski's avatar
Robert Czechowski committed
81 82 83 84 85 86 87 88
struct ErrorReporter;
impl AfterMiddleware for ErrorReporter {
    fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> {
        println!("{}", err);
        Err(err)
    }
}

89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
/** Show error messages to users */
struct ErrorShower;
impl AfterMiddleware for ErrorShower {
    fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> {
        let IronError { error, response } = err;
        if response.body.is_none() {
            Ok(match response.status {
                Some(s) => {
                    let n = s.to_u16();
                    if n >= 400 && n <= 599 {
                        response.set((mime!(Text / Html),
                                      format!("<h1>{} {}</h1>", n, s.canonical_reason().unwrap_or("(Unknown error)"))))
                    } else {
                        response
                    }
                }
                _ => response,
            })
        } else {
            Err(IronError { error, response })
        }
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
113
#[derive(Debug)]
Robert Czechowski's avatar
Robert Czechowski committed
114
struct SessionToken {
Robert Czechowski's avatar
Robert Czechowski committed
115
    token: String,
Robert Czechowski's avatar
Robert Czechowski committed
116 117 118 119 120 121 122 123 124 125 126 127 128
}
impl iron_sessionstorage::Value for SessionToken {
    fn get_key() -> &'static str { "medal_session" }
    fn into_raw(self) -> String { self.token }
    fn from_raw(value: String) -> Option<Self> {
        if value.is_empty() {
            None
        } else {
            Some(SessionToken { token: value })
        }
    }
}

129 130 131
pub struct CookieDistributor {}

impl AroundMiddleware for CookieDistributor {
132
    fn around(self, handler: Box<dyn Handler>) -> Box<dyn Handler> {
133 134
        use rand::{distributions::Alphanumeric, thread_rng, Rng};

135
        Box::new(move |req: &mut Request| -> IronResult<Response> {
136
            if req.session().get::<SessionToken>().expect("blub...").is_none() {
137 138 139 140 141 142 143 144
                let session_token: String = thread_rng().sample_iter(&Alphanumeric).take(10).collect();
                req.session().set(SessionToken { token: session_token }).unwrap();
            }
            handler.handle(req)
        })
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
145
#[cfg(feature = "debug")]
146 147
pub struct RequestLogger {}

Robert Czechowski's avatar
Robert Czechowski committed
148
#[cfg(feature = "debug")]
149 150 151 152 153 154 155 156
impl BeforeMiddleware for RequestLogger {
    fn before(&self, req: &mut Request) -> IronResult<()> {
        println!("{}: {}", req.method, req.url);

        Ok(())
    }
}

157 158
#[derive(Debug)]
struct SessionError {
Robert Czechowski's avatar
Robert Czechowski committed
159
    message: String,
160 161
}
impl ::std::error::Error for SessionError {
Robert Czechowski's avatar
Robert Czechowski committed
162
    fn description(&self) -> &str { &self.message }
163 164 165
}

impl ::std::fmt::Display for SessionError {
Robert Czechowski's avatar
Robert Czechowski committed
166
    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "{}", self.message) }
167 168
}

169 170 171 172
trait RequestSession {
    fn get_session_token(&mut self) -> Option<String>;
    fn require_session_token(&mut self) -> IronResult<String>;
    fn expect_session_token(&mut self) -> IronResult<String>;
173 174
}

175 176
impl<'a, 'b> RequestSession for Request<'a, 'b> {
    fn get_session_token(self: &mut Self) -> Option<String> {
177
        let session_token = self.session().get::<SessionToken>().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
178
        (|st: Option<SessionToken>| -> Option<String> { Some(st?.token) })(session_token)
179 180 181
    }

    fn require_session_token(self: &mut Self) -> IronResult<String> {
182
        match self.session().get::<SessionToken>().unwrap() {
183 184
            Some(SessionToken { token: session }) => Ok(session),
            _ => {
Robert Czechowski's avatar
Robert Czechowski committed
185
                use rand::{distributions::Alphanumeric, thread_rng, Rng};
186

Daniel Brüning's avatar
Daniel Brüning committed
187
                let new_session_key: String = thread_rng().sample_iter(&Alphanumeric).take(28).collect();
188
                self.session().set(SessionToken { token: new_session_key }).unwrap();
Robert Czechowski's avatar
rustfmt  
Robert Czechowski committed
189 190 191 192 193 194 195 196 197
                Err(IronError {
                    error: Box::new(SessionError {
                        message: "No valid session found, redirecting to cookie page".to_string(),
                    }),
                    response: Response::with((
                        status::Found,
                        RedirectRaw(format!("/cookie?{}", self.url.path().join("/"))),
                    )),
                })
198 199 200 201 202
            }
        }
    }

    fn expect_session_token(self: &mut Self) -> IronResult<String> {
203
        match self.session().get::<SessionToken>().unwrap() {
204
            Some(SessionToken { token: session }) => Ok(session),
Robert Czechowski's avatar
Robert Czechowski committed
205 206 207
            _ => Err(IronError { error: Box::new(SessionError { message:
                                                                    "No valid session found, access denied".to_string() }),
                                 response: Response::with(status::Forbidden) }),
208 209 210 211 212 213 214 215
        }
    }
}

trait RequestRouterParam {
    fn get_str(self: &mut Self, key: &str) -> Option<String>;
    fn get_int<T: ::std::str::FromStr>(self: &mut Self, key: &str) -> Option<T>;
    fn expect_int<T: ::std::str::FromStr>(self: &mut Self, key: &str) -> IronResult<T>;
216
    fn expect_str(self: &mut Self, key: &str) -> IronResult<String>;
217 218 219 220 221 222 223 224 225 226
}

impl<'a, 'b> RequestRouterParam for Request<'a, 'b> {
    fn get_str(self: &mut Self, key: &str) -> Option<String> {
        Some(self.extensions.get::<Router>()?.find(key)?.to_owned())
    }

    fn get_int<T: ::std::str::FromStr>(self: &mut Self, key: &str) -> Option<T> {
        Some(self.extensions.get::<Router>()?.find(key)?.parse::<T>().ok()?)
    }
227

228 229 230
    fn expect_int<T: ::std::str::FromStr>(self: &mut Self, key: &str) -> IronResult<T> {
        match self.get_int::<T>(key) {
            Some(i) => Ok(i),
Robert Czechowski's avatar
Robert Czechowski committed
231 232 233
            _ => Err(IronError { error: Box::new(SessionError { message:
                                                                    "No valid routing parameter".to_string() }),
                                 response: Response::with(status::Forbidden) }),
234 235
        }
    }
236

237 238 239 240 241 242 243 244
    fn expect_str(self: &mut Self, key: &str) -> IronResult<String> {
        match self.get_str(key) {
            Some(s) => Ok(s),
            _ => Err(IronError { error: Box::new(SessionError { message:
                                                                    "Routing parameter missing".to_string() }),
                                 response: Response::with(status::Forbidden) }),
        }
    }
245 246
}

247
struct AugMedalError<'c, 'a: 'c, 'b: 'c + 'a>(core::MedalError, &'c mut Request<'a, 'b>);
248 249 250 251

impl<'c, 'a, 'b> From<AugMedalError<'c, 'a, 'b>> for IronError {
    fn from(AugMedalError(me, req): AugMedalError<'c, 'a, 'b>) -> Self {
        match me {
252
            core::MedalError::NotLoggedIn => {
Robert Czechowski's avatar
Robert Czechowski committed
253 254 255 256 257
                IronError { error: Box::new(SessionError { message:
                                                               "Not Logged in, redirecting to login page".to_string() }),
                            response: Response::with((status::Found,
                                                      RedirectRaw(format!("/login?{}", req.url.path().join("/"))))) }
            }
258 259 260 261 262 263 264
            core::MedalError::AccessDenied => IronError { error: Box::new(SessionError { message:
                                                                                             "Access denied".to_string() }),
                                                          response: Response::with(status::Unauthorized) },
            core::MedalError::CsrfCheckFailed => IronError { error: Box::new(SessionError { message:
                                                                                                "CSRF Error".to_string() }),
                                                             response: Response::with(status::Forbidden) },
            core::MedalError::SessionTimeout => {
Robert Czechowski's avatar
Robert Czechowski committed
265 266 267
                IronError { error: Box::new(SessionError { message: "Session timed out".to_string() }),
                            response: Response::with(status::Forbidden) }
            }
268
            core::MedalError::DatabaseError => {
Robert Czechowski's avatar
Robert Czechowski committed
269
                IronError { error: Box::new(SessionError { message: "Database Error".to_string() }),
270
                            response: Response::with(status::InternalServerError) }
Robert Czechowski's avatar
Robert Czechowski committed
271
            }
272
            core::MedalError::PasswordHashingError => {
Robert Czechowski's avatar
Robert Czechowski committed
273
                IronError { error: Box::new(SessionError { message: "Error hashing the passwords".to_string() }),
274
                            response: Response::with(status::InternalServerError) }
Robert Czechowski's avatar
Robert Czechowski committed
275
            }
276
            core::MedalError::UnmatchedPasswords => {
Robert Czechowski's avatar
Robert Czechowski committed
277 278 279 280
                IronError { error: Box::new(SessionError { message:
                                                               "The two passwords did not match.".to_string() }),
                            response: Response::with(status::Forbidden) }
            }
281
        }
282 283 284
    }
}

285 286 287
trait RequestAugmentMedalError<'c, 'a: 'c, 'b: 'c + 'a, R> {
    fn aug(self, req: &'c mut Request<'a, 'b>) -> Result<R, AugMedalError<'c, 'a, 'b>>;
}
288
impl<'c, 'a: 'c, 'b: 'c + 'a, T> RequestAugmentMedalError<'c, 'a, 'b, T> for Result<T, core::MedalError> {
289
    fn aug(self, req: &'c mut Request<'a, 'b>) -> Result<T, AugMedalError<'c, 'a, 'b>> {
Robert Czechowski's avatar
Robert Czechowski committed
290
        self.map_err(move |me| AugMedalError(me, req))
291 292 293
    }
}

294 295
fn greet_personal<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
296
    let session_token = req.get_session_token();
Robert Czechowski's avatar
Robert Czechowski committed
297 298
    // hier ggf. Daten aus dem Request holen

299
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
300
    let oauth_infos = (config.self_url.clone(), config.oauth_providers.clone());
301

302
    let (template, mut data) = with_conn![core::index, C, req, session_token, oauth_infos];
303

304 305 306 307 308
    /*if let Some(server_message) = &config.server_message {
        data.insert("server_message".to_string(), to_json(&server_message));
    }*/
    config.server_message.as_ref().map(|sm| data.insert("server_message".to_string(), to_json(&sm)));

309
    // Antwort erstellen und zurücksenden
Robert Czechowski's avatar
Robert Czechowski committed
310 311 312 313 314
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

315 316
fn dbstatus<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
317
    let status = with_conn![core::status, C, req, ()];
318 319 320 321 322 323

    let mut resp = Response::new();
    resp.set_mut(status).set_mut(status::Ok);
    Ok(resp)
}

324 325
fn debug<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
326 327
    let session_token = req.get_session_token();

328
    let (template, data) = with_conn![core::debug, C, req, session_token];
329 330 331 332 333 334

    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

335 336
fn debug_new_token<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
337 338
    let session_token = req.get_session_token();

339 340
    #[cfg(feature = "debug")]
    println!("Logging out session {:?}", session_token);
341

342
    with_conn![core::logout, C, req, session_token];
343 344 345 346

    Ok(Response::with((status::Found, Redirect(url_for!(req, "debug")))))
}

347 348
fn debug_logout<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
349 350
    let session_token = req.get_session_token();

351 352
    #[cfg(feature = "debug")]
    println!("Logging out session {:?}", session_token);
353

354
    with_conn![core::logout, C, req, session_token];
355 356 357 358

    Ok(Response::with((status::Found, Redirect(url_for!(req, "debug")))))
}

359 360
fn debug_create_session<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
361 362
    let session_token = req.get_session_token();

363
    with_conn![core::debug_create_session, C, req, session_token];
364 365 366 367

    Ok(Response::with((status::Found, Redirect(url_for!(req, "debug")))))
}

368 369
fn contests<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
370
    let session_token = req.require_session_token()?;
371 372 373 374 375 376 377
    let query_string = req.url.query().unwrap_or("").to_string();

    // TODO: Move to core::* ?
    let visibility = if query_string.contains("open") {
        core::ContestVisibility::Open
    } else if query_string.contains("current") {
        core::ContestVisibility::Current
378 379
    } else if query_string.contains("challenge") {
        core::ContestVisibility::LoginRequired
380 381 382
    } else {
        core::ContestVisibility::All
    };
383

384 385
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
    let (self_url, oauth_providers) = (config.self_url.clone(), config.oauth_providers.clone());
386

387 388
    let (template, mut data) =
        with_conn![core::show_contests, C, req, &session_token, (self_url, oauth_providers), visibility];
389 390

    config.server_message.as_ref().map(|sm| data.insert("server_message".to_string(), to_json(&sm)));
391

392 393 394 395
    if query_string.contains("results") {
        data.insert("direct_link_to_results".to_string(), to_json(&true));
    }

396 397 398 399 400
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

401 402
fn contest<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
403
    let contest_id = req.expect_int::<i32>("contestid")?;
404
    let session_token = req.require_session_token()?;
405
    let query_string = req.url.query().map(|s| s.to_string());
Robert Czechowski's avatar
Robert Czechowski committed
406

407 408 409 410 411
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
    let oauth_infos = (config.self_url.clone(), config.oauth_providers.clone());

    let (template, data) =
        with_conn![core::show_contest, C, req, contest_id, &session_token, query_string, oauth_infos].aug(req)?;
Robert Czechowski's avatar
Robert Czechowski committed
412 413 414 415 416 417

    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

418 419
fn contestresults<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
420 421
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
    let disable_contest_results = config.disable_results_page.unwrap_or(false);
422 423 424 425 426 427 428

    if disable_contest_results {
        let mut resp = Response::new();
        resp.set_mut(Template::new(&"nocontestresults", 2)).set_mut(status::Locked);
        return Ok(resp);
    }

429
    let contest_id = req.expect_int::<i32>("contestid")?;
430 431
    let session_token = req.require_session_token()?;

432
    let (template, data) = with_conn![core::show_contest_results, C, req, contest_id, &session_token].aug(req)?;
Daniel Brüning's avatar
Daniel Brüning committed
433

434 435 436 437 438
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

439 440 441 442 443 444
fn contestresults_download<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
    let disable_contest_results = config.disable_results_page.unwrap_or(false);

    println!("test");
445

446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
    if disable_contest_results {
        let mut resp = Response::new();
        resp.set_mut(Template::new(&"nocontestresults", 2)).set_mut(status::Locked);
        return Ok(resp);
    }

    let contest_id = req.expect_int::<i32>("contestid")?;
    let session_token = req.require_session_token()?;

    let (template, data) = with_conn![core::show_contest_results, C, req, contest_id, &session_token].aug(req)?;

    use iron::headers::{Charset, ContentDisposition, DispositionParam, DispositionType};

    let cd = ContentDisposition { disposition: DispositionType::Attachment,
                                  parameters: vec![DispositionParam::Filename(
        Charset::Ext("Utf-8".to_string()), // The character set for the bytes of the filename
        None,                              // The optional language tag (see `language-tag` crate)
        format!("{}.csv", data.get("contestname").unwrap().as_str().unwrap()).as_bytes().to_vec(), // the actual bytes of the filename
464
                                                                                                   // TODO: The name should be returned by core::show_contest_results directly
465 466 467 468 469 470 471 472
    )] };

    let mut resp = Response::new();
    resp.headers.set(cd);
    resp.set_mut(Template::new(&format!("{}_download", template), data)).set_mut(status::Ok);
    Ok(resp)
}

473 474
fn contest_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
475
    let contest_id = req.expect_int::<i32>("contestid")?;
476
    let session_token = req.expect_session_token()?;
477

Robert Czechowski's avatar
Robert Czechowski committed
478 479
    let csrf_token = {
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
480
        iexpect!(formdata.get("csrf_token"))[0].to_owned()
Robert Czechowski's avatar
Robert Czechowski committed
481 482
    };

483
    // TODO: Was mit dem Result?
484
    with_conn![core::start_contest, C, req, contest_id, &session_token, &csrf_token].aug(req)?;
Robert Czechowski's avatar
Robert Czechowski committed
485

486
    Ok(Response::with((status::Found, Redirect(url_for!(req, "contest", "contestid" => format!("{}",contest_id))))))
Robert Czechowski's avatar
Robert Czechowski committed
487 488
}

489 490
fn login<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
491 492
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
    let (self_url, oauth_providers) = (config.self_url.clone(), config.oauth_providers.clone());
493

494
    let mut data = json_val::Map::new();
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509

    let query_string = req.url.query().map(|s| s.to_string());
    if let Some(query) = query_string {
        data.insert("forward".to_string(), to_json(&query));
    }

    let mut oauth_links: Vec<(String, String, String)> = Vec::new();
    if let Some(oauth_providers) = oauth_providers {
        for oauth_provider in oauth_providers {
            oauth_links.push((oauth_provider.provider_id.to_owned(),
                              oauth_provider.login_link_text.to_owned(),
                              oauth_provider.url.to_owned()));
        }
    }

510
    data.insert("self_url".to_string(), to_json(&self_url));
511
    data.insert("oauth_links".to_string(), to_json(&oauth_links));
512
    data.insert("parent".to_string(), to_json(&"base"));
513

Robert Czechowski's avatar
Robert Czechowski committed
514
    let mut resp = Response::new();
515
    resp.set_mut(Template::new("login", data)).set_mut(status::Ok);
Robert Czechowski's avatar
Robert Czechowski committed
516 517 518
    Ok(resp)
}

519 520
fn login_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
Robert Czechowski's avatar
Robert Czechowski committed
521 522
    let logindata = {
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
Robert Czechowski's avatar
Robert Czechowski committed
523
        (iexpect!(formdata.get("username"))[0].to_owned(), iexpect!(formdata.get("password"))[0].to_owned())
Robert Czechowski's avatar
Robert Czechowski committed
524 525
    };

526 527
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
    let (self_url, oauth_providers) = (config.self_url.clone(), config.oauth_providers.clone());
528

529
    // TODO: Submit current session to login
Daniel Brüning's avatar
Daniel Brüning committed
530

531
    let loginresult = with_conn![core::login, C, req, logindata, (self_url, oauth_providers)];
Robert Czechowski's avatar
Robert Czechowski committed
532 533 534 535 536 537

    match loginresult {
        // Login successful
        Ok(sessionkey) => {
            req.session().set(SessionToken { token: sessionkey }).unwrap();
            Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
Robert Czechowski's avatar
Robert Czechowski committed
538
        }
Robert Czechowski's avatar
Robert Czechowski committed
539 540 541 542 543 544 545 546 547
        // Login failed
        Err((template, data)) => {
            let mut resp = Response::new();
            resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
            Ok(resp)
        }
    }
}

548 549
fn login_code_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
Robert Czechowski's avatar
Robert Czechowski committed
550 551 552 553 554
    let code = {
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
        iexpect!(formdata.get("code"))[0].to_owned()
    };

555 556
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
    let (self_url, oauth_providers) = (config.self_url.clone(), config.oauth_providers.clone());
557

558 559
    // TODO: Submit current session to login

560
    let loginresult = with_conn![core::login_with_code, C, req, &code, (self_url, oauth_providers)];
Robert Czechowski's avatar
Robert Czechowski committed
561 562 563 564 565 566

    match loginresult {
        // Login successful
        Ok(Ok(sessionkey)) => {
            req.session().set(SessionToken { token: sessionkey }).unwrap();
            Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
Robert Czechowski's avatar
Robert Czechowski committed
567
        }
Robert Czechowski's avatar
Robert Czechowski committed
568 569
        Ok(Err(sessionkey)) => {
            req.session().set(SessionToken { token: sessionkey }).unwrap();
570 571
            //Ok(Response::with((status::Found, Redirect(url_for!(req, "profile")))))
            Ok(Response::with((status::Found,
572 573
                               Redirect(iron::Url::parse(&format!("{}?status=firstlogin",
                                                                  &url_for!(req, "profile"))).unwrap()))))
Robert Czechowski's avatar
Robert Czechowski committed
574
        }
Robert Czechowski's avatar
Robert Czechowski committed
575 576 577 578 579 580 581
        // Login failed
        Err((template, data)) => {
            let mut resp = Response::new();
            resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
            Ok(resp)
        }
    }
582
}
Robert Czechowski's avatar
Robert Czechowski committed
583

584 585
fn logout<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
586
    let session_token = req.get_session_token();
Robert Czechowski's avatar
Robert Czechowski committed
587

588
    println!("Loggin out session {:?}", session_token);
Daniel Brüning's avatar
Daniel Brüning committed
589

590
    with_conn![core::logout, C, req, session_token];
Robert Czechowski's avatar
Robert Czechowski committed
591 592 593 594

    Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
}

595 596
fn submission<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
597
    let task_id = req.expect_int::<i32>("taskid")?;
598
    let session_token = req.expect_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
599 600 601
    let subtask: Option<String> = (|| -> Option<String> {
        req.get_ref::<UrlEncodedQuery>().ok()?.get("subtask")?.get(0).map(|x| x.to_owned())
    })();
602

603
    let result = with_conn![core::load_submission, C, req, task_id, &session_token, subtask];
Robert Czechowski's avatar
Robert Czechowski committed
604 605

    match result {
Robert Czechowski's avatar
Robert Czechowski committed
606 607
        Ok(data) => Ok(Response::with((status::Ok, mime!(Application / Json), format!("{}", data)))),
        Err(_) => Ok(Response::with((status::BadRequest, mime!(Application / Json), format!("{{}}")))),
Robert Czechowski's avatar
Robert Czechowski committed
608 609 610
    }
}

611 612
fn submission_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
613
    let task_id = req.expect_int::<i32>("taskid")?;
614
    let session_token = req.expect_session_token()?;
615
    let (csrf_token, data, grade, subtask) = {
Robert Czechowski's avatar
Robert Czechowski committed
616
        let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
617 618 619 620
        (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
         iexpect!(formdata.get("data"))[0].to_owned(),
         iexpect!(formdata.get("grade").unwrap_or(&vec!["0".to_owned()])[0].parse::<i32>().ok()),
         formdata.get("subtask").map(|x| x[0].to_owned()))
Robert Czechowski's avatar
Robert Czechowski committed
621
    };
622 623 624

    #[cfg(feature = "debug")]
    println!("New submission for task {} (graded {}): {}", task_id, grade, data);
625

626
    let result =
627
        with_conn![core::save_submission, C, req, task_id, &session_token, &csrf_token, data, grade, subtask].aug(req)?;
Daniel Brüning's avatar
Daniel Brüning committed
628

629
    Ok(Response::with((status::Ok, mime!(Application / Json), result)))
Robert Czechowski's avatar
Robert Czechowski committed
630 631
}

632 633
fn task<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
634
    let task_id = req.expect_int::<i32>("taskid")?;
635
    let session_token = req.require_session_token()?;
636

637
    let (template, data) = with_conn![core::show_task, C, req, task_id, &session_token].aug(req)?;
Robert Czechowski's avatar
Robert Czechowski committed
638 639 640 641 642 643

    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

644 645
fn groups<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
646
    let session_token = req.require_session_token()?;
Daniel Brüning's avatar
Daniel Brüning committed
647

648
    let (template, data) = with_conn![core::show_groups, C, req, &session_token].aug(req)?;
Daniel Brüning's avatar
Daniel Brüning committed
649

Robert Czechowski's avatar
Robert Czechowski committed
650 651 652 653 654
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

655 656
fn group<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
657
    let group_id = req.expect_int::<i32>("groupid")?;
658
    let session_token = req.require_session_token()?;
Daniel Brüning's avatar
Daniel Brüning committed
659

660
    let (template, data) = with_conn![core::show_group, C, req, group_id, &session_token].aug(req)?;
Daniel Brüning's avatar
Daniel Brüning committed
661

662 663 664
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
Robert Czechowski's avatar
Robert Czechowski committed
665 666
}

667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689
fn group_download<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
    let group_id = req.expect_int::<i32>("groupid")?;
    let session_token = req.require_session_token()?;

    let (template, data) = with_conn![core::show_group, C, req, group_id, &session_token].aug(req)?;

    use iron::headers::{Charset, ContentDisposition, DispositionParam, DispositionType};

    let cd = ContentDisposition { disposition: DispositionType::Attachment,
                                  parameters: vec![DispositionParam::Filename(
        Charset::Ext("Utf-8".to_string()), // The character set for the bytes of the filename
        None,                              // The optional language tag (see `language-tag` crate)
        format!("{}.csv", data.get("groupname").unwrap().as_str().unwrap()).as_bytes().to_vec(), // the actual bytes of the filename
                                                                                                 // TODO: The name should be returned by core::show_group directly
    )] };

    let mut resp = Response::new();
    resp.headers.set(cd);
    resp.set_mut(Template::new(&format!("{}_download", template), data)).set_mut(status::Ok);
    Ok(resp)
}

690
//TODO: Secure with CSRF-Token?
691 692
fn group_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
693
    let group_id = req.expect_int::<i32>("groupid")?;
694
    let session_token = req.expect_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
695

696
    //TODO: use result?
697
    with_conn![core::modify_group, C, req, group_id, &session_token].aug(req)?;
Robert Czechowski's avatar
rustfmt  
Robert Czechowski committed
698

699
    Ok(Response::with((status::Found, Redirect(url_for!(req, "group", "groupid" => format!("{}",group_id))))))
Robert Czechowski's avatar
Robert Czechowski committed
700 701
}

702 703
fn new_group<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
704
    let session_token = req.require_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
705

706
    let (csrf_token, name, tag) = {
Robert Czechowski's avatar
Robert Czechowski committed
707
        let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
708 709 710
        (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
         iexpect!(formdata.get("name"))[0].to_owned(),
         iexpect!(formdata.get("tag"))[0].to_owned())
Robert Czechowski's avatar
Robert Czechowski committed
711 712
    };

713
    let group_id = with_conn![core::add_group, C, req, &session_token, &csrf_token, name, tag].aug(req)?;
Daniel Brüning's avatar
Daniel Brüning committed
714

715
    Ok(Response::with((status::Found, Redirect(url_for!(req, "group", "groupid" => format!("{}",group_id))))))
Robert Czechowski's avatar
Robert Czechowski committed
716 717
}

718 719 720 721 722 723 724 725 726 727 728 729 730
fn group_csv<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
    let session_token = req.require_session_token()?;

    template_ok!(with_conn![core::group_csv, C, req, &session_token].aug(req)?)
}

fn group_csv_upload<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
    let session_token = req.require_session_token()?;

    let (csrf_token, group_data) = {
        let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
Robert Czechowski's avatar
rustfmt  
Robert Czechowski committed
731
        (iexpect!(formdata.get("csrf_token"))[0].to_owned(), iexpect!(formdata.get("group_data"))[0].to_owned())
732 733
    };

Robert Czechowski's avatar
rustfmt  
Robert Czechowski committed
734 735
    println!("{}", group_data);

736
    with_conn![core::upload_groups, C, req, &session_token, &csrf_token, &group_data].aug(req)?;
737 738 739 740

    Ok(Response::with((status::Found, Redirect(url_for!(req, "groups")))))
}

741 742
fn profile<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
743
    let session_token = req.require_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
744
    let query_string = req.url.query().map(|s| s.to_string());
Daniel Brüning's avatar
Daniel Brüning committed
745

746
    let (template, data) = with_conn![core::show_profile, C, req, &session_token, None, query_string].aug(req)?;
Daniel Brüning's avatar
Daniel Brüning committed
747

Robert Czechowski's avatar
Robert Czechowski committed
748 749 750 751 752
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

753 754
fn profile_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
755
    let session_token = req.expect_session_token()?;
756
    let (csrf_token, firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex) = {
Robert Czechowski's avatar
Robert Czechowski committed
757
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
758
        (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
Robert Czechowski's avatar
Robert Czechowski committed
759 760
         iexpect!(formdata.get("firstname"))[0].to_owned(),
         iexpect!(formdata.get("lastname"))[0].to_owned(),
761 762 763 764 765
         formdata.get("street").map(|x| x[0].to_owned()),
         formdata.get("zip").map(|x| x[0].to_owned()),
         formdata.get("city").map(|x| x[0].to_owned()),
         formdata.get("password").map(|x| x[0].to_owned()),
         formdata.get("password_repeat").map(|x| x[0].to_owned()),
766 767
         iexpect!(formdata.get("grade"))[0].parse::<i32>().unwrap_or(0),
         iexpect!(formdata.get("sex"))[0].parse::<i32>().ok())
Robert Czechowski's avatar
Robert Czechowski committed
768
    };
Daniel Brüning's avatar
Daniel Brüning committed
769

770 771 772 773 774 775 776 777
    let profilechangeresult =
        with_conn![core::edit_profile,
                   C,
                   req,
                   &session_token,
                   None,
                   &csrf_token,
                   (firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex)].aug(req)?;
778 779 780 781 782

    Ok(Response::with((status::Found,
                       Redirect(iron::Url::parse(&format!("{}?status={:?}",
                                                          &url_for!(req, "profile"),
                                                          profilechangeresult)).unwrap()))))
Robert Czechowski's avatar
Robert Czechowski committed
783 784
}

785 786
fn user<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
787
    let user_id = req.expect_int::<i32>("userid")?;
788
    let session_token = req.expect_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
789
    let query_string = req.url.query().map(|s| s.to_string());
Daniel Brüning's avatar
Daniel Brüning committed
790

Robert Czechowski's avatar
Robert Czechowski committed
791
    let (template, data) =
792
        with_conn![core::show_profile, C, req, &session_token, Some(user_id), query_string].aug(req)?;
Daniel Brüning's avatar
Daniel Brüning committed
793

Robert Czechowski's avatar
Robert Czechowski committed
794 795 796 797 798
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

799 800
fn user_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
801
    let user_id = req.expect_int::<i32>("userid")?;
802
    let session_token = req.expect_session_token()?;
803
    let (csrf_token, firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex) = {
804
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
805
        (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
Robert Czechowski's avatar
Robert Czechowski committed
806 807
         iexpect!(formdata.get("firstname"))[0].to_owned(),
         iexpect!(formdata.get("lastname"))[0].to_owned(),
808 809 810 811 812
         formdata.get("street").map(|x| x[0].to_owned()),
         formdata.get("zip").map(|x| x[0].to_owned()),
         formdata.get("city").map(|x| x[0].to_owned()),
         formdata.get("password").map(|x| x[0].to_owned()),
         formdata.get("password_repeat").map(|x| x[0].to_owned()),
813 814
         iexpect!(formdata.get("grade"))[0].parse::<i32>().unwrap_or(0),
         iexpect!(formdata.get("sex"))[0].parse::<i32>().ok())
815
    };
Daniel Brüning's avatar
Daniel Brüning committed
816

817 818 819 820 821 822 823 824
    let profilechangeresult =
        with_conn![core::edit_profile,
                   C,
                   req,
                   &session_token,
                   Some(user_id),
                   &csrf_token,
                   (firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex)].aug(req)?;
825 826 827 828 829 830

    Ok(Response::with((status::Found,
                       Redirect(iron::Url::parse(&format!("{}?status={:?}",
                                                          &url_for!(req, "user", "userid" => format!("{}",user_id)),
                                                          profilechangeresult)).unwrap()))))
    //old:   Ok(Response::with((status::Found, Redirect(url_for!(req, "user", "userid" => format!("{}",user_id))))))
831 832
}

833 834 835 836
fn teacherinfos<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
    let session_token = req.expect_session_token()?;

837 838
    let config = req.get::<Read<SharedConfiguration>>().unwrap();

839 840
    let (template, data) =
        with_conn![core::teacher_infos, C, req, &session_token, config.teacher_page.as_ref().map(|x| &**x)].aug(req)?;
841
    // .as_ref().map(|x| &**x) can be written as .as_deref() since rust 1.40
842 843 844 845 846 847

    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

848
fn admin<C>(req: &mut Request) -> IronResult<Response>
849
    where C: MedalConnection + std::marker::Send + 'static {
850 851
    let session_token = req.expect_session_token()?;

852
    let (template, data) = with_conn![core::admin_index, C, req, &session_token].aug(req)?;
853 854

    let mut resp = Response::new();
855
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
856 857 858 859 860 861 862
    Ok(resp)
}

fn admin_users<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
    let session_token = req.expect_session_token()?;

863
    let (s_id, s_firstname, s_lastname, s_logincode, s_groupcode, s_pms_id) = {
864 865 866 867 868
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
        (formdata.get("id").map(|x| x[0].parse::<i32>().unwrap_or(0)),
         formdata.get("firstname").map(|x| x[0].to_owned()),
         formdata.get("lastname").map(|x| x[0].to_owned()),
         formdata.get("logincode").map(|x| x[0].to_owned()),
869
         formdata.get("groupcode").map(|x| x[0].to_owned()),
870 871 872 873 874 875 876
         formdata.get("pmsid").map(|x| x[0].to_owned()))
    };

    let (template, data) = with_conn![core::admin_search_users,
                                      C,
                                      req,
                                      &session_token,
877
                                      (s_id, s_firstname, s_lastname, s_logincode, s_groupcode, s_pms_id)].aug(req)?;
878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908

    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

fn admin_user<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
    let user_id = req.expect_int::<i32>("userid")?;
    let session_token = req.expect_session_token()?;

    let csrf_token = if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
        // or iexpect!(formdata.get("csrf_token"))[0].to_owned(), ?
        formdata.get("csrf_token").map(|x| x[0].to_owned())
    } else {
        None
    };

    let (template, data) = if let Some(csrf_token) = csrf_token {
        with_conn![core::admin_delete_user, C, req, user_id, &session_token, &csrf_token].aug(req)?
    } else {
        with_conn![core::admin_show_user, C, req, user_id, &session_token].aug(req)?
    };

    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

fn admin_group<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
909
    let group_id = req.expect_int::<i32>("groupid")?;
910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930
    let session_token = req.expect_session_token()?;

    let csrf_token = if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
        formdata.get("csrf_token").map(|x| x[0].to_owned())
    } else {
        None
    };

    let (template, data) = if let Some(csrf_token) = csrf_token {
        with_conn![core::admin_delete_group, C, req, group_id, &session_token, &csrf_token].aug(req)?
    } else {
        with_conn![core::admin_show_group, C, req, group_id, &session_token].aug(req)?
    };

    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

fn admin_participation<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
931 932
    let user_id = req.expect_int::<i32>("userid")?;
    let contest_id = req.expect_int::<i32>("contestid")?;
933 934 935 936