use iron; use iron::error::HttpResult; use iron::headers::ContentType; use iron::modifiers::{Header, Redirect}; use iron::mime::{Mime, TopLevel, SubLevel}; use iron::status; use iron::{Iron, IronError, Request, IronResult, Response, Chain, Listening, Plugin, Url}; use iron_sessionstorage::SessionStorage; use iron_sessionstorage::backends::SignedCookieBackend; use iron_sessionstorage::traits::*; use iron_sessionstorage; use params::{Params, Value}; use regex::Regex; use render; use router::Router; use staticfile::Static; use systemd::journal; use systemd::unit; use persistent::{Read, Write}; use rusqlite::Connection; use db; use auth; use url; #[derive(Debug)] pub struct Context { pub base_url: String, } impl iron::typemap::Key for Context { type Value = Context; } #[derive(Debug)] pub struct State { pub conn: Connection, } impl iron::typemap::Key for State { type Value = State; } #[derive(Default, Debug, Clone)] struct Login { user: String, } impl iron_sessionstorage::Value for Login { fn get_key() -> &'static str { "login" } fn into_raw(self) -> String { self.user } fn from_raw(v: String) -> Option { Some(Login { user: v }) } } // Construct an absolute url from base_url if provided, or the request url // otherwise. fn url_for(r: &mut Request, path: &str) -> Url { let context = r.get::>().unwrap(); match context.base_url.as_ref() { "" => { let mut url: url::Url = r.url.clone().into(); url.set_path(path); Url::from_generic_url(url).unwrap() }, base_url => { Url::parse(&format!("{}{}", base_url, path)).unwrap() } } } fn overview(r: &mut Request) -> IronResult { let mut _value = try!(r.session().get::()); let units = unit::get_units("*").unwrap(); let sections = ["service", "timer", "socket", "target", "slice", "mount", "path"]; let units_by_section = sections .iter() .map(|&s| { (s.to_owned(), units .iter() .filter(|&u| &u.type_ == s) .collect::>()) }) .collect::>(); // let res = Ok(Response::with((status::Ok, // Header(ContentType::html()), // render_message(&format!("Hello, {} ({})", // name, // session_value.0), // &units)))); // info!("Updating session value. Current value: {}", session_value.0); // session_value.0.push('a'); // try!(r.session().set(session_value)); let renderer = make_renderer(r)?; Ok(Response::with((status::Ok, Header(ContentType::html()), renderer.system_status(&units_by_section)))) } fn journal(r: &mut Request) -> IronResult { let unit = iexpect!(r.extensions.get::().unwrap().find("unit"), status::BadRequest); let re = Regex::new(r"[-_\w\d]*").unwrap(); if !re.is_match(unit) { return Ok(Response::with((status::BadRequest, format!("Unit ({}) does not match {}", unit, re)))); } Ok(Response::with((status::Ok, itry!(journal::get_log(unit, 100))))) } fn unit_status(r: &mut Request) -> IronResult { let unit_name = iexpect!(r.extensions.get::().unwrap().find("unit"), status::BadRequest) .to_string(); let re = Regex::new(r"[-_\w\d]*").unwrap(); if !re.is_match(&unit_name) { return Ok(Response::with((status::BadRequest, format!("Unit ({}) does not match {}", unit_name, re)))); } let ref unit = itry!(unit::get_units(&unit_name))[0]; let log = itry!(journal::get_log(&unit_name, 15)); let renderer = make_renderer(r)?; Ok(Response::with((status::Ok, Header(ContentType::html()), renderer.unit_status(&unit, &log)))) } fn get_logged_in_user(r: &mut Request) -> IronResult> { let login = r.session().get::()?; // Session storage doesn't have a way to delete its cookie, // so we set the username to empty on logout. if let &Some(Login { ref user }) = &login { if user.is_empty() { return Ok(None); } } Ok(login) } fn login(r: &mut Request) -> IronResult { let renderer = make_renderer(r)?; let is_retry = r.method == iron::method::Method::Post; Ok(Response::with((status::Ok, Header(ContentType::html()), renderer.login_page(is_retry)))) } fn authenticate(r: &mut Request) -> IronResult { let (user, password) = { let map = r.get_ref::().unwrap(); let user = match map.get("username") { Some(&Value::String(ref v)) => v, _ => panic!("no username in params: {:?}", map), }; let password = match map.get("password") { Some(&Value::String(ref v)) => v, _ => panic!("no password in params: {:?}", map), }; (user.to_string(), password.to_string()) }; let hash = { let mutex = r.get::>().unwrap(); let state = mutex.lock().unwrap(); db::lookup_user(&state.conn, &user).unwrap() }; if let Some(true) = hash.map(|h| auth::validate(&password, &h)) { let login = Login { user: user.to_string() }; // TODO Make a validated login type info!("User logged in: {:?}", login); r.session().set(login)?; let url = url_for(r, ""); Ok(Response::with((status::Found, Redirect(url)))) } else { login(r) } } fn logout(r: &mut Request) -> IronResult { r.session().set::(Default::default()); let url = url_for(r, ""); Ok(Response::with((status::Found, Redirect(url)))) } fn make_renderer(r: &mut Request) -> IronResult { let user = get_logged_in_user(r)?.map(|u| u.user); let context = r.get::>().unwrap(); Ok(render::Renderer { base_url: context.base_url.to_string(), user: user }) } fn static_file(r: &mut Request) -> IronResult { let file = iexpect!(r.extensions.get::().unwrap().find("file"), status::BadRequest); let css = ContentType(Mime(TopLevel::Text, SubLevel::Css, vec![])); Ok(match file { "main.css" => Response::with((status::Ok, Header(css), include_str!("data/main.css"))), _ => Response::with(status::NotFound) }) } pub fn serve(context: Context, state: State, port: u16) -> HttpResult { // TODO: Use a real secret. let secret = b"secret2".to_vec(); let router = router!( root: get "/" => overview, login: get "/login" => login, authenticate: post "/login" => authenticate, logout: get "/logout" => logout, details: get "/status/:unit" => unit_status, journal: get "/journal/:unit" => journal, css: get "/static/:file" => static_file, ); let mut chain = Chain::new(router); chain.link_around(SessionStorage::new(SignedCookieBackend::new(secret))); chain.link(Read::::both(context)); chain.link(Write::::both(state)); let bind_address = format!("{}:{}", "::", port); Iron::new(chain).http(bind_address.as_str()) }