1use time;
16
17use config::OauthProvider;
18use db_conn::MedalConnection;
19#[cfg(feature = "signup")]
20use db_conn::SignupResult;
21use db_objects::OptionSession;
22use db_objects::SessionUser;
23use db_objects::{Contest, Grade, Group, Participation, Submission, Taskgroup};
24use helpers;
25use webauthn_rs::prelude as webauthn;
26use webfw_iron::{json_val, to_json};
27
28#[derive(Serialize, Deserialize)]
29pub struct SubTaskInfo {
30 pub id: i32,
31 pub linktext: String,
32 pub active: bool,
33 pub greyout: bool,
34}
35
36#[derive(Serialize, Deserialize)]
37pub struct TaskInfo {
38 pub name: String,
39 pub subtasks: Vec<SubTaskInfo>,
40}
41
42#[derive(Clone, Serialize, Deserialize)]
43pub struct ContestInfo {
44 pub id: i32,
45 pub name: String,
46 pub duration: i32,
47 pub public: bool,
48 pub requires_login: bool,
49 pub image: Option<String>,
50 pub language: Option<String>,
51 pub category: Option<String>,
52 pub team_participation: bool,
53 pub tags: Vec<String>,
54}
55
56#[derive(Clone, Debug)]
57pub enum MedalError {
58 NotLoggedIn,
59 AccessDenied,
60 CsrfCheckFailed,
61 SessionTimeout,
62 DatabaseError,
63 ConfigurationError,
64 DatabaseConnectionError,
65 PasswordHashingError,
66 UnmatchedPasswords,
67 NotFound,
68 AccountIncomplete,
69 UnknownId,
70 OauthError(String),
71 WebauthnError,
72 ErrorWithJson(json_val::Map<String, json_val::Value>),
73}
74
75pub struct LoginInfo {
76 pub password_login: bool,
77 pub self_url: Option<String>,
78 pub oauth_providers: Option<Vec<OauthProvider>>,
79}
80
81type MedalValue = (String, json_val::Map<String, json_val::Value>);
82type MedalResult<T> = Result<T, MedalError>;
83type MedalValueResult = MedalResult<MedalValue>;
84
85type JsonValue = json_val::Map<String, json_val::Value>;
86type JsonValueResult = MedalResult<JsonValue>;
87
88fn fill_user_data_prefix(session: &SessionUser, data: &mut json_val::Map<String, serde_json::Value>, prefix: &str) {
89 data.insert(prefix.to_string() + "username", to_json(&session.username));
90 data.insert(prefix.to_string() + "firstname", to_json(&session.firstname));
91 data.insert(prefix.to_string() + "lastname", to_json(&session.lastname));
92 data.insert(prefix.to_string() + "teacher", to_json(&session.is_teacher));
93 data.insert(prefix.to_string() + "is_teacher", to_json(&session.is_teacher));
94 data.insert(prefix.to_string() + "teacher_schoolname", to_json(&session.school_name));
95 data.insert(prefix.to_string() + "admin", to_json(&session.is_admin));
96 data.insert(prefix.to_string() + "is_admin", to_json(&session.is_admin));
97 data.insert(prefix.to_string() + "logged_in", to_json(&session.is_logged_in()));
98 data.insert(prefix.to_string() + "csrf_token", to_json(&session.csrf_token));
99 data.insert(prefix.to_string() + "sex",
100 to_json(&(match session.sex {
101 Some(0) | None => "/",
102 Some(1) => "m",
103 Some(2) => "w",
104 Some(3) => "d",
105 Some(4) => "…",
106 _ => "?",
107 })));
108}
109
110fn fill_user_data(session: &SessionUser, data: &mut json_val::Map<String, serde_json::Value>) {
111 fill_user_data_prefix(session, data, "");
112
113 data.insert("parent".to_string(), to_json(&"base"));
114 data.insert("medal_version".to_string(), to_json(&env!("GIT_VERSION")));
115}
116
117fn fill_oauth_data(login_info: LoginInfo, data: &mut json_val::Map<String, serde_json::Value>) {
118 let mut oauth_links: Vec<(String, String, String)> = Vec::new();
119 if let Some(oauth_providers) = login_info.oauth_providers {
120 for oauth_provider in oauth_providers {
121 oauth_links.push((oauth_provider.provider_id.to_owned(),
122 oauth_provider.login_link_text.to_owned(),
123 oauth_provider.url.to_owned()));
124 }
125 }
126
127 data.insert("self_url".to_string(), to_json(&login_info.self_url));
128 data.insert("oauth_links".to_string(), to_json(&oauth_links));
129
130 data.insert("password_login".to_string(), to_json(&login_info.password_login));
131}
132
133fn grade_to_string(grade: i32) -> String {
134 match grade {
135 0 => "Noch kein Schüler".to_string(),
136 n @ 1..=10 => format!("{}", n),
137 11 => "11 (G8)".to_string(),
138 12 => "12 (G8)".to_string(),
139 111 => "11 (G9)".to_string(),
140 112 => "12 (G9)".to_string(),
141 113 => "13 (G9)".to_string(),
142 114 => "Berufsschule".to_string(),
143 255 => "Kein Schüler mehr".to_string(),
144 _ => "?".to_string(),
145 }
146}
147
148fn common_prefix<'a>(a: &'a str, b: &'a str) -> &'a str {
149 let mut ac = a.char_indices();
150 let mut bc = b.char_indices();
151
152 let mut ai;
153 let mut bi;
154 loop {
155 ai = ac.next();
156 bi = bc.next();
157 if ai != bi {
158 break;
159 }
160 if ai.is_none() {
161 break;
162 }
163 }
164 if ai.is_none() {
165 return a;
166 }
167 if bi.is_none() {
168 return b;
169 }
170
171 a.get(..(ai.unwrap().0)).unwrap()
172}
173
174pub fn index<T: MedalConnection>(conn: &T, session_token: Option<String>, login_info: LoginInfo) -> MedalValueResult {
175 let mut data = json_val::Map::new();
176
177 if let Some(token) = session_token {
178 if let Some(session) = conn.get_session(&token) {
179 fill_user_data(&session, &mut data);
180
181 if session.logincode.is_some() && session.firstname.is_none() {
182 return Err(MedalError::AccountIncomplete);
183 }
184 }
185 }
186
187 fill_oauth_data(login_info, &mut data);
188
189 let now = time::get_time();
190 let contest_list = conn.get_contest_list();
191 let mut contests_running: Vec<(String, String, (i64, i64))> =
192 contest_list.iter()
193 .filter(|c| c.public)
194 .filter(|c| c.duration != 0 || c.category == Some("contest".to_string()))
195 .filter(|c| !c.requires_login.unwrap_or(false) || c.category.is_some())
196 .filter(|c| c.end.map(|end| now <= end).unwrap_or(false))
197 .filter(|c| c.start.map(|start| now >= start).unwrap_or(true))
198 .map(|c| {
199 (c.name.clone(),
200 if let Some(cat) = c.category.as_ref() {
201 format!("{}/{}", cat, c.id.unwrap_or(0))
202 } else {
203 format!("{}", c.id.unwrap_or(0))
204 },
205 {
206 let t = c.end.unwrap().sec - now.sec;
207 (t / 60 / 60 / 24, t / 60 / 60 % 24)
208 })
209 })
210 .collect();
211
212 let mut i = 0;
213 while i < contests_running.len() {
214 let mut count = 0;
215 while i + 1 < contests_running.len() {
216 if contests_running[i].2 == contests_running[i + 1].2 {
217 contests_running[i].0 = common_prefix(&contests_running[i].0, &contests_running[i + 1].0).to_string();
220 count += 1;
221 contests_running.remove(i + 1);
222 } else {
223 break;
224 }
225 }
226 if count > 0 {
227 contests_running[i].0 = format!("{}… ({}x)", contests_running[i].0, count + 1);
228 contests_running[i].1 = "?filter=current".to_string();
229 }
230 i += 1;
231 }
232
233 if contests_running.len() > 0 {
234 data.insert("current_contests".to_string(), to_json(&contests_running));
235 }
236
237 let mut contests_comming: Vec<(String, String, (i64, i64))> =
238 contest_list.iter()
239 .filter(|c| c.public)
240 .filter(|c| c.duration != 0 || c.category == Some("contest".to_string()))
241 .filter(|c| !c.requires_login.unwrap_or(false) || c.category.is_some())
242 .filter(|c| c.start.map(|start| now <= start).unwrap_or(false))
243 .map(|c| {
244 (c.name.clone(),
245 if let Some(cat) = c.category.as_ref() {
246 format!("{}/{}", cat, c.id.unwrap_or(0))
247 } else {
248 format!("{}", c.id.unwrap_or(0))
249 },
250 {
251 let t = c.start.unwrap().sec - now.sec;
252 (t / 60 / 60 / 24, t / 60 / 60 % 24)
253 })
254 })
255 .collect();
256
257 let mut i = 0;
258 while i < contests_comming.len() {
259 let mut count = 0;
260 while i + 1 < contests_comming.len() {
261 if contests_comming[i].2 == contests_comming[i + 1].2 {
262 contests_comming[i].0 = common_prefix(&contests_comming[i].0, &contests_comming[i + 1].0).to_string();
265 count += 1;
266 contests_comming.remove(i + 1);
267 } else {
268 break;
269 }
270 }
271 if count > 0 {
272 contests_comming[i].0 = format!("{}… ({}x)", contests_comming[i].0, count + 1);
273 contests_comming[i].1 = "?filter=current".to_string();
274 }
275 i += 1;
276 }
277
278 if contests_comming.len() > 0 {
279 data.insert("upcoming_contests".to_string(), to_json(&contests_comming));
280 }
281
282 data.insert("parent".to_string(), to_json(&"base"));
283 data.insert("index".to_string(), to_json(&true));
284 Ok(("index".to_owned(), data))
285}
286
287pub fn show_login<T: MedalConnection>(conn: &T, session_token: Option<String>, login_info: LoginInfo)
288 -> (String, json_val::Map<String, json_val::Value>) {
289 let mut data = json_val::Map::new();
290
291 if let Some(token) = session_token {
292 if let Some(session) = conn.get_session(&token) {
293 fill_user_data(&session, &mut data);
294 }
295 }
296
297 fill_oauth_data(login_info, &mut data);
298
299 data.insert("parent".to_string(), to_json(&"base"));
300 ("login".to_owned(), data)
301}
302
303pub fn status<T: MedalConnection>(conn: &T, config_secret: Option<String>, given_secret: Option<String>)
304 -> MedalResult<String> {
305 if config_secret == given_secret {
306 Ok(conn.get_debug_information())
307 } else {
308 Err(MedalError::AccessDenied)
309 }
310}
311
312pub fn debug<T: MedalConnection>(conn: &T, session_token: Option<String>)
313 -> (String, json_val::Map<String, json_val::Value>) {
314 let mut data = json_val::Map::new();
315
316 if let Some(token) = session_token {
317 if let Some(session) = conn.get_session(&token) {
318 data.insert("known_session".to_string(), to_json(&true));
319 data.insert("session_id".to_string(), to_json(&session.id));
320 data.insert("now_timestamp".to_string(), to_json(&time::get_time().sec));
321 if let Some(last_activity) = session.last_activity {
322 data.insert("session_timestamp".to_string(), to_json(&last_activity.sec));
323 data.insert("timediff".to_string(), to_json(&(time::get_time() - last_activity).num_seconds()));
324 }
325 if session.is_alive() {
326 data.insert("alive_session".to_string(), to_json(&true));
327 if session.is_logged_in() {
328 data.insert("logged_in".to_string(), to_json(&true));
329 data.insert("username".to_string(), to_json(&session.username));
330 data.insert("firstname".to_string(), to_json(&session.firstname));
331 data.insert("lastname".to_string(), to_json(&session.lastname));
332 data.insert("teacher".to_string(), to_json(&session.is_teacher));
333 data.insert("oauth_provider".to_string(), to_json(&session.oauth_provider));
334 data.insert("oauth_id".to_string(), to_json(&session.oauth_foreign_id));
335 data.insert("logincode".to_string(), to_json(&session.logincode));
336 data.insert("managed_by".to_string(), to_json(&session.managed_by));
337 }
338 }
339 }
340 data.insert("session".to_string(), to_json(&token));
341 } else {
342 data.insert("session".to_string(), to_json(&"No session token given"));
343 }
344
345 ("debug".to_owned(), data)
346}
347
348pub fn debug_create_session<T: MedalConnection>(conn: &T, session_token: Option<String>) {
349 if let Some(token) = session_token {
350 conn.get_session_or_new(&token).unwrap();
351 }
352}
353
354#[derive(PartialEq, Eq, Debug)]
355pub enum ContestVisibility {
356 All,
357 Open,
358 Current,
359 LoginRequired,
360 StandaloneTask,
361 WithSecret(String),
362}
363
364pub fn show_contests<T: MedalConnection>(conn: &T, session_token: &str, category: Option<String>,
365 login_info: LoginInfo, visibility: ContestVisibility, is_results: bool)
366 -> MedalValueResult {
367 let mut data = json_val::Map::new();
368
369 let session = conn.get_session_or_new(&session_token).map_err(|_| MedalError::DatabaseConnectionError)?;
370 fill_user_data(&session, &mut data);
371
372 if session.is_logged_in() {
373 data.insert("can_start".to_string(), to_json(&true));
374 }
375
376 fill_oauth_data(login_info, &mut data);
377
378 let contest_list = if is_results {
379 conn.get_contest_list_with_group_member_participations(session.id)
380 } else {
381 conn.get_contest_list()
382 };
383
384 let now = time::get_time();
385 let v: Vec<ContestInfo> =
386 contest_list.iter()
387 .filter(|c| category.as_ref().map(|cat| c.category.as_ref() == Some(cat)).unwrap_or(true))
388 .filter(|c| c.public || matches!(visibility, ContestVisibility::WithSecret(_)))
389 .filter(|c| {
390 if let ContestVisibility::WithSecret(secret) = &visibility {
391 c.secret.as_ref() == Some(secret)
392 } else {
393 true
394 }
395 })
396 .filter(|c| {
397 (!c.standalone_task.unwrap_or(false))
398 || visibility == ContestVisibility::StandaloneTask
399 || category == Some("standalone_task".to_string())
400 })
401 .filter(|c| c.standalone_task.unwrap_or(false) || visibility != ContestVisibility::StandaloneTask)
402 .filter(|c| {
403 c.end.map(|end| now <= end).unwrap_or(true)
404 || (visibility == ContestVisibility::All && (c.category.is_none() || is_results))
405 })
406 .filter(|c| c.duration == 0 || visibility != ContestVisibility::Open)
407 .filter(|c| c.duration != 0 || visibility != ContestVisibility::Current)
408 .filter(|c| c.requires_login.unwrap_or(false) || visibility != ContestVisibility::LoginRequired)
409 .filter(|c| {
410 !c.requires_login.unwrap_or(false)
411 || visibility == ContestVisibility::LoginRequired
412 || visibility == ContestVisibility::All
413 })
414 .map(|c| ContestInfo { id: c.id.unwrap(),
415 name: c.name.clone(),
416 duration: c.duration,
417 public: c.public,
418 requires_login: c.requires_login.unwrap_or(false),
419 image: c.image.as_ref().map(|i| format!("/{}{}", c.location, i)),
420 language: c.language.clone(),
421 category: c.category.clone(),
422 team_participation: false,
423 tags: c.tags.clone() })
424 .collect();
425
426 if category.is_some() {
427 data.insert("contests".to_string(), to_json(&v));
428 } else {
429 let contests_training: Vec<ContestInfo> =
430 v.clone().into_iter().filter(|c| !c.requires_login).filter(|c| c.duration == 0).collect();
431 let contests_contest: Vec<ContestInfo> =
432 v.clone().into_iter().filter(|c| !c.requires_login).filter(|c| c.duration != 0).collect();
433 let contests_challenge: Vec<ContestInfo> = v.into_iter().filter(|c| c.requires_login).collect();
434
435 data.insert("contests_training".to_string(), to_json(&contests_training));
436 data.insert("contests_contest".to_string(), to_json(&contests_contest));
437 data.insert("contests_challenge".to_string(), to_json(&contests_challenge));
438
439 data.insert("contests_training_header".to_string(), to_json(&"Trainingsaufgaben"));
440 data.insert("contests_contest_header".to_string(), to_json(&"Wettbewerbe"));
441 data.insert("contests_challenge_header".to_string(), to_json(&"Herausforderungen"));
442
443 if visibility == ContestVisibility::StandaloneTask {
444 data.insert("contests_training_header".to_string(), to_json(&"Einzelne Aufgaben ohne Wertung"));
445 }
446 }
447
448 if let ContestVisibility::WithSecret(secret) = visibility {
449 data.insert("secret".to_string(), to_json(&secret));
450 data.insert("has_secret".to_string(), to_json(&true));
451 }
452
453 Ok(("contests".to_owned(), data))
454}
455
456fn generate_subtaskstars(tg: &Taskgroup, grade: &Grade, ast: Option<i32>) -> Vec<SubTaskInfo> {
457 let mut subtaskinfos = Vec::new();
458 let mut not_print_yet = true;
459 for st in &tg.tasks {
460 let mut blackstars: usize = 0;
461 if not_print_yet && st.stars >= grade.grade.unwrap_or(0) {
462 blackstars = grade.grade.unwrap_or(0) as usize;
463 not_print_yet = false;
464 }
465
466 let greyout = not_print_yet && st.stars < grade.grade.unwrap_or(0);
467 let active = ast.is_some() && st.id == ast;
468 let linktext = format!("{}{}",
469 str::repeat("★", blackstars as usize),
470 str::repeat("☆", st.stars as usize - blackstars as usize));
471 let si = SubTaskInfo { id: st.id.unwrap(), linktext, active, greyout };
472
473 subtaskinfos.push(si);
474 }
475 subtaskinfos
476}
477
478#[derive(Serialize, Deserialize)]
479pub struct ContestStartConstraints {
480 pub contest_not_begun: bool,
481 pub contest_over: bool,
482 pub contest_running: bool,
483 pub grade_too_low: bool,
484 pub grade_too_high: bool,
485 pub grade_matching: bool,
486}
487
488fn check_contest_qualification<T: MedalConnection>(conn: &T, session: &SessionUser, contest: &Contest) -> Option<bool> {
489 let required_contests = contest.requires_contest.as_ref()?.split(',');
491
492 for req_contest in required_contests {
493 if conn.has_participation_by_contest_file(session.id, &contest.location, req_contest) {
494 return Some(true);
495 }
496 }
497
498 Some(false)
499}
500
501fn check_contest_constraints(session: &SessionUser, contest: &Contest) -> ContestStartConstraints {
502 let now = time::get_time();
503 let student_grade = session.grade % 100 - if session.grade / 100 == 1 { 1 } else { 0 };
504
505 let contest_not_begun = contest.start.map(|start| now < start).unwrap_or(false);
506 let contest_over = contest.end.map(|end| now > end).unwrap_or(false);
507 let grade_too_low =
508 contest.min_grade.map(|min_grade| student_grade < min_grade && !session.is_teacher).unwrap_or(false);
509 let grade_too_high =
510 contest.max_grade.map(|max_grade| student_grade > max_grade && !session.is_teacher).unwrap_or(false);
511
512 let contest_running = !contest_not_begun && !contest_over;
513 let grade_matching = !grade_too_low && !grade_too_high;
514
515 ContestStartConstraints { contest_not_begun,
516 contest_over,
517 contest_running,
518 grade_too_low,
519 grade_too_high,
520 grade_matching }
521}
522
523#[derive(Serialize, Deserialize)]
524pub struct ContestTimeInfo {
525 pub passed_secs_total: i64,
526 pub left_secs_total: i64,
527 pub left_mins_total: i64,
528 pub left_hour: i64,
529 pub left_min: i64,
530 pub left_sec: i64,
531 pub has_timelimit: bool,
532 pub is_time_left: bool,
533 pub exempt_from_timelimit: bool,
534 pub can_still_compete: bool,
535 pub review_has_timelimit: bool,
536 pub has_future_review: bool,
537 pub has_review_end: bool,
538 pub is_review: bool,
539 pub can_still_compete_or_review: bool,
540
541 pub until_review_start_day: i64,
542 pub until_review_start_hour: i64,
543 pub until_review_start_min: i64,
544
545 pub until_review_end_day: i64,
546 pub until_review_end_hour: i64,
547 pub until_review_end_min: i64,
548}
549
550fn check_contest_time_left(session: &SessionUser, contest: &Contest, participation: &Participation) -> ContestTimeInfo {
551 let now = time::get_time();
552 let passed_secs_total = now.sec - participation.start.sec;
553 if passed_secs_total < 0 {
554 }
556 let left_secs_total = i64::from(contest.duration) * 60 - passed_secs_total;
557
558 let is_time_left = contest.duration == 0 || left_secs_total >= 0;
559 let exempt_from_timelimit = session.is_teacher() || session.is_admin();
560
561 let can_still_compete = is_time_left || exempt_from_timelimit;
562
563 let review_has_timelimit = contest.review_end.is_none() && contest.review_start.is_some();
564 let has_future_review = (contest.review_start.is_some() || contest.review_end.is_some())
565 && contest.review_end.map(|end| end > now).unwrap_or(true);
566 let has_review_end = contest.review_end.is_some();
567 let is_review = !can_still_compete
568 && (contest.review_start.is_some() || contest.review_end.is_some())
569 && contest.review_start.map(|start| now >= start).unwrap_or(true)
570 && contest.review_end.map(|end| now <= end).unwrap_or(true);
571
572 let until_review_start = contest.review_start.map(|start| start.sec - now.sec).unwrap_or(0);
573 let until_review_end = contest.review_end.map(|end| end.sec - now.sec).unwrap_or(0);
574
575 ContestTimeInfo { passed_secs_total,
576 left_secs_total,
577 left_mins_total: left_secs_total / 60,
578 left_hour: left_secs_total / (60 * 60),
579 left_min: (left_secs_total / 60) % 60,
580 left_sec: left_secs_total % 60,
581 has_timelimit: contest.duration != 0,
582 is_time_left,
583 exempt_from_timelimit,
584 can_still_compete,
585 review_has_timelimit,
586 has_future_review,
587 has_review_end,
588 is_review,
589 can_still_compete_or_review: can_still_compete || is_review,
590
591 until_review_start_day: until_review_start / (60 * 60 * 24),
592 until_review_start_hour: (until_review_start / (60 * 60)) % 24,
593 until_review_start_min: (until_review_start / 60) % 60,
594
595 until_review_end_day: until_review_end / (60 * 60 * 24),
596 until_review_end_hour: (until_review_end / (60 * 60)) % 24,
597 until_review_end_min: (until_review_end / 60) % 60 }
598}
599
600pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str,
601 query_string: Option<String>, login_info: LoginInfo, secret: Option<String>)
602 -> MedalResult<Result<MedalValue, i32>> {
603 let session = conn.get_session_or_new(&session_token).unwrap();
604
605 if session.logincode.is_some() && session.firstname.is_none() {
606 return Err(MedalError::AccountIncomplete);
607 }
608
609 let contest = conn.get_contest_by_id_complete(contest_id).ok_or(MedalError::UnknownId)?;
610
611 let mut opt_part = conn.get_participation(session.id, contest_id);
612 let mut grades = if let Some(part) = opt_part.as_ref() {
613 if let Some(team) = part.team {
614 conn.get_contest_user_grades(team, contest_id)
615 } else {
616 conn.get_contest_user_grades(session.id, contest_id)
617 }
618 } else {
619 conn.get_contest_user_grades(session.id, contest_id)
620 };
621
622 let ci = ContestInfo { id: contest.id.unwrap(),
623 name: contest.name.clone(),
624 duration: contest.duration,
625 public: contest.public,
626 requires_login: contest.requires_login.unwrap_or(false),
627 image: None,
628 language: None,
629 category: contest.category.clone(),
630 team_participation: contest.max_teamsize.map(|size| size > 1).unwrap_or(false),
631 tags: Vec::new() };
632
633 let mut data = json_val::Map::new();
634 data.insert("parent".to_string(), to_json(&"base"));
635 data.insert("empty".to_string(), to_json(&"empty"));
636 data.insert("contest".to_string(), to_json(&ci));
637 data.insert("title".to_string(), to_json(&ci.name));
638 data.insert("message".to_string(), to_json(&contest.message));
639 fill_oauth_data(login_info, &mut data);
640
641 if secret.is_some() && secret != contest.secret {
642 return Err(MedalError::AccessDenied);
643 }
644
645 let has_secret = contest.secret.is_some();
646 let mut require_secret = false;
647 if has_secret {
648 data.insert("secret_field".to_string(), to_json(&true));
649
650 if secret.is_some() {
651 data.insert("secret_field_prefill".to_string(), to_json(&secret));
652 } else {
653 require_secret = true;
654 }
655 }
656
657 let constraints = check_contest_constraints(&session, &contest);
658 let is_qualified = check_contest_qualification(conn, &session, &contest).unwrap_or(true);
659
660 let has_tasks = contest.taskgroups.len() > 0;
661 let can_start = constraints.contest_running
662 && constraints.grade_matching
663 && is_qualified
664 && (has_tasks || has_secret)
665 && (session.is_logged_in() || contest.secret.is_some() && !contest.requires_login.unwrap_or(false));
666
667 let has_duration = contest.duration > 0;
668
669 data.insert("constraints".to_string(), to_json(&constraints));
670 data.insert("is_qualified".to_string(), to_json(&is_qualified));
671 data.insert("has_duration".to_string(), to_json(&has_duration));
672 data.insert("can_start".to_string(), to_json(&can_start));
673 data.insert("has_tasks".to_string(), to_json(&has_tasks));
674 data.insert("no_tasks".to_string(), to_json(&!has_tasks));
675
676 if opt_part.is_none()
681 && contest.duration == 0
682 && constraints.contest_running
683 && constraints.grade_matching
684 && !require_secret
685 && contest.requires_login != Some(true)
686 {
687 conn.new_participation(session.id, contest_id, None).map_err(|_| MedalError::AccessDenied)?;
688 opt_part = Some(Participation { contest: contest_id, user: session.id, start: time::get_time(), team: None });
689 }
690
691 let now = time::get_time();
692 if let Some(start) = contest.start {
693 if now < start {
694 let until = start - now;
695 data.insert("time_until_start".to_string(),
696 to_json(&[until.num_days(), until.num_hours() % 24, until.num_minutes() % 60]));
697 }
698 }
699
700 if let Some(end) = contest.end {
701 if now < end {
702 let until = end - now;
703 data.insert("time_until_end".to_string(),
704 to_json(&[until.num_days(), until.num_hours() % 24, until.num_minutes() % 60]));
705 }
706 }
707
708 if session.is_logged_in() {
709 data.insert("logged_in".to_string(), to_json(&true));
710 data.insert("username".to_string(), to_json(&session.username));
711 data.insert("firstname".to_string(), to_json(&session.firstname));
712 data.insert("lastname".to_string(), to_json(&session.lastname));
713 data.insert("teacher".to_string(), to_json(&session.is_teacher));
714 data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
715 }
716
717 if let Some(participation) = opt_part {
718 let time_info = check_contest_time_left(&session, &contest, &participation);
719 data.insert("time_info".to_string(), to_json(&time_info));
720
721 let time_left_formatted =
722 format!("{}:{:02}:{:02}", time_info.left_hour, time_info.left_min, time_info.left_sec);
723 data.insert("time_left_formatted".to_string(), to_json(&time_left_formatted));
724
725 if let Some(team) = participation.team {
727 if team != session.id {
729 if time_info.is_time_left {
730 data.insert("block_team_participation".to_string(), to_json(&true));
732 } else {
733 grades = conn.get_contest_user_grades(team, contest_id);
735 }
736 }
737 }
738
739 let mut totalgrade = 0;
740 let mut max_totalgrade = 0;
741
742 let mut tasks = Vec::new();
743 for (taskgroup, grade) in contest.taskgroups.into_iter().zip(grades) {
744 let subtaskstars = generate_subtaskstars(&taskgroup, &grade, None);
745 let ti = TaskInfo { name: taskgroup.name, subtasks: subtaskstars };
746 tasks.push(ti);
747
748 totalgrade += grade.grade.unwrap_or(0);
749 max_totalgrade += taskgroup.tasks.iter().map(|x| x.stars).max().unwrap_or(0);
750 }
751 let relative_points = if max_totalgrade > 0 { (totalgrade * 100) / max_totalgrade } else { 0 };
752
753 data.insert("tasks".to_string(), to_json(&tasks));
754
755 data.insert("is_started".to_string(), to_json(&true));
756 data.insert("total_points".to_string(), to_json(&totalgrade));
757 data.insert("max_total_points".to_string(), to_json(&max_totalgrade));
758 data.insert("relative_points".to_string(), to_json(&relative_points));
759 data.insert("lean_page".to_string(), to_json(&true));
760
761 if has_tasks && contest.standalone_task.unwrap_or(false) {
762 return Ok(Err(tasks[0].subtasks[0].id));
763 }
764 }
765
766 if let Some(query_string) = query_string {
767 if !query_string.starts_with("bare") {
768 data.insert("not_bare".to_string(), to_json(&true));
769 }
770
771 if query_string.contains("team_participation=no_logincode") {
772 data.insert("team_error".to_string(), to_json(&"Kein Logincode angegeben"));
773 }
774 if query_string.contains("team_participation=invalid_logincode") {
775 data.insert("team_error".to_string(), to_json(&"Ungültiger Logincode angegeben"));
776 }
777 if query_string.contains("team_participation=own_logincode") {
778 data.insert("team_error".to_string(), to_json(&"Eigener Logincode angegeben"));
779 }
780 if query_string.contains("team_participation=logincode_has_participation") {
781 data.insert("team_error".to_string(), to_json(&"Logincode hat diesen Wettbewerb bereits gestartet"));
782 }
783 } else {
784 data.insert("not_bare".to_string(), to_json(&true));
785 }
786
787 Ok(Ok(("contest".to_owned(), data)))
788}
789
790pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
791 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
792 let mut data = json_val::Map::new();
793 fill_user_data(&session, &mut data);
794
795 let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);
796
797 #[derive(Serialize, Deserialize)]
798 struct UserResults {
799 firstname: String,
800 lastname: String,
801 user_id: i32,
802 grade: String,
803 logincode: String,
804 annotation: String,
805 annotation_result: String,
806 results: Vec<String>,
807 }
808
809 #[derive(Serialize, Deserialize)]
810 struct GroupResults {
811 groupname: String,
812 group_id: i32,
813 groupcode: String,
814 user_results: Vec<UserResults>,
815 }
816
817 let mut results: Vec<GroupResults> = Vec::new();
818 let mut has_annotations = false;
819 let mut has_annotations_result = false;
820
821 for (group, groupdata) in resultdata {
822 let mut groupresults: Vec<UserResults> = Vec::new();
823
824 for (user, userdata) in groupdata {
825 let mut userresults: Vec<String> = Vec::new();
826
827 userresults.push(String::new());
828 let mut summe = 0;
829
830 for grade in userdata {
831 if let Some(g) = grade.grade {
832 userresults.push(format!("{}", g));
833 summe += g;
834 } else {
835 userresults.push("–".to_string());
836 }
837 }
838
839 userresults[0] = format!("{}", summe);
840
841 let mut annotation = "".to_string();
842 let mut annotation_result = "".to_string();
843 if let Some(annotation_combined) = user.annotation {
844 let mut split = annotation_combined.splitn(2, '\x1f');
845
846 if let Some(ann) = split.next() {
847 if ann.len() > 0 {
848 annotation = ann.to_string();
849 has_annotations = true;
850 }
851 }
852
853 if let Some(ann) = split.next() {
854 if ann.len() > 0 {
855 annotation_result = ann.to_string();
856 has_annotations_result = true;
857 }
858 }
859 }
860
861 groupresults.push(UserResults { firstname: user.firstname.unwrap_or_else(|| "–".to_string()),
862 lastname: user.lastname.unwrap_or_else(|| "–".to_string()),
863 user_id: user.id,
864 grade: grade_to_string(user.grade),
865 logincode: user.logincode.unwrap_or_else(|| "".to_string()),
866 annotation,
867 annotation_result,
868 results: userresults });
869 }
870
871 results.push(GroupResults { groupname: group.name.to_string(),
872 group_id: group.id.unwrap_or(0),
873 groupcode: group.groupcode,
874 user_results: groupresults });
875 }
876
877 data.insert("taskname".to_string(), to_json(&tasknames));
878 data.insert("result".to_string(), to_json(&results));
879 data.insert("has_annotations".to_string(), to_json(&has_annotations));
880 data.insert("has_annotations_result".to_string(), to_json(&has_annotations_result));
881
882 let c = conn.get_contest_by_id(contest_id).ok_or(MedalError::UnknownId)?;
883 let ci = ContestInfo { id: c.id.unwrap(),
884 name: c.name.clone(),
885 duration: c.duration,
886 public: c.public,
887 requires_login: c.requires_login.unwrap_or(false),
888 image: None,
889 language: None,
890 category: c.category.clone(),
891 team_participation: false,
892 tags: Vec::new() };
893
894 data.insert("contest".to_string(), to_json(&ci));
895 data.insert("contestname".to_string(), to_json(&c.name));
896
897 Ok(("contestresults".to_owned(), data))
898}
899
900pub fn start_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str, csrf_token: &str,
901 secret: Option<String>, logincode_team: Option<String>)
902 -> MedalResult<Result<(), String>> {
903 let session = conn.get_session_or_new(&session_token).unwrap();
905 let contest = conn.get_contest_by_id(contest_id).ok_or(MedalError::UnknownId)?;
906
907 if contest.duration != 0
909 && !session.is_logged_in()
910 && (contest.requires_login.unwrap_or(false) || contest.secret.is_none())
911 {
912 return Err(MedalError::AccessDenied);
913 }
914
915 if session.is_logged_in() && session.csrf_token != csrf_token {
917 return Err(MedalError::CsrfCheckFailed);
918 }
919
920 let constraints = check_contest_constraints(&session, &contest);
922
923 if !(constraints.contest_running && constraints.grade_matching) {
924 return Err(MedalError::AccessDenied);
925 }
926
927 let is_qualified = check_contest_qualification(conn, &session, &contest);
928
929 if is_qualified == Some(false) {
930 return Err(MedalError::AccessDenied);
931 }
932
933 if contest.secret != secret {
934 return Err(MedalError::AccessDenied);
935 }
936
937 if let Some(logincode_team) = logincode_team {
938 match contest.max_teamsize {
939 None => return Err(MedalError::AccessDenied), Some(max_teamsize) => {
941 if max_teamsize < 2 {
942 return Err(MedalError::AccessDenied); }
944 }
945 };
946
947 if logincode_team == "" {
948 return Ok(Err("no_logincode".to_string()));
949 }
950
951 let teampartner = conn.get_user_and_group_by_logincode(&logincode_team);
952 if let Some((teampartner_user, _)) = teampartner {
953 if teampartner_user.id == session.id {
954 return Ok(Err("own_logincode".to_string()));
955 }
956
957 if conn.get_participation(teampartner_user.id, contest_id).is_some() {
958 return Ok(Err("logincode_has_participation".to_string()));
959 }
960
961 if conn.get_participation(session.id, contest_id).is_some() {
962 return Err(MedalError::AccessDenied); }
964
965 let res = conn.new_participation(session.id, contest_id, Some(session.id));
969 let _ = conn.new_participation(teampartner_user.id, contest_id, Some(session.id));
970 return match res {
971 Ok(_) => Ok(Ok(())),
972 _ => Err(MedalError::AccessDenied), };
974 } else {
975 return Ok(Err("invalid_logincode".to_string()));
976 }
977 }
978
979 match conn.new_participation(session.id, contest_id, None) {
981 Ok(_) => Ok(Ok(())),
982 _ => Err(MedalError::AccessDenied), }
984}
985
986pub fn login<T: MedalConnection>(conn: &T, login_data: (String, String), login_info: LoginInfo)
987 -> Result<String, MedalValue> {
988 let (username, password) = login_data;
989
990 match conn.login(None, &username, &password) {
991 Ok(session_token) => Ok(session_token),
992 Err(()) => {
993 let mut data = json_val::Map::new();
994 data.insert("reason".to_string(), to_json(&"Login fehlgeschlagen. Bitte erneut versuchen.".to_string()));
995 data.insert("username".to_string(), to_json(&username));
996 data.insert("parent".to_string(), to_json(&"base"));
997
998 fill_oauth_data(login_info, &mut data);
999
1000 Err(("login".to_owned(), data))
1001 }
1002 }
1003}
1004
1005pub fn login_with_code<T: MedalConnection>(
1006 conn: &T, code: &str, login_info: LoginInfo)
1007 -> Result<Result<String, String>, (String, json_val::Map<String, json_val::Value>)> {
1008 match conn.login_with_code(None, &code.trim()) {
1009 Ok(session_token) => Ok(Ok(session_token)),
1010 Err(()) => match conn.create_user_with_groupcode(None, &code.trim()) {
1011 Ok(session_token) => Ok(Err(session_token)),
1012 Err(()) => {
1013 let mut data = json_val::Map::new();
1014 data.insert("reason".to_string(), to_json(&"Kein gültiger Code. Bitte erneut versuchen.".to_string()));
1015 data.insert("code".to_string(), to_json(&code));
1016 data.insert("parent".to_string(), to_json(&"base"));
1017
1018 fill_oauth_data(login_info, &mut data);
1019
1020 Err(("login".to_owned(), data))
1021 }
1022 },
1023 }
1024}
1025
1026fn webauthn(self_url: &str) -> webauthn::Webauthn {
1027 use webauthn_rs::prelude::*;
1028
1029 let rp_id = self_url.split("://").nth(1).unwrap().split('/').next().unwrap().split(':').next().unwrap();
1031 let rp_origin = Url::parse(self_url).expect("Invalid URL");
1032 WebauthnBuilder::new(rp_id, &rp_origin).expect("Invalid configuration").build().expect("Invalid configuration")
1033}
1034
1035pub fn register_key_challenge<T: MedalConnection>(conn: &T, self_url: &str, session_token: &str) -> JsonValueResult {
1036 let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
1037
1038 let webauthn = webauthn(self_url);
1039
1040 let cred_ids = conn.get_all_webauthn_credentials();
1041
1042 let (ccr, skr) =
1044 webauthn.start_passkey_registration(webauthn::Uuid::from_u64_pair(0, session.id as u64),
1045 session.firstname.as_ref().unwrap_or(&("".to_string())),
1046 session.firstname.as_ref().unwrap_or(&("".to_string())),
1047 Some(cred_ids.into_iter()
1048 .map(|x| serde_json::from_str(&format!("\"{}\"", x)).unwrap())
1049 .collect()))
1050 .expect("Failed to start webauthn registration.");
1051
1052 conn.set_webauthn_passkey_registration(session.id, &serde_json::json!(skr).to_string());
1053
1054 let mut data = json_val::Map::new();
1055 data.insert("challenge".to_string(), to_json(&ccr));
1056 Ok(data)
1057}
1058
1059pub fn register_key<T: MedalConnection>(conn: &T, self_url: &str, session_token: &str, csrf_token: &str,
1060 credential: String, name: String)
1061 -> JsonValueResult {
1062 let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
1063
1064 if session.csrf_token != csrf_token {
1065 return Err(MedalError::CsrfCheckFailed);
1066 }
1067
1068 let registration: String = conn.get_webauthn_passkey_registration(session.id).ok_or(MedalError::WebauthnError)?;
1069
1070 let webauthn = webauthn(self_url);
1071
1072 let registration: webauthn::PasskeyRegistration = serde_json::from_str(®istration).unwrap();
1073 let credential: webauthn::RegisterPublicKeyCredential = serde_json::from_str(&credential).unwrap();
1074
1075 let passkey = webauthn.finish_passkey_registration(&credential, ®istration);
1076
1077 match passkey {
1078 Ok(passkey) => {
1079 if let serde_json::Value::String(cred_id) = serde_json::json!(passkey.cred_id()) {
1080 conn.add_webauthn_passkey(session.id, &cred_id, &serde_json::json!(passkey).to_string(), &name);
1081 } else {
1082 println!("Webauthn: Could not unwrap cred_id from webauthn Passkey");
1083 return Err(MedalError::WebauthnError);
1084 }
1085 }
1086 Err(webauthn::WebauthnError::UserNotVerified) => {
1087 println!("Webauthn: UserNotVerified");
1089 return Err(MedalError::WebauthnError);
1090 }
1091 Err(err) => {
1092 println!("Webauthn: Other error: {:#?}", err);
1093 return Err(MedalError::WebauthnError);
1094 }
1095 }
1096
1097 let data = json_val::Map::new();
1098 Ok(data)
1099}
1100
1101pub fn delete_key<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, token_id: i32)
1102 -> JsonValueResult {
1103 let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
1104
1105 if session.csrf_token != csrf_token {
1106 return Err(MedalError::CsrfCheckFailed);
1107 }
1108
1109 if !conn.get_webauthn_passkey_names_for_user(session.id).into_iter().any(|(id, _name)| id == token_id) {
1110 return Err(MedalError::NotFound);
1111 }
1112
1113 conn.delete_webauthn_passkey(session.id, token_id);
1114
1115 let data = json_val::Map::new();
1116 Ok(data)
1117}
1118
1119pub fn login_with_key_challenge<T: MedalConnection>(conn: &T, self_url: &str) -> JsonValue {
1120 let passkeys = conn.get_all_webauthn_passkeys()
1121 .into_iter()
1122 .map(|passkey| {
1123 let passkey: webauthn::Passkey = serde_json::from_str(&passkey).unwrap();
1124 passkey
1125 })
1126 .collect::<Vec<webauthn::Passkey>>();
1127
1128 let webauthn = webauthn(self_url);
1129
1130 let (challenge, authentication) = webauthn.start_passkey_authentication(&passkeys).unwrap();
1131
1132 let auth_id = conn.store_webauthn_auth_challenge(&serde_json::json!(challenge).to_string(),
1133 &serde_json::json!(authentication).to_string());
1134
1135 let mut data = json_val::Map::new();
1136 data.insert("id".to_string(), to_json(&auth_id));
1137 data.insert("challenge".to_string(), to_json(&challenge));
1138 data
1139}
1140
1141pub fn login_with_key<T: MedalConnection>(conn: &T, self_url: &str, auth_id: i32, credential: &str)
1142 -> Result<String, String> {
1143 let authentication = conn.get_webauthn_auth_challenge_by_id(auth_id).unwrap();
1144
1145 let authentication: webauthn::PasskeyAuthentication = serde_json::from_str(&authentication).unwrap();
1146 let credential: webauthn::PublicKeyCredential = serde_json::from_str(&credential).unwrap();
1147
1148 let webauthn = webauthn(self_url);
1149
1150 match webauthn.finish_passkey_authentication(&credential, &authentication) {
1151 Err(err) => {
1152 println!("Webauthn: Other error: {:#?}", err);
1153 let mut data = json_val::Map::new();
1154 data.insert("reason".to_string(),
1155 to_json(&"Unbekannter Key. Bitte Key zuerst im Profil registrieren.".to_string()));
1156 Err(serde_json::json!(data).to_string())
1157 }
1158 Ok(authresult) => {
1159 if let serde_json::Value::String(cred_id) = serde_json::json!(authresult.cred_id()) {
1160 match conn.login_with_key(None, &cred_id) {
1161 Ok(session_token) => Ok(session_token),
1162 Err(()) => {
1163 let mut data = json_val::Map::new();
1164 println!("Webauthn: Auth fail");
1165 data.insert("reason".to_string(), to_json(&"Key konnte nicht authentifiziert werden. Bitte über anderen Weg einloggen.".to_string()));
1166 Err(serde_json::json!(data).to_string())
1167 }
1168 }
1169 } else {
1170 panic!("Webauthn: Could not unwrap cred_id from webauthn AuthenticationResult")
1171 }
1172 }
1173 }
1174}
1175
1176pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) {
1177 session_token.map(|token| conn.logout(&token));
1178}
1179
1180#[cfg(feature = "signup")]
1181pub fn signup<T: MedalConnection>(conn: &T, session_token: Option<String>, signup_data: (String, String, String))
1182 -> MedalResult<SignupResult> {
1183 let (username, email, password) = signup_data;
1184
1185 if username == "" || email == "" || password == "" {
1186 return Ok(SignupResult::EmptyFields);
1187 }
1188
1189 let salt = helpers::make_salt();
1190 let hash = helpers::hash_password(&password, &salt)?;
1191
1192 let result = conn.signup(&session_token.unwrap(), &username, &email, hash, &salt);
1193 Ok(result)
1194}
1195
1196#[cfg(feature = "signup")]
1197pub fn signupdata(query_string: Option<String>) -> json_val::Map<String, json_val::Value> {
1198 let mut data = json_val::Map::new();
1199 if let Some(query) = query_string {
1200 if let Some(status) = query.strip_prefix("status=") {
1201 if ["EmailTaken", "UsernameTaken", "UserLoggedIn", "EmptyFields"].contains(&status) {
1202 data.insert((status).to_string(), to_json(&true));
1203 }
1204 }
1205 }
1206 data
1207}
1208
1209pub fn load_submission<T: MedalConnection>(conn: &T, task_id: i32, session_token: &str, subtask: Option<String>,
1210 submission_id: Option<i32>)
1211 -> MedalResult<String> {
1212 let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
1213
1214 match submission_id {
1215 None => match conn.load_submission(&session, task_id, subtask.as_deref()) {
1216 Some(submission) => Ok(submission.value),
1217 None => Ok("{}".to_string()),
1218 },
1219 Some(submission_id) => {
1220 let (submission, _, _, _) =
1221 conn.get_submission_by_id_complete_shallow_contest(submission_id).ok_or(MedalError::UnknownId)?;
1222
1223 if submission.user != session.id && !session.is_admin.unwrap_or(false) {
1225 if let Some((_, Some(group))) = conn.get_user_and_group_by_id(submission.user) {
1226 if !group.admins.contains(&session.id) {
1227 return Err(MedalError::AccessDenied);
1229 }
1230 } else {
1231 return Err(MedalError::AccessDenied);
1233 }
1234 }
1235 Ok(submission.value)
1236 }
1237 }
1238}
1239
1240pub fn save_submission<T: MedalConnection>(conn: &T, task_id: i32, session_token: &str, csrf_token: &str,
1241 data: String, grade_percentage: i32, subtask: Option<String>)
1242 -> MedalResult<String> {
1243 let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
1244
1245 if session.csrf_token != csrf_token {
1246 return Err(MedalError::CsrfCheckFailed);
1247 }
1248
1249 let (t, _, contest) = conn.get_task_by_id_complete(task_id).ok_or(MedalError::UnknownId)?;
1250
1251 match conn.get_participation(session.id, contest.id.expect("Value from database")) {
1252 None => return Err(MedalError::AccessDenied),
1253 Some(participation) => {
1254 let time_info = check_contest_time_left(&session, &contest, &participation);
1255 if !time_info.can_still_compete && time_info.left_secs_total < -10 {
1256 return Err(MedalError::AccessDenied);
1257 }
1260 }
1261 }
1262
1263 let grade_rounded = ((grade_percentage * t.stars * 10) / 100 + 5) / 10;
1278
1279 let submission = Submission { id: None,
1298 user: session.id,
1299 task: task_id,
1300 grade: grade_rounded,
1301 validated: false,
1302 nonvalidated_grade: grade_rounded,
1303 needs_validation: true,
1304 subtask_identifier: subtask,
1305 value: data,
1306 date: time::get_time() };
1307
1308 conn.submit_submission(submission);
1309
1310 Ok("{}".to_string())
1311}
1312
1313pub fn show_task<T: MedalConnection>(conn: &T, task_id: i32, session_token: &str, autosaveinterval: u64)
1314 -> MedalResult<Result<MedalValue, (i32, Option<String>)>> {
1315 let session = conn.get_session_or_new(&session_token).unwrap();
1316
1317 let (t, tg, contest) = conn.get_task_by_id_complete(task_id).ok_or(MedalError::UnknownId)?;
1318 let grade = conn.get_taskgroup_user_grade(session.id, tg.id.unwrap()); let tasklist = conn.get_contest_by_id_complete(contest.id.unwrap()).ok_or(MedalError::UnknownId)?; let mut prevtaskgroup: Option<Taskgroup> = None;
1322 let mut nexttaskgroup: Option<Taskgroup> = None;
1323 let mut current_found = false;
1324
1325 let mut subtaskstars = Vec::new();
1326
1327 for taskgroup in tasklist.taskgroups {
1328 if current_found {
1329 nexttaskgroup = Some(taskgroup);
1330 break;
1331 }
1332
1333 if taskgroup.id == tg.id {
1334 current_found = true;
1335 subtaskstars = generate_subtaskstars(&taskgroup, &grade, Some(task_id));
1336 } else {
1337 prevtaskgroup = Some(taskgroup);
1338 }
1339 }
1340
1341 match conn.get_own_participation(session.id, contest.id.expect("Value from database")) {
1342 None => Ok(Err((contest.id.unwrap(), contest.category))),
1343 Some(participation) => {
1344 let mut data = json_val::Map::new();
1345 data.insert("subtasks".to_string(), to_json(&subtaskstars));
1346 data.insert("prevtask".to_string(), to_json(&prevtaskgroup.map(|tg| tg.tasks[0].id)));
1347 data.insert("nexttask".to_string(), to_json(&nexttaskgroup.map(|tg| tg.tasks[0].id))); let time_info = check_contest_time_left(&session, &contest, &participation);
1350 data.insert("time_info".to_string(), to_json(&time_info));
1351
1352 data.insert("time_left_mh_formatted".to_string(),
1353 to_json(&format!("{}:{:02}", time_info.left_hour, time_info.left_min)));
1354 data.insert("time_left_sec_formatted".to_string(), to_json(&format!(":{:02}", time_info.left_sec)));
1355
1356 let auto_save_interval_ms = if autosaveinterval > 0 && autosaveinterval < 31536000000 {
1357 autosaveinterval * 1000
1358 } else {
1359 31536000000
1360 };
1361 data.insert("auto_save_interval_ms".to_string(), to_json(&auto_save_interval_ms));
1362
1363 if time_info.can_still_compete || time_info.is_review {
1364 data.insert("contestname".to_string(), to_json(&contest.name));
1365 data.insert("name".to_string(), to_json(&tg.name));
1366 data.insert("title".to_string(), to_json(&format!("Aufgabe „{}“ in {}", &tg.name, &contest.name)));
1367 data.insert("taskid".to_string(), to_json(&task_id));
1368 data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
1369 data.insert("contestid".to_string(), to_json(&contest.id));
1370 data.insert("readonly".to_string(), to_json(&time_info.is_review));
1371 data.insert("standalone_task".to_string(), to_json(&contest.standalone_task));
1372 data.insert("category".to_string(), to_json(&contest.category));
1373
1374 let (template, tasklocation) = if let Some(language) = t.language {
1375 match language.as_str() {
1376 "blockly" => ("wtask".to_owned(), t.location.as_str()),
1377 "python" => {
1378 data.insert("tasklang".to_string(), to_json(&"python"));
1379 ("wtask".to_owned(), t.location.as_str())
1380 }
1381 _ => ("task".to_owned(), t.location.as_str()),
1382 }
1383 } else {
1384 match t.location.chars().next() {
1385 Some('B') => ("wtask".to_owned(), &t.location[1..]),
1386 Some('P') => {
1387 data.insert("tasklang".to_string(), to_json(&"python"));
1388 ("wtask".to_owned(), &t.location[1..])
1389 }
1390 _ => ("task".to_owned(), t.location.as_str()),
1391 }
1392 };
1393
1394 let taskpath = format!("{}{}", contest.location, &tasklocation);
1395 data.insert("taskpath".to_string(), to_json(&taskpath));
1396
1397 Ok(Ok((template, data)))
1398 } else {
1399 Ok(Err((contest.id.unwrap(), contest.category)))
1401 }
1402 }
1403 }
1404}
1405
1406pub fn review_task<T: MedalConnection>(conn: &T, task_id: i32, session_token: &str, submission_id: i32)
1407 -> MedalResult<Result<MedalValue, i32>> {
1408 let session = conn.get_session_or_new(&session_token).unwrap();
1409
1410 let (submission, t, tg, contest) =
1411 conn.get_submission_by_id_complete_shallow_contest(submission_id).ok_or(MedalError::UnknownId)?;
1412
1413 let grade = Grade { taskgroup: tg.id.unwrap(),
1416 user: session.id,
1417 grade: Some(submission.grade),
1418 validated: submission.validated };
1419
1420 if submission.user != session.id && !session.is_admin.unwrap_or(false) {
1422 if let Some((_, Some(group))) = conn.get_user_and_group_by_id(submission.user) {
1423 if !group.admins.contains(&session.id) {
1424 return Err(MedalError::AccessDenied);
1426 }
1427 } else {
1428 return Err(MedalError::AccessDenied);
1430 }
1431 }
1432
1433 let subtaskstars = generate_subtaskstars(&tg, &grade, Some(task_id)); let mut data = json_val::Map::new();
1436 data.insert("subtasks".to_string(), to_json(&subtaskstars));
1437
1438 let time_info = ContestTimeInfo { passed_secs_total: 0,
1439 left_secs_total: 0,
1440 left_mins_total: 0,
1441 left_hour: 0,
1442 left_min: 0,
1443 left_sec: 0,
1444 has_timelimit: contest.duration != 0,
1445 is_time_left: false,
1446 exempt_from_timelimit: true,
1447 can_still_compete: false,
1448 review_has_timelimit: false,
1449 has_future_review: false,
1450 has_review_end: false,
1451 is_review: true,
1452 can_still_compete_or_review: true,
1453
1454 until_review_start_day: 0,
1455 until_review_start_hour: 0,
1456 until_review_start_min: 0,
1457
1458 until_review_end_day: 0,
1459 until_review_end_hour: 0,
1460 until_review_end_min: 0 };
1461
1462 data.insert("time_info".to_string(), to_json(&time_info));
1463
1464 data.insert("time_left_mh_formatted".to_string(),
1465 to_json(&format!("{}:{:02}", time_info.left_hour, time_info.left_min)));
1466 data.insert("time_left_sec_formatted".to_string(), to_json(&format!(":{:02}", time_info.left_sec)));
1467
1468 data.insert("auto_save_interval_ms".to_string(), to_json(&0));
1469
1470 data.insert("name".to_string(), to_json(&tg.name));
1472 data.insert("title".to_string(), to_json(&format!("Aufgabe „{}“ in {}", &tg.name, &contest.name)));
1473 data.insert("taskid".to_string(), to_json(&task_id));
1474 data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
1475 data.insert("readonly".to_string(), to_json(&time_info.is_review));
1477
1478 data.insert("submission".to_string(), to_json(&submission_id));
1479
1480 let (template, tasklocation) = if let Some(language) = t.language {
1481 match language.as_str() {
1482 "blockly" => ("wtask".to_owned(), t.location.as_str()),
1483 "python" => {
1484 data.insert("tasklang".to_string(), to_json(&"python"));
1485 ("wtask".to_owned(), t.location.as_str())
1486 }
1487 _ => ("task".to_owned(), t.location.as_str()),
1488 }
1489 } else {
1490 match t.location.chars().next() {
1491 Some('B') => ("wtask".to_owned(), &t.location[1..]),
1492 Some('P') => {
1493 data.insert("tasklang".to_string(), to_json(&"python"));
1494 ("wtask".to_owned(), &t.location[1..])
1495 }
1496 _ => ("task".to_owned(), t.location.as_str()),
1497 }
1498 };
1499
1500 let taskpath = format!("{}{}", contest.location, &tasklocation);
1501 data.insert("taskpath".to_string(), to_json(&taskpath));
1502
1503 Ok(Ok((template, data)))
1504}
1505
1506pub fn preview_task<T: MedalConnection>(conn: &T, task_id: i32) -> MedalResult<Result<MedalValue, i32>> {
1507 let (t, tg, contest) = conn.get_task_by_id_complete(task_id).ok_or(MedalError::UnknownId)?;
1508
1509 if !contest.public
1511 || contest.duration != 0
1512 || contest.requires_contest.is_some()
1513 || contest.requires_login == Some(true)
1514 || contest.standalone_task != Some(true)
1515 {
1516 return Err(MedalError::UnknownId);
1517 }
1518
1519 let time_info = ContestTimeInfo { passed_secs_total: 0,
1520 left_secs_total: 0,
1521 left_mins_total: 0,
1522 left_hour: 0,
1523 left_min: 0,
1524 left_sec: 0,
1525 has_timelimit: contest.duration != 0,
1526 is_time_left: false,
1527 exempt_from_timelimit: true,
1528 can_still_compete: false,
1529 review_has_timelimit: false,
1530 has_future_review: false,
1531 has_review_end: false,
1532 is_review: true,
1533 can_still_compete_or_review: true,
1534
1535 until_review_start_day: 0,
1536 until_review_start_hour: 0,
1537 until_review_start_min: 0,
1538
1539 until_review_end_day: 0,
1540 until_review_end_hour: 0,
1541 until_review_end_min: 0 };
1542
1543 let mut data = json_val::Map::new();
1544
1545 data.insert("time_info".to_string(), to_json(&time_info));
1546
1547 data.insert("time_left_mh_formatted".to_string(),
1548 to_json(&format!("{}:{:02}", time_info.left_hour, time_info.left_min)));
1549 data.insert("time_left_sec_formatted".to_string(), to_json(&format!(":{:02}", time_info.left_sec)));
1550
1551 data.insert("auto_save_interval_ms".to_string(), to_json(&0));
1552
1553 data.insert("contestname".to_string(), to_json(&contest.name));
1554 data.insert("name".to_string(), to_json(&tg.name));
1555 data.insert("title".to_string(), to_json(&format!("Aufgabe „{}“ in {}", &tg.name, &contest.name)));
1556 data.insert("taskid".to_string(), to_json(&task_id));
1557 data.insert("contestid".to_string(), to_json(&contest.id));
1558 data.insert("readonly".to_string(), to_json(&time_info.is_review));
1559 data.insert("preview".to_string(), to_json(&true));
1560
1561 let (template, tasklocation) = if let Some(language) = t.language {
1562 match language.as_str() {
1563 "blockly" => ("wtask".to_owned(), t.location.as_str()),
1564 "python" => {
1565 data.insert("tasklang".to_string(), to_json(&"python"));
1566 ("wtask".to_owned(), t.location.as_str())
1567 }
1568 _ => ("task".to_owned(), t.location.as_str()),
1569 }
1570 } else {
1571 match t.location.chars().next() {
1572 Some('B') => ("wtask".to_owned(), &t.location[1..]),
1573 Some('P') => {
1574 data.insert("tasklang".to_string(), to_json(&"python"));
1575 ("wtask".to_owned(), &t.location[1..])
1576 }
1577 _ => ("task".to_owned(), t.location.as_str()),
1578 }
1579 };
1580
1581 let taskpath = format!("{}{}", contest.location, &tasklocation);
1582 data.insert("taskpath".to_string(), to_json(&taskpath));
1583
1584 Ok(Ok((template, data)))
1585}
1586
1587#[derive(Serialize, Deserialize)]
1588pub struct GroupInfo {
1589 pub id: i32,
1590 pub name: String,
1591 pub tag: String,
1592 pub code: String,
1593}
1594
1595pub fn show_groups<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
1596 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
1597
1598 let mut data = json_val::Map::new();
1599 fill_user_data(&session, &mut data);
1600
1601 let v: Vec<GroupInfo> =
1602 conn.get_groups(session.id)
1603 .iter()
1604 .map(|g| GroupInfo { id: g.id.unwrap(),
1605 name: g.name.clone(),
1606 tag: g.tag.clone(),
1607 code: g.groupcode.clone() })
1608 .collect();
1609 data.insert("group".to_string(), to_json(&v));
1610 data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
1611
1612 Ok(("groups".to_string(), data))
1613}
1614
1615#[derive(Serialize, Deserialize)]
1616pub struct MemberInfo {
1617 pub id: i32,
1618 pub firstname: String,
1619 pub lastname: String,
1620 pub sex: String,
1621 pub grade: String,
1622 pub logincode: String,
1623 pub anonymous: bool,
1624}
1625
1626pub fn show_group<T: MedalConnection>(conn: &T, group_id: i32, session_token: &str) -> MedalValueResult {
1627 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
1628 let group = conn.get_group_complete(group_id).unwrap(); let mut data = json_val::Map::new();
1631 fill_user_data(&session, &mut data);
1632
1633 if !group.admins.contains(&session.id) {
1634 return Err(MedalError::AccessDenied);
1635 }
1636
1637 let gi = GroupInfo { id: group.id.unwrap(),
1638 name: group.name.clone(),
1639 tag: group.tag.clone(),
1640 code: group.groupcode.clone() };
1641
1642 let v: Vec<MemberInfo> = group.members
1643 .iter()
1644 .filter_map(|m| {
1645 Some(MemberInfo { id: m.id,
1646 firstname: m.firstname.clone()?,
1647 lastname: m.lastname.clone()?,
1648 sex: (match m.sex {
1649 Some(0) | None => "/",
1650 Some(1) => "m",
1651 Some(2) => "w",
1652 Some(3) => "d",
1653 Some(4) => "…",
1654 _ => "?",
1655 }).to_string(),
1656 grade: grade_to_string(m.grade),
1657 logincode: m.logincode.clone()?,
1658 anonymous: m.anonymous })
1659 })
1660 .collect();
1661
1662 data.insert("group".to_string(), to_json(&gi));
1663 data.insert("member".to_string(), to_json(&v));
1664 data.insert("groupname".to_string(), to_json(&gi.name));
1665
1666 Ok(("group".to_string(), data))
1667}
1668
1669pub fn add_group<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, name: String, tag: String)
1670 -> MedalResult<i32> {
1671 let session = conn.get_session(&session_token)
1672 .ensure_logged_in()
1673 .ok_or(MedalError::AccessDenied)?
1674 .ensure_teacher_or_admin()
1675 .ok_or(MedalError::AccessDenied)?;
1676
1677 if session.csrf_token != csrf_token {
1678 return Err(MedalError::CsrfCheckFailed);
1679 }
1680
1681 let mut groupcode = String::new();
1682 for i in 0..10 {
1683 if i == 9 {
1684 panic!("ERROR: Too many groupcode collisions! Give up ...");
1685 }
1686 groupcode = helpers::make_groupcode();
1687 if !conn.code_exists(&groupcode) {
1688 break;
1689 }
1690 println!("WARNING: Groupcode collision! Retrying ...");
1691 }
1692
1693 let mut group = Group { id: None, name, groupcode, tag, admins: vec![session.id], members: Vec::new() };
1694
1695 conn.add_group(&mut group);
1696
1697 Ok(group.id.unwrap())
1698}
1699
1700pub fn group_csv<T: MedalConnection>(conn: &T, session_token: &str, sex_infos: SexInformation) -> MedalValueResult {
1701 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
1702
1703 let mut data = json_val::Map::new();
1704 data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
1705
1706 data.insert("require_sex".to_string(), to_json(&sex_infos.require_sex));
1707 data.insert("allow_sex_na".to_string(), to_json(&sex_infos.allow_sex_na));
1708 data.insert("allow_sex_diverse".to_string(), to_json(&sex_infos.allow_sex_diverse));
1709 data.insert("allow_sex_other".to_string(), to_json(&sex_infos.allow_sex_other));
1710
1711 Ok(("groupcsv".to_string(), data))
1712}
1713
1714pub fn upload_groups<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, group_data: &str)
1716 -> MedalResult<()> {
1717 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
1718
1719 if session.csrf_token != csrf_token {
1720 return Err(MedalError::CsrfCheckFailed);
1721 }
1722
1723 let mut v: Vec<Vec<String>> = serde_json::from_str(group_data).or(Err(MedalError::AccessDenied))?; v.sort_unstable_by(|a, b| a[0].partial_cmp(&b[0]).unwrap());
1725
1726 let mut groupcode = String::new();
1727 let mut name = String::new();
1728 let mut group = Group { id: None,
1729 name: name.clone(),
1730 groupcode,
1731 tag: String::new(),
1732 admins: vec![session.id],
1733 members: Vec::new() };
1734
1735 for line in v {
1736 if name != line[0] {
1737 if name != "" {
1738 conn.update_or_create_group_with_users(group, session.id);
1739 }
1740 name = line[0].clone();
1741
1742 groupcode = String::new();
1743 for i in 0..10 {
1744 if i == 9 {
1745 panic!("ERROR: Too many groupcode collisions! Give up ...");
1746 }
1747 groupcode = helpers::make_groupcode();
1748 if !conn.code_exists(&groupcode) {
1749 break;
1750 }
1751 println!("WARNING: Groupcode collision! Retrying ...");
1752 }
1753
1754 group = Group { id: None,
1755 name: name.clone(),
1756 groupcode,
1757 tag: name.clone(),
1758 admins: vec![session.id],
1759 members: Vec::new() };
1760 }
1761
1762 let mut user = SessionUser::group_user_stub();
1763 user.grade = line[1].parse::<i32>().unwrap_or(0);
1764 user.firstname = Some(line[2].clone());
1765 user.lastname = Some(line[3].clone());
1766
1767 use db_objects::Sex;
1768 match line[4].as_str() {
1769 "m" => user.sex = Some(Sex::Male as i32),
1770 "f" => user.sex = Some(Sex::Female as i32),
1771 "d" => user.sex = Some(Sex::Diverse as i32),
1772 _ => user.sex = None,
1773 }
1774
1775 user.anonymous = line[5] == "y";
1776
1777 group.members.push(user);
1778 }
1779 conn.update_or_create_group_with_users(group, session.id);
1780
1781 Ok(())
1782}
1783
1784pub fn contest_result_csv<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
1785 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
1786
1787 let mut data = json_val::Map::new();
1788 data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
1789
1790 Ok(("admin_admissioncsv".to_string(), data))
1791}
1792
1793pub fn upload_contest_result_csv<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str,
1794 contest_id: i32, admission_data: &str)
1795 -> MedalResult<()> {
1796 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
1797
1798 if session.csrf_token != csrf_token {
1799 return Err(MedalError::CsrfCheckFailed);
1800 }
1801
1802 let v: Vec<Vec<String>> = serde_json::from_str(admission_data).or(Err(MedalError::AccessDenied))?; let w: Vec<(i32, Option<String>)> = v.into_iter()
1805 .map(|vv| {
1806 (vv[0].parse().unwrap_or(-1),
1807 if vv[1].len() == 0 && vv[2].len() == 0 {
1808 None
1809 } else {
1810 Some(format!("{}\x1f{}", vv[1], vv[2]))
1812 })
1813 })
1814 .collect();
1815
1816 let _annotations_inserted = conn.insert_contest_annotations(contest_id, w);
1817
1818 Ok(())
1819}
1820
1821#[allow(dead_code)]
1822pub fn show_groups_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
1823 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
1824 let _g = conn.get_contest_groups_grades(session.id, contest_id);
1826
1827 let data = json_val::Map::new();
1828
1829 Ok(("groupresults".into(), data))
1830}
1831
1832pub struct SexInformation {
1833 pub require_sex: bool,
1834 pub allow_sex_na: bool,
1835 pub allow_sex_diverse: bool,
1836 pub allow_sex_other: bool,
1837}
1838
1839pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>,
1840 query_string: Option<String>, sex_infos: SexInformation)
1841 -> MedalValueResult {
1842 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
1843
1844 let mut data = json_val::Map::new();
1845 fill_user_data(&session, &mut data);
1846
1847 data.insert("require_sex".to_string(), to_json(&sex_infos.require_sex));
1848 data.insert("allow_sex_na".to_string(), to_json(&sex_infos.allow_sex_na));
1849 data.insert("allow_sex_diverse".to_string(), to_json(&sex_infos.allow_sex_diverse));
1850 data.insert("allow_sex_other".to_string(), to_json(&sex_infos.allow_sex_other));
1851
1852 match user_id {
1853 None => {
1854 data.insert("profile_firstname".to_string(), to_json(&session.firstname));
1855 data.insert("profile_lastname".to_string(), to_json(&session.lastname));
1856 data.insert("profile_street".to_string(), to_json(&session.street));
1857 data.insert("profile_zip".to_string(), to_json(&session.zip));
1858 data.insert("profile_city".to_string(), to_json(&session.city));
1859 data.insert(format!("sel{}", session.grade), to_json(&"selected"));
1860 if let Some(sex) = session.sex {
1861 data.insert(format!("sex_{}", sex), to_json(&"selected"));
1862 } else {
1863 data.insert("sex_None".to_string(), to_json(&"selected"));
1864 }
1865
1866 data.insert("profile_logincode".to_string(), to_json(&session.logincode));
1867 if session.password.is_some() {
1868 data.insert("profile_username".to_string(), to_json(&session.username));
1869 }
1870 if session.managed_by.is_none() {
1871 data.insert("profile_not_in_group".into(), to_json(&true));
1872 }
1873 if session.oauth_provider != Some("pms".to_string()) {
1874 data.insert("profile_not_pms".into(), to_json(&true));
1875 }
1878 data.insert("ownprofile".into(), to_json(&true));
1879 data.insert("userid".into(), to_json(&session.id));
1880
1881 let webauthn = conn.get_webauthn_passkey_names_for_user(session.id);
1882 data.insert("webauthn".into(), to_json(&webauthn));
1883 data.insert("has_webauthn".into(), to_json(&!webauthn.is_empty()));
1884
1885 if let Some(query) = query_string {
1886 if let Some(status) = query.strip_prefix("status=") {
1887 if ["NothingChanged",
1888 "DataChanged",
1889 "PasswordChanged",
1890 "PasswordMissmatch",
1891 "firstlogin",
1892 "SignedUp"].contains(&status)
1893 {
1894 data.insert((status).to_string(), to_json(&true));
1895 }
1896 }
1897 }
1898
1899 let now = time::get_time();
1900
1901 let participations: (Vec<(i32, String, bool, bool, bool)>, Vec<(i32, String, bool, bool, bool)>) =
1903 conn.get_all_participations_complete(session.id)
1904 .into_iter()
1905 .rev()
1906 .map(|(participation, contest)| {
1907 let passed_secs = now.sec - participation.start.sec;
1908 let left_secs = i64::from(contest.duration) * 60 - passed_secs;
1909 let is_time_left = contest.duration == 0 || left_secs >= 0;
1910 let has_timelimit = contest.duration != 0;
1911 let requires_login = contest.requires_login == Some(true);
1912 (contest.id.unwrap(), contest.name, has_timelimit, is_time_left, requires_login)
1913 })
1914 .partition(|contest| contest.2 && !contest.4);
1915 data.insert("participations".into(), to_json(&participations));
1916
1917 let stars_count = conn.count_all_stars(session.id);
1918 data.insert("stars_count".into(), to_json(&stars_count));
1919 let stars_message = match stars_count {
1920 0 => "Auf gehts, dein erster Stern wartet auf dich!",
1921 1..=9 => "Ein hervorragender Anfang!",
1922 10..=99 => "Das ist ziemlich gut!",
1923 100..=999 => "Ein wahrer Meister!",
1924 _ => "Wow! Einfach wow!",
1925 }.to_string();
1926
1927 data.insert("stars_message".into(), to_json(&stars_message));
1928 }
1929 Some(user_id) => {
1931 let (user, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
1933 let group = opt_group.ok_or(MedalError::AccessDenied)?;
1934 if !group.admins.contains(&session.id) {
1935 return Err(MedalError::AccessDenied);
1936 }
1937
1938 data.insert("profile_firstname".to_string(), to_json(&user.firstname));
1939 data.insert("profile_lastname".to_string(), to_json(&user.lastname));
1940 data.insert("profile_street".to_string(), to_json(&session.street));
1941 data.insert("profile_zip".to_string(), to_json(&session.zip));
1942 data.insert("profile_city".to_string(), to_json(&session.city));
1943 data.insert(format!("sel{}", user.grade), to_json(&"selected"));
1944 if let Some(sex) = user.sex {
1945 data.insert(format!("sex_{}", sex), to_json(&"selected"));
1946 } else {
1947 data.insert("sex_None".to_string(), to_json(&"selected"));
1948 }
1949
1950 data.insert("profile_logincode".to_string(), to_json(&user.logincode));
1951 if user.username.is_some() {
1952 data.insert("profile_username".to_string(), to_json(&user.username));
1953 }
1954 if user.managed_by.is_none() {
1955 data.insert("profile_not_in_group".into(), to_json(&true));
1956 }
1957 if session.oauth_provider != Some("pms".to_string()) {
1958 data.insert("profile_not_pms".into(), to_json(&true));
1959 }
1960 data.insert("ownprofile".into(), to_json(&false));
1961
1962 if let Some(query) = query_string {
1963 if let Some(status) = query.strip_prefix("status=") {
1964 if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch"].contains(&status) {
1965 data.insert((status).to_string(), to_json(&true));
1966 }
1967 }
1968 }
1969 }
1970 }
1971
1972 Ok(("profile".to_string(), data))
1973}
1974
1975#[derive(Debug, PartialEq, Eq)]
1976pub enum ProfileStatus {
1977 NothingChanged,
1978 DataChanged,
1979 PasswordChanged,
1980 PasswordMissmatch,
1981}
1982impl From<ProfileStatus> for String {
1983 fn from(s: ProfileStatus) -> String { format!("{:?}", s) }
1984}
1985
1986pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>, csrf_token: &str,
1987 (firstname,
1988 lastname,
1989 street,
1990 zip,
1991 city,
1992 password,
1993 password_repeat,
1994 grade,
1995 sex): (String,
1996 String,
1997 Option<String>,
1998 Option<String>,
1999 Option<String>,
2000 Option<String>,
2001 Option<String>,
2002 i32,
2003 Option<i32>))
2004 -> MedalResult<ProfileStatus> {
2005 let mut session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
2006
2007 if session.csrf_token != csrf_token {
2008 return Err(MedalError::AccessDenied); }
2010
2011 let mut result = ProfileStatus::NothingChanged;
2012
2013 let mut password_and_salt = None;
2014
2015 if let (Some(password), Some(password_repeat)) = (password, password_repeat) {
2016 if password != "" || password_repeat != "" {
2017 if password == password_repeat {
2018 let salt = helpers::make_salt();
2019 let hash = helpers::hash_password(&password, &salt)?;
2020
2021 password_and_salt = Some((hash, salt));
2022 result = ProfileStatus::PasswordChanged;
2023 } else {
2024 result = ProfileStatus::PasswordMissmatch;
2025 }
2026 }
2027 }
2028
2029 if result == ProfileStatus::NothingChanged {
2030 if session.firstname.as_ref() == Some(&firstname)
2031 && session.lastname.as_ref() == Some(&lastname)
2032 && session.street == street
2033 && session.zip == zip
2034 && session.city == city
2035 && session.grade == grade
2036 && session.sex == sex
2037 {
2038 return Ok(ProfileStatus::NothingChanged);
2039 } else {
2040 result = ProfileStatus::DataChanged;
2041 }
2042 }
2043
2044 match user_id {
2045 None => {
2046 session.firstname = Some(firstname);
2047 session.lastname = Some(lastname);
2048 session.grade = grade;
2049 session.sex = sex;
2050
2051 if street.is_some() {
2052 session.street = street;
2053 }
2054 if zip.is_some() {
2055 session.zip = zip;
2056 }
2057 if city.is_some() {
2058 session.city = city;
2059 }
2060
2061 if let Some((password, salt)) = password_and_salt {
2062 session.password = Some(password);
2063 session.salt = Some(salt);
2064 }
2065
2066 conn.save_session(session);
2067 }
2068 Some(user_id) => {
2069 let (mut user, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
2071 let group = opt_group.ok_or(MedalError::AccessDenied)?;
2072 if !group.admins.contains(&session.id) {
2073 return Err(MedalError::AccessDenied);
2074 }
2075
2076 user.firstname = Some(firstname);
2077 user.lastname = Some(lastname);
2078 user.grade = grade;
2079 user.sex = sex;
2080
2081 if street.is_some() {
2082 user.street = street;
2083 }
2084 if zip.is_some() {
2085 user.zip = zip;
2086 }
2087 if city.is_some() {
2088 user.city = city;
2089 }
2090
2091 if let Some((password, salt)) = password_and_salt {
2092 user.password = Some(password);
2093 user.salt = Some(salt);
2094 }
2095
2096 conn.save_session(user);
2097 }
2098 }
2099
2100 Ok(result)
2101}
2102
2103pub fn check_profile<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str,
2104 (firstname, lastname): (String, String))
2105 -> JsonValueResult {
2106 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
2107
2108 if session.csrf_token != csrf_token {
2109 return Err(MedalError::AccessDenied);
2110 }
2111
2112 let mut data = json_val::Map::new();
2113
2114 if session.firstname.map(|n| n.len() > 0).unwrap_or(false) && session.lastname.map(|n| n.len() > 0).unwrap_or(false)
2116 {
2117 data.insert("exists".to_string(), to_json(&false));
2118 return Ok(data);
2119 }
2120
2121 let group =
2122 conn.get_group_complete(session.managed_by.ok_or(MedalError::AccessDenied)?).ok_or(MedalError::AccessDenied)?;
2123
2124 for member in group.members {
2125 if member.lastname.map(|l| l.to_lowercase()) == Some(lastname.to_lowercase())
2126 && member.firstname.map(|l| l.to_lowercase()) == Some(firstname.to_lowercase())
2127 && member.id != session.id
2128 {
2129 data.insert("exists".to_string(), to_json(&true));
2130 return Ok(data);
2131 }
2132 }
2133
2134 data.insert("exists".to_string(), to_json(&false));
2135 Ok(data)
2136}
2137
2138pub fn teacher_infos<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
2139 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
2140 if !session.is_teacher {
2141 return Err(MedalError::AccessDenied);
2142 }
2143
2144 let mut data = json_val::Map::new();
2145 fill_user_data(&session, &mut data);
2146
2147 Ok(("teacher".to_string(), data))
2148}
2149
2150pub fn admin_index<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
2151 let session = conn.get_session(&session_token)
2152 .ensure_logged_in()
2153 .ok_or(MedalError::NotLoggedIn)?
2154 .ensure_admin()
2155 .ok_or(MedalError::AccessDenied)?;
2156
2157 let mut data = json_val::Map::new();
2158 fill_user_data(&session, &mut data);
2159
2160 Ok(("admin".to_string(), data))
2161}
2162
2163pub fn admin_search_users<T: MedalConnection>(conn: &T, session_token: &str,
2164 s_data: (Option<i32>,
2165 Option<String>,
2166 Option<String>,
2167 Option<String>,
2168 Option<String>,
2169 Option<String>))
2170 -> MedalValueResult {
2171 let session = conn.get_session(&session_token)
2172 .ensure_logged_in()
2173 .ok_or(MedalError::NotLoggedIn)?
2174 .ensure_admin()
2175 .ok_or(MedalError::AccessDenied)?;
2176
2177 let mut data = json_val::Map::new();
2178 fill_user_data(&session, &mut data);
2179
2180 match conn.get_search_users(s_data) {
2181 Ok(users) => {
2182 data.insert("users".to_string(), to_json(&users));
2183 data.insert("max_results".to_string(), to_json(&200));
2184 data.insert("num_results".to_string(), to_json(&users.len()));
2185 data.insert("no_results".to_string(), to_json(&(users.len() == 0)));
2186 if users.len() > 200 {
2187 data.insert("more_users".to_string(), to_json(&true));
2188 data.insert("more_results".to_string(), to_json(&true));
2189 }
2190 }
2191 Err(groups) => {
2192 data.insert("groups".to_string(), to_json(&groups));
2193 data.insert("max_results".to_string(), to_json(&200));
2194 data.insert("num_results".to_string(), to_json(&groups.len()));
2195 data.insert("no_results".to_string(), to_json(&(groups.len() == 0)));
2196 if groups.len() > 200 {
2197 data.insert("more_groups".to_string(), to_json(&true));
2198 data.insert("more_results".to_string(), to_json(&true));
2199 }
2200 }
2201 };
2202
2203 Ok(("admin_search_results".to_string(), data))
2204}
2205
2206pub fn admin_show_user<T: MedalConnection>(conn: &T, user_id: i32, session_token: &str) -> MedalValueResult {
2207 let session = conn.get_session(&session_token)
2208 .ensure_logged_in()
2209 .ok_or(MedalError::NotLoggedIn)?
2210 .ensure_teacher_or_admin()
2211 .ok_or(MedalError::AccessDenied)?;
2212
2213 let mut data = json_val::Map::new();
2214
2215 let (user, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
2216
2217 if !session.is_admin() {
2218 if let Some(group) = opt_group.clone() {
2220 if !group.admins.contains(&session.id) {
2221 return Err(MedalError::AccessDenied);
2222 }
2223 } else if user_id != session.id {
2224 return Err(MedalError::AccessDenied);
2225 }
2226 }
2227
2228 fill_user_data(&session, &mut data);
2229 fill_user_data_prefix(&user, &mut data, "user_");
2230 data.insert("user_logincode".to_string(), to_json(&user.logincode));
2231 data.insert("user_id".to_string(), to_json(&user.id));
2232 let grade = if user.grade >= 200 {
2233 "Kein Schüler mehr".to_string()
2234 } else if user.grade >= 11 {
2235 format!("{} ({})", user.grade % 100, if user.grade >= 100 { "G9" } else { "G8" })
2236 } else {
2237 format!("{}", user.grade)
2238 };
2239 data.insert("user_grade".to_string(), to_json(&grade));
2240 data.insert("user_oauthid".to_string(), to_json(&user.oauth_foreign_id));
2241 data.insert("user_oauthprovider".to_string(), to_json(&user.oauth_provider));
2242
2243 if let Some(group) = opt_group {
2244 data.insert("user_group_id".to_string(), to_json(&group.id));
2245 data.insert("user_group_name".to_string(), to_json(&group.name));
2246 }
2247
2248 let groups: Vec<GroupInfo> =
2249 conn.get_groups(user_id)
2250 .iter()
2251 .map(|g| GroupInfo { id: g.id.unwrap(),
2252 name: g.name.clone(),
2253 tag: g.tag.clone(),
2254 code: g.groupcode.clone() })
2255 .collect();
2256 data.insert("user_group".to_string(), to_json(&groups));
2257
2258 let parts = conn.get_all_participations_complete(user_id);
2259 let has_protected_participations = parts.iter().any(|p| p.1.protected);
2260 let contest_stars = conn.count_all_stars_by_contest(user_id);
2261
2262 let pi: Vec<(i32, String, String, i32)> =
2263 parts.into_iter()
2264 .map(|(p, c)| {
2265 (c.id.unwrap(),
2266 format!("{}{}{}",
2267 &c.name,
2268 if c.protected { " (geschützt)" } else { "" },
2269 if p.team.is_some() { " (Teamteilnahme)" } else { "" }),
2270 self::time::strftime("%e. %b %Y, %H:%M", &self::time::at(p.start)).unwrap(),
2271 contest_stars.iter()
2272 .filter_map(|(id, stars)| if *id == c.id.unwrap() { Some(*stars) } else { None })
2273 .next()
2274 .unwrap_or(0))
2275 })
2276 .collect();
2277
2278 data.insert("user_participations".to_string(), to_json(&pi));
2279 data.insert("has_protected_participations".to_string(), to_json(&has_protected_participations));
2280 data.insert("can_delete".to_string(),
2281 to_json(&((!has_protected_participations || session.is_admin()) && groups.len() == 0)));
2282
2283 Ok(("admin_user".to_string(), data))
2284}
2285
2286pub fn admin_delete_user<T: MedalConnection>(conn: &T, user_id: i32, session_token: &str, csrf_token: &str)
2287 -> JsonValueResult {
2288 let session = conn.get_session(&session_token)
2289 .ensure_logged_in()
2290 .ok_or(MedalError::NotLoggedIn)?
2291 .ensure_teacher_or_admin()
2292 .ok_or(MedalError::AccessDenied)?;
2293
2294 if session.csrf_token != csrf_token {
2295 return Err(MedalError::CsrfCheckFailed);
2296 }
2297
2298 let (_, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
2299
2300 if !session.is_admin() {
2301 if let Some(group) = opt_group {
2303 if !group.admins.contains(&session.id) {
2304 return Err(MedalError::AccessDenied);
2305 }
2306 } else {
2307 return Err(MedalError::AccessDenied);
2308 }
2309 }
2310
2311 let parts = conn.get_all_participations_complete(user_id);
2312 let has_protected_participations = parts.iter().any(|p| p.1.protected);
2313 let groups = conn.get_groups(user_id);
2314
2315 let mut data = json_val::Map::new();
2316 if has_protected_participations && !session.is_admin() {
2317 data.insert("reason".to_string(), to_json(&"Benutzer hat Teilnahmen an geschützten Wettbewerben."));
2318 Err(MedalError::ErrorWithJson(data))
2319 } else if groups.len() > 0 {
2320 data.insert("reason".to_string(), to_json(&"Benutzer ist Administrator von Gruppen."));
2321 Err(MedalError::ErrorWithJson(data))
2322 } else {
2323 conn.delete_user(user_id);
2324 Ok(data)
2325 }
2326}
2327
2328pub fn admin_move_user_to_group<T: MedalConnection>(conn: &T, user_id: i32, group_id: i32, session_token: &str,
2329 csrf_token: &str)
2330 -> JsonValueResult {
2331 let session = conn.get_session(&session_token)
2332 .ensure_logged_in()
2333 .ok_or(MedalError::NotLoggedIn)?
2334 .ensure_admin()
2335 .ok_or(MedalError::AccessDenied)?;
2336
2337 if session.csrf_token != csrf_token {
2338 return Err(MedalError::CsrfCheckFailed);
2339 }
2340
2341 let (_, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
2342
2343 if !session.is_admin() {
2344 if let Some(group) = opt_group {
2346 if !group.admins.contains(&session.id) {
2347 return Err(MedalError::AccessDenied);
2348 }
2349 } else {
2350 return Err(MedalError::AccessDenied);
2351 }
2352 }
2353
2354 let mut data = json_val::Map::new();
2355 if conn.get_group(group_id).is_some() {
2356 if let Some(mut user) = conn.get_user_by_id(user_id) {
2357 user.managed_by = Some(group_id);
2358 conn.save_session(user);
2359 Ok(data)
2360 } else {
2361 data.insert("reason".to_string(), to_json(&"Benutzer existiert nicht."));
2362 Err(MedalError::ErrorWithJson(data))
2363 }
2364 } else {
2365 data.insert("reason".to_string(), to_json(&"Gruppe existiert nicht."));
2366 Err(MedalError::ErrorWithJson(data))
2367 }
2368}
2369
2370#[derive(Serialize, Deserialize)]
2371pub struct AdminInfo {
2372 pub id: i32,
2373 pub firstname: String,
2374 pub lastname: String,
2375}
2376
2377pub fn admin_show_group<T: MedalConnection>(conn: &T, group_id: i32, session_token: &str) -> MedalValueResult {
2378 let session = conn.get_session(&session_token)
2379 .ensure_logged_in()
2380 .ok_or(MedalError::NotLoggedIn)?
2381 .ensure_teacher_or_admin()
2382 .ok_or(MedalError::AccessDenied)?;
2383
2384 let group = conn.get_group_complete(group_id).unwrap(); if !session.is_admin() {
2387 if !group.admins.contains(&session.id) {
2389 return Err(MedalError::AccessDenied);
2390 }
2391 }
2392
2393 let mut data = json_val::Map::new();
2394 fill_user_data(&session, &mut data);
2395
2396 let gi = GroupInfo { id: group.id.unwrap(),
2397 name: group.name.clone(),
2398 tag: group.tag.clone(),
2399 code: group.groupcode.clone() };
2400
2401 let v: Vec<MemberInfo> =
2402 group.members
2403 .iter()
2404 .filter(|m| session.is_admin() || m.firstname.is_some() || m.lastname.is_some())
2405 .map(|m| MemberInfo { id: m.id,
2406 firstname: m.firstname.clone().unwrap_or_else(|| "".to_string()),
2407 lastname: m.lastname.clone().unwrap_or_else(|| "".to_string()),
2408 sex: (match m.sex {
2409 Some(0) | None => "/",
2410 Some(1) => "m",
2411 Some(2) => "w",
2412 Some(3) => "d",
2413 Some(4) => "…",
2414 _ => "?",
2415 }).to_string(),
2416 grade: grade_to_string(m.grade),
2417 logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()),
2418 anonymous: m.anonymous })
2419 .collect();
2420
2421 let has_anonmous_members = group.members.iter().any(|m| m.anonymous);
2422
2423 let has_protected_participations = conn.group_has_protected_participations(group_id);
2424
2425 data.insert("group".to_string(), to_json(&gi));
2426 data.insert("member".to_string(), to_json(&v));
2427 data.insert("groupname".to_string(), to_json(&gi.name));
2428 data.insert("has_anonymous_members".to_string(), to_json(&has_anonmous_members));
2429 data.insert("has_protected_participations".to_string(), to_json(&has_protected_participations));
2430 data.insert("can_delete".to_string(), to_json(&(!has_protected_participations || session.is_admin())));
2431
2432 let admins: Vec<AdminInfo> =
2433 group.admins
2434 .iter()
2435 .map(|a| {
2436 let admin = conn.get_user_by_id(*a).ok_or(MedalError::AccessDenied)?;
2437 Ok(AdminInfo { id: admin.id,
2438 firstname: admin.firstname.clone().unwrap_or_else(|| "".to_string()),
2439 lastname: admin.lastname.clone().unwrap_or_else(|| "".to_string()) })
2440 })
2441 .collect::<Result<Vec<_>, _>>()?;
2442
2443 data.insert("group_admin".to_string(), to_json(&admins));
2444 data.insert("morethanoneadmin".to_string(), to_json(&(admins.len() > 1)));
2445
2446 Ok(("admin_group".to_string(), data))
2447}
2448
2449pub fn group_add_admin<T: MedalConnection>(conn: &T, group_id: i32, new_admin_id: i32, session_token: &str,
2450 csrf_token: &str)
2451 -> JsonValueResult {
2452 let session = conn.get_session(&session_token)
2453 .ensure_logged_in()
2454 .ok_or(MedalError::NotLoggedIn)?
2455 .ensure_teacher_or_admin()
2456 .ok_or(MedalError::AccessDenied)?;
2457
2458 if session.csrf_token != csrf_token {
2459 return Err(MedalError::CsrfCheckFailed);
2460 }
2461
2462 let mut group = conn.get_group(group_id).unwrap(); if !session.is_admin() {
2465 if !group.admins.contains(&session.id) {
2467 return Err(MedalError::AccessDenied);
2468 }
2469 }
2470
2471 if group.admins.contains(&new_admin_id) {
2472 let mut data = json_val::Map::new();
2473 data.insert("reason".to_string(), to_json(&"Benutzer ist bereits Admin."));
2474 return Err(MedalError::ErrorWithJson(data));
2475 }
2476
2477 let new_admin = conn.get_user_by_id(new_admin_id).ok_or(MedalError::NotFound)?;
2478 let first_admin_id = group.admins.first().ok_or(MedalError::NotFound)?;
2479 let first_admin = conn.get_user_by_id(*first_admin_id).ok_or(MedalError::NotFound)?;
2480 if new_admin.oauth_provider == first_admin.oauth_provider {
2481 if let Some((_, new_admin_school)) = new_admin.oauth_foreign_id.ok_or(MedalError::AccessDenied)?.split_once('/')
2482 {
2483 if let Some((_, first_admin_school)) =
2484 first_admin.oauth_foreign_id.ok_or(MedalError::AccessDenied)?.split_once('/')
2485 {
2486 if new_admin_school == first_admin_school && new_admin_school.len() >= 1 {
2487 conn.add_admin_to_group(&mut group, new_admin_id);
2488
2489 let data = json_val::Map::new();
2490 return Ok(data);
2491 }
2492 }
2493 }
2494 }
2495
2496 let mut data = json_val::Map::new();
2497 data.insert("reason".to_string(),
2498 to_json(&"Benutzer gehört nicht zur gleichen Schule oder Benutzer nicht als Lehrkraft angemeldet."));
2499 Err(MedalError::ErrorWithJson(data))
2500}
2501
2502pub fn admin_delete_group<T: MedalConnection>(conn: &T, group_id: i32, session_token: &str, csrf_token: &str)
2503 -> JsonValueResult {
2504 let session = conn.get_session(&session_token)
2505 .ensure_logged_in()
2506 .ok_or(MedalError::NotLoggedIn)?
2507 .ensure_teacher_or_admin()
2508 .ok_or(MedalError::AccessDenied)?;
2509
2510 if session.csrf_token != csrf_token {
2511 return Err(MedalError::CsrfCheckFailed);
2512 }
2513
2514 let group = conn.get_group(group_id).unwrap(); if !session.is_admin() {
2517 if !group.admins.contains(&session.id) {
2519 return Err(MedalError::AccessDenied);
2520 }
2521 }
2522
2523 let mut data = json_val::Map::new();
2524 if conn.group_has_protected_participations(group_id) && !session.is_admin() {
2525 data.insert("reason".to_string(), to_json(&"Gruppe hat Mitglieder mit geschützten Teilnahmen."));
2526 Err(MedalError::ErrorWithJson(data))
2527 } else {
2528 conn.delete_all_users_for_group(group_id);
2529 conn.delete_group(group_id);
2530 Ok(data)
2531 }
2532}
2533
2534pub fn admin_show_edit_group<T: MedalConnection>(conn: &T, group_id: i32, session_token: &str) -> MedalValueResult {
2535 let session = conn.get_session(&session_token)
2536 .ensure_logged_in()
2537 .ok_or(MedalError::NotLoggedIn)?
2538 .ensure_teacher_or_admin()
2539 .ok_or(MedalError::AccessDenied)?;
2540
2541 let group = conn.get_group_complete(group_id).unwrap(); if !session.is_admin() {
2544 if !group.admins.contains(&session.id) {
2546 return Err(MedalError::AccessDenied);
2547 }
2548 }
2549
2550 let mut data = json_val::Map::new();
2551 fill_user_data(&session, &mut data);
2552
2553 let gi = GroupInfo { id: group.id.unwrap(),
2554 name: group.name.clone(),
2555 tag: group.tag.clone(),
2556 code: group.groupcode.clone() };
2557
2558 data.insert("group".to_string(), to_json(&gi));
2559 data.insert("groupname".to_string(), to_json(&gi.name));
2560 data.insert("grouptag".to_string(), to_json(&gi.name));
2561
2562 Ok(("admin_edit_group".to_string(), data))
2563}
2564
2565pub fn admin_edit_group<T: MedalConnection>(conn: &T, group_id: i32, session_token: &str, csrf_token: &str,
2566 name: String, tag: String)
2567 -> MedalResult<()> {
2568 let session = conn.get_session(&session_token)
2569 .ensure_logged_in()
2570 .ok_or(MedalError::NotLoggedIn)?
2571 .ensure_teacher_or_admin()
2572 .ok_or(MedalError::AccessDenied)?;
2573
2574 if session.csrf_token != csrf_token {
2575 return Err(MedalError::CsrfCheckFailed);
2576 }
2577
2578 let mut group = conn.get_group_complete(group_id).unwrap(); if !session.is_admin() {
2581 if !group.admins.contains(&session.id) {
2583 return Err(MedalError::AccessDenied);
2584 }
2585 }
2586
2587 group.name = name;
2588 group.tag = tag;
2589
2590 conn.save_group(&mut group);
2591
2592 Ok(())
2593}
2594
2595#[derive(Serialize, Deserialize, Debug)]
2596struct SubmissionResult {
2597 id: i32,
2598 grade: i32,
2599 date: String,
2600}
2601#[derive(Serialize, Deserialize, Debug)]
2602struct TaskResult {
2603 id: i32,
2604 stars: i32,
2605 submissions: Vec<SubmissionResult>,
2606}
2607#[derive(Serialize, Deserialize, Debug)]
2608struct TaskgroupResult {
2609 id: i32,
2610 name: String,
2611 tasks: Vec<TaskResult>,
2612}
2613
2614pub fn admin_show_participation<T: MedalConnection>(conn: &T, user_id: i32, contest_id: i32, session_token: &str)
2615 -> MedalValueResult {
2616 let session = conn.get_session(&session_token)
2617 .ensure_logged_in()
2618 .ok_or(MedalError::NotLoggedIn)?
2619 .ensure_teacher_or_admin()
2620 .ok_or(MedalError::AccessDenied)?;
2621
2622 let user = conn.get_user_by_id(user_id).ok_or(MedalError::AccessDenied)?;
2623 let participation = conn.get_participation(user.id, contest_id).ok_or(MedalError::AccessDenied)?;
2624
2625 let user_or_team = if let Some(team) = participation.team { team } else { user_id };
2626
2627 let (_, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
2628
2629 if !session.is_admin() {
2630 if let Some(ref group) = opt_group {
2632 if !group.admins.contains(&session.id) {
2633 return Err(MedalError::AccessDenied);
2634 }
2635 } else {
2636 return Err(MedalError::AccessDenied);
2637 }
2638 }
2639
2640 let contest = conn.get_contest_by_id_complete(contest_id).ok_or(MedalError::UnknownId)?;
2641 let grades = conn.get_contest_user_grades(user_or_team, contest_id);
2642
2643 let mut totalgrade = 0;
2644 let mut max_totalgrade = 0;
2645
2646 for taskgroup in &contest.taskgroups {
2647 max_totalgrade += taskgroup.tasks.iter().map(|x| x.stars).max().unwrap_or(0);
2648 }
2649 for grade in grades {
2650 totalgrade += grade.grade.unwrap_or(0);
2651 }
2652
2653 #[rustfmt::skip]
2654 let subms: Vec<TaskgroupResult> =
2655 contest.taskgroups
2656 .into_iter()
2657 .map(|tg| TaskgroupResult {
2658 id: tg.id.unwrap(),
2659 name: tg.name,
2660 tasks: tg.tasks
2661 .into_iter()
2662 .map(|t| TaskResult {
2663 id: t.id.unwrap(),
2664 stars: t.stars,
2665 submissions: conn.get_all_submissions(user_or_team, t.id.unwrap(), None)
2666 .into_iter()
2667 .map(|s| SubmissionResult {
2668 id: s.id.unwrap(),
2669 grade: s.grade,
2670 date: self::time::strftime("%e. %b %Y, %H:%M", &self::time::at(s.date)).unwrap(),
2671 })
2672 .collect(),
2673 })
2674 .collect(),
2675 })
2676 .collect();
2677
2678 let mut data = json_val::Map::new();
2679
2680 data.insert("submissions".to_string(), to_json(&subms));
2681 data.insert("contestid".to_string(), to_json(&contest.id));
2682 data.insert("contestname".to_string(), to_json(&contest.name));
2683 data.insert("has_timelimit".to_string(), to_json(&(contest.duration > 0)));
2684
2685 data.insert("total_grade".to_string(), to_json(&totalgrade));
2686 data.insert("max_total_grade".to_string(), to_json(&max_totalgrade));
2687
2688 if let Some(group) = opt_group {
2689 data.insert("group_id".to_string(), to_json(&group.id));
2690 data.insert("group_name".to_string(), to_json(&group.name));
2691 }
2692
2693 fill_user_data(&session, &mut data);
2694 fill_user_data_prefix(&user, &mut data, "user_");
2695 data.insert("user_id".to_string(), to_json(&user.id));
2696
2697 data.insert("start_date".to_string(),
2698 to_json(&self::time::strftime("%e. %b %Y, %H:%M", &self::time::at(participation.start)).unwrap()));
2699
2700 data.insert("can_delete".to_string(), to_json(&(!contest.protected || session.is_admin.unwrap_or(false))));
2701 Ok(("admin_participation".to_string(), data))
2702}
2703
2704pub fn admin_delete_participation<T: MedalConnection>(conn: &T, user_id: i32, contest_id: i32, session_token: &str,
2705 csrf_token: &str)
2706 -> JsonValueResult {
2707 let session = conn.get_session(&session_token)
2708 .ensure_logged_in()
2709 .ok_or(MedalError::NotLoggedIn)?
2710 .ensure_teacher_or_admin()
2711 .ok_or(MedalError::AccessDenied)?;
2712
2713 if session.csrf_token != csrf_token {
2714 return Err(MedalError::CsrfCheckFailed);
2715 }
2716
2717 let (user, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
2718 let _part = conn.get_participation(user.id, contest_id).ok_or(MedalError::AccessDenied)?;
2719 let contest = conn.get_contest_by_id_complete(contest_id).ok_or(MedalError::UnknownId)?;
2720
2721 if !session.is_admin() {
2722 if contest.protected {
2724 return Err(MedalError::AccessDenied);
2725 }
2726
2727 if let Some(group) = opt_group {
2728 if !group.admins.contains(&session.id) {
2729 return Err(MedalError::AccessDenied);
2730 }
2731 } else {
2732 return Err(MedalError::AccessDenied);
2733 }
2734 }
2735
2736 let mut data = json_val::Map::new();
2737 fill_user_data(&session, &mut data);
2738
2739 conn.delete_participation(user_id, contest_id);
2740 Ok(data)
2741}
2742
2743pub fn admin_show_contests<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
2744 let session = conn.get_session(&session_token)
2745 .ensure_logged_in()
2746 .ok_or(MedalError::NotLoggedIn)?
2747 .ensure_admin()
2748 .ok_or(MedalError::AccessDenied)?;
2749
2750 let mut data = json_val::Map::new();
2751 fill_user_data(&session, &mut data);
2752
2753 let mut contests: Vec<_> = conn.get_contest_list().into_iter().map(|contest| (contest.id, contest.name)).collect();
2754 contests.sort(); contests.reverse();
2756
2757 data.insert("contests".to_string(), to_json(&contests));
2758
2759 Ok(("admin_contests".to_string(), data))
2760}
2761
2762pub fn admin_contest_export<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalResult<String> {
2763 conn.get_session(&session_token)
2764 .ensure_logged_in()
2765 .ok_or(MedalError::NotLoggedIn)?
2766 .ensure_admin()
2767 .ok_or(MedalError::AccessDenied)?;
2768
2769 let contest = conn.get_contest_by_id_complete(contest_id).ok_or(MedalError::UnknownId)?;
2770
2771 let taskgroup_ids: Vec<(i32, String)> =
2772 contest.taskgroups.into_iter().map(|tg| (tg.id.unwrap(), tg.name)).collect();
2773 let filename = format!("contest_{}__{}__{}.csv",
2774 contest_id,
2775 self::time::strftime("%F_%H-%M-%S", &self::time::now()).unwrap(),
2776 helpers::make_filename_secret());
2777
2778 conn.export_contest_results_to_file(contest_id, &taskgroup_ids, &format!("./export/{}", filename));
2779
2780 Ok(filename)
2781}
2782
2783pub fn admin_show_cleanup<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
2784 let session = conn.get_session(&session_token)
2785 .ensure_logged_in()
2786 .ok_or(MedalError::NotLoggedIn)?
2787 .ensure_admin()
2788 .ok_or(MedalError::AccessDenied)?;
2789
2790 let mut data = json_val::Map::new();
2791 fill_user_data(&session, &mut data);
2792
2793 let now = time::get_time();
2794 let maxage = now - time::Duration::days(30); let n_temporary_session = conn.count_temporary_sessions(maxage);
2796 data.insert("temporary_session_count".to_string(), to_json(&n_temporary_session));
2797
2798 Ok(("admin_cleanup".to_string(), data))
2799}
2800
2801pub fn admin_do_cleanup<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str) -> JsonValueResult {
2802 let session = conn.get_session(&session_token)
2803 .ensure_logged_in()
2804 .ok_or(MedalError::NotLoggedIn)?
2805 .ensure_admin()
2806 .ok_or(MedalError::AccessDenied)?;
2807
2808 if session.csrf_token != csrf_token {
2809 return Err(MedalError::CsrfCheckFailed);
2810 }
2811
2812 let now = time::get_time();
2813 let maxstudentage = now - time::Duration::days(180); let maxteacherage = now - time::Duration::days(1095); let maxage = now - time::Duration::days(3650); let result = conn.remove_old_users_and_groups(maxstudentage, Some(maxteacherage), Some(maxage));
2818
2819 let mut data = json_val::Map::new();
2820 if let Ok((n_user, n_group, n_teacher, n_other)) = result {
2821 data.insert("n_user".to_string(), to_json(&n_user));
2822 data.insert("n_group".to_string(), to_json(&n_group));
2823 data.insert("n_teacher".to_string(), to_json(&n_teacher));
2824 data.insert("n_other".to_string(), to_json(&n_other));
2825 Ok(data)
2826 } else {
2827 data.insert("reason".to_string(), to_json(&"Datenbank-Fehler."));
2828 Err(MedalError::ErrorWithJson(data))
2829 }
2830}
2831
2832pub fn do_session_cleanup<T: MedalConnection>(conn: &T) -> JsonValueResult {
2833 let now = time::get_time();
2834 let maxage = now - time::Duration::days(30); conn.remove_temporary_sessions(maxage, Some(1000));
2837
2838 let data = json_val::Map::new();
2839 Ok(data)
2840}
2841
2842pub fn move_task_location<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, old_location: &str,
2843 new_location: &str, contest: Option<i32>)
2844 -> JsonValueResult {
2845 let session = conn.get_session(&session_token)
2846 .ensure_logged_in()
2847 .ok_or(MedalError::NotLoggedIn)?
2848 .ensure_admin()
2849 .ok_or(MedalError::AccessDenied)?;
2850
2851 if session.csrf_token != csrf_token {
2852 return Err(MedalError::CsrfCheckFailed);
2853 }
2854
2855 let mut data = json_val::Map::new();
2856 if old_location == new_location {
2857 data.insert("reason".to_string(), to_json(&"old and new location identical"));
2858 return Err(MedalError::ErrorWithJson(data));
2859 }
2860
2861 let n_contest = conn.move_task_location(old_location, new_location, contest);
2862
2863 data.insert("contests_modified".to_string(), to_json(&n_contest));
2864
2865 Ok(data)
2866}
2867
2868#[derive(PartialEq, Eq, Clone, Copy)]
2869pub enum UserType {
2870 User,
2871 Teacher,
2872 Admin,
2873}
2874
2875pub enum UserSex {
2876 Female,
2877 Male,
2878 Unknown,
2879}
2880
2881pub struct ForeignUserData {
2882 pub foreign_id: String,
2883 pub foreign_type: UserType,
2884 pub sex: UserSex,
2885 pub firstname: String,
2886 pub lastname: String,
2887 pub school_name: Option<String>,
2888}
2889
2890pub fn login_oauth<T: MedalConnection>(conn: &T, user_data: ForeignUserData, oauth_provider_id: String)
2891 -> Result<(String, bool), (String, json_val::Map<String, json_val::Value>)> {
2892 match conn.login_foreign(None,
2893 &oauth_provider_id,
2894 &user_data.foreign_id,
2895 (user_data.foreign_type != UserType::User,
2896 user_data.foreign_type == UserType::Admin,
2897 &user_data.firstname,
2898 &user_data.lastname,
2899 match user_data.sex {
2900 UserSex::Male => Some(1),
2901 UserSex::Female => Some(2),
2902 UserSex::Unknown => Some(0),
2903 },
2904 &user_data.school_name))
2905 {
2906 Ok((session_token, last_activity)) => {
2907 let redirect_profile = if let Some(last_activity) = last_activity {
2908 let now = time::get_time();
2909 now - last_activity > time::Duration::days(60)
2910 } else {
2911 true
2912 };
2913
2914 let now = time::get_time();
2918 let maxage = now - time::Duration::days(30); conn.remove_temporary_sessions(maxage, Some(200));
2920
2921 Ok((session_token, redirect_profile))
2922 }
2923 Err(()) => {
2924 let mut data = json_val::Map::new();
2925 data.insert("reason".to_string(), to_json(&"OAuth-Login failed.".to_string()));
2926 Err(("login".to_owned(), data))
2927 }
2928 }
2929}