functions.rs 23.9 KB
Newer Older
1
2
extern crate bcrypt;

Robert Czechowski's avatar
Robert Czechowski committed
3
use webfw_iron::{json_val, to_json};
Robert Czechowski's avatar
Robert Czechowski committed
4

5
6
use time;

Robert Czechowski's avatar
Robert Czechowski committed
7
use rand::{distributions::Alphanumeric, thread_rng, Rng};
8

Robert Czechowski's avatar
Robert Czechowski committed
9
use db_conn::MedalConnection;
Robert Czechowski's avatar
Robert Czechowski committed
10

Robert Czechowski's avatar
Robert Czechowski committed
11
use db_objects::{Group, SessionUser, Submission};
Robert Czechowski's avatar
Robert Czechowski committed
12

Robert Czechowski's avatar
Robert Czechowski committed
13
use self::bcrypt::hash;
14

15
16
17
18
19
20
21
22
23
24
25
26
#[derive(Serialize, Deserialize)]
pub struct SubTaskInfo {
    pub id: u32,
    pub linktext: String,
}

#[derive(Serialize, Deserialize)]
pub struct TaskInfo {
    pub name: String,
    pub subtasks: Vec<SubTaskInfo>,
}

Robert Czechowski's avatar
Robert Czechowski committed
27
28
29
30
31
32
33
34
#[derive(Serialize, Deserialize)]
pub struct ContestInfo {
    pub id: u32,
    pub location: String,
    pub filename: String,
    pub name: String,
    pub duration: u32,
    pub public: bool,
35
    pub tasks: Vec<TaskInfo>,
Robert Czechowski's avatar
Robert Czechowski committed
36
37
}

Robert Czechowski's avatar
Robert Czechowski committed
38
#[allow(dead_code)] //CsrfCheckFailed, SessionTimeout, DatabaseError and UnmatchedPasswords are used in webfw_iron.rs.
39
40
#[derive(Clone)]
pub enum MedalError {
41
42
    NotLoggedIn,
    AccessDenied,
43
44
45
    CsrfCheckFailed,
    SessionTimeout,
    DatabaseError,
46
    PasswordHashingError,
47
    UnmatchedPasswords,
48
}
49

50
51
52
53
type MedalValue = (String, json_val::Map<String, json_val::Value>);
type MedalResult<T> = Result<T, MedalError>;
type MedalValueResult = MedalResult<MedalValue>;

Robert Czechowski's avatar
Robert Czechowski committed
54
55
56
57
pub fn index<T: MedalConnection>(conn: &T, session_token: Option<String>,
                                 (self_url, oauth_url): (Option<String>, Option<String>))
                                 -> (String, json_val::Map<String, json_val::Value>)
{
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
    let mut data = json_val::Map::new();

    //let mut contests = Vec::new();

    if let Some(token) = session_token {
        if let Some(session) = conn.get_session(&token) {
            data.insert("logged_in".to_string(), to_json(&true));
            data.insert("username".to_string(), to_json(&session.username));
            data.insert("firstname".to_string(), to_json(&session.firstname));
            data.insert("lastname".to_string(), to_json(&session.lastname));
            data.insert("teacher".to_string(), to_json(&session.is_teacher));
        }
    }

    data.insert("self_url".to_string(), to_json(&self_url));
    data.insert("oauth_url".to_string(), to_json(&oauth_url));
    /*contests.push("blaa".to_string());
    data.insert("contest".to_string(), to_json(&contests));*/

    ("index".to_owned(), data)
}

80
pub fn show_contests<T: MedalConnection>(conn: &T) -> MedalValue {
Robert Czechowski's avatar
Robert Czechowski committed
81
82
    let mut data = json_val::Map::new();

Robert Czechowski's avatar
Robert Czechowski committed
83
84
85
86
87
88
89
90
91
92
    let v: Vec<ContestInfo> = conn.get_contest_list()
                                  .iter()
                                  .map(|c| ContestInfo { id: c.id.unwrap(),
                                                         location: c.location.clone(),
                                                         filename: c.filename.clone(),
                                                         name: c.name.clone(),
                                                         duration: c.duration,
                                                         public: c.public,
                                                         tasks: Vec::new() })
                                  .collect();
Robert Czechowski's avatar
Robert Czechowski committed
93
94
95
96
97
    data.insert("contest".to_string(), to_json(&v));

    ("contests".to_owned(), data)
}

98
pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: u32, session_token: String) -> MedalValueResult {
99
    let c = conn.get_contest_by_id_complete(contest_id);
100
    let grades = conn.get_contest_user_grades(session_token.clone(), contest_id);
101

102
    // TODO: Clean up star generation
103

104
    let mut tasks = Vec::new();
105
106
    for (task, grade) in c.taskgroups.into_iter().zip(grades) {
        let mut not_print_yet = true;
107
108
        let mut stasks = Vec::new();
        for st in task.tasks {
Robert Czechowski's avatar
Robert Czechowski committed
109
            let mut blackstars: usize = 0;
110
111
112
113
114
            if not_print_yet && st.stars >= grade.grade.unwrap_or(0) {
                blackstars = grade.grade.unwrap_or(0) as usize;
                not_print_yet = false;
            }

Robert Czechowski's avatar
Robert Czechowski committed
115
116
117
118
            stasks.push(SubTaskInfo { id: st.id.unwrap(),
                                      linktext: format!("{}{}",
                                                        str::repeat("★", blackstars as usize),
                                                        str::repeat("☆", st.stars as usize - blackstars as usize)) })
119
        }
Robert Czechowski's avatar
Robert Czechowski committed
120
        let mut ti = TaskInfo { name: task.name, subtasks: stasks };
121
122
        tasks.push(ti);
    }
123

Robert Czechowski's avatar
Robert Czechowski committed
124
125
126
127
128
129
130
    let ci = ContestInfo { id: c.id.unwrap(),
                           location: c.location.clone(),
                           filename: c.filename.clone(),
                           name: c.name.clone(),
                           duration: c.duration,
                           public: c.public,
                           tasks: tasks };
Robert Czechowski's avatar
Robert Czechowski committed
131
132
133
134

    let mut data = json_val::Map::new();
    data.insert("contest".to_string(), to_json(&ci));

135
    data.insert("logged_in".to_string(), to_json(&false));
136
    if let Some(session) = conn.get_session(&session_token) {
137
138
139
140
141
142
143
        data.insert("logged_in".to_string(), to_json(&true));
        data.insert("username".to_string(), to_json(&session.username));
        data.insert("firstname".to_string(), to_json(&session.firstname));
        data.insert("lastname".to_string(), to_json(&session.lastname));
        data.insert("teacher".to_string(), to_json(&session.is_teacher));
    }

144
    match conn.get_participation(&session_token, contest_id) {
Robert Czechowski's avatar
Robert Czechowski committed
145
        None => Ok(("contest".to_owned(), data)),
Robert Czechowski's avatar
Robert Czechowski committed
146
147
148
149
150
151
152
        Some(participation) => {
            let now = time::get_time();
            let passed_secs = now.sec - participation.start.sec;
            if passed_secs < 0 {
                // behandle inkonsistente Serverzeit
            }

Robert Czechowski's avatar
Robert Czechowski committed
153
            data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
154

155
            let left_secs = i64::from(ci.duration) * 60 - passed_secs;
Robert Czechowski's avatar
Robert Czechowski committed
156
157
            if left_secs < 0 {
                // Contest over
158

Robert Czechowski's avatar
Robert Czechowski committed
159
            } else {
Robert Czechowski's avatar
Robert Czechowski committed
160
161
                let left_min = left_secs / 60;
                let left_sec = left_secs % 60;
162
                if left_sec < 10 {
Robert Czechowski's avatar
Robert Czechowski committed
163
164
165
                    data.insert("time_left".to_string(), to_json(&format!("{}:0{}", left_min, left_sec)));
                } else {
                    data.insert("time_left".to_string(), to_json(&format!("{}:{}", left_min, left_sec)));
Robert Czechowski's avatar
Robert Czechowski committed
166
167
                }
            }
168

169
            Ok(("contest".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
170
171
172
173
        }
    }
}

174
pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: u32, session_token: String) -> MedalValueResult {
Robert Czechowski's avatar
Robert Czechowski committed
175
176
177
178
    let session = conn.get_session(&session_token)
                      .ok_or(MedalError::AccessDenied)?
                      .ensure_alive()
                      .ok_or(MedalError::AccessDenied)?; // TODO SessionTimeout?
179
180
181
182
183
184
185
    let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);

    let mut results: Vec<(String, Vec<(String, Vec<String>)>)> = Vec::new();

    for (group, groupdata) in resultdata {
        let mut groupresults: Vec<(String, Vec<String>)> = Vec::new();

Daniel Brüning's avatar
Daniel Brüning committed
186
187
        //TODO: use user
        for (_user, userdata) in groupdata {
188
189
            let mut userresults: Vec<String> = Vec::new();

190
191
192
            userresults.push(String::new());
            let mut summe = 0;

193
            for grade in userdata {
194
195
196
197
198
                if let Some(g) = grade.grade {
                    userresults.push(format!("{}", g));
                    summe += g;
                } else {
                    userresults.push(format!("–"));
199
                }
200
201
            }

202
203
            userresults[0] = format!("{}", summe);

204
205
            groupresults.push((format!("Name"), userresults))
        }
206

Robert Czechowski's avatar
Robert Czechowski committed
207
        results.push((format!("{}", group.name), groupresults));
208
209
210
211
212
    }

    let mut data = json_val::Map::new();
    data.insert("taskname".to_string(), to_json(&tasknames));
    data.insert("result".to_string(), to_json(&results));
213
214

    let c = conn.get_contest_by_id(contest_id);
Robert Czechowski's avatar
Robert Czechowski committed
215
216
217
218
219
220
221
    let ci = ContestInfo { id: c.id.unwrap(),
                           location: c.location.clone(),
                           filename: c.filename.clone(),
                           name: c.name.clone(),
                           duration: c.duration,
                           public: c.public,
                           tasks: Vec::new() };
222
    data.insert("contest".to_string(), to_json(&ci));
223

224
225
226
    Ok(("contestresults".to_owned(), data))
}

Daniel Brüning's avatar
Daniel Brüning committed
227
//TODO: use csrf_token
Robert Czechowski's avatar
Robert Czechowski committed
228
229
pub fn start_contest<T: MedalConnection>(conn: &T, contest_id: u32, session_token: String, _csrf_token: String)
                                         -> MedalResult<()> {
Daniel Brüning's avatar
Daniel Brüning committed
230
231
    //TODO: use data
    let _data = json_val::Map::new();
Robert Czechowski's avatar
Robert Czechowski committed
232

233
    match conn.new_participation(&session_token, contest_id) {
Robert Czechowski's avatar
Robert Czechowski committed
234
        Ok(_) => Ok(()),
Robert Czechowski's avatar
Robert Czechowski committed
235
        _ => Err(MedalError::AccessDenied),
Robert Czechowski's avatar
Robert Czechowski committed
236
    }
Robert Czechowski's avatar
Robert Czechowski committed
237
238
}

239
pub fn login<T: MedalConnection>(conn: &T, login_data: (String, String)) -> Result<String, MedalValue> {
Robert Czechowski's avatar
Robert Czechowski committed
240
241
    let (username, password) = login_data;

242
    match conn.login(None, &username, &password) {
Robert Czechowski's avatar
Robert Czechowski committed
243
        Ok(session_token) => Ok(session_token),
Robert Czechowski's avatar
Robert Czechowski committed
244
245
        Err(()) => {
            let mut data = json_val::Map::new();
246
            data.insert("reason".to_string(), to_json(&"Login fehlgeschlagen. Bitte erneut versuchen.".to_string()));
Robert Czechowski's avatar
Robert Czechowski committed
247
            data.insert("username".to_string(), to_json(&username));
248
            Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
249
250
251
252
        }
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
253
254
255
pub fn login_with_code<T: MedalConnection>(
    conn: &T, code: String)
    -> Result<Result<String, String>, (String, json_val::Map<String, json_val::Value>)> {
256
    match conn.login_with_code(None, &code) {
Robert Czechowski's avatar
Robert Czechowski committed
257
258
259
260
261
262
263
264
        Ok(session_token) => Ok(Ok(session_token)),
        Err(()) => match conn.create_user_with_groupcode(None, &code) {
            Ok(session_token) => Ok(Err(session_token)),
            Err(()) => {
                let mut data = json_val::Map::new();
                data.insert("reason".to_string(), to_json(&"Kein gültiger Code. Bitte erneut versuchen.".to_string()));
                data.insert("code".to_string(), to_json(&code));
                Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
265
            }
Robert Czechowski's avatar
Robert Czechowski committed
266
        },
Robert Czechowski's avatar
Robert Czechowski committed
267
268
269
    }
}

270
pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) {
271
    session_token.map(|token| conn.logout(&token));
Robert Czechowski's avatar
Robert Czechowski committed
272
273
}

Robert Czechowski's avatar
Robert Czechowski committed
274
275
276
277
278
279
pub fn load_submission<T: MedalConnection>(conn: &T, task_id: u32, session_token: String, subtask: Option<String>)
                                           -> MedalResult<String> {
    let session = conn.get_session(&session_token)
                      .ok_or(MedalError::AccessDenied)?
                      .ensure_alive()
                      .ok_or(MedalError::AccessDenied)?; // TODO SessionTimeout
280

281
    match match subtask {
Robert Czechowski's avatar
Robert Czechowski committed
282
283
284
              Some(s) => conn.load_submission(&session, task_id, Some(&s)),
              None => conn.load_submission(&session, task_id, None),
          } {
Robert Czechowski's avatar
Robert Czechowski committed
285
        Some(submission) => Ok(submission.value),
Robert Czechowski's avatar
Robert Czechowski committed
286
        None => Ok("{}".to_string()),
Robert Czechowski's avatar
Robert Czechowski committed
287
288
289
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
290
291
292
293
294
295
296
297
pub fn save_submission<T: MedalConnection>(conn: &T, task_id: u32, session_token: String, csrf_token: String,
                                           data: String, grade: u8, subtask: Option<String>)
                                           -> MedalResult<String>
{
    let session = conn.get_session(&session_token)
                      .ok_or(MedalError::AccessDenied)?
                      .ensure_alive()
                      .ok_or(MedalError::AccessDenied)?; // TODO SessionTimeout
Robert Czechowski's avatar
Robert Czechowski committed
298
299

    if session.csrf_token != csrf_token {
300
        return Err(MedalError::AccessDenied); // CsrfError
Robert Czechowski's avatar
Robert Czechowski committed
301
302
    }

Robert Czechowski's avatar
Robert Czechowski committed
303
304
305
306
307
308
309
310
311
312
    let submission = Submission { id: None,
                                  session_user: session.id,
                                  task: task_id,
                                  grade: grade,
                                  validated: false,
                                  nonvalidated_grade: grade,
                                  needs_validation: true,
                                  subtask_identifier: subtask,
                                  value: data,
                                  date: time::get_time() };
313

Robert Czechowski's avatar
Robert Czechowski committed
314
315
316
317
318
    conn.submit_submission(submission);

    Ok("{}".to_string())
}

319
pub fn show_task<T: MedalConnection>(conn: &T, task_id: u32, session_token: String) -> MedalValueResult {
320
    let session = conn.get_session_or_new(&session_token).ensure_alive().ok_or(MedalError::AccessDenied)?; // TODO SessionTimeout
Robert Czechowski's avatar
Robert Czechowski committed
321

322
323
324
    let (t, tg, c) = conn.get_task_by_id_complete(task_id);

    let taskpath = format!("{}{}", c.location, t.location);
325

Robert Czechowski's avatar
Robert Czechowski committed
326
327
    let mut data = json_val::Map::new();

328
    data.insert("name".to_string(), to_json(&tg.name));
Robert Czechowski's avatar
Robert Czechowski committed
329
330
    data.insert("taskid".to_string(), to_json(&task_id));
    data.insert("csrftoken".to_string(), to_json(&session.csrf_token));
331
    data.insert("taskpath".to_string(), to_json(&taskpath));
Robert Czechowski's avatar
Robert Czechowski committed
332

333
    Ok(("task".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
334
}
Robert Czechowski's avatar
Robert Czechowski committed
335
336
337
338
//?state=42&scope=authenticate&code=250a4f49-e122-4b10-8da0-bc400ba5ea3d
// TOKEN  ->  {"token_type" : "Bearer","expires" : 3600,"refresh_token" : "R3a716e23-b320-4dab-a529-4c19e6b7ffc5","access_token" : "A6f681904-ded6-4e8b-840e-ac79ca1ffc07"}
// DATA  ->  {"lastName" : "Czechowski","gender" : "?","userType" : "a","userID" : "12622","dateOfBirth" : "2001-01-01","firstName" : "Robert","eMail" : "czechowski@bwinf.de","schoolId" : -1}

Robert Czechowski's avatar
Robert Czechowski committed
339
340
#[derive(Serialize, Deserialize)]
pub struct GroupInfo {
341
    pub id: u32,
Robert Czechowski's avatar
Robert Czechowski committed
342
343
344
345
346
    pub name: String,
    pub tag: String,
    pub code: String,
}

347
pub fn show_groups<T: MedalConnection>(conn: &T, session_token: String) -> MedalValueResult {
348
    let session = conn.get_session_or_new(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
349

Robert Czechowski's avatar
Robert Czechowski committed
350
    //    let groupvec = conn.get_group(session_token);
Robert Czechowski's avatar
Robert Czechowski committed
351
352
353

    let mut data = json_val::Map::new();

Robert Czechowski's avatar
Robert Czechowski committed
354
355
356
357
358
359
360
361
    let v: Vec<GroupInfo> =
        conn.get_groups(session.id)
            .iter()
            .map(|g| GroupInfo { id: g.id.unwrap(),
                                 name: g.name.clone(),
                                 tag: g.tag.clone(),
                                 code: g.groupcode.clone() })
            .collect();
Robert Czechowski's avatar
Robert Czechowski committed
362
363
    data.insert("group".to_string(), to_json(&v));
    data.insert("csrftoken".to_string(), to_json(&session.csrf_token));
364

365
    Ok(("groups".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
366
367
}

Robert Czechowski's avatar
Robert Czechowski committed
368
369
#[derive(Serialize, Deserialize)]
pub struct MemberInfo {
370
    pub id: u32,
Robert Czechowski's avatar
Robert Czechowski committed
371
372
373
374
375
376
    pub firstname: String,
    pub lastname: String,
    pub grade: u8,
    pub logincode: String,
}

377
pub fn show_group<T: MedalConnection>(conn: &T, group_id: u32, session_token: String) -> MedalValueResult {
378
    let session = conn.get_session_or_new(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
379
380
381
    let group = conn.get_group_complete(group_id).unwrap(); // TODO handle error

    let mut data = json_val::Map::new();
382

Robert Czechowski's avatar
Robert Czechowski committed
383
    if group.admin != session.id {
384
        return Err(MedalError::AccessDenied);
Robert Czechowski's avatar
Robert Czechowski committed
385
386
    }

Robert Czechowski's avatar
Robert Czechowski committed
387
388
389
390
391
392
393
394
395
396
397
398
399
400
    let gi = GroupInfo { id: group.id.unwrap(),
                         name: group.name.clone(),
                         tag: group.tag.clone(),
                         code: group.groupcode.clone() };

    let v: Vec<MemberInfo> =
        group.members
             .iter()
             .map(|m| MemberInfo { id: m.id,
                                   firstname: m.firstname.clone().unwrap_or_else(|| "".to_string()),
                                   lastname: m.lastname.clone().unwrap_or_else(|| "".to_string()),
                                   grade: m.grade,
                                   logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()) })
             .collect();
Robert Czechowski's avatar
Robert Czechowski committed
401
402
403

    data.insert("group".to_string(), to_json(&gi));
    data.insert("member".to_string(), to_json(&v));
Robert Czechowski's avatar
Robert Czechowski committed
404
405
406

    Ok(("group".to_string(), data))
}
Robert Czechowski's avatar
Robert Czechowski committed
407

Daniel Brüning's avatar
Daniel Brüning committed
408
pub fn modify_group<T: MedalConnection>(_conn: &T, _group_id: u32, _session_token: String) -> MedalResult<()> {
Robert Czechowski's avatar
Robert Czechowski committed
409
410
    unimplemented!()
}
Robert Czechowski's avatar
Robert Czechowski committed
411

Robert Czechowski's avatar
Robert Czechowski committed
412
413
414
415
416
417
pub fn add_group<T: MedalConnection>(conn: &T, session_token: String, csrf_token: String, name: String, tag: String)
                                     -> MedalResult<u32> {
    let session = conn.get_session(&session_token)
                      .ok_or(MedalError::AccessDenied)?
                      .ensure_logged_in()
                      .ok_or(MedalError::AccessDenied)?;
Robert Czechowski's avatar
Robert Czechowski committed
418
419

    if session.csrf_token != csrf_token {
420
        return Err(MedalError::AccessDenied); // CsrfError
Robert Czechowski's avatar
Robert Czechowski committed
421
422
    }

Robert Czechowski's avatar
Robert Czechowski committed
423
424
425
426
427
428
429
430
    let group_code: String = Some('g').into_iter()
                                      .chain(thread_rng().sample_iter(&Alphanumeric))
                                      .filter(|x| {
                                          let x = *x;
                                          !(x == 'l' || x == 'I' || x == '1' || x == 'O' || x == 'o' || x == '0')
                                      })
                                      .take(7)
                                      .collect();
431
432
    // todo: check for collisions

Robert Czechowski's avatar
Robert Czechowski committed
433
434
    let mut group =
        Group { id: None, name: name, groupcode: group_code, tag: tag, admin: session.id, members: Vec::new() };
Robert Czechowski's avatar
Robert Czechowski committed
435

Robert Czechowski's avatar
Robert Czechowski committed
436
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
437
438
439

    Ok(group.id.unwrap())
}
440

Daniel Brüning's avatar
Daniel Brüning committed
441
#[allow(dead_code)]
442
pub fn show_groups_results<T: MedalConnection>(conn: &T, contest_id: u32, session_token: String) -> MedalValueResult {
443
    let session = conn.get_session_or_new(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Daniel Brüning's avatar
Daniel Brüning committed
444
445
    //TODO: use g
    let _g = conn.get_contest_groups_grades(session.id, contest_id);
446

447
    let data = json_val::Map::new();
448
449
450

    Ok(("groupresults".into(), data))
}
Robert Czechowski's avatar
Robert Czechowski committed
451

Robert Czechowski's avatar
Robert Czechowski committed
452
453
454
455
pub fn show_profile<T: MedalConnection>(conn: &T, session_token: String, user_id: Option<u32>,
                                        query_string: Option<String>)
                                        -> MedalValueResult
{
456
    let session = conn.get_session_or_new(&session_token).ensure_alive().ok_or(MedalError::AccessDenied)?; // TODO SessionTimeout
Robert Czechowski's avatar
Robert Czechowski committed
457
458
459

    let mut data = json_val::Map::new();

460
461
462
463
464
465
466
467
468
469
470
471
472
    match user_id {
        None => {
            data.insert("firstname".to_string(), to_json(&session.firstname));
            data.insert("lastname".to_string(), to_json(&session.lastname));
            data.insert(format!("sel{}", session.grade), to_json(&"selected"));

            data.insert("logincode".to_string(), to_json(&session.logincode));
            if session.password.is_some() {
                data.insert("username".to_string(), to_json(&session.username));
            }
            data.insert("ownprofile".into(), to_json(&true));

            data.insert("csrftoken".to_string(), to_json(&session.csrf_token));
473

474
            if let Some(query) = query_string {
475
                if query.starts_with("status=") {
476
                    let status: &str = &query[7..];
Robert Czechowski's avatar
Robert Czechowski committed
477
                    if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch"].contains(&status) {
478
479
                        data.insert((status).to_string(), to_json(&true));
                    }
480
481
                }
            }
Robert Czechowski's avatar
Robert Czechowski committed
482
        }
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
        Some(user_id) => {
            // TODO: Add test to check if this access restriction works
            let (user, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
            let group = opt_group.ok_or(MedalError::AccessDenied)?;
            if group.admin != session.id {
                return Err(MedalError::AccessDenied);
            }

            data.insert("firstname".to_string(), to_json(&user.firstname));
            data.insert("lastname".to_string(), to_json(&user.lastname));
            data.insert(format!("sel{}", user.grade), to_json(&"selected"));

            data.insert("logincode".to_string(), to_json(&user.logincode));
            if user.password.is_some() {
                data.insert("username".to_string(), to_json(&user.username));
            }
499

500
            data.insert("ownprofile".into(), to_json(&false));
501

502
            data.insert("csrftoken".to_string(), to_json(&session.csrf_token));
503
504

            // data.insert("query_string".to_string(), to_json(&query_string.unwrap()));
505
506
        }
    }
507

508
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
509
510
}

511
fn hash_password(password: &str, salt: &str) -> Result<String, MedalError> {
Robert Czechowski's avatar
Robert Czechowski committed
512
513
514
515
516
    let password_and_salt = [password, salt].concat().to_string();
    match hash(password_and_salt, 5) {
        Ok(result) => Ok(result),
        Err(_) => Err(MedalError::PasswordHashingError),
    }
517
}
Robert Czechowski's avatar
Robert Czechowski committed
518

519
520
#[derive(Debug)]
pub enum ProfileStatus {
Daniel Brüning's avatar
Daniel Brüning committed
521
522
523
524
    NothingChanged,
    DataChanged,
    PasswordChanged,
    PasswordMissmatch,
525
526
}
impl std::convert::Into<String> for ProfileStatus {
Robert Czechowski's avatar
Robert Czechowski committed
527
    fn into(self) -> String { format!("{:?}", self) }
528
529
}

Robert Czechowski's avatar
Robert Czechowski committed
530
531
532
533
534
535
536
537
538
pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: String, user_id: Option<u32>, csrf_token: String,
                                        firstname: String, lastname: String, password: String,
                                        password_repeat: String, grade: u8)
                                        -> MedalResult<ProfileStatus>
{
    let mut session = conn.get_session(&session_token)
                          .ok_or(MedalError::AccessDenied)?
                          .ensure_alive()
                          .ok_or(MedalError::AccessDenied)?; // TODO SessionTimeout
Robert Czechowski's avatar
Robert Czechowski committed
539
540

    if session.csrf_token != csrf_token {
541
        return Err(MedalError::AccessDenied); // CsrfError
Robert Czechowski's avatar
Robert Czechowski committed
542
543
    }

544
    if session.firstname.as_ref() == Some(&firstname)
Robert Czechowski's avatar
Robert Czechowski committed
545
546
547
548
549
550
551
       && session.lastname.as_ref() == Some(&lastname)
       && session.grade == grade
       && password == ""
       && password_repeat == ""
    {
        return Ok(ProfileStatus::NothingChanged);
    }
552

Daniel Brüning's avatar
Daniel Brüning committed
553
    let mut result = ProfileStatus::DataChanged;
554
555

    let mut password_salt = None;
556

557
558
559
    if password != "" || password_repeat != "" {
        if password == password_repeat {
            let salt: String = thread_rng().sample_iter(&Alphanumeric).take(10).collect();
560
            let hash = hash_password(&password, &salt)?;
Daniel Brüning's avatar
Daniel Brüning committed
561

562
            password_salt = Some((hash, salt));
Daniel Brüning's avatar
Daniel Brüning committed
563
            result = ProfileStatus::PasswordChanged;
564
        } else {
Daniel Brüning's avatar
Daniel Brüning committed
565
            result = ProfileStatus::PasswordMissmatch;
566
        }
567
568
    }

569
570
571
572
573
    match user_id {
        None => {
            session.firstname = Some(firstname);
            session.lastname = Some(lastname);
            session.grade = grade;
574

Robert Czechowski's avatar
Robert Czechowski committed
575
            if let Some((password, salt)) = password_salt {
576
577
                session.password = Some(password);
                session.salt = Some(salt);
578
            }
Daniel Brüning's avatar
Daniel Brüning committed
579

580
581
582
583
584
585
586
587
588
            conn.save_session(session);
        }
        Some(user_id) => {
            // TODO: Add test to check if this access restriction works
            let (mut user, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
            let group = opt_group.ok_or(MedalError::AccessDenied)?;
            if group.admin != session.id {
                return Err(MedalError::AccessDenied);
            }
Robert Czechowski's avatar
Robert Czechowski committed
589

590
591
592
            user.firstname = Some(firstname);
            user.lastname = Some(lastname);
            user.grade = grade;
593

594
595
596
            if let Some((password, salt)) = password_salt {
                user.password = Some(password);
                user.salt = Some(salt);
597
            }
Daniel Brüning's avatar
Daniel Brüning committed
598

599
            conn.save_session(user);
Robert Czechowski's avatar
Robert Czechowski committed
600
        }
601
    }
602

603
    Ok(result)
Robert Czechowski's avatar
Robert Czechowski committed
604
605
}

606
607
608
609
610
611
612
613
614
615
616
617
618
619
#[derive(PartialEq)]
pub enum UserType {
    User,
    Teacher,
    Admin,
}

pub enum UserGender {
    Female,
    Male,
    Unknown,
}

pub struct ForeignUserData {
Robert Czechowski's avatar
Robert Czechowski committed
620
    pub foreign_id: u32,
621
    pub foreign_type: UserType,
Robert Czechowski's avatar
Robert Czechowski committed
622
623
624
    pub gender: UserGender,
    pub firstname: String,
    pub lastname: String,
625
626
}

Robert Czechowski's avatar
Robert Czechowski committed
627
628
629
630
631
632
633
634
635
pub fn login_oauth<T: MedalConnection>(conn: &T, user_data: ForeignUserData)
                                       -> Result<String, (String, json_val::Map<String, json_val::Value>)> {
    match conn.login_foreign(None,
                             user_data.foreign_id,
                             user_data.foreign_type,
                             &user_data.firstname,
                             &user_data.lastname)
    {
        Ok(session_token) => Ok(session_token),
636
637
638
        Err(()) => {
            let mut data = json_val::Map::new();
            data.insert("reason".to_string(), to_json(&"OAuth-Login failed.".to_string()));
639
            Err(("login".to_owned(), data))
640
641
642
        }
    }
}
643
644
645
646
647
648
649
650
651
652

pub trait SetPassword {
    fn set_password(&mut self, &str) -> Option<()>;
}
impl SetPassword for SessionUser {
    fn set_password(&mut self, password: &str) -> Option<()> {
        let salt: String = thread_rng().sample_iter(&Alphanumeric).take(10).collect();
        let hash = hash_password(password, &salt).ok()?;

        self.password = Some(hash);
653
        self.salt = Some(salt);
654
655
656
        Some(())
    }
}