main.rs 34.2 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;

Robert Czechowski's avatar
Robert Czechowski committed
24
extern crate handlebars_iron;
Robert Czechowski's avatar
Robert Czechowski committed
25
26
extern crate iron_sessionstorage;
extern crate mount;
27
extern crate params;
Robert Czechowski's avatar
Robert Czechowski committed
28
29
extern crate persistent;
extern crate rand;
30
extern crate reqwest;
Robert Czechowski's avatar
Robert Czechowski committed
31
extern crate serde_json;
32
extern crate serde_yaml;
Robert Czechowski's avatar
Robert Czechowski committed
33
34
35
36
extern crate staticfile;
extern crate structopt;
extern crate time;
extern crate urlencoded;
37
extern crate csv;
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
246
247
248
249
250
251
252
    #[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;
        }
253
    }
254
255
256
257
258
259
260
261
262
263
264
265
266
267

    #[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.");
268
}
269
270
271
272

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

Robert Czechowski's avatar
Robert Czechowski committed
275
276
    fn start_server_and_fn<F>(port: u16, set_user: Option<(String, String, bool)>, f: F)
        where F: Fn() {
277
        use std::sync::mpsc::channel;
Robert Czechowski's avatar
Robert Czechowski committed
278
        use std::{thread, time};
279
280
281
282
        let (start_tx, start_rx) = channel();
        let (stop_tx, stop_rx) = channel();

        thread::spawn(move || {
283
            let mut conn = rusqlite::Connection::open_in_memory().unwrap();
284
285
            db_apply_migrations::test(&mut conn);

286
            if let Some(user) = set_user {
287
                let mut test_user = conn.new_session("");
288
                test_user.username = Some(user.0);
Robert Czechowski's avatar
Robert Czechowski committed
289
290
291
                test_user.is_teacher = user.2;
                test_user.set_password(&user.1).expect("Set Password did not work correctly.");
                conn.save_session(test_user);
292
293
            }

294
            // ID: 1, gets renamed
Robert Czechowski's avatar
Robert Czechowski committed
295
296
297
298
299
300
            let mut contest = Contest::new("directory".to_string(),
                                           "public.yaml".to_string(),
                                           "RenamedContestName".to_string(),
                                           1,
                                           true,
                                           None,
301
302
                                           None,
                                           None,
303
                                           None,
304
305
                                           None,
                                           None,
Robert Czechowski's avatar
Robert Czechowski committed
306
307
308
                                           None);
            contest.save(&conn);

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

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

351
            // ID: 3
Robert Czechowski's avatar
Robert Czechowski committed
352
353
354
355
356
357
            let mut contest = Contest::new("directory".to_string(),
                                           "infinte.yaml".to_string(),
                                           "InfiniteContestName".to_string(),
                                           0,
                                           true,
                                           None,
358
359
                                           None,
                                           None,
360
                                           None,
361
362
                                           None,
                                           None,
Robert Czechowski's avatar
Robert Czechowski committed
363
                                           None);
364
365
366
367
368
369
370
371
372
            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);
373
            let task = Task::new("taskdir1".to_string(), 3); // ID: 5
Robert Czechowski's avatar
Robert Czechowski committed
374
            taskgroup.tasks.push(task);
375
            let task = Task::new("taskdir2".to_string(), 4); // ID: 6
Robert Czechowski's avatar
Robert Czechowski committed
376
377
378
379
            taskgroup.tasks.push(task);
            contest.taskgroups.push(taskgroup);
            contest.save(&conn);

380
            let mut config = config::read_config_from_file(Path::new("thisfileshoudnotexist"));
381
            config.port = Some(port);
382
            config.cookie_signing_secret = Some("testtesttesttesttesttesttesttest".to_string());
383
            let mut srvr = start_server(conn, config).expect(&format!("Could not start server on port {}", port));
384

385
            // Message server started
386
387
            start_tx.send(()).unwrap();

388
            // Wait for test to finish
389
390
            stop_rx.recv().unwrap();

391
            srvr.close().unwrap();
392
393
        });

394
        // Wait for server to start
395
396
        start_rx.recv().unwrap();
        thread::sleep(time::Duration::from_millis(100));
397
398

        // Run test code
399
        f();
400

401
        // Message test finished
402
403
404
        stop_tx.send(()).unwrap();
    }

405
    fn login(port: u16, client: &reqwest::Client, username: &str, password: &str) -> reqwest::Response {
406
        let params = [("username", username), ("password", password)];
Robert Czechowski's avatar
Robert Czechowski committed
407
        let resp = client.post(&format!("http://localhost:{}/login", port)).form(&params).send().unwrap();
408
        resp
409
    }
Robert Czechowski's avatar
Robert Czechowski committed
410

411
412
413
414
415
    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
    }
416

417
    #[test]
Robert Czechowski's avatar
Robert Czechowski committed
418
    fn start_server_and_check_requests() {
Robert Czechowski's avatar
Robert Czechowski committed
419
        start_server_and_fn(8080, None, || {
420
            let mut resp = reqwest::get("http://localhost:8080").unwrap();
421
            assert_eq!(resp.status(), StatusCode::OK);
422
423

            let content = resp.text().unwrap();
424
            assert!(content.contains("Jugendwettbewerb Informatik</h1>"));
425
            assert!(!content.contains("Error"));
Robert Czechowski's avatar
Robert Czechowski committed
426
            assert!(!content.contains("Gruppenverwaltung"));
427
428

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

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

            let mut resp = reqwest::get("http://localhost:8080/group").unwrap();
436
            let content = resp.text().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
437
            assert!(content.contains("<h1>Login</h1>"));
438
439
        })
    }
Daniel Brüning's avatar
Daniel Brüning committed
440

441
442
    #[test]
    fn check_login_wrong_credentials() {
Robert Czechowski's avatar
Robert Czechowski committed
443
        start_server_and_fn(8081, None, || {
444
            let client = reqwest::Client::new();
Robert Czechowski's avatar
Robert Czechowski committed
445

446
            let mut resp = login(8081, &client, "nonexistingusername", "wrongpassword");
447
            assert_eq!(resp.status(), StatusCode::OK);
448
449

            let content = resp.text().unwrap();
450
451
            assert!(content.contains("<h1>Login</h1>"));
            assert!(content.contains("Login fehlgeschlagen."));
452
            assert!(!content.contains("Error"));
Robert Czechowski's avatar
Robert Czechowski committed
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468

            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"));
469
        })
470
    }
471
472

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

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

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

486
487
488
489
            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
490
491
492
493
            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();
494
495
            assert_eq!(resp.status(), StatusCode::OK);

496
            let content = resp.text().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
497
498
499
500
            assert!(!content.contains("Error"));
            assert!(!content.contains("Gruppenverwaltung"));
            assert!(content.contains("Eingeloggt als <em>testusr</em>"));
            assert!(content.contains("Jugendwettbewerb Informatik</h1>"));
501
502
503
        })
    }

504
    #[test]
Robert Czechowski's avatar
Robert Czechowski committed
505
    fn check_logout() {
Robert Czechowski's avatar
Robert Czechowski committed
506
        start_server_and_fn(8083, Some(("testusr".to_string(), "testpw".to_string(), false)), || {
507
508
509
510
            let client = reqwest::Client::builder().cookie_store(true)
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
511

512
            let resp = login(8083, &client, "testusr", "testpw");
513
514
515
516
517
518
519
            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);
520
521

            let content = resp.text().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
522
523
524
525
            assert!(content.contains("Benutzername"));
            assert!(content.contains("Passwort"));
            assert!(content.contains("Gruppencode / Teilnahmecode"));
            assert!(content.contains("Jugendwettbewerb Informatik</h1>"));
526
527
528
        })
    }

529
530
    #[test]
    fn check_group_creation_and_group_code_login() {
Robert Czechowski's avatar
Robert Czechowski committed
531
        start_server_and_fn(8084, Some(("testusr".to_string(), "testpw".to_string(), true)), || {
532
533
534
535
536
            let client = reqwest::Client::builder().cookie_store(true)
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();

537
            let resp = login(8084, &client, "testusr", "testpw");
538
            assert_eq!(resp.status(), StatusCode::FOUND);
539

540
541
            let mut resp = client.get("http://localhost:8084").send().unwrap();
            assert_eq!(resp.status(), StatusCode::OK);
542
543

            let content = resp.text().unwrap();
544
545
            assert!(content.contains("[Lehrer]"));
            assert!(content.contains("Gruppenverwaltung"));
546
547
548

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

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

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

557
558
559
            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)];
560
561
            let resp = client.post("http://localhost:8084/group/").form(&params).send().unwrap();
            assert_eq!(resp.status(), StatusCode::FOUND);
562
563
564
565

            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
566

567
568
569
570
571
            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
572
573
574
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();
575
576
577
578

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

Robert Czechowski's avatar
Robert Czechowski committed
579
580
581
582
583
            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();
584
            assert_eq!(location, "http://localhost:8084/profile?status=firstlogin");
Robert Czechowski's avatar
Robert Czechowski committed
585
586

            let mut resp = client.get(location).send().unwrap();
587
588
589
590
591
592
593
            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
594
595
596
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();
597
598
599
600

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

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

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

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

646
647
648
            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
649
650
            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
651
652
653
654
655
656
657

            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>"));
658
659
660
661
662
        })
    }

    #[test]
    fn check_task_load_save() {
663
        start_server_and_fn(8086, None, || {
664
665
666
667
668
            let client = reqwest::Client::builder().cookie_store(true)
                                                   .redirect(reqwest::RedirectPolicy::none())
                                                   .build()
                                                   .unwrap();

669
            let resp = client.get("http://localhost:8086/contest/3").send().unwrap();
670
671
672
673
674
675
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
            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");
734
            assert_eq!(resp.status(), StatusCode::FOUND);
Robert Czechowski's avatar
Robert Czechowski committed
735

736
            let mut resp = client.get("http://localhost:8087/contest/1").send().unwrap();
737
738
739
740
741
742
            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)];
743
            let resp = client.post("http://localhost:8087/contest/1").form(&params).send().unwrap();
744
745
            assert_eq!(resp.status(), StatusCode::FOUND);

746
            let mut resp = client.get("http://localhost:8087/task/1").send().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
747
748
749
750
751
752
            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];

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

Robert Czechowski's avatar
Robert Czechowski committed
756
757
758
            let content = resp.text().unwrap();
            assert_eq!(content, "{}");

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

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

Robert Czechowski's avatar
Robert Czechowski committed
767
768
            let content = resp.text().unwrap();
            assert_eq!(content, "{}");
769

770
            let mut resp = client.get("http://localhost:8087/contest/1").send().unwrap();
771
772
773
774
775
            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>"));
776
777

            let params = [("data", "SomeData"), ("grade", "2"), ("csrf_token", csrf)];
778
            let mut resp = client.post("http://localhost:8087/save/1").form(&params).send().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
779
780
781
782
783
            assert_eq!(resp.status(), StatusCode::OK);

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

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

Robert Czechowski's avatar
Robert Czechowski committed
787
788
            let content = resp.text().unwrap();
            assert_eq!(content, "SomeData");
789

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

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