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

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

5
6
use time;

7
use rand::{thread_rng, Rng,  distributions::Alphanumeric};
8

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

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

Daniel Brüning's avatar
Daniel Brüning 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
}

Daniel Brüning's avatar
Daniel Brüning 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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

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>) {
    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
83
84
85
86
87
88
    let mut data = json_val::Map::new();

    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,
89
90
        public: c.public,
        tasks: Vec::new(),
Robert Czechowski's avatar
Robert Czechowski committed
91
92
93
94
95
96
97
    }}).collect();
    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 {
Daniel Brüning's avatar
Daniel Brüning committed
109
            let mut blackstars :usize = 0;
110
111
112
113
114
115
            if not_print_yet && st.stars >= grade.grade.unwrap_or(0) {
                blackstars = grade.grade.unwrap_or(0) as usize;
                not_print_yet = false;
            }

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

Robert Czechowski's avatar
Robert Czechowski committed
122
123
124
125
126
127
    let ci = ContestInfo {
        id: c.id.unwrap(),
        location: c.location.clone(),
        filename: c.filename.clone(),
        name: c.name.clone(),
        duration: c.duration,
128
129
        public: c.public,
        tasks: tasks,
Robert Czechowski's avatar
Robert Czechowski committed
130
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 => {
146
            Ok(("contest".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
147
148
149
150
151
152
153
154
155
        },
        Some(participation) => {
            let now = time::get_time();
            let passed_secs = now.sec - participation.start.sec;
            if passed_secs < 0 {
                // behandle inkonsistente Serverzeit
            }

            data.insert("participation_start_date".to_string(), to_json(&format!("{}",passed_secs)));
156

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


Robert Czechowski's avatar
Robert Czechowski committed
162
163
164
165
            }
            else {
                let left_min = left_secs / 60;
                let left_sec = left_secs % 60;
166
                if left_sec < 10 {
Robert Czechowski's avatar
Robert Czechowski committed
167
168
169
170
171
172
                    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)));
                }
            }
173

174
            Ok(("contest".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
175
176
177
178
        }
    }
}

179
pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: u32, session_token: String) -> MedalValueResult {
180
    let session = conn.get_session(&session_token).ok_or(MedalError::AccessDenied)?.ensure_alive().ok_or(MedalError::AccessDenied)?; // TODO SessionTimeout?
181
182
183
184
185
186
187
    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
188
189
        //TODO: use user
        for (_user, userdata) in groupdata {
190
191
            let mut userresults: Vec<String> = Vec::new();

192
193
194
            userresults.push(String::new());
            let mut summe = 0;

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

204
205
            userresults[0] = format!("{}", summe);

206
207
            groupresults.push((format!("Name"), userresults))
        }
208

209
210
211
212
213
214
        results.push((format!("{}",group.name), groupresults));
    }

    let mut data = json_val::Map::new();
    data.insert("taskname".to_string(), to_json(&tasknames));
    data.insert("result".to_string(), to_json(&results));
215
216
217
218
219
220
221
222
223
224
225
226

    let c = conn.get_contest_by_id(contest_id);
    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(),
    };
    data.insert("contest".to_string(), to_json(&ci));
227

228
229
230
    Ok(("contestresults".to_owned(), data))
}

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

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


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

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

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

Robert Czechowski's avatar
Robert Czechowski committed
280

281
pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) {
282
    session_token.map(|token| conn.logout(&token));
Robert Czechowski's avatar
Robert Czechowski committed
283
284
}

Robert Czechowski's avatar
Robert Czechowski committed
285

286
pub fn load_submission<T: MedalConnection>(conn: &T, task_id: u32, session_token: String, subtask: Option<String>) -> MedalResult<String> {
287
288
    let session = conn.get_session(&session_token).ok_or(MedalError::AccessDenied)?.ensure_alive().ok_or(MedalError::AccessDenied)?; // TODO SessionTimeout

289
290
291
292
    match match subtask {
        Some(s) => conn.load_submission(&session, task_id, Some(&s)),
        None => conn.load_submission(&session, task_id, None)
        } {
293

Robert Czechowski's avatar
Robert Czechowski committed
294
295
296
297
298
        Some(submission) => Ok(submission.value),
        None => Ok("{}".to_string())
    }
}

299
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> {
300
    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
301
302

    if session.csrf_token != csrf_token {
303
        return Err(MedalError::AccessDenied); // CsrfError
Robert Czechowski's avatar
Robert Czechowski committed
304
305
306
307
308
309
    }

    let submission = Submission {
        id: None,
        session_user: session.id,
        task: task_id,
310
        grade: grade,
Robert Czechowski's avatar
Robert Czechowski committed
311
        validated: false,
312
        nonvalidated_grade: grade,
Robert Czechowski's avatar
Robert Czechowski committed
313
        needs_validation: true,
314
        subtask_identifier: subtask,
Robert Czechowski's avatar
Robert Czechowski committed
315
316
317
        value: data,
        date: time::get_time()
    };
318

Robert Czechowski's avatar
Robert Czechowski committed
319
320
321
322
323
324
    conn.submit_submission(submission);

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


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

328
329
330
    let (t, tg, c) = conn.get_task_by_id_complete(task_id);

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

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

334
    data.insert("name".to_string(), to_json(&tg.name));
Robert Czechowski's avatar
Robert Czechowski committed
335
336
    data.insert("taskid".to_string(), to_json(&task_id));
    data.insert("csrftoken".to_string(), to_json(&session.csrf_token));
337
    data.insert("taskpath".to_string(), to_json(&taskpath));
Robert Czechowski's avatar
Robert Czechowski committed
338

339
    Ok(("task".to_owned(), data))
340

Robert Czechowski's avatar
Robert Czechowski committed
341
}
Robert Czechowski's avatar
Robert Czechowski committed
342
343
344
345
//?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
346
347
#[derive(Serialize, Deserialize)]
pub struct GroupInfo {
348
    pub id: u32,
Robert Czechowski's avatar
Robert Czechowski committed
349
350
351
352
353
    pub name: String,
    pub tag: String,
    pub code: String,
}

354
pub fn show_groups<T: MedalConnection>(conn: &T, session_token: String) -> MedalValueResult {
355
    let session = conn.get_session_or_new(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
356
357
358
359
360
361

//    let groupvec = conn.get_group(session_token);

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

    let v : Vec<GroupInfo> = conn.get_groups(session.id).iter().map(|g| { GroupInfo {
Robert Czechowski's avatar
Robert Czechowski committed
362
        id: g.id.unwrap(),
Robert Czechowski's avatar
Robert Czechowski committed
363
364
365
366
367
368
        name: g.name.clone(),
        tag: g.tag.clone(),
        code: g.groupcode.clone(),
    }}).collect();
    data.insert("group".to_string(), to_json(&v));
    data.insert("csrftoken".to_string(), to_json(&session.csrf_token));
369

370
    Ok(("groups".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
371
372
}

Robert Czechowski's avatar
Robert Czechowski committed
373
374
#[derive(Serialize, Deserialize)]
pub struct MemberInfo {
375
    pub id: u32,
Robert Czechowski's avatar
Robert Czechowski committed
376
377
378
379
380
381
    pub firstname: String,
    pub lastname: String,
    pub grade: u8,
    pub logincode: String,
}

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

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

Robert Czechowski's avatar
Robert Czechowski committed
388
    if group.admin != session.id {
389
        return Err(MedalError::AccessDenied);
Robert Czechowski's avatar
Robert Czechowski committed
390
391
    }

Robert Czechowski's avatar
Robert Czechowski committed
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,
401
402
        firstname: m.firstname.clone().unwrap_or_else(|| "".to_string()),
        lastname: m.lastname.clone().unwrap_or_else(|| "".to_string()),
Robert Czechowski's avatar
Robert Czechowski committed
403
        grade: m.grade,
404
        logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()),
Robert Czechowski's avatar
Robert Czechowski committed
405
406
407
408
    }}).collect();

    data.insert("group".to_string(), to_json(&gi));
    data.insert("member".to_string(), to_json(&v));
Robert Czechowski's avatar
Robert Czechowski committed
409
410
411

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

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

417
pub fn add_group<T: MedalConnection>(conn: &T, session_token: String, csrf_token: String, name: String, tag: String) -> MedalResult<u32> {
418
    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
419
420

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

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

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

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

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

Daniel Brüning's avatar
Daniel Brüning committed
443
#[allow(dead_code)]
444
pub fn show_groups_results<T: MedalConnection>(conn: &T, contest_id: u32, session_token: String) -> MedalValueResult {
445
    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
446
447
    //TODO: use g
    let _g = conn.get_contest_groups_grades(session.id, contest_id);
448

449
    let data = json_val::Map::new();
450
451
452

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

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

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

459
460
461
462
463
464
465
466
467
468
469
470
471
    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));
472

473
474

            if let Some(query) = query_string {
475
                if query.starts_with("status=") {
476
                    let status: &str = &query[7..];
Daniel Brüning's avatar
Daniel Brüning committed
477
                    if ["NothingChanged","DataChanged","PasswordChanged","PasswordMissmatch"].contains(&status) {
478
479
                        data.insert((status).to_string(), to_json(&true));
                    }
480
481
                }
            }
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> {
512
513
514
   let password_and_salt = [password, salt].concat().to_string();
   match hash(password_and_salt, 5) {
       Ok(result) => Ok(result),
Daniel Brüning's avatar
Daniel Brüning committed
515
       Err(_) => Err(MedalError::PasswordHashingError)
516
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
527
528
529
}
impl std::convert::Into<String> for ProfileStatus {
    fn into(self) -> String {
        format!("{:?}", self)
    }
530
531
}

532
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> {
533
    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
534
535

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

539
540
541
542
543
    if session.firstname.as_ref() == Some(&firstname)
        && session.lastname.as_ref() == Some(&lastname)
        && session.grade == grade
        && password == ""
        && password_repeat == "" {
Daniel Brüning's avatar
Daniel Brüning committed
544
            return Ok(ProfileStatus::NothingChanged);
545
546
        }

Daniel Brüning's avatar
Daniel Brüning committed
547
    let mut result = ProfileStatus::DataChanged;
548
549

    let mut password_salt = None;
550

551
552
553
    if password != "" || password_repeat != "" {
        if password == password_repeat {
            let salt: String = thread_rng().sample_iter(&Alphanumeric).take(10).collect();
554
            let hash = hash_password(&password, &salt)?;
Daniel Brüning's avatar
Daniel Brüning committed
555

556
            password_salt = Some((hash, salt));
Daniel Brüning's avatar
Daniel Brüning committed
557
            result = ProfileStatus::PasswordChanged;
558
        } else {
Daniel Brüning's avatar
Daniel Brüning committed
559
            result = ProfileStatus::PasswordMissmatch;
560
        }
561
562
    }

563
564
565
566
567
    match user_id {
        None => {
            session.firstname = Some(firstname);
            session.lastname = Some(lastname);
            session.grade = grade;
568

569
570
571
            if let Some((password, salt))  = password_salt {
                session.password = Some(password);
                session.salt = Some(salt);
572
            }
Daniel Brüning's avatar
Daniel Brüning committed
573

574
575
576
577
578
579
580
581
582
            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
583

584
585
586
            user.firstname = Some(firstname);
            user.lastname = Some(lastname);
            user.grade = grade;
587

588
589
590
            if let Some((password, salt)) = password_salt {
                user.password = Some(password);
                user.salt = Some(salt);
591
            }
Daniel Brüning's avatar
Daniel Brüning committed
592

593
594
595
            conn.save_session(user);
         }
    }
596

597
    Ok(result)
Robert Czechowski's avatar
Robert Czechowski committed
598
599
}

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

pub enum UserGender {
    Female,
    Male,
    Unknown,
}

pub struct ForeignUserData {
    pub foreign_id:   u32,
    pub foreign_type: UserType,
    pub gender:       UserGender,
    pub firstname:    String,
    pub lastname:     String,
}

Robert Czechowski's avatar
Robert Czechowski committed
621

622
pub fn login_oauth<T: MedalConnection>(conn: &T, user_data: ForeignUserData) -> Result<String, (String, json_val::Map<String, json_val::Value>)> {
623
    match conn.login_foreign(None, user_data.foreign_id, user_data.foreign_type, &user_data.firstname, &user_data.lastname) {
624
625
626
627
628
629
        Ok(session_token) => {
            Ok(session_token)
        },
        Err(()) => {
            let mut data = json_val::Map::new();
            data.insert("reason".to_string(), to_json(&"OAuth-Login failed.".to_string()));
630
            Err(("login".to_owned(), data))
631
632
633
634
        }
    }

}
635
636
637
638
639
640
641
642
643
644

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);
645
        self.salt = Some(salt);
646
647
648
        Some(())
    }
}