Commit 2119f7a2 authored by Robert Czechowski's avatar Robert Czechowski
Browse files

Merge branch 'master' into password

parents d13460d4 c8c643cd
......@@ -15,6 +15,9 @@ struct ContestYaml {
// The task path is stored relatively to the contest.yaml for easier identificationy
// Concatenation happens in functions::show_task
pub fn parse_yaml(content: &str, filename: &str, directory: &str) -> Option<Contest> {
let config: ContestYaml = serde_yaml::from_str(&content).unwrap();
......
......@@ -28,6 +28,7 @@ pub trait MedalConnection {
fn submit_submission(&self, submission: Submission);
fn get_grade_by_submission(&self, submission_id: u32) -> Grade;
fn get_contest_groups_grades(&self, session_id: u32, contest_id: u32) -> (Vec<String>, Vec<(Group, Vec<(UserInfo, Vec<Grade>)>)>);
fn get_contest_user_grades(&self, session: String, contest_id: u32) -> Vec<Grade>;
fn get_contest_list(&self) -> Vec<Contest>;
fn get_contest_by_id(&self, contest_id: u32) -> Contest;
......
......@@ -7,7 +7,9 @@ use db_objects::*;
use rand::{thread_rng, Rng, distributions::Alphanumeric};
use time;
use self::time::{Timespec, Duration};
use std::path::{Path};
......@@ -82,8 +84,20 @@ impl MedalConnection for Connection {
}
});
match res {
Ok(session) => Some(session),
_ => None
Ok(session) => {
let duration = if session.permanent_login { Duration::days(90) } else { Duration::minutes(90) };
let now = time::get_time();
if now - session.last_activity? < duration {
self.execute("UPDATE session_user SET last_activity = ?1 WHERE id = ?2", &[&now, &session.id]).unwrap();
Some(session)
}
else {
// Session timed out
// Should remove session token from session_user
None
}
},
_ => None // no session found, should create new session in get_session_or_new()
}
}
fn save_session(&self, session: SessionUser) {
......@@ -181,7 +195,7 @@ impl MedalConnection for Connection {
}) {
Ok((id, password_hash, salt)) => {
//println!("{}, {}", password, password_hash.unwrap());
if hash_password(&password, &salt.unwrap()) == password_hash.unwrap() {
if hash_password(&password, &salt.unwrap()) == password_hash.unwrap() { // TODO: fail more pleasantly
// Login okay, update session now!
let session_token: String = thread_rng().sample_iter(&Alphanumeric).take(10).collect();
......@@ -313,15 +327,15 @@ impl MedalConnection for Connection {
submission.save(self);
let mut grade = self.get_grade_by_submission(submission.id.unwrap());
if submission.grade > grade.grade {
grade.grade = submission.grade;
if grade.grade.is_none() || submission.grade > grade.grade.unwrap() {
grade.grade = Some(submission.grade);
grade.validated = false;
grade.save(self);
}
}
fn get_grade_by_submission(&self, submission_id: u32) -> Grade {
self.query_row("SELECT grade.taskgroup, grade.user, grade.grade, grade.validated FROM grade JOIN task ON grade.taskgroup = task.taskgroup JOIN submission ON task.id = submission.task AND grade.user = submission.user WHERE submission.id = ?1", &[&submission_id], |row| {
self.query_row("SELECT grade.taskgroup, grade.user, grade.grade, grade.validated FROM grade JOIN task ON grade.taskgroup = task.taskgroup JOIN submission ON task.id = submission.task AND grade.user = submission.session_user WHERE submission.id = ?1", &[&submission_id], |row| {
Grade {
taskgroup: row.get(0),
user: row.get(1),
......@@ -329,11 +343,11 @@ impl MedalConnection for Connection {
validated: row.get(3),
}
}).unwrap_or_else(|_| {
self.query_row("SELECT task.taskgroup, submission.user FROM submission JOIN task ON task.id = submission.task WHERE submission.id = ?1", &[&submission_id], |row| {
self.query_row("SELECT task.taskgroup, submission.session_user FROM submission JOIN task ON task.id = submission.task WHERE submission.id = ?1", &[&submission_id], |row| {
Grade {
taskgroup: row.get(0),
user: row.get(1),
grade: 0,
grade: None,
validated: false,
}
}).unwrap() // should this unwrap?
......@@ -356,8 +370,8 @@ impl MedalConnection for Connection {
taskindex.insert(*i, index);
index = index + 1
}
let mut stmt = self.prepare("SELECT grade.taskgroup, grade.user, grade.grade, grade.validated, usergroup.id, usergroup.name, usergroup.groupcode, usergroup.tag, student.id, student.username, student.firstname, student.lastname
let mut stmt = self.prepare("SELECT grade.taskgroup, grade.user, grade.grade, grade.validated, usergroup.id, usergroup.name, usergroup.groupcode, usergroup.tag, student.id, student.username, student.logincode, student.firstname, student.lastname
FROM grade
JOIN taskgroup ON grade.taskgroup = taskgroup.id
JOIN session_user AS student ON grade.user = student.id
......@@ -389,43 +403,90 @@ impl MedalConnection for Connection {
if let Some(t/*Ok((grade, mut group, mut userinfo))*/) = gradeinfo_iter.next() {
let (grade, mut group, mut userinfo) = t.unwrap();
println!("yes");
let mut grades: Vec<Grade> = vec![Default::default(); n_tasks];
let mut users: Vec<(UserInfo, Vec<Grade>)> = Vec::new();
let mut groups: Vec<(Group, Vec<(UserInfo, Vec<Grade>)>)> = Vec::new();
let index = grade.taskgroup;
grades[taskindex[&index]] = grade;
// TODO: does
// https://stackoverflow.com/questions/29859892/mutating-an-item-inside-of-nested-loops
// help to spare all these clones?
for ggu in gradeinfo_iter {
if let Ok((g, gr, ui)) = ggu {
if gr.id != group.id {
users.push((userinfo.clone(), grades));
grades = vec![Default::default(); n_tasks];
groups.push((group.clone(), users));
users = Vec::new();
let mut grades: Vec<Grade> = vec![Default::default(); n_tasks];
let mut users: Vec<(UserInfo, Vec<Grade>)> = Vec::new();
let mut groups: Vec<(Group, Vec<(UserInfo, Vec<Grade>)>)> = Vec::new();
let index = grade.taskgroup;
grades[taskindex[&index]] = grade;
// TODO: does
// https://stackoverflow.com/questions/29859892/mutating-an-item-inside-of-nested-loops
// help to spare all these clones?
for ggu in gradeinfo_iter {
if let Ok((g, gr, ui)) = ggu {
if gr.id != group.id {
users.push((userinfo.clone(), grades));
grades = vec![Default::default(); n_tasks];
groups.push((group.clone(), users));
users = Vec::new();
}
else if ui.id != userinfo.id {
users.push((userinfo.clone(), grades));
grades = vec![Default::default(); n_tasks];
}
let index = g.taskgroup;
grades[taskindex[&index]] = g;
}
else if ui.id != userinfo.id {
users.push((userinfo.clone(), grades));
grades = vec![Default::default(); n_tasks];
}
let index = g.taskgroup;
grades[taskindex[&index]] = g;
}
}
users.push((userinfo, grades));
groups.push((group, users));
users.push((userinfo, grades));
groups.push((group, users));
(tasknames.iter().map(|(_, name)| name.clone()).collect(), groups)
}
else {
println!("no");
(Vec::new(), Vec::new())
(Vec::new(), Vec::new()) // should those be default filled?
}
}
fn get_contest_user_grades(&self, session_token: String, contest_id: u32) -> Vec<Grade> {
let mut stmt = self.prepare("SELECT id, name FROM taskgroup WHERE contest = ?1 ORDER BY id ASC").unwrap();
let mut tasknames_iter = stmt.query_map(&[&contest_id], |row| {
let x : (u32, String) = (row.get(0), row.get(1));
x
}).unwrap();
let tasknames : Vec<(u32, String)> = tasknames_iter.map(|x| x.unwrap()).collect();
let mut taskindex: ::std::collections::BTreeMap<u32, usize> = ::std::collections::BTreeMap::new();
let n_tasks = tasknames.len();
let mut index = 0;
for (i, _) in &tasknames {
taskindex.insert(*i, index);
index = index + 1
}
let mut stmt = self.prepare("SELECT grade.taskgroup, grade.user, grade.grade, grade.validated
FROM grade
JOIN taskgroup ON grade.taskgroup = taskgroup.id
JOIN session_user ON session_user.id = grade.user
WHERE session_user.session_token = ?1 AND taskgroup.contest = ?2
ORDER BY taskgroup.id ASC").unwrap();
let mut gradeinfo_iter = stmt.query_map(&[&session_token, &contest_id], |row| {
Grade {
taskgroup: row.get(0),
user: row.get(1),
grade: row.get(2),
validated: row.get(3),
}
}).unwrap();
let mut grades: Vec<Grade> = vec![Default::default(); n_tasks];
for g in gradeinfo_iter {
let g = g.unwrap();
let index = g.taskgroup;
grades[taskindex[&index]] = g;
}
grades
/*else {
println!("no");
Vec::new()
}*/
}
fn get_contest_list(&self) -> Vec<Contest> {
......@@ -522,7 +583,7 @@ impl MedalConnection for Connection {
SELECT ?1, id, ?2 FROM session_user WHERE session_token = ?3",
&[&contest_id, &now, &session]).unwrap();
Ok(self.get_participation(session, contest_id).unwrap())
Ok(self.get_participation(session, contest_id).unwrap()) // TODO: This errors if not logged in …
}
}
......@@ -578,7 +639,7 @@ impl MedalConnection for Connection {
}
fn find_next_submission_to_validate(&self, userid: u32, taskgroupid: u32) {
let (id, validated) : (u32, bool) = self.query_row("SELECT id, validated FROM submission JOIN task ON submission.task = task.id WHERE task.taskgroup = ?1 AND submission.user = ?2 ORDER BY value DESC id DESC LIMIT 1", &[&taskgroupid, &userid], |row| {(row.get(0), row.get(1))}).unwrap();;
let (id, validated) : (u32, bool) = self.query_row("SELECT id, validated FROM submission JOIN task ON submission.task = task.id WHERE task.taskgroup = ?1 AND submission.session_user = ?2 ORDER BY value DESC id DESC LIMIT 1", &[&taskgroupid, &userid], |row| {(row.get(0), row.get(1))}).unwrap();;
if !validated {
self.execute("UPDATE submission SET needs_validation = 1 WHERE id = ?1", &[&id]).unwrap();
}
......
......@@ -99,7 +99,7 @@ pub struct Submission {
pub struct Grade {
pub taskgroup: u32,
pub user: u32,
pub grade: u8,
pub grade: Option<u8>,
pub validated: bool,
}
......@@ -169,7 +169,7 @@ impl SessionUser {
}
pub fn ensure_alive(self) -> Option<Self> {
let duration = if self.permanent_login { Duration::days(90) } else { Duration::minutes(30) };
let duration = if self.permanent_login { Duration::days(90) } else { Duration::minutes(90) };
let now = time::get_time();
if now - self.last_activity? < duration {
Some(self)
......
......@@ -101,12 +101,23 @@ pub fn show_contests<T: MedalConnection>(conn: &T) -> MedalValue {
pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: u32, session_token: String) -> MedalValueResult {
let c = conn.get_contest_by_id_complete(contest_id);
let grades = conn.get_contest_user_grades(session_token.clone(), contest_id);
// TODO: Clean up star generation
let mut tasks = Vec::new();
for task in c.taskgroups {
for (task, grade) in c.taskgroups.into_iter().zip(grades) {
let mut not_print_yet = true;
let mut blackstars :usize = 0;
let mut stasks = Vec::new();
for st in task.tasks {
stasks.push(SubTaskInfo{id: st.id.unwrap(), linktext: str::repeat("☆", st.stars as usize)})
blackstars = 0;
if not_print_yet && st.stars >= grade.grade.unwrap_or(0) {
blackstars = grade.grade.unwrap_or(0) as usize;
not_print_yet = false;
}
stasks.push(SubTaskInfo{id: st.id.unwrap(), linktext: format!("{}{}", str::repeat("★", blackstars as usize),str::repeat("☆", st.stars as usize - blackstars as usize))})
}
let mut ti = TaskInfo {name: task.name,
subtasks: stasks};
......@@ -126,7 +137,8 @@ pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: u32, session_token
let mut data = json_val::Map::new();
data.insert("contest".to_string(), to_json(&ci));
if let Some(session) = conn.get_session(&session_token) {
data.insert("logged_in".to_string(), to_json(&false));
if let Some(session) = conn.get_session(&session_token) {
data.insert("logged_in".to_string(), to_json(&true));
data.insert("username".to_string(), to_json(&session.username));
data.insert("firstname".to_string(), to_json(&session.firstname));
......@@ -181,10 +193,20 @@ pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: u32, sessi
for (user, userdata) in groupdata {
let mut userresults: Vec<String> = Vec::new();
userresults.push(String::new());
let mut summe = 0;
for grade in userdata {
userresults.push(format!("Grade"))
if let Some(g) = grade.grade {
userresults.push(format!("{}", g));
summe += g;
} else {
userresults.push(format!("–"));
}
}
userresults[0] = format!("{}", summe);
groupresults.push((format!("Name"), userresults))
}
......@@ -195,6 +217,18 @@ pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: u32, sessi
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(),
location: c.location.clone(),
filename: c.filename.clone(),
name: c.name.clone(),
duration: c.duration,
public: c.public,
tasks: Vec::new(),
};
data.insert("contest".to_string(), to_json(&ci));
Ok(("contestresults".to_owned(), data))
}
......@@ -251,16 +285,20 @@ pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) -> ()
}
pub fn load_submission<T: MedalConnection>(conn: &T, task_id: u32, session_token: String) -> MedalResult<String> {
pub fn load_submission<T: MedalConnection>(conn: &T, task_id: u32, session_token: String, subtask: Option<String>) -> MedalResult<String> {
let session = conn.get_session(&session_token).ok_or(MedalError::AccessDenied)?.ensure_alive().ok_or(MedalError::AccessDenied)?; // TODO SessionTimeout
match conn.load_submission(&session, task_id, None) {
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<T: MedalConnection>(conn: &T, task_id: u32, session_token: String, csrf_token: String, data: String, grade: u8) -> MedalResult<String> {
pub fn save_submission<T: MedalConnection>(conn: &T, task_id: u32, session_token: String, csrf_token: String, data: String, grade: u8, subtask: Option<String>) -> MedalResult<String> {
let session = conn.get_session(&session_token).ok_or(MedalError::AccessDenied)?.ensure_alive().ok_or(MedalError::AccessDenied)?; // TODO SessionTimeout
if session.csrf_token != csrf_token {
......@@ -275,7 +313,7 @@ pub fn save_submission<T: MedalConnection>(conn: &T, task_id: u32, session_token
validated: false,
nonvalidated_grade: grade,
needs_validation: true,
subtask_identifier: None,
subtask_identifier: subtask,
value: data,
date: time::get_time()
};
......
......@@ -16,7 +16,7 @@ use staticfile::Static;
use iron_sessionstorage::SessionStorage;
use iron_sessionstorage::backends::SignedCookieBackend;
use rusqlite::Connection;
use urlencoded::{UrlEncodedBody};
use urlencoded::{UrlEncodedBody, UrlEncodedQuery};
use persistent::Write;
use handlebars_iron::{HandlebarsEngine,DirectorySource,Template};
......@@ -77,6 +77,29 @@ impl iron_sessionstorage::Value for SessionToken {
}
use ::iron::middleware::{AroundMiddleware,Handler};
use rand::{thread_rng, Rng, distributions::Alphanumeric};
pub struct CookieDistributor {}
impl CookieDistributor {
pub fn new() -> Self { Self {} }
}
impl AroundMiddleware for CookieDistributor {
fn around(self, handler: Box<Handler>) -> Box<Handler> {
Box::new(move |req: &mut Request| -> IronResult<Response> {
if !req.session().get::<SessionToken>().expect("blub...").is_some() {
let session_token: String = thread_rng().sample_iter(&Alphanumeric).take(10).collect();
req.session().set(SessionToken { token: session_token }).unwrap();
}
handler.handle(req)
})
}
}
#[derive(Debug)]
struct SessionError {
message: String
......@@ -101,12 +124,12 @@ trait RequestSession {
impl<'a, 'b> RequestSession for Request<'a, 'b> {
fn get_session_token(self: &mut Self) -> Option<String> {
let session_token = SessionRequestExt::session(self).get::<SessionToken>().unwrap();
let session_token = self.session().get::<SessionToken>().unwrap();
(|st: Option<SessionToken>| -> Option<String> {Some(st?.token)}) (session_token)
}
fn require_session_token(self: &mut Self) -> IronResult<String> {
match SessionRequestExt::session(self).get::<SessionToken>().unwrap() {
match self.session().get::<SessionToken>().unwrap() {
Some(SessionToken { token: session }) => Ok(session),
_ => {
......@@ -121,7 +144,7 @@ impl<'a, 'b> RequestSession for Request<'a, 'b> {
}
fn expect_session_token(self: &mut Self) -> IronResult<String> {
match SessionRequestExt::session(self).get::<SessionToken>().unwrap() {
match self.session().get::<SessionToken>().unwrap() {
Some(SessionToken { token: session }) => Ok(session),
_ => {
Err(IronError { error: Box::new(SessionError { message: "No valid session found, access denied".to_string() }),
......@@ -413,13 +436,15 @@ fn logout(req: &mut Request) -> IronResult<Response> {
fn submission(req: &mut Request) -> IronResult<Response> {
let task_id = req.expect_int::<u32>("taskid")?;
let session_token = req.expect_session_token()?;
let subtask : Option<String> =
(|| -> Option<String> {req.get_ref::<UrlEncodedQuery>().ok()?.get("subtask")?.get(0).map(|x| x.to_owned())})();
println!("{}",task_id);
let result = {
let mutex = req.get::<Write<SharedDatabaseConnection>>().unwrap();
let conn = mutex.lock().unwrap_or_else(|e| e.into_inner());
functions::load_submission(&*conn, task_id, session_token)
functions::load_submission(&*conn, task_id, session_token, subtask)
};
match result {
......@@ -437,20 +462,22 @@ fn submission(req: &mut Request) -> IronResult<Response> {
fn submission_post(req: &mut Request) -> IronResult<Response> {
let task_id = req.expect_int::<u32>("taskid")?;
let session_token = req.expect_session_token()?;
let (csrf_token, data, grade) = {
let (csrf_token, data, grade, subtask) = {
let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
(iexpect!(formdata.get("csrf"),(status::BadRequest, mime!(Text/Html), format!("400 Bad Request")))[0].to_owned(),
iexpect!(formdata.get("data"),(status::BadRequest, mime!(Text/Html), format!("400 Bad Request")))[0].to_owned(),
iexpect!(formdata.get("grade").unwrap_or(&vec!["0".to_owned()])[0].parse::<u8>().ok(),(status::BadRequest, mime!(Text/Html), format!("400 Bad Request"))),
formdata.get("subtask").map(|x| x[0].to_owned()),
)
};
println!("{}",data);
println!("{}",task_id);
println!("{}",grade);
let result = {
let mutex = req.get::<Write<SharedDatabaseConnection>>().unwrap();
let conn = mutex.lock().unwrap_or_else(|e| e.into_inner());
functions::save_submission(&*conn, task_id, session_token, csrf_token, data, grade)
functions::save_submission(&*conn, task_id, session_token, csrf_token, data, grade, subtask)
};
match result {
......@@ -857,8 +884,10 @@ pub fn start_server(conn: Connection, config: ::Config) -> iron::error::HttpResu
ch.link(Write::<SharedDatabaseConnection>::both(conn));
ch.link(Write::<SharedConfiguration>::both(config.clone()));
ch.link_around(CookieDistributor::new());
ch.link_around(SessionStorage::new(SignedCookieBackend::new(my_secret)));
ch.link_after(get_handlebars_engine());
ch.link_after(ErrorReporter);
......
"use strict";
function hash_to_dict() {
splithash = location.hash.substr(1).split('&').filter(function(x){return x.length>0}).map(function(x){return x.split('=')});
dict = {};
for (i in splithash) {
for (j in splithash[i]) {
var splithash = location.hash.substr(1).split('&').filter(function(x){return x.length>0}).map(function(x){return x.split('=')});
var dict = {};
for (var i in splithash) {
for (var j in splithash[i]) {
dict[splithash[i][0]] = splithash[i][j];
}
}
......@@ -14,10 +15,8 @@ window.hashdict = hash_to_dict();
window.load_task_object = function (callback) {
params = {
task: window.hashdict["taskid"]
}
$.get("/load", params,
console.log(callback);
$.get("/load/" + window.hashdict["taskid"], {},
function(data) {
callback(data);
}, "json").fail(function(){
......@@ -28,23 +27,21 @@ window.load_task_object = function (callback) {
window.save_task_object = function (object, callback) {
if (!callback) callback = function(data){}; // is this necessary?
params = {
task: window.hashdict["taskid"],
var params = {
csrf: window.hashdict["csrftoken"],
value: JSON.stringify(object)
data: JSON.stringify(object)
}
$.post("/save", params, callback, "json").fail(function(){
$.post("/save/" + window.hashdict["taskid"], params, callback, "json").fail(function(){
alert("Save failed.");
});
}
window.load_subtask_object = function (subtaskname, callback) {
params = {
task: window.hashdict["taskid"],
var params = {
subtask: subtaskname
}
$.get("/load", params,
$.get("/load/" + window.hashdict["taskid"], params,
function(data) {
callback(data);
}, "json").fail(function(){
......@@ -55,13 +52,12 @@ window.load_subtask_object = function (subtaskname, callback) {
window.save_subtask_object = function (subtaskname, object, callback) {
if (!callback) callback = function(data){}; // is this necessary?
params = {
task: window.hashdict["taskid"],
var params = {
subtask: subtaskname,
csrf: window.hashdict["csrftoken"],
value: JSON.stringify(object)
data: JSON.stringify(object)
}
$.post("/save", params, callback, "json").fail(function(){
$.post("/save/" + window.hashdict["taskid"], params, callback, "json").fail(function(){
alert("Save failed.");
});
}
"use strict";
function hash_to_dict() {
var splithash = location.hash.substr(1).split('&').filter(function(x){return x.length>0}).map(function(x){return x.split('=')});
var dict = {};
for (var i in splithash) {
for (var j in splithash[i]) {
dict[splithash[i][0]] = splithash[i][j];
}
}
return dict;
}
window.hashdict = hash_to_dict();
window.load_task_object = function (callback) {
console.log(callback);
$.get("/load/" + window.hashdict["taskid"], {},
function(data) {
callback(data);
}, "json").fail(function(){
alert("Load failed.");
})
}
window.save_task_object = function (object, callback) {
if (!callback) callback = function(data){}; // is this necessary?
var params = {
csrf: window.hashdict["csrftoken"],
data: JSON.stringify(object)
}
$.post("/save/" + window.hashdict["taskid"], params, callback, "json").fail(function(){
alert("Save failed.");
});