split human-readable interface and hkp
This commit is contained in:
parent
05e1029037
commit
f8df6ed1e7
|
@ -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!"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
140
src/web/mod.rs
140
src/web/mod.rs
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
|
|
Loading…
Reference in New Issue