webfw_iron.rs 31.5 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;
Robert Czechowski's avatar
Robert Czechowski committed
10
use iron::{status, AfterMiddleware};
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;
Robert Czechowski's avatar
Robert Czechowski committed
19
use rusqlite::Connection;
20
use urlencoded::{UrlEncodedBody, UrlEncodedQuery};
Robert Czechowski's avatar
Robert Czechowski committed
21
22

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

25
use iron;
Robert Czechowski's avatar
Robert Czechowski committed
26
use iron_sessionstorage;
27
28
29
use reqwest;
use rusqlite;

Robert Czechowski's avatar
Robert Czechowski committed
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
pub use serde_json::value as json_val;

use iron::typemap::Key;

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

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

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

86
87
88
89
90
91
92
93
pub struct CookieDistributor {}

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

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

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

106
107
#[derive(Debug)]
struct SessionError {
Robert Czechowski's avatar
Robert Czechowski committed
108
    message: String,
109
110
}
impl ::std::error::Error for SessionError {
Robert Czechowski's avatar
Robert Czechowski committed
111
    fn description(&self) -> &str { &self.message }
112
113
114
}

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

118
119
120
121
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>;
122
123
}

124
125
impl<'a, 'b> RequestSession for Request<'a, 'b> {
    fn get_session_token(self: &mut Self) -> Option<String> {
126
        let session_token = self.session().get::<SessionToken>().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
127
        (|st: Option<SessionToken>| -> Option<String> { Some(st?.token) })(session_token)
128
129
130
    }

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

Daniel Brüning's avatar
Daniel Brüning committed
136
                let new_session_key: String = thread_rng().sample_iter(&Alphanumeric).take(28).collect();
137
                self.session().set(SessionToken { token: new_session_key }).unwrap();
138
139
140
141
142
143
144
                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> {
145
        match self.session().get::<SessionToken>().unwrap() {
146
            Some(SessionToken { token: session }) => Ok(session),
Robert Czechowski's avatar
Robert Czechowski committed
147
148
149
            _ => Err(IronError { error: Box::new(SessionError { message:
                                                                    "No valid session found, access denied".to_string() }),
                                 response: Response::with(status::Forbidden) }),
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
        }
    }
}

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

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

169
170
171
    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
172
173
174
            _ => Err(IronError { error: Box::new(SessionError { message:
                                                                    "No valid routing parameter".to_string() }),
                                 response: Response::with(status::Forbidden) }),
175
176
177
178
        }
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
179
use functions;
Robert Czechowski's avatar
Robert Czechowski committed
180

181
182
183
184
185
struct AugMedalError<'c, 'a: 'c, 'b: 'c + 'a>(functions::MedalError, &'c mut Request<'a, 'b>);

impl<'c, 'a, 'b> From<AugMedalError<'c, 'a, 'b>> for IronError {
    fn from(AugMedalError(me, req): AugMedalError<'c, 'a, 'b>) -> Self {
        match me {
Robert Czechowski's avatar
Robert Czechowski committed
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
            functions::MedalError::NotLoggedIn => {
                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("/"))))) }
            }
            functions::MedalError::AccessDenied => {
                IronError { error: Box::new(SessionError { message: "Access denied".to_string() }),
                            response: Response::with(status::Forbidden) }
            }
            functions::MedalError::CsrfCheckFailed => {
                IronError { error: Box::new(SessionError { message: "CSRF Error".to_string() }),
                            response: Response::with(status::Forbidden) }
            }
            functions::MedalError::SessionTimeout => {
                IronError { error: Box::new(SessionError { message: "Session timed out".to_string() }),
                            response: Response::with(status::Forbidden) }
            }
            functions::MedalError::DatabaseError => {
                IronError { error: Box::new(SessionError { message: "Database Error".to_string() }),
                            response: Response::with(status::Forbidden) }
            }
            functions::MedalError::PasswordHashingError => {
                IronError { error: Box::new(SessionError { message: "Error hashing the passwords".to_string() }),
                            response: Response::with(status::Forbidden) }
            }
            functions::MedalError::UnmatchedPasswords => {
                IronError { error: Box::new(SessionError { message:
                                                               "The two passwords did not match.".to_string() }),
                            response: Response::with(status::Forbidden) }
            }
217
        }
218
219
220
    }
}

221
222
223
trait RequestAugmentMedalError<'c, 'a: 'c, 'b: 'c + 'a, R> {
    fn aug(self, req: &'c mut Request<'a, 'b>) -> Result<R, AugMedalError<'c, 'a, 'b>>;
}
Robert Czechowski's avatar
Robert Czechowski committed
224
impl<'c, 'a: 'c, 'b: 'c + 'a, T> RequestAugmentMedalError<'c, 'a, 'b, T> for Result<T, functions::MedalError> {
225
    fn aug(self, req: &'c mut Request<'a, 'b>) -> Result<T, AugMedalError<'c, 'a, 'b>> {
Robert Czechowski's avatar
Robert Czechowski committed
226
        self.map_err(move |me| AugMedalError(me, req))
227
228
229
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
230
fn greet_personal(req: &mut Request) -> IronResult<Response> {
231
    let session_token = req.get_session_token();
Robert Czechowski's avatar
Robert Czechowski committed
232
233
    // hier ggf. Daten aus dem Request holen

234
235
236
    let (self_url, oauth_url) = {
        let mutex = req.get::<Write<SharedConfiguration>>().unwrap();
        let config = mutex.lock().unwrap_or_else(|e| e.into_inner());
237
        (config.self_url.clone(), config.oauth_url.clone())
238
239
    };

Robert Czechowski's avatar
Robert Czechowski committed
240
241
242
243
244
    let (template, data) = {
        // hier ggf. Daten aus dem Request holen
        let mutex = req.get::<Write<SharedDatabaseConnection>>().unwrap();
        let conn = mutex.lock().unwrap_or_else(|e| e.into_inner());

245
        // Antwort erstellen und zurücksenden
246
        functions::index(&*conn, session_token, (self_url, oauth_url))
Robert Czechowski's avatar
Robert Czechowski committed
247
    };
Robert Czechowski's avatar
Robert Czechowski committed
248
249
    // Daten verarbeiten

250
    // Antwort erstellen und zurücksenden
Robert Czechowski's avatar
Robert Czechowski committed
251
252
253
254
255
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
fn debug(req: &mut Request) -> IronResult<Response> {
    let session_token = req.get_session_token();

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

        functions::debug(&*conn, session_token)
    };

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

fn debug_new_token(req: &mut Request) -> IronResult<Response> {
    let session_token = req.get_session_token();

    println!("Loggin out session {:?}", session_token);

    with_conn![functions::logout, req, session_token];

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

fn debug_logout(req: &mut Request) -> IronResult<Response> {
    let session_token = req.get_session_token();

    println!("Loggin out session {:?}", session_token);

    with_conn![functions::logout, req, session_token];

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

fn debug_create_session(req: &mut Request) -> IronResult<Response> {
    let session_token = req.get_session_token();

    with_conn![functions::debug_create_session, req, session_token];

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

Robert Czechowski's avatar
Robert Czechowski committed
299
fn contests(req: &mut Request) -> IronResult<Response> {
Robert Czechowski's avatar
Robert Czechowski committed
300
    let (template, data) = with_conn![functions::show_contests, req,];
301

Robert Czechowski's avatar
Robert Czechowski committed
302
303
304
305
306
307
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

fn contest(req: &mut Request) -> IronResult<Response> {
Robert Czechowski's avatar
Robert Czechowski committed
308
    let contest_id = req.expect_int::<u32>("contestid")?;
309
    let session_token = req.require_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
310

Robert Czechowski's avatar
Robert Czechowski committed
311
    let (template, data) = with_conn![functions::show_contest, req, contest_id, session_token].aug(req)?;
Robert Czechowski's avatar
Robert Czechowski committed
312
313
314
315
316
317

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

318
fn contestresults(req: &mut Request) -> IronResult<Response> {
Robert Czechowski's avatar
Robert Czechowski committed
319
    let contest_id = req.expect_int::<u32>("contestid")?;
320
321
    let session_token = req.require_session_token()?;

Robert Czechowski's avatar
Robert Czechowski committed
322
    let (template, data) = with_conn![functions::show_contest_results, req, contest_id, session_token].aug(req)?;
Daniel Brüning's avatar
Daniel Brüning committed
323

324
325
326
327
328
    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
329
fn contest_post(req: &mut Request) -> IronResult<Response> {
Robert Czechowski's avatar
Robert Czechowski committed
330
    let contest_id = req.expect_int::<u32>("contestid")?;
331
    let session_token = req.expect_session_token()?;
332

Robert Czechowski's avatar
Robert Czechowski committed
333
334
335
336
337
    let csrf_token = {
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
        iexpect!(formdata.get("csrftoken"))[0].to_owned()
    };

338
    // TODO: Was mit dem Result?
339
    with_conn![functions::start_contest, req, contest_id, session_token, csrf_token].aug(req)?;
Robert Czechowski's avatar
Robert Czechowski committed
340

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

344
fn login(req: &mut Request) -> IronResult<Response> {
Daniel Brüning's avatar
Daniel Brüning committed
345
    let (self_url, oauth_url) = {
346
347
        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
348

349
        (config.self_url.clone(), config.oauth_url.clone())
350
351
352
353
354
    };

    let mut data = json_val::Map::new();
    data.insert("self_url".to_string(), to_json(&self_url));
    data.insert("oauth_url".to_string(), to_json(&oauth_url));
355

Robert Czechowski's avatar
Robert Czechowski committed
356
    let mut resp = Response::new();
357
    resp.set_mut(Template::new("login", data)).set_mut(status::Ok);
Robert Czechowski's avatar
Robert Czechowski committed
358
359
360
361
362
363
    Ok(resp)
}

fn login_post(req: &mut Request) -> IronResult<Response> {
    let logindata = {
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
Robert Czechowski's avatar
Robert Czechowski committed
364
        (iexpect!(formdata.get("username"))[0].to_owned(), iexpect!(formdata.get("password"))[0].to_owned())
Robert Czechowski's avatar
Robert Czechowski committed
365
366
    };

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

369
    let loginresult = with_conn![functions::login, req, logindata];
Robert Czechowski's avatar
Robert Czechowski committed
370
371
372
373
374
375

    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
376
        }
Robert Czechowski's avatar
Robert Czechowski committed
377
378
379
380
381
382
383
384
385
        // Login failed
        Err((template, data)) => {
            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
386
387
388
389
390
391
fn login_code_post(req: &mut Request) -> IronResult<Response> {
    let code = {
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
        iexpect!(formdata.get("code"))[0].to_owned()
    };

392
393
    // TODO: Submit current session to login

394
    let loginresult = with_conn![functions::login_with_code, req, code];
Robert Czechowski's avatar
Robert Czechowski committed
395
396
397
398
399
400
401
    println!("aa");

    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
402
        }
Robert Czechowski's avatar
Robert Czechowski committed
403
404
405
        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
406
        }
Robert Czechowski's avatar
Robert Czechowski committed
407
408
409
410
411
412
413
414
        // Login failed
        Err((template, data)) => {
            println!("bb");
            let mut resp = Response::new();
            resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
            Ok(resp)
        }
    }
415
}
Robert Czechowski's avatar
Robert Czechowski committed
416
417

fn logout(req: &mut Request) -> IronResult<Response> {
418
    let session_token = req.get_session_token();
Robert Czechowski's avatar
Robert Czechowski committed
419

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

422
    with_conn![functions::logout, req, session_token];
Robert Czechowski's avatar
Robert Czechowski committed
423
424
425
426

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

Robert Czechowski's avatar
Robert Czechowski committed
427
fn submission(req: &mut Request) -> IronResult<Response> {
Robert Czechowski's avatar
Robert Czechowski committed
428
    let task_id = req.expect_int::<u32>("taskid")?;
429
    let session_token = req.expect_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
430
431
432
    let subtask: Option<String> = (|| -> Option<String> {
        req.get_ref::<UrlEncodedQuery>().ok()?.get("subtask")?.get(0).map(|x| x.to_owned())
    })();
433

Robert Czechowski's avatar
Robert Czechowski committed
434
    println!("{}", task_id);
Robert Czechowski's avatar
Robert Czechowski committed
435

436
    let result = with_conn![functions::load_submission, req, task_id, session_token, subtask];
Robert Czechowski's avatar
Robert Czechowski committed
437
438

    match result {
Robert Czechowski's avatar
Robert Czechowski committed
439
440
        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
441
442
443
444
    }
}

fn submission_post(req: &mut Request) -> IronResult<Response> {
Robert Czechowski's avatar
Robert Czechowski committed
445
    let task_id = req.expect_int::<u32>("taskid")?;
446
    let session_token = req.expect_session_token()?;
447
    let (csrf_token, data, grade, subtask) = {
Robert Czechowski's avatar
Robert Czechowski committed
448
449
        let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
        (iexpect!(formdata.get("csrf"),(status::BadRequest, mime!(Text/Html), format!("400 Bad Request")))[0].to_owned(),
450
         iexpect!(formdata.get("data"),(status::BadRequest, mime!(Text/Html), format!("400 Bad Request")))[0].to_owned(),
451
         iexpect!(formdata.get("grade").unwrap_or(&vec!["0".to_owned()])[0].parse::<u8>().ok(),(status::BadRequest, mime!(Text/Html), format!("400 Bad Request"))),
452
         formdata.get("subtask").map(|x| x[0].to_owned()),
453
        )
Robert Czechowski's avatar
Robert Czechowski committed
454
455
456
457
    };
    println!("{}", data);
    println!("{}", task_id);
    println!("{}", grade);
Daniel Brüning's avatar
Daniel Brüning committed
458

459
    let result = with_conn![functions::save_submission, req, task_id, session_token, csrf_token, data, grade, subtask];
Daniel Brüning's avatar
Daniel Brüning committed
460

Robert Czechowski's avatar
Robert Czechowski committed
461
    match result {
Robert Czechowski's avatar
Robert Czechowski committed
462
463
        Ok(_) => Ok(Response::with((status::Ok, mime!(Application / Json), format!("{{}}")))),
        Err(_) => Ok(Response::with((status::BadRequest, mime!(Application / Json), format!("{{}}")))),
464
    }
Robert Czechowski's avatar
Robert Czechowski committed
465
466
467
}

fn task(req: &mut Request) -> IronResult<Response> {
Robert Czechowski's avatar
Robert Czechowski committed
468
    let task_id = req.expect_int::<u32>("taskid")?;
469
    let session_token = req.require_session_token()?;
470

Robert Czechowski's avatar
Robert Czechowski committed
471
    println!("{}", task_id);
Robert Czechowski's avatar
Robert Czechowski committed
472

473
    let (template, data) = with_conn![functions::show_task, req, task_id, session_token].aug(req)?;
Robert Czechowski's avatar
Robert Czechowski committed
474
475
476
477
478
479
480

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

fn groups(req: &mut Request) -> IronResult<Response> {
481
    let session_token = req.require_session_token()?;
Daniel Brüning's avatar
Daniel Brüning committed
482

483
    let (template, data) = with_conn![functions::show_groups, req, session_token].aug(req)?;
Daniel Brüning's avatar
Daniel Brüning committed
484

Robert Czechowski's avatar
Robert Czechowski committed
485
486
487
488
489
490
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

fn group(req: &mut Request) -> IronResult<Response> {
Robert Czechowski's avatar
Robert Czechowski committed
491
    let group_id = req.expect_int::<u32>("groupid")?;
492
    let session_token = req.require_session_token()?;
Daniel Brüning's avatar
Daniel Brüning committed
493

494
    let (template, data) = with_conn![functions::show_group, req, group_id, session_token].aug(req)?;
Daniel Brüning's avatar
Daniel Brüning committed
495

496
497
498
    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
499
500
501
}

fn group_post(req: &mut Request) -> IronResult<Response> {
Robert Czechowski's avatar
Robert Czechowski committed
502
    let group_id = req.expect_int::<u32>("groupid")?;
503
    let session_token = req.expect_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
504

505
506
    //TODO: use result?
    with_conn![functions::modify_group, req, group_id, session_token].aug(req)?;
Robert Czechowski's avatar
Robert Czechowski committed
507

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

fn new_group(req: &mut Request) -> IronResult<Response> {
512
    let session_token = req.require_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
513
514
515
516
517
518
519

    let (csrf, name, tag) = {
        let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
        (iexpect!(formdata.get("csrf"),(status::BadRequest, mime!(Text/Html), format!("400 Bad Request")))[0].to_owned(),
         iexpect!(formdata.get("name"),(status::BadRequest, mime!(Text/Html), format!("400 Bad Request")))[0].to_owned(),
         iexpect!(formdata.get("tag"),(status::BadRequest, mime!(Text/Html), format!("400 Bad Request")))[0].to_owned())
    };
Robert Czechowski's avatar
Robert Czechowski committed
520
521
    println!("{}", csrf);
    println!("{}", name);
Robert Czechowski's avatar
Robert Czechowski committed
522

523
    let group_id = with_conn![functions::add_group, req, session_token, csrf, name, tag].aug(req)?;
Daniel Brüning's avatar
Daniel Brüning committed
524

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

fn profile(req: &mut Request) -> IronResult<Response> {
529
    let session_token = req.require_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
530
    let query_string = req.url.query().map(|s| s.to_string());
Daniel Brüning's avatar
Daniel Brüning committed
531

532
    let (template, data) = with_conn![functions::show_profile, req, session_token, None, query_string].aug(req)?;
Daniel Brüning's avatar
Daniel Brüning committed
533

Robert Czechowski's avatar
Robert Czechowski committed
534
535
536
537
538
539
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

fn profile_post(req: &mut Request) -> IronResult<Response> {
540
    let session_token = req.expect_session_token()?;
541
    let (csrf_token, firstname, lastname, pwd, pwd_repeat, grade) = {
Robert Czechowski's avatar
Robert Czechowski committed
542
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
Robert Czechowski's avatar
Robert Czechowski committed
543
544
545
546
547
548
        (iexpect!(formdata.get("csrftoken"))[0].to_owned(),
         iexpect!(formdata.get("firstname"))[0].to_owned(),
         iexpect!(formdata.get("lastname"))[0].to_owned(),
         iexpect!(formdata.get("password"))[0].to_owned(),
         iexpect!(formdata.get("password_repeat"))[0].to_owned(),
         iexpect!(formdata.get("grade"))[0].parse::<u8>().unwrap_or(0))
Robert Czechowski's avatar
Robert Czechowski committed
549
    };
Daniel Brüning's avatar
Daniel Brüning committed
550
551

    //TODO: use profilechangeresult
Robert Czechowski's avatar
Robert Czechowski committed
552
553
554
555
556
557
558
559
560
561
    let _profilechangeresult = with_conn![functions::edit_profile,
                                          req,
                                          session_token,
                                          None,
                                          csrf_token,
                                          firstname,
                                          lastname,
                                          pwd,
                                          pwd_repeat,
                                          grade].aug(req)?;
562

563
    Ok(Response::with((status::Found, Redirect(url_for!(req, "profile")))))
Robert Czechowski's avatar
Robert Czechowski committed
564
565
566
}

fn user(req: &mut Request) -> IronResult<Response> {
567
568
    let user_id = req.expect_int::<u32>("userid")?;
    let session_token = req.expect_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
569
    let query_string = req.url.query().map(|s| s.to_string());
Daniel Brüning's avatar
Daniel Brüning committed
570

Robert Czechowski's avatar
Robert Czechowski committed
571
572
    let (template, data) =
        with_conn![functions::show_profile, req, session_token, Some(user_id), query_string].aug(req)?;
Daniel Brüning's avatar
Daniel Brüning committed
573

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

579
580
581
fn user_post(req: &mut Request) -> IronResult<Response> {
    let user_id = req.expect_int::<u32>("userid")?;
    let session_token = req.expect_session_token()?;
582
    let (csrf_token, firstname, lastname, pwd, pwd_repeat, grade) = {
583
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
Robert Czechowski's avatar
Robert Czechowski committed
584
585
586
587
588
589
        (iexpect!(formdata.get("csrftoken"))[0].to_owned(),
         iexpect!(formdata.get("firstname"))[0].to_owned(),
         iexpect!(formdata.get("lastname"))[0].to_owned(),
         iexpect!(formdata.get("password"))[0].to_owned(),
         iexpect!(formdata.get("password_repeat"))[0].to_owned(),
         iexpect!(formdata.get("grade"))[0].parse::<u8>().unwrap_or(0))
590
    };
Daniel Brüning's avatar
Daniel Brüning committed
591
592

    //TODO: use profilechangeresult
Robert Czechowski's avatar
Robert Czechowski committed
593
594
595
596
597
598
599
600
601
602
    let _profilechangeresult = with_conn![functions::edit_profile,
                                          req,
                                          session_token,
                                          Some(user_id),
                                          csrf_token,
                                          firstname,
                                          lastname,
                                          pwd,
                                          pwd_repeat,
                                          grade].aug(req)?;
603
604
605
606

    Ok(Response::with((status::Found, Redirect(url_for!(req, "user", "userid" => format!("{}",user_id))))))
}

607
608
#[derive(Deserialize, Debug)]
struct OAuthAccess {
Robert Czechowski's avatar
Robert Czechowski committed
609
610
    access_token: String,
    token_type: String,
611
    refresh_token: String,
Robert Czechowski's avatar
Robert Czechowski committed
612
613
    expires: Option<u32>,    // documented as 'expires_in'
    expires_in: Option<u32>, // sent as 'expires'
614
615
616
}

#[derive(Deserialize, Debug)]
617
#[allow(non_snake_case)]
618
pub struct OAuthUserData {
Robert Czechowski's avatar
Robert Czechowski committed
619
620
621
622
623
624
    userID: Option<String>, // documented as 'userId'
    userId: Option<String>, // sent as 'userID'
    userType: String,
    gender: String,
    firstName: String,
    lastName: String,
625
    dateOfBirth: Option<String>,
Robert Czechowski's avatar
Robert Czechowski committed
626
627
    eMail: Option<String>,
    userId_int: Option<u32>,
628
629
630
631
}

fn oauth(req: &mut Request) -> IronResult<Response> {
    use params::{Params, Value};
Robert Czechowski's avatar
Robert Czechowski committed
632
    use reqwest::header;
633
634
635

    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
636
        let config = mutex.lock().unwrap_or_else(|e| e.into_inner());
Robert Czechowski's avatar
Robert Czechowski committed
637
638
639
640
641
        if let (Some(id), Some(secret), Some(atu), Some(udu)) = (&config.oauth_client_id,
                                                                 &config.oauth_client_secret,
                                                                 &config.oauth_access_token_url,
                                                                 &config.oauth_user_data_url)
        {
642
643
            (id.clone(), secret.clone(), atu.clone(), udu.clone())
        } else {
Robert Czechowski's avatar
Robert Czechowski committed
644
            return Ok(Response::with(iron::status::NotFound));
645
646
        }
    };
647

648
    let (_state, _scope, code): (String, String, String) = {
649
650
        let map = req.get_ref::<Params>().unwrap();

Robert Czechowski's avatar
Robert Czechowski committed
651
652
653
654
        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" =>
            {
655
                (state.clone(), scope.clone(), code.clone())
Robert Czechowski's avatar
Robert Czechowski committed
656
            }
657
658
659
660
            _ => return Ok(Response::with(iron::status::NotFound)),
        }
    };

661
662
663
    let client = reqwest::Client::new().unwrap();
    let params = [("code", code), ("grant_type", "authorization_code".to_string())];
    let res = client.post(&access_token_url)
Robert Czechowski's avatar
Robert Czechowski committed
664
665
666
                    .header(header::Authorization(header::Basic { username: client_id, password: Some(client_secret) }))
                    .form(&params)
                    .send();
667
668
669
    let access: OAuthAccess = res.expect("network error").json().expect("malformed json");

    let res = client.post(&user_data_url)
Robert Czechowski's avatar
Robert Czechowski committed
670
671
672
                    .header(header::Authorization(header::Bearer { token: access.access_token }))
                    .form(&params)
                    .send();
673
674
675
676
677
678
679
680
681
    let mut user_data: OAuthUserData = res.expect("network error").json().expect("malformed json");

    if let Some(ref id) = user_data.userID {
        user_data.userId_int = Some(id.parse::<u32>().unwrap());
    }
    if let Some(ref id) = user_data.userId {
        user_data.userId_int = Some(id.parse::<u32>().unwrap());
    }

Robert Czechowski's avatar
Robert Czechowski committed
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
    use functions::{UserGender, UserType};

    let user_data = functions::ForeignUserData { foreign_id: user_data.userId_int.unwrap(),
                                                 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 };

    let oauthloginresult = {
699
700
701
        // hier ggf. Daten aus dem Request holen
        let mutex = req.get::<Write<SharedDatabaseConnection>>().unwrap();
        let conn = mutex.lock().unwrap_or_else(|e| e.into_inner());
702
703

        // Antwort erstellen und zurücksenden
704
705
        functions::login_oauth(&*conn, user_data)
        /*let mut data = json_val::Map::new();
Robert Czechowski's avatar
Robert Czechowski committed
706
707
        data.insert("reason".to_string(), to_json(&"Not implemented".to_string()));
        ("profile", data)*/
708
    };
709

710
711
712
713
714
    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
715
        }
716
717
718
719
720
721
722
        // Login failed
        Err((template, data)) => {
            let mut resp = Response::new();
            resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
            Ok(resp)
        }
    }
723
724
}

Robert Czechowski's avatar
Robert Czechowski committed
725
726
727
// Share Database connection between workers
#[derive(Copy, Clone)]
pub struct SharedDatabaseConnection;
Robert Czechowski's avatar
Robert Czechowski committed
728
729
730
impl Key for SharedDatabaseConnection {
    type Value = rusqlite::Connection;
}
Robert Czechowski's avatar
Robert Czechowski committed
731

732
733
734
// Share Configuration between workers
#[derive(Copy, Clone)]
pub struct SharedConfiguration;
Robert Czechowski's avatar
Robert Czechowski committed
735
736
737
impl Key for SharedConfiguration {
    type Value = ::Config;
}
738

Robert Czechowski's avatar
Robert Czechowski committed
739
740
#[cfg(feature = "watch")]
pub fn get_handlebars_engine() -> impl AfterMiddleware {
741
    // HandlebarsEngine will look up all files with "./examples/templates/**/*.hbs"
Robert Czechowski's avatar
Robert Czechowski committed
742
743
744
745
746
747
748
749
750
    let mut hbse = HandlebarsEngine::new();
    hbse.add(Box::new(DirectorySource::new("./templates/", ".hbs")));

    // 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
751
    use std::sync::Arc;
752

Robert Czechowski's avatar
Robert Czechowski committed
753
754
755
756
757
758
759
    let hbse_ref = Arc::new(hbse);
    hbse_ref.watch("./templates/");
    hbse_ref
}

#[cfg(not(feature = "watch"))]
pub fn get_handlebars_engine() -> impl AfterMiddleware {
760
    // HandlebarsEngine will look up all files with "./examples/templates/**/*.hbs"
Robert Czechowski's avatar
Robert Czechowski committed
761
762
763
764
765
766
767
    let mut hbse = HandlebarsEngine::new();
    hbse.add(Box::new(DirectorySource::new("./templates/", ".hbs")));

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

    hbse
Robert Czechowski's avatar
Robert Czechowski committed
770
771
}

772
fn cookie_warning(req: &mut Request) -> IronResult<Response> {
773
    match req.get_session_token() {
Daniel Brüning's avatar
Daniel Brüning committed
774
        Some(_session_token) => {
775
776
            // TODO: Set session!
            // TODO:
Robert Czechowski's avatar
Robert Czechowski committed
777
778
            Ok(Response::with((status::Found, RedirectRaw(format!("/{}", req.url.query().unwrap_or(""))))))
        }
779
780
781
782
783
784
785
        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
786

787
pub fn start_server(conn: Connection, config: ::Config) -> iron::error::HttpResult<iron::Listening> {
Robert Czechowski's avatar
Robert Czechowski committed
788
    let router = router!(
Robert Czechowski's avatar
Robert Czechowski committed
789
        greet: get "/" => greet_personal,
Robert Czechowski's avatar
Robert Czechowski committed
790
        contests: get "/contest/" => contests,
Robert Czechowski's avatar
Robert Czechowski committed
791
        contest: get "/contest/:contestid" => contest,
792
        contestresults: get "/contest/:contestid/result/" => contestresults,
Robert Czechowski's avatar
Robert Czechowski committed
793
        contest_post: post "/contest/:contestid" => contest_post,
Robert Czechowski's avatar
Robert Czechowski committed
794
795
        login: get "/login" => login,
        login_post: post "/login" => login_post,
Robert Czechowski's avatar
Robert Czechowski committed
796
797
        login_code_post: post "/clogin" => login_code_post,
        logout: get "/logout" => logout,
Robert Czechowski's avatar
Robert Czechowski committed
798
799
800
801
802
803
804
805
806
        subm: get "/submission/:taskid" => submission,
        subm_post: post "/submission/:taskid" => submission_post,
        subm_load: get "/load/:taskid" => submission,
        subm_save: post "/save/:taskid" => submission_post,
        groups: get "/group/" => groups,
        groups: post "/group/" => new_group,
        group: get "/group/:groupid" => group,
        group_post: post "/group" => group_post,
        profile: get "/profile" => profile,
Robert Czechowski's avatar
Robert Czechowski committed
807
808
        profile_post: post "/profile" => profile_post,
        user: get "/user/:userid" => user,
809
        user_post: post "/user/:userid" => user_post,
Robert Czechowski's avatar
Robert Czechowski committed
810
        task: get "/task/:taskid" => task,
811
        oauth: get "/oauth" => oauth,
812
        check_cookie: get "/cookie" => cookie_warning,
813
814
815
816
        debug: get "/debug" => debug,
        debug_reset: get "/debug/reset" => debug_new_token,
        debug_logout: get "/debug/logout" => debug_logout,
        debug_create: get "/debug/create" => debug_create_session,
Robert Czechowski's avatar
Robert Czechowski committed
817
818
819
820
821
822
823
824
    );

    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
825
    mount.mount("/tasks/", Static::new(Path::new(TASK_DIR)));
Robert Czechowski's avatar
Robert Czechowski committed
826
827
828
    mount.mount("/", router);

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

Robert Czechowski's avatar
Robert Czechowski committed
830
    ch.link(Write::<SharedDatabaseConnection>::both(conn));
831
    ch.link(Write::<SharedConfiguration>::both(config.clone()));
832
    ch.link_around(CookieDistributor::new());
Robert Czechowski's avatar
Robert Czechowski committed
833
834
    ch.link_around(SessionStorage::new(SignedCookieBackend::new(my_secret)));

Robert Czechowski's avatar
Robert Czechowski committed
835
    ch.link_after(get_handlebars_engine());
Robert Czechowski's avatar
Robert Czechowski committed
836
    ch.link_after(ErrorReporter);
837

838
    let socket_addr = format!("{}:{}", config.host.unwrap(), config.port.unwrap());
839

840
    let srvr = Iron::new(ch).http(&socket_addr);
841
    println!("Listening on {}.", &socket_addr);
842
    srvr
Robert Czechowski's avatar
Robert Czechowski committed
843
}