webfw_iron.rs 31 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> {
Robert Czechowski's avatar
Robert Czechowski committed
94

95
96
        use rand::{distributions::Alphanumeric, thread_rng, Rng};

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

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

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

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

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

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

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

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>;
159
    fn expect_str(self: &mut Self, key: &str) -> IronResult<String>;
160
161
162
163
164
165
166
167
168
169
}

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

171
172
173
    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
174
175
176
            _ => Err(IronError { error: Box::new(SessionError { message:
                                                                    "No valid routing parameter".to_string() }),
                                 response: Response::with(status::Forbidden) }),
177
178
        }
    }
179
180
181
182
183
184
185
186
187
    
    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) }),
        }
    }
188
189
}

Robert Czechowski's avatar
Robert Czechowski committed
190
use functions;
Robert Czechowski's avatar
Robert Czechowski committed
191

192
193
194
195
196
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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
            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) }
            }
228
        }
229
230
231
    }
}

232
233
234
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
235
impl<'c, 'a: 'c, 'b: 'c + 'a, T> RequestAugmentMedalError<'c, 'a, 'b, T> for Result<T, functions::MedalError> {
236
    fn aug(self, req: &'c mut Request<'a, 'b>) -> Result<T, AugMedalError<'c, 'a, 'b>> {
Robert Czechowski's avatar
Robert Czechowski committed
237
        self.map_err(move |me| AugMedalError(me, req))
238
239
240
    }
}

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

245
    let (self_url, oauth_providers) = {
246
247
        let mutex = req.get::<Write<SharedConfiguration>>().unwrap();
        let config = mutex.lock().unwrap_or_else(|e| e.into_inner());
248
        (config.self_url.clone(), config.oauth_providers.clone())
249
250
    };

Robert Czechowski's avatar
Robert Czechowski committed
251
252
253
254
255
    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());

256
        // Antwort erstellen und zurücksenden
257
        functions::index(&*conn, session_token, (self_url, oauth_providers))
Robert Czechowski's avatar
Robert Czechowski committed
258
    };
Robert Czechowski's avatar
Robert Czechowski committed
259
260
    // Daten verarbeiten

261
    // Antwort erstellen und zurücksenden
Robert Czechowski's avatar
Robert Czechowski committed
262
263
264
265
266
267
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

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

Robert Czechowski's avatar
Robert Czechowski committed
270
271
272
273
274
275
    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
276
    let contest_id = req.expect_int::<u32>("contestid")?;
277
    let session_token = req.require_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
278

Robert Czechowski's avatar
Robert Czechowski committed
279
    let (template, data) = with_conn![functions::show_contest, req, contest_id, session_token].aug(req)?;
Robert Czechowski's avatar
Robert Czechowski committed
280
281
282
283
284
285

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

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

Robert Czechowski's avatar
Robert Czechowski committed
290
    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
291

292
293
294
295
296
    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
297
fn contest_post(req: &mut Request) -> IronResult<Response> {
Robert Czechowski's avatar
Robert Czechowski committed
298
    let contest_id = req.expect_int::<u32>("contestid")?;
299
    let session_token = req.expect_session_token()?;
300

Robert Czechowski's avatar
Robert Czechowski committed
301
302
303
304
305
    let csrf_token = {
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
        iexpect!(formdata.get("csrftoken"))[0].to_owned()
    };

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

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

312
fn login(req: &mut Request) -> IronResult<Response> {
313
    let (self_url, oauth_providers) = {
314
315
        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
316

317
        (config.self_url.clone(), config.oauth_providers.clone())
318
319
320
321
    };

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

Robert Czechowski's avatar
Robert Czechowski committed
325
    let mut resp = Response::new();
326
    resp.set_mut(Template::new("login", data)).set_mut(status::Ok);
Robert Czechowski's avatar
Robert Czechowski committed
327
328
329
330
331
332
    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
333
        (iexpect!(formdata.get("username"))[0].to_owned(), iexpect!(formdata.get("password"))[0].to_owned())
Robert Czechowski's avatar
Robert Czechowski committed
334
335
    };

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

338
    let loginresult = with_conn![functions::login, req, logindata];
Robert Czechowski's avatar
Robert Czechowski committed
339
340
341
342
343
344

    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
345
        }
Robert Czechowski's avatar
Robert Czechowski committed
346
347
348
349
350
351
352
353
354
        // 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
355
356
357
358
359
360
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()
    };

361
362
    // TODO: Submit current session to login

363
    let loginresult = with_conn![functions::login_with_code, req, code];
Robert Czechowski's avatar
Robert Czechowski committed
364
365
366
367
368
369
370
    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
371
        }
Robert Czechowski's avatar
Robert Czechowski committed
372
373
374
        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
375
        }
Robert Czechowski's avatar
Robert Czechowski committed
376
377
378
379
380
381
382
383
        // 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)
        }
    }
384
}
Robert Czechowski's avatar
Robert Czechowski committed
385
386

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

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

391
    with_conn![functions::logout, req, session_token];
Robert Czechowski's avatar
Robert Czechowski committed
392
393
394
395

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

Robert Czechowski's avatar
Robert Czechowski committed
396
fn submission(req: &mut Request) -> IronResult<Response> {
Robert Czechowski's avatar
Robert Czechowski committed
397
    let task_id = req.expect_int::<u32>("taskid")?;
398
    let session_token = req.expect_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
399
400
401
    let subtask: Option<String> = (|| -> Option<String> {
        req.get_ref::<UrlEncodedQuery>().ok()?.get("subtask")?.get(0).map(|x| x.to_owned())
    })();
402

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

405
    let result = with_conn![functions::load_submission, req, task_id, session_token, subtask];
Robert Czechowski's avatar
Robert Czechowski committed
406
407

    match result {
Robert Czechowski's avatar
Robert Czechowski committed
408
409
        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
410
411
412
413
    }
}

fn submission_post(req: &mut Request) -> IronResult<Response> {
Robert Czechowski's avatar
Robert Czechowski committed
414
    let task_id = req.expect_int::<u32>("taskid")?;
415
    let session_token = req.expect_session_token()?;
416
    let (csrf_token, data, grade, subtask) = {
Robert Czechowski's avatar
Robert Czechowski committed
417
418
        let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
        (iexpect!(formdata.get("csrf"),(status::BadRequest, mime!(Text/Html), format!("400 Bad Request")))[0].to_owned(),
419
         iexpect!(formdata.get("data"),(status::BadRequest, mime!(Text/Html), format!("400 Bad Request")))[0].to_owned(),
420
         iexpect!(formdata.get("grade").unwrap_or(&vec!["0".to_owned()])[0].parse::<u8>().ok(),(status::BadRequest, mime!(Text/Html), format!("400 Bad Request"))),
421
         formdata.get("subtask").map(|x| x[0].to_owned()),
422
        )
Robert Czechowski's avatar
Robert Czechowski committed
423
424
425
426
    };
    println!("{}", data);
    println!("{}", task_id);
    println!("{}", grade);
Daniel Brüning's avatar
Daniel Brüning committed
427

428
    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
429

Robert Czechowski's avatar
Robert Czechowski committed
430
    match result {
Robert Czechowski's avatar
Robert Czechowski committed
431
432
        Ok(_) => Ok(Response::with((status::Ok, mime!(Application / Json), format!("{{}}")))),
        Err(_) => Ok(Response::with((status::BadRequest, mime!(Application / Json), format!("{{}}")))),
433
    }
Robert Czechowski's avatar
Robert Czechowski committed
434
435
436
}

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

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

442
    let (template, data) = with_conn![functions::show_task, req, task_id, session_token].aug(req)?;
Robert Czechowski's avatar
Robert Czechowski committed
443
444
445
446
447
448
449

    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> {
450
    let session_token = req.require_session_token()?;
Daniel Brüning's avatar
Daniel Brüning committed
451

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

Robert Czechowski's avatar
Robert Czechowski committed
454
455
456
457
458
459
    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
460
    let group_id = req.expect_int::<u32>("groupid")?;
461
    let session_token = req.require_session_token()?;
Daniel Brüning's avatar
Daniel Brüning committed
462

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

465
466
467
    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
468
469
470
}

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

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

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

fn new_group(req: &mut Request) -> IronResult<Response> {
481
    let session_token = req.require_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
482
483
484
485
486
487
488

    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
489
490
    println!("{}", csrf);
    println!("{}", name);
Robert Czechowski's avatar
Robert Czechowski committed
491

492
    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
493

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

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

501
    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
502

Robert Czechowski's avatar
Robert Czechowski committed
503
504
505
506
507
508
    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> {
509
    let session_token = req.expect_session_token()?;
510
    let (csrf_token, firstname, lastname, pwd, pwd_repeat, grade) = {
Robert Czechowski's avatar
Robert Czechowski committed
511
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
Robert Czechowski's avatar
Robert Czechowski committed
512
513
514
515
516
517
        (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
518
    };
Daniel Brüning's avatar
Daniel Brüning committed
519
520

    //TODO: use profilechangeresult
Robert Czechowski's avatar
Robert Czechowski committed
521
522
523
524
525
526
527
528
529
530
    let _profilechangeresult = with_conn![functions::edit_profile,
                                          req,
                                          session_token,
                                          None,
                                          csrf_token,
                                          firstname,
                                          lastname,
                                          pwd,
                                          pwd_repeat,
                                          grade].aug(req)?;
531

532
    Ok(Response::with((status::Found, Redirect(url_for!(req, "profile")))))
Robert Czechowski's avatar
Robert Czechowski committed
533
534
535
}

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

Robert Czechowski's avatar
Robert Czechowski committed
540
541
    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
542

Robert Czechowski's avatar
Robert Czechowski committed
543
544
545
546
547
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

548
549
550
fn user_post(req: &mut Request) -> IronResult<Response> {
    let user_id = req.expect_int::<u32>("userid")?;
    let session_token = req.expect_session_token()?;
551
    let (csrf_token, firstname, lastname, pwd, pwd_repeat, grade) = {
552
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
Robert Czechowski's avatar
Robert Czechowski committed
553
554
555
556
557
558
        (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))
559
    };
Daniel Brüning's avatar
Daniel Brüning committed
560
561

    //TODO: use profilechangeresult
Robert Czechowski's avatar
Robert Czechowski committed
562
563
564
565
566
567
568
569
570
571
    let _profilechangeresult = with_conn![functions::edit_profile,
                                          req,
                                          session_token,
                                          Some(user_id),
                                          csrf_token,
                                          firstname,
                                          lastname,
                                          pwd,
                                          pwd_repeat,
                                          grade].aug(req)?;
572
573
574
575

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

576
577
#[derive(Deserialize, Debug)]
struct OAuthAccess {
Robert Czechowski's avatar
Robert Czechowski committed
578
579
    access_token: String,
    token_type: String,
580
    refresh_token: String,
Robert Czechowski's avatar
Robert Czechowski committed
581
582
    expires: Option<u32>,    // documented as 'expires_in'
    expires_in: Option<u32>, // sent as 'expires'
583
584
585
}

#[derive(Deserialize, Debug)]
586
#[allow(non_snake_case)]
587
pub struct OAuthUserData {
Robert Czechowski's avatar
Robert Czechowski committed
588
589
590
591
592
593
    userID: Option<String>, // documented as 'userId'
    userId: Option<String>, // sent as 'userID'
    userType: String,
    gender: String,
    firstName: String,
    lastName: String,
594
    dateOfBirth: Option<String>,
Robert Czechowski's avatar
Robert Czechowski committed
595
596
    eMail: Option<String>,
    userId_int: Option<u32>,
597
598
599
600
}

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

603
604
    let oauth_id = req.expect_str("oauthid")?;
    
605
606
    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
607
        let config = mutex.lock().unwrap_or_else(|e| e.into_inner());
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627

        let mut result: Option<(String, String, String, String)> = None;
        
        if let Some(ref oauth_providers) = config.oauth_providers {
            for oauth_provider in oauth_providers {
                if oauth_provider.provider_id == oauth_id {
                    result = Some(
                        (oauth_provider.client_id.clone(),
                        oauth_provider.client_secret.clone(),
                        oauth_provider.access_token_url.clone(),
                        oauth_provider.user_data_url.clone()));
                    break;
                }
            }

            if let Some(result) = result {
                result
            } else {
                return Ok(Response::with(iron::status::NotFound));
            }
628
        } else {
Robert Czechowski's avatar
Robert Czechowski committed
629
            return Ok(Response::with(iron::status::NotFound));
630
631
        }
    };
632

633
    let (_state, _scope, code): (String, String, String) = {
634
635
        let map = req.get_ref::<Params>().unwrap();

Robert Czechowski's avatar
Robert Czechowski committed
636
637
638
639
        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" =>
            {
640
                (state.clone(), scope.clone(), code.clone())
Robert Czechowski's avatar
Robert Czechowski committed
641
            }
642
643
644
645
            _ => return Ok(Response::with(iron::status::NotFound)),
        }
    };

646
647
648
    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
649
650
651
                    .header(header::Authorization(header::Basic { username: client_id, password: Some(client_secret) }))
                    .form(&params)
                    .send();
652
653
654
    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
655
656
657
                    .header(header::Authorization(header::Bearer { token: access.access_token }))
                    .form(&params)
                    .send();
658
659
660
661
662
663
664
665
666
    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
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
    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 = {
684
685
686
        // 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());
687
688

        // Antwort erstellen und zurücksenden
689
690
        functions::login_oauth(&*conn, user_data)
        /*let mut data = json_val::Map::new();
Robert Czechowski's avatar
Robert Czechowski committed
691
692
        data.insert("reason".to_string(), to_json(&"Not implemented".to_string()));
        ("profile", data)*/
693
    };
694

695
696
697
698
699
    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
700
        }
701
702
703
704
705
706
707
        // Login failed
        Err((template, data)) => {
            let mut resp = Response::new();
            resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
            Ok(resp)
        }
    }
708
709
}

Robert Czechowski's avatar
Robert Czechowski committed
710
711
712
// Share Database connection between workers
#[derive(Copy, Clone)]
pub struct SharedDatabaseConnection;
Robert Czechowski's avatar
Robert Czechowski committed
713
714
715
impl Key for SharedDatabaseConnection {
    type Value = rusqlite::Connection;
}
Robert Czechowski's avatar
Robert Czechowski committed
716

717
718
719
// Share Configuration between workers
#[derive(Copy, Clone)]
pub struct SharedConfiguration;
Robert Czechowski's avatar
Robert Czechowski committed
720
721
722
impl Key for SharedConfiguration {
    type Value = ::Config;
}
723

Robert Czechowski's avatar
Robert Czechowski committed
724
725
#[cfg(feature = "watch")]
pub fn get_handlebars_engine() -> impl AfterMiddleware {
726
    // HandlebarsEngine will look up all files with "./examples/templates/**/*.hbs"
Robert Czechowski's avatar
Robert Czechowski committed
727
728
729
730
731
732
733
734
735
    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
736
    use std::sync::Arc;
737

Robert Czechowski's avatar
Robert Czechowski committed
738
739
740
741
742
743
744
    let hbse_ref = Arc::new(hbse);
    hbse_ref.watch("./templates/");
    hbse_ref
}

#[cfg(not(feature = "watch"))]
pub fn get_handlebars_engine() -> impl AfterMiddleware {
745
    // HandlebarsEngine will look up all files with "./examples/templates/**/*.hbs"
Robert Czechowski's avatar
Robert Czechowski committed
746
747
748
749
750
751
752
    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
753
754

    hbse
Robert Czechowski's avatar
Robert Czechowski committed
755
756
}

757
fn cookie_warning(req: &mut Request) -> IronResult<Response> {
758
    match req.get_session_token() {
Daniel Brüning's avatar
Daniel Brüning committed
759
        Some(_session_token) => {
760
761
            // TODO: Set session!
            // TODO:
Robert Czechowski's avatar
Robert Czechowski committed
762
763
            Ok(Response::with((status::Found, RedirectRaw(format!("/{}", req.url.query().unwrap_or(""))))))
        }
764
765
766
767
768
769
770
        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
771

772
pub fn start_server(conn: Connection, config: ::Config) -> iron::error::HttpResult<iron::Listening> {
Robert Czechowski's avatar
Robert Czechowski committed
773
    let router = router!(
Robert Czechowski's avatar
Robert Czechowski committed
774
        greet: get "/" => greet_personal,
Robert Czechowski's avatar
Robert Czechowski committed
775
        contests: get "/contest/" => contests,
Robert Czechowski's avatar
Robert Czechowski committed
776
        contest: get "/contest/:contestid" => contest,
777
        contestresults: get "/contest/:contestid/result/" => contestresults,
Robert Czechowski's avatar
Robert Czechowski committed
778
        contest_post: post "/contest/:contestid" => contest_post,
Robert Czechowski's avatar
Robert Czechowski committed
779
780
        login: get "/login" => login,
        login_post: post "/login" => login_post,
Robert Czechowski's avatar
Robert Czechowski committed
781
782
        login_code_post: post "/clogin" => login_code_post,
        logout: get "/logout" => logout,
Robert Czechowski's avatar
Robert Czechowski committed
783
784
785
786
787
788
789
790
791
        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
792
793
        profile_post: post "/profile" => profile_post,
        user: get "/user/:userid" => user,
794
        user_post: post "/user/:userid" => user_post,
Robert Czechowski's avatar
Robert Czechowski committed
795
        task: get "/task/:taskid" => task,
796
        oauth: get "/oauth/:oauthid" => oauth,
797
        check_cookie: get "/cookie" => cookie_warning,
Robert Czechowski's avatar
Robert Czechowski committed
798
799
    );

800
    // TODO: how important is this? Should this be in the config?
Robert Czechowski's avatar
Robert Czechowski committed
801
802
803
804
805
806
    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
807
    mount.mount("/tasks/", Static::new(Path::new(TASK_DIR)));
Robert Czechowski's avatar
Robert Czechowski committed
808
809
810
    mount.mount("/", router);

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

Robert Czechowski's avatar
Robert Czechowski committed
812
    ch.link(Write::<SharedDatabaseConnection>::both(conn));
813
    ch.link(Write::<SharedConfiguration>::both(config.clone()));
814
    ch.link_around(CookieDistributor::new());
Robert Czechowski's avatar
Robert Czechowski committed
815
816
    ch.link_around(SessionStorage::new(SignedCookieBackend::new(my_secret)));

Robert Czechowski's avatar
Robert Czechowski committed
817
    ch.link_after(get_handlebars_engine());
Robert Czechowski's avatar
Robert Czechowski committed
818
    ch.link_after(ErrorReporter);
819

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

822
    let srvr = Iron::new(ch).http(&socket_addr);
823
    println!("Listening on {}.", &socket_addr);
824
    srvr
Robert Czechowski's avatar
Robert Czechowski committed
825
}