hagrid-keyserver--hagrid/src/web/mod.rs

353 lines
9.5 KiB
Rust
Raw Normal View History

2018-08-16 18:35:19 +00:00
use rocket;
2018-10-18 14:26:25 +00:00
use rocket::{State, Outcome};
2018-09-19 20:24:38 +00:00
use rocket::http::Status;
use rocket::request::{self, Request, FromRequest};
use rocket::response::status::Custom;
2018-10-18 14:26:25 +00:00
use rocket::response::NamedFile;
use rocket::fairing::AdHoc;
2018-09-19 20:24:38 +00:00
2018-12-25 19:06:28 +00:00
use rocket_contrib::templates::Template;
2018-10-18 14:26:25 +00:00
use std::path::{Path, PathBuf};
2018-08-16 18:35:19 +00:00
mod upload;
use database::{Polymorphic, Database};
use types::{Fingerprint, Email};
2018-09-19 20:24:38 +00:00
use errors::Result;
use Opt;
use std::str::FromStr;
use std::result;
mod queries {
use types::{Fingerprint, Email};
2018-09-19 20:24:38 +00:00
#[derive(Debug)]
pub enum Hkp {
Fingerprint(Fingerprint),
Email(Email),
2018-09-19 20:24:38 +00:00
}
2018-08-16 18:35:19 +00:00
}
2018-09-19 20:24:38 +00:00
mod templates {
#[derive(Serialize)]
pub struct Verify {
pub verified: bool,
pub userid: String,
pub fpr: String,
}
#[derive(Serialize)]
pub struct Delete {
pub token: String,
pub fpr: String,
}
#[derive(Serialize)]
pub struct Confirm {
pub deleted: bool,
}
2018-08-16 18:35:19 +00:00
}
2018-10-18 14:26:25 +00:00
struct StaticDir(String);
pub struct MailTemplateDir(String);
pub struct Domain(String);
2018-10-18 14:26:25 +00:00
2018-10-24 17:44:36 +00:00
impl<'a, 'r> FromRequest<'a, 'r> for queries::Hkp {
type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<queries::Hkp, ()> {
use rocket::request::FormItems;
use std::collections::HashMap;
let query = request.uri().query().unwrap_or("");
2018-12-25 19:06:28 +00:00
let fields = FormItems::from(query).map(|item| {
let (k, v) = item.key_value();
2018-10-24 17:44:36 +00:00
let key = k.url_decode().unwrap_or_default();
let value = v.url_decode().unwrap_or_default();
(key, value)
}).collect::<HashMap<_,_>>();
2018-11-02 10:55:07 +00:00
if fields.len() >= 2 && fields.get("op").map(|x| x == "get").unwrap_or(false) {
2018-10-24 17:44:36 +00:00
let search = fields.get("search").cloned().unwrap_or_default();
2018-11-02 10:55:07 +00:00
let maybe_fpr = Fingerprint::from_str(&search);
2018-10-24 17:44:36 +00:00
2018-11-02 10:55:07 +00:00
if let Ok(fpr) = maybe_fpr {
Outcome::Success(queries::Hkp::Fingerprint(fpr))
2018-10-24 17:44:36 +00:00
} else {
match Email::from_str(&search) {
Ok(email) => Outcome::Success(queries::Hkp::Email(email)),
Err(_) => Outcome::Failure((Status::BadRequest, ())),
2018-08-16 18:35:19 +00:00
}
2018-09-19 20:24:38 +00:00
}
} else {
Outcome::Failure((Status::BadRequest, ()))
}
}
}
2018-10-25 15:37:33 +00:00
fn process_key(bytes: &[u8]) -> result::Result<String, Custom<String>> {
2018-09-19 20:24:38 +00:00
use std::io::Write;
2018-11-25 14:03:27 +00:00
use sequoia_openpgp::armor::{Writer, Kind};
2018-09-19 20:24:38 +00:00
2018-10-25 15:37:33 +00:00
let key = || -> Result<String> {
let mut buffer = Vec::default();
{
let mut writer = Writer::new(&mut buffer, Kind::PublicKey, &[])?;
writer.write_all(&bytes)?;
}
Ok(String::from_utf8(buffer)?)
}();
match key {
Ok(s) => Ok(s),
Err(_) =>
Err(Custom(Status::InternalServerError,
"Failed to ASCII armor key".to_string())),
}
}
#[get("/by-fpr/<fpr>")]
2018-10-25 15:37:33 +00:00
fn by_fpr(db: rocket::State<Polymorphic>, fpr: String)
-> result::Result<String, Custom<String>>
{
let maybe_key = match Fingerprint::from_str(&fpr) {
Ok(ref fpr) => db.by_fpr(fpr),
Err(_) => None,
2018-09-19 20:24:38 +00:00
};
match maybe_key {
2018-10-25 15:37:33 +00:00
Some(ref bytes) => process_key(bytes),
None => Ok("No such key :-(".to_string()),
}
}
2018-09-19 20:24:38 +00:00
#[get("/by-email/<email>")]
2018-10-25 15:37:33 +00:00
fn by_email(db: rocket::State<Polymorphic>, email: String)
-> result::Result<String, Custom<String>>
{
let maybe_key = match Email::from_str(&email) {
Ok(ref email) => db.by_email(email),
Err(_) => None,
};
2018-09-19 20:24:38 +00:00
2018-10-25 15:37:33 +00:00
match maybe_key {
Some(ref bytes) => process_key(bytes),
2018-09-19 20:24:38 +00:00
None => Ok("No such key :-(".to_string()),
}
}
#[get("/vks/verify/<token>")]
2018-09-19 20:24:38 +00:00
fn verify(db: rocket::State<Polymorphic>, token: String)
-> result::Result<Template, Custom<String>>
{
match db.verify_token(&token) {
Ok(Some((userid, fpr))) => {
let context = templates::Verify{
verified: true,
userid: userid.to_string(),
fpr: fpr.to_string(),
};
Ok(Template::render("verify", context))
}
Ok(None) | Err(_) => {
let context = templates::Verify{
verified: false,
userid: "".into(),
fpr: "".into(),
};
Ok(Template::render("verify", context))
2018-08-16 18:35:19 +00:00
}
2018-09-19 20:24:38 +00:00
}
}
#[get("/vks/delete/<fpr>")]
fn delete(db: rocket::State<Polymorphic>, fpr: String,
tmpl: State<MailTemplateDir>, domain: State<Domain>)
2018-09-19 20:24:38 +00:00
-> result::Result<Template, Custom<String>>
{
use mail::send_confirmation_mail;
2018-09-19 20:24:38 +00:00
let fpr = match Fingerprint::from_str(&fpr) {
Ok(fpr) => fpr,
Err(_) => {
return Err(Custom(Status::BadRequest,
"Invalid fingerprint".to_string()));
}
};
match db.request_deletion(fpr.clone()) {
2018-11-02 10:53:49 +00:00
Ok((token,uids)) => {
2018-09-19 20:24:38 +00:00
let context = templates::Delete{
fpr: fpr.to_string(),
2018-11-02 10:53:49 +00:00
token: token.clone(),
2018-09-19 20:24:38 +00:00
};
2018-11-02 10:53:49 +00:00
for uid in uids {
send_confirmation_mail(&uid, &token, &tmpl.0, &domain.0).map_err(|err| {
Custom(Status::InternalServerError, format!("{:?}", err))
})?;
}
2018-09-19 20:24:38 +00:00
Ok(Template::render("delete", context))
}
Err(e) => Err(Custom(Status::InternalServerError,
format!("{}", e))),
}
}
#[get("/vks/confirm/<token>")]
2018-09-19 20:24:38 +00:00
fn confirm(db: rocket::State<Polymorphic>, token: String)
-> result::Result<Template, Custom<String>>
{
match db.confirm_deletion(&token) {
Ok(true) => {
let context = templates::Confirm{
deleted: true,
};
Ok(Template::render("confirm", context))
}
Ok(false) | Err(_) => {
let context = templates::Confirm{
deleted: false,
};
Ok(Template::render("confirm", context))
}
}
2018-08-16 18:35:19 +00:00
}
#[get("/assets/<file..>")]
2018-10-18 14:26:25 +00:00
fn files(file: PathBuf, static_dir: State<StaticDir>) -> Option<NamedFile> {
NamedFile::open(Path::new(&static_dir.0).join("assets").join(file)).ok()
2018-10-18 14:26:25 +00:00
}
2018-10-24 17:44:36 +00:00
#[get("/pks/lookup")]
fn lookup(db: rocket::State<Polymorphic>, key: Option<queries::Hkp>)
2018-10-24 17:44:36 +00:00
-> result::Result<String, Custom<String>>
{
use std::io::Write;
2018-11-25 14:03:27 +00:00
use sequoia_openpgp::armor::{Writer, Kind};
2018-10-24 17:44:36 +00:00
let maybe_key = match key {
Some(queries::Hkp::Fingerprint(ref fpr)) => db.by_fpr(fpr),
Some(queries::Hkp::Email(ref email)) => db.by_email(email),
None => { return Ok("nothing to do".to_string()); }
};
match maybe_key {
Some(bytes) => {
let key = || -> Result<String> {
let mut buffer = Vec::default();
{
let mut writer = Writer::new(&mut buffer, Kind::PublicKey, &[])?;
writer.write_all(&bytes)?;
}
Ok(String::from_utf8(buffer)?)
}();
match key {
Ok(s) => Ok(s),
Err(_) =>
Err(Custom(Status::InternalServerError,
"Failed to ASCII armor key".to_string())),
}
}
None => Ok("No such key :-(".to_string()),
}
}
2018-08-16 18:35:19 +00:00
#[get("/")]
2018-09-19 20:24:38 +00:00
fn root() -> Template {
use std::collections::HashMap;
Template::render("index", HashMap::<String, String>::default())
2018-08-16 18:35:19 +00:00
}
2018-09-19 20:24:38 +00:00
pub fn serve(opt: &Opt, db: Polymorphic) -> Result<()> {
use rocket::config::{Config, Environment};
use std::str::FromStr;
let (addr, port) = match opt.listen.find(':') {
Some(p) => {
let addr = opt.listen[0..p].to_string();
let port = if p < opt.listen.len() - 1 {
u16::from_str(&opt.listen[p+1..]).ok().unwrap_or(8080)
} else {
8080
};
(addr, port)
}
None => (opt.listen.to_string(), 8080)
};
let config = Config::build(Environment::Staging)
.address(addr)
.port(port)
.workers(2)
.root(opt.base.clone())
2018-12-25 19:06:28 +00:00
.extra("template_dir", opt.base.join("templates").to_str().ok_or("Template path invalid")?)
.extra("static_dir", opt.base.join("public").to_str().ok_or("Static path invalid")?)
.extra("domain", opt.domain.clone())
2018-09-19 20:24:38 +00:00
.finalize()?;
let routes = routes![
// infra
root,
files,
// nginx-supported lookup
2018-10-25 15:37:33 +00:00
by_email,
by_fpr,
// HKP
lookup,
upload::multipart_upload,
// verification & deletion
2018-09-19 20:24:38 +00:00
verify,
delete,
confirm,
];
2018-12-25 19:06:28 +00:00
rocket::custom(config)
2018-09-19 20:24:38 +00:00
.attach(Template::fairing())
2018-12-25 19:06:28 +00:00
.attach(AdHoc::on_attach("static_dir", |rocket| {
2018-10-18 14:26:25 +00:00
let static_dir = rocket.config()
.get_str("static_dir")
.unwrap()
.to_string();
Ok(rocket.manage(StaticDir(static_dir)))
}))
2018-12-25 19:06:28 +00:00
.attach(AdHoc::on_attach("template_dir", |rocket| {
let static_dir = rocket.config()
.get_str("template_dir")
.unwrap()
.to_string();
Ok(rocket.manage(MailTemplateDir(static_dir)))
}))
2018-12-25 19:06:28 +00:00
.attach(AdHoc::on_attach("domain", |rocket| {
let static_dir = rocket.config()
.get_str("domain")
.unwrap()
.to_string();
Ok(rocket.manage(Domain(static_dir)))
}))
2018-09-19 20:24:38 +00:00
.mount("/", routes)
.manage(db)
.launch();
Ok(())
2018-08-16 18:35:19 +00:00
}
//POST /keys
//GET /keys/<fpr>