2018-08-16 18:35:19 +00:00
|
|
|
use rocket;
|
2019-02-07 19:58:31 +00:00
|
|
|
use rocket::fairing::AdHoc;
|
2019-03-01 11:58:17 +00:00
|
|
|
use rocket::http::{Header, Status};
|
2019-02-07 19:58:31 +00:00
|
|
|
use rocket::request::{self, FromRequest, Request};
|
2018-09-19 20:24:38 +00:00
|
|
|
use rocket::response::status::Custom;
|
2019-02-22 15:25:06 +00:00
|
|
|
use rocket::response::NamedFile;
|
2019-02-07 19:58:31 +00:00
|
|
|
use rocket::{Outcome, State};
|
2018-12-25 19:06:28 +00:00
|
|
|
use rocket_contrib::templates::Template;
|
2019-02-22 22:29:54 +00:00
|
|
|
use rocket::response::Flash;
|
2019-02-26 16:35:57 +00:00
|
|
|
use rocket::request::{Form, FlashMessage};
|
2019-02-22 22:29:54 +00:00
|
|
|
use rocket::response::Redirect;
|
2019-01-10 13:45:11 +00:00
|
|
|
|
2019-02-22 15:25:06 +00:00
|
|
|
use serde::Serialize;
|
2019-01-10 13:45:11 +00:00
|
|
|
use handlebars::Handlebars;
|
2019-02-22 15:25:06 +00:00
|
|
|
|
2018-10-18 14:26:25 +00:00
|
|
|
use std::path::{Path, PathBuf};
|
2018-08-16 18:35:19 +00:00
|
|
|
|
|
|
|
mod upload;
|
|
|
|
|
2019-03-01 08:55:50 +00:00
|
|
|
use database::{Database, Polymorphic, Query};
|
2019-02-22 20:29:53 +00:00
|
|
|
use Result;
|
2019-02-07 19:58:31 +00:00
|
|
|
use types::{Email, Fingerprint, KeyID};
|
2018-09-19 20:24:38 +00:00
|
|
|
use Opt;
|
|
|
|
|
|
|
|
use std::result;
|
2019-02-07 19:58:31 +00:00
|
|
|
use std::str::FromStr;
|
2018-09-19 20:24:38 +00:00
|
|
|
|
|
|
|
mod queries {
|
2019-02-22 15:25:06 +00:00
|
|
|
use std::fmt;
|
2019-02-26 09:12:00 +00:00
|
|
|
use types::{Email, Fingerprint, KeyID};
|
2018-09-19 20:24:38 +00:00
|
|
|
|
2018-10-24 17:45:11 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum Hkp {
|
2019-02-26 10:19:54 +00:00
|
|
|
Fingerprint { fpr: Fingerprint, index: bool, machine_readable: bool },
|
|
|
|
KeyID { keyid: KeyID, index: bool, machine_readable: bool },
|
2019-02-07 19:58:31 +00:00
|
|
|
Email { email: Email, index: bool },
|
2019-02-22 15:25:06 +00:00
|
|
|
Invalid{ query: String, },
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for Hkp {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
Hkp::Fingerprint{ ref fpr,.. } => write!(f, "{}", fpr.to_string()),
|
2019-02-26 09:12:00 +00:00
|
|
|
Hkp::KeyID{ ref keyid,.. } => write!(f, "{}", keyid.to_string()),
|
2019-02-22 15:25:06 +00:00
|
|
|
Hkp::Email{ ref email,.. } => write!(f, "{}", email.to_string()),
|
|
|
|
Hkp::Invalid{ ref query } => write!(f, "{}", query),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-26 13:24:29 +00:00
|
|
|
use rocket::http::hyper::header::ContentDisposition;
|
|
|
|
|
2019-02-22 15:25:06 +00:00
|
|
|
#[derive(Responder)]
|
|
|
|
enum MyResponse {
|
|
|
|
#[response(status = 200, content_type = "html")]
|
|
|
|
Success(Template),
|
|
|
|
#[response(status = 200, content_type = "plain")]
|
|
|
|
Plain(String),
|
2019-02-26 13:24:29 +00:00
|
|
|
#[response(status = 200, content_type = "application/pgp-keys")]
|
|
|
|
Key(String, ContentDisposition),
|
2019-03-01 11:58:17 +00:00
|
|
|
#[response(status = 200, content_type = "application/pgp-keys")]
|
|
|
|
XAccelRedirect(&'static str, Header<'static>, ContentDisposition),
|
2019-02-22 15:25:06 +00:00
|
|
|
#[response(status = 500, content_type = "html")]
|
|
|
|
ServerError(Template),
|
2019-02-22 22:29:54 +00:00
|
|
|
NotFound(Flash<Redirect>),
|
2019-02-22 15:25:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl MyResponse {
|
|
|
|
pub fn ok<S: Serialize>(tmpl: &'static str, ctx: S) -> Self {
|
|
|
|
MyResponse::Success(Template::render(tmpl, ctx))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn plain(s: String) -> Self {
|
|
|
|
MyResponse::Plain(s)
|
|
|
|
}
|
|
|
|
|
2019-03-01 08:55:50 +00:00
|
|
|
pub fn key(armored_key: String, fp: &Fingerprint) -> Self {
|
2019-02-26 13:24:29 +00:00
|
|
|
use rocket::http::hyper::header::{ContentDisposition, DispositionType,
|
|
|
|
DispositionParam, Charset};
|
|
|
|
MyResponse::Key(
|
|
|
|
armored_key,
|
|
|
|
ContentDisposition {
|
|
|
|
disposition: DispositionType::Attachment,
|
|
|
|
parameters: vec![
|
|
|
|
DispositionParam::Filename(
|
|
|
|
Charset::Us_Ascii, None,
|
2019-03-01 08:55:50 +00:00
|
|
|
(fp.to_string() + ".asc").into_bytes()),
|
2019-02-26 13:24:29 +00:00
|
|
|
],
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-03-01 11:58:17 +00:00
|
|
|
pub fn x_accel_redirect(path: PathBuf, fp: &Fingerprint) -> Self {
|
|
|
|
use rocket::http::hyper::header::{ContentDisposition, DispositionType,
|
|
|
|
DispositionParam, Charset};
|
|
|
|
// The path is relative to our base directory, but we need to
|
|
|
|
// get it relative to base/public.
|
|
|
|
let mut path = path.into_os_string().into_string().expect("valid UTF8");
|
|
|
|
// Drop the first component.
|
|
|
|
assert!(path.starts_with("public/"));
|
|
|
|
path.drain(..6);
|
|
|
|
|
|
|
|
MyResponse::XAccelRedirect(
|
|
|
|
"",
|
|
|
|
Header::new("X-Accel-Redirect", path),
|
|
|
|
ContentDisposition {
|
|
|
|
disposition: DispositionType::Attachment,
|
|
|
|
parameters: vec![
|
|
|
|
DispositionParam::Filename(
|
|
|
|
Charset::Us_Ascii, None,
|
|
|
|
(fp.to_string() + ".asc").into_bytes()),
|
|
|
|
],
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-02-22 20:29:53 +00:00
|
|
|
pub fn ise(e: failure::Error) -> Self {
|
2019-02-22 15:25:06 +00:00
|
|
|
let ctx = templates::FiveHundred{
|
2019-02-22 20:29:53 +00:00
|
|
|
error: format!("{}", e),
|
2019-02-22 15:25:06 +00:00
|
|
|
version: env!("VERGEN_SEMVER").to_string(),
|
|
|
|
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
|
|
|
};
|
|
|
|
MyResponse::ServerError(Template::render("500", ctx))
|
|
|
|
}
|
|
|
|
|
2019-02-26 16:34:48 +00:00
|
|
|
pub fn not_found<T, M>(redirect_to: T, message: M)
|
|
|
|
-> Self
|
|
|
|
where T: Into<Option<&'static str>>,
|
|
|
|
M: Into<Option<String>>,
|
|
|
|
{
|
|
|
|
MyResponse::NotFound(Flash::error(
|
|
|
|
Redirect::to(redirect_to.into().unwrap_or("/?")),
|
|
|
|
message.into().unwrap_or_else(|| "Key not found".to_owned())))
|
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,
|
2019-02-22 20:34:48 +00:00
|
|
|
pub domain: String,
|
2019-02-08 18:51:06 +00:00
|
|
|
pub commit: String,
|
|
|
|
pub version: String,
|
2018-09-19 20:24:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize)]
|
|
|
|
pub struct Delete {
|
|
|
|
pub token: String,
|
|
|
|
pub fpr: String,
|
2019-02-08 18:51:06 +00:00
|
|
|
pub commit: String,
|
|
|
|
pub version: String,
|
2018-09-19 20:24:38 +00:00
|
|
|
}
|
|
|
|
|
2019-02-22 15:25:06 +00:00
|
|
|
#[derive(Serialize)]
|
|
|
|
pub struct Search {
|
|
|
|
pub query: String,
|
|
|
|
pub fpr: Option<String>,
|
2019-02-22 19:52:40 +00:00
|
|
|
pub domain: Option<String>,
|
2019-02-22 15:25:06 +00:00
|
|
|
pub commit: String,
|
|
|
|
pub version: String,
|
|
|
|
}
|
|
|
|
|
2018-09-19 20:24:38 +00:00
|
|
|
#[derive(Serialize)]
|
|
|
|
pub struct Confirm {
|
|
|
|
pub deleted: bool,
|
2019-02-08 18:51:06 +00:00
|
|
|
pub commit: String,
|
|
|
|
pub version: String,
|
2018-09-19 20:24:38 +00:00
|
|
|
}
|
2019-02-08 19:09:53 +00:00
|
|
|
|
|
|
|
#[derive(Serialize)]
|
2019-02-22 15:25:06 +00:00
|
|
|
pub struct FiveHundred {
|
|
|
|
pub error: String,
|
|
|
|
pub commit: String,
|
|
|
|
pub version: String,
|
|
|
|
}
|
|
|
|
|
2019-02-22 22:29:54 +00:00
|
|
|
#[derive(Serialize)]
|
|
|
|
pub struct Index {
|
|
|
|
pub error: Option<String>,
|
|
|
|
pub commit: String,
|
|
|
|
pub version: String,
|
|
|
|
}
|
2019-02-22 15:25:06 +00:00
|
|
|
#[derive(Serialize)]
|
|
|
|
pub struct General {
|
2019-02-08 19:09:53 +00:00
|
|
|
pub commit: String,
|
|
|
|
pub version: String,
|
|
|
|
}
|
2018-08-16 18:35:19 +00:00
|
|
|
}
|
|
|
|
|
2018-10-18 14:26:25 +00:00
|
|
|
struct StaticDir(String);
|
2018-11-02 10:50:57 +00:00
|
|
|
pub struct Domain(String);
|
2019-01-09 12:35:47 +00:00
|
|
|
pub struct From(String);
|
2019-01-10 13:45:11 +00:00
|
|
|
pub struct MailTemplates(Handlebars);
|
2019-03-01 11:58:17 +00:00
|
|
|
pub struct XAccelRedirect(bool);
|
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 = ();
|
|
|
|
|
2019-02-07 19:58:31 +00:00
|
|
|
fn from_request(
|
|
|
|
request: &'a Request<'r>,
|
|
|
|
) -> request::Outcome<queries::Hkp, ()> {
|
2018-10-24 17:44:36 +00:00
|
|
|
use rocket::request::FormItems;
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
let query = request.uri().query().unwrap_or("");
|
2019-02-07 19:58:31 +00:00
|
|
|
let fields = FormItems::from(query)
|
|
|
|
.map(|item| {
|
|
|
|
let (k, v) = item.key_value();
|
|
|
|
|
|
|
|
let key = k.url_decode().unwrap_or_default();
|
|
|
|
let value = v.url_decode().unwrap_or_default();
|
|
|
|
(key, value)
|
|
|
|
})
|
|
|
|
.collect::<HashMap<_, _>>();
|
|
|
|
|
|
|
|
if fields.len() >= 2
|
|
|
|
&& fields
|
|
|
|
.get("op")
|
|
|
|
.map(|x| x == "get" || x == "index")
|
|
|
|
.unwrap_or(false)
|
|
|
|
{
|
|
|
|
let index = fields.get("op").map(|x| x == "index").unwrap_or(false);
|
2019-02-26 10:19:54 +00:00
|
|
|
let machine_readable =
|
|
|
|
fields.get("options").map(|x| x.contains("mr"))
|
|
|
|
.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);
|
2019-02-26 09:12:00 +00:00
|
|
|
let maybe_keyid = KeyID::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 {
|
2019-02-07 19:58:31 +00:00
|
|
|
Outcome::Success(queries::Hkp::Fingerprint {
|
|
|
|
fpr: fpr,
|
|
|
|
index: index,
|
2019-02-26 10:19:54 +00:00
|
|
|
machine_readable: machine_readable,
|
2019-01-08 18:01:45 +00:00
|
|
|
})
|
2019-02-26 09:12:00 +00:00
|
|
|
} else if let Ok(keyid) = maybe_keyid {
|
|
|
|
Outcome::Success(queries::Hkp::KeyID {
|
|
|
|
keyid: keyid,
|
|
|
|
index: index,
|
2019-02-26 10:19:54 +00:00
|
|
|
machine_readable: machine_readable,
|
2019-02-26 09:12:00 +00:00
|
|
|
})
|
2018-10-24 17:44:36 +00:00
|
|
|
} else {
|
|
|
|
match Email::from_str(&search) {
|
2019-02-07 19:58:31 +00:00
|
|
|
Ok(email) => {
|
|
|
|
Outcome::Success(queries::Hkp::Email {
|
|
|
|
email: email,
|
|
|
|
index: index,
|
|
|
|
})
|
|
|
|
}
|
2019-02-22 15:25:06 +00:00
|
|
|
Err(_) => {
|
|
|
|
Outcome::Success(queries::Hkp::Invalid{
|
|
|
|
query: search
|
|
|
|
})
|
|
|
|
}
|
2018-08-16 18:35:19 +00:00
|
|
|
}
|
2018-09-19 20:24:38 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Outcome::Failure((Status::BadRequest, ()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-01 08:55:50 +00:00
|
|
|
fn key_to_response<'a>(db: rocket::State<Polymorphic>,
|
|
|
|
query_string: String, domain: String,
|
|
|
|
query: Query,
|
2019-03-01 11:58:17 +00:00
|
|
|
machine_readable: bool,
|
|
|
|
x_accel_redirect: rocket::State<XAccelRedirect>)
|
|
|
|
-> MyResponse {
|
2019-03-01 08:55:50 +00:00
|
|
|
let fp = if let Some(fp) = db.lookup_primary_fingerprint(&query) {
|
|
|
|
fp
|
|
|
|
} else {
|
|
|
|
return MyResponse::not_found(None, None);
|
2019-02-22 15:25:06 +00:00
|
|
|
};
|
2019-02-26 13:24:29 +00:00
|
|
|
|
|
|
|
if machine_readable {
|
2019-03-01 11:58:17 +00:00
|
|
|
if x_accel_redirect.0 {
|
|
|
|
if let Some(path) = db.lookup_path(&query) {
|
|
|
|
return MyResponse::x_accel_redirect(path, &fp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-01 08:55:50 +00:00
|
|
|
return match db.by_fpr(&fp) {
|
|
|
|
Some(armored) => MyResponse::key(armored, &fp.into()),
|
|
|
|
None => MyResponse::not_found(None, None),
|
|
|
|
}
|
2019-02-26 13:24:29 +00:00
|
|
|
}
|
|
|
|
|
2019-02-22 15:25:06 +00:00
|
|
|
let context = templates::Search{
|
2019-03-01 08:55:50 +00:00
|
|
|
query: query_string,
|
2019-02-22 19:52:40 +00:00
|
|
|
domain: Some(domain),
|
2019-03-01 08:55:50 +00:00
|
|
|
fpr: fp.to_string().into(),
|
2019-02-22 15:25:06 +00:00
|
|
|
version: env!("VERGEN_SEMVER").to_string(),
|
|
|
|
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
|
|
|
};
|
2018-10-25 15:37:33 +00:00
|
|
|
|
2019-02-22 15:25:06 +00:00
|
|
|
MyResponse::ok("found", context)
|
|
|
|
}
|
|
|
|
|
2019-03-01 08:55:50 +00:00
|
|
|
fn key_to_hkp_index<'a>(db: rocket::State<Polymorphic>, query: Query)
|
|
|
|
-> MyResponse {
|
2019-02-22 15:25:06 +00:00
|
|
|
use sequoia_openpgp::RevocationStatus;
|
|
|
|
|
2019-03-01 08:55:50 +00:00
|
|
|
let tpk = match db.lookup(&query) {
|
|
|
|
Ok(Some(tpk)) => tpk,
|
|
|
|
Ok(None) => return MyResponse::not_found(None, None), // XXX: This should return 404.
|
2019-02-22 20:29:53 +00:00
|
|
|
Err(err) => { return MyResponse::ise(err); }
|
2019-02-22 15:25:06 +00:00
|
|
|
};
|
|
|
|
let mut out = String::default();
|
|
|
|
let p = tpk.primary();
|
|
|
|
|
|
|
|
let ctime = tpk
|
|
|
|
.primary_key_signature()
|
|
|
|
.and_then(|x| x.signature_creation_time())
|
|
|
|
.map(|x| format!("{}", x.to_timespec().sec))
|
|
|
|
.unwrap_or_default();
|
|
|
|
let extime = tpk
|
|
|
|
.primary_key_signature()
|
|
|
|
.and_then(|x| x.signature_expiration_time())
|
|
|
|
.map(|x| format!("{}", x))
|
|
|
|
.unwrap_or_default();
|
|
|
|
let is_exp = tpk
|
|
|
|
.primary_key_signature()
|
|
|
|
.and_then(|x| {
|
|
|
|
if x.signature_expired() { "e" } else { "" }.into()
|
|
|
|
})
|
|
|
|
.unwrap_or_default();
|
|
|
|
let is_rev =
|
|
|
|
if tpk.revoked(None) != RevocationStatus::NotAsFarAsWeKnow {
|
|
|
|
"r"
|
|
|
|
} else {
|
|
|
|
""
|
|
|
|
};
|
|
|
|
let algo: u8 = p.pk_algo().into();
|
|
|
|
|
|
|
|
out.push_str("info:1:1\r\n");
|
|
|
|
out.push_str(&format!(
|
|
|
|
"pub:{}:{}:{}:{}:{}:{}{}\r\n",
|
|
|
|
p.fingerprint().to_string().replace(" ", ""),
|
|
|
|
algo,
|
|
|
|
p.mpis().bits(),
|
|
|
|
ctime,
|
|
|
|
extime,
|
|
|
|
is_exp,
|
|
|
|
is_rev
|
|
|
|
));
|
|
|
|
|
|
|
|
for uid in tpk.userids() {
|
|
|
|
let u =
|
|
|
|
url::form_urlencoded::byte_serialize(uid.userid().userid())
|
|
|
|
.fold(String::default(), |acc, x| acc + x);
|
|
|
|
let ctime = uid
|
|
|
|
.binding_signature()
|
|
|
|
.and_then(|x| x.signature_creation_time())
|
|
|
|
.map(|x| format!("{}", x.to_timespec().sec))
|
|
|
|
.unwrap_or_default();
|
|
|
|
let extime = uid
|
|
|
|
.binding_signature()
|
|
|
|
.and_then(|x| x.signature_expiration_time())
|
|
|
|
.map(|x| format!("{}", x))
|
|
|
|
.unwrap_or_default();
|
|
|
|
let is_exp = uid
|
|
|
|
.binding_signature()
|
|
|
|
.and_then(|x| {
|
|
|
|
if x.signature_expired() { "e" } else { "" }.into()
|
|
|
|
})
|
|
|
|
.unwrap_or_default();
|
|
|
|
let is_rev = if uid.revoked(None)
|
|
|
|
!= RevocationStatus::NotAsFarAsWeKnow
|
|
|
|
{
|
|
|
|
"r"
|
|
|
|
} else {
|
|
|
|
""
|
|
|
|
};
|
|
|
|
|
|
|
|
out.push_str(&format!(
|
|
|
|
"uid:{}:{}:{}:{}{}\r\n",
|
|
|
|
u, ctime, extime, is_exp, is_rev
|
|
|
|
));
|
2018-10-25 15:37:33 +00:00
|
|
|
}
|
2019-02-22 15:25:06 +00:00
|
|
|
|
|
|
|
MyResponse::plain(out)
|
2019-02-22 21:37:01 +00:00
|
|
|
|
2018-10-25 15:37:33 +00:00
|
|
|
}
|
|
|
|
|
2019-02-28 15:57:03 +00:00
|
|
|
#[get("/vks/by-fingerprint/<fpr>")]
|
2019-03-01 11:58:17 +00:00
|
|
|
fn by_fingerprint(db: rocket::State<Polymorphic>, domain: rocket::State<Domain>,
|
|
|
|
x_accel_redirect: rocket::State<XAccelRedirect>,
|
|
|
|
fpr: String) -> MyResponse {
|
2019-03-01 08:55:50 +00:00
|
|
|
let query = match Fingerprint::from_str(&fpr) {
|
|
|
|
Ok(fpr) => Query::ByFingerprint(fpr),
|
2019-02-28 17:39:29 +00:00
|
|
|
Err(e) => return MyResponse::ise(e),
|
2018-09-19 20:24:38 +00:00
|
|
|
};
|
|
|
|
|
2019-03-01 11:58:17 +00:00
|
|
|
key_to_response(db, fpr, domain.0.clone(), query, true, x_accel_redirect)
|
2018-10-25 15:37:33 +00:00
|
|
|
}
|
2018-09-19 20:24:38 +00:00
|
|
|
|
2019-02-28 15:57:03 +00:00
|
|
|
#[get("/vks/by-email/<email>")]
|
2019-03-01 11:58:17 +00:00
|
|
|
fn by_email(db: rocket::State<Polymorphic>, domain: rocket::State<Domain>,
|
|
|
|
x_accel_redirect: rocket::State<XAccelRedirect>,
|
|
|
|
email: String) -> MyResponse {
|
2019-03-01 08:55:50 +00:00
|
|
|
let query = match Email::from_str(&email) {
|
|
|
|
Ok(email) => Query::ByEmail(email),
|
2019-02-28 17:39:29 +00:00
|
|
|
Err(e) => return MyResponse::ise(e),
|
2018-10-25 15:37:33 +00:00
|
|
|
};
|
2018-09-19 20:24:38 +00:00
|
|
|
|
2019-03-01 11:58:17 +00:00
|
|
|
key_to_response(db, email, domain.0.clone(), query, true, x_accel_redirect)
|
2018-09-19 20:24:38 +00:00
|
|
|
}
|
|
|
|
|
2019-02-28 15:57:03 +00:00
|
|
|
#[get("/vks/by-keyid/<kid>")]
|
2019-03-01 11:58:17 +00:00
|
|
|
fn by_keyid(db: rocket::State<Polymorphic>, domain: rocket::State<Domain>,
|
|
|
|
x_accel_redirect: rocket::State<XAccelRedirect>,
|
|
|
|
kid: String) -> MyResponse {
|
2019-03-01 08:55:50 +00:00
|
|
|
let query = match KeyID::from_str(&kid) {
|
|
|
|
Ok(keyid) => Query::ByKeyID(keyid),
|
2019-02-28 17:39:29 +00:00
|
|
|
Err(e) => return MyResponse::ise(e),
|
2019-01-04 13:07:14 +00:00
|
|
|
};
|
|
|
|
|
2019-03-01 11:58:17 +00:00
|
|
|
key_to_response(db, kid, domain.0.clone(), query, true, x_accel_redirect)
|
2019-01-04 13:07:14 +00:00
|
|
|
}
|
|
|
|
|
2018-11-02 10:48:02 +00:00
|
|
|
#[get("/vks/verify/<token>")]
|
2019-02-07 19:58:31 +00:00
|
|
|
fn verify(
|
2019-02-22 20:34:48 +00:00
|
|
|
db: rocket::State<Polymorphic>, domain: rocket::State<Domain>, token: String,
|
2019-02-07 19:58:31 +00:00
|
|
|
) -> result::Result<Template, Custom<String>> {
|
2018-09-19 20:24:38 +00:00
|
|
|
match db.verify_token(&token) {
|
|
|
|
Ok(Some((userid, fpr))) => {
|
2019-02-07 19:58:31 +00:00
|
|
|
let context = templates::Verify {
|
2018-09-19 20:24:38 +00:00
|
|
|
verified: true,
|
2019-02-22 20:34:48 +00:00
|
|
|
domain: domain.0.clone(),
|
2018-09-19 20:24:38 +00:00
|
|
|
userid: userid.to_string(),
|
|
|
|
fpr: fpr.to_string(),
|
2019-02-08 18:51:06 +00:00
|
|
|
version: env!("VERGEN_SEMVER").to_string(),
|
|
|
|
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
2018-09-19 20:24:38 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(Template::render("verify", context))
|
|
|
|
}
|
|
|
|
Ok(None) | Err(_) => {
|
2019-02-07 19:58:31 +00:00
|
|
|
let context = templates::Verify {
|
2018-09-19 20:24:38 +00:00
|
|
|
verified: false,
|
2019-02-22 20:34:48 +00:00
|
|
|
domain: domain.0.clone(),
|
2018-09-19 20:24:38 +00:00
|
|
|
userid: "".into(),
|
|
|
|
fpr: "".into(),
|
2019-02-08 18:51:06 +00:00
|
|
|
version: env!("VERGEN_SEMVER").to_string(),
|
|
|
|
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
2018-09-19 20:24:38 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(Template::render("verify", context))
|
2018-08-16 18:35:19 +00:00
|
|
|
}
|
2018-09-19 20:24:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-26 16:35:57 +00:00
|
|
|
#[get("/vks/manage")]
|
|
|
|
fn manage(flash: Option<FlashMessage>)
|
|
|
|
-> result::Result<Template, Custom<String>> {
|
|
|
|
let context = templates::Index {
|
|
|
|
error: flash.and_then(|flash| error_from_flash(&flash)),
|
|
|
|
version: env!("VERGEN_SEMVER").to_string(),
|
|
|
|
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(Template::render("manage", context))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(FromForm)]
|
|
|
|
struct ManageRequest {
|
|
|
|
search_term: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/vks/manage", data="<request>")]
|
|
|
|
fn manage_post(
|
|
|
|
db: State<Polymorphic>, tmpl: State<MailTemplates>, domain: State<Domain>,
|
|
|
|
from: State<From>, request: Form<ManageRequest>,
|
|
|
|
) -> MyResponse {
|
|
|
|
use std::convert::TryInto;
|
2018-11-02 10:50:57 +00:00
|
|
|
use mail::send_confirmation_mail;
|
|
|
|
|
2019-02-28 16:16:26 +00:00
|
|
|
let query = match request.search_term.parse() {
|
|
|
|
Ok(query) => query,
|
|
|
|
Err(e) => return MyResponse::ise(e),
|
|
|
|
};
|
|
|
|
let tpk = match db.lookup(&query) {
|
2019-02-26 16:35:57 +00:00
|
|
|
Ok(Some(tpk)) => tpk,
|
|
|
|
Ok(None) => return MyResponse::not_found(
|
|
|
|
Some("/vks/manage"),
|
|
|
|
Some(format!("No such key found for {:?}", request.search_term))),
|
|
|
|
Err(e) => return MyResponse::ise(e),
|
2018-09-19 20:24:38 +00:00
|
|
|
};
|
|
|
|
|
2019-02-26 16:35:57 +00:00
|
|
|
match db.request_deletion(tpk.fingerprint().try_into().unwrap()) {
|
2019-02-07 19:58:31 +00:00
|
|
|
Ok((token, uids)) => {
|
|
|
|
let context = templates::Delete {
|
2019-02-26 16:35:57 +00:00
|
|
|
fpr: tpk.fingerprint().to_string(),
|
2018-11-02 10:53:49 +00:00
|
|
|
token: token.clone(),
|
2019-02-08 18:51:06 +00:00
|
|
|
version: env!("VERGEN_SEMVER").to_string(),
|
|
|
|
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
2018-09-19 20:24:38 +00:00
|
|
|
};
|
|
|
|
|
2018-11-02 10:53:49 +00:00
|
|
|
for uid in uids {
|
2019-02-26 16:35:57 +00:00
|
|
|
if let Err(e) = send_confirmation_mail(
|
|
|
|
&uid, &token, &tmpl.0, &domain.0, &from.0) {
|
|
|
|
return MyResponse::ise(e);
|
|
|
|
}
|
2018-11-02 10:53:49 +00:00
|
|
|
}
|
|
|
|
|
2019-02-26 16:35:57 +00:00
|
|
|
MyResponse::ok("delete", context)
|
2018-09-19 20:24:38 +00:00
|
|
|
}
|
2019-02-26 16:35:57 +00:00
|
|
|
Err(e) => MyResponse::ise(e),
|
2018-09-19 20:24:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-02 10:48:02 +00:00
|
|
|
#[get("/vks/confirm/<token>")]
|
2019-02-07 19:58:31 +00:00
|
|
|
fn confirm(
|
|
|
|
db: rocket::State<Polymorphic>, token: String,
|
|
|
|
) -> result::Result<Template, Custom<String>> {
|
2018-09-19 20:24:38 +00:00
|
|
|
match db.confirm_deletion(&token) {
|
|
|
|
Ok(true) => {
|
2019-02-08 18:51:06 +00:00
|
|
|
let context = templates::Confirm {
|
|
|
|
deleted: true,
|
|
|
|
version: env!("VERGEN_SEMVER").to_string(),
|
|
|
|
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
|
|
|
};
|
2018-09-19 20:24:38 +00:00
|
|
|
|
|
|
|
Ok(Template::render("confirm", context))
|
|
|
|
}
|
|
|
|
Ok(false) | Err(_) => {
|
2019-02-08 18:51:06 +00:00
|
|
|
let context = templates::Confirm {
|
|
|
|
deleted: false,
|
|
|
|
version: env!("VERGEN_SEMVER").to_string(),
|
|
|
|
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
|
|
|
};
|
2018-09-19 20:24:38 +00:00
|
|
|
|
|
|
|
Ok(Template::render("confirm", context))
|
|
|
|
}
|
|
|
|
}
|
2018-08-16 18:35:19 +00:00
|
|
|
}
|
|
|
|
|
2018-11-02 10:48:02 +00:00
|
|
|
#[get("/assets/<file..>")]
|
2018-10-18 14:26:25 +00:00
|
|
|
fn files(file: PathBuf, static_dir: State<StaticDir>) -> Option<NamedFile> {
|
2018-11-02 10:48:02 +00:00
|
|
|
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")]
|
2019-03-01 11:58:17 +00:00
|
|
|
fn lookup(db: rocket::State<Polymorphic>, domain: rocket::State<Domain>,
|
|
|
|
x_accel_redirect: rocket::State<XAccelRedirect>,
|
|
|
|
key: Option<queries::Hkp>) -> MyResponse {
|
2019-03-01 08:55:50 +00:00
|
|
|
let query_string = key.as_ref().map(|k| format!("{}", k));
|
|
|
|
let (query, index, machine_readable) = match key {
|
|
|
|
Some(queries::Hkp::Fingerprint { fpr, index, machine_readable }) =>
|
|
|
|
(Query::ByFingerprint(fpr), index, machine_readable),
|
|
|
|
Some(queries::Hkp::KeyID { keyid, index, machine_readable }) =>
|
|
|
|
(Query::ByKeyID(keyid), index, machine_readable),
|
|
|
|
Some(queries::Hkp::Email { email, index }) => {
|
2019-02-26 10:19:54 +00:00
|
|
|
// XXX: Maybe return 501 Not Implemented if machine_readable
|
2019-03-01 08:55:50 +00:00
|
|
|
(Query::ByEmail(email), index, false)
|
2019-01-08 18:01:45 +00:00
|
|
|
}
|
2019-02-22 22:49:36 +00:00
|
|
|
Some(queries::Hkp::Invalid { query: _ }) => {
|
2019-02-26 16:35:57 +00:00
|
|
|
return MyResponse::not_found(None, None);
|
2019-02-22 15:25:06 +00:00
|
|
|
}
|
2019-02-07 19:58:31 +00:00
|
|
|
None => {
|
2019-02-26 16:35:57 +00:00
|
|
|
return MyResponse::not_found(None, None);
|
2019-01-08 18:01:45 +00:00
|
|
|
}
|
2018-10-24 17:44:36 +00:00
|
|
|
};
|
|
|
|
|
2019-03-01 08:55:50 +00:00
|
|
|
if index {
|
|
|
|
key_to_hkp_index(db, query)
|
|
|
|
} else {
|
|
|
|
key_to_response(db,
|
|
|
|
query_string.expect("key was Some if we made it here"),
|
2019-03-01 11:58:17 +00:00
|
|
|
domain.0.clone(), query, machine_readable,
|
|
|
|
x_accel_redirect)
|
2018-10-24 17:44:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-22 22:29:54 +00:00
|
|
|
fn error_from_flash(flash: &FlashMessage) -> Option<String> {
|
|
|
|
if flash.name() == "error" {
|
|
|
|
Some(flash.msg().to_owned())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-16 18:35:19 +00:00
|
|
|
#[get("/")]
|
2019-02-22 22:29:54 +00:00
|
|
|
fn root(
|
|
|
|
flash: Option<FlashMessage>
|
|
|
|
) -> Template {
|
|
|
|
let context = templates::Index {
|
|
|
|
error: flash.and_then(|flash| error_from_flash(&flash)),
|
2019-02-08 19:09:53 +00:00
|
|
|
version: env!("VERGEN_SEMVER").to_string(),
|
|
|
|
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
|
|
|
};
|
2018-09-19 20:24:38 +00:00
|
|
|
|
2019-02-08 19:09:53 +00:00
|
|
|
Template::render("index", context)
|
2018-08-16 18:35:19 +00:00
|
|
|
}
|
|
|
|
|
2019-02-22 20:15:06 +00:00
|
|
|
#[get("/about")]
|
|
|
|
fn about() -> Template {
|
2019-02-22 20:16:14 +00:00
|
|
|
let context = templates::General {
|
2019-02-22 20:15:06 +00:00
|
|
|
version: env!("VERGEN_SEMVER").to_string(),
|
|
|
|
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
|
|
|
};
|
|
|
|
|
|
|
|
Template::render("about", context)
|
|
|
|
}
|
|
|
|
|
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 {
|
2019-02-07 19:58:31 +00:00
|
|
|
u16::from_str(&opt.listen[p + 1..]).ok().unwrap_or(8080)
|
2018-09-19 20:24:38 +00:00
|
|
|
} else {
|
|
|
|
8080
|
|
|
|
};
|
|
|
|
|
|
|
|
(addr, port)
|
|
|
|
}
|
2019-02-07 19:58:31 +00:00
|
|
|
None => (opt.listen.to_string(), 8080),
|
2018-09-19 20:24:38 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let config = Config::build(Environment::Staging)
|
|
|
|
.address(addr)
|
|
|
|
.port(port)
|
|
|
|
.workers(2)
|
2018-11-02 10:48:02 +00:00
|
|
|
.root(opt.base.clone())
|
2019-02-07 19:58:31 +00:00
|
|
|
.extra(
|
|
|
|
"template_dir",
|
|
|
|
opt.base
|
|
|
|
.join("templates")
|
|
|
|
.to_str()
|
2019-02-22 20:29:53 +00:00
|
|
|
.ok_or(failure::err_msg("Template path invalid"))?,
|
2019-02-07 19:58:31 +00:00
|
|
|
)
|
|
|
|
.extra(
|
|
|
|
"static_dir",
|
2019-02-22 20:29:53 +00:00
|
|
|
opt.base.join("public").to_str()
|
|
|
|
.ok_or(failure::err_msg("Static path invalid"))?,
|
2019-02-07 19:58:31 +00:00
|
|
|
)
|
2018-11-02 10:50:57 +00:00
|
|
|
.extra("domain", opt.domain.clone())
|
2019-01-09 12:35:47 +00:00
|
|
|
.extra("from", opt.from.clone())
|
2019-03-01 11:58:17 +00:00
|
|
|
.extra("x-accel-redirect", opt.x_accel_redirect)
|
2018-09-19 20:24:38 +00:00
|
|
|
.finalize()?;
|
2019-02-25 17:11:43 +00:00
|
|
|
|
|
|
|
rocket_factory(rocket::custom(config), db).launch();
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn rocket_factory(rocket: rocket::Rocket, db: Polymorphic) -> rocket::Rocket {
|
2018-09-19 20:24:38 +00:00
|
|
|
let routes = routes![
|
2018-11-02 10:48:02 +00:00
|
|
|
// infra
|
|
|
|
root,
|
2019-02-22 15:25:06 +00:00
|
|
|
manage,
|
2019-02-26 16:35:57 +00:00
|
|
|
manage_post,
|
2018-11-02 10:48:02 +00:00
|
|
|
files,
|
|
|
|
// nginx-supported lookup
|
2018-10-25 15:37:33 +00:00
|
|
|
by_email,
|
2019-02-21 20:26:02 +00:00
|
|
|
by_fingerprint,
|
|
|
|
by_keyid,
|
2018-11-02 10:48:02 +00:00
|
|
|
// HKP
|
|
|
|
lookup,
|
2019-02-22 20:38:38 +00:00
|
|
|
upload::vks_publish,
|
|
|
|
upload::vks_publish_submit,
|
2018-11-02 10:48:02 +00:00
|
|
|
// verification & deletion
|
2018-09-19 20:24:38 +00:00
|
|
|
verify,
|
|
|
|
confirm,
|
2019-02-22 20:15:06 +00:00
|
|
|
// about
|
|
|
|
about,
|
2018-09-19 20:24:38 +00:00
|
|
|
];
|
|
|
|
|
2019-02-25 17:11:43 +00:00
|
|
|
rocket
|
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| {
|
2019-02-07 19:58:31 +00:00
|
|
|
let static_dir =
|
|
|
|
rocket.config().get_str("static_dir").unwrap().to_string();
|
2018-10-18 14:26:25 +00:00
|
|
|
|
|
|
|
Ok(rocket.manage(StaticDir(static_dir)))
|
|
|
|
}))
|
2018-12-25 19:06:28 +00:00
|
|
|
.attach(AdHoc::on_attach("domain", |rocket| {
|
2019-02-07 19:58:31 +00:00
|
|
|
let domain = rocket.config().get_str("domain").unwrap().to_string();
|
2018-11-02 10:50:57 +00:00
|
|
|
|
2019-01-09 12:35:47 +00:00
|
|
|
Ok(rocket.manage(Domain(domain)))
|
|
|
|
}))
|
|
|
|
.attach(AdHoc::on_attach("from", |rocket| {
|
2019-02-07 19:58:31 +00:00
|
|
|
let from = rocket.config().get_str("from").unwrap().to_string();
|
2019-01-09 12:35:47 +00:00
|
|
|
|
|
|
|
Ok(rocket.manage(From(from)))
|
2018-11-02 10:50:57 +00:00
|
|
|
}))
|
2019-03-01 11:58:17 +00:00
|
|
|
.attach(AdHoc::on_attach("x-accel-redirect", |rocket| {
|
|
|
|
let x_accel_redirect =
|
|
|
|
rocket.config().get_bool("x-accel-redirect").unwrap();
|
|
|
|
|
|
|
|
Ok(rocket.manage(XAccelRedirect(x_accel_redirect)))
|
|
|
|
}))
|
2019-01-10 13:45:11 +00:00
|
|
|
.attach(AdHoc::on_attach("mail_templates", |rocket| {
|
2019-02-07 19:58:31 +00:00
|
|
|
let dir: PathBuf = rocket
|
|
|
|
.config()
|
2019-01-10 13:45:11 +00:00
|
|
|
.get_str("template_dir")
|
|
|
|
.unwrap()
|
|
|
|
.to_string()
|
|
|
|
.into();
|
|
|
|
let confirm_html = dir.join("confirm-email-html.hbs");
|
|
|
|
let confirm_txt = dir.join("confirm-email-txt.hbs");
|
|
|
|
let verify_html = dir.join("verify-email-html.hbs");
|
|
|
|
let verify_txt = dir.join("verify-email-txt.hbs");
|
2019-02-07 19:58:31 +00:00
|
|
|
let mut handlebars = Handlebars::new();
|
|
|
|
|
|
|
|
handlebars
|
|
|
|
.register_template_file("confirm-html", confirm_html)
|
|
|
|
.unwrap();
|
|
|
|
handlebars
|
|
|
|
.register_template_file("confirm-txt", confirm_txt)
|
|
|
|
.unwrap();
|
|
|
|
handlebars
|
|
|
|
.register_template_file("verify-html", verify_html)
|
|
|
|
.unwrap();
|
|
|
|
handlebars
|
|
|
|
.register_template_file("verify-txt", verify_txt)
|
|
|
|
.unwrap();
|
2019-01-10 13:45:11 +00:00
|
|
|
|
|
|
|
Ok(rocket.manage(MailTemplates(handlebars)))
|
|
|
|
}))
|
2018-09-19 20:24:38 +00:00
|
|
|
.mount("/", routes)
|
|
|
|
.manage(db)
|
2018-08-16 18:35:19 +00:00
|
|
|
}
|
2019-02-25 17:34:07 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use fs_extra;
|
|
|
|
use tempfile::{tempdir, TempDir};
|
|
|
|
use super::rocket;
|
|
|
|
use rocket::local::Client;
|
|
|
|
use rocket::http::Status;
|
|
|
|
use rocket::http::ContentType;
|
|
|
|
|
2019-02-26 10:34:53 +00:00
|
|
|
use sequoia_openpgp::TPK;
|
|
|
|
use sequoia_openpgp::tpk::TPKBuilder;
|
|
|
|
use sequoia_openpgp::parse::Parse;
|
|
|
|
use sequoia_openpgp::serialize::Serialize;
|
|
|
|
|
2019-02-25 17:34:07 +00:00
|
|
|
use database::*;
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
/// Creates a configuration and empty state dir for testing purposes.
|
|
|
|
///
|
|
|
|
/// Note that you need to keep the returned TempDir alive for the
|
|
|
|
/// duration of your test. To debug the test, mem::forget it to
|
|
|
|
/// prevent cleanup.
|
|
|
|
fn configuration() -> Result<(TempDir, rocket::Config)> {
|
|
|
|
use rocket::config::{Config, Environment};
|
|
|
|
|
|
|
|
let root = tempdir()?;
|
|
|
|
fs_extra::copy_items(&vec!["dist/templates"], &root,
|
|
|
|
&fs_extra::dir::CopyOptions::new())?;
|
|
|
|
|
|
|
|
let config = Config::build(Environment::Staging)
|
|
|
|
.root(root.path().to_path_buf())
|
|
|
|
.extra(
|
|
|
|
"template_dir",
|
|
|
|
root.path().join("templates").to_str()
|
|
|
|
.ok_or(failure::err_msg("Template path invalid"))?,
|
|
|
|
)
|
|
|
|
.extra(
|
|
|
|
"static_dir",
|
|
|
|
root.path().join("public").to_str()
|
|
|
|
.ok_or(failure::err_msg("Static path invalid"))?,
|
|
|
|
)
|
|
|
|
.extra("domain", "domain")
|
|
|
|
.extra("from", "from")
|
2019-03-01 11:58:17 +00:00
|
|
|
.extra("x-accel-redirect", false)
|
2019-02-25 17:34:07 +00:00
|
|
|
.finalize()?;
|
|
|
|
Ok((root, config))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn basics() {
|
|
|
|
let (_tmpdir, config) = configuration().unwrap();
|
|
|
|
|
|
|
|
let db = Polymorphic::Filesystem(
|
|
|
|
Filesystem::new(config.root().unwrap().to_path_buf()).unwrap());
|
|
|
|
let rocket = rocket_factory(rocket::custom(config), db);
|
|
|
|
let client = Client::new(rocket).expect("valid rocket instance");
|
|
|
|
|
|
|
|
// Check that we see the landing page.
|
|
|
|
let mut response = client.get("/").dispatch();
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
assert_eq!(response.content_type(), Some(ContentType::HTML));
|
|
|
|
assert!(response.body_string().unwrap().contains("Hagrid"));
|
|
|
|
|
|
|
|
// Check that we see the privacy policy.
|
|
|
|
let mut response = client.get("/about").dispatch();
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
assert_eq!(response.content_type(), Some(ContentType::HTML));
|
|
|
|
assert!(response.body_string().unwrap().contains("Public Key Data"));
|
|
|
|
}
|
2019-02-26 10:34:53 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn upload() {
|
|
|
|
let (_tmpdir, config) = configuration().unwrap();
|
|
|
|
|
|
|
|
// eprintln!("LEAKING: {:?}", _tmpdir);
|
|
|
|
// ::std::mem::forget(_tmpdir);
|
|
|
|
|
|
|
|
let db = Polymorphic::Filesystem(
|
|
|
|
Filesystem::new(config.root().unwrap().to_path_buf()).unwrap());
|
|
|
|
let rocket = rocket_factory(rocket::custom(config), db);
|
|
|
|
let client = Client::new(rocket).expect("valid rocket instance");
|
|
|
|
|
|
|
|
// Generate a key and upload it.
|
|
|
|
let (tpk, _) = TPKBuilder::autocrypt(
|
|
|
|
None, Some("foo@invalid.example.com".into()))
|
|
|
|
.generate().unwrap();
|
|
|
|
let fp = tpk.fingerprint().to_hex();
|
|
|
|
let keyid = tpk.fingerprint().to_keyid().to_hex();
|
|
|
|
|
|
|
|
let mut tpk_serialized = Vec::new();
|
|
|
|
tpk.serialize(&mut tpk_serialized).unwrap();
|
|
|
|
let response = vks_publish_submit(&client, &tpk_serialized);
|
|
|
|
assert_eq!(response.status(), Status::SeeOther);
|
|
|
|
assert_eq!(response.headers().get_one("Location"),
|
|
|
|
Some("/vks/publish?ok"));
|
|
|
|
|
|
|
|
// And check that we can get it back, modulo user ids.
|
|
|
|
fn check_mr_response(client: &Client, uri: &str, tpk: &TPK) {
|
|
|
|
let mut response = client.get(uri).dispatch();
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
2019-02-26 13:24:29 +00:00
|
|
|
assert_eq!(response.content_type(),
|
|
|
|
Some(ContentType::new("application", "pgp-keys")));
|
2019-02-26 10:34:53 +00:00
|
|
|
let body = response.body_string().unwrap();
|
|
|
|
assert!(body.contains("END PGP PUBLIC KEY BLOCK"));
|
|
|
|
let tpk_ = TPK::from_bytes(body.as_bytes()).unwrap();
|
|
|
|
assert_eq!(tpk.fingerprint(), tpk_.fingerprint());
|
|
|
|
assert_eq!(tpk.subkeys().map(|skb| skb.subkey().fingerprint())
|
|
|
|
.collect::<Vec<_>>(),
|
|
|
|
tpk_.subkeys().map(|skb| skb.subkey().fingerprint())
|
|
|
|
.collect::<Vec<_>>());
|
|
|
|
assert_eq!(tpk_.userids().count(), 0);
|
|
|
|
}
|
|
|
|
|
2019-02-28 15:57:03 +00:00
|
|
|
check_mr_response(&client, &format!("/vks/by-keyid/{}", keyid), &tpk);
|
|
|
|
check_mr_response(&client, &format!("/vks/by-fingerprint/{}", fp), &tpk);
|
2019-02-26 10:34:53 +00:00
|
|
|
check_mr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/pks/lookup?op=get&options=mr&search={}", fp),
|
|
|
|
&tpk);
|
|
|
|
check_mr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/pks/lookup?op=get&options=mr&search=0x{}", fp),
|
|
|
|
&tpk);
|
|
|
|
check_mr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/pks/lookup?op=get&options=mr&search={}", keyid),
|
|
|
|
&tpk);
|
|
|
|
check_mr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/pks/lookup?op=get&options=mr&search=0x{}", keyid),
|
|
|
|
&tpk);
|
|
|
|
|
|
|
|
// And check that we can see the human-readable result page.
|
|
|
|
fn check_hr_response(client: &Client, uri: &str, tpk: &TPK) {
|
|
|
|
let mut response = client.get(uri).dispatch();
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
assert_eq!(response.content_type(), Some(ContentType::HTML));
|
|
|
|
let body = response.body_string().unwrap();
|
|
|
|
assert!(body.contains("found"));
|
|
|
|
assert!(body.contains(&tpk.fingerprint().to_hex()));
|
|
|
|
}
|
|
|
|
|
|
|
|
check_hr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/pks/lookup?op=get&search={}", fp),
|
|
|
|
&tpk);
|
|
|
|
check_hr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/pks/lookup?op=get&search=0x{}", fp),
|
|
|
|
&tpk);
|
|
|
|
check_hr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/pks/lookup?op=get&search={}", keyid),
|
|
|
|
&tpk);
|
|
|
|
check_hr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/pks/lookup?op=get&search=0x{}", keyid),
|
|
|
|
&tpk);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn vks_publish_submit<'a>(client: &'a Client, data: &[u8])
|
|
|
|
-> rocket::local::LocalResponse<'a> {
|
|
|
|
let ct = ContentType::with_params(
|
|
|
|
"multipart", "form-data",
|
|
|
|
("boundary", "---------------------------14733842173518794281682249499"));
|
|
|
|
|
|
|
|
let header =
|
|
|
|
b"-----------------------------14733842173518794281682249499\r\n\
|
|
|
|
Content-Disposition: form-data; name=\"csrf\"\r\n\
|
|
|
|
\r\n\
|
|
|
|
\r\n\
|
|
|
|
-----------------------------14733842173518794281682249499\r\n\
|
|
|
|
Content-Disposition: form-data; name=\"keytext\"; filename=\".k\"\r\n\
|
|
|
|
Content-Type: application/octet-stream\r\n\
|
|
|
|
\r\n";
|
|
|
|
let footer = b"\r\n-----------------------------14733842173518794281682249499--";
|
|
|
|
|
|
|
|
let mut body = Vec::new();
|
|
|
|
body.extend_from_slice(header);
|
|
|
|
body.extend_from_slice(data);
|
|
|
|
body.extend_from_slice(footer);
|
|
|
|
client.post("/vks/publish/submit")
|
|
|
|
.header(ct)
|
|
|
|
.body(&body[..])
|
|
|
|
.dispatch()
|
|
|
|
}
|
2019-02-25 17:34:07 +00:00
|
|
|
}
|