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>
{
98
    conn.reset_all_contest_visibilities();
Robert Czechowski's avatar
Robert Czechowski committed
99
100
101
102
103
104
105
    let v = get_all_contest_info("tasks/");

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

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

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

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

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

143
144
145
146
147
148
149
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) {
150
        print!("Scanning for contests …");
151
        refresh_all_contests(&mut conn);
152
        println!(" Done")
153
154
155
156
157
    }

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

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

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

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

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

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

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

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

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

197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
    #[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);
212

213
214
215
216
217
218
219
220
221
222
    #[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;
        }
223
    }
224
225
226
227
228
229
230
231
232
233
234
235
236
237

    #[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.");
238
}
239
240
241
242

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

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

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

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

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

274
            // ID: 1
Robert Czechowski's avatar
Robert Czechowski committed
275
276
277
278
279
280
281
282
            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());
283
            let task = Task::new("taskdir1".to_string(), 3); // ID: 1
Robert Czechowski's avatar
Robert Czechowski committed
284
            taskgroup.tasks.push(task);
285
            let task = Task::new("taskdir2".to_string(), 4); // ID: 2
Robert Czechowski's avatar
Robert Czechowski committed
286
287
288
289
            taskgroup.tasks.push(task);
            contest.taskgroups.push(taskgroup);
            contest.save(&conn);

290
            // ID: 2
Robert Czechowski's avatar
Robert Czechowski committed
291
292
293
294
295
296
297
298
            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());
299
            let task = Task::new("taskdir1".to_string(), 3); // ID: 3
Robert Czechowski's avatar
Robert Czechowski committed
300
            taskgroup.tasks.push(task);
301
            let task = Task::new("taskdir2".to_string(), 4); // ID: 4
Robert Czechowski's avatar
Robert Czechowski committed
302
303
304
305
            taskgroup.tasks.push(task);
            contest.taskgroups.push(taskgroup);
            contest.save(&conn);

306
            // ID: 3
Robert Czechowski's avatar
Robert Czechowski committed
307
308
309
310
311
312
313
314
            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());
315
            let task = Task::new("taskdir1".to_string(), 3); // ID: 5
Robert Czechowski's avatar
Robert Czechowski committed
316
            taskgroup.tasks.push(task);
317
            let task = Task::new("taskdir2".to_string(), 4); // ID: 6
Robert Czechowski's avatar
Robert Czechowski committed
318
319
320
321
            taskgroup.tasks.push(task);
            contest.taskgroups.push(taskgroup);
            contest.save(&conn);

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

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

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

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

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

        // Run test code
341
        f();
342

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

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

353
354
355
356
357
    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
    }
358

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

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

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

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

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

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

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

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

            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"));
411
        })
412
    }
413
414

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

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

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

428
429
430
431
            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
432
433
434
435
            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();
436
437
            assert_eq!(resp.status(), StatusCode::OK);

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

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

454
            let resp = login(8083, &client, "testusr", "testpw");
455
456
457
458
459
460
461
            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);
462
463

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

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

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

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

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

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

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

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

499
500
501
            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)];
502
503
            let resp = client.post("http://localhost:8084/group/").form(&params).send().unwrap();
            assert_eq!(resp.status(), StatusCode::FOUND);
504
505
506
507

            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
508

509
510
511
512
513
            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
514
515
516
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();
517
518
519
520

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

Robert Czechowski's avatar
Robert Czechowski committed
521
522
523
524
525
526
527
528
            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();
529
530
531
532
533
534
535
            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
536
537
538
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();
539
540
541
542

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

Robert Czechowski's avatar
Robert Czechowski committed
543
544
545
546
            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();
547
548
            let content = resp.text().unwrap();
            assert!(content.contains("Eingeloggt als <em></em>"));
549
550
        })
    }
Robert Czechowski's avatar
Robert Czechowski committed
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
583

    #[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"));

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

588
589
590
            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
591
592
            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
593
594
595
596
597
598
599

            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>"));
600
601
602
603
604
        })
    }

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

611
            let resp = client.get("http://localhost:8086/contest/3").send().unwrap();
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
675
            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");
676
            assert_eq!(resp.status(), StatusCode::FOUND);
Robert Czechowski's avatar
Robert Czechowski committed
677

678
            let mut resp = client.get("http://localhost:8087/contest/1").send().unwrap();
679
680
681
682
683
684
            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)];
685
            let resp = client.post("http://localhost:8087/contest/1").form(&params).send().unwrap();
686
687
            assert_eq!(resp.status(), StatusCode::FOUND);

688
            let mut resp = client.get("http://localhost:8087/task/1").send().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
689
690
691
692
693
694
            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];

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

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

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

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

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

712
            let mut resp = client.get("http://localhost:8087/contest/1").send().unwrap();
713
714
715
716
717
            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>"));
718
719

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

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

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

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

732
            let mut resp = client.get("http://localhost:8087/contest/1").send().unwrap();
733
734
735
736
737
            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
738
739
        })
    }
740
}