main.rs 26.4 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
            }

Robert Czechowski's avatar
Robert Czechowski committed
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
            let mut contest = Contest::new("directory".to_string(),
                                           "public.yaml".to_string(),
                                           "RenamedContestName".to_string(),
                                           1,
                                           true,
                                           None,
                                           None);
            contest.save(&conn);

            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());
            let task = Task::new("taskdir1".to_string(), 3);
            taskgroup.tasks.push(task);
            let task = Task::new("taskdir2".to_string(), 4);
            taskgroup.tasks.push(task);
            contest.taskgroups.push(taskgroup);
            contest.save(&conn);

            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());
            let task = Task::new("taskdir1".to_string(), 3);
            taskgroup.tasks.push(task);
            let task = Task::new("taskdir2".to_string(), 4);
            taskgroup.tasks.push(task);
            contest.taskgroups.push(taskgroup);
            contest.save(&conn);

            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());
            let task = Task::new("taskdir1".to_string(), 3);
            taskgroup.tasks.push(task);
            let task = Task::new("taskdir2".to_string(), 4);
            taskgroup.tasks.push(task);
            contest.taskgroups.push(taskgroup);
            contest.save(&conn);

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

322
            // Message server started
323
324
            start_tx.send(()).unwrap();

325
            // Wait for test to finish
326
327
            stop_rx.recv().unwrap();

328
            srvr.close().unwrap();
329
330
        });

331
        // Wait for server to start
332
333
        start_rx.recv().unwrap();
        thread::sleep(time::Duration::from_millis(100));
334
335

        // Run test code
336
        f();
337

338
        // Message test finished
339
340
341
        stop_tx.send(()).unwrap();
    }

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

348
349
350
351
352
    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
    }
353

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

            let content = resp.text().unwrap();
361
            assert!(content.contains("Jugendwettbewerb Informatik</h1>"));
362
            assert!(!content.contains("Error"));
Robert Czechowski's avatar
Robert Czechowski committed
363
            assert!(!content.contains("Gruppenverwaltung"));
364
365

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

            let content = resp.text().unwrap();
369
370
            assert!(content.contains("<h1>Wettbewerbe</h1>"));
            assert!(!content.contains("Error"));
Robert Czechowski's avatar
Robert Czechowski committed
371
372

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

378
379
    #[test]
    fn check_login_wrong_credentials() {
Robert Czechowski's avatar
Robert Czechowski committed
380
        start_server_and_fn(8081, None, || {
381
            let client = reqwest::Client::new();
Robert Czechowski's avatar
Robert Czechowski committed
382

383
            let mut resp = login(8081, &client, "nonexistingusername", "wrongpassword");
384
            assert_eq!(resp.status(), StatusCode::OK);
385
386

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

            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"));
406
        })
407
    }
408
409

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

417
            let mut resp = login(8082, &client, "testusr", "testpw");
418
            assert_eq!(resp.status(), StatusCode::FOUND);
419

420
            let content = resp.text().unwrap();
421
422
            assert!(!content.contains("Error"));

423
424
425
426
            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
427
428
429
430
            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();
431
432
            assert_eq!(resp.status(), StatusCode::OK);

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

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

449
            let resp = login(8083, &client, "testusr", "testpw");
450
451
452
453
454
455
456
            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);
457
458

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

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

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

477
478
            let mut resp = client.get("http://localhost:8084").send().unwrap();
            assert_eq!(resp.status(), StatusCode::OK);
479
480

            let content = resp.text().unwrap();
481
482
            assert!(content.contains("[Lehrer]"));
            assert!(content.contains("Gruppenverwaltung"));
483
484
485

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

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

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

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

            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
503

504
505
506
507
508
            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
509
510
511
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();
512
513
514
515

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

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

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

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

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

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

583
584
585
            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
586
587
            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
588
589
590
591
592
593
594

            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>"));
595
596
597
598
599
600
601
602
603
604
605
606
607
        })
    }

    #[test]
    fn check_task_load_save() {
        start_server_and_fn(8086, 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(8086, &client, "testusr", "testpw");
            assert_eq!(resp.status(), StatusCode::FOUND);
Robert Czechowski's avatar
Robert Czechowski committed
608

609
610
611
612
613
614
615
616
617
618
619
            let mut resp = client.get("http://localhost:8086/contest/1").send().unwrap();
            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)];
            let resp = client.post("http://localhost:8086/contest/1").form(&params).send().unwrap();
            assert_eq!(resp.status(), StatusCode::FOUND);

            let mut resp = client.get("http://localhost:8086/task/1").send().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
620
621
622
623
624
625
            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];

626
            let mut resp = client.get("http://localhost:8086/load/1").send().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
627
            assert_eq!(resp.status(), StatusCode::OK);
628

Robert Czechowski's avatar
Robert Czechowski committed
629
630
631
            let content = resp.text().unwrap();
            assert_eq!(content, "{}");

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

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

Robert Czechowski's avatar
Robert Czechowski committed
640
641
            let content = resp.text().unwrap();
            assert_eq!(content, "{}");
642

643
            let mut resp = client.get("http://localhost:8086/contest/1").send().unwrap();
644
645
646
647
648
            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>"));
649
650

            let params = [("data", "SomeData"), ("grade", "2"), ("csrf_token", csrf)];
651
            let mut resp = client.post("http://localhost:8086/save/1").form(&params).send().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
652
653
654
655
656
            assert_eq!(resp.status(), StatusCode::OK);

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

657
            let mut resp = client.get("http://localhost:8086/load/1").send().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
658
            assert_eq!(resp.status(), StatusCode::OK);
659

Robert Czechowski's avatar
Robert Czechowski committed
660
661
            let content = resp.text().unwrap();
            assert_eq!(content, "SomeData");
662

663
            let mut resp = client.get("http://localhost:8086/contest/1").send().unwrap();
664
665
666
667
668
            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
669
670
        })
    }
671
}