webfw_iron.rs 61 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
use iron::prelude::*;
use iron::{status, AfterMiddleware, AroundMiddleware, Handler};
24
use iron::mime::Mime;
Robert Czechowski's avatar
Robert Czechowski committed
25
use iron_sessionstorage;
Robert Czechowski's avatar
Robert Czechowski committed
26
use iron_sessionstorage::backends::SignedCookieBackend;
Robert Czechowski's avatar
Robert Czechowski committed
27
use iron_sessionstorage::traits::*;
Robert Czechowski's avatar
Robert Czechowski committed
28
use iron_sessionstorage::SessionStorage;
Robert Czechowski's avatar
Robert Czechowski committed
29
use mount::Mount;
30
use persistent::{Read, Write};
31
use reqwest;
Robert Czechowski's avatar
Robert Czechowski committed
32
33
34
use router::Router;
use staticfile::Static;
use urlencoded::{UrlEncodedBody, UrlEncodedQuery};
35

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

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

45
#[cfg(feature = "signup")]
46
47
use db_conn::SignupResult;

48
static TASK_DIR: &str = "tasks";
Robert Czechowski's avatar
Robert Czechowski committed
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

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)),* ]
        )
    );
}

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

74
macro_rules! template_ok {
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
75
76
77
78
79
80
81
    ( $x:expr ) => {{
        let (template, data) = $x;

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

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

93
94
95
96
97
98
99
100
101
/** 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();
Robert Czechowski's avatar
Robert Czechowski committed
102
                    if (400..=599).contains(&n) {
103
104
105
106
107
108
109
110
111
112
113
114
115
116
                        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
117
#[derive(Debug)]
Robert Czechowski's avatar
Robert Czechowski committed
118
struct SessionToken {
Robert Czechowski's avatar
Robert Czechowski committed
119
    token: String,
Robert Czechowski's avatar
Robert Czechowski committed
120
121
122
123
124
125
126
127
128
129
130
131
132
}
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 })
        }
    }
}

133
134
135
pub struct CookieDistributor {}

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

139
        Box::new(move |req: &mut Request| -> IronResult<Response> {
140
            if req.session().get::<SessionToken>().expect("blub...").is_none() {
141
142
143
144
145
146
147
148
                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
149
#[cfg(feature = "debug")]
150
151
pub struct RequestLogger {}

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

        Ok(())
    }
}

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

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

173
174
175
176
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>;
177
178
}

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

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

Daniel Brüning's avatar
Daniel Brüning committed
191
                let new_session_key: String = thread_rng().sample_iter(&Alphanumeric).take(28).collect();
192
                self.session().set(SessionToken { token: new_session_key }).unwrap();
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
193
194
195
196
197
198
199
200
201
                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("/"))),
                    )),
                })
202
203
204
205
            }
        }
    }

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

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

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

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

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

241
    fn expect_str(&mut self, key: &str) -> IronResult<String> {
242
243
244
245
246
247
248
        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) }),
        }
    }
249
250
}

251
struct AugMedalError<'c, 'a: 'c, 'b: 'c + 'a>(core::MedalError, &'c mut Request<'a, 'b>);
252
253
254
255

impl<'c, 'a, 'b> From<AugMedalError<'c, 'a, 'b>> for IronError {
    fn from(AugMedalError(me, req): AugMedalError<'c, 'a, 'b>) -> Self {
        match me {
256
            core::MedalError::NotLoggedIn => {
Robert Czechowski's avatar
Robert Czechowski committed
257
                IronError { error: Box::new(SessionError { message:
Robert Czechowski's avatar
Robert Czechowski committed
258
259
260
261
                                                               "Not Logged in, redirecting to login page".to_string() }),
                            response: Response::with((status::Found,
                                                      RedirectRaw(format!("/login?{}", req.url.path().join("/"))))) }
            }
262
263
264
265
266
267
268
            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
269
270
271
                IronError { error: Box::new(SessionError { message: "Session timed out".to_string() }),
                            response: Response::with(status::Forbidden) }
            }
272
            core::MedalError::DatabaseError => {
Robert Czechowski's avatar
Robert Czechowski committed
273
                IronError { error: Box::new(SessionError { message: "Database Error".to_string() }),
274
                            response: Response::with(status::InternalServerError) }
Robert Czechowski's avatar
Robert Czechowski committed
275
            }
276
277
278
279
            core::MedalError::ConfigurationError => {
                IronError { error: Box::new(SessionError { message: "Server misconfiguration. Please contact an administrator!".to_string() }),
                            response: Response::with(status::InternalServerError) }
            }
280
281
282
283
            core::MedalError::DatabaseConnectionError => {
                IronError { error: Box::new(SessionError { message: "Database Connection Error".to_string() }),
                            response: Response::with(status::InternalServerError) }
            }
284
            core::MedalError::PasswordHashingError => {
Robert Czechowski's avatar
Robert Czechowski committed
285
                IronError { error: Box::new(SessionError { message: "Error hashing the passwords".to_string() }),
286
                            response: Response::with(status::InternalServerError) }
Robert Czechowski's avatar
Robert Czechowski committed
287
            }
288
            core::MedalError::UnmatchedPasswords => {
Robert Czechowski's avatar
Robert Czechowski committed
289
290
291
292
                IronError { error: Box::new(SessionError { message:
                                                               "The two passwords did not match.".to_string() }),
                            response: Response::with(status::Forbidden) }
            }
Robert Czechowski's avatar
Robert Czechowski committed
293
294
295
            core::MedalError::NotFound => IronError { error: Box::new(SessionError { message:
                                                                                         "Not found".to_string() }),
                                                      response: Response::with(status::NotFound) },
296
297
298
299
300
            core::MedalError::AccountIncomplete => IronError { error: Box::new(SessionError { message:
                                                                                              "Account incomplete".to_string() }),
                                                               response: Response::with((status::Found,
                                                                                         Redirect(iron::Url::parse(&format!("{}?status=firstlogin",
                                                                                                                            &url_for!(req, "profile"))).unwrap()))) },
301
302
303
304
            core::MedalError::OauthError(errstr) => {
                IronError { error: Box::new(SessionError { message: format!("Access denied (Error {})", errstr) }),
                            response: Response::with(status::Unauthorized) }
            }
305
        }
306
307
308
    }
}

309
310
311
trait RequestAugmentMedalError<'c, 'a: 'c, 'b: 'c + 'a, R> {
    fn aug(self, req: &'c mut Request<'a, 'b>) -> Result<R, AugMedalError<'c, 'a, 'b>>;
}
312
impl<'c, 'a: 'c, 'b: 'c + 'a, T> RequestAugmentMedalError<'c, 'a, 'b, T> for Result<T, core::MedalError> {
313
    fn aug(self, req: &'c mut Request<'a, 'b>) -> Result<T, AugMedalError<'c, 'a, 'b>> {
Robert Czechowski's avatar
Robert Czechowski committed
314
        self.map_err(move |me| AugMedalError(me, req))
315
316
317
    }
}

318
319
320
321
322
323
fn login_info(config: &Config) -> core::LoginInfo {
    core::LoginInfo { password_login: config.enable_password_login == Some(true),
                      self_url: config.self_url.clone(),
                      oauth_providers: config.oauth_providers.clone() }
}

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

329
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
330
    let (template, mut data) = with_conn![core::index, C, req, session_token, login_info(&config)].aug(req)?;
331

332
    data.insert("config".to_string(), to_json(&config.template_params));
333

334
    // Antwort erstellen und zurücksenden
Robert Czechowski's avatar
Robert Czechowski committed
335
336
337
338
339
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

340
341
fn dbstatus<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
342
343
344
345
346

    let config = req.get::<Read<SharedConfiguration>>().unwrap();
    let query_string = req.url.query().map(|s| s.to_string());
    
    let status = with_conn![core::status, C, req, config.dbstatus_secret.clone(), query_string].aug(req)?;
347
348
349
350
351
352

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

353
354
fn debug<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
355
356
    let session_token = req.get_session_token();

357
    let (template, data) = with_conn![core::debug, C, req, session_token];
358
359
360
361
362
363

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

364
365
fn debug_new_token<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
366
367
    let session_token = req.get_session_token();

368
369
    #[cfg(feature = "debug")]
    println!("Logging out session {:?}", session_token);
370

371
    with_conn![core::logout, C, req, session_token];
372
373
374
375

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

376
377
fn debug_logout<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
378
379
    let session_token = req.get_session_token();

380
381
    #[cfg(feature = "debug")]
    println!("Logging out session {:?}", session_token);
382

383
    with_conn![core::logout, C, req, session_token];
384
385
386
387

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

388
389
fn debug_create_session<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
390
391
    let session_token = req.get_session_token();

392
    with_conn![core::debug_create_session, C, req, session_token];
393
394
395
396

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

397
398
fn contests<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
399
    let session_token = req.require_session_token()?;
400
401
402
403
404
405
406
    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
407
408
    } else if query_string.contains("challenge") {
        core::ContestVisibility::LoginRequired
409
410
411
    } else {
        core::ContestVisibility::All
    };
412

413
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428

    let res = with_conn![core::show_contests, C, req, &session_token, login_info(&config), visibility];

    if res.is_err() {
        // Database connection failed … Create a new database connection!
        // TODO: This code should be unified with the database creation code in main.rs
        println!("DATABASE CONNECTION LOST! Restarting database connection.");
        let conn = C::reconnect(&config);
        let mutex = req.get::<Write<SharedDatabaseConnection<C>>>().unwrap();
        let mut sharedconn = mutex.lock().unwrap_or_else(|e| e.into_inner());
        *sharedconn = conn;
        // return ServerError();
    }

    let (template, mut data) = res.unwrap();
429

430
    data.insert("config".to_string(), to_json(&config.template_params));
431

432
433
434
435
    if query_string.contains("results") {
        data.insert("direct_link_to_results".to_string(), to_json(&true));
    }

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

441
442
fn contest<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
443
    let contest_id = req.expect_int::<i32>("contestid")?;
444
    let session_token = req.require_session_token()?;
445
    let query_string = req.url.query().map(|s| s.to_string());
Robert Czechowski's avatar
Robert Czechowski committed
446

447
448
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
    let (template, data) =
449
        with_conn![core::show_contest, C, req, contest_id, &session_token, query_string, login_info(&config)].aug(req)?;
Robert Czechowski's avatar
Robert Czechowski committed
450
451
452
453
454
455

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

456
457
fn contestresults<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
458
459
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
    let disable_contest_results = config.disable_results_page.unwrap_or(false);
460
461
462
463
464
465
466

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

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

470
    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
471

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

477
478
479
480
481
482
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");
483

484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
    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
502
                                                                                                   // TODO: The name should be returned by core::show_contest_results directly
503
504
    )] };

505
    let mime: Mime = "text/csv".parse().unwrap();
506
507
    let mut resp = Response::new();
    resp.headers.set(cd);
508
    resp.set_mut(Template::new(&format!("{}_download", template), data)).set_mut(status::Ok).set_mut(mime);
509
510
511
    Ok(resp)
}

512
513
fn contest_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
514
    let contest_id = req.expect_int::<i32>("contestid")?;
515
    let session_token = req.expect_session_token()?;
516

Robert Czechowski's avatar
Robert Czechowski committed
517
518
    let csrf_token = {
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
519
        iexpect!(formdata.get("csrf_token"))[0].to_owned()
Robert Czechowski's avatar
Robert Czechowski committed
520
521
    };

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

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

528
529
fn login<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
530
    let session_token = req.get_session_token();
531

532
533
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
    let (template, mut data) = with_conn![core::show_login, C, req, session_token, login_info(&config)];
534
535
536
537
538
539

    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));
    }

540
    // Antwort erstellen und zurücksenden
Robert Czechowski's avatar
Robert Czechowski committed
541
    let mut resp = Response::new();
542
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
Robert Czechowski's avatar
Robert Czechowski committed
543
544
545
    Ok(resp)
}

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

553
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
554
    // TODO: Submit current session to login
555
    let loginresult = with_conn![core::login, C, req, logindata, login_info(&config)];
Robert Czechowski's avatar
Robert Czechowski committed
556
557
558
559
560
561

    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
562
        }
Robert Czechowski's avatar
Robert Czechowski committed
563
564
565
566
567
568
569
570
571
        // Login failed
        Err((template, data)) => {
            let mut resp = Response::new();
            resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
            Ok(resp)
        }
    }
}

572
573
fn login_code_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
Robert Czechowski's avatar
Robert Czechowski committed
574
575
576
577
578
    let code = {
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
        iexpect!(formdata.get("code"))[0].to_owned()
    };

579
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
580
    // TODO: Submit current session to login
581
    let loginresult = with_conn![core::login_with_code, C, req, &code, login_info(&config)];
Robert Czechowski's avatar
Robert Czechowski committed
582
583
584
585
586
587

    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
588
        }
Robert Czechowski's avatar
Robert Czechowski committed
589
590
        Ok(Err(sessionkey)) => {
            req.session().set(SessionToken { token: sessionkey }).unwrap();
591
592
            //Ok(Response::with((status::Found, Redirect(url_for!(req, "profile")))))
            Ok(Response::with((status::Found,
593
594
                               Redirect(iron::Url::parse(&format!("{}?status=firstlogin",
                                                                  &url_for!(req, "profile"))).unwrap()))))
Robert Czechowski's avatar
Robert Czechowski committed
595
        }
Robert Czechowski's avatar
Robert Czechowski committed
596
597
598
599
600
601
602
        // Login failed
        Err((template, data)) => {
            let mut resp = Response::new();
            resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
            Ok(resp)
        }
    }
603
}
Robert Czechowski's avatar
Robert Czechowski committed
604

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

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

611
    with_conn![core::logout, C, req, session_token];
Robert Czechowski's avatar
Robert Czechowski committed
612
613
614
615

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

616
#[cfg(feature = "signup")]
617
618
619
620
621
622
623
624
625
626
fn signup<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
    let query_string = req.url.query().map(|s| s.to_string());

    let data = core::signupdata(query_string);
    let mut resp = Response::new();
    resp.set_mut(Template::new("signup", data)).set_mut(status::Ok);
    Ok(resp)
}

627
#[cfg(feature = "signup")]
628
629
630
631
632
fn signup_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
    let session_token = req.get_session_token();
    let signupdata = {
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
Robert Czechowski's avatar
Robert Czechowski committed
633
634
635
        (iexpect!(formdata.get("username"))[0].to_owned(),
         iexpect!(formdata.get("email"))[0].to_owned(),
         iexpect!(formdata.get("password"))[0].to_owned())
636
637
638
639
    };

    let signupresult = with_conn![core::signup, C, req, session_token, signupdata].aug(req)?;
    match signupresult {
Robert Czechowski's avatar
Robert Czechowski committed
640
641
642
643
644
645
646
647
        SignupResult::SignedUp => Ok(Response::with((status::Found,
                                                     Redirect(iron::Url::parse(&format!("{}?status={:?}",
                                                                                        &url_for!(req, "profile"),
                                                                                        signupresult)).unwrap())))),
        _ => Ok(Response::with((status::Found,
                                Redirect(iron::Url::parse(&format!("{}?status={:?}",
                                                                   &url_for!(req, "signup"),
                                                                   signupresult)).unwrap())))),
648
649
650
    }
}

651
652
653
654
655
656
657
658
659
660
661
662
#[cfg(not(feature = "signup"))]
fn signup<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
    Err(core::MedalError::NotFound).aug(req).map_err(|x| x.into())
}

#[cfg(not(feature = "signup"))]
fn signup_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
    Err(core::MedalError::NotFound).aug(req).map_err(|x| x.into())
}

663
664
fn submission<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
665
    let task_id = req.expect_int::<i32>("taskid")?;
Robert Czechowski's avatar
Robert Czechowski committed
666

667
    let session_token = req.expect_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
668
669
670
    let subtask: Option<String> = (|| -> Option<String> {
        req.get_ref::<UrlEncodedQuery>().ok()?.get("subtask")?.get(0).map(|x| x.to_owned())
    })();
671

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

    match result {
675
        Ok(data) => Ok(Response::with((status::Ok, mime!(Application / Json), data))),
Robert Czechowski's avatar
Robert Czechowski committed
676
        Err(_) => Ok(Response::with((status::BadRequest, mime!(Application / Json), "{}".to_string()))),
Robert Czechowski's avatar
Robert Czechowski committed
677
678
679
    }
}

680
681
fn submission_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
682
    let task_id = req.expect_int::<i32>("taskid")?;
683
    let session_token = req.expect_session_token()?;
684
    let (csrf_token, data, grade, subtask) = {
Robert Czechowski's avatar
Robert Czechowski committed
685
        let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
686
687
688
689
        (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
690
    };
691
692
693

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

695
    let result =
696
        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
697

698
    Ok(Response::with((status::Ok, mime!(Application / Json), result)))
Robert Czechowski's avatar
Robert Czechowski committed
699
700
}

701
702
fn task<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
703
    let task_id = req.expect_int::<i32>("taskid")?;
704
    let session_token = req.require_session_token()?;
705

706
    let (template, data) = with_conn![core::show_task, C, req, task_id, &session_token].aug(req)?;
Robert Czechowski's avatar
Robert Czechowski committed
707
708
709
710
711
712

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

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

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

Robert Czechowski's avatar
Robert Czechowski committed
719
720
721
722
723
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

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

729
    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
730

731
732
733
    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
734
735
}

736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
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
    )] };

753
    let mime: Mime = "text/csv".parse().unwrap();
754
755
    let mut resp = Response::new();
    resp.headers.set(cd);
756
    resp.set_mut(Template::new(&format!("{}_download", template), data)).set_mut(status::Ok).set_mut(mime);
757
758
759
    Ok(resp)
}

760
//TODO: Secure with CSRF-Token?
761
762
fn group_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
763
    let group_id = req.expect_int::<i32>("groupid")?;
764
    let session_token = req.expect_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
765

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

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

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

776
    let (csrf_token, name, tag) = {
Robert Czechowski's avatar
Robert Czechowski committed
777
        let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
778
779
780
        (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
781
782
    };

783
    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
784

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

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

792
793
794
795
796
797
798
799
800
    let si = {
        let config = req.get::<Read<SharedConfiguration>>().unwrap();
        core::SexInformation { require_sex: config.require_sex.unwrap_or(false),
                               allow_sex_na: config.allow_sex_na.unwrap_or(true),
                               allow_sex_diverse: config.allow_sex_diverse.unwrap_or(false),
                               allow_sex_other: config.allow_sex_other.unwrap_or(true) }
    };

    template_ok!(with_conn![core::group_csv, C, req, &session_token, si].aug(req)?)
801
802
803
804
805
806
807
808
}

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
809
        (iexpect!(formdata.get("csrf_token"))[0].to_owned(), iexpect!(formdata.get("group_data"))[0].to_owned())
810
811
    };

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

814
    with_conn![core::upload_groups, C, req, &session_token, &csrf_token, &group_data].aug(req)?;
815
816
817
818

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

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

824
825
    let si = {
        let config = req.get::<Read<SharedConfiguration>>().unwrap();
826
827
828
829
        core::SexInformation { require_sex: config.require_sex.unwrap_or(false),
                               allow_sex_na: config.allow_sex_na.unwrap_or(true),
                               allow_sex_diverse: config.allow_sex_diverse.unwrap_or(false),
                               allow_sex_other: config.allow_sex_other.unwrap_or(true) }
830
831
832
    };

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

Robert Czechowski's avatar
Robert Czechowski committed
834
835
836
837
838
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

839
840
fn profile_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
841
    let session_token = req.expect_session_token()?;
842
    let (csrf_token, firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex) = {
Robert Czechowski's avatar
Robert Czechowski committed
843
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
844
        (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
Robert Czechowski's avatar
Robert Czechowski committed
845
846
         iexpect!(formdata.get("firstname"))[0].to_owned(),
         iexpect!(formdata.get("lastname"))[0].to_owned(),
847
848
849
850
851
         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()),
852
853
         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
854
    };
Daniel Brüning's avatar
Daniel Brüning committed
855

856
857
858
859
860
861
862
863
    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)?;
864
865
866
867
868

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

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

877
878
    let si = {
        let config = req.get::<Read<SharedConfiguration>>().unwrap();
879
880
881
882
        core::SexInformation { require_sex: config.require_sex.unwrap_or(false),
                               allow_sex_na: config.allow_sex_na.unwrap_or(true),
                               allow_sex_diverse: config.allow_sex_diverse.unwrap_or(false),
                               allow_sex_other: config.allow_sex_other.unwrap_or(true) }
883
884
    };

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

Robert Czechowski's avatar
Robert Czechowski committed
888
889
890
891
892
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

893
894
fn user_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
895
    let user_id = req.expect_int::<i32>("userid")?;
896
    let session_token = req.expect_session_token()?;
897
    let (csrf_token, firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex) = {
898
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
899
        (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
Robert Czechowski's avatar
Robert Czechowski committed
900
901
         iexpect!(formdata.get("firstname"))[0].to_owned(),
         iexpect!(formdata.get("lastname"))[0].to_owned(),
902
903
904
905
906
         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()),
907
908
         iexpect!(formdata.get("grade"))[0].parse::<i32>().unwrap_or(0),
         iexpect!(formdata.get("sex"))[0].parse::<i32>().ok())
909
    };
Daniel Brüning's avatar
Daniel Brüning committed
910

911
912
913
914
915
916
917
918
    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)?;
919
920
921
922
923
924

    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))))))
925
926
}

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

931
932
    let config = req.get::<Read<SharedConfiguration>>().unwrap();

933
934
935
    let (template, mut data) = with_conn![core::teacher_infos, C, req, &session_token].aug(req)?;

    data.insert("config".to_string(), to_json(&config.template_params));
936
937
938
939
940
941

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

942
fn admin<C>(req: &mut Request) -> IronResult<Response>
943
    where C: MedalConnection + std::marker::Send + 'static {