main.rs 24.9 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
extern crate persistent;
15
#[cfg(feature = "postgres")]
16
extern crate postgres;
Robert Czechowski's avatar
Robert Czechowski committed
17
extern crate rand;
18
extern crate reqwest;
19
#[cfg(feature = "rusqlite")]
Robert Czechowski's avatar
Robert Czechowski committed
20
21
extern crate rusqlite;
extern crate serde_json;
22
extern crate serde_yaml;
Robert Czechowski's avatar
Robert Czechowski committed
23
24
25
26
extern crate staticfile;
extern crate structopt;
extern crate time;
extern crate urlencoded;
27
#[cfg(feature = "webbrowser")]
28
extern crate webbrowser;
29

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

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

43
use db_conn::{MedalConnection, MedalObject};
44
use db_objects::*;
45
use helpers::SetPassword; // TODO: Refactor, so we don't need to take this from there!
Robert Czechowski's avatar
Robert Czechowski committed
46
47
use webfw_iron::start_server;

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

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

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

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

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

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

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

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

    contests
}

93
94
95
96
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
97
98
99
100
101
102
103
    let v = get_all_contest_info("tasks/");

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

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

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

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

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

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

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

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

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

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

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

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

Robert Czechowski's avatar
Robert Czechowski committed
187
fn main() {
188
    let opt = config::Opt::from_args();
189
    //println!("{:?}", opt); // Show in different debug level?
Daniel Brüning's avatar
Daniel Brüning committed
190

191
    let mut config = config::read_config_from_file(&opt.configfile);
192

Robert Czechowski's avatar
Robert Czechowski committed
193
194
195
    if opt.databasefile.is_some() {
        config.database_file = opt.databasefile;
    }
196
197
198
    if config.database_file.is_none() {
        config.database_file = Some(Path::new("medal.db").to_owned())
    }
199
200
201
    if opt.databaseurl.is_some() {
        config.database_url = opt.databaseurl;
    }
Robert Czechowski's avatar
Robert Czechowski committed
202
203
204
    if opt.port.is_some() {
        config.port = opt.port;
    }
205
206
207
    if opt.nocontestscan {
        config.no_contest_scan = Some(true);
    }
208
209
210
    if opt.openbrowser {
        config.open_browser = Some(true)
    }
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
319
320
            config.port = Some(port);
            let srvr = start_server(conn, config);

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

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

            srvr.unwrap().close().unwrap();
        });

330
        // Wait for server to start
331
332
333
        start_rx.recv().unwrap();
        thread::sleep(time::Duration::from_millis(100));
        f();
334
        // Message test finished
335
336
337
        stop_tx.send(()).unwrap();
    }

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

344
345
346
347
348
    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
    }
349

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

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

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

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

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

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

379
            let mut resp = login(8081, &client, "nonexistingusername", "wrongpassword");
380
            assert_eq!(resp.status(), StatusCode::OK);
381
382

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

            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"));
402
        })
403
    }
404
405

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

413
            let mut resp = login(8082, &client, "testusr", "testpw");
414
            assert_eq!(resp.status(), StatusCode::FOUND);
415

416
            let content = resp.text().unwrap();
417
418
            assert!(!content.contains("Error"));

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

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

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

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

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

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

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

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

            let content = resp.text().unwrap();
477
478
            assert!(content.contains("[Lehrer]"));
            assert!(content.contains("Gruppenverwaltung"));
479
480
481

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

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

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

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

            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
499

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

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

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

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

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

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

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

579
580
581
            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
582
583
            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
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600

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

            let mut resp = client.get("http://localhost:8085/task/1").send().unwrap();
            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];

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

Robert Czechowski's avatar
Robert Czechowski committed
602
603
604
            let content = resp.text().unwrap();
            assert_eq!(content, "{}");

605
            let params = [("data", "WrongData"), ("grade", "1"), ("csrf_token", "FNQU4QsEMY")];
606
            let resp = client.post("http://localhost:8085/save/1").form(&params).send().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
607
608
609
610
            assert_eq!(resp.status(), StatusCode::FORBIDDEN);

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

Robert Czechowski's avatar
Robert Czechowski committed
612
613
            let content = resp.text().unwrap();
            assert_eq!(content, "{}");
614
615
616
617
618
619
620

            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>"));
621
622

            let params = [("data", "SomeData"), ("grade", "2"), ("csrf_token", csrf)];
Robert Czechowski's avatar
Robert Czechowski committed
623
624
625
626
627
628
629
630
            let mut resp = client.post("http://localhost:8085/save/1").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:8085/load/1").send().unwrap();
            assert_eq!(resp.status(), StatusCode::OK);
631

Robert Czechowski's avatar
Robert Czechowski committed
632
633
            let content = resp.text().unwrap();
            assert_eq!(content, "SomeData");
634
635
636
637
638
639
640

            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>"));
Robert Czechowski's avatar
Robert Czechowski committed
641
642
        })
    }
643
}