main.rs 29.5 KB
Newer Older
1
2
#![cfg_attr(feature = "strict", deny(warnings))]

Robert Czechowski's avatar
Robert Czechowski committed
3
4
5
6
7
8
9
#[macro_use]
extern crate iron;
#[macro_use]
extern crate router;
#[macro_use]
extern crate serde_derive;

Robert Czechowski's avatar
Robert Czechowski committed
10
extern crate handlebars_iron;
Robert Czechowski's avatar
Robert Czechowski committed
11
12
extern crate iron_sessionstorage;
extern crate mount;
13
extern crate params;
Robert Czechowski's avatar
Robert Czechowski committed
14
15
extern crate persistent;
extern crate rand;
16
extern crate reqwest;
Robert Czechowski's avatar
Robert Czechowski committed
17
extern crate serde_json;
18
extern crate serde_yaml;
Robert Czechowski's avatar
Robert Czechowski committed
19
20
21
22
extern crate staticfile;
extern crate structopt;
extern crate time;
extern crate urlencoded;
23
24
25
26
27

#[cfg(feature = "postgres")]
extern crate postgres;
#[cfg(feature = "rusqlite")]
extern crate rusqlite;
28
#[cfg(feature = "webbrowser")]
29
extern crate webbrowser;
30

31
32
pub mod config;
pub mod contestreader_yaml;
33
pub mod core;
34
pub mod db_conn;
35
pub mod helpers;
36
37
38
pub mod oauth_provider;

mod db_apply_migrations;
39
mod db_conn_postgres;
40
mod db_conn_sqlite;
41
mod db_objects;
42
43
mod webfw_iron;

44
use db_conn::{MedalConnection, MedalObject};
45
use db_objects::*;
46
use helpers::SetPassword;
Robert Czechowski's avatar
Robert Czechowski committed
47
48
use webfw_iron::start_server;

49
use config::Config;
50
51
use structopt::StructOpt;

52
use std::path::{Path, PathBuf};
53

54
fn read_contest(p: &PathBuf) -> Option<Contest> {
55
56
    use std::fs::File;
    use std::io::Read;
57

58
59
60
    let mut file = File::open(p).unwrap();
    let mut contents = String::new();
    file.read_to_string(&mut contents).unwrap();
61

62
    contestreader_yaml::parse_yaml(&contents,
63
64
                                   p.file_name().to_owned()?.to_str()?,
                                   &format!("{}/", p.parent().unwrap().to_str()?))
Robert Czechowski's avatar
Robert Czechowski committed
65
66
67
}

fn get_all_contest_info(task_dir: &str) -> Vec<Contest> {
68
69
    fn walk_me_recursively(p: &PathBuf, contests: &mut Vec<Contest>) {
        if let Ok(paths) = std::fs::read_dir(p) {
70
            for path in paths {
Robert Czechowski's avatar
Robert Czechowski committed
71
72
                let p = path.unwrap().path();
                walk_me_recursively(&p, contests);
73
            }
Robert Czechowski's avatar
Robert Czechowski committed
74
        }
75

76
        if p.file_name().unwrap().to_string_lossy().to_string().ends_with(".yaml") {
77
            read_contest(p).map(|contest| contests.push(contest));
78
        };
Robert Czechowski's avatar
Robert Czechowski committed
79
80
81
    };

    let mut contests = Vec::new();
82
    match std::fs::read_dir(task_dir) {
Robert Czechowski's avatar
Robert Czechowski committed
83
        Err(why) => println!("Error opening tasks directory! {:?}", why.kind()),
Robert Czechowski's avatar
Robert Czechowski committed
84
85
86
87
88
        Ok(paths) => {
            for path in paths {
                walk_me_recursively(&path.unwrap().path(), &mut contests);
            }
        }
Robert Czechowski's avatar
Robert Czechowski committed
89
90
91
92
93
    };

    contests
}

94
95
96
97
fn refresh_all_contests<C>(conn: &mut C)
    where C: MedalConnection,
          db_objects::Contest: db_conn::MedalObject<C>
{
Robert Czechowski's avatar
Robert Czechowski committed
98
99
100
101
102
103
104
    let v = get_all_contest_info("tasks/");

    for mut contest_info in v {
        contest_info.save(conn);
    }
}

105
106
fn add_admin_user<C>(conn: &mut C, resetpw: bool)
    where C: MedalConnection {
107
108
109
    let mut admin = match conn.get_user_by_id(1) {
        None => {
            print!("New Database. Creating new admin user with credentials 'admin':");
110
            conn.new_session("")
Robert Czechowski's avatar
Robert Czechowski committed
111
        }
112
113
        Some(user) => {
            if !resetpw {
Robert Czechowski's avatar
Robert Czechowski committed
114
                return;
115
            }
116
117
118
119
120
            print!("Request to reset admin password. Set credentials 'admin':");
            user
        }
    };

Robert Czechowski's avatar
Robert Czechowski committed
121
    use rand::{distributions::Alphanumeric, thread_rng, Rng};
122
123

    let password: String = thread_rng().sample_iter(&Alphanumeric)
Robert Czechowski's avatar
Robert Czechowski committed
124
125
126
127
128
129
                                       .filter(|x| {
                                           let x = *x;
                                           !(x == 'l' || x == 'I' || x == '1' || x == 'O' || x == 'o' || x == '0')
                                       })
                                       .take(8)
                                       .collect();
130
131
132
133
    print!("'{}' …", &password);

    admin.username = Some("admin".into());
    match admin.set_password(&password) {
134
        None => println!(" FAILED! (Password hashing error)"),
135
136
        _ => {
            conn.save_session(admin);
137
            println!(" Done");
138
        }
139
140
141
    }
}

142
143
144
145
146
147
148
fn prepare_and_start_server<C>(mut conn: C, config: Config, onlycontestscan: bool, resetadminpw: bool)
    where C: MedalConnection + std::marker::Send + 'static,
          db_objects::Contest: db_conn::MedalObject<C>
{
    db_apply_migrations::test(&mut conn);

    if onlycontestscan || config.no_contest_scan == Some(false) {
149
        print!("Scanning for contests …");
150
        refresh_all_contests(&mut conn);
151
        println!(" Done")
152
153
154
155
156
    }

    if !onlycontestscan {
        add_admin_user(&mut conn, resetadminpw);

157
        #[cfg(feature = "webbrowser")]
158
        let self_url = config.self_url.clone();
159
        #[cfg(feature = "webbrowser")]
160
161
        let open_browser = config.open_browser;

162
        match start_server(conn, config) {
163
164
            Ok(_) => {
                println!("Server started");
165

166
167
168
169
170
                #[cfg(feature = "webbrowser")]
                {
                    if let (Some(self_url), Some(true)) = (self_url, open_browser) {
                        open_browser_window(&self_url);
                    }
171
                }
172
            }
173
174
            Err(_) => println!("Error on server start …"),
        };
175

176
177
178
179
        println!("Could not run server. Is the port already in use?");
    }
}

180
#[cfg(feature = "webbrowser")]
181
182
183
fn open_browser_window(self_url: &str) {
    match webbrowser::open(&self_url) {
        Ok(_) => (),
184
        Err(e) => println!("Error while opening webbrowser: {:?}", e),
185
186
187
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
188
fn main() {
189
    let opt = config::Opt::from_args();
190
191
192

    #[cfg(feature = "debug")]
    println!("Options: {:#?}", opt);
Daniel Brüning's avatar
Daniel Brüning committed
193

194
    let mut config = config::read_config_from_file(&opt.configfile);
195

196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
    #[cfg(feature = "debug")]
    println!("Config: {:#?}", config);

    // Let options override config values
    opt.databasefile.map(|x| config.database_file = Some(x));
    opt.databaseurl.map(|x| config.database_url = Some(x));
    opt.port.map(|x| config.port = Some(x));
    config.no_contest_scan = if opt.nocontestscan { Some(true) } else { config.no_contest_scan };
    config.open_browser = if opt.openbrowser { Some(true) } else { config.open_browser };

    // Use default database file if none set
    config.database_file.get_or_insert(Path::new("medal.db").to_owned());

    #[cfg(feature = "debug")]
    println!("Using config: {:#?}", config);
211

212
213
214
215
216
217
218
219
220
221
    #[cfg(feature = "postgres")]
    {
        if let Some(url) = config.database_url.clone() {
            print!("Using database {} … ", &url);
            let conn = postgres::Connection::connect(url, postgres::TlsMode::None).unwrap();
            println!("Connected");

            prepare_and_start_server(conn, config, opt.onlycontestscan, opt.resetadminpw);
            return;
        }
222
    }
223
224
225
226
227
228
229
230
231
232
233
234
235
236

    #[cfg(feature = "rusqlite")]
    {
        if let Some(path) = config.database_file.clone() {
            print!("Using database file {} … ", &path.to_str().unwrap_or("<unprintable filename>"));
            let conn = rusqlite::Connection::open(path).unwrap();
            println!("Connected");

            prepare_and_start_server(conn, config, opt.onlycontestscan, opt.resetadminpw);
            return;
        }
    }

    println!("No database configured. Try enableing the 'rusqlite' feature during compilation.\nLeaving now.");
237
}
238
239
240
241

#[cfg(test)]
mod tests {
    use super::*;
Robert Czechowski's avatar
Robert Czechowski committed
242
    use reqwest::StatusCode;
243

Robert Czechowski's avatar
Robert Czechowski committed
244
245
    fn start_server_and_fn<F>(port: u16, set_user: Option<(String, String, bool)>, f: F)
        where F: Fn() {
246
        use std::sync::mpsc::channel;
Robert Czechowski's avatar
Robert Czechowski committed
247
        use std::{thread, time};
248
249
250
251
        let (start_tx, start_rx) = channel();
        let (stop_tx, stop_rx) = channel();

        thread::spawn(move || {
252
            let mut conn = rusqlite::Connection::open_in_memory().unwrap();
253
254
            db_apply_migrations::test(&mut conn);

255
            if let Some(user) = set_user {
256
                let mut test_user = conn.new_session("");
257
                test_user.username = Some(user.0);
Robert Czechowski's avatar
Robert Czechowski committed
258
259
260
                test_user.is_teacher = user.2;
                test_user.set_password(&user.1).expect("Set Password did not work correctly.");
                conn.save_session(test_user);
261
262
            }

263
            // ID: 1, gets renamed
Robert Czechowski's avatar
Robert Czechowski committed
264
265
266
267
268
269
270
271
272
            let mut contest = Contest::new("directory".to_string(),
                                           "public.yaml".to_string(),
                                           "RenamedContestName".to_string(),
                                           1,
                                           true,
                                           None,
                                           None);
            contest.save(&conn);

273
            // ID: 1
Robert Czechowski's avatar
Robert Czechowski committed
274
275
276
277
278
279
280
281
            let mut contest = Contest::new("directory".to_string(),
                                           "public.yaml".to_string(),
                                           "PublicContestName".to_string(),
                                           1,
                                           true,
                                           None,
                                           None);
            let mut taskgroup = Taskgroup::new("TaksgroupName".to_string());
282
            let task = Task::new("taskdir1".to_string(), 3); // ID: 1
Robert Czechowski's avatar
Robert Czechowski committed
283
            taskgroup.tasks.push(task);
284
            let task = Task::new("taskdir2".to_string(), 4); // ID: 2
Robert Czechowski's avatar
Robert Czechowski committed
285
286
287
288
            taskgroup.tasks.push(task);
            contest.taskgroups.push(taskgroup);
            contest.save(&conn);

289
            // ID: 2
Robert Czechowski's avatar
Robert Czechowski committed
290
291
292
293
294
295
296
297
            let mut contest = Contest::new("directory".to_string(),
                                           "private.yaml".to_string(),
                                           "PrivateContestName".to_string(),
                                           1,
                                           false,
                                           None,
                                           None);
            let mut taskgroup = Taskgroup::new("TaksgroupName".to_string());
298
            let task = Task::new("taskdir1".to_string(), 3); // ID: 3
Robert Czechowski's avatar
Robert Czechowski committed
299
            taskgroup.tasks.push(task);
300
            let task = Task::new("taskdir2".to_string(), 4); // ID: 4
Robert Czechowski's avatar
Robert Czechowski committed
301
302
303
304
            taskgroup.tasks.push(task);
            contest.taskgroups.push(taskgroup);
            contest.save(&conn);

305
            // ID: 3
Robert Czechowski's avatar
Robert Czechowski committed
306
307
308
309
310
311
312
313
            let mut contest = Contest::new("directory".to_string(),
                                           "infinte.yaml".to_string(),
                                           "InfiniteContestName".to_string(),
                                           0,
                                           true,
                                           None,
                                           None);
            let mut taskgroup = Taskgroup::new("TaksgroupName".to_string());
314
            let task = Task::new("taskdir1".to_string(), 3); // ID: 5
Robert Czechowski's avatar
Robert Czechowski committed
315
            taskgroup.tasks.push(task);
316
            let task = Task::new("taskdir2".to_string(), 4); // ID: 6
Robert Czechowski's avatar
Robert Czechowski committed
317
318
319
320
            taskgroup.tasks.push(task);
            contest.taskgroups.push(taskgroup);
            contest.save(&conn);

321
            let mut config = config::read_config_from_file(Path::new("thisfileshoudnotexist"));
322
            config.port = Some(port);
323
            config.cookie_signing_secret = Some("testtesttesttesttesttesttesttest".to_string());
324
            let mut srvr = start_server(conn, config).expect(&format!("Could not start server on port {}", port));
325

326
            // Message server started
327
328
            start_tx.send(()).unwrap();

329
            // Wait for test to finish
330
331
            stop_rx.recv().unwrap();

332
            srvr.close().unwrap();
333
334
        });

335
        // Wait for server to start
336
337
        start_rx.recv().unwrap();
        thread::sleep(time::Duration::from_millis(100));
338
339

        // Run test code
340
        f();
341

342
        // Message test finished
343
344
345
        stop_tx.send(()).unwrap();
    }

346
    fn login(port: u16, client: &reqwest::Client, username: &str, password: &str) -> reqwest::Response {
347
        let params = [("username", username), ("password", password)];
Robert Czechowski's avatar
Robert Czechowski committed
348
        let resp = client.post(&format!("http://localhost:{}/login", port)).form(&params).send().unwrap();
349
        resp
350
    }
Robert Czechowski's avatar
Robert Czechowski committed
351

352
353
354
355
356
    fn login_code(port: u16, client: &reqwest::Client, code: &str) -> reqwest::Response {
        let params = [("code", code)];
        let resp = client.post(&format!("http://localhost:{}/clogin", port)).form(&params).send().unwrap();
        resp
    }
357

358
    #[test]
Robert Czechowski's avatar
Robert Czechowski committed
359
    fn start_server_and_check_requests() {
Robert Czechowski's avatar
Robert Czechowski committed
360
        start_server_and_fn(8080, None, || {
361
            let mut resp = reqwest::get("http://localhost:8080").unwrap();
362
            assert_eq!(resp.status(), StatusCode::OK);
363
364

            let content = resp.text().unwrap();
365
            assert!(content.contains("Jugendwettbewerb Informatik</h1>"));
366
            assert!(!content.contains("Error"));
Robert Czechowski's avatar
Robert Czechowski committed
367
            assert!(!content.contains("Gruppenverwaltung"));
368
369

            let mut resp = reqwest::get("http://localhost:8080/contest").unwrap();
370
            assert_eq!(resp.status(), StatusCode::OK);
371
372

            let content = resp.text().unwrap();
373
374
            assert!(content.contains("<h1>Wettbewerbe</h1>"));
            assert!(!content.contains("Error"));
Robert Czechowski's avatar
Robert Czechowski committed
375
376

            let mut resp = reqwest::get("http://localhost:8080/group").unwrap();
377
            let content = resp.text().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
378
            assert!(content.contains("<h1>Login</h1>"));
379
380
        })
    }
Daniel Brüning's avatar
Daniel Brüning committed
381

382
383
    #[test]
    fn check_login_wrong_credentials() {
Robert Czechowski's avatar
Robert Czechowski committed
384
        start_server_and_fn(8081, None, || {
385
            let client = reqwest::Client::new();
Robert Czechowski's avatar
Robert Czechowski committed
386

387
            let mut resp = login(8081, &client, "nonexistingusername", "wrongpassword");
388
            assert_eq!(resp.status(), StatusCode::OK);
389
390

            let content = resp.text().unwrap();
391
392
            assert!(content.contains("<h1>Login</h1>"));
            assert!(content.contains("Login fehlgeschlagen."));
393
            assert!(!content.contains("Error"));
Robert Czechowski's avatar
Robert Czechowski committed
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409

            let mut resp = login_code(8081, &client, "g23AgaV");
            assert_eq!(resp.status(), StatusCode::OK);

            let content = resp.text().unwrap();
            assert!(content.contains("<h1>Login</h1>"));
            assert!(content.contains("Kein gültiger Code."));
            assert!(!content.contains("Error"));

            let mut resp = login_code(8081, &client, "u9XuAbH7p");
            assert_eq!(resp.status(), StatusCode::OK);

            let content = resp.text().unwrap();
            assert!(content.contains("<h1>Login</h1>"));
            assert!(content.contains("Kein gültiger Code."));
            assert!(!content.contains("Error"));
410
        })
411
    }
412
413

    #[test]
Robert Czechowski's avatar
Robert Czechowski committed
414
    fn check_login() {
Robert Czechowski's avatar
Robert Czechowski committed
415
        start_server_and_fn(8082, Some(("testusr".to_string(), "testpw".to_string(), false)), || {
416
417
418
419
            let client = reqwest::Client::builder().cookie_store(true)
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
420

421
            let mut resp = login(8082, &client, "testusr", "testpw");
422
            assert_eq!(resp.status(), StatusCode::FOUND);
423

424
            let content = resp.text().unwrap();
425
426
            assert!(!content.contains("Error"));

427
428
429
430
            let mut set_cookie = resp.headers().get_all("Set-Cookie").iter();
            assert!(set_cookie.next().is_some());
            assert!(set_cookie.next().is_none());

Robert Czechowski's avatar
Robert Czechowski committed
431
432
433
434
            let location = resp.headers().get(reqwest::header::LOCATION).unwrap().to_str().unwrap();
            assert_eq!(location, "http://localhost:8082/");

            let mut resp = client.get(location).send().unwrap();
435
436
            assert_eq!(resp.status(), StatusCode::OK);

437
            let content = resp.text().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
438
439
440
441
            assert!(!content.contains("Error"));
            assert!(!content.contains("Gruppenverwaltung"));
            assert!(content.contains("Eingeloggt als <em>testusr</em>"));
            assert!(content.contains("Jugendwettbewerb Informatik</h1>"));
442
443
444
        })
    }

445
    #[test]
Robert Czechowski's avatar
Robert Czechowski committed
446
    fn check_logout() {
Robert Czechowski's avatar
Robert Czechowski committed
447
        start_server_and_fn(8083, Some(("testusr".to_string(), "testpw".to_string(), false)), || {
448
449
450
451
            let client = reqwest::Client::builder().cookie_store(true)
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
452

453
            let resp = login(8083, &client, "testusr", "testpw");
454
455
456
457
458
459
460
            assert_eq!(resp.status(), StatusCode::FOUND);

            let resp = client.get("http://localhost:8083/logout").send().unwrap();
            assert_eq!(resp.status(), StatusCode::FOUND);

            let mut resp = client.get("http://localhost:8083").send().unwrap();
            assert_eq!(resp.status(), StatusCode::OK);
461
462

            let content = resp.text().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
463
464
465
466
            assert!(content.contains("Benutzername"));
            assert!(content.contains("Passwort"));
            assert!(content.contains("Gruppencode / Teilnahmecode"));
            assert!(content.contains("Jugendwettbewerb Informatik</h1>"));
467
468
469
        })
    }

470
471
    #[test]
    fn check_group_creation_and_group_code_login() {
Robert Czechowski's avatar
Robert Czechowski committed
472
        start_server_and_fn(8084, Some(("testusr".to_string(), "testpw".to_string(), true)), || {
473
474
475
476
477
            let client = reqwest::Client::builder().cookie_store(true)
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();

478
            let resp = login(8084, &client, "testusr", "testpw");
479
            assert_eq!(resp.status(), StatusCode::FOUND);
480

481
482
            let mut resp = client.get("http://localhost:8084").send().unwrap();
            assert_eq!(resp.status(), StatusCode::OK);
483
484

            let content = resp.text().unwrap();
485
486
            assert!(content.contains("[Lehrer]"));
            assert!(content.contains("Gruppenverwaltung"));
487
488
489

            let mut resp = client.get("http://localhost:8084/group/").send().unwrap();
            assert_eq!(resp.status(), StatusCode::OK);
490
491

            let content = resp.text().unwrap();
492
493
            assert!(content.contains("Gruppe anlegen"));

494
            let params = [("name", "WrongGroupname"), ("tag", "WrongMarker"), ("csrf_token", "76CfTPJaoz")];
495
496
            let resp = client.post("http://localhost:8084/group/").form(&params).send().unwrap();
            assert_eq!(resp.status(), StatusCode::FORBIDDEN);
497

498
499
500
            let pos = content.find("type=\"hidden\" name=\"csrf_token\" value=\"").expect("CSRF-Token not found");
            let csrf = &content[pos + 39..pos + 49];
            let params = [("name", "Groupname"), ("tag", "Marker"), ("csrf_token", csrf)];
501
502
            let resp = client.post("http://localhost:8084/group/").form(&params).send().unwrap();
            assert_eq!(resp.status(), StatusCode::FOUND);
503
504
505
506

            let mut resp = client.get("http://localhost:8084/group/").send().unwrap();
            let content = resp.text().unwrap();
            assert!(!content.contains("WrongGroupname"));
Robert Czechowski's avatar
Robert Czechowski committed
507

508
509
510
511
512
            let pos = content.find("<td><a href=\"/group/1\">Groupname</a></td>").expect("Group not found");
            let groupcode = &content[pos + 58..pos + 65];

            // New client to test group code login
            let client = reqwest::Client::builder().cookie_store(true)
Robert Czechowski's avatar
Robert Czechowski committed
513
514
515
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();
516
517
518
519

            let resp = login_code(8084, &client, groupcode);
            assert_eq!(resp.status(), StatusCode::FOUND);

Robert Czechowski's avatar
Robert Czechowski committed
520
521
522
523
524
525
526
527
            let mut set_cookie = resp.headers().get_all("Set-Cookie").iter();
            assert!(set_cookie.next().is_some());
            assert!(set_cookie.next().is_none());

            let location = resp.headers().get(reqwest::header::LOCATION).unwrap().to_str().unwrap();
            assert_eq!(location, "http://localhost:8084/profile");

            let mut resp = client.get(location).send().unwrap();
528
529
530
531
532
533
534
            let content = resp.text().unwrap();

            let pos = content.find("<p>Login-Code: ").expect("Logincode not found");
            let logincode = &content[pos + 15..pos + 24];

            // New client to test login code login
            let client = reqwest::Client::builder().cookie_store(true)
Robert Czechowski's avatar
Robert Czechowski committed
535
536
537
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();
538
539
540
541

            let resp = login_code(8084, &client, logincode);
            assert_eq!(resp.status(), StatusCode::FOUND);

Robert Czechowski's avatar
Robert Czechowski committed
542
543
544
545
            let location = resp.headers().get(reqwest::header::LOCATION).unwrap().to_str().unwrap();
            assert_eq!(location, "http://localhost:8084/");

            let mut resp = client.get(location).send().unwrap();
546
547
            let content = resp.text().unwrap();
            assert!(content.contains("Eingeloggt als <em></em>"));
548
549
        })
    }
Robert Czechowski's avatar
Robert Czechowski committed
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582

    #[test]
    fn check_contest_start() {
        start_server_and_fn(8085, Some(("testusr".to_string(), "testpw".to_string(), false)), || {
            let client = reqwest::Client::builder().cookie_store(true)
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();

            let resp = login(8085, &client, "testusr", "testpw");
            assert_eq!(resp.status(), StatusCode::FOUND);

            let mut resp = client.get("http://localhost:8085/contest/").send().unwrap();
            assert_eq!(resp.status(), StatusCode::OK);

            let content = resp.text().unwrap();
            assert!(content.contains("PublicContestName"));
            assert!(content.contains("InfiniteContestName"));
            //assert!(content.contains("PrivateContestName"));
            assert!(!content.contains("WrongContestName"));
            assert!(!content.contains("RenamedContestName"));
            assert!(content.contains("<a href=\"/contest/1\">PublicContestName</a>"));

            let mut resp = client.get("http://localhost:8085/contest/1").send().unwrap();
            assert_eq!(resp.status(), StatusCode::OK);

            let content = resp.text().unwrap();
            assert!(content.contains("PublicContestName"));
            assert!(!content.contains("InfiniteContestName"));
            assert!(!content.contains("PrivateContestName"));
            assert!(!content.contains("WrongContestName"));
            assert!(!content.contains("RenamedContestName"));

583
            let params = [("csrf_token", "76CfTPJaoz")];
Robert Czechowski's avatar
Robert Czechowski committed
584
585
586
            let resp = client.post("http://localhost:8085/contest/1").form(&params).send().unwrap();
            assert_eq!(resp.status(), StatusCode::FORBIDDEN);

587
588
589
            let pos = content.find("type=\"hidden\" name=\"csrf_token\" value=\"").expect("CSRF-Token not found");
            let csrf = &content[pos + 39..pos + 49];
            let params = [("csrf_token", csrf)];
Robert Czechowski's avatar
Robert Czechowski committed
590
591
            let resp = client.post("http://localhost:8085/contest/1").form(&params).send().unwrap();
            assert_eq!(resp.status(), StatusCode::FOUND);
Robert Czechowski's avatar
Robert Czechowski committed
592
593
594
595
596
597
598

            let mut resp = client.get("http://localhost:8085/contest/1").send().unwrap();
            assert_eq!(resp.status(), StatusCode::OK);

            let content = resp.text().unwrap();
            assert!(content.contains("<a href=\"/task/1\">☆☆☆</a></li>"));
            assert!(content.contains("<a href=\"/task/2\">☆☆☆☆</a></li>"));
599
600
601
602
603
        })
    }

    #[test]
    fn check_task_load_save() {
604
        start_server_and_fn(8086, None, || {
605
606
607
608
609
            let client = reqwest::Client::builder().cookie_store(true)
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();

610
            let resp = client.get("http://localhost:8086/contest/3").send().unwrap();
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
            assert_eq!(resp.status(), StatusCode::OK);

            let mut resp = client.get("http://localhost:8086/task/5").send().unwrap();
            assert_eq!(resp.status(), StatusCode::OK);

            let content = resp.text().unwrap();
            let pos = content.find("#taskid=5&csrftoken=").expect("CSRF-Token not found");
            let csrf = &content[pos + 20..pos + 30];

            let mut resp = client.get("http://localhost:8086/load/5").send().unwrap();
            assert_eq!(resp.status(), StatusCode::OK);

            let content = resp.text().unwrap();
            assert_eq!(content, "{}");

            let params = [("data", "WrongData"), ("grade", "1"), ("csrf_token", "FNQU4QsEMY")];
            let resp = client.post("http://localhost:8086/save/5").form(&params).send().unwrap();
            assert_eq!(resp.status(), StatusCode::FORBIDDEN);

            // Check that the illegitimate request did not actually change anything
            let mut resp = client.get("http://localhost:8086/load/5").send().unwrap();
            assert_eq!(resp.status(), StatusCode::OK);

            let content = resp.text().unwrap();
            assert_eq!(content, "{}");

            let mut resp = client.get("http://localhost:8086/contest/3").send().unwrap();
            assert_eq!(resp.status(), StatusCode::OK);

            let content = resp.text().unwrap();
            assert!(content.contains("<a href=\"/task/5\">☆☆☆</a></li>"));
            assert!(content.contains("<a href=\"/task/6\">☆☆☆☆</a></li>"));

            let params = [("data", "SomeData"), ("grade", "2"), ("csrf_token", csrf)];
            let mut resp = client.post("http://localhost:8086/save/5").form(&params).send().unwrap();
            assert_eq!(resp.status(), StatusCode::OK);

            let content = resp.text().unwrap();
            assert_eq!(content, "{}");

            let mut resp = client.get("http://localhost:8086/load/5").send().unwrap();
            assert_eq!(resp.status(), StatusCode::OK);

            let content = resp.text().unwrap();
            assert_eq!(content, "SomeData");

            let mut resp = client.get("http://localhost:8086/contest/3").send().unwrap();
            assert_eq!(resp.status(), StatusCode::OK);

            let content = resp.text().unwrap();
            assert!(content.contains("<a href=\"/task/5\">★★☆</a></li>"));
            assert!(content.contains("<a href=\"/task/6\">☆☆☆☆</a></li>"));
        })
    }

    #[test]
    fn check_task_load_save_logged_in() {
        start_server_and_fn(8087, Some(("testusr".to_string(), "testpw".to_string(), false)), || {
            let client = reqwest::Client::builder().cookie_store(true)
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();

            let resp = login(8087, &client, "testusr", "testpw");
675
            assert_eq!(resp.status(), StatusCode::FOUND);
Robert Czechowski's avatar
Robert Czechowski committed
676

677
            let mut resp = client.get("http://localhost:8087/contest/1").send().unwrap();
678
679
680
681
682
683
            assert_eq!(resp.status(), StatusCode::OK);

            let content = resp.text().unwrap();
            let pos = content.find("type=\"hidden\" name=\"csrf_token\" value=\"").expect("CSRF-Token not found");
            let csrf = &content[pos + 39..pos + 49];
            let params = [("csrf_token", csrf)];
684
            let resp = client.post("http://localhost:8087/contest/1").form(&params).send().unwrap();
685
686
            assert_eq!(resp.status(), StatusCode::FOUND);

687
            let mut resp = client.get("http://localhost:8087/task/1").send().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
688
689
690
691
692
693
            assert_eq!(resp.status(), StatusCode::OK);

            let content = resp.text().unwrap();
            let pos = content.find("#taskid=1&csrftoken=").expect("CSRF-Token not found");
            let csrf = &content[pos + 20..pos + 30];

694
            let mut resp = client.get("http://localhost:8087/load/1").send().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
695
            assert_eq!(resp.status(), StatusCode::OK);
696

Robert Czechowski's avatar
Robert Czechowski committed
697
698
699
            let content = resp.text().unwrap();
            assert_eq!(content, "{}");

700
            let params = [("data", "WrongData"), ("grade", "1"), ("csrf_token", "FNQU4QsEMY")];
701
            let resp = client.post("http://localhost:8087/save/1").form(&params).send().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
702
703
            assert_eq!(resp.status(), StatusCode::FORBIDDEN);

704
            // Check that the illigal request did not actually change anything
705
            let mut resp = client.get("http://localhost:8087/load/1").send().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
706
            assert_eq!(resp.status(), StatusCode::OK);
707

Robert Czechowski's avatar
Robert Czechowski committed
708
709
            let content = resp.text().unwrap();
            assert_eq!(content, "{}");
710

711
            let mut resp = client.get("http://localhost:8087/contest/1").send().unwrap();
712
713
714
715
716
            assert_eq!(resp.status(), StatusCode::OK);

            let content = resp.text().unwrap();
            assert!(content.contains("<a href=\"/task/1\">☆☆☆</a></li>"));
            assert!(content.contains("<a href=\"/task/2\">☆☆☆☆</a></li>"));
717
718

            let params = [("data", "SomeData"), ("grade", "2"), ("csrf_token", csrf)];
719
            let mut resp = client.post("http://localhost:8087/save/1").form(&params).send().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
720
721
722
723
724
            assert_eq!(resp.status(), StatusCode::OK);

            let content = resp.text().unwrap();
            assert_eq!(content, "{}");

725
            let mut resp = client.get("http://localhost:8087/load/1").send().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
726
            assert_eq!(resp.status(), StatusCode::OK);
727

Robert Czechowski's avatar
Robert Czechowski committed
728
729
            let content = resp.text().unwrap();
            assert_eq!(content, "SomeData");
730

731
            let mut resp = client.get("http://localhost:8087/contest/1").send().unwrap();
732
733
734
735
736
            assert_eq!(resp.status(), StatusCode::OK);

            let content = resp.text().unwrap();
            assert!(content.contains("<a href=\"/task/1\">★★☆</a></li>"));
            assert!(content.contains("<a href=\"/task/2\">☆☆☆☆</a></li>"));
Robert Czechowski's avatar
Robert Czechowski committed
737
738
        })
    }
739
}