hagrid-keyserver--hagrid/src/web/vks_web.rs

470 lines
14 KiB
Rust
Raw Normal View History

2020-11-04 21:21:00 +00:00
use crate::Result;
2019-05-23 23:01:24 +00:00
use multipart::server::save::Entries;
use multipart::server::save::SaveResult::*;
use multipart::server::Multipart;
use rocket::http::ContentType;
use rocket::request::Form;
use rocket::Data;
2019-08-28 18:33:24 +00:00
use rocket_i18n::I18n;
2019-11-02 12:00:45 +00:00
use gettext_macros::i18n;
2019-05-23 23:01:24 +00:00
2019-09-02 20:49:02 +00:00
use crate::database::{KeyDatabase, StatefulTokens, Query, Database};
use crate::mail;
use crate::tokens;
use crate::web::{RequestOrigin, MyResponse};
use crate::rate_limiter::RateLimiter;
2019-05-23 23:01:24 +00:00
use std::io::Read;
use std::collections::HashMap;
2019-09-02 20:49:02 +00:00
use crate::web::vks;
use crate::web::vks::response::*;
2019-05-23 23:01:24 +00:00
const UPLOAD_LIMIT: u64 = 1024 * 1024; // 1 MiB.
mod forms {
#[derive(FromForm,Deserialize)]
pub struct VerifyRequest {
pub token: String,
pub address: String,
}
#[derive(Deserialize)]
pub struct UploadRequest {
pub keytext: String,
}
}
mod template {
#[derive(Serialize)]
pub struct VerifyForm {
pub token: String,
}
2019-05-23 23:01:24 +00:00
#[derive(Serialize)]
pub struct Verify {
pub key_fpr: String,
2019-05-23 23:01:24 +00:00
pub userid: String,
pub userid_link: String,
2019-05-23 23:01:24 +00:00
}
2019-06-11 14:59:27 +00:00
#[derive(Serialize)]
pub struct Search {
pub query: String,
pub fpr: String,
2019-05-23 23:01:24 +00:00
}
#[derive(Serialize)]
pub struct VerificationSent {
pub key_fpr: String,
pub key_link: String,
pub is_revoked: bool,
pub token: String,
2019-06-05 11:21:37 +00:00
pub email_published: Vec<String>,
pub email_unpublished: Vec<UploadUidStatus>,
pub count_revoked_one: bool,
pub count_revoked: usize,
pub count_unparsed_one: bool,
pub count_unparsed: usize,
2019-05-23 23:01:24 +00:00
}
#[derive(Serialize)]
pub struct UploadOkKey {
pub key_fpr: String,
pub key_link: String,
}
#[derive(Serialize)]
pub struct UploadOkMultiple {
pub keys: Vec<UploadOkKey>,
}
#[derive(Serialize)]
pub struct UploadUidStatus {
pub address: String,
pub requested: bool,
}
}
impl MyResponse {
2019-06-05 12:33:02 +00:00
fn upload_response_quick(response: UploadResponse, base_uri: &str) -> Self {
match response {
UploadResponse::Ok { token, .. } => {
2019-06-05 12:33:02 +00:00
let uri = uri!(quick_upload_proceed: token);
let text = format!(
"Key successfully uploaded. Proceed with verification here:\n{}{}\n",
2019-06-05 12:33:02 +00:00
base_uri,
uri
);
MyResponse::plain(text)
},
UploadResponse::OkMulti { key_fprs } =>
MyResponse::plain(format!("Uploaded {} keys. For verification, please upload keys individually.\n", key_fprs.len())),
2019-06-05 12:33:02 +00:00
UploadResponse::Error(error) => MyResponse::bad_request(
2020-11-04 21:21:00 +00:00
"400-plain", anyhow!(error)),
2019-06-05 12:33:02 +00:00
}
}
2019-05-23 23:01:24 +00:00
fn upload_response(response: UploadResponse) -> Self {
match response {
UploadResponse::Ok { token, key_fpr, is_revoked, count_unparsed, status, .. } =>
Self::upload_ok(token, key_fpr, is_revoked, count_unparsed, status),
2019-06-05 12:33:02 +00:00
UploadResponse::OkMulti { key_fprs } =>
Self::upload_ok_multi(key_fprs),
2019-05-23 23:01:24 +00:00
UploadResponse::Error(error) => MyResponse::bad_request(
2020-11-04 21:21:00 +00:00
"upload/upload", anyhow!(error)),
2019-05-23 23:01:24 +00:00
}
}
fn upload_ok(
token: String,
key_fpr: String,
2019-06-05 11:21:37 +00:00
is_revoked: bool,
count_unparsed: usize,
2019-05-23 23:01:24 +00:00
uid_status: HashMap<String,EmailStatus>,
) -> Self {
let key_link = uri!(search: &key_fpr).to_string();
2019-05-23 23:01:24 +00:00
2019-06-05 11:21:37 +00:00
let count_revoked = uid_status.iter()
.filter(|(_,status)| **status == EmailStatus::Revoked)
.count();
let mut email_published: Vec<_> = uid_status.iter()
.filter(|(_,status)| **status == EmailStatus::Published)
.map(|(email,_)| email.to_string())
.collect();
email_published.sort_unstable();
let mut email_unpublished: Vec<_> = uid_status.into_iter()
.filter(|(_,status)| *status == EmailStatus::Unpublished ||
*status == EmailStatus::Pending)
.map(|(email,status)|
template::UploadUidStatus {
address: email.to_string(),
requested: status == EmailStatus::Pending,
})
2019-05-23 23:01:24 +00:00
.collect();
2019-06-05 11:21:37 +00:00
email_unpublished
.sort_unstable_by(|fst,snd| fst.address.cmp(&snd.address));
2019-05-23 23:01:24 +00:00
let context = template::VerificationSent {
2019-06-05 11:21:37 +00:00
is_revoked,
key_fpr,
key_link,
token,
email_published,
email_unpublished,
count_revoked_one: count_revoked == 1,
count_revoked,
count_unparsed_one: count_unparsed == 1,
count_unparsed,
2019-05-23 23:01:24 +00:00
};
MyResponse::ok("upload/upload-ok", context)
}
fn upload_ok_multi(key_fprs: Vec<String>) -> Self {
let keys = key_fprs.into_iter()
.map(|fpr| {
let key_link = uri!(search: &fpr).to_string();
template::UploadOkKey {
key_fpr: fpr.to_owned(),
key_link,
}
2019-05-23 23:01:24 +00:00
})
.collect();
let context = template::UploadOkMultiple {
keys,
};
MyResponse::ok("upload/upload-ok-multiple", context)
}
}
#[get("/upload")]
pub fn upload() -> MyResponse {
2019-09-29 09:03:43 +00:00
MyResponse::ok_bare("upload/upload")
2019-05-23 23:01:24 +00:00
}
#[post("/upload/submit", format = "multipart/form-data", data = "<data>")]
pub fn upload_post_form_data(
db: rocket::State<KeyDatabase>,
tokens_stateless: rocket::State<tokens::Service>,
rate_limiter: rocket::State<RateLimiter>,
2019-09-28 19:54:00 +00:00
i18n: I18n,
2019-05-23 23:01:24 +00:00
cont_type: &ContentType,
data: Data,
) -> MyResponse {
2019-09-28 19:54:00 +00:00
match process_post_form_data(db, tokens_stateless, rate_limiter, i18n, cont_type, data) {
Ok(response) => MyResponse::upload_response(response),
Err(err) => MyResponse::bad_request("upload/upload", err),
}
}
pub fn process_post_form_data(
db: rocket::State<KeyDatabase>,
tokens_stateless: rocket::State<tokens::Service>,
rate_limiter: rocket::State<RateLimiter>,
2019-09-28 19:54:00 +00:00
i18n: I18n,
cont_type: &ContentType,
data: Data,
) -> Result<UploadResponse> {
2019-05-23 23:01:24 +00:00
// multipart/form-data
let (_, boundary) = cont_type
.params()
.find(|&(k, _)| k == "boundary")
2020-11-04 21:21:00 +00:00
.ok_or_else(|| anyhow!("`Content-Type: multipart/form-data` \
boundary param not provided"))?;
2019-05-23 23:01:24 +00:00
2019-09-28 19:54:00 +00:00
process_upload(&db, &tokens_stateless, &rate_limiter, &i18n, data, boundary)
2019-05-23 23:01:24 +00:00
}
2019-06-11 14:59:27 +00:00
#[get("/search?<q>")]
pub fn search(
db: rocket::State<KeyDatabase>,
q: String,
) -> MyResponse {
match q.parse::<Query>() {
2019-09-27 14:21:10 +00:00
Ok(query) => key_to_response(db, q, query),
2019-06-11 14:59:27 +00:00
Err(e) => MyResponse::bad_request("index", e),
}
}
fn key_to_response(
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,
fpr: fp.to_string(),
};
MyResponse::ok("found", context)
}
2019-06-05 12:33:02 +00:00
#[put("/", data = "<data>")]
pub fn quick_upload(
db: rocket::State<KeyDatabase>,
tokens_stateless: rocket::State<tokens::Service>,
rate_limiter: rocket::State<RateLimiter>,
2019-09-28 19:54:00 +00:00
i18n: I18n,
request_origin: RequestOrigin,
2019-06-05 12:33:02 +00:00
data: Data,
) -> MyResponse {
use std::io::Cursor;
let mut buf = Vec::default();
if let Err(error) = std::io::copy(&mut data.open().take(UPLOAD_LIMIT), &mut buf) {
2020-11-04 21:21:00 +00:00
return MyResponse::bad_request("400-plain", anyhow!(error));
2019-06-05 12:33:02 +00:00
}
2019-09-28 19:54:00 +00:00
MyResponse::upload_response_quick(
vks::process_key(
&db,
&i18n,
&tokens_stateless,
&rate_limiter,
Cursor::new(buf)
), request_origin.get_base_uri())
2019-06-05 12:33:02 +00:00
}
#[get("/upload/<token>", rank = 2)]
pub fn quick_upload_proceed(
db: rocket::State<KeyDatabase>,
request_origin: RequestOrigin,
2019-06-05 12:33:02 +00:00
token_stateful: rocket::State<StatefulTokens>,
token_stateless: rocket::State<tokens::Service>,
mail_service: rocket::State<mail::Service>,
rate_limiter: rocket::State<RateLimiter>,
2019-08-28 18:33:24 +00:00
i18n: I18n,
2019-06-05 12:33:02 +00:00
token: String,
) -> MyResponse {
let result = vks::request_verify(
db, request_origin, token_stateful, token_stateless, mail_service,
2019-08-28 18:33:24 +00:00
rate_limiter, i18n, token, vec!());
2019-06-05 12:33:02 +00:00
MyResponse::upload_response(result)
}
2019-05-23 23:01:24 +00:00
#[post("/upload/submit", format = "application/x-www-form-urlencoded", data = "<data>")]
pub fn upload_post_form(
db: rocket::State<KeyDatabase>,
tokens_stateless: rocket::State<tokens::Service>,
rate_limiter: rocket::State<RateLimiter>,
2019-09-28 19:54:00 +00:00
i18n: I18n,
2019-05-23 23:01:24 +00:00
data: Data,
) -> MyResponse {
2019-09-28 19:54:00 +00:00
match process_post_form(&db, &tokens_stateless, &rate_limiter, &i18n, data) {
Ok(response) => MyResponse::upload_response(response),
Err(err) => MyResponse::bad_request("upload/upload", err),
}
}
pub fn process_post_form(
2019-09-28 19:54:00 +00:00
db: &KeyDatabase,
tokens_stateless: &tokens::Service,
rate_limiter: &RateLimiter,
i18n: &I18n,
data: Data,
) -> Result<UploadResponse> {
2019-05-23 23:01:24 +00:00
use rocket::request::FormItems;
use std::io::Cursor;
// application/x-www-form-urlencoded
let mut buf = Vec::default();
std::io::copy(&mut data.open().take(UPLOAD_LIMIT), &mut buf)?;
for item in FormItems::from(&*String::from_utf8_lossy(&buf)) {
let (key, value) = item.key_value();
let decoded_value = value.url_decode().or_else(|_| {
2020-11-04 21:21:00 +00:00
Err(anyhow!(
2019-05-23 23:01:24 +00:00
"`Content-Type: application/x-www-form-urlencoded` \
not valid"))
})?;
match key.as_str() {
"keytext" => {
return Ok(vks::process_key(
2019-05-23 23:01:24 +00:00
&db,
2019-09-28 19:54:00 +00:00
&i18n,
2019-05-23 23:01:24 +00:00
&tokens_stateless,
&rate_limiter,
Cursor::new(decoded_value.as_bytes())
));
2019-05-23 23:01:24 +00:00
}
_ => { /* skip */ }
}
}
2020-11-04 21:21:00 +00:00
Err(anyhow!("No keytext found"))
2019-05-23 23:01:24 +00:00
}
fn process_upload(
db: &KeyDatabase,
tokens_stateless: &tokens::Service,
rate_limiter: &RateLimiter,
2019-09-28 19:54:00 +00:00
i18n: &I18n,
2019-05-23 23:01:24 +00:00
data: Data,
boundary: &str,
) -> Result<UploadResponse> {
2019-05-23 23:01:24 +00:00
// saves all fields, any field longer than 10kB goes to a temporary directory
// Entries could implement FromData though that would give zero control over
// how the files are saved; Multipart would be a good impl candidate though
match Multipart::with_body(data.open().take(UPLOAD_LIMIT), boundary).save().temp() {
Full(entries) => {
2019-09-28 19:54:00 +00:00
process_multipart(db, tokens_stateless, rate_limiter, i18n, entries)
2019-05-23 23:01:24 +00:00
}
Partial(partial, _) => {
2019-09-28 19:54:00 +00:00
process_multipart(db, tokens_stateless, rate_limiter, i18n, partial.entries)
2019-05-23 23:01:24 +00:00
}
Error(err) => Err(err.into())
}
}
fn process_multipart(
db: &KeyDatabase,
tokens_stateless: &tokens::Service,
rate_limiter: &RateLimiter,
2019-09-28 19:54:00 +00:00
i18n: &I18n,
2019-05-23 23:01:24 +00:00
entries: Entries,
) -> Result<UploadResponse> {
2019-05-23 23:01:24 +00:00
match entries.fields.get("keytext") {
Some(ent) if ent.len() == 1 => {
let reader = ent[0].data.readable()?;
2019-09-28 19:54:00 +00:00
Ok(vks::process_key(db, i18n, tokens_stateless, rate_limiter, reader))
2019-05-23 23:01:24 +00:00
}
2020-11-04 21:21:00 +00:00
Some(_) => Err(anyhow!("Multiple keytexts found")),
None => Err(anyhow!("No keytext found")),
2019-05-23 23:01:24 +00:00
}
}
#[post("/upload/request-verify", format = "application/x-www-form-urlencoded", data="<request>")]
pub fn request_verify_form(
db: rocket::State<KeyDatabase>,
request_origin: RequestOrigin,
2019-05-23 23:01:24 +00:00
token_stateful: rocket::State<StatefulTokens>,
token_stateless: rocket::State<tokens::Service>,
mail_service: rocket::State<mail::Service>,
rate_limiter: rocket::State<RateLimiter>,
2019-08-28 18:33:24 +00:00
i18n: I18n,
2019-05-23 23:01:24 +00:00
request: Form<forms::VerifyRequest>,
) -> MyResponse {
let forms::VerifyRequest { token, address } = request.into_inner();
let result = vks::request_verify(
db, request_origin, token_stateful, token_stateless, mail_service,
2019-08-28 18:33:24 +00:00
rate_limiter, i18n, token, vec!(address));
2019-05-23 23:01:24 +00:00
MyResponse::upload_response(result)
}
#[post("/upload/request-verify", format = "multipart/form-data", data="<request>")]
pub fn request_verify_form_data(
db: rocket::State<KeyDatabase>,
request_origin: RequestOrigin,
2019-05-23 23:01:24 +00:00
token_stateful: rocket::State<StatefulTokens>,
token_stateless: rocket::State<tokens::Service>,
mail_service: rocket::State<mail::Service>,
rate_limiter: rocket::State<RateLimiter>,
2019-08-28 18:33:24 +00:00
i18n: I18n,
2019-05-23 23:01:24 +00:00
request: Form<forms::VerifyRequest>,
) -> MyResponse {
let forms::VerifyRequest { token, address } = request.into_inner();
let result = vks::request_verify(
db, request_origin, token_stateful, token_stateless, mail_service,
2019-08-28 18:33:24 +00:00
rate_limiter, i18n, token, vec!(address));
2019-05-23 23:01:24 +00:00
MyResponse::upload_response(result)
}
#[post("/verify/<token>")]
2019-05-23 23:01:24 +00:00
pub fn verify_confirm(
db: rocket::State<KeyDatabase>,
token_service: rocket::State<StatefulTokens>,
rate_limiter: rocket::State<RateLimiter>,
2019-09-28 19:54:00 +00:00
i18n: I18n,
2019-05-23 23:01:24 +00:00
token: String,
) -> MyResponse {
let rate_limit_id = format!("verify-token-{}", &token);
2019-09-28 19:54:00 +00:00
match vks::verify_confirm(db, &i18n, token_service, token) {
PublishResponse::Ok { fingerprint, email } => {
rate_limiter.action_perform(rate_limit_id);
let userid_link = uri!(search: &email).to_string();
2019-05-23 23:01:24 +00:00
let context = template::Verify {
userid: email,
key_fpr: fingerprint,
userid_link,
2019-05-23 23:01:24 +00:00
};
MyResponse::ok("upload/publish-result", context)
},
PublishResponse::Error(error) => {
2019-11-02 12:00:45 +00:00
let error_msg = if rate_limiter.action_check(rate_limit_id) {
2020-11-04 21:21:00 +00:00
anyhow!(error)
} else {
2020-11-04 21:21:00 +00:00
anyhow!(i18n!(i18n.catalog, "This address has already been verified."))
2019-11-02 12:00:45 +00:00
};
MyResponse::bad_request("400", error_msg)
}
2019-05-23 23:01:24 +00:00
}
}
#[get("/verify/<token>")]
pub fn verify_confirm_form(
token: String,
) -> MyResponse {
MyResponse::ok("upload/verification-form", template::VerifyForm {
token
})
2019-09-27 14:21:10 +00:00
}