2018-08-16 18:35:19 +00:00
|
|
|
use rocket;
|
2019-07-11 20:38:16 +00:00
|
|
|
use rocket::http::{Header, Status};
|
2019-06-22 21:12:14 +00:00
|
|
|
use rocket::request;
|
|
|
|
use rocket::outcome::Outcome;
|
2019-09-27 14:21:10 +00:00
|
|
|
use rocket::response::{NamedFile, Responder, Response};
|
2019-04-26 16:33:26 +00:00
|
|
|
use rocket::config::Config;
|
2019-09-27 14:21:10 +00:00
|
|
|
use rocket_contrib::templates::{Template, Engines};
|
2019-05-04 00:36:46 +00:00
|
|
|
use rocket::http::uri::Uri;
|
2019-06-22 22:09:15 +00:00
|
|
|
use rocket_contrib::json::JsonValue;
|
2019-07-11 20:38:16 +00:00
|
|
|
use rocket::response::status::Custom;
|
2019-09-27 14:21:10 +00:00
|
|
|
use rocket_i18n::I18n;
|
2019-01-10 13:45:11 +00:00
|
|
|
|
2019-07-19 18:46:16 +00:00
|
|
|
use rocket_prometheus::PrometheusMetrics;
|
|
|
|
|
2019-08-28 18:33:24 +00:00
|
|
|
use gettext_macros::{compile_i18n, include_i18n};
|
2019-08-27 21:56:19 +00:00
|
|
|
|
2019-02-22 15:25:06 +00:00
|
|
|
use serde::Serialize;
|
|
|
|
|
2019-03-06 15:19:33 +00:00
|
|
|
use std::path::PathBuf;
|
2018-08-16 18:35:19 +00:00
|
|
|
|
2019-09-02 20:49:02 +00:00
|
|
|
use crate::mail;
|
|
|
|
use crate::tokens;
|
|
|
|
use crate::counters;
|
2021-02-20 12:28:32 +00:00
|
|
|
use crate::i18n_helpers::describe_query_error;
|
2019-09-30 10:52:00 +00:00
|
|
|
use crate::template_helpers::TemplateOverrides;
|
2019-09-27 14:21:10 +00:00
|
|
|
use crate::i18n::I18NHelper;
|
2019-09-02 20:49:02 +00:00
|
|
|
use crate::rate_limiter::RateLimiter;
|
2018-08-16 18:35:19 +00:00
|
|
|
|
2019-09-02 20:49:02 +00:00
|
|
|
use crate::database::{Database, KeyDatabase, Query};
|
|
|
|
use crate::database::types::Fingerprint;
|
|
|
|
use crate::Result;
|
2018-09-19 20:24:38 +00:00
|
|
|
|
2019-05-05 12:58:05 +00:00
|
|
|
use std::convert::TryInto;
|
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-05-05 18:17:54 +00:00
|
|
|
mod maintenance;
|
2019-05-23 23:01:24 +00:00
|
|
|
mod vks;
|
|
|
|
mod vks_web;
|
|
|
|
mod vks_api;
|
2019-06-23 13:47:13 +00:00
|
|
|
mod debug_web;
|
2019-05-05 18:17:54 +00:00
|
|
|
|
2019-09-02 20:49:02 +00:00
|
|
|
use crate::web::maintenance::MaintenanceMode;
|
2019-02-22 15:25:06 +00:00
|
|
|
|
2019-02-26 13:24:29 +00:00
|
|
|
use rocket::http::hyper::header::ContentDisposition;
|
|
|
|
|
2019-09-27 14:21:10 +00:00
|
|
|
pub struct HagridTemplate(&'static str, serde_json::Value);
|
|
|
|
|
|
|
|
impl Responder<'static> for HagridTemplate {
|
|
|
|
fn respond_to(self, req: &rocket::Request) -> std::result::Result<Response<'static>, Status> {
|
|
|
|
let HagridTemplate(tmpl, ctx) = self;
|
|
|
|
let i18n: I18n = req.guard().expect("Error parsing language");
|
2019-09-30 10:52:00 +00:00
|
|
|
let template_overrides: rocket::State<TemplateOverrides> = req.guard().expect("TemplateOverrides must be in managed state");
|
|
|
|
let template_override = template_overrides.get_template_override(i18n.lang, tmpl);
|
2019-09-27 14:21:10 +00:00
|
|
|
let origin: RequestOrigin = req.guard().expect("Error determining request origin");
|
|
|
|
let layout_context = templates::HagridLayout::new(ctx, i18n, origin);
|
2019-09-30 10:52:00 +00:00
|
|
|
|
|
|
|
if let Some(template_override) = template_override {
|
|
|
|
Template::render(template_override, layout_context)
|
|
|
|
} else {
|
|
|
|
Template::render(tmpl, layout_context)
|
|
|
|
}.respond_to(req)
|
2019-09-27 14:21:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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")]
|
2019-09-27 14:21:10 +00:00
|
|
|
Success(HagridTemplate),
|
2019-05-23 23:01:24 +00:00
|
|
|
#[response(status = 200, content_type = "plain")]
|
2019-02-22 15:25:06 +00:00
|
|
|
Plain(String),
|
2019-10-04 13:02:01 +00:00
|
|
|
#[response(status = 200, content_type = "xml")]
|
|
|
|
Xml(HagridTemplate),
|
2019-05-23 23:01:24 +00:00
|
|
|
#[response(status = 200, content_type = "application/pgp-keys")]
|
2019-02-26 13:24:29 +00:00
|
|
|
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")]
|
2019-09-27 14:21:10 +00:00
|
|
|
NotFound(HagridTemplate),
|
2019-06-11 14:59:27 +00:00
|
|
|
#[response(status = 404, content_type = "html")]
|
|
|
|
NotFoundPlain(String),
|
2019-03-12 14:17:49 +00:00
|
|
|
#[response(status = 400, content_type = "html")]
|
2019-09-27 14:21:10 +00:00
|
|
|
BadRequest(HagridTemplate),
|
2019-06-11 14:59:27 +00:00
|
|
|
#[response(status = 400, content_type = "html")]
|
|
|
|
BadRequestPlain(String),
|
2019-06-22 14:53:21 +00:00
|
|
|
#[response(status = 503, content_type = "html")]
|
|
|
|
Maintenance(Template),
|
2019-06-22 22:09:15 +00:00
|
|
|
#[response(status = 503, content_type = "json")]
|
|
|
|
MaintenanceJson(JsonValue),
|
2019-06-22 14:53:21 +00:00
|
|
|
#[response(status = 503, content_type = "plain")]
|
|
|
|
MaintenancePlain(String),
|
2019-02-22 15:25:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl MyResponse {
|
2019-09-27 14:21:10 +00:00
|
|
|
pub fn ok(tmpl: &'static str, ctx: impl Serialize) -> Self {
|
|
|
|
let context_json = serde_json::to_value(ctx).unwrap();
|
|
|
|
MyResponse::Success(HagridTemplate(tmpl, context_json))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn ok_bare(tmpl: &'static str) -> Self {
|
|
|
|
let context_json = serde_json::to_value(templates::Bare { dummy: () }).unwrap();
|
|
|
|
MyResponse::Success(HagridTemplate(tmpl, context_json))
|
2019-02-22 15:25:06 +00:00
|
|
|
}
|
|
|
|
|
2019-10-04 13:02:01 +00:00
|
|
|
pub fn xml(tmpl: &'static str) -> Self {
|
|
|
|
let context_json = serde_json::to_value(templates::Bare { dummy: () }).unwrap();
|
|
|
|
MyResponse::Xml(HagridTemplate(tmpl, context_json))
|
|
|
|
}
|
|
|
|
|
2019-02-22 15:25:06 +00:00
|
|
|
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-06-22 21:08:40 +00:00
|
|
|
use rocket::http::hyper::header::{DispositionType, DispositionParam, Charset};
|
2019-02-26 13:24:29 +00:00
|
|
|
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-04-26 20:14:57 +00:00
|
|
|
pub fn x_accel_redirect(x_accel_path: String, fp: &Fingerprint) -> Self {
|
2019-06-22 21:08:40 +00:00
|
|
|
use rocket::http::hyper::header::{DispositionType, DispositionParam, Charset};
|
2019-05-04 00:36:46 +00:00
|
|
|
// nginx expects percent-encoded URIs
|
|
|
|
let x_accel_path = Uri::percent_encode(&x_accel_path).into_owned();
|
2019-03-01 11:58:17 +00:00
|
|
|
MyResponse::XAccelRedirect(
|
|
|
|
"",
|
2019-04-26 20:14:57 +00:00
|
|
|
Header::new("X-Accel-Redirect", x_accel_path),
|
2019-03-01 11:58:17 +00:00
|
|
|
ContentDisposition {
|
|
|
|
disposition: DispositionType::Attachment,
|
|
|
|
parameters: vec![
|
|
|
|
DispositionParam::Filename(
|
|
|
|
Charset::Us_Ascii, None,
|
|
|
|
(fp.to_string() + ".asc").into_bytes()),
|
|
|
|
],
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-11-04 21:21:00 +00:00
|
|
|
pub fn ise(e: anyhow::Error) -> Self {
|
2019-06-07 20:31:39 +00:00
|
|
|
eprintln!("Internal error: {:?}", e);
|
2019-09-27 14:21:10 +00:00
|
|
|
let ctx = templates::FiveHundred {
|
2019-05-05 21:31:40 +00:00
|
|
|
internal_error: e.to_string(),
|
2021-02-20 12:48:00 +00:00
|
|
|
version: env!("CARGO_PKG_VERSION").to_string(),
|
2019-02-22 15:25:06 +00:00
|
|
|
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
2019-09-27 20:12:36 +00:00
|
|
|
lang: "en".to_string(),
|
2019-02-22 15:25:06 +00:00
|
|
|
};
|
|
|
|
MyResponse::ServerError(Template::render("500", ctx))
|
|
|
|
}
|
|
|
|
|
2020-11-04 21:21:00 +00:00
|
|
|
pub fn bad_request(template: &'static str, e: anyhow::Error) -> Self {
|
2019-09-27 14:21:10 +00:00
|
|
|
let ctx = templates::Error { error: format!("{}", e) };
|
|
|
|
let context_json = serde_json::to_value(ctx).unwrap();
|
|
|
|
MyResponse::BadRequest(HagridTemplate(template, context_json))
|
2019-03-12 14:17:49 +00:00
|
|
|
}
|
|
|
|
|
2019-06-11 14:59:27 +00:00
|
|
|
pub fn bad_request_plain(message: impl Into<String>) -> Self {
|
|
|
|
MyResponse::BadRequestPlain(message.into())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn not_found_plain(message: impl Into<String>) -> Self {
|
|
|
|
MyResponse::NotFoundPlain(message.into())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn not_found(
|
|
|
|
tmpl: Option<&'static str>,
|
2019-09-27 14:21:10 +00:00
|
|
|
message: impl Into<Option<String>>,
|
2019-06-11 14:59:27 +00:00
|
|
|
) -> Self {
|
2019-09-27 14:21:10 +00:00
|
|
|
let ctx = templates::Error { error: message.into()
|
|
|
|
.unwrap_or_else(|| "Key not found".to_owned()) };
|
|
|
|
let context_json = serde_json::to_value(ctx).unwrap();
|
|
|
|
MyResponse::NotFound(HagridTemplate(tmpl.unwrap_or("index"), context_json))
|
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 {
|
2019-09-27 14:21:10 +00:00
|
|
|
use super::{I18n, RequestOrigin};
|
|
|
|
|
2019-02-08 19:09:53 +00:00
|
|
|
#[derive(Serialize)]
|
2019-02-22 15:25:06 +00:00
|
|
|
pub struct FiveHundred {
|
2019-05-05 21:31:40 +00:00
|
|
|
pub internal_error: String,
|
2019-02-22 15:25:06 +00:00
|
|
|
pub commit: String,
|
|
|
|
pub version: String,
|
2019-09-27 20:12:36 +00:00
|
|
|
pub lang: String,
|
2019-02-22 15:25:06 +00:00
|
|
|
}
|
|
|
|
|
2019-02-22 22:29:54 +00:00
|
|
|
#[derive(Serialize)]
|
2019-09-27 14:21:10 +00:00
|
|
|
pub struct HagridLayout<T: serde::Serialize> {
|
2019-02-22 22:29:54 +00:00
|
|
|
pub error: Option<String>,
|
|
|
|
pub commit: String,
|
|
|
|
pub version: String,
|
2019-09-27 14:21:10 +00:00
|
|
|
pub base_uri: String,
|
|
|
|
pub lang: String,
|
2021-02-19 02:40:52 +00:00
|
|
|
pub htmldir: String,
|
|
|
|
pub htmlclass: String,
|
2019-09-27 14:21:10 +00:00
|
|
|
pub page: T,
|
2019-02-22 22:29:54 +00:00
|
|
|
}
|
2019-03-05 10:07:59 +00:00
|
|
|
|
2019-06-05 12:33:02 +00:00
|
|
|
#[derive(Serialize)]
|
2019-09-27 14:21:10 +00:00
|
|
|
pub struct Error {
|
|
|
|
pub error: String,
|
2019-06-05 12:33:02 +00:00
|
|
|
}
|
|
|
|
|
2019-09-27 14:21:10 +00:00
|
|
|
#[derive(Serialize)]
|
|
|
|
pub struct Bare {
|
|
|
|
// Dummy value to make sure {{#with page}} always passes
|
|
|
|
pub dummy: (),
|
2019-06-05 12:33:02 +00:00
|
|
|
}
|
|
|
|
|
2019-09-27 14:21:10 +00:00
|
|
|
impl<T: serde::Serialize> HagridLayout<T> {
|
|
|
|
pub fn new(page: T, i18n: I18n, origin: RequestOrigin) -> Self {
|
2021-02-19 02:40:52 +00:00
|
|
|
let is_rtl = (i18n.lang) == "ar";
|
2019-03-05 10:07:59 +00:00
|
|
|
Self {
|
2019-09-27 14:21:10 +00:00
|
|
|
error: None,
|
2021-02-20 12:48:00 +00:00
|
|
|
version: env!("CARGO_PKG_VERSION").to_string(),
|
2019-03-05 10:07:59 +00:00
|
|
|
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
2019-09-27 14:21:10 +00:00
|
|
|
base_uri: origin.get_base_uri().to_string(),
|
|
|
|
page: page,
|
|
|
|
lang: i18n.lang.to_string(),
|
2021-02-19 02:40:52 +00:00
|
|
|
htmldir: if is_rtl { "rtl".to_owned() } else { "ltr".to_owned() },
|
|
|
|
htmlclass: if is_rtl { "rtl".to_owned() } else { "".to_owned() },
|
2019-03-05 10:07:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-08-16 18:35:19 +00:00
|
|
|
}
|
|
|
|
|
2019-04-26 13:15:54 +00:00
|
|
|
pub struct HagridState {
|
2019-04-26 16:33:26 +00:00
|
|
|
/// Assets directory, mounted to /assets, served by hagrid or nginx
|
|
|
|
assets_dir: PathBuf,
|
|
|
|
|
|
|
|
/// The keys directory, where keys are located, served by hagrid or nginx
|
2019-04-28 20:41:41 +00:00
|
|
|
keys_external_dir: PathBuf,
|
2019-03-06 12:11:49 +00:00
|
|
|
|
|
|
|
/// XXX
|
2019-03-13 09:32:21 +00:00
|
|
|
base_uri: String,
|
2019-06-22 21:12:14 +00:00
|
|
|
base_uri_onion: String,
|
2019-03-06 12:11:49 +00:00
|
|
|
|
2019-05-03 22:06:05 +00:00
|
|
|
///
|
2019-03-06 12:11:49 +00:00
|
|
|
x_accel_redirect: bool,
|
2019-05-03 22:06:05 +00:00
|
|
|
x_accel_prefix: Option<PathBuf>,
|
2019-03-06 12:11:49 +00:00
|
|
|
}
|
2018-10-18 14:26:25 +00:00
|
|
|
|
2019-06-22 21:12:14 +00:00
|
|
|
#[derive(Debug)]
|
2019-06-22 21:25:49 +00:00
|
|
|
pub enum RequestOrigin {
|
2019-06-22 21:12:14 +00:00
|
|
|
Direct(String),
|
|
|
|
OnionService(String),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, 'r> request::FromRequest<'a, 'r> for RequestOrigin {
|
|
|
|
type Error = ();
|
|
|
|
|
|
|
|
fn from_request(request: &'a request::Request<'r>) -> request::Outcome<Self, Self::Error> {
|
|
|
|
let hagrid_state = request.guard::<rocket::State<HagridState>>().unwrap();
|
2019-06-22 21:37:08 +00:00
|
|
|
let result = match request.headers().get("x-is-onion").next() {
|
2019-06-22 21:12:14 +00:00
|
|
|
Some(_) => RequestOrigin::OnionService(hagrid_state.base_uri_onion.clone()),
|
|
|
|
None => RequestOrigin::Direct(hagrid_state.base_uri.clone()),
|
|
|
|
};
|
|
|
|
Outcome::Success(result)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl RequestOrigin {
|
|
|
|
fn get_base_uri(&self) -> &str {
|
|
|
|
match self {
|
|
|
|
RequestOrigin::Direct(uri) => uri.as_str(),
|
|
|
|
RequestOrigin::OnionService(uri) => uri.as_str(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-11 14:59:27 +00:00
|
|
|
pub fn key_to_response_plain(
|
|
|
|
state: rocket::State<HagridState>,
|
|
|
|
db: rocket::State<KeyDatabase>,
|
2021-02-20 12:28:32 +00:00
|
|
|
i18n: I18n,
|
2019-06-11 14:59:27 +00:00
|
|
|
query: Query,
|
|
|
|
) -> MyResponse {
|
2019-03-01 08:55:50 +00:00
|
|
|
let fp = if let Some(fp) = db.lookup_primary_fingerprint(&query) {
|
|
|
|
fp
|
|
|
|
} else {
|
2021-02-20 12:28:32 +00:00
|
|
|
return MyResponse::not_found_plain(describe_query_error(&i18n, &query));
|
2019-02-22 15:25:06 +00:00
|
|
|
};
|
2019-02-26 13:24:29 +00:00
|
|
|
|
2019-06-11 14:59:27 +00:00
|
|
|
if state.x_accel_redirect {
|
|
|
|
if let Some(key_path) = db.lookup_path(&query) {
|
|
|
|
let mut x_accel_path = state.keys_external_dir.join(&key_path);
|
|
|
|
if let Some(prefix) = state.x_accel_prefix.as_ref() {
|
|
|
|
x_accel_path = x_accel_path.strip_prefix(&prefix).unwrap().to_path_buf();
|
2019-03-01 11:58:17 +00:00
|
|
|
}
|
2019-06-11 14:59:27 +00:00
|
|
|
// prepend a / to make path relative to nginx root
|
|
|
|
let x_accel_path = format!("/{}", x_accel_path.to_string_lossy());
|
|
|
|
return MyResponse::x_accel_redirect(x_accel_path, &fp);
|
2019-03-01 08:55:50 +00:00
|
|
|
}
|
2019-02-26 13:24:29 +00:00
|
|
|
}
|
|
|
|
|
2019-06-11 14:59:27 +00:00
|
|
|
return match db.by_fpr(&fp) {
|
|
|
|
Some(armored) => MyResponse::key(armored, &fp.into()),
|
2021-02-20 12:28:32 +00:00
|
|
|
None => MyResponse::not_found_plain(describe_query_error(&i18n, &query)),
|
2019-06-11 14:59:27 +00:00
|
|
|
}
|
2019-01-04 13:07:14 +00:00
|
|
|
}
|
|
|
|
|
2018-11-02 10:48:02 +00:00
|
|
|
#[get("/assets/<file..>")]
|
2019-04-26 13:15:54 +00:00
|
|
|
fn files(file: PathBuf, state: rocket::State<HagridState>) -> Option<NamedFile> {
|
2019-04-26 16:33:26 +00:00
|
|
|
NamedFile::open(state.assets_dir.join(file)).ok()
|
2018-10-18 14:26:25 +00:00
|
|
|
}
|
|
|
|
|
2018-08-16 18:35:19 +00:00
|
|
|
#[get("/")]
|
2019-09-27 14:21:10 +00:00
|
|
|
fn root() -> MyResponse {
|
|
|
|
MyResponse::ok_bare("index")
|
2018-08-16 18:35:19 +00:00
|
|
|
}
|
|
|
|
|
2019-02-22 20:15:06 +00:00
|
|
|
#[get("/about")]
|
2019-09-27 14:21:10 +00:00
|
|
|
fn about() -> MyResponse {
|
|
|
|
MyResponse::ok_bare("about/about")
|
2019-02-22 20:15:06 +00:00
|
|
|
}
|
|
|
|
|
2019-06-12 12:02:45 +00:00
|
|
|
#[get("/about/news")]
|
2019-09-27 14:21:10 +00:00
|
|
|
fn news() -> MyResponse {
|
|
|
|
MyResponse::ok_bare("about/news")
|
2019-06-12 12:02:45 +00:00
|
|
|
}
|
|
|
|
|
2019-10-04 13:02:01 +00:00
|
|
|
#[get("/atom.xml")]
|
|
|
|
fn news_atom() -> MyResponse {
|
|
|
|
MyResponse::xml("atom")
|
|
|
|
}
|
|
|
|
|
2019-06-04 22:11:38 +00:00
|
|
|
#[get("/about/faq")]
|
2019-09-27 14:21:10 +00:00
|
|
|
fn faq() -> MyResponse {
|
|
|
|
MyResponse::ok_bare("about/faq")
|
2019-06-04 22:11:38 +00:00
|
|
|
}
|
|
|
|
|
2019-05-17 20:31:04 +00:00
|
|
|
#[get("/about/usage")]
|
2019-09-27 14:21:10 +00:00
|
|
|
fn usage() -> MyResponse {
|
|
|
|
MyResponse::ok_bare("about/usage")
|
2019-05-17 20:31:04 +00:00
|
|
|
}
|
|
|
|
|
2019-04-25 15:52:54 +00:00
|
|
|
#[get("/about/privacy")]
|
2019-09-27 14:21:10 +00:00
|
|
|
fn privacy() -> MyResponse {
|
|
|
|
MyResponse::ok_bare("about/privacy")
|
2019-04-25 15:44:46 +00:00
|
|
|
}
|
|
|
|
|
2019-04-25 15:52:54 +00:00
|
|
|
#[get("/about/api")]
|
2019-09-27 14:21:10 +00:00
|
|
|
fn apidoc() -> MyResponse {
|
|
|
|
MyResponse::ok_bare("about/api")
|
2019-03-06 15:39:52 +00:00
|
|
|
}
|
|
|
|
|
2019-06-26 23:54:09 +00:00
|
|
|
#[get("/about/stats")]
|
2019-09-27 14:21:10 +00:00
|
|
|
fn stats() -> MyResponse {
|
|
|
|
MyResponse::ok_bare("about/stats")
|
2019-06-26 23:54:09 +00:00
|
|
|
}
|
|
|
|
|
2019-07-11 20:38:16 +00:00
|
|
|
#[get("/errors/<code>/<template>")]
|
|
|
|
fn errors(
|
2019-09-27 14:21:10 +00:00
|
|
|
i18n: I18n,
|
|
|
|
origin: RequestOrigin,
|
2019-07-11 20:38:16 +00:00
|
|
|
code: u16,
|
|
|
|
template: String,
|
|
|
|
) -> Result<Custom<Template>> {
|
|
|
|
if !template.chars().all(|x| x == '-' || char::is_ascii_alphabetic(&x)) {
|
2020-11-04 21:21:00 +00:00
|
|
|
return Err(anyhow!("bad request"));
|
2019-07-11 20:38:16 +00:00
|
|
|
}
|
|
|
|
let status_code = Status::from_code(code)
|
2020-11-04 21:21:00 +00:00
|
|
|
.ok_or(anyhow!("bad request"))?;
|
2019-07-11 20:38:16 +00:00
|
|
|
let response_body = Template::render(
|
|
|
|
format!("errors/{}-{}", code, template),
|
2019-09-27 14:21:10 +00:00
|
|
|
templates::HagridLayout::new(templates::Bare{dummy: ()}, i18n, origin)
|
2019-07-11 20:38:16 +00:00
|
|
|
);
|
|
|
|
Ok(Custom(status_code, response_body))
|
|
|
|
}
|
|
|
|
|
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-08-28 18:33:24 +00:00
|
|
|
compile_i18n!();
|
2019-08-27 21:56:19 +00:00
|
|
|
|
2019-07-19 18:46:16 +00:00
|
|
|
fn rocket_factory(mut rocket: rocket::Rocket) -> Result<rocket::Rocket> {
|
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-06-12 12:02:45 +00:00
|
|
|
news,
|
2019-10-04 13:02:01 +00:00
|
|
|
news_atom,
|
2019-04-25 15:44:46 +00:00
|
|
|
privacy,
|
2019-03-12 12:41:36 +00:00
|
|
|
apidoc,
|
2019-06-04 22:11:38 +00:00
|
|
|
faq,
|
2019-05-17 20:31:04 +00:00
|
|
|
usage,
|
2019-03-12 12:41:36 +00:00
|
|
|
files,
|
2019-06-26 23:54:09 +00:00
|
|
|
stats,
|
2019-07-11 20:38:16 +00:00
|
|
|
errors,
|
2019-03-12 12:41:36 +00:00
|
|
|
// VKSv1
|
2019-06-11 14:59:27 +00:00
|
|
|
vks_api::vks_v1_by_email,
|
|
|
|
vks_api::vks_v1_by_fingerprint,
|
|
|
|
vks_api::vks_v1_by_keyid,
|
2019-05-23 23:01:24 +00:00
|
|
|
vks_api::upload_json,
|
|
|
|
vks_api::upload_fallback,
|
|
|
|
vks_api::request_verify_json,
|
|
|
|
vks_api::request_verify_fallback,
|
2019-03-12 12:41:36 +00:00
|
|
|
// User interaction.
|
2019-06-11 14:59:27 +00:00
|
|
|
vks_web::search,
|
2019-05-23 23:01:24 +00:00
|
|
|
vks_web::upload,
|
|
|
|
vks_web::upload_post_form,
|
|
|
|
vks_web::upload_post_form_data,
|
|
|
|
vks_web::request_verify_form,
|
|
|
|
vks_web::request_verify_form_data,
|
|
|
|
vks_web::verify_confirm,
|
2019-09-26 19:04:48 +00:00
|
|
|
vks_web::verify_confirm_form,
|
2019-06-05 12:33:02 +00:00
|
|
|
vks_web::quick_upload,
|
|
|
|
vks_web::quick_upload_proceed,
|
2019-06-23 13:47:13 +00:00
|
|
|
// Debug
|
|
|
|
debug_web::debug_info,
|
2019-03-12 11:18:28 +00:00
|
|
|
// HKP
|
|
|
|
hkp::pks_lookup,
|
2019-05-20 21:17:50 +00:00
|
|
|
hkp::pks_add_form,
|
|
|
|
hkp::pks_add_form_data,
|
2019-07-11 21:44:56 +00:00
|
|
|
hkp::pks_internal_index,
|
2019-04-05 15:07:40 +00:00
|
|
|
// EManage
|
|
|
|
manage::vks_manage,
|
|
|
|
manage::vks_manage_key,
|
|
|
|
manage::vks_manage_post,
|
|
|
|
manage::vks_manage_unpublish,
|
2019-05-05 18:17:54 +00:00
|
|
|
// Maintenance error page
|
2019-06-22 14:53:21 +00:00
|
|
|
maintenance::maintenance_error_web,
|
2019-06-22 22:09:15 +00:00
|
|
|
maintenance::maintenance_error_json,
|
|
|
|
maintenance::maintenance_error_plain,
|
2018-09-19 20:24:38 +00:00
|
|
|
];
|
|
|
|
|
2019-04-26 16:33:26 +00:00
|
|
|
let db_service = configure_db_service(rocket.config())?;
|
|
|
|
let hagrid_state = configure_hagrid_state(rocket.config())?;
|
2019-04-26 20:14:57 +00:00
|
|
|
let stateful_token_service = configure_stateful_token_service(rocket.config())?;
|
|
|
|
let stateless_token_service = configure_stateless_token_service(rocket.config())?;
|
2019-04-26 16:33:26 +00:00
|
|
|
let mail_service = configure_mail_service(rocket.config())?;
|
2019-05-05 12:58:05 +00:00
|
|
|
let rate_limiter = configure_rate_limiter(rocket.config())?;
|
2019-05-05 18:17:54 +00:00
|
|
|
let maintenance_mode = configure_maintenance_mode(rocket.config())?;
|
2019-09-30 10:52:00 +00:00
|
|
|
let localized_template_list = configure_localized_template_list(rocket.config())?;
|
|
|
|
println!("{:?}", localized_template_list);
|
2019-04-26 16:33:26 +00:00
|
|
|
|
2019-07-19 18:46:16 +00:00
|
|
|
let prometheus = configure_prometheus(rocket.config());
|
|
|
|
|
|
|
|
rocket = rocket
|
2019-09-27 14:21:10 +00:00
|
|
|
.attach(Template::custom(|engines: &mut Engines| {
|
|
|
|
let i18ns = include_i18n!();
|
|
|
|
let i18n_helper = I18NHelper::new(i18ns);
|
|
|
|
engines.handlebars.register_helper("text", Box::new(i18n_helper));
|
|
|
|
}))
|
2019-05-05 18:17:54 +00:00
|
|
|
.attach(maintenance_mode)
|
2019-08-27 21:56:19 +00:00
|
|
|
.manage(include_i18n!())
|
2019-04-26 16:33:26 +00:00
|
|
|
.manage(hagrid_state)
|
2019-04-26 20:14:57 +00:00
|
|
|
.manage(stateless_token_service)
|
|
|
|
.manage(stateful_token_service)
|
2019-04-26 16:33:26 +00:00
|
|
|
.manage(mail_service)
|
|
|
|
.manage(db_service)
|
2019-05-05 12:58:05 +00:00
|
|
|
.manage(rate_limiter)
|
2019-09-30 10:52:00 +00:00
|
|
|
.manage(localized_template_list)
|
2019-07-19 18:46:16 +00:00
|
|
|
.mount("/", routes);
|
|
|
|
|
|
|
|
if let Some(prometheus) = prometheus {
|
|
|
|
rocket = rocket
|
|
|
|
.attach(prometheus.clone())
|
|
|
|
.mount("/metrics", prometheus);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(rocket)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn configure_prometheus(config: &Config) -> Option<PrometheusMetrics> {
|
|
|
|
if !config.get_bool("enable_prometheus").unwrap_or(false) {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
let prometheus = PrometheusMetrics::new();
|
|
|
|
counters::register_counters(&prometheus.registry());
|
|
|
|
return Some(prometheus);
|
2019-04-26 16:33:26 +00:00
|
|
|
}
|
|
|
|
|
2019-04-26 22:21:30 +00:00
|
|
|
fn configure_db_service(config: &Config) -> Result<KeyDatabase> {
|
2019-04-28 20:41:41 +00:00
|
|
|
let keys_internal_dir: PathBuf = config.get_str("keys_internal_dir")?.into();
|
|
|
|
let keys_external_dir: PathBuf = config.get_str("keys_external_dir")?.into();
|
2019-04-26 16:33:26 +00:00
|
|
|
let tmp_dir: PathBuf = config.get_str("tmp_dir")?.into();
|
|
|
|
|
2019-04-28 20:41:41 +00:00
|
|
|
let fs_db = KeyDatabase::new(keys_internal_dir, keys_external_dir, tmp_dir)?;
|
2019-04-26 22:21:30 +00:00
|
|
|
Ok(fs_db)
|
2019-04-26 16:33:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn configure_hagrid_state(config: &Config) -> Result<HagridState> {
|
|
|
|
let assets_dir: PathBuf = config.get_str("assets_dir")?.into();
|
2019-04-28 20:41:41 +00:00
|
|
|
let keys_external_dir: PathBuf = config.get_str("keys_external_dir")?.into();
|
2019-05-03 22:06:05 +00:00
|
|
|
let x_accel_prefix: Option<PathBuf> =
|
|
|
|
config.get_string("x_accel_prefix").map(|prefix| prefix.into()).ok();
|
2019-03-12 09:41:04 +00:00
|
|
|
|
|
|
|
// State
|
2019-04-26 16:33:26 +00:00
|
|
|
let base_uri = config.get_str("base-URI")?.to_string();
|
2019-06-22 21:12:14 +00:00
|
|
|
let base_uri_onion = config.get_str("base-URI-Onion")
|
|
|
|
.map(|c| c.to_string())
|
|
|
|
.unwrap_or(base_uri.clone());
|
2019-04-26 16:33:26 +00:00
|
|
|
Ok(HagridState {
|
|
|
|
assets_dir,
|
2019-04-28 20:41:41 +00:00
|
|
|
keys_external_dir: keys_external_dir,
|
2019-06-22 21:12:14 +00:00
|
|
|
base_uri,
|
|
|
|
base_uri_onion,
|
2019-04-26 16:33:26 +00:00
|
|
|
x_accel_redirect: config.get_bool("x-accel-redirect")?,
|
2019-05-03 22:06:05 +00:00
|
|
|
x_accel_prefix,
|
2019-04-26 16:33:26 +00:00
|
|
|
})
|
|
|
|
}
|
2019-03-12 09:41:04 +00:00
|
|
|
|
2019-04-26 20:14:57 +00:00
|
|
|
fn configure_stateful_token_service(config: &Config) -> Result<database::StatefulTokens> {
|
2019-04-26 21:18:01 +00:00
|
|
|
let token_dir: PathBuf = config.get_str("token_dir")?.into();
|
|
|
|
database::StatefulTokens::new(token_dir)
|
2019-04-26 20:14:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn configure_stateless_token_service(config: &Config) -> Result<tokens::Service> {
|
2019-04-26 16:33:26 +00:00
|
|
|
use std::convert::TryFrom;
|
|
|
|
|
|
|
|
let secret = config.get_str("token_secret")?.to_string();
|
|
|
|
let validity = config.get_int("token_validity")?;
|
|
|
|
let validity = u64::try_from(validity)?;
|
|
|
|
Ok(tokens::Service::init(&secret, validity))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn configure_mail_service(config: &Config) -> Result<mail::Service> {
|
2019-03-12 09:41:04 +00:00
|
|
|
// Mail service
|
2019-08-28 18:33:24 +00:00
|
|
|
let email_template_dir: PathBuf = config.get_str("email_template_dir")?.into();
|
|
|
|
|
2020-03-28 12:48:20 +00:00
|
|
|
let base_uri = config.get_str("base-URI")?;
|
|
|
|
let from = config.get_str("from")?;
|
2019-03-12 09:41:04 +00:00
|
|
|
|
2019-04-26 16:33:26 +00:00
|
|
|
let filemail_into = config.get_str("filemail_into")
|
2019-03-12 09:41:04 +00:00
|
|
|
.ok().map(|p| PathBuf::from(p));
|
2019-04-02 12:54:40 +00:00
|
|
|
|
2019-04-26 16:33:26 +00:00
|
|
|
if let Some(path) = filemail_into {
|
2020-03-28 12:48:20 +00:00
|
|
|
mail::Service::filemail(from, base_uri, &email_template_dir, &path)
|
2019-04-26 16:33:26 +00:00
|
|
|
} else {
|
2020-03-28 12:48:20 +00:00
|
|
|
mail::Service::sendmail(from, base_uri, &email_template_dir)
|
2019-04-26 16:33:26 +00:00
|
|
|
}
|
2018-08-16 18:35:19 +00:00
|
|
|
}
|
2019-02-25 17:34:07 +00:00
|
|
|
|
2019-05-05 12:58:05 +00:00
|
|
|
fn configure_rate_limiter(config: &Config) -> Result<RateLimiter> {
|
|
|
|
let timeout_secs = config.get_int("mail_rate_limit").unwrap_or(60);
|
|
|
|
let timeout_secs = timeout_secs.try_into()?;
|
|
|
|
Ok(RateLimiter::new(timeout_secs))
|
|
|
|
}
|
|
|
|
|
2019-09-30 10:52:00 +00:00
|
|
|
fn configure_localized_template_list(config: &Config) -> Result<TemplateOverrides> {
|
|
|
|
let template_dir: PathBuf = config.get_str("template_dir")?.into();
|
|
|
|
TemplateOverrides::load(&template_dir, "localized")
|
|
|
|
}
|
|
|
|
|
2019-05-05 18:17:54 +00:00
|
|
|
fn configure_maintenance_mode(config: &Config) -> Result<MaintenanceMode> {
|
|
|
|
let maintenance_file: PathBuf = config.get_str("maintenance_file")
|
|
|
|
.unwrap_or("maintenance").into();
|
|
|
|
Ok(MaintenanceMode::new(maintenance_file))
|
|
|
|
}
|
|
|
|
|
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-06-22 22:09:35 +00:00
|
|
|
use std::fs::File;
|
|
|
|
use std::io::Write;
|
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;
|
|
|
|
|
2020-01-29 22:47:29 +00:00
|
|
|
use sequoia_openpgp::Cert;
|
|
|
|
use sequoia_openpgp::cert::CertBuilder;
|
2019-02-26 10:34:53 +00:00
|
|
|
use sequoia_openpgp::parse::Parse;
|
|
|
|
use sequoia_openpgp::serialize::Serialize;
|
|
|
|
|
2020-02-19 10:17:27 +00:00
|
|
|
use std::time::SystemTime;
|
|
|
|
|
2020-03-28 12:48:20 +00:00
|
|
|
use mail::pop_mail;
|
|
|
|
|
2019-09-02 20:49:02 +00:00
|
|
|
use crate::database::*;
|
2019-02-25 17:34:07 +00:00
|
|
|
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-06-22 21:37:08 +00:00
|
|
|
const BASE_URI_ONION: &'static str = "http://local.connection.onion";
|
2019-03-13 09:32:21 +00:00
|
|
|
|
2020-02-08 10:38:26 +00:00
|
|
|
pub fn build_cert(name: &str) -> Cert {
|
|
|
|
let (tpk, _) = CertBuilder::new()
|
|
|
|
.add_signing_subkey()
|
|
|
|
.add_transport_encryption_subkey()
|
|
|
|
.add_userid(name)
|
|
|
|
.generate()
|
|
|
|
.unwrap();
|
|
|
|
return tpk;
|
|
|
|
}
|
|
|
|
|
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-09-02 21:12:27 +00:00
|
|
|
use rocket::config::Environment;
|
2019-02-25 17:34:07 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
2019-04-26 16:33:26 +00:00
|
|
|
let base_dir: PathBuf = root.path().into();
|
|
|
|
|
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-08-28 18:33:24 +00:00
|
|
|
.extra("email_template_dir",
|
|
|
|
::std::env::current_dir().unwrap().join("dist/email-templates")
|
|
|
|
.to_str().unwrap())
|
2019-04-26 16:33:26 +00:00
|
|
|
.extra("assets_dir",
|
|
|
|
::std::env::current_dir().unwrap().join("dist/assets")
|
|
|
|
.to_str().unwrap())
|
2019-04-28 20:41:41 +00:00
|
|
|
.extra("keys_internal_dir", base_dir.join("keys_internal").to_str().unwrap())
|
|
|
|
.extra("keys_external_dir", base_dir.join("keys_external").to_str().unwrap())
|
2019-04-26 16:33:26 +00:00
|
|
|
.extra("tmp_dir", base_dir.join("tmp").to_str().unwrap())
|
2019-04-26 21:18:01 +00:00
|
|
|
.extra("token_dir", base_dir.join("tokens").to_str().unwrap())
|
2019-06-22 22:09:35 +00:00
|
|
|
.extra("maintenance_file", base_dir.join("maintenance").to_str().unwrap())
|
2019-03-13 09:32:21 +00:00
|
|
|
.extra("base-URI", BASE_URI)
|
2019-06-22 21:37:08 +00:00
|
|
|
.extra("base-URI-Onion", BASE_URI_ONION)
|
2019-06-04 14:58:44 +00:00
|
|
|
.extra("from", "from@example.com")
|
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-04-26 22:21:30 +00:00
|
|
|
let db = rocket.state::<KeyDatabase>().unwrap();
|
|
|
|
db.check_consistency().unwrap();
|
2019-03-11 14:49:01 +00:00
|
|
|
}
|
|
|
|
|
2019-09-30 10:53:20 +00:00
|
|
|
#[test]
|
|
|
|
fn about_translation() {
|
|
|
|
let (_tmpdir, config) = configuration().unwrap();
|
|
|
|
let rocket = rocket_factory(rocket::custom(config)).unwrap();
|
|
|
|
let client = Client::new(rocket).expect("valid rocket instance");
|
|
|
|
|
|
|
|
// Check that we see the landing page.
|
|
|
|
let mut response = client.get("/about")
|
|
|
|
.header(Header::new("Accept-Language", "de"))
|
|
|
|
.dispatch();
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
assert_eq!(response.content_type(), Some(ContentType::HTML));
|
|
|
|
// TODO check translation
|
|
|
|
assert!(response.body_string().unwrap().contains("Hagrid"));
|
|
|
|
}
|
|
|
|
|
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.
|
2019-05-23 23:01:24 +00:00
|
|
|
let mut response = client.get("/upload").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("upload"));
|
|
|
|
|
|
|
|
// Check that we see the deletion form.
|
2019-04-25 18:04:20 +00:00
|
|
|
let mut response = client.get("/manage").dispatch();
|
2019-03-12 14:47:16 +00:00
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
assert_eq!(response.content_type(), Some(ContentType::HTML));
|
2019-10-31 13:02:41 +00:00
|
|
|
assert!(response.body_string().unwrap().contains("any verified email address"));
|
2019-03-12 14:47:16 +00:00
|
|
|
|
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
|
|
|
|
2019-06-22 22:09:35 +00:00
|
|
|
#[test]
|
|
|
|
fn maintenance() {
|
|
|
|
let (tmpdir, client) = client().unwrap();
|
|
|
|
|
|
|
|
let maintenance_path = tmpdir.path().join("maintenance");
|
|
|
|
let mut file = File::create(&maintenance_path).unwrap();
|
|
|
|
file.write_all(b"maintenance-message").unwrap();
|
|
|
|
|
|
|
|
// Check that endpoints return a maintenance message
|
|
|
|
check_maintenance(&client, "/upload", ContentType::HTML);
|
|
|
|
check_maintenance(&client, "/manage", ContentType::HTML);
|
|
|
|
check_maintenance(&client, "/verify", ContentType::HTML);
|
|
|
|
check_maintenance(&client, "/pks/add", ContentType::Plain);
|
|
|
|
check_maintenance(&client, "/vks/v1/upload", ContentType::JSON);
|
|
|
|
check_maintenance(&client, "/vks/v1/request-verify", ContentType::JSON);
|
|
|
|
|
|
|
|
// Extra check for the shortcut "PUT" endpoint
|
|
|
|
let mut response = client.put("/").dispatch();
|
|
|
|
assert_eq!(response.status(), Status::ServiceUnavailable);
|
|
|
|
assert_eq!(response.content_type(), Some(ContentType::Plain));
|
|
|
|
assert!(response.body_string().unwrap().contains("maintenance-message"));
|
|
|
|
|
|
|
|
fs::remove_file(&maintenance_path).unwrap();
|
|
|
|
// Check that we see the upload form.
|
|
|
|
let mut response = client.get("/upload").dispatch();
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
assert_eq!(response.content_type(), Some(ContentType::HTML));
|
|
|
|
assert!(!response.body_string().unwrap().contains("maintenance-message"));
|
|
|
|
}
|
|
|
|
|
|
|
|
fn check_maintenance(client: &Client, uri: &str, content_type: ContentType) {
|
|
|
|
let mut response = client.get(uri).dispatch();
|
|
|
|
assert_eq!(response.status(), Status::ServiceUnavailable);
|
|
|
|
assert_eq!(response.content_type(), Some(content_type));
|
|
|
|
assert!(response.body_string().unwrap().contains("maintenance-message"));
|
|
|
|
}
|
|
|
|
|
2019-02-26 10:34:53 +00:00
|
|
|
#[test]
|
2019-05-03 13:34:34 +00:00
|
|
|
fn upload_verify_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.
|
2020-02-08 10:38:26 +00:00
|
|
|
let tpk = build_cert("foo@invalid.example.com");
|
2019-02-26 10:34:53 +00:00
|
|
|
|
|
|
|
let mut tpk_serialized = Vec::new();
|
|
|
|
tpk.serialize(&mut tpk_serialized).unwrap();
|
2019-05-03 13:34:34 +00:00
|
|
|
let token = vks_publish_submit_get_token(&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-05-03 13:34:34 +00:00
|
|
|
// Check the verification link
|
2019-08-28 18:33:24 +00:00
|
|
|
check_verify_link(&client, &token, "foo@invalid.example.com", "");
|
2019-05-03 13:34:34 +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-08-28 18:33:24 +00:00
|
|
|
#[test]
|
|
|
|
fn upload_verify_lang() {
|
|
|
|
let (tmpdir, client) = client().unwrap();
|
|
|
|
let filemail_into = tmpdir.path().join("filemail");
|
|
|
|
|
|
|
|
// Generate a key and upload it.
|
2020-02-08 10:38:26 +00:00
|
|
|
let tpk = build_cert("foo@invalid.example.com");
|
2019-08-28 18:33:24 +00:00
|
|
|
|
|
|
|
let mut tpk_serialized = Vec::new();
|
|
|
|
tpk.serialize(&mut tpk_serialized).unwrap();
|
|
|
|
let token = vks_publish_submit_get_token(&client, &tpk_serialized);
|
|
|
|
|
2019-10-03 15:19:36 +00:00
|
|
|
check_verify_link(&client, &token, "foo@invalid.example.com", "de");
|
2019-08-28 18:33:24 +00:00
|
|
|
let mail_content = pop_mail(&filemail_into).unwrap().unwrap();
|
2019-11-18 10:26:41 +00:00
|
|
|
assert!(mail_content.contains("Dies ist eine automatisierte Nachricht"));
|
2019-10-03 15:19:36 +00:00
|
|
|
assert!(mail_content.contains("Subject: =?utf-8?q?Best=C3=A4tige?= foo@invalid.example.com\r\n\t=?utf-8?q?f=C3=BCr?= deinen =?utf-8?q?Schl=C3=BCssel?= auf local.connection"));
|
2019-08-28 18:33:24 +00:00
|
|
|
}
|
|
|
|
|
2019-03-11 11:07:26 +00:00
|
|
|
#[test]
|
|
|
|
fn upload_two() {
|
2019-05-03 13:34:34 +00:00
|
|
|
let (_tmpdir, config) = configuration().unwrap();
|
2019-03-11 11:07:26 +00:00
|
|
|
|
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.
|
2020-02-08 10:38:26 +00:00
|
|
|
let tpk_0 = build_cert("foo@invalid.example.com");
|
|
|
|
let tpk_1 = build_cert("bar@invalid.example.com");
|
2019-03-11 11:07:26 +00:00
|
|
|
|
|
|
|
let mut tpk_serialized = Vec::new();
|
|
|
|
tpk_0.serialize(&mut tpk_serialized).unwrap();
|
|
|
|
tpk_1.serialize(&mut tpk_serialized).unwrap();
|
2019-05-03 13:34:34 +00:00
|
|
|
vks_publish_submit_multiple(&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-05-03 13:34:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn upload_verify_two() {
|
|
|
|
let (tmpdir, config) = configuration().unwrap();
|
|
|
|
let filemail_into = tmpdir.path().join("filemail");
|
|
|
|
|
|
|
|
let rocket = rocket_factory(rocket::custom(config)).unwrap();
|
|
|
|
let client = Client::new(rocket).expect("valid rocket instance");
|
|
|
|
|
|
|
|
// Generate two keys and upload them.
|
2020-02-08 10:38:26 +00:00
|
|
|
let tpk_1 = build_cert("foo@invalid.example.com");
|
|
|
|
let tpk_2 = build_cert("bar@invalid.example.com");
|
2019-05-03 13:34:34 +00:00
|
|
|
|
|
|
|
let mut tpk_serialized_1 = Vec::new();
|
|
|
|
tpk_1.serialize(&mut tpk_serialized_1).unwrap();
|
|
|
|
let token_1 = vks_publish_submit_get_token(&client, &tpk_serialized_1);
|
|
|
|
|
|
|
|
let mut tpk_serialized_2 = Vec::new();
|
|
|
|
tpk_2.serialize(&mut tpk_serialized_2).unwrap();
|
2019-05-20 21:17:50 +00:00
|
|
|
let token_2 = vks_publish_json_get_token(&client, &tpk_serialized_2);
|
2019-05-03 13:34:34 +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_1, 0);
|
|
|
|
check_mr_responses_by_fingerprint(&client, &tpk_2, 0);
|
|
|
|
|
|
|
|
// And check that we can see the human-readable result page.
|
|
|
|
check_hr_responses_by_fingerprint(&client, &tpk_1, 0);
|
|
|
|
check_hr_responses_by_fingerprint(&client, &tpk_2, 0);
|
|
|
|
|
|
|
|
// Check the verification link
|
2019-08-28 18:33:24 +00:00
|
|
|
check_verify_link(&client, &token_1, "foo@invalid.example.com", "");
|
2019-05-20 21:17:50 +00:00
|
|
|
check_verify_link_json(&client, &token_2, "bar@invalid.example.com");
|
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-05-03 13:34:34 +00:00
|
|
|
check_responses_by_email(&client, "foo@invalid.example.com", &tpk_1, 1);
|
|
|
|
check_responses_by_email(&client, "bar@invalid.example.com", &tpk_2, 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_1, 0);
|
2019-05-03 13:34:34 +00:00
|
|
|
check_mr_responses_by_fingerprint(&client, &tpk_2, 0);
|
2019-03-12 12:34:40 +00:00
|
|
|
|
|
|
|
// 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_1, 0);
|
2019-05-03 13:34:34 +00:00
|
|
|
check_hr_responses_by_fingerprint(&client, &tpk_2, 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-06-22 23:39:36 +00:00
|
|
|
#[test]
|
|
|
|
fn upload_verify_onion() {
|
|
|
|
let (tmpdir, client) = client().unwrap();
|
|
|
|
let filemail_into = tmpdir.path().join("filemail");
|
|
|
|
|
|
|
|
// Generate a key and upload it.
|
2020-02-08 10:38:26 +00:00
|
|
|
let tpk = build_cert("foo@invalid.example.com");
|
2019-06-22 23:39:36 +00:00
|
|
|
|
|
|
|
let mut tpk_serialized = Vec::new();
|
|
|
|
tpk.serialize(&mut tpk_serialized).unwrap();
|
|
|
|
let token = vks_publish_submit_get_token(&client, &tpk_serialized);
|
|
|
|
|
|
|
|
// Check the verification link
|
|
|
|
let encoded = ::url::form_urlencoded::Serializer::new(String::new())
|
|
|
|
.append_pair("token", &token)
|
|
|
|
.append_pair("address", "foo@invalid.example.com")
|
|
|
|
.finish();
|
|
|
|
|
|
|
|
let response = client.post("/upload/request-verify")
|
|
|
|
.header(ContentType::Form)
|
|
|
|
.header(Header::new("X-Is-Onion", "true"))
|
|
|
|
.body(encoded.as_bytes())
|
|
|
|
.dispatch();
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
|
|
|
|
// Now check for the verification mail.
|
|
|
|
let pattern = format!("{}(/verify/[^ \t\n]*)", BASE_URI_ONION);
|
|
|
|
let confirm_uri = pop_mail_capture_pattern(&filemail_into, &pattern);
|
|
|
|
|
|
|
|
let response = client.get(&confirm_uri).dispatch();
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
|
|
|
|
assert_consistency(client.rocket());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-05 12:33:02 +00:00
|
|
|
#[test]
|
|
|
|
fn upload_curl_shortcut() {
|
|
|
|
let (_tmpdir, client) = client().unwrap();
|
|
|
|
|
2020-02-08 10:38:26 +00:00
|
|
|
let tpk = build_cert("foo@invalid.example.com");
|
2019-06-05 12:33:02 +00:00
|
|
|
|
|
|
|
let mut tpk_serialized = Vec::new();
|
|
|
|
tpk.serialize(&mut tpk_serialized).unwrap();
|
|
|
|
|
|
|
|
let _token = vks_publish_shortcut_get_token(&client, &tpk_serialized);
|
|
|
|
|
|
|
|
check_mr_responses_by_fingerprint(&client, &tpk, 0);
|
|
|
|
check_null_responses_by_email(&client, "foo@invalid.example.com");
|
|
|
|
}
|
|
|
|
|
2019-11-27 10:59:15 +00:00
|
|
|
#[test]
|
|
|
|
fn search_invalid() {
|
|
|
|
let (_tmpdir, client) = client().unwrap();
|
|
|
|
check_response(&client, "/search?q=0x1234abcd",
|
|
|
|
Status::BadRequest, "not supported, sorry!");
|
|
|
|
check_response(&client, "/search?q=1234abcd",
|
|
|
|
Status::BadRequest, "not supported, sorry!");
|
|
|
|
check_response(&client, "/pks/lookup?op=get&search=0x1234abcd",
|
|
|
|
Status::BadRequest, "not supported, sorry!");
|
|
|
|
check_response(&client, "/pks/lookup?op=get&search=1234abcd",
|
|
|
|
Status::BadRequest, "not supported, sorry!");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
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.
|
2020-01-29 22:47:29 +00:00
|
|
|
pub fn check_responses_by_email(client: &Client, addr: &str, tpk: &Cert,
|
2019-03-13 10:11:10 +00:00
|
|
|
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,
|
2019-06-11 14:59:27 +00:00
|
|
|
&format!("/search?q={}", addr),
|
2019-03-13 10:11:10 +00:00
|
|
|
&tpk, nr_uids);
|
2019-06-22 21:37:08 +00:00
|
|
|
check_hr_response_onion(
|
|
|
|
&client,
|
|
|
|
&format!("/search?q={}", addr),
|
|
|
|
&tpk, nr_uids);
|
2019-03-12 12:16:10 +00:00
|
|
|
}
|
|
|
|
|
2020-01-29 22:47:29 +00:00
|
|
|
/// Asserts that the given URI returns a Cert matching the given
|
2019-03-05 15:14:26 +00:00
|
|
|
/// one, with the given number of userids.
|
2020-01-29 22:47:29 +00:00
|
|
|
pub fn check_mr_response(client: &Client, uri: &str, tpk: &Cert,
|
2019-03-12 11:18:28 +00:00
|
|
|
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"));
|
2020-01-29 22:47:29 +00:00
|
|
|
let tpk_ = Cert::from_bytes(body.as_bytes()).unwrap();
|
2019-03-05 15:14:26 +00:00
|
|
|
assert_eq!(tpk.fingerprint(), tpk_.fingerprint());
|
2020-01-29 22:47:29 +00:00
|
|
|
assert_eq!(tpk.keys().map(|skb| skb.key().fingerprint())
|
2019-03-05 15:14:26 +00:00
|
|
|
.collect::<Vec<_>>(),
|
2020-01-29 22:47:29 +00:00
|
|
|
tpk_.keys().map(|skb| skb.key().fingerprint())
|
2019-03-05 15:14:26 +00:00
|
|
|
.collect::<Vec<_>>());
|
|
|
|
assert_eq!(tpk_.userids().count(), nr_uids);
|
|
|
|
}
|
|
|
|
|
2019-12-10 16:17:47 +00:00
|
|
|
// it's a rather "reverse implementation" style test.. can we do better?
|
|
|
|
/// Asserts that the given URI returns a correct hkp "index"
|
2020-01-29 22:47:29 +00:00
|
|
|
/// response for the given Cert.
|
|
|
|
pub fn check_index_response(client: &Client, uri: &str, tpk: &Cert) {
|
2019-12-10 16:17:47 +00:00
|
|
|
let mut response = client.get(uri).dispatch();
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
assert_eq!(response.content_type(),
|
|
|
|
Some(ContentType::new("text", "plain")));
|
|
|
|
let body = response.body_string().unwrap();
|
|
|
|
|
|
|
|
assert!(body.contains("info:1:1"));
|
|
|
|
let primary_fpr = tpk.fingerprint().to_hex();
|
2020-02-08 10:38:26 +00:00
|
|
|
let algo: u8 = tpk.primary_key().pk_algo().into();
|
2019-12-10 16:17:47 +00:00
|
|
|
assert!(body.contains(&format!("pub:{}:{}:", primary_fpr, algo)));
|
|
|
|
|
2020-02-08 10:38:26 +00:00
|
|
|
let creation_time = tpk.primary_key().creation_time().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
|
2019-12-10 16:17:47 +00:00
|
|
|
assert!(body.contains(&format!(":{}:", creation_time)));
|
|
|
|
}
|
|
|
|
|
2020-01-29 22:47:29 +00:00
|
|
|
/// Asserts that we can get the given Cert back using the various
|
2019-03-05 15:14:26 +00:00
|
|
|
/// by-fingerprint or by-keyid lookup mechanisms.
|
2020-01-29 22:47:29 +00:00
|
|
|
pub fn check_mr_responses_by_fingerprint(client: &Client, tpk: &Cert,
|
2019-03-12 11:18:28 +00:00
|
|
|
nr_uids: usize) {
|
2019-03-05 15:14:26 +00:00
|
|
|
let fp = tpk.fingerprint().to_hex();
|
2020-01-29 22:47:29 +00:00
|
|
|
let keyid = sequoia_openpgp::KeyID::from(tpk.fingerprint()).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-06-11 14:59:27 +00:00
|
|
|
check_mr_response(
|
|
|
|
&client,
|
|
|
|
&format!("/pks/lookup?op=get&search=0x{}", keyid),
|
|
|
|
&tpk, nr_uids);
|
2019-12-10 16:17:47 +00:00
|
|
|
|
|
|
|
check_index_response(
|
|
|
|
&client,
|
|
|
|
&format!("/pks/lookup?op=index&search={}", fp),
|
|
|
|
&tpk);
|
2019-03-05 15:14:26 +00:00
|
|
|
}
|
2019-02-26 10:34:53 +00:00
|
|
|
|
2019-11-27 10:59:15 +00:00
|
|
|
/// Asserts that the given URI contains the search string.
|
|
|
|
pub fn check_response(client: &Client, uri: &str, status: Status, needle: &str) {
|
|
|
|
let mut response = client.get(uri).dispatch();
|
|
|
|
assert_eq!(response.status(), status);
|
|
|
|
let body = response.body_string().unwrap();
|
|
|
|
println!("{}", body);
|
|
|
|
assert!(body.contains(needle));
|
|
|
|
}
|
|
|
|
|
2019-03-05 15:14:26 +00:00
|
|
|
/// Asserts that the given URI returns human readable response
|
2020-01-29 22:47:29 +00:00
|
|
|
/// page that contains a URI pointing to the Cert.
|
|
|
|
pub fn check_hr_response(client: &Client, uri: &str, tpk: &Cert,
|
2019-03-13 10:11:10 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2019-06-22 21:37:08 +00:00
|
|
|
/// Asserts that the given URI returns human readable response
|
2020-01-29 22:47:29 +00:00
|
|
|
/// page that contains an onion URI pointing to the Cert.
|
|
|
|
pub fn check_hr_response_onion(client: &Client, uri: &str, tpk: &Cert,
|
2019-09-02 20:49:02 +00:00
|
|
|
_nr_uids: usize) {
|
2019-06-22 21:37:08 +00:00
|
|
|
let mut response = client
|
|
|
|
.get(uri)
|
|
|
|
.header(Header::new("X-Is-Onion", "true"))
|
|
|
|
.dispatch();
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
let body = response.body_string().unwrap();
|
|
|
|
assert!(body.contains("found"));
|
|
|
|
assert!(body.contains(&tpk.fingerprint().to_hex()));
|
|
|
|
|
|
|
|
// Extract the links.
|
|
|
|
let link_re = regex::Regex::new(
|
|
|
|
&format!("{}(/vks/[^ \t\n\"<]*)", BASE_URI_ONION)).unwrap();
|
|
|
|
assert!(link_re.is_match(&body));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-01-29 22:47:29 +00:00
|
|
|
/// Asserts that we can get the given Cert back using the various
|
2019-03-05 15:14:26 +00:00
|
|
|
/// by-fingerprint or by-keyid lookup mechanisms.
|
2020-01-29 22:47:29 +00:00
|
|
|
pub fn check_hr_responses_by_fingerprint(client: &Client, tpk: &Cert,
|
2019-03-13 10:11:10 +00:00
|
|
|
nr_uids: usize) {
|
2019-03-05 15:14:26 +00:00
|
|
|
let fp = tpk.fingerprint().to_hex();
|
2020-01-29 22:47:29 +00:00
|
|
|
let keyid = sequoia_openpgp::KeyID::from(tpk.fingerprint()).to_hex();
|
2019-02-26 10:34:53 +00:00
|
|
|
|
|
|
|
check_hr_response(
|
|
|
|
&client,
|
2019-06-11 14:59:27 +00:00
|
|
|
&format!("/search?q={}", fp),
|
2019-03-13 10:11:10 +00:00
|
|
|
&tpk, nr_uids);
|
2019-02-26 10:34:53 +00:00
|
|
|
check_hr_response(
|
|
|
|
&client,
|
2019-06-11 14:59:27 +00:00
|
|
|
&format!("/search?q=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,
|
2019-06-11 14:59:27 +00:00
|
|
|
&format!("/search?q={}", keyid),
|
2019-03-13 10:11:10 +00:00
|
|
|
&tpk, nr_uids);
|
2019-02-26 10:34:53 +00:00
|
|
|
check_hr_response(
|
|
|
|
&client,
|
2019-06-11 14:59:27 +00:00
|
|
|
&format!("/search?q=0x{}", keyid),
|
2019-03-13 10:11:10 +00:00
|
|
|
&tpk, nr_uids);
|
2019-03-04 16:57:36 +00:00
|
|
|
}
|
|
|
|
|
2019-08-28 18:33:24 +00:00
|
|
|
fn check_verify_link(client: &Client, token: &str, address: &str, lang: &'static str) {
|
2019-05-03 13:34:34 +00:00
|
|
|
let encoded = ::url::form_urlencoded::Serializer::new(String::new())
|
|
|
|
.append_pair("token", token)
|
|
|
|
.append_pair("address", address)
|
|
|
|
.finish();
|
|
|
|
|
2019-05-23 23:01:24 +00:00
|
|
|
let response = client.post("/upload/request-verify")
|
2019-05-03 13:34:34 +00:00
|
|
|
.header(ContentType::Form)
|
2019-08-28 18:33:24 +00:00
|
|
|
.header(Header::new("Accept-Language", lang))
|
2019-05-03 13:34:34 +00:00
|
|
|
.body(encoded.as_bytes())
|
|
|
|
.dispatch();
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
}
|
|
|
|
|
2019-05-20 21:17:50 +00:00
|
|
|
fn check_verify_link_json(client: &Client, token: &str, address: &str) {
|
2019-05-23 23:01:24 +00:00
|
|
|
let json = format!(r#"{{"token":"{}","addresses":["{}"]}}"#, token, address);
|
2019-05-20 21:17:50 +00:00
|
|
|
|
|
|
|
let mut response = client.post("/vks/v1/request-verify")
|
|
|
|
.header(ContentType::JSON)
|
|
|
|
.body(json.as_bytes())
|
|
|
|
.dispatch();
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
assert!(response.body_string().unwrap().contains("pending"));
|
|
|
|
}
|
|
|
|
|
2019-03-12 12:17:23 +00:00
|
|
|
fn check_mails_and_verify_email(client: &Client, filemail_path: &Path) {
|
2019-05-23 23:01:24 +00:00
|
|
|
let pattern = format!("{}(/verify/[^ \t\n]*)", BASE_URI);
|
2019-04-17 10:13:45 +00:00
|
|
|
let confirm_uri = pop_mail_capture_pattern(filemail_path, &pattern);
|
|
|
|
|
2019-09-26 19:04:48 +00:00
|
|
|
let response = client.post(&confirm_uri).dispatch();
|
2019-03-11 11:06:44 +00:00
|
|
|
assert_eq!(response.status(), Status::Ok);
|
2019-09-26 20:58:52 +00:00
|
|
|
|
|
|
|
let mut response_second = client.post(&confirm_uri).dispatch();
|
|
|
|
assert_eq!(response_second.status(), Status::BadRequest);
|
2019-10-31 13:02:41 +00:00
|
|
|
assert!(response_second.body_string().unwrap().contains("already been verified"));
|
2019-03-11 11:06:44 +00:00
|
|
|
}
|
|
|
|
|
2019-04-17 10:13:45 +00:00
|
|
|
fn check_mails_and_confirm_deletion(client: &Client, filemail_path: &Path, address: &str) {
|
2019-04-25 18:04:20 +00:00
|
|
|
let pattern = format!("{}/manage/([^ \t\n]*)", BASE_URI);
|
2019-04-17 10:13:45 +00:00
|
|
|
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 {
|
2019-06-04 14:58:44 +00:00
|
|
|
let mail_content = pop_mail(filemail_path).unwrap().unwrap();
|
2019-04-17 10:13:45 +00:00
|
|
|
|
|
|
|
let capture_re = regex::bytes::Regex::new(pattern).unwrap();
|
2019-06-04 14:58:44 +00:00
|
|
|
let capture_content = capture_re.captures(mail_content.as_ref()).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-05-03 13:34:34 +00:00
|
|
|
fn vks_publish_submit_multiple<'a>(client: &'a Client, data: &[u8]) {
|
|
|
|
let mut response = vks_publish_submit_response(client, data);
|
|
|
|
let response_body = response.body_string().unwrap();
|
|
|
|
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
assert!(response_body.contains("you must upload them individually"));
|
|
|
|
}
|
|
|
|
|
|
|
|
fn vks_publish_submit_get_token<'a>(client: &'a Client, data: &[u8]) -> String {
|
|
|
|
let mut response = vks_publish_submit_response(client, data);
|
|
|
|
let response_body = response.body_string().unwrap();
|
|
|
|
|
|
|
|
let pattern = "name=\"token\" value=\"([^\"]*)\"";
|
|
|
|
let capture_re = regex::bytes::Regex::new(pattern).unwrap();
|
|
|
|
let capture_content = capture_re .captures(response_body.as_bytes()).unwrap()
|
|
|
|
.get(1).unwrap().as_bytes();
|
|
|
|
let token = String::from_utf8_lossy(capture_content).to_string();
|
|
|
|
|
2019-03-13 10:23:55 +00:00
|
|
|
assert_eq!(response.status(), Status::Ok);
|
2019-05-03 13:34:34 +00:00
|
|
|
token
|
2019-03-13 10:23:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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-05-23 23:01:24 +00:00
|
|
|
client.post("/upload/submit")
|
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
|
|
|
|
2019-06-05 12:33:02 +00:00
|
|
|
fn vks_publish_shortcut_get_token<'a>(client: &'a Client, data: &[u8]) -> String {
|
|
|
|
let mut response = client.put("/")
|
|
|
|
.body(data)
|
|
|
|
.dispatch();
|
|
|
|
let response_body = response.body_string().unwrap();
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
assert!(response_body.contains("Key successfully uploaded"));
|
|
|
|
|
|
|
|
let pattern = format!("{}/upload/([^ \t\n]*)", BASE_URI);
|
|
|
|
let capture_re = regex::bytes::Regex::new(&pattern).unwrap();
|
|
|
|
let capture_content = capture_re .captures(response_body.as_bytes()).unwrap()
|
|
|
|
.get(1).unwrap().as_bytes();
|
|
|
|
String::from_utf8_lossy(capture_content).to_string()
|
|
|
|
}
|
|
|
|
|
2019-05-20 21:17:50 +00:00
|
|
|
fn vks_publish_json_get_token<'a>(client: &'a Client, data: &[u8]) -> String {
|
2019-05-23 23:01:24 +00:00
|
|
|
let mut response = client.post("/vks/v1/upload")
|
2019-05-20 21:17:50 +00:00
|
|
|
.header(ContentType::JSON)
|
|
|
|
.body(format!(r#"{{ "keytext": "{}" }}"#, base64::encode(data)))
|
|
|
|
.dispatch();
|
|
|
|
let response_body = response.body_string().unwrap();
|
2019-05-23 23:01:24 +00:00
|
|
|
let result: vks_api::json::UploadResult = serde_json::from_str(&response_body).unwrap();
|
2019-05-20 21:17:50 +00:00
|
|
|
|
|
|
|
assert_eq!(response.status(), Status::Ok);
|
|
|
|
result.token
|
|
|
|
}
|
|
|
|
|
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-25 18:04:20 +00:00
|
|
|
let response = client.post("/manage")
|
2019-04-17 10:13:45 +00:00
|
|
|
.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();
|
2019-04-25 18:04:20 +00:00
|
|
|
let response = client.post("/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
|
|
|
}
|