2018-08-16 18:35:19 +00:00
|
|
|
use rocket;
|
2019-03-07 08:54:55 +00:00
|
|
|
use rocket::http::Header;
|
2019-02-22 15:25:06 +00:00
|
|
|
use rocket::response::NamedFile;
|
2018-12-25 19:06:28 +00:00
|
|
|
use rocket_contrib::templates::Template;
|
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
|
|
|
|
2019-03-06 15:19:33 +00:00
|
|
|
use std::path::PathBuf;
|
2018-08-16 18:35:19 +00:00
|
|
|
|
2019-03-12 11:18:28 +00:00
|
|
|
pub mod upload;
|
2019-03-04 15:43:10 +00:00
|
|
|
use mail;
|
2019-04-02 12:54:40 +00:00
|
|
|
use tokens;
|
2018-08-16 18:35:19 +00:00
|
|
|
|
2019-03-01 08:55:50 +00:00
|
|
|
use database::{Database, Polymorphic, Query};
|
2019-03-05 16:30:56 +00:00
|
|
|
use database::types::{Email, Fingerprint, KeyID};
|
2019-02-22 20:29:53 +00:00
|
|
|
use Result;
|
2018-09-19 20:24:38 +00:00
|
|
|
|
2019-02-07 19:58:31 +00:00
|
|
|
use std::str::FromStr;
|
2018-09-19 20:24:38 +00:00
|
|
|
|
2019-03-12 11:18:28 +00:00
|
|
|
mod hkp;
|
2019-04-05 15:07:40 +00:00
|
|
|
mod manage;
|
2019-02-22 15:25:06 +00:00
|
|
|
|
2019-02-26 13:24:29 +00:00
|
|
|
use rocket::http::hyper::header::ContentDisposition;
|
|
|
|
|
2019-02-22 15:25:06 +00:00
|
|
|
#[derive(Responder)]
|
2019-03-05 15:15:03 +00:00
|
|
|
pub enum MyResponse {
|
2019-02-22 15:25:06 +00:00
|
|
|
#[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-03-05 10:07:59 +00:00
|
|
|
#[response(status = 404, content_type = "html")]
|
|
|
|
NotFound(Template),
|
2019-03-12 14:17:49 +00:00
|
|
|
#[response(status = 400, content_type = "html")]
|
|
|
|
BadRequest(Template),
|
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-03-12 14:17:49 +00:00
|
|
|
pub fn bad_request(template: &'static str, e: failure::Error) -> Self {
|
|
|
|
let ctx = templates::General {
|
|
|
|
error: Some(format!("{}", e)),
|
|
|
|
version: env!("VERGEN_SEMVER").to_string(),
|
|
|
|
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
|
|
|
};
|
|
|
|
MyResponse::BadRequest(Template::render(template, ctx))
|
|
|
|
}
|
|
|
|
|
2019-03-05 10:07:59 +00:00
|
|
|
pub fn not_found<M>(tmpl: Option<&'static str>, message: M)
|
|
|
|
-> Self
|
|
|
|
where M: Into<Option<String>>,
|
2019-02-26 16:34:48 +00:00
|
|
|
{
|
2019-03-05 10:07:59 +00:00
|
|
|
MyResponse::NotFound(
|
|
|
|
Template::render(
|
|
|
|
tmpl.unwrap_or("index"),
|
2019-03-12 14:24:07 +00:00
|
|
|
templates::General::new(
|
2019-03-05 10:07:59 +00:00
|
|
|
Some(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,
|
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,
|
2019-03-06 12:25:21 +00:00
|
|
|
pub gpg_options: Option<&'static str>,
|
2019-03-13 09:07:29 +00:00
|
|
|
pub fpr: String,
|
2019-03-13 09:32:21 +00:00
|
|
|
pub base_uri: String,
|
2019-02-22 15:25:06 +00:00
|
|
|
pub commit: String,
|
|
|
|
pub version: String,
|
|
|
|
}
|
|
|
|
|
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)]
|
2019-03-12 14:24:07 +00:00
|
|
|
pub struct General {
|
2019-02-22 22:29:54 +00:00
|
|
|
pub error: Option<String>,
|
|
|
|
pub commit: String,
|
|
|
|
pub version: String,
|
|
|
|
}
|
2019-03-05 10:07:59 +00:00
|
|
|
|
2019-03-12 14:24:07 +00:00
|
|
|
impl General {
|
2019-03-05 10:07:59 +00:00
|
|
|
pub fn new(error: Option<String>) -> Self {
|
|
|
|
Self {
|
|
|
|
error: error,
|
|
|
|
version: env!("VERGEN_SEMVER").to_string(),
|
|
|
|
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-05 10:11:23 +00:00
|
|
|
impl Default for General {
|
|
|
|
fn default() -> Self {
|
2019-03-12 14:24:07 +00:00
|
|
|
Self::new(None)
|
2019-03-05 10:11:23 +00:00
|
|
|
}
|
|
|
|
}
|
2018-08-16 18:35:19 +00:00
|
|
|
}
|
|
|
|
|
2019-03-06 12:11:49 +00:00
|
|
|
pub struct State {
|
|
|
|
/// The base directory.
|
|
|
|
state_dir: PathBuf,
|
|
|
|
|
|
|
|
/// The public directory.
|
|
|
|
///
|
|
|
|
/// This is what nginx serves.
|
|
|
|
public_dir: PathBuf,
|
|
|
|
|
|
|
|
/// XXX
|
2019-03-13 09:32:21 +00:00
|
|
|
base_uri: String,
|
2019-03-06 12:11:49 +00:00
|
|
|
|
|
|
|
/// Controls the use of NGINX'es XAccelRedirect feature.
|
|
|
|
x_accel_redirect: bool,
|
|
|
|
}
|
2018-10-18 14:26:25 +00:00
|
|
|
|
2019-03-06 12:11:49 +00:00
|
|
|
fn key_to_response<'a>(state: rocket::State<State>,
|
|
|
|
db: rocket::State<Polymorphic>,
|
|
|
|
query_string: String,
|
2019-03-01 08:55:50 +00:00
|
|
|
query: Query,
|
2019-03-06 12:11:49 +00:00
|
|
|
machine_readable: bool)
|
2019-03-01 11:58:17 +00:00
|
|
|
-> 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-06 12:11:49 +00:00
|
|
|
if state.x_accel_redirect {
|
2019-03-01 11:58:17 +00:00
|
|
|
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-03-06 12:25:21 +00:00
|
|
|
let has_uids = match key_has_uids(&state, &db, &query) {
|
|
|
|
Ok(x) => x,
|
|
|
|
Err(e) => return MyResponse::ise(e),
|
|
|
|
};
|
|
|
|
|
2019-02-22 15:25:06 +00:00
|
|
|
let context = templates::Search{
|
2019-03-01 08:55:50 +00:00
|
|
|
query: query_string,
|
2019-03-06 12:25:21 +00:00
|
|
|
gpg_options: if has_uids {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some("--keyserver-options import-drop-uids ")
|
|
|
|
},
|
2019-03-13 09:32:21 +00:00
|
|
|
base_uri: state.base_uri.clone(),
|
2019-03-13 09:07:29 +00:00
|
|
|
fpr: fp.to_string(),
|
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-06 12:25:21 +00:00
|
|
|
fn key_has_uids(state: &State, db: &Polymorphic, query: &Query)
|
|
|
|
-> Result<bool> {
|
|
|
|
use sequoia_openpgp::Packet;
|
|
|
|
use sequoia_openpgp::parse::{Parse, PacketParser, PacketParserResult};
|
|
|
|
let mut ppr = match db.lookup_path(query) {
|
|
|
|
Some(path) => PacketParser::from_file(&state.state_dir.join(path))?,
|
|
|
|
None => return Err(failure::err_msg("key vanished")),
|
|
|
|
};
|
|
|
|
|
|
|
|
while let PacketParserResult::Some(pp) = ppr {
|
|
|
|
if let Packet::UserID(_) = pp.packet {
|
|
|
|
return Ok(true);
|
|
|
|
}
|
|
|
|
ppr = pp.recurse()?.1;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(false)
|
|
|
|
}
|
|
|
|
|
2019-03-04 14:23:53 +00:00
|
|
|
#[get("/vks/v1/by-fingerprint/<fpr>")]
|
2019-03-12 12:41:36 +00:00
|
|
|
fn vks_v1_by_fingerprint(state: rocket::State<State>,
|
|
|
|
db: rocket::State<Polymorphic>,
|
|
|
|
fpr: String) -> MyResponse {
|
2019-03-01 08:55:50 +00:00
|
|
|
let query = match Fingerprint::from_str(&fpr) {
|
|
|
|
Ok(fpr) => Query::ByFingerprint(fpr),
|
2019-03-12 14:17:49 +00:00
|
|
|
Err(e) => return MyResponse::bad_request("index", e),
|
2018-09-19 20:24:38 +00:00
|
|
|
};
|
|
|
|
|
2019-03-06 12:11:49 +00:00
|
|
|
key_to_response(state, db, fpr, query, true)
|
2018-10-25 15:37:33 +00:00
|
|
|
}
|
2018-09-19 20:24:38 +00:00
|
|
|
|
2019-03-04 14:23:53 +00:00
|
|
|
#[get("/vks/v1/by-email/<email>")]
|
2019-03-12 12:41:36 +00:00
|
|
|
fn vks_v1_by_email(state: rocket::State<State>,
|
|
|
|
db: rocket::State<Polymorphic>,
|
|
|
|
email: String) -> MyResponse {
|
2019-03-01 08:55:50 +00:00
|
|
|
let query = match Email::from_str(&email) {
|
|
|
|
Ok(email) => Query::ByEmail(email),
|
2019-03-12 14:17:49 +00:00
|
|
|
Err(e) => return MyResponse::bad_request("index", e),
|
2018-10-25 15:37:33 +00:00
|
|
|
};
|
2018-09-19 20:24:38 +00:00
|
|
|
|
2019-03-06 12:11:49 +00:00
|
|
|
key_to_response(state, db, email, query, true)
|
2018-09-19 20:24:38 +00:00
|
|
|
}
|
|
|
|
|
2019-03-04 14:23:53 +00:00
|
|
|
#[get("/vks/v1/by-keyid/<kid>")]
|
2019-03-12 12:41:36 +00:00
|
|
|
fn vks_v1_by_keyid(state: rocket::State<State>,
|
|
|
|
db: rocket::State<Polymorphic>,
|
|
|
|
kid: String) -> MyResponse {
|
2019-03-01 08:55:50 +00:00
|
|
|
let query = match KeyID::from_str(&kid) {
|
|
|
|
Ok(keyid) => Query::ByKeyID(keyid),
|
2019-03-12 14:17:49 +00:00
|
|
|
Err(e) => return MyResponse::bad_request("index", e),
|
2019-01-04 13:07:14 +00:00
|
|
|
};
|
|
|
|
|
2019-03-06 12:11:49 +00:00
|
|
|
key_to_response(state, db, kid, query, true)
|
2019-01-04 13:07:14 +00:00
|
|
|
}
|
|
|
|
|
2019-03-12 13:47:46 +00:00
|
|
|
#[get("/publish/<token>")]
|
2019-03-13 09:07:29 +00:00
|
|
|
fn publish_verify(db: rocket::State<Polymorphic>,
|
2019-03-12 13:47:46 +00:00
|
|
|
token: String) -> MyResponse {
|
2018-09-19 20:24:38 +00:00
|
|
|
match db.verify_token(&token) {
|
2019-03-13 09:07:29 +00:00
|
|
|
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,
|
|
|
|
userid: userid.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
|
|
|
};
|
|
|
|
|
2019-03-12 13:47:46 +00:00
|
|
|
MyResponse::ok("publish-result", context)
|
2018-08-16 18:35:19 +00:00
|
|
|
}
|
2019-03-05 11:47:19 +00:00
|
|
|
Ok(None) => MyResponse::not_found(Some("generic-error"), None),
|
|
|
|
Err(e) => MyResponse::ise(e),
|
2018-09-19 20:24:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-02 10:48:02 +00:00
|
|
|
#[get("/assets/<file..>")]
|
2019-03-06 12:11:49 +00:00
|
|
|
fn files(file: PathBuf, state: rocket::State<State>) -> Option<NamedFile> {
|
|
|
|
NamedFile::open(state.public_dir.join("assets").join(file)).ok()
|
2018-10-18 14:26:25 +00:00
|
|
|
}
|
|
|
|
|
2018-08-16 18:35:19 +00:00
|
|
|
#[get("/")]
|
2019-03-05 10:07:59 +00:00
|
|
|
fn root() -> Template {
|
2019-03-12 14:24:07 +00:00
|
|
|
Template::render("index", templates::General::default())
|
2018-08-16 18:35:19 +00:00
|
|
|
}
|
|
|
|
|
2019-02-22 20:15:06 +00:00
|
|
|
#[get("/about")]
|
|
|
|
fn about() -> Template {
|
2019-04-25 15:52:54 +00:00
|
|
|
Template::render("about/about", templates::General::default())
|
2019-02-22 20:15:06 +00:00
|
|
|
}
|
|
|
|
|
2019-04-25 15:52:54 +00:00
|
|
|
#[get("/about/privacy")]
|
2019-04-25 15:44:46 +00:00
|
|
|
fn privacy() -> Template {
|
2019-04-25 15:52:54 +00:00
|
|
|
Template::render("about/privacy", templates::General::default())
|
2019-04-25 15:44:46 +00:00
|
|
|
}
|
|
|
|
|
2019-04-25 15:52:54 +00:00
|
|
|
#[get("/about/api")]
|
2019-03-06 15:39:52 +00:00
|
|
|
fn apidoc() -> Template {
|
2019-04-25 15:52:54 +00:00
|
|
|
Template::render("about/api", templates::General::default())
|
2019-03-06 15:39:52 +00:00
|
|
|
}
|
|
|
|
|
2019-03-12 09:31:56 +00:00
|
|
|
pub fn serve() -> Result<()> {
|
|
|
|
Err(rocket_factory(rocket::ignite())?.launch().into())
|
2019-02-25 17:11:43 +00:00
|
|
|
}
|
|
|
|
|
2019-03-12 09:31:56 +00:00
|
|
|
fn rocket_factory(rocket: rocket::Rocket) -> Result<rocket::Rocket> {
|
2019-04-02 12:54:40 +00:00
|
|
|
use std::convert::TryFrom;
|
|
|
|
|
2018-09-19 20:24:38 +00:00
|
|
|
let routes = routes![
|
2018-11-02 10:48:02 +00:00
|
|
|
// infra
|
|
|
|
root,
|
2019-03-12 12:41:36 +00:00
|
|
|
about,
|
2019-04-25 15:44:46 +00:00
|
|
|
privacy,
|
2019-03-12 12:41:36 +00:00
|
|
|
apidoc,
|
|
|
|
files,
|
|
|
|
// VKSv1
|
|
|
|
vks_v1_by_email,
|
|
|
|
vks_v1_by_fingerprint,
|
|
|
|
vks_v1_by_keyid,
|
2019-03-12 15:21:28 +00:00
|
|
|
upload::vks_v1_publish_post,
|
2019-03-12 12:41:36 +00:00
|
|
|
// User interaction.
|
2019-03-12 13:47:46 +00:00
|
|
|
upload::publish,
|
|
|
|
publish_verify,
|
2019-03-12 11:18:28 +00:00
|
|
|
// HKP
|
|
|
|
hkp::pks_lookup,
|
|
|
|
hkp::pks_add,
|
2019-04-05 15:07:40 +00:00
|
|
|
// EManage
|
|
|
|
manage::vks_manage,
|
|
|
|
manage::vks_manage_key,
|
|
|
|
manage::vks_manage_post,
|
|
|
|
manage::vks_manage_unpublish,
|
2018-09-19 20:24:38 +00:00
|
|
|
];
|
|
|
|
|
2019-03-12 09:31:56 +00:00
|
|
|
use database::{Filesystem, Polymorphic};
|
|
|
|
let db = Polymorphic::Filesystem(
|
|
|
|
Filesystem::new(&PathBuf::from(rocket.config().get_str("state_dir")?))?
|
|
|
|
);
|
2019-03-12 09:41:04 +00:00
|
|
|
|
|
|
|
// State
|
|
|
|
let state_dir: PathBuf = rocket.config().get_str("state_dir")?.into();
|
|
|
|
let public_dir = state_dir.join("public");
|
2019-03-13 09:32:21 +00:00
|
|
|
let base_uri = rocket.config().get_str("base-URI")?.to_string();
|
2019-03-12 09:41:04 +00:00
|
|
|
let state = State {
|
|
|
|
state_dir: state_dir,
|
|
|
|
public_dir: public_dir,
|
2019-03-13 09:32:21 +00:00
|
|
|
base_uri: base_uri.clone(),
|
2019-03-12 09:41:04 +00:00
|
|
|
x_accel_redirect: rocket.config().get_bool("x-accel-redirect")?,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Mail service
|
|
|
|
let template_dir: PathBuf = rocket.config().get_str("template_dir")?.into();
|
|
|
|
let from = rocket.config().get_str("from")?.to_string();
|
2019-04-05 15:07:40 +00:00
|
|
|
let verify_html = template_dir.join("email/publish-html.hbs");
|
|
|
|
let verify_txt = template_dir.join("email/publish-txt.hbs");
|
|
|
|
let manage_html = template_dir.join("email/manage-html.hbs");
|
|
|
|
let manage_txt = template_dir.join("email/manage-txt.hbs");
|
2019-03-12 09:41:04 +00:00
|
|
|
|
|
|
|
let mut handlebars = Handlebars::new();
|
|
|
|
handlebars.register_template_file("verify-html", verify_html)?;
|
|
|
|
handlebars.register_template_file("verify-txt", verify_txt)?;
|
2019-04-05 15:07:40 +00:00
|
|
|
handlebars.register_template_file("manage-html", manage_html)?;
|
|
|
|
handlebars.register_template_file("manage-txt", manage_txt)?;
|
2019-03-12 09:41:04 +00:00
|
|
|
|
|
|
|
let filemail_into = rocket.config().get_str("filemail_into")
|
|
|
|
.ok().map(|p| PathBuf::from(p));
|
|
|
|
let mail_service = if let Some(path) = filemail_into {
|
2019-03-13 09:32:21 +00:00
|
|
|
mail::Service::filemail(from, base_uri, handlebars, path)?
|
2019-03-12 09:41:04 +00:00
|
|
|
} else {
|
2019-03-13 09:32:21 +00:00
|
|
|
mail::Service::sendmail(from, base_uri, handlebars)?
|
2019-03-12 09:41:04 +00:00
|
|
|
};
|
|
|
|
|
2019-04-02 12:54:40 +00:00
|
|
|
let secret = rocket.config().get_str("token_secret")?.to_string();
|
|
|
|
let validity = rocket.config().get_int("token_validity")?;
|
|
|
|
let validity = u64::try_from(validity)?;
|
|
|
|
let token_service = tokens::Service::init(&secret, validity);
|
|
|
|
|
2019-03-12 09:31:56 +00:00
|
|
|
Ok(rocket
|
2019-03-12 09:41:04 +00:00
|
|
|
.attach(Template::fairing())
|
|
|
|
.manage(state)
|
|
|
|
.manage(mail_service)
|
2019-04-02 12:54:40 +00:00
|
|
|
.manage(token_service)
|
2019-03-12 09:41:04 +00:00
|
|
|
.mount("/", routes)
|
|
|
|
.manage(db))
|
2018-08-16 18:35:19 +00:00
|
|
|
}
|
2019-02-25 17:34:07 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
2019-03-12 11:18:28 +00:00
|
|
|
pub mod tests {
|
2019-03-04 16:57:36 +00:00
|
|
|
use regex;
|
|
|
|
use std::fs;
|
2019-03-06 15:19:33 +00:00
|
|
|
use std::path::Path;
|
2019-02-25 17:34:07 +00:00
|
|
|
use tempfile::{tempdir, TempDir};
|
|
|
|
use super::rocket;
|
2019-03-13 10:23:55 +00:00
|
|
|
use rocket::local::{Client, LocalResponse};
|
2019-02-25 17:34:07 +00:00
|
|
|
use rocket::http::Status;
|
|
|
|
use rocket::http::ContentType;
|
2019-03-04 16:57:36 +00:00
|
|
|
use lettre::{SendableEmail, SimpleSendableEmail};
|
2019-02-25 17:34:07 +00:00
|
|
|
|
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::*;
|
|
|
|
|
2019-03-13 09:32:21 +00:00
|
|
|
/// Fake base URI to use in tests.
|
|
|
|
const BASE_URI: &'static str = "http://local.connection";
|
|
|
|
|
2019-02-25 17:34:07 +00:00
|
|
|
/// 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.
|
2019-03-12 11:18:28 +00:00
|
|
|
pub fn configuration() -> Result<(TempDir, rocket::Config)> {
|
2019-02-25 17:34:07 +00:00
|
|
|
use rocket::config::{Config, Environment};
|
|
|
|
|
|
|
|
let root = tempdir()?;
|
2019-03-04 16:57:36 +00:00
|
|
|
let filemail = root.path().join("filemail");
|
|
|
|
::std::fs::create_dir_all(&filemail)?;
|
2019-02-25 17:34:07 +00:00
|
|
|
|
|
|
|
let config = Config::build(Environment::Staging)
|
|
|
|
.root(root.path().to_path_buf())
|
2019-03-13 11:22:06 +00:00
|
|
|
.extra("template_dir",
|
|
|
|
::std::env::current_dir().unwrap().join("dist/templates")
|
|
|
|
.to_str().unwrap())
|
2019-02-25 17:34:07 +00:00
|
|
|
.extra(
|
2019-03-06 12:11:49 +00:00
|
|
|
"state_dir",
|
|
|
|
root.path().to_str()
|
2019-02-25 17:34:07 +00:00
|
|
|
.ok_or(failure::err_msg("Static path invalid"))?,
|
|
|
|
)
|
2019-03-13 09:32:21 +00:00
|
|
|
.extra("base-URI", BASE_URI)
|
2019-02-25 17:34:07 +00:00
|
|
|
.extra("from", "from")
|
2019-04-02 12:54:40 +00:00
|
|
|
.extra("token_secret", "hagrid")
|
|
|
|
.extra("token_validity", 3600)
|
2019-03-04 16:57:36 +00:00
|
|
|
.extra("filemail_into", filemail.into_os_string().into_string()
|
|
|
|
.expect("path is valid UTF8"))
|
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))
|
|
|
|
}
|
|
|
|
|
2019-03-12 11:18:28 +00:00
|
|
|
pub fn client() -> Result<(TempDir, Client)> {
|
|
|
|
let (tmpdir, config) = configuration()?;
|
|
|
|
let rocket = rocket_factory(rocket::custom(config))?;
|
|
|
|
Ok((tmpdir, Client::new(rocket)?))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn assert_consistency(rocket: &rocket::Rocket) {
|
2019-03-11 14:49:01 +00:00
|
|
|
let db = rocket.state::<Polymorphic>().unwrap();
|
|
|
|
if let Polymorphic::Filesystem(fs) = db {
|
|
|
|
fs.check_consistency().unwrap();
|
|
|
|
} else {
|
|
|
|
unreachable!();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-25 17:34:07 +00:00
|
|
|
#[test]
|
|
|
|
fn basics() {
|
|
|
|
let (_tmpdir, config) = configuration().unwrap();
|
2019-03-12 09:31:56 +00:00
|
|
|
let rocket = rocket_factory(rocket::custom(config)).unwrap();
|
2019-02-25 17:34:07 +00:00
|
|
|
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));
|
2019-04-25 17:44:16 +00:00
|
|
|
assert!(response.body_string().unwrap().contains("distribution and discovery"));
|
|
|
|
|
|
|
|
// Check that we see the privacy policy.
|
|
|
|
let mut response = client.get("/about/privacy").dispatch();
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
assert_eq!(response.content_type(), Some(ContentType::HTML));
|
2019-02-25 17:34:07 +00:00
|
|
|
assert!(response.body_string().unwrap().contains("Public Key Data"));
|
2019-03-07 11:58:03 +00:00
|
|
|
|
|
|
|
// Check that we see the API docs.
|
2019-04-25 17:44:16 +00:00
|
|
|
let mut response = client.get("/about/api").dispatch();
|
2019-03-07 11:58:03 +00:00
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
assert_eq!(response.content_type(), Some(ContentType::HTML));
|
|
|
|
assert!(response.body_string().unwrap().contains("/vks/v1/by-keyid"));
|
2019-03-11 14:49:01 +00:00
|
|
|
|
2019-03-12 14:47:16 +00:00
|
|
|
// Check that we see the upload form.
|
|
|
|
let mut response = client.get("/publish").dispatch();
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
assert_eq!(response.content_type(), Some(ContentType::HTML));
|
|
|
|
assert!(response.body_string().unwrap().contains("upload"));
|
|
|
|
|
|
|
|
// Check that we see the deletion form.
|
2019-04-17 10:13:45 +00:00
|
|
|
let mut response = client.get("/vks/manage").dispatch();
|
2019-03-12 14:47:16 +00:00
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
assert_eq!(response.content_type(), Some(ContentType::HTML));
|
|
|
|
assert!(response.body_string().unwrap().contains("email"));
|
|
|
|
|
2019-03-11 14:49:01 +00:00
|
|
|
assert_consistency(client.rocket());
|
2019-02-25 17:34:07 +00:00
|
|
|
}
|
2019-02-26 10:34:53 +00:00
|
|
|
|
|
|
|
#[test]
|
2019-04-17 10:13:45 +00:00
|
|
|
fn upload_single() {
|
2019-03-12 12:16:10 +00:00
|
|
|
let (tmpdir, client) = client().unwrap();
|
2019-03-04 16:57:36 +00:00
|
|
|
let filemail_into = tmpdir.path().join("filemail");
|
2019-02-26 10:34:53 +00:00
|
|
|
|
|
|
|
// Generate a key and upload it.
|
|
|
|
let (tpk, _) = TPKBuilder::autocrypt(
|
|
|
|
None, Some("foo@invalid.example.com".into()))
|
|
|
|
.generate().unwrap();
|
|
|
|
|
|
|
|
let mut tpk_serialized = Vec::new();
|
|
|
|
tpk.serialize(&mut tpk_serialized).unwrap();
|
2019-03-12 15:06:23 +00:00
|
|
|
vks_publish_submit(&client, &tpk_serialized);
|
2019-02-26 10:34:53 +00:00
|
|
|
|
2019-03-04 16:45:50 +00:00
|
|
|
// Prior to email confirmation, we should not be able to look
|
|
|
|
// it up by email address.
|
2019-03-05 15:14:26 +00:00
|
|
|
check_null_responses_by_email(&client, "foo@invalid.example.com");
|
|
|
|
|
|
|
|
// And check that we can get it back via the machine readable
|
|
|
|
// interface.
|
|
|
|
check_mr_responses_by_fingerprint(&client, &tpk, 0);
|
|
|
|
|
|
|
|
// And check that we can see the human-readable result page.
|
2019-03-13 10:11:10 +00:00
|
|
|
check_hr_responses_by_fingerprint(&client, &tpk, 0);
|
2019-03-05 15:14:26 +00:00
|
|
|
|
2019-03-12 12:17:23 +00:00
|
|
|
// Now check for the verification mail.
|
|
|
|
check_mails_and_verify_email(&client, filemail_into.as_path());
|
2019-03-05 15:14:26 +00:00
|
|
|
|
|
|
|
// Now lookups using the mail address should work.
|
2019-03-13 10:11:10 +00:00
|
|
|
check_responses_by_email(&client, "foo@invalid.example.com", &tpk, 1);
|
|
|
|
|
|
|
|
// And check that we can see the human-readable result page.
|
|
|
|
check_hr_responses_by_fingerprint(&client, &tpk, 1);
|
2019-03-11 14:49:01 +00:00
|
|
|
|
2019-03-12 12:34:40 +00:00
|
|
|
// Request deletion of the binding.
|
|
|
|
vks_manage(&client, "foo@invalid.example.com");
|
|
|
|
|
|
|
|
// Confirm deletion.
|
2019-04-17 10:13:45 +00:00
|
|
|
check_mails_and_confirm_deletion(&client, filemail_into.as_path(), "foo@invalid.example.com");
|
2019-03-12 12:34:40 +00:00
|
|
|
|
|
|
|
// Now, we should no longer be able to look it up by email
|
|
|
|
// address.
|
|
|
|
check_null_responses_by_email(&client, "foo@invalid.example.com");
|
|
|
|
|
|
|
|
// But lookup by fingerprint should still work.
|
|
|
|
check_mr_responses_by_fingerprint(&client, &tpk, 0);
|
|
|
|
|
|
|
|
// And check that we can see the human-readable result page.
|
2019-03-13 10:11:10 +00:00
|
|
|
check_hr_responses_by_fingerprint(&client, &tpk, 0);
|
2019-03-12 12:34:40 +00:00
|
|
|
|
2019-03-11 14:49:01 +00:00
|
|
|
assert_consistency(client.rocket());
|
2019-03-05 15:14:26 +00:00
|
|
|
}
|
2019-03-04 16:45:50 +00:00
|
|
|
|
2019-03-11 11:07:26 +00:00
|
|
|
#[test]
|
|
|
|
fn upload_two() {
|
|
|
|
let (tmpdir, config) = configuration().unwrap();
|
|
|
|
let filemail_into = tmpdir.path().join("filemail");
|
|
|
|
|
2019-03-12 09:31:56 +00:00
|
|
|
let rocket = rocket_factory(rocket::custom(config)).unwrap();
|
2019-03-11 11:07:26 +00:00
|
|
|
let client = Client::new(rocket).expect("valid rocket instance");
|
|
|
|
|
|
|
|
// Generate two keys and upload them.
|
|
|
|
let tpk_0 = TPKBuilder::autocrypt(
|
|
|
|
None, Some("foo@invalid.example.com".into()))
|
|
|
|
.generate().unwrap().0;
|
|
|
|
let tpk_1 = TPKBuilder::autocrypt(
|
|
|
|
None, Some("bar@invalid.example.com".into()))
|
|
|
|
.generate().unwrap().0;
|
|
|
|
|
|
|
|
let mut tpk_serialized = Vec::new();
|
|
|
|
tpk_0.serialize(&mut tpk_serialized).unwrap();
|
|
|
|
tpk_1.serialize(&mut tpk_serialized).unwrap();
|
2019-03-12 15:06:23 +00:00
|
|
|
vks_publish_submit(&client, &tpk_serialized);
|
2019-03-11 11:07:26 +00:00
|
|
|
|
|
|
|
// Prior to email confirmation, we should not be able to look
|
|
|
|
// them up by email address.
|
|
|
|
check_null_responses_by_email(&client, "foo@invalid.example.com");
|
|
|
|
check_null_responses_by_email(&client, "bar@invalid.example.com");
|
|
|
|
|
|
|
|
// And check that we can get them back via the machine readable
|
|
|
|
// interface.
|
|
|
|
check_mr_responses_by_fingerprint(&client, &tpk_0, 0);
|
|
|
|
check_mr_responses_by_fingerprint(&client, &tpk_1, 0);
|
|
|
|
|
|
|
|
// And check that we can see the human-readable result page.
|
2019-03-13 10:11:10 +00:00
|
|
|
check_hr_responses_by_fingerprint(&client, &tpk_0, 0);
|
|
|
|
check_hr_responses_by_fingerprint(&client, &tpk_1, 0);
|
2019-03-11 11:07:26 +00:00
|
|
|
|
2019-03-12 12:17:23 +00:00
|
|
|
// Now check for the verification mails.
|
2019-04-17 10:13:45 +00:00
|
|
|
check_mails_and_verify_email(&client, &filemail_into);
|
|
|
|
check_mails_and_verify_email(&client, &filemail_into);
|
2019-03-11 11:07:26 +00:00
|
|
|
|
|
|
|
// Now lookups using the mail address should work.
|
2019-03-13 10:11:10 +00:00
|
|
|
check_responses_by_email(&client, "foo@invalid.example.com", &tpk_0, 1);
|
|
|
|
check_responses_by_email(&client, "bar@invalid.example.com", &tpk_1, 1);
|
2019-03-11 14:49:01 +00:00
|
|
|
|
2019-03-12 12:34:40 +00:00
|
|
|
// Request deletion of the bindings.
|
2019-04-17 10:13:45 +00:00
|
|
|
vks_manage(&client, "foo@invalid.example.com");
|
|
|
|
check_mails_and_confirm_deletion(&client, &filemail_into, "foo@invalid.example.com");
|
|
|
|
vks_manage(&client, "bar@invalid.example.com");
|
|
|
|
check_mails_and_confirm_deletion(&client, &filemail_into, "bar@invalid.example.com");
|
2019-03-12 12:34:40 +00:00
|
|
|
|
|
|
|
// Now, we should no longer be able to look it up by email
|
|
|
|
// address.
|
|
|
|
check_null_responses_by_email(&client, "foo@invalid.example.com");
|
|
|
|
check_null_responses_by_email(&client, "bar@invalid.example.com");
|
|
|
|
|
|
|
|
// But lookup by fingerprint should still work.
|
|
|
|
check_mr_responses_by_fingerprint(&client, &tpk_0, 0);
|
|
|
|
check_mr_responses_by_fingerprint(&client, &tpk_1, 0);
|
|
|
|
|
|
|
|
// And check that we can see the human-readable result page.
|
2019-03-13 10:11:10 +00:00
|
|
|
check_hr_responses_by_fingerprint(&client, &tpk_0, 0);
|
|
|
|
check_hr_responses_by_fingerprint(&client, &tpk_1, 0);
|
2019-03-12 12:34:40 +00:00
|
|
|
|
2019-03-11 14:49:01 +00:00
|
|
|
assert_consistency(client.rocket());
|
2019-03-11 11:07:26 +00:00
|
|
|
}
|
|
|
|
|
2019-03-13 10:23:55 +00:00
|
|
|
#[test]
|
|
|
|
fn upload_no_key() {
|
|
|
|
let (_tmpdir, client) = client().unwrap();
|
|
|
|
let response = vks_publish_submit_response(&client, b"");
|
|
|
|
assert_eq!(response.status(), Status::BadRequest);
|
|
|
|
}
|
|
|
|
|
2019-03-05 15:14:26 +00:00
|
|
|
/// Asserts that the given URI 404s.
|
2019-03-12 11:18:28 +00:00
|
|
|
pub fn check_null_response(client: &Client, uri: &str) {
|
2019-03-05 15:14:26 +00:00
|
|
|
let response = client.get(uri).dispatch();
|
|
|
|
assert_eq!(response.status(), Status::NotFound);
|
|
|
|
}
|
|
|
|
|
2019-03-12 12:16:10 +00:00
|
|
|
/// Asserts that lookups by the given email 404.
|
2019-03-12 11:18:28 +00:00
|
|
|
pub fn check_null_responses_by_email(client: &Client, addr: &str) {
|
2019-03-04 16:45:50 +00:00
|
|
|
check_null_response(
|
2019-03-05 15:14:26 +00:00
|
|
|
&client, &format!("/vks/v1/by-email/{}", addr));
|
2019-03-04 16:45:50 +00:00
|
|
|
check_null_response(
|
2019-03-05 15:14:26 +00:00
|
|
|
&client, &format!("/pks/lookup?op=get&search={}", addr));
|
2019-03-04 16:45:50 +00:00
|
|
|
check_null_response(
|
2019-03-05 15:14:26 +00:00
|
|
|
&client, &format!("/pks/lookup?op=get&options=mr&search={}",
|
|
|
|
addr));
|
|
|
|
}
|
|
|
|
|
2019-03-12 12:16:10 +00:00
|
|
|
/// Asserts that lookups by the given email are successful.
|
2019-03-13 10:11:10 +00:00
|
|
|
pub fn check_responses_by_email(client: &Client, addr: &str, tpk: &TPK,
|
|
|
|
nr_uids: usize) {
|
2019-03-12 12:16:10 +00:00
|
|
|
check_mr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/vks/v1/by-email/{}", addr),
|
2019-03-13 10:11:10 +00:00
|
|
|
&tpk, nr_uids);
|
2019-03-12 12:16:10 +00:00
|
|
|
check_mr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/vks/v1/by-email/{}", addr.replace("@", "%40")),
|
2019-03-13 10:11:10 +00:00
|
|
|
&tpk, nr_uids);
|
2019-03-12 12:16:10 +00:00
|
|
|
check_mr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/pks/lookup?op=get&options=mr&search={}", addr),
|
2019-03-13 10:11:10 +00:00
|
|
|
&tpk, nr_uids);
|
2019-03-12 12:16:10 +00:00
|
|
|
check_hr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/pks/lookup?op=get&search={}", addr),
|
2019-03-13 10:11:10 +00:00
|
|
|
&tpk, nr_uids);
|
2019-03-12 12:16:10 +00:00
|
|
|
}
|
|
|
|
|
2019-03-05 15:14:26 +00:00
|
|
|
/// Asserts that the given URI returns a TPK matching the given
|
|
|
|
/// one, with the given number of userids.
|
2019-03-12 11:18:28 +00:00
|
|
|
pub fn check_mr_response(client: &Client, uri: &str, tpk: &TPK,
|
|
|
|
nr_uids: usize) {
|
2019-03-05 15:14:26 +00:00
|
|
|
let mut response = client.get(uri).dispatch();
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
assert_eq!(response.content_type(),
|
|
|
|
Some(ContentType::new("application", "pgp-keys")));
|
|
|
|
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(), nr_uids);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Asserts that we can get the given TPK back using the various
|
|
|
|
/// by-fingerprint or by-keyid lookup mechanisms.
|
2019-03-12 11:18:28 +00:00
|
|
|
pub fn check_mr_responses_by_fingerprint(client: &Client, tpk: &TPK,
|
|
|
|
nr_uids: usize) {
|
2019-03-05 15:14:26 +00:00
|
|
|
let fp = tpk.fingerprint().to_hex();
|
|
|
|
let keyid = tpk.fingerprint().to_keyid().to_hex();
|
2019-02-26 10:34:53 +00:00
|
|
|
|
2019-03-05 12:36:26 +00:00
|
|
|
check_mr_response(
|
2019-03-05 15:14:26 +00:00
|
|
|
&client, &format!("/vks/v1/by-keyid/{}", keyid), &tpk, nr_uids);
|
2019-03-05 12:36:26 +00:00
|
|
|
check_mr_response(
|
2019-03-05 15:14:26 +00:00
|
|
|
&client, &format!("/vks/v1/by-fingerprint/{}", fp), &tpk, nr_uids);
|
2019-02-26 10:34:53 +00:00
|
|
|
check_mr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/pks/lookup?op=get&options=mr&search={}", fp),
|
2019-03-05 15:14:26 +00:00
|
|
|
&tpk, nr_uids);
|
2019-02-26 10:34:53 +00:00
|
|
|
check_mr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/pks/lookup?op=get&options=mr&search=0x{}", fp),
|
2019-03-05 15:14:26 +00:00
|
|
|
&tpk, nr_uids);
|
2019-02-26 10:34:53 +00:00
|
|
|
check_mr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/pks/lookup?op=get&options=mr&search={}", keyid),
|
2019-03-05 15:14:26 +00:00
|
|
|
&tpk, nr_uids);
|
2019-02-26 10:34:53 +00:00
|
|
|
check_mr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/pks/lookup?op=get&options=mr&search=0x{}", keyid),
|
2019-03-05 15:14:26 +00:00
|
|
|
&tpk, nr_uids);
|
|
|
|
}
|
2019-02-26 10:34:53 +00:00
|
|
|
|
2019-03-05 15:14:26 +00:00
|
|
|
/// Asserts that the given URI returns human readable response
|
2019-03-13 10:11:10 +00:00
|
|
|
/// page that contains a URI pointing to the TPK.
|
|
|
|
pub fn check_hr_response(client: &Client, uri: &str, tpk: &TPK,
|
|
|
|
nr_uids: usize) {
|
2019-03-05 15:14:26 +00:00
|
|
|
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()));
|
2019-03-13 10:11:10 +00:00
|
|
|
|
|
|
|
// Extract the links.
|
|
|
|
let link_re = regex::Regex::new(
|
|
|
|
&format!("{}(/vks/[^ \t\n\"<]*)", BASE_URI)).unwrap();
|
|
|
|
let mut n = 0;
|
|
|
|
for link in link_re.captures_iter(&body) {
|
|
|
|
check_mr_response(client, link.get(1).unwrap().as_str(), tpk,
|
|
|
|
nr_uids);
|
|
|
|
n += 1;
|
|
|
|
}
|
|
|
|
assert!(n > 0);
|
2019-03-05 15:14:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Asserts that we can get the given TPK back using the various
|
|
|
|
/// by-fingerprint or by-keyid lookup mechanisms.
|
2019-03-13 10:11:10 +00:00
|
|
|
pub fn check_hr_responses_by_fingerprint(client: &Client, tpk: &TPK,
|
|
|
|
nr_uids: usize) {
|
2019-03-05 15:14:26 +00:00
|
|
|
let fp = tpk.fingerprint().to_hex();
|
|
|
|
let keyid = tpk.fingerprint().to_keyid().to_hex();
|
2019-02-26 10:34:53 +00:00
|
|
|
|
|
|
|
check_hr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/pks/lookup?op=get&search={}", fp),
|
2019-03-13 10:11:10 +00:00
|
|
|
&tpk, nr_uids);
|
2019-02-26 10:34:53 +00:00
|
|
|
check_hr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/pks/lookup?op=get&search=0x{}", fp),
|
2019-03-13 10:11:10 +00:00
|
|
|
&tpk, nr_uids);
|
2019-02-26 10:34:53 +00:00
|
|
|
check_hr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/pks/lookup?op=get&search={}", keyid),
|
2019-03-13 10:11:10 +00:00
|
|
|
&tpk, nr_uids);
|
2019-02-26 10:34:53 +00:00
|
|
|
check_hr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/pks/lookup?op=get&search=0x{}", keyid),
|
2019-03-13 10:11:10 +00:00
|
|
|
&tpk, nr_uids);
|
2019-03-04 16:57:36 +00:00
|
|
|
}
|
|
|
|
|
2019-03-12 12:17:23 +00:00
|
|
|
fn check_mails_and_verify_email(client: &Client, filemail_path: &Path) {
|
2019-04-17 10:13:45 +00:00
|
|
|
let pattern = format!("{}(/publish/[^ \t\n]*)", BASE_URI);
|
|
|
|
let confirm_uri = pop_mail_capture_pattern(filemail_path, &pattern);
|
|
|
|
|
2019-03-11 11:06:44 +00:00
|
|
|
let response = client.get(&confirm_uri).dispatch();
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
}
|
|
|
|
|
2019-04-17 10:13:45 +00:00
|
|
|
fn check_mails_and_confirm_deletion(client: &Client, filemail_path: &Path, address: &str) {
|
|
|
|
let pattern = format!("{}/vks/manage/([^ \t\n]*)", BASE_URI);
|
|
|
|
let token = pop_mail_capture_pattern(filemail_path, &pattern);
|
|
|
|
vks_manage_delete(client, &token, address);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn pop_mail_capture_pattern(filemail_path: &Path, pattern: &str) -> String {
|
|
|
|
let mail_message = pop_mail(filemail_path).unwrap().unwrap();
|
|
|
|
let mail_content = mail_message.message();
|
|
|
|
|
|
|
|
let capture_re = regex::bytes::Regex::new(pattern).unwrap();
|
|
|
|
let capture_content = capture_re .captures(&mail_content).unwrap()
|
2019-03-12 12:34:40 +00:00
|
|
|
.get(1).unwrap().as_bytes();
|
2019-04-17 10:13:45 +00:00
|
|
|
String::from_utf8_lossy(capture_content).to_string()
|
2019-03-12 12:34:40 +00:00
|
|
|
}
|
|
|
|
|
2019-03-04 16:57:36 +00:00
|
|
|
/// Returns and removes the first mail it finds from the given
|
|
|
|
/// directory.
|
2019-03-12 11:18:28 +00:00
|
|
|
pub fn pop_mail(dir: &Path) -> Result<Option<SimpleSendableEmail>> {
|
2019-03-04 16:57:36 +00:00
|
|
|
for entry in fs::read_dir(dir)? {
|
|
|
|
let entry = entry?;
|
|
|
|
if entry.file_type()?.is_file() {
|
|
|
|
let fh = fs::File::open(entry.path())?;
|
|
|
|
fs::remove_file(entry.path())?;
|
|
|
|
let mail: SimpleSendableEmail =
|
|
|
|
::serde_json::from_reader(fh)?;
|
|
|
|
return Ok(Some(mail));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(None)
|
2019-02-26 10:34:53 +00:00
|
|
|
}
|
|
|
|
|
2019-03-12 15:06:23 +00:00
|
|
|
fn vks_publish_submit<'a>(client: &'a Client, data: &[u8]) {
|
2019-03-13 10:23:55 +00:00
|
|
|
let response = vks_publish_submit_response(client, data);
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn vks_publish_submit_response<'a>(client: &'a Client, data: &[u8]) ->
|
|
|
|
LocalResponse<'a> {
|
2019-02-26 10:34:53 +00:00
|
|
|
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);
|
2019-03-13 10:23:55 +00:00
|
|
|
client.post("/vks/v1/publish")
|
2019-02-26 10:34:53 +00:00
|
|
|
.header(ct)
|
|
|
|
.body(&body[..])
|
2019-03-13 10:23:55 +00:00
|
|
|
.dispatch()
|
2019-02-26 10:34:53 +00:00
|
|
|
}
|
2019-03-12 12:34:40 +00:00
|
|
|
|
|
|
|
fn vks_manage<'a>(client: &'a Client, search_term: &str) {
|
|
|
|
let encoded = ::url::form_urlencoded::Serializer::new(String::new())
|
|
|
|
.append_pair("search_term", search_term)
|
|
|
|
.finish();
|
2019-04-17 10:13:45 +00:00
|
|
|
let response = client.post("/vks/manage")
|
|
|
|
.header(ContentType::Form)
|
|
|
|
.body(encoded.as_bytes())
|
|
|
|
.dispatch();
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn vks_manage_delete(client: &Client, token: &str, address: &str) {
|
|
|
|
let encoded = ::url::form_urlencoded::Serializer::new(String::new())
|
|
|
|
.append_pair("token", token)
|
|
|
|
.append_pair("address", address)
|
|
|
|
.finish();
|
|
|
|
let response = client.post("/vks/manage/unpublish")
|
2019-03-12 12:34:40 +00:00
|
|
|
.header(ContentType::Form)
|
|
|
|
.body(encoded.as_bytes())
|
|
|
|
.dispatch();
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
}
|
2019-02-25 17:34:07 +00:00
|
|
|
}
|