main.rs 26.3 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
            let mut srvr = start_server(conn, config).expect(&format!("Could not start server on port {}", port));
320

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

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

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

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

        // Run test code
335
        f();
336

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            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
502

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

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

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

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

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

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

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

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

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

    #[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
607

608
609
610
611
612
613
614
615
616
617
618
            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
619
620
621
622
623
624
            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];

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

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

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

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

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

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

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

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

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

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

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