main.rs 34.6 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*  medal                                                                                                            *\
 *  Copyright (C) 2020  Bundesweite Informatikwettbewerbe                                                            *
 *                                                                                                                   *
 *  This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero        *
 *  General Public License as published  by the Free Software Foundation, either version 3 of the License, or (at    *
 *  your option) any later version.                                                                                  *
 *                                                                                                                   *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the       *
 *  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public      *
 *  License for more details.                                                                                        *
 *                                                                                                                   *
 *  You should have received a copy of the GNU Affero General Public License along with this program.  If not, see   *
\*  <http://www.gnu.org/licenses/>.                                                                                  */

15
16
#![cfg_attr(feature = "strict", deny(warnings))]

Robert Czechowski's avatar
Robert Czechowski committed
17
18
19
20
21
22
23
#[macro_use]
extern crate iron;
#[macro_use]
extern crate router;
#[macro_use]
extern crate serde_derive;

24
extern crate csv;
Robert Czechowski's avatar
Robert Czechowski committed
25
extern crate handlebars_iron;
Robert Czechowski's avatar
Robert Czechowski committed
26
27
extern crate iron_sessionstorage;
extern crate mount;
28
extern crate params;
Robert Czechowski's avatar
Robert Czechowski committed
29
30
extern crate persistent;
extern crate rand;
31
extern crate reqwest;
Robert Czechowski's avatar
Robert Czechowski committed
32
extern crate serde_json;
33
extern crate serde_yaml;
Robert Czechowski's avatar
Robert Czechowski committed
34
35
36
37
extern crate staticfile;
extern crate structopt;
extern crate time;
extern crate urlencoded;
38
39
40

#[cfg(feature = "postgres")]
extern crate postgres;
41
#[cfg(feature = "rusqlite")]
42
extern crate rusqlite;
43
#[cfg(feature = "webbrowser")]
44
extern crate webbrowser;
45

46
47
pub mod config;
pub mod contestreader_yaml;
48
pub mod core;
49
pub mod db_conn;
50
pub mod helpers;
51
52
53
pub mod oauth_provider;

mod db_apply_migrations;
54
mod db_conn_postgres;
55
mod db_conn_sqlite_new;
56
mod db_objects;
57
58
mod webfw_iron;

59
use db_conn::{MedalConnection, MedalObject};
60
use db_objects::*;
61
use helpers::SetPassword;
Robert Czechowski's avatar
Robert Czechowski committed
62
63
use webfw_iron::start_server;

64
use config::Config;
65
66
use structopt::StructOpt;

67
use std::path::{Path, PathBuf};
68

69
fn read_contest(p: &PathBuf) -> Option<Contest> {
70
71
    use std::fs::File;
    use std::io::Read;
72

73
74
75
    let mut file = File::open(p).unwrap();
    let mut contents = String::new();
    file.read_to_string(&mut contents).unwrap();
76

77
    contestreader_yaml::parse_yaml(&contents,
78
79
                                   p.file_name().to_owned()?.to_str()?,
                                   &format!("{}/", p.parent().unwrap().to_str()?))
Robert Czechowski's avatar
Robert Czechowski committed
80
81
82
}

fn get_all_contest_info(task_dir: &str) -> Vec<Contest> {
83
84
    fn walk_me_recursively(p: &PathBuf, contests: &mut Vec<Contest>) {
        if let Ok(paths) = std::fs::read_dir(p) {
85
            for path in paths {
Robert Czechowski's avatar
Robert Czechowski committed
86
87
                let p = path.unwrap().path();
                walk_me_recursively(&p, contests);
88
            }
Robert Czechowski's avatar
Robert Czechowski committed
89
        }
90

91
        if p.file_name().unwrap().to_string_lossy().to_string().ends_with(".yaml") {
92
            read_contest(p).map(|contest| contests.push(contest));
93
        };
Robert Czechowski's avatar
Robert Czechowski committed
94
95
96
    };

    let mut contests = Vec::new();
97
    match std::fs::read_dir(task_dir) {
Robert Czechowski's avatar
Robert Czechowski committed
98
        Err(why) => println!("Error opening tasks directory! {:?}", why.kind()),
Robert Czechowski's avatar
Robert Czechowski committed
99
100
101
102
103
        Ok(paths) => {
            for path in paths {
                walk_me_recursively(&path.unwrap().path(), &mut contests);
            }
        }
Robert Czechowski's avatar
Robert Czechowski committed
104
105
106
107
108
    };

    contests
}

109
110
111
112
fn refresh_all_contests<C>(conn: &mut C)
    where C: MedalConnection,
          db_objects::Contest: db_conn::MedalObject<C>
{
113
    conn.reset_all_contest_visibilities();
114
115
    conn.reset_all_taskgroup_visibilities();

Robert Czechowski's avatar
Robert Czechowski committed
116
117
118
119
120
121
122
    let v = get_all_contest_info("tasks/");

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

123
124
fn add_admin_user<C>(conn: &mut C, resetpw: bool)
    where C: MedalConnection {
125
126
127
    let mut admin = match conn.get_user_by_id(1) {
        None => {
            print!("New Database. Creating new admin user with credentials 'admin':");
128
            conn.new_session("")
Robert Czechowski's avatar
Robert Czechowski committed
129
        }
130
131
        Some(user) => {
            if !resetpw {
Robert Czechowski's avatar
Robert Czechowski committed
132
                return;
133
            }
134
135
136
137
138
            print!("Request to reset admin password. Set credentials 'admin':");
            user
        }
    };

Robert Czechowski's avatar
Robert Czechowski committed
139
    use rand::{distributions::Alphanumeric, thread_rng, Rng};
140
141

    let password: String = thread_rng().sample_iter(&Alphanumeric)
Robert Czechowski's avatar
Robert Czechowski committed
142
143
144
145
146
147
                                       .filter(|x| {
                                           let x = *x;
                                           !(x == 'l' || x == 'I' || x == '1' || x == 'O' || x == 'o' || x == '0')
                                       })
                                       .take(8)
                                       .collect();
148
149
150
    print!("'{}', ", &password);

    let logincode: String = thread_rng().sample_iter(&Alphanumeric)
151
152
153
154
155
156
                                        .filter(|x| {
                                            let x = *x;
                                            !(x == 'l' || x == 'I' || x == '1' || x == 'O' || x == 'o' || x == '0')
                                        })
                                        .take(8)
                                        .collect();
157
158
    let logincode = format!("a{}", logincode);
    print!(" logincode:'{}' …", &logincode);
159
160

    admin.username = Some("admin".into());
161
    admin.logincode = Some(logincode);
162
    match admin.set_password(&password) {
163
        None => println!(" FAILED! (Password hashing error)"),
164
165
        _ => {
            conn.save_session(admin);
166
            println!(" Done");
167
        }
168
169
170
    }
}

171
172
173
174
175
176
177
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) {
178
        print!("Scanning for contests …");
179
        refresh_all_contests(&mut conn);
180
        println!(" Done")
181
182
183
184
185
    }

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

186
        #[cfg(feature = "webbrowser")]
187
        let self_url = config.self_url.clone();
188
        #[cfg(feature = "webbrowser")]
189
190
        let open_browser = config.open_browser;

191
        match start_server(conn, config) {
192
193
            Ok(_) => {
                println!("Server started");
194

195
196
197
198
199
                #[cfg(feature = "webbrowser")]
                {
                    if let (Some(self_url), Some(true)) = (self_url, open_browser) {
                        open_browser_window(&self_url);
                    }
200
                }
201
            }
202
203
            Err(_) => println!("Error on server start …"),
        };
204

205
206
207
208
        println!("Could not run server. Is the port already in use?");
    }
}

209
#[cfg(feature = "webbrowser")]
210
211
212
fn open_browser_window(self_url: &str) {
    match webbrowser::open(&self_url) {
        Ok(_) => (),
213
        Err(e) => println!("Error while opening webbrowser: {:?}", e),
214
215
216
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
217
fn main() {
218
    let opt = config::Opt::from_args();
219
220
221

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

223
    let mut config = config::read_config_from_file(&opt.configfile);
224

225
226
227
228
229
230
    #[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));
231
    opt.teacherpage.map(|x| config.teacher_page = Some(x));
232
233
234
    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 };
235
    config.disable_results_page = if opt.disableresultspage { Some(true) } else { config.disable_results_page };
236
237
238
239
240
241

    // 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);
242

243
244
245
    #[cfg(feature = "postgres")]
    {
        if let Some(url) = config.database_url.clone() {
246
            #[cfg(feature = "debug")]
247
            print!("Using database {} … ", &url);
248
249
250
251
252
            #[cfg(not(feature = "debug"))]{
                let (begin_middle, end) = url.split_at(url.find('@').unwrap_or(0));
                let (begin, _middle) = begin_middle.split_at(begin_middle.rfind(':').unwrap_or(0));
                print!("Using database {}:***{} … ", begin, end);
            }
253
254
255
256
257
258
            let conn = postgres::Connection::connect(url, postgres::TlsMode::None).unwrap();
            println!("Connected");

            prepare_and_start_server(conn, config, opt.onlycontestscan, opt.resetadminpw);
            return;
        }
259
    }
260
261
262
263
264
265
266
267
268
269
270
271
272
273

    #[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.");
274
}
275
276
277
278

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

Robert Czechowski's avatar
Robert Czechowski committed
281
282
    fn start_server_and_fn<F>(port: u16, set_user: Option<(String, String, bool)>, f: F)
        where F: Fn() {
283
        use std::sync::mpsc::channel;
Robert Czechowski's avatar
Robert Czechowski committed
284
        use std::{thread, time};
285
286
287
288
        let (start_tx, start_rx) = channel();
        let (stop_tx, stop_rx) = channel();

        thread::spawn(move || {
289
            let mut conn = rusqlite::Connection::open_in_memory().unwrap();
290
291
            db_apply_migrations::test(&mut conn);

292
            if let Some(user) = set_user {
293
                let mut test_user = conn.new_session("");
294
                test_user.username = Some(user.0);
Robert Czechowski's avatar
Robert Czechowski committed
295
296
297
                test_user.is_teacher = user.2;
                test_user.set_password(&user.1).expect("Set Password did not work correctly.");
                conn.save_session(test_user);
298
299
            }

300
            // ID: 1, gets renamed
Robert Czechowski's avatar
Robert Czechowski committed
301
302
303
304
305
306
            let mut contest = Contest::new("directory".to_string(),
                                           "public.yaml".to_string(),
                                           "RenamedContestName".to_string(),
                                           1,
                                           true,
                                           None,
307
308
                                           None,
                                           None,
309
                                           None,
310
311
                                           None,
                                           None,
Robert Czechowski's avatar
Robert Czechowski committed
312
313
314
                                           None);
            contest.save(&conn);

315
            // ID: 1
Robert Czechowski's avatar
Robert Czechowski committed
316
317
318
319
320
321
            let mut contest = Contest::new("directory".to_string(),
                                           "public.yaml".to_string(),
                                           "PublicContestName".to_string(),
                                           1,
                                           true,
                                           None,
322
323
                                           None,
                                           None,
324
                                           None,
325
326
                                           None,
                                           None,
Robert Czechowski's avatar
Robert Czechowski committed
327
                                           None);
328
            let mut taskgroup = Taskgroup::new("TaskgroupName".to_string(), None);
329
            let task = Task::new("taskdir1".to_string(), 3); // ID: 1
Robert Czechowski's avatar
Robert Czechowski committed
330
            taskgroup.tasks.push(task);
331
            let task = Task::new("taskdir2".to_string(), 4); // ID: 2
Robert Czechowski's avatar
Robert Czechowski committed
332
333
334
335
            taskgroup.tasks.push(task);
            contest.taskgroups.push(taskgroup);
            contest.save(&conn);

336
            // ID: 2
Robert Czechowski's avatar
Robert Czechowski committed
337
338
339
340
341
342
            let mut contest = Contest::new("directory".to_string(),
                                           "private.yaml".to_string(),
                                           "PrivateContestName".to_string(),
                                           1,
                                           false,
                                           None,
343
344
                                           None,
                                           None,
345
                                           None,
346
347
                                           None,
                                           None,
Robert Czechowski's avatar
Robert Czechowski committed
348
                                           None);
349
            let mut taskgroup = Taskgroup::new("TaskgroupName".to_string(), None);
350
            let task = Task::new("taskdir1".to_string(), 3); // ID: 3
Robert Czechowski's avatar
Robert Czechowski committed
351
            taskgroup.tasks.push(task);
352
            let task = Task::new("taskdir2".to_string(), 4); // ID: 4
Robert Czechowski's avatar
Robert Czechowski committed
353
354
355
356
            taskgroup.tasks.push(task);
            contest.taskgroups.push(taskgroup);
            contest.save(&conn);

357
            // ID: 3
Robert Czechowski's avatar
Robert Czechowski committed
358
359
360
361
362
363
            let mut contest = Contest::new("directory".to_string(),
                                           "infinte.yaml".to_string(),
                                           "InfiniteContestName".to_string(),
                                           0,
                                           true,
                                           None,
364
365
                                           None,
                                           None,
366
                                           None,
367
368
                                           None,
                                           None,
Robert Czechowski's avatar
Robert Czechowski committed
369
                                           None);
370
371
372
373
374
375
376
377
378
            let mut taskgroup = Taskgroup::new("TaskgroupRenameName".to_string(), None);
            let task = Task::new("taskdir1".to_string(), 3); // ID: 5
            taskgroup.tasks.push(task);
            let task = Task::new("taskdir2".to_string(), 4); // ID: 6
            taskgroup.tasks.push(task);
            contest.taskgroups.push(taskgroup);
            contest.save(&conn);

            let mut taskgroup = Taskgroup::new("TaskgroupNewName".to_string(), None);
379
            let task = Task::new("taskdir1".to_string(), 3); // ID: 5
Robert Czechowski's avatar
Robert Czechowski committed
380
            taskgroup.tasks.push(task);
381
            let task = Task::new("taskdir2".to_string(), 4); // ID: 6
Robert Czechowski's avatar
Robert Czechowski committed
382
383
384
385
            taskgroup.tasks.push(task);
            contest.taskgroups.push(taskgroup);
            contest.save(&conn);

386
            let mut config = config::read_config_from_file(Path::new("thisfileshoudnotexist"));
387
            config.port = Some(port);
388
            config.cookie_signing_secret = Some("testtesttesttesttesttesttesttest".to_string());
389
            let mut srvr = start_server(conn, config).expect(&format!("Could not start server on port {}", port));
390

391
            // Message server started
392
393
            start_tx.send(()).unwrap();

394
            // Wait for test to finish
395
396
            stop_rx.recv().unwrap();

397
            srvr.close().unwrap();
398
399
        });

400
        // Wait for server to start
401
402
        start_rx.recv().unwrap();
        thread::sleep(time::Duration::from_millis(100));
403
404

        // Run test code
405
        f();
406

407
        // Message test finished
408
409
410
        stop_tx.send(()).unwrap();
    }

411
    fn login(port: u16, client: &reqwest::Client, username: &str, password: &str) -> reqwest::Response {
412
        let params = [("username", username), ("password", password)];
Robert Czechowski's avatar
Robert Czechowski committed
413
        let resp = client.post(&format!("http://localhost:{}/login", port)).form(&params).send().unwrap();
414
        resp
415
    }
Robert Czechowski's avatar
Robert Czechowski committed
416

417
418
419
420
421
    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
    }
422

423
    #[test]
Robert Czechowski's avatar
Robert Czechowski committed
424
    fn start_server_and_check_requests() {
Robert Czechowski's avatar
Robert Czechowski committed
425
        start_server_and_fn(8080, None, || {
426
            let mut resp = reqwest::get("http://localhost:8080").unwrap();
427
            assert_eq!(resp.status(), StatusCode::OK);
428
429

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

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

            let content = resp.text().unwrap();
438
439
            assert!(content.contains("<h1>Wettbewerbe</h1>"));
            assert!(!content.contains("Error"));
Robert Czechowski's avatar
Robert Czechowski committed
440
441

            let mut resp = reqwest::get("http://localhost:8080/group").unwrap();
442
            let content = resp.text().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
443
            assert!(content.contains("<h1>Login</h1>"));
444
445
        })
    }
Daniel Brüning's avatar
Daniel Brüning committed
446

447
448
    #[test]
    fn check_login_wrong_credentials() {
Robert Czechowski's avatar
Robert Czechowski committed
449
        start_server_and_fn(8081, None, || {
450
            let client = reqwest::Client::new();
Robert Czechowski's avatar
Robert Czechowski committed
451

452
            let mut resp = login(8081, &client, "nonexistingusername", "wrongpassword");
453
            assert_eq!(resp.status(), StatusCode::OK);
454
455

            let content = resp.text().unwrap();
456
457
            assert!(content.contains("<h1>Login</h1>"));
            assert!(content.contains("Login fehlgeschlagen."));
458
            assert!(!content.contains("Error"));
Robert Czechowski's avatar
Robert Czechowski committed
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474

            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"));
475
        })
476
    }
477
478

    #[test]
Robert Czechowski's avatar
Robert Czechowski committed
479
    fn check_login() {
Robert Czechowski's avatar
Robert Czechowski committed
480
        start_server_and_fn(8082, Some(("testusr".to_string(), "testpw".to_string(), false)), || {
481
482
483
484
            let client = reqwest::Client::builder().cookie_store(true)
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
485

486
            let mut resp = login(8082, &client, "testusr", "testpw");
487
            assert_eq!(resp.status(), StatusCode::FOUND);
488

489
            let content = resp.text().unwrap();
490
491
            assert!(!content.contains("Error"));

492
493
494
495
            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
496
497
498
499
            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();
500
501
            assert_eq!(resp.status(), StatusCode::OK);

502
            let content = resp.text().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
503
504
505
506
            assert!(!content.contains("Error"));
            assert!(!content.contains("Gruppenverwaltung"));
            assert!(content.contains("Eingeloggt als <em>testusr</em>"));
            assert!(content.contains("Jugendwettbewerb Informatik</h1>"));
507
508
509
        })
    }

510
    #[test]
Robert Czechowski's avatar
Robert Czechowski committed
511
    fn check_logout() {
Robert Czechowski's avatar
Robert Czechowski committed
512
        start_server_and_fn(8083, Some(("testusr".to_string(), "testpw".to_string(), false)), || {
513
514
515
516
            let client = reqwest::Client::builder().cookie_store(true)
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
517

518
            let resp = login(8083, &client, "testusr", "testpw");
519
520
521
522
523
524
525
            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);
526
527

            let content = resp.text().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
528
529
530
531
            assert!(content.contains("Benutzername"));
            assert!(content.contains("Passwort"));
            assert!(content.contains("Gruppencode / Teilnahmecode"));
            assert!(content.contains("Jugendwettbewerb Informatik</h1>"));
532
533
534
        })
    }

535
536
    #[test]
    fn check_group_creation_and_group_code_login() {
Robert Czechowski's avatar
Robert Czechowski committed
537
        start_server_and_fn(8084, Some(("testusr".to_string(), "testpw".to_string(), true)), || {
538
539
540
541
542
            let client = reqwest::Client::builder().cookie_store(true)
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();

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

546
547
            let mut resp = client.get("http://localhost:8084").send().unwrap();
            assert_eq!(resp.status(), StatusCode::OK);
548
549

            let content = resp.text().unwrap();
550
551
            assert!(content.contains("[Lehrer]"));
            assert!(content.contains("Gruppenverwaltung"));
552
553
554

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

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

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

563
564
565
            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)];
566
567
            let resp = client.post("http://localhost:8084/group/").form(&params).send().unwrap();
            assert_eq!(resp.status(), StatusCode::FOUND);
568
569
570
571

            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
572

573
574
575
576
577
            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
578
579
580
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();
581
582
583
584

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

Robert Czechowski's avatar
Robert Czechowski committed
585
586
587
588
589
            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();
590
            assert_eq!(location, "http://localhost:8084/profile?status=firstlogin");
Robert Czechowski's avatar
Robert Czechowski committed
591
592

            let mut resp = client.get(location).send().unwrap();
593
594
595
596
597
598
599
            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
600
601
602
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();
603
604
605
606

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

Robert Czechowski's avatar
Robert Czechowski committed
607
608
609
610
            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();
611
612
            let content = resp.text().unwrap();
            assert!(content.contains("Eingeloggt als <em></em>"));
613
614
        })
    }
Robert Czechowski's avatar
Robert Czechowski committed
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647

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

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

652
653
654
            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
655
656
            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
657
658
659
660
661
662
663

            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>"));
664
665
666
667
668
        })
    }

    #[test]
    fn check_task_load_save() {
669
        start_server_and_fn(8086, None, || {
670
671
672
673
674
            let client = reqwest::Client::builder().cookie_store(true)
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();

675
            let resp = client.get("http://localhost:8086/contest/3").send().unwrap();
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
            assert_eq!(resp.status(), StatusCode::OK);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

742
            let mut resp = client.get("http://localhost:8087/contest/1").send().unwrap();
743
744
745
746
747
748
            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)];
749
            let resp = client.post("http://localhost:8087/contest/1").form(&params).send().unwrap();
750
751
            assert_eq!(resp.status(), StatusCode::FOUND);

752
            let mut resp = client.get("http://localhost:8087/task/1").send().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
753
754
755
756
757
758
            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];

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

Robert Czechowski's avatar
Robert Czechowski committed
762
763
764
            let content = resp.text().unwrap();
            assert_eq!(content, "{}");

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

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

Robert Czechowski's avatar
Robert Czechowski committed
773
774
            let content = resp.text().unwrap();
            assert_eq!(content, "{}");
775

776
            let mut resp = client.get("http://localhost:8087/contest/1").send().unwrap();
777
778
779
780
781
            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>"));
782
783

            let params = [("data", "SomeData"), ("grade", "2"), ("csrf_token", csrf)];
784
            let mut resp = client.post("http://localhost:8087/save/1").form(&params).send().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
785
786
787
788
789
            assert_eq!(resp.status(), StatusCode::OK);

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

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

Robert Czechowski's avatar
Robert Czechowski committed
793
794
            let content = resp.text().unwrap();
            assert_eq!(content, "SomeData");
795

796
            let mut resp = client.get("http://localhost:8087/contest/1").send().unwrap();
797
798
799
800
801
            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
802
803
        })
    }
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828

    #[test]
    fn check_taskgroup_rename() {
        start_server_and_fn(8088, None, || {
            let client = reqwest::Client::builder().cookie_store(true)
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();

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

            let content = resp.text().unwrap();
            println!("{}", content);
            assert!(content.contains("TaskgroupNewName"));
            assert!(!content.contains("TaskgroupRenameName"));

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

            let content = resp.text().unwrap();
            assert!(content.contains("TaskgroupNewName"));
            assert!(!content.contains("TaskgroupRenameName"));
        })
    }
829
}