/* medal *\
* Copyright (C) 2020 Bundesweite Informatikwettbewerbe *
* *
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero *
* General Public License as published by the Free Software Foundation, either version 3 of the License, or (at *
* your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the *
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public *
* License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License along with this program. If not, see *
\* . */
use time;
use db_conn::MedalConnection;
#[cfg(feature = "signup")]
use db_conn::SignupResult;
use db_objects::OptionSession;
use db_objects::SessionUser;
use db_objects::{Contest, Grade, Group, Participation, Submission, Taskgroup};
use helpers;
use oauth_provider::OauthProvider;
use webfw_iron::{json_val, to_json};
#[derive(Serialize, Deserialize)]
pub struct SubTaskInfo {
pub id: i32,
pub linktext: String,
pub active: bool,
pub greyout: bool,
}
#[derive(Serialize, Deserialize)]
pub struct TaskInfo {
pub name: String,
pub subtasks: Vec,
}
#[derive(Serialize, Deserialize)]
pub struct ContestInfo {
pub id: i32,
pub name: String,
pub duration: i32,
pub public: bool,
}
#[derive(Clone, Debug)]
pub enum MedalError {
NotLoggedIn,
AccessDenied,
CsrfCheckFailed,
SessionTimeout,
DatabaseError,
ConfigurationError,
DatabaseConnectionError,
PasswordHashingError,
UnmatchedPasswords,
NotFound,
OauthError(String),
}
pub struct LoginInfo {
pub password_login: bool,
pub self_url: Option,
pub oauth_providers: Option>,
}
type MedalValue = (String, json_val::Map);
type MedalResult = Result;
type MedalValueResult = MedalResult;
fn fill_user_data(session: &SessionUser, data: &mut json_val::Map) {
if session.is_logged_in() {
data.insert("logged_in".to_string(), to_json(&true));
}
if session.is_admin() {
data.insert("admin".to_string(), to_json(&true));
}
data.insert("username".to_string(), to_json(&session.username));
data.insert("firstname".to_string(), to_json(&session.firstname));
data.insert("lastname".to_string(), to_json(&session.lastname));
data.insert("teacher".to_string(), to_json(&session.is_teacher));
data.insert("admin".to_string(), to_json(&session.is_admin));
data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
data.insert("parent".to_string(), to_json(&"base"));
data.insert("medal_version".to_string(), to_json(&env!("CARGO_PKG_VERSION")));
}
fn fill_oauth_data(login_info: LoginInfo, data: &mut json_val::Map) {
let mut oauth_links: Vec<(String, String, String)> = Vec::new();
if let Some(oauth_providers) = login_info.oauth_providers {
for oauth_provider in oauth_providers {
oauth_links.push((oauth_provider.provider_id.to_owned(),
oauth_provider.login_link_text.to_owned(),
oauth_provider.url.to_owned()));
}
}
data.insert("self_url".to_string(), to_json(&login_info.self_url));
data.insert("oauth_links".to_string(), to_json(&oauth_links));
data.insert("password_login".to_string(), to_json(&login_info.password_login));
}
fn grade_to_string(grade: i32) -> String {
match grade {
0 => "Noch kein Schüler".to_string(),
n @ 1..=10 => format!("{}", n),
11 => "11 (G8)".to_string(),
12 => "12 (G8)".to_string(),
111 => "11 (G9)".to_string(),
112 => "12 (G9)".to_string(),
113 => "13 (G9)".to_string(),
114 => "Berufsschule".to_string(),
255 => "Kein Schüler mehr".to_string(),
_ => "?".to_string(),
}
}
pub fn index(conn: &T, session_token: Option, login_info: LoginInfo)
-> (String, json_val::Map) {
let mut data = json_val::Map::new();
if let Some(token) = session_token {
if let Some(session) = conn.get_session(&token) {
fill_user_data(&session, &mut data);
}
}
fill_oauth_data(login_info, &mut data);
data.insert("parent".to_string(), to_json(&"base"));
("index".to_owned(), data)
}
pub fn show_login(conn: &T, session_token: Option, login_info: LoginInfo)
-> (String, json_val::Map) {
let mut data = json_val::Map::new();
if let Some(token) = session_token {
if let Some(session) = conn.get_session(&token) {
fill_user_data(&session, &mut data);
}
}
fill_oauth_data(login_info, &mut data);
data.insert("parent".to_string(), to_json(&"base"));
("login".to_owned(), data)
}
pub fn status(conn: &T, config_secret: Option, given_secret: Option) -> MedalResult {
if config_secret == given_secret {
Ok(conn.get_debug_information())
} else {
Err(MedalError::AccessDenied)
}
}
pub fn debug(conn: &T, session_token: Option)
-> (String, json_val::Map) {
let mut data = json_val::Map::new();
if let Some(token) = session_token {
if let Some(session) = conn.get_session(&token) {
data.insert("known_session".to_string(), to_json(&true));
data.insert("session_id".to_string(), to_json(&session.id));
data.insert("now_timestamp".to_string(), to_json(&time::get_time().sec));
if let Some(last_activity) = session.last_activity {
data.insert("session_timestamp".to_string(), to_json(&last_activity.sec));
data.insert("timediff".to_string(), to_json(&(time::get_time() - last_activity).num_seconds()));
}
if session.is_alive() {
data.insert("alive_session".to_string(), to_json(&true));
if session.is_logged_in() {
data.insert("logged_in".to_string(), to_json(&true));
data.insert("username".to_string(), to_json(&session.username));
data.insert("firstname".to_string(), to_json(&session.firstname));
data.insert("lastname".to_string(), to_json(&session.lastname));
data.insert("teacher".to_string(), to_json(&session.is_teacher));
data.insert("oauth_provider".to_string(), to_json(&session.oauth_provider));
data.insert("oauth_id".to_string(), to_json(&session.oauth_foreign_id));
data.insert("logincode".to_string(), to_json(&session.logincode));
data.insert("managed_by".to_string(), to_json(&session.managed_by));
}
}
}
data.insert("session".to_string(), to_json(&token));
} else {
data.insert("session".to_string(), to_json(&"No session token given"));
}
("debug".to_owned(), data)
}
pub fn debug_create_session(conn: &T, session_token: Option) {
if let Some(token) = session_token {
conn.get_session_or_new(&token).unwrap();
}
}
#[derive(PartialEq, Eq)]
pub enum ContestVisibility {
All,
Open,
Current,
LoginRequired,
}
pub fn show_contests(conn: &T, session_token: &str, login_info: LoginInfo,
visibility: ContestVisibility)
-> MedalValueResult
{
let mut data = json_val::Map::new();
let session = conn.get_session_or_new(&session_token).map_err(|_| MedalError::DatabaseConnectionError)?;
fill_user_data(&session, &mut data);
if session.is_logged_in() {
data.insert("can_start".to_string(), to_json(&true));
}
fill_oauth_data(login_info, &mut data);
let now = time::get_time();
let v: Vec =
conn.get_contest_list()
.iter()
.filter(|c| c.public)
.filter(|c| c.end.map(|end| now <= end).unwrap_or(true) || visibility == ContestVisibility::All)
.filter(|c| c.duration == 0 || visibility != ContestVisibility::Open)
.filter(|c| c.duration != 0 || visibility != ContestVisibility::Current)
.filter(|c| c.requires_login.unwrap_or(false) || visibility != ContestVisibility::LoginRequired)
.filter(|c| {
!c.requires_login.unwrap_or(false)
|| visibility == ContestVisibility::LoginRequired
|| visibility == ContestVisibility::All
})
.map(|c| ContestInfo { id: c.id.unwrap(), name: c.name.clone(), duration: c.duration, public: c.public })
.collect();
data.insert("contest".to_string(), to_json(&v));
data.insert("contestlist_header".to_string(),
to_json(&match visibility {
ContestVisibility::Open => "Trainingsaufgaben",
ContestVisibility::Current => "Aktuelle Wettbewerbe",
ContestVisibility::LoginRequired => "Herausforderungen",
ContestVisibility::All => "Alle Wettbewerbe",
}));
Ok(("contests".to_owned(), data))
}
fn generate_subtaskstars(tg: &Taskgroup, grade: &Grade, ast: Option) -> Vec {
let mut subtaskinfos = Vec::new();
let mut not_print_yet = true;
for st in &tg.tasks {
let mut blackstars: usize = 0;
if not_print_yet && st.stars >= grade.grade.unwrap_or(0) {
blackstars = grade.grade.unwrap_or(0) as usize;
not_print_yet = false;
}
let greyout = not_print_yet && st.stars < grade.grade.unwrap_or(0);
let active = ast.is_some() && st.id == ast;
let linktext = format!("{}{}",
str::repeat("★", blackstars as usize),
str::repeat("☆", st.stars as usize - blackstars as usize));
let si = SubTaskInfo { id: st.id.unwrap(), linktext, active, greyout };
subtaskinfos.push(si);
}
subtaskinfos
}
#[derive(Serialize, Deserialize)]
pub struct ContestStartConstraints {
pub contest_not_begun: bool,
pub contest_over: bool,
pub contest_running: bool,
pub grade_too_low: bool,
pub grade_too_high: bool,
pub grade_matching: bool,
}
fn check_contest_constraints(session: &SessionUser, contest: &Contest) -> ContestStartConstraints {
let now = time::get_time();
let student_grade = session.grade % 100 - if session.grade / 100 == 1 { 1 } else { 0 };
let contest_not_begun = contest.start.map(|start| now < start).unwrap_or(false);
let contest_over = contest.end.map(|end| now > end).unwrap_or(false);
let grade_too_low =
contest.min_grade.map(|min_grade| student_grade < min_grade && !session.is_teacher).unwrap_or(false);
let grade_too_high =
contest.max_grade.map(|max_grade| student_grade > max_grade && !session.is_teacher).unwrap_or(false);
let contest_running = !contest_not_begun && !contest_over;
let grade_matching = !grade_too_low && !grade_too_high;
ContestStartConstraints { contest_not_begun,
contest_over,
contest_running,
grade_too_low,
grade_too_high,
grade_matching }
}
pub fn show_contest(conn: &T, contest_id: i32, session_token: &str,
query_string: Option, login_info: LoginInfo)
-> MedalValueResult
{
let session = conn.get_session_or_new(&session_token).unwrap();
let contest = conn.get_contest_by_id_complete(contest_id);
let grades = conn.get_contest_user_grades(&session_token, contest_id);
let mut opt_part = conn.get_participation(session.id, contest_id);
let ci = ContestInfo { id: contest.id.unwrap(),
name: contest.name.clone(),
duration: contest.duration,
public: contest.public };
let mut data = json_val::Map::new();
data.insert("parent".to_string(), to_json(&"base"));
data.insert("empty".to_string(), to_json(&"empty"));
data.insert("contest".to_string(), to_json(&ci));
data.insert("title".to_string(), to_json(&ci.name));
data.insert("message".to_string(), to_json(&contest.message));
fill_oauth_data(login_info, &mut data);
let constraints = check_contest_constraints(&session, &contest);
let can_start = session.is_logged_in() && constraints.contest_running && constraints.grade_matching;
let has_duration = contest.duration > 0;
data.insert("constraints".to_string(), to_json(&constraints));
data.insert("has_duration".to_string(), to_json(&has_duration));
data.insert("can_start".to_string(), to_json(&can_start));
let has_tasks = contest.taskgroups.len() > 0;
data.insert("has_tasks".to_string(), to_json(&has_tasks));
data.insert("no_tasks".to_string(), to_json(&!has_tasks));
// Autostart if appropriate
// TODO: Should participation start automatically for teacher? Even before the contest start?
// Should teachers have all time access or only the same limited amount of time?
// if opt_part.is_none() && (contest.duration == 0 || session.is_teacher) {
if opt_part.is_none()
&& contest.duration == 0
&& constraints.contest_running
&& constraints.grade_matching
&& contest.requires_login != Some(true)
{
conn.new_participation(&session_token, contest_id).map_err(|_| MedalError::AccessDenied)?;
opt_part = Some(Participation { contest: contest_id, user: session.id, start: time::get_time() });
}
let now = time::get_time();
if let Some(start) = contest.start {
if now < start {
let until = start - now;
data.insert("time_until_start".to_string(),
to_json(&[until.num_days(), until.num_hours() % 24, until.num_minutes() % 60]));
}
}
if let Some(end) = contest.end {
if now < end {
let until = end - now;
data.insert("time_until_end".to_string(),
to_json(&[until.num_days(), until.num_hours() % 24, until.num_minutes() % 60]));
}
}
if session.is_logged_in() {
data.insert("logged_in".to_string(), to_json(&true));
data.insert("username".to_string(), to_json(&session.username));
data.insert("firstname".to_string(), to_json(&session.firstname));
data.insert("lastname".to_string(), to_json(&session.lastname));
data.insert("teacher".to_string(), to_json(&session.is_teacher));
data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
}
if let Some(participation) = opt_part {
let mut totalgrade = 0;
let mut max_totalgrade = 0;
let mut tasks = Vec::new();
for (taskgroup, grade) in contest.taskgroups.into_iter().zip(grades) {
let subtaskstars = generate_subtaskstars(&taskgroup, &grade, None);
let ti = TaskInfo { name: taskgroup.name, subtasks: subtaskstars };
tasks.push(ti);
totalgrade += grade.grade.unwrap_or(0);
max_totalgrade += taskgroup.tasks.iter().map(|x| x.stars).max().unwrap_or(0);
}
let relative_points = (totalgrade * 100) / max_totalgrade;
data.insert("tasks".to_string(), to_json(&tasks));
let now = time::get_time();
let passed_secs = now.sec - participation.start.sec;
if passed_secs < 0 {
// behandle inkonsistente Serverzeit
}
let left_secs = i64::from(contest.duration) * 60 - passed_secs;
let is_time_left = contest.duration == 0 || left_secs >= 0;
let is_time_up = !is_time_left;
let left_min = left_secs / 60;
let left_sec = left_secs % 60;
let time_left = format!("{}:{:02}", left_min, left_sec);
data.insert("is_started".to_string(), to_json(&true));
data.insert("participation_start_date".to_string(), to_json(&passed_secs));
data.insert("total_points".to_string(), to_json(&totalgrade));
data.insert("max_total_points".to_string(), to_json(&max_totalgrade));
data.insert("relative_points".to_string(), to_json(&relative_points));
data.insert("is_time_left".to_string(), to_json(&is_time_left));
data.insert("is_time_up".to_string(), to_json(&is_time_up));
if is_time_left {
data.insert("left_min".to_string(), to_json(&left_min));
data.insert("left_sec".to_string(), to_json(&left_sec));
data.insert("time_left".to_string(), to_json(&time_left));
data.insert("seconds_left".to_string(), to_json(&left_secs));
}
data.insert("no_login".to_string(), to_json(&true));
}
// This only checks if a query string is existent, so any query string will
// lead to the assumption that a bare page is requested. This is useful to
// disable caching (via random token) but should be changed if query string
// can obtain more than only this meaning in the future
if query_string.is_none() {
data.insert("not_bare".to_string(), to_json(&true));
}
Ok(("contest".to_owned(), data))
}
pub fn show_contest_results(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
let mut data = json_val::Map::new();
fill_user_data(&session, &mut data);
let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);
let mut results: Vec<(String, i32, Vec<(String, String, i32, String, Vec)>)> = Vec::new();
for (group, groupdata) in resultdata {
let mut groupresults: Vec<(String, String, i32, String, Vec)> = Vec::new();
//TODO: use user
for (user, userdata) in groupdata {
let mut userresults: Vec = Vec::new();
userresults.push(String::new());
let mut summe = 0;
for grade in userdata {
if let Some(g) = grade.grade {
userresults.push(format!("{}", g));
summe += g;
} else {
userresults.push("–".to_string());
}
}
userresults[0] = format!("{}", summe);
groupresults.push((user.firstname.unwrap_or_else(|| "–".to_string()),
user.lastname.unwrap_or_else(|| "–".to_string()),
user.id,
grade_to_string(user.grade),
userresults))
}
results.push((group.name.to_string(), group.id.unwrap_or(0), groupresults));
}
data.insert("taskname".to_string(), to_json(&tasknames));
data.insert("result".to_string(), to_json(&results));
let c = conn.get_contest_by_id(contest_id);
let ci = ContestInfo { id: c.id.unwrap(), name: c.name.clone(), duration: c.duration, public: c.public };
data.insert("contest".to_string(), to_json(&ci));
data.insert("contestname".to_string(), to_json(&c.name));
Ok(("contestresults".to_owned(), data))
}
pub fn start_contest(conn: &T, contest_id: i32, session_token: &str, csrf_token: &str)
-> MedalResult<()> {
// TODO: Is _or_new the right semantic? We need a CSRF token anyway …
let session = conn.get_session_or_new(&session_token).unwrap();
let contest = conn.get_contest_by_id(contest_id);
// Check logged in or open contest
if contest.duration != 0 && !session.is_logged_in() {
return Err(MedalError::AccessDenied);
}
// Check CSRF token
if session.is_logged_in() && session.csrf_token != csrf_token {
return Err(MedalError::CsrfCheckFailed);
}
// Check other constraints
let constraints = check_contest_constraints(&session, &contest);
if !(constraints.contest_running && constraints.grade_matching) {
return Err(MedalError::AccessDenied);
}
// Start contest
match conn.new_participation(&session_token, contest_id) {
Ok(_) => Ok(()),
_ => Err(MedalError::AccessDenied), // Contest already started TODO: Maybe redirect to page with hint
}
}
pub fn login(conn: &T, login_data: (String, String), login_info: LoginInfo)
-> Result {
let (username, password) = login_data;
match conn.login(None, &username, &password) {
Ok(session_token) => Ok(session_token),
Err(()) => {
let mut data = json_val::Map::new();
data.insert("reason".to_string(), to_json(&"Login fehlgeschlagen. Bitte erneut versuchen.".to_string()));
data.insert("username".to_string(), to_json(&username));
data.insert("parent".to_string(), to_json(&"base"));
fill_oauth_data(login_info, &mut data);
Err(("login".to_owned(), data))
}
}
}
pub fn login_with_code(
conn: &T, code: &str, login_info: LoginInfo)
-> Result, (String, json_val::Map)> {
match conn.login_with_code(None, &code) {
Ok(session_token) => Ok(Ok(session_token)),
Err(()) => match conn.create_user_with_groupcode(None, &code) {
Ok(session_token) => Ok(Err(session_token)),
Err(()) => {
let mut data = json_val::Map::new();
data.insert("reason".to_string(), to_json(&"Kein gültiger Code. Bitte erneut versuchen.".to_string()));
data.insert("code".to_string(), to_json(&code));
data.insert("parent".to_string(), to_json(&"base"));
fill_oauth_data(login_info, &mut data);
Err(("login".to_owned(), data))
}
},
}
}
pub fn logout(conn: &T, session_token: Option) {
session_token.map(|token| conn.logout(&token));
}
#[cfg(feature = "signup")]
pub fn signup(conn: &T, session_token: Option, signup_data: (String, String, String))
-> MedalResult {
let (username, email, password) = signup_data;
if username == "" || email == "" || password == "" {
return Ok(SignupResult::EmptyFields);
}
let salt = helpers::make_salt();
let hash = helpers::hash_password(&password, &salt)?;
let result = conn.signup(&session_token.unwrap(), &username, &email, hash, &salt);
Ok(result)
}
#[cfg(feature = "signup")]
pub fn signupdata(query_string: Option) -> json_val::Map {
let mut data = json_val::Map::new();
if let Some(query) = query_string {
if let Some(status) = query.strip_prefix("status=") {
if ["EmailTaken", "UsernameTaken", "UserLoggedIn", "EmptyFields"].contains(&status) {
data.insert((status).to_string(), to_json(&true));
}
}
}
data
}
pub fn load_submission(conn: &T, task_id: i32, session_token: &str, subtask: Option)
-> MedalResult {
let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
match match subtask {
Some(s) => conn.load_submission(&session, task_id, Some(&s)),
None => conn.load_submission(&session, task_id, None),
} {
Some(submission) => Ok(submission.value),
None => Ok("{}".to_string()),
}
}
pub fn save_submission(conn: &T, task_id: i32, session_token: &str, csrf_token: &str,
data: String, grade_percentage: i32, subtask: Option)
-> MedalResult
{
let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
if session.csrf_token != csrf_token {
return Err(MedalError::CsrfCheckFailed);
}
let (t, _, c) = conn.get_task_by_id_complete(task_id);
match conn.get_participation(session.id, c.id.expect("Value from database")) {
None => return Err(MedalError::AccessDenied),
Some(participation) => {
let now = time::get_time();
let passed_secs = now.sec - participation.start.sec;
if passed_secs < 0 {
// behandle inkonsistente Serverzeit
}
let left_secs = i64::from(c.duration) * 60 - passed_secs;
if c.duration > 0 && left_secs < -10 {
return Err(MedalError::AccessDenied);
// Contest over
// TODO: Nicer message!
}
}
}
/* Here, two variants of the grade are calculated. Which one is correct depends on how the percentage value is
* calculated in the task. Currently, grade_rounded is the correct one, but if that ever changes, the other code
* can just be used.
*
* Switch to grade_truncated, when a user scores 98/99 but only gets 97/99 awarded.
* Switch to grade_rounded, when a user scores 5/7 but only gets 4/7 awarded.
*/
/* Code for percentages calculated with integer rounding.
*
* This is a poor man's rounding that only works for division by 100.
*
* floor((floor((x*10)/100)+5)/10) = round(x/100)
*/
let grade_rounded = ((grade_percentage * t.stars * 10) / 100 + 5) / 10;
/* Code for percentages calculated with integer truncation.
*
* Why add one to grade_percentage and divide by 101?
*
* For all m in 1..100 and all n in 0..n, this holds:
*
* floor( ((floor(n / m * 100)+1) * m ) / 101 ) = n
*
* Thus, when percentages are calculated as
*
* p = floor(n / m * 100)
*
* we can recover n by using
*
* n = floor( ((p+1) * m) / 101 )
*/
// let grade_truncated = ((grade_percentage+1) * t.stars) / 101;
let submission = Submission { id: None,
session_user: session.id,
task: task_id,
grade: grade_rounded,
validated: false,
nonvalidated_grade: grade_rounded,
needs_validation: true,
subtask_identifier: subtask,
value: data,
date: time::get_time() };
conn.submit_submission(submission);
Ok("{}".to_string())
}
pub fn show_task(conn: &T, task_id: i32, session_token: &str) -> MedalValueResult {
let session = conn.get_session_or_new(&session_token).unwrap();
let (t, tg, c) = conn.get_task_by_id_complete(task_id);
let grade = conn.get_taskgroup_user_grade(&session_token, tg.id.unwrap()); // TODO: Unwrap?
let tasklist = conn.get_contest_by_id_complete(c.id.unwrap()); // TODO: Unwrap?
let mut prevtaskgroup: Option = None;
let mut nexttaskgroup: Option = None;
let mut current_found = false;
let mut subtaskstars = Vec::new();
for taskgroup in tasklist.taskgroups {
if current_found {
nexttaskgroup = Some(taskgroup);
break;
}
if taskgroup.id == tg.id {
current_found = true;
subtaskstars = generate_subtaskstars(&taskgroup, &grade, Some(task_id));
} else {
prevtaskgroup = Some(taskgroup);
}
}
match conn.get_own_participation(&session_token, c.id.expect("Value from database")) {
None => Err(MedalError::AccessDenied),
Some(participation) => {
let now = time::get_time();
let passed_secs = now.sec - participation.start.sec;
if passed_secs < 0 {
// behandle inkonsistente Serverzeit
}
let mut data = json_val::Map::new();
data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
data.insert("subtasks".to_string(), to_json(&subtaskstars));
data.insert("prevtask".to_string(), to_json(&prevtaskgroup.map(|tg| tg.tasks[0].id)));
data.insert("nexttask".to_string(), to_json(&nexttaskgroup.map(|tg| tg.tasks[0].id))); // TODO: fail better
let left_secs = i64::from(c.duration) * 60 - passed_secs;
if c.duration > 0 && left_secs < 0 {
Err(MedalError::AccessDenied)
// Contest over
// TODO: Nicer message!
} else {
let (hour, min, sec) = (left_secs / 3600, left_secs / 60 % 60, left_secs % 60);
data.insert("time_left".to_string(), to_json(&format!("{}:{:02}", hour, min)));
data.insert("time_left_sec".to_string(), to_json(&format!(":{:02}", sec)));
let taskpath = format!("{}{}", c.location, t.location);
data.insert("contestname".to_string(), to_json(&c.name));
data.insert("name".to_string(), to_json(&tg.name));
data.insert("title".to_string(), to_json(&format!("Aufgabe „{}“ in {}", &tg.name, &c.name)));
data.insert("taskid".to_string(), to_json(&task_id));
data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
data.insert("taskpath".to_string(), to_json(&taskpath));
data.insert("contestid".to_string(), to_json(&c.id));
data.insert("seconds_left".to_string(), to_json(&left_secs));
if c.duration > 0 {
data.insert("duration".to_string(), to_json(&true));
}
Ok(("task".to_owned(), data))
}
}
}
}
#[derive(Serialize, Deserialize)]
pub struct GroupInfo {
pub id: i32,
pub name: String,
pub tag: String,
pub code: String,
}
pub fn show_groups(conn: &T, session_token: &str) -> MedalValueResult {
let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
// let groupvec = conn.get_group(session_token);
let mut data = json_val::Map::new();
fill_user_data(&session, &mut data);
let v: Vec =
conn.get_groups(session.id)
.iter()
.map(|g| GroupInfo { id: g.id.unwrap(),
name: g.name.clone(),
tag: g.tag.clone(),
code: g.groupcode.clone() })
.collect();
data.insert("group".to_string(), to_json(&v));
data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
Ok(("groups".to_string(), data))
}
#[derive(Serialize, Deserialize)]
pub struct MemberInfo {
pub id: i32,
pub firstname: String,
pub lastname: String,
pub grade: String,
pub logincode: String,
}
pub fn show_group(conn: &T, group_id: i32, session_token: &str) -> MedalValueResult {
let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
let group = conn.get_group_complete(group_id).unwrap(); // TODO handle error
let mut data = json_val::Map::new();
fill_user_data(&session, &mut data);
if group.admin != session.id {
return Err(MedalError::AccessDenied);
}
let gi = GroupInfo { id: group.id.unwrap(),
name: group.name.clone(),
tag: group.tag.clone(),
code: group.groupcode.clone() };
let v: Vec =
group.members
.iter()
.map(|m| MemberInfo { id: m.id,
firstname: m.firstname.clone().unwrap_or_else(|| "".to_string()),
lastname: m.lastname.clone().unwrap_or_else(|| "".to_string()),
grade: grade_to_string(m.grade),
logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()) })
.collect();
data.insert("group".to_string(), to_json(&gi));
data.insert("member".to_string(), to_json(&v));
data.insert("groupname".to_string(), to_json(&gi.name));
Ok(("group".to_string(), data))
}
pub fn modify_group(_conn: &T, _group_id: i32, _session_token: &str) -> MedalResult<()> {
unimplemented!()
}
pub fn add_group(conn: &T, session_token: &str, csrf_token: &str, name: String, tag: String)
-> MedalResult {
let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::AccessDenied)?;
if session.csrf_token != csrf_token {
return Err(MedalError::CsrfCheckFailed);
}
let groupcode = helpers::make_group_code();
// TODO: check for collisions
let mut group = Group { id: None, name, groupcode, tag, admin: session.id, members: Vec::new() };
conn.add_group(&mut group);
Ok(group.id.unwrap())
}
pub fn group_csv(conn: &T, session_token: &str) -> MedalValueResult {
let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
let mut data = json_val::Map::new();
data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
Ok(("groupcsv".to_string(), data))
}
// TODO: Should creating the users and groups happen in a batch operation to speed things up?
pub fn upload_groups(conn: &T, session_token: &str, csrf_token: &str, group_data: &str)
-> MedalResult<()> {
let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
if session.csrf_token != csrf_token {
return Err(MedalError::CsrfCheckFailed);
}
println!("{}", group_data);
let mut v: Vec> = serde_json::from_str(group_data).or(Err(MedalError::AccessDenied))?; // TODO: Change error type
v.sort_unstable_by(|a, b| a[0].partial_cmp(&b[0]).unwrap());
let mut group_code = "".to_string();
let mut name = "".to_string();
let mut group = Group { id: None,
name: name.clone(),
groupcode: group_code,
tag: "".to_string(),
admin: session.id,
members: Vec::new() };
for line in v {
if name != line[0] {
if name != "" {
conn.create_group_with_users(group);
}
name = line[0].clone();
group_code = helpers::make_group_code();
// TODO: check for collisions
group = Group { id: None,
name: name.clone(),
groupcode: group_code,
tag: name.clone(),
admin: session.id,
members: Vec::new() };
}
let mut user = SessionUser::group_user_stub();
user.grade = line[1].parse::().unwrap_or(0);
user.firstname = Some(line[2].clone());
user.lastname = Some(line[3].clone());
group.members.push(user);
}
conn.create_group_with_users(group);
Ok(())
}
#[allow(dead_code)]
pub fn show_groups_results(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
//TODO: use g
let _g = conn.get_contest_groups_grades(session.id, contest_id);
let data = json_val::Map::new();
Ok(("groupresults".into(), data))
}
pub struct SexInformation {
pub require_sex: bool,
pub allow_sex_na: bool,
pub allow_sex_diverse: bool,
pub allow_sex_other: bool,
}
pub fn show_profile(conn: &T, session_token: &str, user_id: Option,
query_string: Option, sex_infos: SexInformation)
-> MedalValueResult
{
let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
let mut data = json_val::Map::new();
fill_user_data(&session, &mut data);
data.insert("require_sex".to_string(), to_json(&sex_infos.require_sex));
data.insert("allow_sex_na".to_string(), to_json(&sex_infos.allow_sex_na));
data.insert("allow_sex_diverse".to_string(), to_json(&sex_infos.allow_sex_diverse));
data.insert("allow_sex_other".to_string(), to_json(&sex_infos.allow_sex_other));
match user_id {
None => {
data.insert("profile_firstname".to_string(), to_json(&session.firstname));
data.insert("profile_lastname".to_string(), to_json(&session.lastname));
data.insert("profile_street".to_string(), to_json(&session.street));
data.insert("profile_zip".to_string(), to_json(&session.zip));
data.insert("profile_city".to_string(), to_json(&session.city));
data.insert(format!("sel{}", session.grade), to_json(&"selected"));
if let Some(sex) = session.sex {
data.insert(format!("sex_{}", sex), to_json(&"selected"));
} else {
data.insert("sex_None".to_string(), to_json(&"selected"));
}
data.insert("profile_logincode".to_string(), to_json(&session.logincode));
if session.password.is_some() {
data.insert("profile_username".to_string(), to_json(&session.username));
}
if session.managed_by.is_none() {
data.insert("profile_not_in_group".into(), to_json(&true));
}
if session.oauth_provider != Some("pms".to_string()) {
data.insert("profile_not_pms".into(), to_json(&true));
}
data.insert("ownprofile".into(), to_json(&true));
if let Some(query) = query_string {
if let Some(status) = query.strip_prefix("status=") {
if ["NothingChanged",
"DataChanged",
"PasswordChanged",
"PasswordMissmatch",
"firstlogin",
"SignedUp"].contains(&status)
{
data.insert((status).to_string(), to_json(&true));
}
}
}
let now = time::get_time();
// TODO: Needs to be filtered
let participations: Vec<(i32, String, bool, bool)> =
conn.get_all_participations_complete(session.id)
.into_iter()
.rev()
.map(|(participation, contest)| {
let passed_secs = now.sec - participation.start.sec;
let left_secs = i64::from(contest.duration) * 60 - passed_secs;
let is_time_left = contest.duration == 0 || left_secs >= 0;
let has_timelimit = contest.duration != 0;
(contest.id.unwrap(), contest.name, has_timelimit, is_time_left)
})
.collect();
data.insert("participations".into(), to_json(&participations));
}
// Case user_id: teacher modifing a students profile
Some(user_id) => {
// TODO: Add test to check if this access restriction works
let (user, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
let group = opt_group.ok_or(MedalError::AccessDenied)?;
if group.admin != session.id {
return Err(MedalError::AccessDenied);
}
data.insert("profile_firstname".to_string(), to_json(&user.firstname));
data.insert("profile_lastname".to_string(), to_json(&user.lastname));
data.insert("profile_street".to_string(), to_json(&session.street));
data.insert("profile_zip".to_string(), to_json(&session.zip));
data.insert("profile_city".to_string(), to_json(&session.city));
data.insert(format!("sel{}", user.grade), to_json(&"selected"));
if let Some(sex) = user.sex {
data.insert(format!("sex_{}", sex), to_json(&"selected"));
} else {
data.insert("sex_None".to_string(), to_json(&"selected"));
}
data.insert("profile_logincode".to_string(), to_json(&user.logincode));
if user.username.is_some() {
data.insert("profile_username".to_string(), to_json(&user.username));
}
if user.managed_by.is_none() {
data.insert("profile_not_in_group".into(), to_json(&true));
}
if session.oauth_provider != Some("pms".to_string()) {
data.insert("profile_not_pms".into(), to_json(&true));
}
data.insert("ownprofile".into(), to_json(&false));
if let Some(query) = query_string {
if let Some(status) = query.strip_prefix("status=") {
if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch"].contains(&status) {
data.insert((status).to_string(), to_json(&true));
}
}
}
}
}
Ok(("profile".to_string(), data))
}
#[derive(Debug, PartialEq)]
pub enum ProfileStatus {
NothingChanged,
DataChanged,
PasswordChanged,
PasswordMissmatch,
}
impl From for String {
fn from(s: ProfileStatus) -> String { format!("{:?}", s) }
}
pub fn edit_profile(conn: &T, session_token: &str, user_id: Option, csrf_token: &str,
(firstname,
lastname,
street,
zip,
city,
password,
password_repeat,
grade,
sex): (String,
String,
Option,
Option,
Option,
Option,
Option,
i32,
Option))
-> MedalResult
{
let mut session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
if session.csrf_token != csrf_token {
return Err(MedalError::AccessDenied); // CsrfError
}
let mut result = ProfileStatus::NothingChanged;
let mut password_and_salt = None;
if let (Some(password), Some(password_repeat)) = (password, password_repeat) {
if password != "" || password_repeat != "" {
if password == password_repeat {
let salt = helpers::make_salt();
let hash = helpers::hash_password(&password, &salt)?;
password_and_salt = Some((hash, salt));
result = ProfileStatus::PasswordChanged;
} else {
result = ProfileStatus::PasswordMissmatch;
}
}
}
if result == ProfileStatus::NothingChanged {
if session.firstname.as_ref() == Some(&firstname)
&& session.lastname.as_ref() == Some(&lastname)
&& session.street == street
&& session.zip == zip
&& session.city == city
&& session.grade == grade
&& session.sex == sex
{
return Ok(ProfileStatus::NothingChanged);
} else {
result = ProfileStatus::DataChanged;
}
}
match user_id {
None => {
session.firstname = Some(firstname);
session.lastname = Some(lastname);
session.grade = grade;
session.sex = sex;
if street.is_some() {
session.street = street;
}
if zip.is_some() {
session.zip = zip;
}
if city.is_some() {
session.city = city;
}
if let Some((password, salt)) = password_and_salt {
session.password = Some(password);
session.salt = Some(salt);
}
conn.save_session(session);
}
Some(user_id) => {
// TODO: Add test to check if this access restriction works
let (mut user, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
let group = opt_group.ok_or(MedalError::AccessDenied)?;
if group.admin != session.id {
return Err(MedalError::AccessDenied);
}
user.firstname = Some(firstname);
user.lastname = Some(lastname);
user.grade = grade;
user.sex = sex;
if street.is_some() {
user.street = street;
}
if zip.is_some() {
user.zip = zip;
}
if city.is_some() {
user.city = city;
}
if let Some((password, salt)) = password_and_salt {
user.password = Some(password);
user.salt = Some(salt);
}
conn.save_session(user);
}
}
Ok(result)
}
pub fn teacher_infos(conn: &T, session_token: &str) -> MedalValueResult {
let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
if !session.is_teacher {
return Err(MedalError::AccessDenied);
}
let mut data = json_val::Map::new();
fill_user_data(&session, &mut data);
Ok(("teacher".to_string(), data))
}
pub fn admin_index(conn: &T, session_token: &str) -> MedalValueResult {
conn.get_session(&session_token)
.ensure_logged_in()
.ok_or(MedalError::NotLoggedIn)?
.ensure_admin()
.ok_or(MedalError::AccessDenied)?;
let data = json_val::Map::new();
Ok(("admin".to_string(), data))
}
pub fn admin_search_users(conn: &T, session_token: &str,
s_data: (Option,
Option,
Option,
Option,
Option,
Option))
-> MedalValueResult
{
conn.get_session(&session_token)
.ensure_logged_in()
.ok_or(MedalError::NotLoggedIn)?
.ensure_admin()
.ok_or(MedalError::AccessDenied)?;
let mut data = json_val::Map::new();
match conn.get_search_users(s_data) {
Ok(users) => {
data.insert("users".to_string(), to_json(&users));
if users.len() >= 30 {
data.insert("more_users".to_string(), to_json(&true));
}
}
Err(groups) => {
data.insert("groups".to_string(), to_json(&groups));
if groups.len() >= 30 {
data.insert("more_groups".to_string(), to_json(&true));
}
}
};
Ok(("admin_search_results".to_string(), data))
}
pub fn admin_show_user(conn: &T, user_id: i32, session_token: &str) -> MedalValueResult {
let session = conn.get_session(&session_token)
.ensure_logged_in()
.ok_or(MedalError::NotLoggedIn)?
.ensure_admin()
.ok_or(MedalError::AccessDenied)?;
let mut data = json_val::Map::new();
let (user, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
// TODO: This is not nice, the fill_user_data is meant to fill the data of the user being logged in right now!
// Need to find a better solution for this, so we have no longer to replace the CSRF token here
fill_user_data(&user, &mut data);
data.insert("logincode".to_string(), to_json(&user.logincode));
data.insert("userid".to_string(), to_json(&user.id));
data.insert("oauthid".to_string(), to_json(&user.oauth_foreign_id));
data.insert("oauthprovider".to_string(), to_json(&user.oauth_provider));
if let Some(group) = opt_group {
data.insert("group_id".to_string(), to_json(&group.id));
data.insert("group_name".to_string(), to_json(&group.name));
}
let v: Vec =
conn.get_groups(user_id)
.iter()
.map(|g| GroupInfo { id: g.id.unwrap(),
name: g.name.clone(),
tag: g.tag.clone(),
code: g.groupcode.clone() })
.collect();
data.insert("group".to_string(), to_json(&v));
let parts = conn.get_all_participations_complete(user_id);
let pi: Vec<(i32, String)> = parts.into_iter().map(|(_, c)| (c.id.unwrap(), c.name)).collect();
data.insert("participations".to_string(), to_json(&pi));
data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
Ok(("admin_user".to_string(), data))
}
pub fn admin_delete_user(conn: &T, user_id: i32, session_token: &str, csrf_token: &str)
-> MedalValueResult {
let session = conn.get_session(&session_token)
.ensure_logged_in()
.ok_or(MedalError::NotLoggedIn)?
.ensure_admin()
.ok_or(MedalError::AccessDenied)?;
if session.csrf_token != csrf_token {
return Err(MedalError::CsrfCheckFailed);
}
let parts = conn.get_all_participations_complete(user_id);
let groups = conn.get_groups(user_id);
let mut data = json_val::Map::new();
if parts.len() > 0 {
data.insert("reason".to_string(), to_json(&"Benutzer hat Teilnahmen."));
Ok(("delete_fail".to_string(), data))
} else if groups.len() > 0 {
data.insert("reason".to_string(), to_json(&"Benutzer ist Administrator von Gruppen."));
Ok(("delete_fail".to_string(), data))
} else {
conn.delete_user(user_id);
Ok(("delete_ok".to_string(), data))
}
}
pub fn admin_show_group(conn: &T, group_id: i32, session_token: &str) -> MedalValueResult {
let session = conn.get_session(&session_token)
.ensure_logged_in()
.ok_or(MedalError::NotLoggedIn)?
.ensure_admin()
.ok_or(MedalError::AccessDenied)?;
let group = conn.get_group_complete(group_id).unwrap(); // TODO handle error
let mut data = json_val::Map::new();
let gi = GroupInfo { id: group.id.unwrap(),
name: group.name.clone(),
tag: group.tag.clone(),
code: group.groupcode.clone() };
let v: Vec =
group.members
.iter()
.map(|m| MemberInfo { id: m.id,
firstname: m.firstname.clone().unwrap_or_else(|| "".to_string()),
lastname: m.lastname.clone().unwrap_or_else(|| "".to_string()),
grade: grade_to_string(m.grade),
logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()) })
.collect();
data.insert("group".to_string(), to_json(&gi));
data.insert("member".to_string(), to_json(&v));
data.insert("groupname".to_string(), to_json(&gi.name));
data.insert("group_admin_id".to_string(), to_json(&group.admin));
let user = conn.get_user_by_id(group.admin).ok_or(MedalError::AccessDenied)?;
data.insert("group_admin_firstname".to_string(), to_json(&user.firstname));
data.insert("group_admin_lastname".to_string(), to_json(&user.lastname));
data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
Ok(("admin_group".to_string(), data))
}
pub fn admin_delete_group(conn: &T, group_id: i32, session_token: &str, csrf_token: &str)
-> MedalValueResult {
let session = conn.get_session(&session_token)
.ensure_logged_in()
.ok_or(MedalError::NotLoggedIn)?
.ensure_admin()
.ok_or(MedalError::AccessDenied)?;
if session.csrf_token != csrf_token {
return Err(MedalError::CsrfCheckFailed);
}
let group = conn.get_group_complete(group_id).unwrap(); // TODO handle error
let mut data = json_val::Map::new();
if group.members.len() > 0 {
data.insert("reason".to_string(), to_json(&"Gruppe ist nicht leer."));
Ok(("delete_fail".to_string(), data))
} else {
conn.delete_group(group_id);
Ok(("delete_ok".to_string(), data))
}
}
pub fn admin_show_participation(conn: &T, user_id: i32, contest_id: i32, session_token: &str)
-> MedalValueResult {
let session = conn.get_session(&session_token)
.ensure_logged_in()
.ok_or(MedalError::NotLoggedIn)?
.ensure_admin()
.ok_or(MedalError::AccessDenied)?;
let contest = conn.get_contest_by_id_complete(contest_id);
let subms: Vec<(String, Vec<(i32, Vec<(String, i32)>)>)> = contest.taskgroups
.into_iter()
.map(|tg| {
(tg.name,
tg.tasks
.into_iter()
.map(|t| {
(t.stars,
conn.get_all_submissions(user_id, t.id.unwrap(), None)
.into_iter()
.map(|s| (self::time::strftime("%FT%T%z", &self::time::at(s.date)).unwrap(), s.grade))
.collect())
})
.collect())
})
.collect();
let mut data = json_val::Map::new();
data.insert("submissions".to_string(), to_json(&subms));
data.insert("contestid".to_string(), to_json(&contest.id));
data.insert("contestname".to_string(), to_json(&contest.name));
let user = conn.get_user_by_id(user_id).ok_or(MedalError::AccessDenied)?;
fill_user_data(&user, &mut data);
data.insert("userid".to_string(), to_json(&user.id));
let participation = conn.get_participation(user.id, contest_id).ok_or(MedalError::AccessDenied)?;
data.insert("start_date".to_string(),
to_json(&self::time::strftime("%FT%T%z", &self::time::at(participation.start)).unwrap()));
data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
Ok(("admin_participation".to_string(), data))
}
pub fn admin_delete_participation(conn: &T, user_id: i32, contest_id: i32, session_token: &str,
csrf_token: &str)
-> MedalValueResult
{
let session = conn.get_session(&session_token)
.ensure_logged_in()
.ok_or(MedalError::NotLoggedIn)?
.ensure_admin()
.ok_or(MedalError::AccessDenied)?;
if session.csrf_token != csrf_token {
return Err(MedalError::CsrfCheckFailed);
}
let user = conn.get_user_by_id(user_id).ok_or(MedalError::AccessDenied)?;
let _part = conn.get_participation(user.id, contest_id).ok_or(MedalError::AccessDenied)?;
let data = json_val::Map::new();
conn.delete_participation(user_id, contest_id);
Ok(("delete_ok".to_string(), data))
}
pub fn admin_show_contests(conn: &T, session_token: &str) -> MedalValueResult {
let _session = conn.get_session(&session_token)
.ensure_logged_in()
.ok_or(MedalError::NotLoggedIn)?
.ensure_admin()
.ok_or(MedalError::AccessDenied)?;
let mut data = json_val::Map::new();
// TODO: This returns the contests ordered by 'positionalnumber'
// Should this ordering be changed?
let contests: Vec<_> = conn.get_contest_list().into_iter().map(|contest| (contest.id, contest.name)).collect();
data.insert("contests".to_string(), to_json(&contests));
Ok(("admin_contests".to_string(), data))
}
pub fn admin_contest_export(conn: &T, contest_id: i32, session_token: &str) -> MedalResult {
conn.get_session(&session_token)
.ensure_logged_in()
.ok_or(MedalError::NotLoggedIn)?
.ensure_admin()
.ok_or(MedalError::AccessDenied)?;
let contest = conn.get_contest_by_id_complete(contest_id);
let taskgroup_ids: Vec<(i32, String)> =
contest.taskgroups.into_iter().map(|tg| (tg.id.unwrap(), tg.name)).collect();
let filename = format!("contest_{}__{}__{}.csv",
contest_id,
self::time::strftime("%F_%H-%M-%S", &self::time::now()).unwrap(),
helpers::make_filename_secret());
conn.export_contest_results_to_file(contest_id, &taskgroup_ids, &format!("./export/{}", filename));
Ok(filename)
}
pub fn admin_show_cleanup(conn: &T, session_token: &str) -> MedalValueResult {
let session = conn.get_session(&session_token)
.ensure_logged_in()
.ok_or(MedalError::NotLoggedIn)?
.ensure_admin()
.ok_or(MedalError::AccessDenied)?;
let mut data = json_val::Map::new();
data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
Ok(("admin_cleanup".to_string(), data))
}
pub fn admin_do_cleanup(conn: &T, session_token: &str, csrf_token: &str)
-> MedalValueResult {
let session = conn.get_session(&session_token)
.ensure_logged_in()
.ok_or(MedalError::NotLoggedIn)?
.ensure_admin()
.ok_or(MedalError::AccessDenied)?;
if session.csrf_token != csrf_token {
return Err(MedalError::CsrfCheckFailed);
}
let now = time::get_time();
let maxstudentage = now - time::Duration::days(180); // Delete managed users after 180 days of inactivity
let maxteacherage = now - time::Duration::days(1095); // Delete teachers after 3 years of inactivity
let maxage = now - time::Duration::days(3650); // Delete every user after 10 years of inactivity
let result = conn.remove_old_users_and_groups(maxstudentage, Some(maxteacherage), Some(maxage));
let mut data = json_val::Map::new();
if let Ok((n_users, n_groups, n_teachers, n_other)) = result {
let infodata = format!(",n_users:{},n_groups:{},n_teachers:{},n_other:{}",
n_users, n_groups, n_teachers, n_other);
data.insert("data".to_string(), to_json(&infodata));
Ok(("delete_ok".to_string(), data))
} else {
data.insert("reason".to_string(), to_json(&"Fehler."));
Ok(("delete_fail".to_string(), data))
}
}
#[derive(PartialEq, Clone, Copy)]
pub enum UserType {
User,
Teacher,
Admin,
}
pub enum UserSex {
Female,
Male,
Unknown,
}
pub struct ForeignUserData {
pub foreign_id: String,
pub foreign_type: UserType,
pub sex: UserSex,
pub firstname: String,
pub lastname: String,
}
pub fn login_oauth(conn: &T, user_data: ForeignUserData, oauth_provider_id: String)
-> Result<(String, bool), (String, json_val::Map)> {
match conn.login_foreign(None,
&oauth_provider_id,
&user_data.foreign_id,
(user_data.foreign_type != UserType::User,
user_data.foreign_type == UserType::Admin,
&user_data.firstname,
&user_data.lastname,
match user_data.sex {
UserSex::Male => Some(1),
UserSex::Female => Some(2),
UserSex::Unknown => Some(0),
})) {
Ok((session_token, last_activity)) => {
let redirect_profile = if let Some(last_activity) = last_activity {
let now = time::get_time();
now - last_activity > time::Duration::days(60)
} else {
true
};
Ok((session_token, redirect_profile))
}
Err(()) => {
let mut data = json_val::Map::new();
data.insert("reason".to_string(), to_json(&"OAuth-Login failed.".to_string()));
Err(("login".to_owned(), data))
}
}
}