Commit 7654ce04 authored by Robert Czechowski's avatar Robert Czechowski Committed by Robert Czechowski
Browse files

Add cleanup function that deletes old users after 180 days / 3 years / 10 years respectively

parent dc74523b
......@@ -1479,6 +1479,49 @@ pub fn admin_contest_export<T: MedalConnection>(conn: &T, contest_id: i32, sessi
Ok(filename)
}
pub fn admin_show_cleanup<T: MedalConnection>(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<T: MedalConnection>(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,
......
......@@ -304,7 +304,7 @@ impl MedalConnection for Connection {
let query = "INSERT INTO session (session_token, csrf_token, last_activity, account_created, grade, sex,
is_teacher)
VALUES ($1, $2, $3, $4, $5, $6, $7)";
self.execute(query, &[&session_token, &csrf_token, &now, &None::<self::time::Timespec>, &0, &None::<i32>, &false]).unwrap();
self.execute(query, &[&session_token, &csrf_token, &now, &None::<time::Timespec>, &0, &None::<i32>, &false]).unwrap();
let id = self.get_last_id().expect("Expected to get last row id");
......@@ -556,7 +556,7 @@ impl MedalConnection for Connection {
fn create_group_with_users(&self, mut group: Group) {
// Generate group ID:
group.save(self);
let now = time::get_time();
for user in group.members {
......@@ -978,8 +978,8 @@ impl MedalConnection for Connection {
teacher_school_id,
row.get::<_, Option<String>>(17)),
row.get::<_, Option<i32>>(18),
row.get::<_, Option<self::time::Timespec>>(19)
.map(|ts| self::time::strftime("%FT%T%z", &self::time::at(ts)).unwrap()),
row.get::<_, Option<time::Timespec>>(19)
.map(|ts| self::time::strftime("%FT%T%z", &time::at(ts)).unwrap()),
points))
.unwrap();
})
......@@ -1456,6 +1456,97 @@ impl MedalConnection for Connection {
}
}
// TODO, should those unwraps be handled?
fn remove_old_users_and_groups(&self, maxstudentage: time::Timespec, maxteacherage: Option<time::Timespec>, maxage: Option<time::Timespec>) -> Result<(i32, i32, i32, i32),()> {
let query = "SELECT managed_by
FROM session
WHERE username IS NULL AND password IS NULL AND oauth_foreign_id IS NULL AND oauth_provider IS NULL
AND ((last_login < $1 AND last_activity < $1)
OR (last_login < $1 AND last_activity IS NULL)
OR (last_login IS NULL AND last_activity < $1)
OR (last_login IS NULL AND last_activity IS NULL AND account_created < $1))";
let mut groups: Vec<i32> = self.query_map_many(query, &[&maxstudentage], |row| row.get(0)).unwrap();
let query = "DELETE
FROM session
WHERE username IS NULL AND password IS NULL AND oauth_foreign_id IS NULL AND oauth_provider IS NULL
AND ((last_login < $1 AND last_activity < $1)
OR (last_login < $1 AND last_activity IS NULL)
OR (last_login IS NULL AND last_activity < $1)
OR (last_login IS NULL AND last_activity IS NULL AND account_created < $1))";
self.execute(query, &[&maxstudentage]).unwrap();
let n_users = groups.len() as i32;
let mut n_groups = 0;
let mut n_teachers = 0;
let mut n_other = 0;
groups.sort();
groups.dedup();
let query = "SELECT count(*)
FROM session
WHERE managed_by = $1;";
for group in groups {
let groupsize: i64 = self.query_map_one(query, &[&group], |row| row.get(0)).unwrap().unwrap();
if groupsize == 0 {
let query = "DELETE
FROM usergroup
WHERE id = $1";
self.execute(query, &[&group]).unwrap();
n_groups += 1;
}
}
let query = "SELECT id
FROM session
WHERE is_teacher = $1
AND ((last_login < $2 AND last_activity < $2)
OR (last_login < $2 AND last_activity IS NULL)
OR (last_login IS NULL AND last_activity < $2)
OR (last_login IS NULL AND last_activity IS NULL AND account_created < $2))";
if let Some(maxteacherage) = maxteacherage {
let teachers: Vec<i32> = self.query_map_many(query, &[&true, &maxteacherage], |row| row.get(1)).unwrap();
let query = "SELECT count(*)
FROM usergroup
WHERE admin = $1;";
for teacher in teachers {
let groupcount: i64 = self.query_map_one(query, &[&teacher], |row| row.get(0)).unwrap().unwrap();
if groupcount == 0 {
let query = "DELETE
FROM session
WHERE id = $1";
self.execute(query, &[&teacher]).unwrap();
n_teachers += 1;
}
}
}
if let Some(maxage) = maxage {
let query = "SELECT count(*)
FROM session
AND ((last_login < $1 AND last_activity < $1)
OR (last_login < $1 AND last_activity IS NULL)
OR (last_login IS NULL AND last_activity < $1)
OR (last_login IS NULL AND last_activity IS NULL AND account_created < $1))";
n_other = self.query_map_one(query, &[&maxage], |row| row.get(1)).unwrap().unwrap();
let query = "DELETE
FROM session
AND ((last_login < $1 AND last_activity < $1)
OR (last_login < $1 AND last_activity IS NULL)
OR (last_login IS NULL AND last_activity < $1)
OR (last_login IS NULL AND last_activity IS NULL AND account_created < $1))";
self.execute(query, &[&maxage]).unwrap();
}
return Ok((n_users, n_groups, n_teachers, n_other));
}
fn get_debug_information(&self) -> String {
let duration = Duration::minutes(60);
let now = time::get_time();
......
......@@ -141,6 +141,7 @@ pub trait MedalConnection {
fn delete_user(&self, user_id: i32);
fn delete_group(&self, group_id: i32);
fn delete_participation(&self, user_id: i32, contest_id: i32);
fn remove_old_users_and_groups(&self, maxstudentage: time::Timespec, maxteacherage: Option<time::Timespec>, maxage: Option<time::Timespec>) -> Result<(i32, i32, i32, i32),()>;
fn get_search_users(&self,
_: (Option<i32>,
......
......@@ -423,7 +423,7 @@ impl MedalConnection for Connection {
let query = "INSERT INTO session (session_token, csrf_token, last_activity, account_created, grade, sex,
is_teacher)
VALUES ($1, $2, $3, $4, $5, $6, $7)";
self.execute(query, &[&session_token, &csrf_token, &now, &None::<self::time::Timespec>, &0, &None::<i32>, &false]).unwrap();
self.execute(query, &[&session_token, &csrf_token, &now, &None::<time::Timespec>, &0, &None::<i32>, &false]).unwrap();
let id = self.get_last_id().expect("Expected to get last row id");
......@@ -675,7 +675,7 @@ impl MedalConnection for Connection {
fn create_group_with_users(&self, mut group: Group) {
// Generate group ID:
group.save(self);
let now = time::get_time();
for user in group.members {
......@@ -1097,8 +1097,8 @@ impl MedalConnection for Connection {
teacher_school_id,
row.get::<_, Option<String>>(17)),
row.get::<_, Option<i32>>(18),
row.get::<_, Option<self::time::Timespec>>(19)
.map(|ts| self::time::strftime("%FT%T%z", &self::time::at(ts)).unwrap()),
row.get::<_, Option<time::Timespec>>(19)
.map(|ts| self::time::strftime("%FT%T%z", &time::at(ts)).unwrap()),
points))
.unwrap();
})
......@@ -1575,6 +1575,97 @@ impl MedalConnection for Connection {
}
}
// TODO, should those unwraps be handled?
fn remove_old_users_and_groups(&self, maxstudentage: time::Timespec, maxteacherage: Option<time::Timespec>, maxage: Option<time::Timespec>) -> Result<(i32, i32, i32, i32),()> {
let query = "SELECT managed_by
FROM session
WHERE username IS NULL AND password IS NULL AND oauth_foreign_id IS NULL AND oauth_provider IS NULL
AND ((last_login < $1 AND last_activity < $1)
OR (last_login < $1 AND last_activity IS NULL)
OR (last_login IS NULL AND last_activity < $1)
OR (last_login IS NULL AND last_activity IS NULL AND account_created < $1))";
let mut groups: Vec<i32> = self.query_map_many(query, &[&maxstudentage], |row| row.get(0)).unwrap();
let query = "DELETE
FROM session
WHERE username IS NULL AND password IS NULL AND oauth_foreign_id IS NULL AND oauth_provider IS NULL
AND ((last_login < $1 AND last_activity < $1)
OR (last_login < $1 AND last_activity IS NULL)
OR (last_login IS NULL AND last_activity < $1)
OR (last_login IS NULL AND last_activity IS NULL AND account_created < $1))";
self.execute(query, &[&maxstudentage]).unwrap();
let n_users = groups.len() as i32;
let mut n_groups = 0;
let mut n_teachers = 0;
let mut n_other = 0;
groups.sort();
groups.dedup();
let query = "SELECT count(*)
FROM session
WHERE managed_by = $1;";
for group in groups {
let groupsize: i64 = self.query_map_one(query, &[&group], |row| row.get(0)).unwrap().unwrap();
if groupsize == 0 {
let query = "DELETE
FROM usergroup
WHERE id = $1";
self.execute(query, &[&group]).unwrap();
n_groups += 1;
}
}
let query = "SELECT id
FROM session
WHERE is_teacher = $1
AND ((last_login < $2 AND last_activity < $2)
OR (last_login < $2 AND last_activity IS NULL)
OR (last_login IS NULL AND last_activity < $2)
OR (last_login IS NULL AND last_activity IS NULL AND account_created < $2))";
if let Some(maxteacherage) = maxteacherage {
let teachers: Vec<i32> = self.query_map_many(query, &[&true, &maxteacherage], |row| row.get(1)).unwrap();
let query = "SELECT count(*)
FROM usergroup
WHERE admin = $1;";
for teacher in teachers {
let groupcount: i64 = self.query_map_one(query, &[&teacher], |row| row.get(0)).unwrap().unwrap();
if groupcount == 0 {
let query = "DELETE
FROM session
WHERE id = $1";
self.execute(query, &[&teacher]).unwrap();
n_teachers += 1;
}
}
}
if let Some(maxage) = maxage {
let query = "SELECT count(*)
FROM session
AND ((last_login < $1 AND last_activity < $1)
OR (last_login < $1 AND last_activity IS NULL)
OR (last_login IS NULL AND last_activity < $1)
OR (last_login IS NULL AND last_activity IS NULL AND account_created < $1))";
n_other = self.query_map_one(query, &[&maxage], |row| row.get(1)).unwrap().unwrap();
let query = "DELETE
FROM session
AND ((last_login < $1 AND last_activity < $1)
OR (last_login < $1 AND last_activity IS NULL)
OR (last_login IS NULL AND last_activity < $1)
OR (last_login IS NULL AND last_activity IS NULL AND account_created < $1))";
self.execute(query, &[&maxage]).unwrap();
}
return Ok((n_users, n_groups, n_teachers, n_other));
}
fn get_debug_information(&self) -> String {
let duration = Duration::minutes(60);
let now = time::get_time();
......
......@@ -423,7 +423,7 @@ impl MedalConnection for Connection {
let query = "INSERT INTO session (session_token, csrf_token, last_activity, account_created, grade, sex,
is_teacher)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)";
self.execute(query, &[&session_token, &csrf_token, &now, &None::<self::time::Timespec>, &0, &None::<i32>, &false]).unwrap();
self.execute(query, &[&session_token, &csrf_token, &now, &None::<time::Timespec>, &0, &None::<i32>, &false]).unwrap();
let id = self.get_last_id().expect("Expected to get last row id");
......@@ -675,7 +675,7 @@ impl MedalConnection for Connection {
fn create_group_with_users(&self, mut group: Group) {
// Generate group ID:
group.save(self);
let now = time::get_time();
for user in group.members {
......@@ -1097,8 +1097,8 @@ impl MedalConnection for Connection {
teacher_school_id,
row.get::<_, Option<String>>(17)),
row.get::<_, Option<i32>>(18),
row.get::<_, Option<self::time::Timespec>>(19)
.map(|ts| self::time::strftime("%FT%T%z", &self::time::at(ts)).unwrap()),
row.get::<_, Option<time::Timespec>>(19)
.map(|ts| self::time::strftime("%FT%T%z", &time::at(ts)).unwrap()),
points))
.unwrap();
})
......@@ -1575,6 +1575,97 @@ impl MedalConnection for Connection {
}
}
// TODO, should those unwraps be handled?
fn remove_old_users_and_groups(&self, maxstudentage: time::Timespec, maxteacherage: Option<time::Timespec>, maxage: Option<time::Timespec>) -> Result<(i32, i32, i32, i32),()> {
let query = "SELECT managed_by
FROM session
WHERE username IS NULL AND password IS NULL AND oauth_foreign_id IS NULL AND oauth_provider IS NULL
AND ((last_login < ?1 AND last_activity < ?1)
OR (last_login < ?1 AND last_activity IS NULL)
OR (last_login IS NULL AND last_activity < ?1)
OR (last_login IS NULL AND last_activity IS NULL AND account_created < ?1))";
let mut groups: Vec<i32> = self.query_map_many(query, &[&maxstudentage], |row| row.get(0)).unwrap();
let query = "DELETE
FROM session
WHERE username IS NULL AND password IS NULL AND oauth_foreign_id IS NULL AND oauth_provider IS NULL
AND ((last_login < ?1 AND last_activity < ?1)
OR (last_login < ?1 AND last_activity IS NULL)
OR (last_login IS NULL AND last_activity < ?1)
OR (last_login IS NULL AND last_activity IS NULL AND account_created < ?1))";
self.execute(query, &[&maxstudentage]).unwrap();
let n_users = groups.len() as i32;
let mut n_groups = 0;
let mut n_teachers = 0;
let mut n_other = 0;
groups.sort();
groups.dedup();
let query = "SELECT count(*)
FROM session
WHERE managed_by = ?1;";
for group in groups {
let groupsize: i64 = self.query_map_one(query, &[&group], |row| row.get(0)).unwrap().unwrap();
if groupsize == 0 {
let query = "DELETE
FROM usergroup
WHERE id = ?1";
self.execute(query, &[&group]).unwrap();
n_groups += 1;
}
}
let query = "SELECT id
FROM session
WHERE is_teacher = ?1
AND ((last_login < ?2 AND last_activity < ?2)
OR (last_login < ?2 AND last_activity IS NULL)
OR (last_login IS NULL AND last_activity < ?2)
OR (last_login IS NULL AND last_activity IS NULL AND account_created < ?2))";
if let Some(maxteacherage) = maxteacherage {
let teachers: Vec<i32> = self.query_map_many(query, &[&true, &maxteacherage], |row| row.get(1)).unwrap();
let query = "SELECT count(*)
FROM usergroup
WHERE admin = ?1;";
for teacher in teachers {
let groupcount: i64 = self.query_map_one(query, &[&teacher], |row| row.get(0)).unwrap().unwrap();
if groupcount == 0 {
let query = "DELETE
FROM session
WHERE id = ?1";
self.execute(query, &[&teacher]).unwrap();
n_teachers += 1;
}
}
}
if let Some(maxage) = maxage {
let query = "SELECT count(*)
FROM session
AND ((last_login < ?1 AND last_activity < ?1)
OR (last_login < ?1 AND last_activity IS NULL)
OR (last_login IS NULL AND last_activity < ?1)
OR (last_login IS NULL AND last_activity IS NULL AND account_created < ?1))";
n_other = self.query_map_one(query, &[&maxage], |row| row.get(1)).unwrap().unwrap();
let query = "DELETE
FROM session
AND ((last_login < ?1 AND last_activity < ?1)
OR (last_login < ?1 AND last_activity IS NULL)
OR (last_login IS NULL AND last_activity < ?1)
OR (last_login IS NULL AND last_activity IS NULL AND account_created < ?1))";
self.execute(query, &[&maxage]).unwrap();
}
return Ok((n_users, n_groups, n_teachers, n_other));
}
fn get_debug_information(&self) -> String {
let duration = Duration::minutes(60);
let now = time::get_time();
......
......@@ -1055,6 +1055,27 @@ fn admin_export_contest<C>(req: &mut Request) -> IronResult<Response>
Ok(Response::with((status::Found, RedirectRaw(format!("/export/{}", filename)))))
}
fn admin_cleanup<C>(req: &mut Request) -> IronResult<Response>
where C: MedalConnection + std::marker::Send + 'static {
let session_token = req.expect_session_token()?;
let csrf_token = if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
formdata.get("csrf_token").map(|x| x[0].to_owned())
} else {
None
};
let (template, data) = if let Some(csrf_token) = csrf_token {
with_conn![core::admin_do_cleanup, C, req, &session_token, &csrf_token].aug(req)?
} else {
with_conn![core::admin_show_cleanup, C, req, &session_token].aug(req)?
};
let mut resp = Response::new();
resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
Ok(resp)
}
fn oauth<C>(req: &mut Request) -> IronResult<Response>
where C: MedalConnection + std::marker::Send + 'static {
println!("{:?}", req.url.query().unwrap_or(""));
......@@ -1405,6 +1426,8 @@ pub fn start_server<C>(conn: C, config: Config) -> iron::error::HttpResult<iron:
admin_participation_post: post "/admin/user/:userid/:contestid" => admin_participation::<C>,
admin_contests: get "/admin/contest/" => admin_contests::<C>,
admin_export_contest: get "/admin/contest/:contestid/export" => admin_export_contest::<C>,
admin_cleanup: get "/admin/cleanup" => admin_cleanup::<C>,
admin_cleanup_post: post "/admin/cleanup" => admin_cleanup::<C>,
oauth: get "/oauth/:oauthid/" => oauth::<C>,
oauth_school: get "/oauth/:oauthid/:schoolid" => oauth::<C>,
check_cookie: get "/cookie" => cookie_warning,
......
{"status":"ok"}
{"status":"ok"{{data}}}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment