split human-readable interface and hkp

This commit is contained in:
Vincent Breitmoser 2019-06-11 16:59:27 +02:00
parent 05e1029037
commit f8df6ed1e7
No known key found for this signature in database
GPG Key ID: 7BD18320DEADFA11
7 changed files with 162 additions and 126 deletions

View File

@ -64,6 +64,10 @@ impl FromStr for Query {
fn from_str(term: &str) -> Result<Self> {
use self::Query::*;
if term.starts_with("0x") && term.len() < 16 && !term.contains('@') {
return Err(failure::err_msg(
"Search by Short Key ID is not supported, sorry!"));
}
if let Ok(fp) = Fingerprint::from_str(term) {
Ok(ByFingerprint(fp))
} else if let Ok(keyid) = KeyID::from_str(term) {
@ -71,7 +75,7 @@ impl FromStr for Query {
} else if let Ok(email) = Email::from_str(term) {
Ok(ByEmail(email))
} else {
Err(failure::err_msg("Malformed query"))
Err(failure::err_msg("Invalid search query!"))
}
}
}

View File

@ -1,8 +1,7 @@
<div class="row">
<form action="/pks/lookup" method=GET>
<form action="/search" method=GET>
<div class="search">
<input type="hidden" id="op" name="op" value="get">
<input type="text" class="searchTerm" id="search" name="search" autofocus placeholder="Search by Email Address / Key ID / Fingerprint">
<input type="text" class="searchTerm" id="search" name="q" autofocus placeholder="Search by Email Address / Key ID / Fingerprint">
<button type="submit" class="searchButton button">
<img src="/assets/search.svg" style="width: 1em; padding-bottom: 4px;"> Search
</button>

View File

@ -102,6 +102,10 @@ location /verify {
proxy_pass http://127.0.0.1:8080;
}
location /search {
proxy_pass http://127.0.0.1:8080;
}
location /upload {
proxy_pass http://127.0.0.1:8080;
}

View File

@ -13,19 +13,19 @@ use rate_limiter::RateLimiter;
use tokens;
use web;
use web::{
HagridState,
MyResponse,
key_to_response,
vks_web,
};
#[derive(Debug)]
pub enum Hkp {
Fingerprint { fpr: Fingerprint, index: bool, machine_readable: bool },
KeyID { keyid: KeyID, index: bool, machine_readable: bool },
ShortKeyID { query: String, index: bool, machine_readable: bool },
Email { email: Email, index: bool, machine_readable: bool },
Fingerprint { fpr: Fingerprint, index: bool },
KeyID { keyid: KeyID, index: bool },
ShortKeyID { query: String, index: bool },
Email { email: Email, index: bool },
Invalid { query: String, },
}
@ -67,9 +67,6 @@ impl<'a, 'r> FromRequest<'a, 'r> for Hkp {
.unwrap_or(false)
{
let index = fields.get("op").map(|x| x == "index").unwrap_or(false);
let machine_readable =
fields.get("options").map(|x| x.contains("mr"))
.unwrap_or(false);
let search = fields.get("search").cloned().unwrap_or_default();
let maybe_fpr = Fingerprint::from_str(&search);
let maybe_keyid = KeyID::from_str(&search);
@ -78,19 +75,16 @@ impl<'a, 'r> FromRequest<'a, 'r> for Hkp {
Outcome::Success(Hkp::ShortKeyID {
query: search,
index: index,
machine_readable: machine_readable,
})
} else if let Ok(fpr) = maybe_fpr {
Outcome::Success(Hkp::Fingerprint {
fpr: fpr,
index: index,
machine_readable: machine_readable,
})
} else if let Ok(keyid) = maybe_keyid {
Outcome::Success(Hkp::KeyID {
keyid: keyid,
index: index,
machine_readable: machine_readable,
})
} else {
match Email::from_str(&search) {
@ -98,7 +92,6 @@ impl<'a, 'r> FromRequest<'a, 'r> for Hkp {
Outcome::Success(Hkp::Email {
email: email,
index: index,
machine_readable: machine_readable,
})
}
Err(_) => {
@ -150,39 +143,36 @@ pub fn pks_add_form(
pub fn pks_lookup(state: rocket::State<HagridState>,
db: rocket::State<KeyDatabase>,
key: Hkp) -> MyResponse {
let query_string = key.to_string();
let (query, index, machine_readable) = match key {
Hkp::Fingerprint { fpr, index, machine_readable } =>
(Query::ByFingerprint(fpr), index, machine_readable),
Hkp::KeyID { keyid, index, machine_readable } =>
(Query::ByKeyID(keyid), index, machine_readable),
Hkp::Email { email, index, machine_readable } => {
(Query::ByEmail(email), index, machine_readable)
let (query, index) = match key {
Hkp::Fingerprint { fpr, index } =>
(Query::ByFingerprint(fpr), index),
Hkp::KeyID { keyid, index } =>
(Query::ByKeyID(keyid), index),
Hkp::Email { email, index } => {
(Query::ByEmail(email), index)
}
Hkp::ShortKeyID { query: _, .. } => {
return MyResponse::not_found(None, Some(
"Search by short key ids is not supported, sorry!".to_owned()));
return MyResponse::bad_request_plain("Search by short key ids is not supported, sorry!");
}
Hkp::Invalid { query: _ } => {
return MyResponse::not_found(None, Some(
"Invalid search query!".to_owned()));
return MyResponse::bad_request_plain("Invalid search query!");
}
};
if index {
key_to_hkp_index(db, query)
} else {
key_to_response(state, db, query_string, query, machine_readable)
web::key_to_response_plain(state, db, query)
}
}
fn key_to_hkp_index<'a>(db: rocket::State<KeyDatabase>, query: Query)
fn key_to_hkp_index(db: rocket::State<KeyDatabase>, query: Query)
-> MyResponse {
use sequoia_openpgp::RevocationStatus;
let tpk = match db.lookup(&query) {
Ok(Some(tpk)) => tpk,
Ok(None) => return MyResponse::not_found(None, None),
Ok(None) => return MyResponse::not_found_plain(query.describe_error()),
Err(err) => { return MyResponse::ise(err); }
};
let mut out = String::default();

View File

@ -15,10 +15,9 @@ use tokens;
use rate_limiter::RateLimiter;
use database::{Database, KeyDatabase, Query};
use database::types::{Email, Fingerprint, KeyID};
use database::types::Fingerprint;
use Result;
use std::str::FromStr;
use std::convert::TryInto;
mod hkp;
@ -46,8 +45,12 @@ pub enum MyResponse {
ServerError(Template),
#[response(status = 404, content_type = "html")]
NotFound(Template),
#[response(status = 404, content_type = "html")]
NotFoundPlain(String),
#[response(status = 400, content_type = "html")]
BadRequest(Template),
#[response(status = 400, content_type = "html")]
BadRequestPlain(String),
}
impl MyResponse {
@ -103,7 +106,6 @@ impl MyResponse {
}
pub fn bad_request(template: &'static str, e: failure::Error) -> Self {
eprintln!("Bad Request: {:?}", e);
let ctx = templates::General {
error: Some(format!("{}", e)),
version: env!("VERGEN_SEMVER").to_string(),
@ -112,10 +114,18 @@ impl MyResponse {
MyResponse::BadRequest(Template::render(template, ctx))
}
pub fn not_found<M>(tmpl: Option<&'static str>, message: M)
-> Self
where M: Into<Option<String>>,
{
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>,
message: impl Into<Option<String>>
) -> Self {
MyResponse::NotFound(
Template::render(
tmpl.unwrap_or("index"),
@ -126,15 +136,6 @@ impl MyResponse {
}
mod templates {
#[derive(Serialize)]
pub struct Search {
pub query: String,
pub fpr: String,
pub base_uri: String,
pub commit: String,
pub version: String,
}
#[derive(Serialize)]
pub struct FiveHundred {
pub internal_error: String,
@ -196,85 +197,35 @@ pub struct HagridState {
///
x_accel_redirect: bool,
x_accel_prefix: Option<PathBuf>,
}
fn key_to_response<'a>(state: rocket::State<HagridState>,
db: rocket::State<KeyDatabase>,
query_string: String,
query: Query,
machine_readable: bool)
-> MyResponse {
pub fn key_to_response_plain(
state: rocket::State<HagridState>,
db: rocket::State<KeyDatabase>,
query: Query,
) -> MyResponse {
let fp = if let Some(fp) = db.lookup_primary_fingerprint(&query) {
fp
} else {
return MyResponse::not_found(None, query.describe_error());
return MyResponse::not_found_plain(query.describe_error());
};
if machine_readable {
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();
}
// 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);
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();
}
}
return match db.by_fpr(&fp) {
Some(armored) => MyResponse::key(armored, &fp.into()),
None => MyResponse::not_found(None, None),
// 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);
}
}
let context = templates::Search{
query: query_string,
base_uri: state.base_uri.clone(),
fpr: fp.to_string(),
version: env!("VERGEN_SEMVER").to_string(),
commit: env!("VERGEN_SHA_SHORT").to_string(),
};
MyResponse::ok("found", context)
}
#[get("/vks/v1/by-fingerprint/<fpr>")]
fn vks_v1_by_fingerprint(state: rocket::State<HagridState>,
db: rocket::State<KeyDatabase>,
fpr: String) -> MyResponse {
let query = match Fingerprint::from_str(&fpr) {
Ok(fpr) => Query::ByFingerprint(fpr),
Err(e) => return MyResponse::bad_request("index", e),
};
key_to_response(state, db, fpr, query, true)
}
#[get("/vks/v1/by-email/<email>")]
fn vks_v1_by_email(state: rocket::State<HagridState>,
db: rocket::State<KeyDatabase>,
email: String) -> MyResponse {
let query = match Email::from_str(&email) {
Ok(email) => Query::ByEmail(email),
Err(e) => return MyResponse::bad_request("index", e),
};
key_to_response(state, db, email, query, true)
}
#[get("/vks/v1/by-keyid/<kid>")]
fn vks_v1_by_keyid(state: rocket::State<HagridState>,
db: rocket::State<KeyDatabase>,
kid: String) -> MyResponse {
let query = match KeyID::from_str(&kid) {
Ok(keyid) => Query::ByKeyID(keyid),
Err(e) => return MyResponse::bad_request("index", e),
};
key_to_response(state, db, kid, query, true)
return match db.by_fpr(&fp) {
Some(armored) => MyResponse::key(armored, &fp.into()),
None => MyResponse::not_found_plain(query.describe_error()),
}
}
#[get("/assets/<file..>")]
@ -327,14 +278,15 @@ fn rocket_factory(rocket: rocket::Rocket) -> Result<rocket::Rocket> {
usage,
files,
// VKSv1
vks_v1_by_email,
vks_v1_by_fingerprint,
vks_v1_by_keyid,
vks_api::vks_v1_by_email,
vks_api::vks_v1_by_fingerprint,
vks_api::vks_v1_by_keyid,
vks_api::upload_json,
vks_api::upload_fallback,
vks_api::request_verify_json,
vks_api::request_verify_fallback,
// User interaction.
vks_web::search,
vks_web::upload,
vks_web::upload_post_form,
vks_web::upload_post_form_data,
@ -805,7 +757,7 @@ pub mod tests {
&tpk, nr_uids);
check_hr_response(
&client,
&format!("/pks/lookup?op=get&search={}", addr),
&format!("/search?q={}", addr),
&tpk, nr_uids);
}
@ -855,6 +807,10 @@ pub mod tests {
&client,
&format!("/pks/lookup?op=get&options=mr&search=0x{}", keyid),
&tpk, nr_uids);
check_mr_response(
&client,
&format!("/pks/lookup?op=get&search=0x{}", keyid),
&tpk, nr_uids);
}
/// Asserts that the given URI returns human readable response
@ -889,19 +845,19 @@ pub mod tests {
check_hr_response(
&client,
&format!("/pks/lookup?op=get&search={}", fp),
&format!("/search?q={}", fp),
&tpk, nr_uids);
check_hr_response(
&client,
&format!("/pks/lookup?op=get&search=0x{}", fp),
&format!("/search?q=0x{}", fp),
&tpk, nr_uids);
check_hr_response(
&client,
&format!("/pks/lookup?op=get&search={}", keyid),
&format!("/search?q={}", keyid),
&tpk, nr_uids);
check_hr_response(
&client,
&format!("/pks/lookup?op=get&search=0x{}", keyid),
&format!("/search?q=0x{}", keyid),
&tpk, nr_uids);
}

View File

@ -4,12 +4,14 @@ use rocket::response::{self, Response, Responder};
use rocket::http::{ContentType,Status};
use std::io::Cursor;
use database::{KeyDatabase, StatefulTokens};
use database::{KeyDatabase, StatefulTokens, Query};
use database::types::{Email, Fingerprint, KeyID};
use mail;
use tokens;
use rate_limiter::RateLimiter;
use web::HagridState;
use web;
use web::{HagridState, MyResponse};
use web::vks;
use web::vks::response::*;
@ -115,3 +117,39 @@ pub fn request_verify_fallback(
let error_msg = format!("expected application/json data. see {}/about/api for api docs.", state.base_uri);
JsonErrorResponse(Status::BadRequest, error_msg)
}
#[get("/vks/v1/by-fingerprint/<fpr>")]
pub fn vks_v1_by_fingerprint(state: rocket::State<HagridState>,
db: rocket::State<KeyDatabase>,
fpr: String) -> MyResponse {
let query = match fpr.parse::<Fingerprint>() {
Ok(fpr) => Query::ByFingerprint(fpr),
Err(e) => return MyResponse::bad_request("index", e),
};
web::key_to_response_plain(state, db, query)
}
#[get("/vks/v1/by-email/<email>")]
pub fn vks_v1_by_email(state: rocket::State<HagridState>,
db: rocket::State<KeyDatabase>,
email: String) -> MyResponse {
let query = match email.parse::<Email>() {
Ok(email) => Query::ByEmail(email),
Err(e) => return MyResponse::bad_request("index", e),
};
web::key_to_response_plain(state, db, query)
}
#[get("/vks/v1/by-keyid/<kid>")]
pub fn vks_v1_by_keyid(state: rocket::State<HagridState>,
db: rocket::State<KeyDatabase>,
kid: String) -> MyResponse {
let query = match kid.parse::<KeyID>() {
Ok(keyid) => Query::ByKeyID(keyid),
Err(e) => return MyResponse::bad_request("index", e),
};
web::key_to_response_plain(state, db, query)
}

View File

@ -9,7 +9,7 @@ use rocket::http::ContentType;
use rocket::request::Form;
use rocket::Data;
use database::{KeyDatabase, StatefulTokens};
use database::{KeyDatabase, StatefulTokens, Query, Database};
use mail;
use tokens;
use web::{HagridState,MyResponse};
@ -45,6 +45,15 @@ mod template {
pub version: String,
}
#[derive(Serialize)]
pub struct Search {
pub query: String,
pub fpr: String,
pub base_uri: String,
pub commit: String,
pub version: String,
}
#[derive(Serialize)]
pub struct Upload {
pub commit: String,
@ -215,6 +224,42 @@ pub fn upload_post_form_data(
process_upload(&db, &tokens_stateless, &rate_limiter, data, boundary)
}
#[get("/search?<q>")]
pub fn search(
state: rocket::State<HagridState>,
db: rocket::State<KeyDatabase>,
q: String,
) -> MyResponse {
match q.parse::<Query>() {
Ok(query) => key_to_response(state, db, q, query),
Err(e) => MyResponse::bad_request("index", e),
}
}
fn key_to_response(
state: rocket::State<HagridState>,
db: rocket::State<KeyDatabase>,
query_string: String,
query: Query,
) -> MyResponse {
let fp = if let Some(fp) = db.lookup_primary_fingerprint(&query) {
fp
} else {
return MyResponse::not_found(None, query.describe_error());
};
let context = template::Search{
query: query_string,
base_uri: state.base_uri.clone(),
fpr: fp.to_string(),
version: env!("VERGEN_SEMVER").to_string(),
commit: env!("VERGEN_SHA_SHORT").to_string(),
};
MyResponse::ok("found", context)
}
#[put("/", data = "<data>")]
pub fn quick_upload(
db: rocket::State<KeyDatabase>,