webfw_iron.rs 35.4 KB
Newer Older
Robert Czechowski's avatar
Robert Czechowski committed
1
2
3
4
5
//extern crate serde;

use std::path::Path;

use iron::prelude::*;
Robert Czechowski's avatar
Robert Czechowski committed
6
use iron_sessionstorage::traits::*;
7

Robert Czechowski's avatar
Robert Czechowski committed
8
use iron::modifiers::Redirect;
9
use iron::modifiers::RedirectRaw;
10
use iron::{status, AfterMiddleware, BeforeMiddleware};
Robert Czechowski's avatar
Robert Czechowski committed
11
12
13
14
15
16

use mount::Mount;
use router::Router;
use staticfile::Static;

use iron_sessionstorage::backends::SignedCookieBackend;
Robert Czechowski's avatar
Robert Czechowski committed
17
18
use iron_sessionstorage::SessionStorage;
use persistent::Write;
19
use urlencoded::{UrlEncodedBody, UrlEncodedQuery};
Robert Czechowski's avatar
Robert Czechowski committed
20
21

pub use handlebars_iron::handlebars::to_json;
Robert Czechowski's avatar
Robert Czechowski committed
22
use handlebars_iron::{DirectorySource, HandlebarsEngine, Template};
Robert Czechowski's avatar
Robert Czechowski committed
23

24
use iron;
Robert Czechowski's avatar
Robert Czechowski committed
25
use iron_sessionstorage;
26
27
use reqwest;

28
29
use db_conn::MedalConnection;

Robert Czechowski's avatar
Robert Czechowski committed
30
31
pub use serde_json::value as json_val;

32
use config::Config;
Robert Czechowski's avatar
Robert Czechowski committed
33
34
use iron::typemap::Key;

35
36
use core;

Robert Czechowski's avatar
Robert Czechowski committed
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
static TASK_DIR: &'static str = "tasks";

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

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

struct ErrorReporter;
impl AfterMiddleware for ErrorReporter {
    fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> {
        println!("{}", err);
        Err(err)
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
71
#[derive(Debug)]
Robert Czechowski's avatar
Robert Czechowski committed
72
struct SessionToken {
Robert Czechowski's avatar
Robert Czechowski committed
73
    token: String,
Robert Czechowski's avatar
Robert Czechowski committed
74
75
76
77
78
79
80
81
82
83
84
85
86
}
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 })
        }
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
87
88
use iron::middleware::{AroundMiddleware, Handler};

89
90
91
92
93
94
95
96
pub struct CookieDistributor {}

impl CookieDistributor {
    pub fn new() -> Self { Self {} }
}

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

99
        Box::new(move |req: &mut Request| -> IronResult<Response> {
100
            if req.session().get::<SessionToken>().expect("blub...").is_none() {
101
102
103
104
105
106
107
108
                let session_token: String = thread_rng().sample_iter(&Alphanumeric).take(10).collect();
                req.session().set(SessionToken { token: session_token }).unwrap();
            }
            handler.handle(req)
        })
    }
}

109
110
111
112
113
114
115
116
117
118
119
120
121
122
pub struct RequestLogger {}

impl RequestLogger {
    pub fn new() -> Self { Self {} }
}

impl BeforeMiddleware for RequestLogger {
    fn before(&self, req: &mut Request) -> IronResult<()> {
        println!("{}: {}", req.method, req.url);

        Ok(())
    }
}

123
124
#[derive(Debug)]
struct SessionError {
Robert Czechowski's avatar
Robert Czechowski committed
125
    message: String,
126
127
}
impl ::std::error::Error for SessionError {
Robert Czechowski's avatar
Robert Czechowski committed
128
    fn description(&self) -> &str { &self.message }
129
130
131
}

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

135
136
137
138
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>;
139
140
}

141
142
impl<'a, 'b> RequestSession for Request<'a, 'b> {
    fn get_session_token(self: &mut Self) -> Option<String> {
143
        let session_token = self.session().get::<SessionToken>().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
144
        (|st: Option<SessionToken>| -> Option<String> { Some(st?.token) })(session_token)
145
146
147
    }

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

Daniel Brüning's avatar
Daniel Brüning committed
153
                let new_session_key: String = thread_rng().sample_iter(&Alphanumeric).take(28).collect();
154
                self.session().set(SessionToken { token: new_session_key }).unwrap();
155
156
157
158
159
160
161
                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("/"))))) })
            }
        }
    }

    fn expect_session_token(self: &mut Self) -> IronResult<String> {
162
        match self.session().get::<SessionToken>().unwrap() {
163
            Some(SessionToken { token: session }) => Ok(session),
Robert Czechowski's avatar
Robert Czechowski committed
164
165
166
            _ => Err(IronError { error: Box::new(SessionError { message:
                                                                    "No valid session found, access denied".to_string() }),
                                 response: Response::with(status::Forbidden) }),
167
168
169
170
171
172
173
174
        }
    }
}

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>;
175
    fn expect_str(self: &mut Self, key: &str) -> IronResult<String>;
176
177
178
179
180
181
182
183
184
185
}

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()?)
    }
186

187
188
189
    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
190
191
192
            _ => Err(IronError { error: Box::new(SessionError { message:
                                                                    "No valid routing parameter".to_string() }),
                                 response: Response::with(status::Forbidden) }),
193
194
        }
    }
195

196
197
198
199
200
201
202
203
    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) }),
        }
    }
204
205
}

206
struct AugMedalError<'c, 'a: 'c, 'b: 'c + 'a>(core::MedalError, &'c mut Request<'a, 'b>);
207
208
209
210

impl<'c, 'a, 'b> From<AugMedalError<'c, 'a, 'b>> for IronError {
    fn from(AugMedalError(me, req): AugMedalError<'c, 'a, 'b>) -> Self {
        match me {
211
            core::MedalError::NotLoggedIn => {
Robert Czechowski's avatar
Robert Czechowski committed
212
213
214
215
216
                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("/"))))) }
            }
217
218
219
220
221
222
223
            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
224
225
226
                IronError { error: Box::new(SessionError { message: "Session timed out".to_string() }),
                            response: Response::with(status::Forbidden) }
            }
227
            core::MedalError::DatabaseError => {
Robert Czechowski's avatar
Robert Czechowski committed
228
                IronError { error: Box::new(SessionError { message: "Database Error".to_string() }),
229
                            response: Response::with(status::InternalServerError) }
Robert Czechowski's avatar
Robert Czechowski committed
230
            }
231
            core::MedalError::PasswordHashingError => {
Robert Czechowski's avatar
Robert Czechowski committed
232
                IronError { error: Box::new(SessionError { message: "Error hashing the passwords".to_string() }),
233
                            response: Response::with(status::InternalServerError) }
Robert Czechowski's avatar
Robert Czechowski committed
234
            }
235
            core::MedalError::UnmatchedPasswords => {
Robert Czechowski's avatar
Robert Czechowski committed
236
237
238
239
                IronError { error: Box::new(SessionError { message:
                                                               "The two passwords did not match.".to_string() }),
                            response: Response::with(status::Forbidden) }
            }
240
        }
241
242
243
    }
}

244
245
246
trait RequestAugmentMedalError<'c, 'a: 'c, 'b: 'c + 'a, R> {
    fn aug(self, req: &'c mut Request<'a, 'b>) -> Result<R, AugMedalError<'c, 'a, 'b>>;
}
247
impl<'c, 'a: 'c, 'b: 'c + 'a, T> RequestAugmentMedalError<'c, 'a, 'b, T> for Result<T, core::MedalError> {
248
    fn aug(self, req: &'c mut Request<'a, 'b>) -> Result<T, AugMedalError<'c, 'a, 'b>> {
Robert Czechowski's avatar
Robert Czechowski committed
249
        self.map_err(move |me| AugMedalError(me, req))
250
251
252
    }
}

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

258
    let (self_url, oauth_providers) = {
259
260
        let mutex = req.get::<Write<SharedConfiguration>>().unwrap();
        let config = mutex.lock().unwrap_or_else(|e| e.into_inner());
261
        (config.self_url.clone(), config.oauth_providers.clone())
262
263
    };

Robert Czechowski's avatar
Robert Czechowski committed
264
265
    let (template, data) = {
        // hier ggf. Daten aus dem Request holen
266
        let mutex = req.get::<Write<SharedDatabaseConnection<C>>>().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
267
268
        let conn = mutex.lock().unwrap_or_else(|e| e.into_inner());

269
        // Antwort erstellen und zurücksenden
270
        core::index(&*conn, session_token, (self_url, oauth_providers))
Robert Czechowski's avatar
Robert Czechowski committed
271
    };
Robert Czechowski's avatar
Robert Czechowski committed
272
273
    // Daten verarbeiten

274
    // Antwort erstellen und zurücksenden
Robert Czechowski's avatar
Robert Czechowski committed
275
276
277
278
279
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

280
281
fn debug<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
282
283
284
    let session_token = req.get_session_token();

    let (template, data) = {
285
        let mutex = req.get::<Write<SharedDatabaseConnection<C>>>().unwrap();
286
287
        let conn = mutex.lock().unwrap_or_else(|e| e.into_inner());

288
        core::debug(&*conn, session_token)
289
290
291
292
293
294
295
    };

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

296
297
fn debug_new_token<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
298
299
    let session_token = req.get_session_token();

300
301
    #[cfg(feature = "debug")]
    println!("Logging out session {:?}", session_token);
302

303
    with_conn![core::logout, C, req, session_token];
304
305
306
307

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

308
309
fn debug_logout<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
310
311
    let session_token = req.get_session_token();

312
313
    #[cfg(feature = "debug")]
    println!("Logging out session {:?}", session_token);
314

315
    with_conn![core::logout, C, req, session_token];
316
317
318
319

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

320
321
fn debug_create_session<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
322
323
    let session_token = req.get_session_token();

324
    with_conn![core::debug_create_session, C, req, session_token];
325
326
327
328

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

329
330
fn contests<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
331
    let (template, data) = with_conn![core::show_contests, C, req,];
332

Robert Czechowski's avatar
Robert Czechowski committed
333
334
335
336
337
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

338
339
fn contest<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
340
    let contest_id = req.expect_int::<i32>("contestid")?;
341
    let session_token = req.require_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
342

343
    let (template, data) = with_conn![core::show_contest, C, req, contest_id, &session_token].aug(req)?;
Robert Czechowski's avatar
Robert Czechowski committed
344
345
346
347
348
349

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

350
351
fn contestresults<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
352
    let contest_id = req.expect_int::<i32>("contestid")?;
353
354
    let session_token = req.require_session_token()?;

355
    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
356

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

362
363
fn contest_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
364
    let contest_id = req.expect_int::<i32>("contestid")?;
365
    let session_token = req.expect_session_token()?;
366

Robert Czechowski's avatar
Robert Czechowski committed
367
368
    let csrf_token = {
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
369
        iexpect!(formdata.get("csrf_token"))[0].to_owned()
Robert Czechowski's avatar
Robert Czechowski committed
370
371
    };

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

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

378
379
fn login<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
380
381
    // TODO: Use OAuth providers
    let (self_url, _oauth_providers) = {
382
383
        let mutex = req.get::<Write<SharedConfiguration>>().unwrap();
        let config = mutex.lock().unwrap_or_else(|e| e.into_inner());
Daniel Brüning's avatar
Daniel Brüning committed
384

385
        (config.self_url.clone(), config.oauth_providers.clone())
386
387
388
389
    };

    let mut data = json_val::Map::new();
    data.insert("self_url".to_string(), to_json(&self_url));
390
    // TODO: Generate list of links as in greet_personal
391
    //    data.insert("oauth_url".to_string(), to_json(&oauth_url));
392

Robert Czechowski's avatar
Robert Czechowski committed
393
    let mut resp = Response::new();
394
    resp.set_mut(Template::new("login", data)).set_mut(status::Ok);
Robert Czechowski's avatar
Robert Czechowski committed
395
396
397
    Ok(resp)
}

398
399
fn login_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
Robert Czechowski's avatar
Robert Czechowski committed
400
401
    let logindata = {
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
Robert Czechowski's avatar
Robert Czechowski committed
402
        (iexpect!(formdata.get("username"))[0].to_owned(), iexpect!(formdata.get("password"))[0].to_owned())
Robert Czechowski's avatar
Robert Czechowski committed
403
404
    };

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

407
    let loginresult = with_conn![core::login, C, req, logindata];
Robert Czechowski's avatar
Robert Czechowski committed
408
409
410
411
412
413

    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
414
        }
Robert Czechowski's avatar
Robert Czechowski committed
415
416
417
418
419
420
421
422
423
        // Login failed
        Err((template, data)) => {
            let mut resp = Response::new();
            resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
            Ok(resp)
        }
    }
}

424
425
fn login_code_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
Robert Czechowski's avatar
Robert Czechowski committed
426
427
428
429
430
    let code = {
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
        iexpect!(formdata.get("code"))[0].to_owned()
    };

431
432
    // TODO: Submit current session to login

433
    let loginresult = with_conn![core::login_with_code, C, req, &code];
Robert Czechowski's avatar
Robert Czechowski committed
434
435
436
437
438
439

    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
440
        }
Robert Czechowski's avatar
Robert Czechowski committed
441
442
443
        Ok(Err(sessionkey)) => {
            req.session().set(SessionToken { token: sessionkey }).unwrap();
            Ok(Response::with((status::Found, Redirect(url_for!(req, "profile")))))
Robert Czechowski's avatar
Robert Czechowski committed
444
        }
Robert Czechowski's avatar
Robert Czechowski committed
445
446
447
448
449
450
451
        // Login failed
        Err((template, data)) => {
            let mut resp = Response::new();
            resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
            Ok(resp)
        }
    }
452
}
Robert Czechowski's avatar
Robert Czechowski committed
453

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

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

460
    with_conn![core::logout, C, req, session_token];
Robert Czechowski's avatar
Robert Czechowski committed
461
462
463
464

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

465
466
fn submission<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
467
    let task_id = req.expect_int::<i32>("taskid")?;
468
    let session_token = req.expect_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
469
470
471
    let subtask: Option<String> = (|| -> Option<String> {
        req.get_ref::<UrlEncodedQuery>().ok()?.get("subtask")?.get(0).map(|x| x.to_owned())
    })();
472

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

    match result {
Robert Czechowski's avatar
Robert Czechowski committed
476
477
        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
478
479
480
    }
}

481
482
fn submission_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
483
    let task_id = req.expect_int::<i32>("taskid")?;
484
    let session_token = req.expect_session_token()?;
485
    let (csrf_token, data, grade, subtask) = {
Robert Czechowski's avatar
Robert Czechowski committed
486
        let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
487
488
489
490
        (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
491
    };
492
493
494

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

496
    let result =
497
        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
498

499
    Ok(Response::with((status::Ok, mime!(Application / Json), result)))
Robert Czechowski's avatar
Robert Czechowski committed
500
501
}

502
503
fn task<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
504
    let task_id = req.expect_int::<i32>("taskid")?;
505
    let session_token = req.require_session_token()?;
506

507
    let (template, data) = with_conn![core::show_task, C, req, task_id, &session_token].aug(req)?;
Robert Czechowski's avatar
Robert Czechowski committed
508
509
510
511
512
513

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

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

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

Robert Czechowski's avatar
Robert Czechowski committed
520
521
522
523
524
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

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

530
    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
531

532
533
534
    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
535
536
}

537
538
fn group_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
539
    let group_id = req.expect_int::<i32>("groupid")?;
540
    let session_token = req.expect_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
541

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

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

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

552
    let (csrf_token, name, tag) = {
Robert Czechowski's avatar
Robert Czechowski committed
553
        let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
554
555
556
        (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
557
558
    };

559
    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
560

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

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

569
    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
570

Robert Czechowski's avatar
Robert Czechowski committed
571
572
573
574
575
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

576
577
fn profile_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
578
    let session_token = req.expect_session_token()?;
579
    let (csrf_token, firstname, lastname, street, zip, city, pwd, pwd_repeat, grade) = {
Robert Czechowski's avatar
Robert Czechowski committed
580
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
581
        (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
Robert Czechowski's avatar
Robert Czechowski committed
582
583
         iexpect!(formdata.get("firstname"))[0].to_owned(),
         iexpect!(formdata.get("lastname"))[0].to_owned(),
584
585
586
587
588
         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()),
589
         iexpect!(formdata.get("grade"))[0].parse::<i32>().unwrap_or(0))
Robert Czechowski's avatar
Robert Czechowski committed
590
    };
Daniel Brüning's avatar
Daniel Brüning committed
591

592
    let profilechangeresult = with_conn![core::edit_profile,
593
                                         C,
594
                                         req,
Daniel Brüning's avatar
Daniel Brüning committed
595
                                         &session_token,
596
                                         None,
Daniel Brüning's avatar
Daniel Brüning committed
597
                                         &csrf_token,
598
599
600
601
602
603
604
605
606
607
608
609
610
                                         firstname,
                                         lastname,
                                         street,
                                         zip,
                                         city,
                                         pwd,
                                         pwd_repeat,
                                         grade].aug(req)?;

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

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

Robert Czechowski's avatar
Robert Czechowski committed
619
    let (template, data) =
620
        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
621

Robert Czechowski's avatar
Robert Czechowski committed
622
623
624
625
626
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

627
628
fn user_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
629
    let user_id = req.expect_int::<i32>("userid")?;
630
    let session_token = req.expect_session_token()?;
631
    let (csrf_token, firstname, lastname, street, zip, city, pwd, pwd_repeat, grade) = {
632
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
633
        (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
Robert Czechowski's avatar
Robert Czechowski committed
634
635
         iexpect!(formdata.get("firstname"))[0].to_owned(),
         iexpect!(formdata.get("lastname"))[0].to_owned(),
636
637
638
639
640
         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()),
641
         iexpect!(formdata.get("grade"))[0].parse::<i32>().unwrap_or(0))
642
    };
Daniel Brüning's avatar
Daniel Brüning committed
643

644
    let profilechangeresult = with_conn![core::edit_profile,
645
                                         C,
646
                                         req,
Daniel Brüning's avatar
Daniel Brüning committed
647
                                         &session_token,
648
                                         Some(user_id),
Daniel Brüning's avatar
Daniel Brüning committed
649
                                         &csrf_token,
650
651
652
653
654
655
656
657
658
659
660
661
662
663
                                         firstname,
                                         lastname,
                                         street,
                                         zip,
                                         city,
                                         pwd,
                                         pwd_repeat,
                                         grade].aug(req)?;

    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))))))
664
665
}

666
667
#[derive(Deserialize, Debug)]
struct OAuthAccess {
Robert Czechowski's avatar
Robert Czechowski committed
668
669
    access_token: String,
    token_type: String,
670
    refresh_token: String,
671
672
    expires: Option<i32>,    // documented as 'expires_in'
    expires_in: Option<i32>, // sent as 'expires'
673
674
675
}

#[derive(Deserialize, Debug)]
676
#[allow(non_snake_case)]
677
pub struct OAuthUserData {
Robert Czechowski's avatar
Robert Czechowski committed
678
679
680
681
682
683
    userID: Option<String>, // documented as 'userId'
    userId: Option<String>, // sent as 'userID'
    userType: String,
    gender: String,
    firstName: String,
    lastName: String,
684
    dateOfBirth: Option<String>,
Robert Czechowski's avatar
Robert Czechowski committed
685
    eMail: Option<String>,
686
    userId_int: Option<String>,
687
688
}

689
690
fn oauth<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
691
692
    use params::{Params, Value};

693
    let oauth_id = req.expect_str("oauthid")?;
694

695
696
    let (client_id, client_secret, access_token_url, user_data_url) = {
        let mutex = req.get::<Write<SharedConfiguration>>().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
697
        let config = mutex.lock().unwrap_or_else(|e| e.into_inner());
698
699

        let mut result: Option<(String, String, String, String)> = None;
700

701
702
703
        if let Some(ref oauth_providers) = config.oauth_providers {
            for oauth_provider in oauth_providers {
                if oauth_provider.provider_id == oauth_id {
704
705
706
707
                    result = Some((oauth_provider.client_id.clone(),
                                   oauth_provider.client_secret.clone(),
                                   oauth_provider.access_token_url.clone(),
                                   oauth_provider.user_data_url.clone()));
708
709
710
711
712
713
714
715
716
                    break;
                }
            }

            if let Some(result) = result {
                result
            } else {
                return Ok(Response::with(iron::status::NotFound));
            }
717
        } else {
Robert Czechowski's avatar
Robert Czechowski committed
718
            return Ok(Response::with(iron::status::NotFound));
719
720
        }
    };
721

722
    let (_state, _scope, code): (String, String, String) = {
723
724
        let map = req.get_ref::<Params>().unwrap();

Robert Czechowski's avatar
Robert Czechowski committed
725
726
727
728
        match (map.find(&["state"]), map.find(&["scope"]), map.find(&["code"])) {
            (Some(&Value::String(ref state)), Some(&Value::String(ref scope)), Some(&Value::String(ref code)))
                if state == "42" =>
            {
729
                (state.clone(), scope.clone(), code.clone())
Robert Czechowski's avatar
Robert Czechowski committed
730
            }
731
732
733
734
            _ => return Ok(Response::with(iron::status::NotFound)),
        }
    };

735
    let client = reqwest::Client::new();
736
    let params = [("code", code), ("grant_type", "authorization_code".to_string())];
737
    let res = client.post(&access_token_url).basic_auth(client_id, Some(client_secret)).form(&params).send();
738
739
    let access: OAuthAccess = res.expect("network error").json().expect("malformed json");

740
    let res = client.post(&user_data_url).bearer_auth(access.access_token).form(&params).send();
741
742
    let mut user_data: OAuthUserData = res.expect("network error").json().expect("malformed json");

743
744
    if let Some(id) = user_data.userID {
        user_data.userId_int = Some(id);
745
    }
746
747
    if let Some(id) = user_data.userId {
        user_data.userId_int = Some(id);
748
749
    }

750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
    use core::{UserGender, UserType};

    let user_data = core::ForeignUserData { foreign_id: user_data.userId_int.unwrap(), // todo: don't unwrap here
                                            foreign_type: match user_data.userType.as_ref() {
                                                "a" | "A" => UserType::Admin,
                                                "t" | "T" => UserType::Teacher,
                                                "s" | "S" | _ => UserType::User,
                                            },
                                            gender: match user_data.gender.as_ref() {
                                                "m" | "M" => UserGender::Male,
                                                "f" | "F" | "w" | "W" => UserGender::Female,
                                                "?" | _ => UserGender::Unknown,
                                            },
                                            firstname: user_data.firstName,
                                            lastname: user_data.lastName };
Robert Czechowski's avatar
Robert Czechowski committed
765
766

    let oauthloginresult = {
767
        // hier ggf. Daten aus dem Request holen
768
        let mutex = req.get::<Write<SharedDatabaseConnection<C>>>().unwrap();
769
        let conn = mutex.lock().unwrap_or_else(|e| e.into_inner());
770
771

        // Antwort erstellen und zurücksenden
772
        core::login_oauth(&*conn, user_data)
773
        /*let mut data = json_val::Map::new();
Robert Czechowski's avatar
Robert Czechowski committed
774
775
        data.insert("reason".to_string(), to_json(&"Not implemented".to_string()));
        ("profile", data)*/
776
    };
777

778
779
780
781
782
    match oauthloginresult {
        // 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
783
        }
784
785
786
787
788
789
790
        // Login failed
        Err((template, data)) => {
            let mut resp = Response::new();
            resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
            Ok(resp)
        }
    }
791
792
}

Robert Czechowski's avatar
Robert Czechowski committed
793
794
// Share Database connection between workers
#[derive(Copy, Clone)]
795
796
797
798
799
800
801
802
pub struct SharedDatabaseConnection<C>
    where C: MedalConnection
{
    phantom: std::marker::PhantomData<C>,
}
impl<C> Key for SharedDatabaseConnection<C> where C: MedalConnection + 'static
{
    type Value = C;
Robert Czechowski's avatar
Robert Czechowski committed
803
}
Robert Czechowski's avatar
Robert Czechowski committed
804

805
806
807
// Share Configuration between workers
#[derive(Copy, Clone)]
pub struct SharedConfiguration;
Robert Czechowski's avatar
Robert Czechowski committed
808
impl Key for SharedConfiguration {
809
    type Value = Config;
Robert Czechowski's avatar
Robert Czechowski committed
810
}
811

Robert Czechowski's avatar
Robert Czechowski committed
812
#[cfg(feature = "watch")]
813
pub fn get_handlebars_engine(template_name: &str) -> impl AfterMiddleware {
814
    // HandlebarsEngine will look up all files with "./examples/templates/**/*.hbs"
Robert Czechowski's avatar
Robert Czechowski committed
815
    let mut hbse = HandlebarsEngine::new();
816
    hbse.add(Box::new(DirectorySource::new(&format!("./templates/{}/", template_name) as &str, ".hbs")));
Robert Czechowski's avatar
Robert Czechowski committed
817
818
819
820
821
822
823

    // load templates from all registered sources
    if let Err(r) = hbse.reload() {
        panic!("{}", r);
    }

    use handlebars_iron::Watchable;
Robert Czechowski's avatar
Robert Czechowski committed
824
    use std::sync::Arc;
825

Robert Czechowski's avatar
Robert Czechowski committed
826
827
828
829
830
831
    let hbse_ref = Arc::new(hbse);
    hbse_ref.watch("./templates/");
    hbse_ref
}

#[cfg(not(feature = "watch"))]
832
833
pub fn get_handlebars_engine(template_name: &str) -> impl AfterMiddleware {
    // HandlebarsEngine will look up all files with "./templates/<template>/**/*.hbs"
Robert Czechowski's avatar
Robert Czechowski committed
834
    let mut hbse = HandlebarsEngine::new();
835
    hbse.add(Box::new(DirectorySource::new(&format!("./templates/{}/", template_name) as &str, ".hbs")));
Robert Czechowski's avatar
Robert Czechowski committed
836
837
838
839
840

    // load templates from all registered sources
    if let Err(r) = hbse.reload() {
        panic!("{}", r);
    }
Robert Czechowski's avatar
Robert Czechowski committed
841
842

    hbse
Robert Czechowski's avatar
Robert Czechowski committed
843
844
}

845
fn cookie_warning(req: &mut Request) -> IronResult<Response> {
846
    match req.get_session_token() {
Daniel Brüning's avatar
Daniel Brüning committed
847
        Some(_session_token) => {
848
849
            // TODO: Set session!
            // TODO:
Robert Czechowski's avatar
Robert Czechowski committed
850
851
            Ok(Response::with((status::Found, RedirectRaw(format!("/{}", req.url.query().unwrap_or(""))))))
        }
852
853
854
855
856
857
858
        None => {
            let mut resp = Response::new();
            resp.set_mut(Template::new("cookie", json_val::Map::new())).set_mut(status::Ok);
            Ok(resp)
        }
    }
}
Robert Czechowski's avatar
Robert Czechowski committed
859

860
pub fn start_server<C>(conn: C, config: Config) -> iron::error::HttpResult<iron::Listening>
861
    where C: MedalConnection + std::marker::Send + 'static {
Robert Czechowski's avatar
Robert Czechowski committed
862
    let router = router!(
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
        greet: get "/" => greet_personal::<C>,
        contests: get "/contest/" => contests::<C>,
        contest: get "/contest/:contestid" => contest::<C>,
        contestresults: get "/contest/:contestid/result/" => contestresults::<C>,
        contest_post: post "/contest/:contestid" => contest_post::<C>,
        login: get "/login" => login::<C>,
        login_post: post "/login" => login_post::<C>,
        login_code_post: post "/clogin" => login_code_post::<C>,
        logout: get "/logout" => logout::<C>,
        subm: get "/submission/:taskid" => submission::<C>,
        subm_post: post "/submission/:taskid" => submission_post::<C>,
        subm_load: get "/load/:taskid" => submission::<C>,
        subm_save: post "/save/:taskid" => submission_post::<C>,
        groups: get "/group/" => groups::<C>,
        groups: post "/group/" => new_group::<C>,
        group: get "/group/:groupid" => group::<C>,
        group_post: post "/group" => group_post::<C>,
        profile: get "/profile" => profile::<C>,
        profile_post: post "/profile" => profile_post::<C>,
        user: get "/user/:userid" => user::<C>,
        user_post: post "/user/:userid" => user_post::<C>,
        task: get "/task/:taskid" => task::<C>,
        oauth: get "/oauth/:oauthid" => oauth::<C>,
886
        check_cookie: get "/cookie" => cookie_warning,
887
888
889
890
        debug: get "/debug" => debug::<C>,
        debug_reset: get "/debug/reset" => debug_new_token::<C>,
        debug_logout: get "/debug/logout" => debug_logout::<C>,
        debug_create: get "/debug/create" => debug_create_session::<C>,
Robert Czechowski's avatar
Robert Czechowski committed
891
892
    );

893
    // TODO: how important is this? Should this be in the config? Or should this be autogenerated and saved to disk?
Robert Czechowski's avatar
Robert Czechowski committed
894
895
896
897
898
899
    let my_secret = b"verysecret".to_vec();

    let mut mount = Mount::new();

    // Serve the shared JS/CSS at /
    mount.mount("/static/", Static::new(Path::new("static")));
Robert Czechowski's avatar
Robert Czechowski committed
900
    mount.mount("/tasks/", Static::new(Path::new(TASK_DIR)));
Robert Czechowski's avatar
Robert Czechowski committed
901
902
903
    mount.mount("/", router);

    let mut ch = Chain::new(mount);
904

905
906
907
    #[cfg(feature = "debug")]
    ch.link_before(RequestLogger::new());