Commit 05480abf authored by Robert Czechowski's avatar Robert Czechowski
Browse files

Add Yaml-contest-config support, make contest & task display work

parent d4b1f567
...@@ -14,13 +14,17 @@ iron = "0.5.1" ...@@ -14,13 +14,17 @@ iron = "0.5.1"
rand = "*" rand = "*"
mount ="0.3" mount ="0.3"
router = "0.5" router = "0.5"
params = "*"
serde = "1.0" serde = "1.0"
urlencoded = "0.5" urlencoded = "0.5"
persistent = "0.3" persistent = "0.3"
staticfile = "0.4" staticfile = "0.4"
serde_derive = "1.0" serde_derive = "1.0"
handlebars-iron = "0.25.1" handlebars-iron = "0.25.1"
iron-sessionstorage = "" iron-sessionstorage = "*"
serde_yaml = "*"
reqwest = "0.6.2"
linked-hash-map = "0.5.1" linked-hash-map = "0.5.1"
......
...@@ -14,3 +14,5 @@ ...@@ -14,3 +14,5 @@
} }
} }
} }
use db_objects::*; use db_objects::*;
use ::functions;
pub trait MedalConnection { pub trait MedalConnection {
...@@ -17,6 +18,7 @@ pub trait MedalConnection { ...@@ -17,6 +18,7 @@ pub trait MedalConnection {
fn login(&self, session: Option<String>, username: String, password: String) -> Result<String,()>; fn login(&self, session: Option<String>, username: String, password: String) -> Result<String,()>;
fn login_with_code(&self, session: Option<String>, logincode: String) -> Result<String,()>; fn login_with_code(&self, session: Option<String>, logincode: String) -> Result<String,()>;
fn login_foreign(&self, session: Option<String>, foreign_id: u32, foreign_type: functions::UserType, firstname: String, lastname:String) -> Result<String,()>;
fn create_user_with_groupcode(&self, session: Option<String>, groupcode: String) -> Result<String,()>; fn create_user_with_groupcode(&self, session: Option<String>, groupcode: String) -> Result<String,()>;
fn logout(&self, session: String); fn logout(&self, session: String);
......
...@@ -7,6 +7,8 @@ use db_objects::*; ...@@ -7,6 +7,8 @@ use db_objects::*;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use ::functions; // todo: remove (usertype in db)
fn hash_password(password: &str, hash: &str) -> String { fn hash_password(password: &str, hash: &str) -> String {
password.to_string() password.to_string()
} }
...@@ -124,7 +126,7 @@ impl MedalConnection for Connection { ...@@ -124,7 +126,7 @@ impl MedalConnection for Connection {
}, },
_ => {println!("c"); Err(()) } _ => {println!("c"); Err(()) }
} }
} }
fn login_with_code(&self, session: Option<String>, logincode: String) -> Result<String,()> { fn login_with_code(&self, session: Option<String>, logincode: String) -> Result<String,()> {
println!("a {}", logincode); println!("a {}", logincode);
match self.query_row( match self.query_row(
...@@ -147,6 +149,30 @@ impl MedalConnection for Connection { ...@@ -147,6 +149,30 @@ impl MedalConnection for Connection {
_ => {println!("c"); Err(()) } _ => {println!("c"); Err(()) }
} }
} }
fn login_foreign(&self, session: Option<String>, foreign_id: u32, foreign_type: functions::UserType, firstname: String, lastname:String) -> Result<String,()> {
let session_token: String = thread_rng().gen_ascii_chars().take(10).collect();
let csrf_token: String = thread_rng().gen_ascii_chars().take(10).collect();
let now = time::get_time();
println!("x {} {}", firstname, lastname);
match self.query_row(
"SELECT id FROM session_user WHERE pms_id = ?1",
&[&foreign_id],
|row| -> u32 {row.get(0)}) {
Ok(id) => {
self.execute("UPDATE session_user SET session_token = ?1, csrf_token = ?2, last_login = ?3, last_activity = ?3 WHERE id = ?4", &[&session_token, &csrf_token, &now, &id]).unwrap();
Ok(session_token)
},
// Add!
_ => {
self.execute("INSERT INTO session_user (session_token, csrf_token, last_login, last_activity, permanent_login, grade, is_teacher, pms_id, firstname, lastname) VALUES (?1, ?2, ?3, ?3, ?4, ?5, ?6, ?7, ?8, ?9)", &[&session_token, &csrf_token, &now, &false, &0, &(foreign_type != functions::UserType::User), &foreign_id, &firstname, &lastname]).unwrap();
Ok(session_token)
}
}
}
fn create_user_with_groupcode(&self, session: Option<String>, groupcode: String) -> Result<String,()> { fn create_user_with_groupcode(&self, session: Option<String>, groupcode: String) -> Result<String,()> {
println!("a {}", groupcode); println!("a {}", groupcode);
...@@ -328,8 +354,9 @@ impl MedalConnection for Connection { ...@@ -328,8 +354,9 @@ impl MedalConnection for Connection {
}).unwrap() }).unwrap()
} }
fn get_task_by_id_complete(&self, task_id : u32) -> (Task, Taskgroup, Contest) { fn get_task_by_id_complete(&self, task_id : u32) -> (Task, Taskgroup, Contest) {
println!("{}!!", task_id);
self.query_row( self.query_row(
"SELECT task.location, task.stars, taskgroup.id, taskgroup.name, contest.id, contest.location, contest.filename, contest.name, contest.duration, contest.public, contest.start_date, contest.end_date FROM task JOIN taskgroup ON task.taskgroup = taskgroup.id JOIN contest ON taskgroup.contest = contest.id WHERE task.id = ?1", "SELECT task.location, task.stars, taskgroup.id, taskgroup.name, contest.id, contest.location, contest.filename, contest.name, contest.duration, contest.public, contest.start_date, contest.end_date FROM contest JOIN taskgroup ON taskgroup.contest = contest.id JOIN task ON task.taskgroup = taskgroup.id WHERE task.id = ?1",
&[&task_id], &[&task_id],
|row| { |row| {
(Task { (Task {
......
...@@ -18,6 +18,18 @@ pub fn blaa() -> (String, json_val::Map<String, json_val::Value>) { ...@@ -18,6 +18,18 @@ pub fn blaa() -> (String, json_val::Map<String, json_val::Value>) {
#[derive(Serialize, Deserialize)]
pub struct SubTaskInfo {
pub id: u32,
pub linktext: String,
}
#[derive(Serialize, Deserialize)]
pub struct TaskInfo {
pub name: String,
pub subtasks: Vec<SubTaskInfo>,
}
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct ContestInfo { pub struct ContestInfo {
pub id: u32, pub id: u32,
...@@ -26,6 +38,7 @@ pub struct ContestInfo { ...@@ -26,6 +38,7 @@ pub struct ContestInfo {
pub name: String, pub name: String,
pub duration: u32, pub duration: u32,
pub public: bool, pub public: bool,
pub tasks: Vec<TaskInfo>,
} }
...@@ -38,7 +51,8 @@ pub fn show_contests<T: MedalConnection>(conn: &T) -> (String, json_val::Map<Str ...@@ -38,7 +51,8 @@ pub fn show_contests<T: MedalConnection>(conn: &T) -> (String, json_val::Map<Str
filename: c.filename.clone(), filename: c.filename.clone(),
name: c.name.clone(), name: c.name.clone(),
duration: c.duration, duration: c.duration,
public: c.public public: c.public,
tasks: Vec::new(),
}}).collect(); }}).collect();
data.insert("contest".to_string(), to_json(&v)); data.insert("contest".to_string(), to_json(&v));
...@@ -46,15 +60,29 @@ pub fn show_contests<T: MedalConnection>(conn: &T) -> (String, json_val::Map<Str ...@@ -46,15 +60,29 @@ pub fn show_contests<T: MedalConnection>(conn: &T) -> (String, json_val::Map<Str
} }
pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: u32, session_token: String) -> (String, json_val::Map<String, json_val::Value>) { pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: u32, session_token: String) -> (String, json_val::Map<String, json_val::Value>) {
let c = conn.get_contest_by_id(contest_id); use std;
let c = conn.get_contest_by_id_complete(contest_id);
let mut tasks = Vec::new();
for task in c.taskgroups {
let mut stasks = Vec::new();
for st in task.tasks {
stasks.push(SubTaskInfo{id: st.id.unwrap(), linktext: str::repeat("☆", st.stars as usize)})
}
let mut ti = TaskInfo {name: task.name,
subtasks: stasks};
tasks.push(ti);
}
let ci = ContestInfo { let ci = ContestInfo {
id: c.id.unwrap(), id: c.id.unwrap(),
location: c.location.clone(), location: c.location.clone(),
filename: c.filename.clone(), filename: c.filename.clone(),
name: c.name.clone(), name: c.name.clone(),
duration: c.duration, duration: c.duration,
public: c.public public: c.public,
tasks: tasks,
}; };
let mut data = json_val::Map::new(); let mut data = json_val::Map::new();
...@@ -188,11 +216,16 @@ pub fn save_submission<T: MedalConnection>(conn: &T, task_id: u32, session_token ...@@ -188,11 +216,16 @@ pub fn save_submission<T: MedalConnection>(conn: &T, task_id: u32, session_token
pub fn show_task<T: MedalConnection>(conn: &T, task_id: u32, session_token: String) -> (String, json_val::Map<String, json_val::Value>) { pub fn show_task<T: MedalConnection>(conn: &T, task_id: u32, session_token: String) -> (String, json_val::Map<String, json_val::Value>) {
let session = conn.get_session(session_token).unwrap(); // TODO handle error let session = conn.get_session(session_token).unwrap(); // TODO handle error
let (t, tg, c) = conn.get_task_by_id_complete(task_id);
let taskpath = format!("{}{}", c.location, t.location);
let mut data = json_val::Map::new(); let mut data = json_val::Map::new();
data.insert("name".to_string(), to_json(&"Blubtask")); data.insert("name".to_string(), to_json(&tg.name));
data.insert("taskid".to_string(), to_json(&task_id)); data.insert("taskid".to_string(), to_json(&task_id));
data.insert("csrftoken".to_string(), to_json(&session.csrf_token)); data.insert("csrftoken".to_string(), to_json(&session.csrf_token));
data.insert("taskpath".to_string(), to_json(&taskpath));
("task".to_owned(), data) ("task".to_owned(), data)
...@@ -327,4 +360,38 @@ pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: String, csrf_to ...@@ -327,4 +360,38 @@ pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: String, csrf_to
Ok(()) Ok(())
} }
#[derive(PartialEq)]
pub enum UserType {
User,
Teacher,
Admin,
}
pub enum UserGender {
Female,
Male,
Unknown,
}
pub struct ForeignUserData {
pub foreign_id: u32,
pub foreign_type: UserType,
pub gender: UserGender,
pub firstname: String,
pub lastname: String,
}
pub fn login_oauth<T: MedalConnection>(conn: &T, user_data: ForeignUserData) -> Result<String, (String, json_val::Map<String, json_val::Value>)> {
match conn.login_foreign(None, user_data.foreign_id, user_data.foreign_type, user_data.firstname, user_data.lastname) {
Ok(session_token) => {
Ok(session_token)
},
Err(()) => {
let mut data = json_val::Map::new();
data.insert("reason".to_string(), to_json(&"OAuth-Login failed.".to_string()));
Err(("login".to_owned(), data))
}
}
}
...@@ -17,6 +17,9 @@ extern crate mount; ...@@ -17,6 +17,9 @@ extern crate mount;
extern crate staticfile; extern crate staticfile;
extern crate handlebars_iron; extern crate handlebars_iron;
extern crate serde_json; extern crate serde_json;
extern crate params;
extern crate reqwest;
extern crate serde_yaml;
use rusqlite::Connection; use rusqlite::Connection;
...@@ -30,6 +33,7 @@ use db_conn::{MedalConnection, MedalObject}; ...@@ -30,6 +33,7 @@ use db_conn::{MedalConnection, MedalObject};
use db_objects::*; use db_objects::*;
mod webfw_iron; mod webfw_iron;
mod configreader_yaml;
use webfw_iron::start_server; use webfw_iron::start_server;
...@@ -38,10 +42,45 @@ mod functions; ...@@ -38,10 +42,45 @@ mod functions;
use std::path; use std::path;
use std::fs; use std::fs;
#[derive(Serialize, Deserialize)]
pub struct Config {
oauth_client_id: Option<String>,
oauth_client_secret: Option<String>,
oauth_access_token_url: Option<String>,
oauth_user_data_url: Option<String>,
}
fn read_config_from_file(filename: &str) -> Config {
use std::io::Read;
if let Ok(mut file) = fs::File::open(filename) {
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
serde_json::from_str(&contents).unwrap()
} else {
println!("Configuration file '{}' not found.", filename);
Config {
oauth_client_id: None,
oauth_client_secret: None,
oauth_access_token_url: None,
oauth_user_data_url: None,
}
}
}
fn read_contest(p: &path::PathBuf) -> Option<Contest> { fn read_contest(p: &path::PathBuf) -> Option<Contest> {
use std::fs::File;
use std::io::Read;
println!("Try to read some file …"); println!("Try to read some file …");
let mut contest = Contest::new("./".to_string(), "blub.json".to_string(), "Wettbewerb IX".to_string(), 45, true, None, None); let mut file = File::open(p).unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
configreader_yaml::parse_yaml(&contents, p.file_name().to_owned()?.to_str()?, &format!("{}/", p.parent().unwrap().to_str()?)) // unwrap_or("./")?
/*let mut contest = Contest::new("./".to_string(), "blub.json".to_string(), "Wettbewerb IX".to_string(), 45, true, None, None);
let mut taskgroup = Taskgroup::new("Lustige Aufgabe".to_string()); let mut taskgroup = Taskgroup::new("Lustige Aufgabe".to_string());
let mut task = Task::new("blub".to_string(), 1); let mut task = Task::new("blub".to_string(), 1);
taskgroup.tasks.push(task); taskgroup.tasks.push(task);
...@@ -55,7 +94,7 @@ fn read_contest(p: &path::PathBuf) -> Option<Contest> { ...@@ -55,7 +94,7 @@ fn read_contest(p: &path::PathBuf) -> Option<Contest> {
taskgroup.tasks.push(task); taskgroup.tasks.push(task);
contest.taskgroups.push(taskgroup); contest.taskgroups.push(taskgroup);
Some(contest) Some(contest)*/
} }
fn get_all_contest_info(task_dir: &str) -> Vec<Contest> { fn get_all_contest_info(task_dir: &str) -> Vec<Contest> {
...@@ -68,7 +107,7 @@ fn get_all_contest_info(task_dir: &str) -> Vec<Contest> { ...@@ -68,7 +107,7 @@ fn get_all_contest_info(task_dir: &str) -> Vec<Contest> {
_ => (), _ => (),
} }
if p.file_name().unwrap().to_string_lossy().to_string().ends_with(".json") { if p.file_name().unwrap().to_string_lossy().to_string().ends_with(".yaml") {
match read_contest(p) { match read_contest(p) {
Some(contest) => contests.push(contest), Some(contest) => contests.push(contest),
_ => (), _ => (),
...@@ -97,6 +136,8 @@ fn refresh_all_contests(conn : &mut Connection) { ...@@ -97,6 +136,8 @@ fn refresh_all_contests(conn : &mut Connection) {
} }
fn main() { fn main() {
let config = read_config_from_file("config.json");
let mut conn = Connection::create(); let mut conn = Connection::create();
db_apply_migrations::test(&mut conn); db_apply_migrations::test(&mut conn);
...@@ -116,7 +157,7 @@ fn main() { ...@@ -116,7 +157,7 @@ fn main() {
println!(""); println!("");
} }
start_server(conn); start_server(conn, config);
println!("Server started."); println!("Server started.");
} }
...@@ -126,7 +126,7 @@ fn contests(req: &mut Request) -> IronResult<Response> { ...@@ -126,7 +126,7 @@ fn contests(req: &mut Request) -> IronResult<Response> {
fn contest(req: &mut Request) -> IronResult<Response> { fn contest(req: &mut Request) -> IronResult<Response> {
let contest_id = req.extensions.get::<Router>().unwrap().find("contestid").unwrap_or("").parse::<u32>().unwrap_or(0); let contest_id = req.extensions.get::<Router>().unwrap().find("contestid").unwrap_or("").parse::<u32>().unwrap_or(0);
let session_token = SessionRequestExt::session(req).get::<SessionToken>().unwrap().unwrap(); let session_token = SessionRequestExt::session(req).get::<SessionToken>().unwrap().unwrap(); // TODO: Was ist ohne session?
let (template, data) = { let (template, data) = {
// hier ggf. Daten aus dem Request holen // hier ggf. Daten aus dem Request holen
...@@ -313,7 +313,7 @@ fn submission_post(req: &mut Request) -> IronResult<Response> { ...@@ -313,7 +313,7 @@ fn submission_post(req: &mut Request) -> IronResult<Response> {
} }
fn task(req: &mut Request) -> IronResult<Response> { fn task(req: &mut Request) -> IronResult<Response> {
let task_id = req.extensions.get::<Router>().unwrap().find("contestid").unwrap_or("").parse::<u32>().unwrap_or(0); let task_id = req.extensions.get::<Router>().unwrap().find("taskid").unwrap_or("").parse::<u32>().unwrap_or(0);
// TODO: Make work without session // TODO: Make work without session
let session_token = SessionRequestExt::session(req).get::<SessionToken>().unwrap().unwrap(); let session_token = SessionRequestExt::session(req).get::<SessionToken>().unwrap().unwrap();
...@@ -511,12 +511,146 @@ fn user(req: &mut Request) -> IronResult<Response> { ...@@ -511,12 +511,146 @@ fn user(req: &mut Request) -> IronResult<Response> {
Ok(resp) Ok(resp)
} }
#[derive(Deserialize, Debug)]
struct OAuthAccess {
access_token: String,
token_type: String,
refresh_token: String,
expires: Option<u32>, // documented as 'expires_in'
expires_in: Option<u32>, // sent as 'expires'
}
#[derive(Deserialize, Debug)]
pub struct OAuthUserData {
userID: Option<String>, // documented as 'userId'
userId: Option<String>, // sent as 'userID'
userType: String,
gender: String,
firstName: String,
lastName: String,
dateOfBirth: Option<String>,
eMail: Option<String>,
userId_int: Option<u32>,
}
fn oauth(req: &mut Request) -> IronResult<Response> {
use reqwest::header;
use params::{Params, Value};
use std::io::Read;
let (client_id, client_secret, access_token_url, user_data_url) = {
let mutex = req.get::<Write<SharedConfiguration>>().unwrap();
let config = mutex.lock().unwrap();
if let (Some(id), Some(secret), Some(atu), Some(udu))
= (&config.oauth_client_id, &config.oauth_client_secret, &config.oauth_access_token_url, &config.oauth_user_data_url) {
(id.clone(), secret.clone(), atu.clone(), udu.clone())
} else {
return Ok(Response::with(iron::status::NotFound))
}
};
let (state, scope, code): (String, String, String) = {
let map = req.get_ref::<Params>().unwrap();
match (map.find(&["state"]),map.find(&["scope"]),map.find(&["code"])) {
(Some(&Value::String(ref state)),Some(&Value::String(ref scope)),Some(&Value::String(ref code))) if state == "42" => {
(state.clone(), scope.clone(), code.clone())
},
_ => return Ok(Response::with(iron::status::NotFound)),
}
};
let client = reqwest::Client::new().unwrap();
let params = [("code", code), ("grant_type", "authorization_code".to_string())];
let res = client.post(&access_token_url)
.header(header::Authorization(header::Basic {
username: client_id,
password: Some(client_secret)}))
.form(&params)
.send();
let access: OAuthAccess = res.expect("network error").json().expect("malformed json");
let res = client.post(&user_data_url)
.header(header::Authorization(header::Bearer{token: access.access_token}))
.form(&params)
.send();
let mut user_data: OAuthUserData = res.expect("network error").json().expect("malformed json");
if let Some(ref id) = user_data.userID {
user_data.userId_int = Some(id.parse::<u32>().unwrap());
}
if let Some(ref id) = user_data.userId {
user_data.userId_int = Some(id.parse::<u32>().unwrap());
}
use functions::{UserType, UserGender};
let user_data = functions::ForeignUserData {
foreign_id: user_data.userId_int.unwrap(),
foreign_type: match user_data.userType.as_ref() {
"a" | "A" => UserType::Admin,
"t" | "T" => UserType::Teacher,
"s" | "S" | _ => UserType::User,
},
gender: match user_data.gender.as_ref() {
"m" | "M" => UserGender::Male,
"f" | "F" | "w" | "W" => UserGender::Female,
"?" | _ => UserGender::Unknown,
},
firstname: user_data.firstName,
lastname: user_data.lastName,
};
let oauthloginresult = {
// hier ggf. Daten aus dem Request holen
let mutex = req.get::<Write<SharedDatabaseConnection>>().unwrap();
let conn = mutex.lock().unwrap();
// Antwort erstellen und zurücksenden
functions::login_oauth(&*conn, user_data)
/*let mut data = json_val::Map::new();
data.insert("reason".to_string(), to_json(&"Not implemented".to_string()));
("profile", data)*/
};
match oauthloginresult {
// Login successful
Ok(sessionkey) => {
req.session().set(SessionToken { token: sessionkey }).unwrap();
Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
},
// Login failed
Err((template, data)) => {
let mut resp = Response::new();
resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
Ok(resp)
}
}
// Ok(Response::with((iron::status::Ok, format!("{:?}", user_data))))
/*println!("oauth");
let mut data = json_val::Map::new();
let mut resp = Response::new();
resp.set_mut(Template::new("template", data)).set_mut(status::Ok);
Ok(resp)*/
}
// Share Database connection between workers // Share Database connection between workers
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct SharedDatabaseConnection; pub struct SharedDatabaseConnection;
impl Key for SharedDatabaseConnection { type Value = rusqlite::Connection; } impl Key for SharedDatabaseConnection { type Value = rusqlite::Connection; }
// Share Configuration between workers
#[derive(Copy, Clone)]
pub struct SharedConfiguration;
impl Key for SharedConfiguration { type Value = ::Config; }
#[cfg(feature = "watch")] #[cfg(feature = "watch")]
pub fn get_handlebars_engine() -> impl AfterMiddleware { pub fn get_handlebars_engine() -> impl AfterMiddleware {