drop polymorphic database abstraction

This commit is contained in:
Vincent Breitmoser 2019-04-27 00:21:30 +02:00
parent a724a96406
commit 851ea659b6
9 changed files with 36 additions and 359 deletions

View File

@ -45,11 +45,7 @@ pub mod sync;
use sync::MutexGuard;
mod fs;
pub use self::fs::Filesystem;
mod memory;
pub use self::memory::Memory;
mod poly;
pub use self::poly::Polymorphic;
pub use self::fs::Filesystem as KeyDatabase;
mod stateful_tokens;
pub use stateful_tokens::StatefulTokens;

View File

@ -1,198 +0,0 @@
use parking_lot::Mutex;
use std::collections::HashMap;
use {Database, Query};
use types::{Email, Fingerprint, KeyID};
use sync::MutexGuard;
use Result;
#[derive(Debug)]
pub struct Memory {
update_lock: Mutex<()>,
fpr: Mutex<HashMap<Fingerprint, String>>,
fpr_links: Mutex<HashMap<Fingerprint, Fingerprint>>,
email: Mutex<HashMap<Email, Fingerprint>>,
kid: Mutex<HashMap<KeyID, Fingerprint>>,
}
impl Default for Memory {
fn default() -> Self {
Memory {
update_lock: Mutex::new(()),
fpr: Mutex::new(HashMap::default()),
fpr_links: Mutex::new(HashMap::default()),
kid: Mutex::new(HashMap::default()),
email: Mutex::new(HashMap::default()),
}
}
}
impl Database for Memory {
fn lock(&self) -> MutexGuard<()> {
self.update_lock.lock().into()
}
fn update(
&self, fpr: &Fingerprint, new: Option<String>,
) -> Result<()> {
let mut fprs = self.fpr.lock();
if let Some(new) = new {
fprs.insert(fpr.clone(), new);
} else {
fprs.remove(fpr);
}
Ok(())
}
fn lookup_primary_fingerprint(&self, term: &Query) -> Option<Fingerprint> {
use self::Query::*;
match term {
ByFingerprint(ref fp) =>
if self.fpr.lock().contains_key(fp) {
Some(fp.clone())
} else {
self.fpr_links.lock().get(fp).map(|fp| fp.clone())
},
ByKeyID(ref keyid) =>
self.kid.lock().get(keyid).map(|fp| fp.clone()),
ByEmail(ref email) =>
self.email.lock().get(email).map(|fp| fp.clone()),
}
}
fn link_fpr(&self, from: &Fingerprint, fpr: &Fingerprint) -> Result<()> {
self.fpr_links.lock().insert(from.clone(), fpr.clone());
Ok(())
}
fn unlink_fpr(&self, from: &Fingerprint, _: &Fingerprint) -> Result<()> {
self.fpr_links.lock().remove(from);
Ok(())
}
fn link_email(&self, email: &Email, fpr: &Fingerprint) -> Result<()> {
self.email.lock().insert(email.clone(), fpr.clone());
Ok(())
}
fn unlink_email(&self, email: &Email, _: &Fingerprint) -> Result<()> {
self.email.lock().remove(email);
Ok(())
}
fn link_kid(&self, kid: &KeyID, fpr: &Fingerprint) -> Result<()> {
self.kid.lock().insert(kid.clone(), fpr.clone());
Ok(())
}
fn unlink_kid(&self, kid: &KeyID, _: &Fingerprint) -> Result<()> {
self.kid.lock().remove(kid);
Ok(())
}
fn by_fpr(&self, fpr: &Fingerprint) -> Option<String> {
let fprs = self.fpr.lock();
let links = self.fpr_links.lock();
fprs.get(fpr).map(|x| x.clone()).or_else(|| {
links.get(fpr).and_then(|fpr| fprs.get(fpr).map(|x| x.clone()))
})
}
fn by_email(&self, email: &Email) -> Option<String> {
let fprs = self.fpr.lock();
let by_email = self.email.lock();
by_email.get(email).and_then(|fpr| fprs.get(fpr).map(|x| x.clone()))
}
fn by_kid(&self, kid: &KeyID) -> Option<String> {
let fprs = self.fpr.lock();
let by_kid = self.kid.lock();
by_kid.get(kid).and_then(|fpr| fprs.get(fpr).map(|x| x.clone()))
}
}
impl Memory {
pub fn new_token() -> String {
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
let mut rng = thread_rng();
// samples from [a-zA-Z0-9]
// 43 chars ~ 256 bit
rng.sample_iter(&Alphanumeric).take(43).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use test;
use openpgp::tpk::TPKBuilder;
#[test]
fn new() {
let db = Memory::default();
let k1 = TPKBuilder::default().add_userid("a@invalid.example.org")
.generate().unwrap().0;
let k2 = TPKBuilder::default().add_userid("b@invalid.example.org")
.generate().unwrap().0;
let k3 = TPKBuilder::default().add_userid("c@invalid.example.org")
.generate().unwrap().0;
assert!(db.merge_or_publish(&k1).unwrap().len() > 0);
assert!(db.merge_or_publish(&k2).unwrap().len() > 0);
assert!(!db.merge_or_publish(&k2).unwrap().len() > 0);
assert!(db.merge_or_publish(&k3).unwrap().len() > 0);
assert!(!db.merge_or_publish(&k3).unwrap().len() > 0);
assert!(!db.merge_or_publish(&k3).unwrap().len() > 0);
}
#[test]
fn uid_verification() {
let mut db = Memory::default();
test::test_uid_verification(&mut db);
}
#[test]
fn subkey_lookup() {
let mut db = Memory::default();
test::test_subkey_lookup(&mut db);
}
#[test]
fn kid_lookup() {
let mut db = Memory::default();
test::test_kid_lookup(&mut db);
}
#[test]
fn uid_revocation() {
let mut db = Memory::default();
test::test_uid_revocation(&mut db);
}
#[test]
fn test_same_email_1() {
let mut db = Memory::default();
test::test_same_email_1(&mut db);
}
#[test]
fn test_same_email_2() {
let mut db = Memory::default();
test::test_same_email_2(&mut db);
}
}

View File

@ -1,115 +0,0 @@
use std::path::PathBuf;
use {Database, Filesystem, Memory, Query};
use Result;
use types::{Email, Fingerprint, KeyID};
use sync::MutexGuard;
pub enum Polymorphic {
Memory(Memory),
Filesystem(Filesystem),
}
impl Database for Polymorphic {
fn lock(&self) -> MutexGuard<()> {
match self {
&Polymorphic::Memory(ref db) => db.lock(),
&Polymorphic::Filesystem(ref db) => db.lock(),
}
}
fn update(
&self, fpr: &Fingerprint, new: Option<String>,
) -> Result<()> {
match self {
&Polymorphic::Memory(ref db) => {
db.update(fpr, new)
}
&Polymorphic::Filesystem(ref db) => {
db.update(fpr, new)
}
}
}
fn lookup_primary_fingerprint(&self, term: &Query) -> Option<Fingerprint> {
match self {
&Polymorphic::Memory(ref db) =>
db.lookup_primary_fingerprint(term),
&Polymorphic::Filesystem(ref db) =>
db.lookup_primary_fingerprint(term),
}
}
/// Gets the path to the underlying file, if any.
fn lookup_path(&self, term: &Query) -> Option<PathBuf> {
match self {
&Polymorphic::Memory(ref db) =>
db.lookup_path(term),
&Polymorphic::Filesystem(ref db) =>
db.lookup_path(term),
}
}
fn link_fpr(&self, from: &Fingerprint, fpr: &Fingerprint) -> Result<()> {
match self {
&Polymorphic::Memory(ref db) => db.link_fpr(from, fpr),
&Polymorphic::Filesystem(ref db) => db.link_fpr(from, fpr),
}
}
fn unlink_fpr(&self, from: &Fingerprint, fpr: &Fingerprint) -> Result<()> {
match self {
&Polymorphic::Memory(ref db) => db.unlink_fpr(from, fpr),
&Polymorphic::Filesystem(ref db) => db.unlink_fpr(from, fpr),
}
}
fn link_kid(&self, kid: &KeyID, fpr: &Fingerprint) -> Result<()> {
match self {
&Polymorphic::Memory(ref db) => db.link_kid(kid, fpr),
&Polymorphic::Filesystem(ref db) => db.link_kid(kid, fpr),
}
}
fn unlink_kid(&self, kid: &KeyID, fpr: &Fingerprint) -> Result<()> {
match self {
&Polymorphic::Memory(ref db) => db.unlink_kid(kid, fpr),
&Polymorphic::Filesystem(ref db) => db.unlink_kid(kid, fpr),
}
}
fn link_email(&self, email: &Email, fpr: &Fingerprint) -> Result<()> {
match self {
&Polymorphic::Memory(ref db) => db.link_email(email, fpr),
&Polymorphic::Filesystem(ref db) => db.link_email(email, fpr),
}
}
fn unlink_email(&self, email: &Email, fpr: &Fingerprint) -> Result<()> {
match self {
&Polymorphic::Memory(ref db) => db.unlink_email(email, fpr),
&Polymorphic::Filesystem(ref db) => db.unlink_email(email, fpr),
}
}
fn by_fpr(&self, fpr: &Fingerprint) -> Option<String> {
match self {
&Polymorphic::Memory(ref db) => db.by_fpr(fpr),
&Polymorphic::Filesystem(ref db) => db.by_fpr(fpr),
}
}
fn by_email(&self, email: &Email) -> Option<String> {
match self {
&Polymorphic::Memory(ref db) => db.by_email(email),
&Polymorphic::Filesystem(ref db) => db.by_email(email),
}
}
fn by_kid(&self, kid: &KeyID) -> Option<String> {
match self {
&Polymorphic::Memory(ref db) => db.by_kid(kid),
&Polymorphic::Filesystem(ref db) => db.by_kid(kid),
}
}
}

View File

@ -11,7 +11,7 @@ extern crate structopt;
use structopt::StructOpt;
extern crate hagrid_database as database;
use database::{Query, Database, Filesystem};
use database::{Query, Database, KeyDatabase};
#[derive(Debug, StructOpt)]
#[structopt(
@ -51,11 +51,11 @@ fn main() {
fn real_main() -> Result<()> {
let opt = Opt::from_args();
let db = Filesystem::new_from_base(opt.base.canonicalize()?)?;
let db = KeyDatabase::new_from_base(opt.base.canonicalize()?)?;
delete(&db, &opt.query.parse()?, opt.all_bindings, opt.all)
}
fn delete(db: &Filesystem, query: &Query, all_bindings: bool, mut all: bool)
fn delete(db: &KeyDatabase, query: &Query, all_bindings: bool, mut all: bool)
-> Result<()> {
match query {
Query::ByFingerprint(_) | Query::ByKeyID(_) => {

View File

@ -25,7 +25,7 @@ use openpgp::Packet;
use openpgp::parse::{PacketParser, PacketParserResult, Parse};
extern crate hagrid_database as database;
use database::{Database, Filesystem};
use database::{Database, KeyDatabase};
fn main() {
let args: Vec<String> = env::args().collect();
@ -56,7 +56,7 @@ fn main() {
}
fn do_import(base: PathBuf, keyrings: Vec<PathBuf>) -> Result<()> {
let db = Filesystem::new_from_base(base)?;
let db = KeyDatabase::new_from_base(base)?;
// For each input file, create a parser.
for input in keyrings.iter() {
@ -110,7 +110,7 @@ mod import_tests {
fn import() {
let root = tempdir().unwrap();
let db = Filesystem::new_from_base(root.path().to_path_buf()).unwrap();
let db = KeyDatabase::new_from_base(root.path().to_path_buf()).unwrap();
// Generate a key and import it.
let (tpk, _) = openpgp::tpk::TPKBuilder::autocrypt(

View File

@ -6,7 +6,7 @@ use rocket::http::{ContentType, Status};
use rocket::request::{self, Request, FromRequest};
use rocket::http::uri::Uri;
use database::{Database, Query, Polymorphic};
use database::{Database, Query, KeyDatabase};
use database::types::{Email, Fingerprint, KeyID};
use web::{
@ -109,7 +109,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for Hkp {
#[post("/pks/add", data = "<data>")]
pub fn pks_add(
db: rocket::State<Polymorphic>,
db: rocket::State<KeyDatabase>,
cont_type: &ContentType,
data: Data,
) -> MyResponse {
@ -121,7 +121,7 @@ pub fn pks_add(
#[get("/pks/lookup")]
pub fn pks_lookup(state: rocket::State<HagridState>,
db: rocket::State<Polymorphic>,
db: rocket::State<KeyDatabase>,
key: Hkp) -> MyResponse {
let query_string = key.to_string();
let (query, index, machine_readable) = match key {
@ -144,7 +144,7 @@ pub fn pks_lookup(state: rocket::State<HagridState>,
}
}
fn key_to_hkp_index<'a>(db: rocket::State<Polymorphic>, query: Query)
fn key_to_hkp_index<'a>(db: rocket::State<KeyDatabase>, query: Query)
-> MyResponse {
use sequoia_openpgp::RevocationStatus;

View File

@ -5,7 +5,7 @@ use rocket::request::Form;
use failure::Fallible as Result;
use web::{HagridState, MyResponse, templates::General};
use database::{Database, Polymorphic, types::Email};
use database::{Database, KeyDatabase, types::Email};
use mail;
use tokens;
@ -54,7 +54,7 @@ pub fn vks_manage() -> Result<MyResponse> {
#[get("/manage/<token>")]
pub fn vks_manage_key(
state: rocket::State<HagridState>,
db: State<Polymorphic>,
db: State<KeyDatabase>,
token: String,
token_service: rocket::State<tokens::Service>,
) -> MyResponse {
@ -100,7 +100,7 @@ pub fn vks_manage_key(
#[post("/manage", data="<request>")]
pub fn vks_manage_post(
db: State<Polymorphic>,
db: State<KeyDatabase>,
request: Form<forms::ManageRequest>,
token_service: rocket::State<tokens::Service>,
mail_service: Option<rocket::State<mail::Service>>,
@ -148,7 +148,7 @@ pub fn vks_manage_post(
#[post("/manage/unpublish", data="<request>")]
pub fn vks_manage_unpublish(
state: rocket::State<HagridState>,
db: rocket::State<Polymorphic>,
db: rocket::State<KeyDatabase>,
token_service: rocket::State<tokens::Service>,
request: Form<forms::ManageDelete>,
) -> MyResponse {
@ -160,7 +160,7 @@ pub fn vks_manage_unpublish(
pub fn vks_manage_unpublish_or_fail(
state: rocket::State<HagridState>,
db: rocket::State<Polymorphic>,
db: rocket::State<KeyDatabase>,
token_service: rocket::State<tokens::Service>,
request: Form<forms::ManageDelete>,
) -> Result<MyResponse> {

View File

@ -13,7 +13,7 @@ pub mod upload;
use mail;
use tokens;
use database::{Database, Polymorphic, Query, StatefulTokens};
use database::{Database, KeyDatabase, Query, StatefulTokens};
use database::types::{Email, Fingerprint, KeyID};
use Result;
@ -179,7 +179,7 @@ pub struct HagridState {
}
fn key_to_response<'a>(state: rocket::State<HagridState>,
db: rocket::State<Polymorphic>,
db: rocket::State<KeyDatabase>,
query_string: String,
query: Query,
machine_readable: bool)
@ -225,7 +225,7 @@ fn key_to_response<'a>(state: rocket::State<HagridState>,
MyResponse::ok("found", context)
}
fn key_has_uids(state: &HagridState, db: &Polymorphic, query: &Query)
fn key_has_uids(state: &HagridState, db: &KeyDatabase, query: &Query)
-> Result<bool> {
use sequoia_openpgp::Packet;
use sequoia_openpgp::parse::{Parse, PacketParser, PacketParserResult};
@ -250,7 +250,7 @@ pub fn get_link_by_fingerprint(fpr: &Fingerprint) -> String {
#[get("/vks/v1/by-fingerprint/<fpr>")]
fn vks_v1_by_fingerprint(state: rocket::State<HagridState>,
db: rocket::State<Polymorphic>,
db: rocket::State<KeyDatabase>,
fpr: String) -> MyResponse {
let query = match Fingerprint::from_str(&fpr) {
Ok(fpr) => Query::ByFingerprint(fpr),
@ -262,7 +262,7 @@ fn vks_v1_by_fingerprint(state: rocket::State<HagridState>,
#[get("/vks/v1/by-email/<email>")]
fn vks_v1_by_email(state: rocket::State<HagridState>,
db: rocket::State<Polymorphic>,
db: rocket::State<KeyDatabase>,
email: String) -> MyResponse {
let query = match Email::from_str(&email) {
Ok(email) => Query::ByEmail(email),
@ -274,7 +274,7 @@ fn vks_v1_by_email(state: rocket::State<HagridState>,
#[get("/vks/v1/by-keyid/<kid>")]
fn vks_v1_by_keyid(state: rocket::State<HagridState>,
db: rocket::State<Polymorphic>,
db: rocket::State<KeyDatabase>,
kid: String) -> MyResponse {
let query = match KeyID::from_str(&kid) {
Ok(keyid) => Query::ByKeyID(keyid),
@ -286,7 +286,7 @@ fn vks_v1_by_keyid(state: rocket::State<HagridState>,
#[get("/publish/<token>")]
fn publish_verify(
db: rocket::State<Polymorphic>,
db: rocket::State<KeyDatabase>,
token_service: rocket::State<StatefulTokens>,
token: String,
) -> MyResponse {
@ -297,7 +297,7 @@ fn publish_verify(
}
fn publish_verify_or_fail(
db: rocket::State<Polymorphic>,
db: rocket::State<KeyDatabase>,
token_service: rocket::State<StatefulTokens>,
token: String,
) -> Result<MyResponse> {
@ -391,14 +391,12 @@ fn rocket_factory(rocket: rocket::Rocket) -> Result<rocket::Rocket> {
)
}
fn configure_db_service(config: &Config) -> Result<Polymorphic> {
use database::{Filesystem, Polymorphic};
fn configure_db_service(config: &Config) -> Result<KeyDatabase> {
let keys_dir: PathBuf = config.get_str("keys_dir")?.into();
let tmp_dir: PathBuf = config.get_str("tmp_dir")?.into();
let fs_db = Filesystem::new(keys_dir, tmp_dir)?;
Ok(Polymorphic::Filesystem(fs_db))
let fs_db = KeyDatabase::new(keys_dir, tmp_dir)?;
Ok(fs_db)
}
fn configure_hagrid_state(config: &Config) -> Result<HagridState> {
@ -521,12 +519,8 @@ pub mod tests {
}
pub fn assert_consistency(rocket: &rocket::Rocket) {
let db = rocket.state::<Polymorphic>().unwrap();
if let Polymorphic::Filesystem(fs) = db {
fs.check_consistency().unwrap();
} else {
unreachable!();
}
let db = rocket.state::<KeyDatabase>().unwrap();
db.check_consistency().unwrap();
}
#[test]

View File

@ -8,7 +8,7 @@ use multipart::server::Multipart;
use rocket::http::ContentType;
use rocket::Data;
use database::{Database, Polymorphic, StatefulTokens};
use database::{Database, KeyDatabase, StatefulTokens};
use mail;
use web::MyResponse;
@ -45,7 +45,7 @@ pub fn publish(guide: bool) -> MyResponse {
#[post("/vks/v1/publish", data = "<data>")]
pub fn vks_v1_publish_post(
db: rocket::State<Polymorphic>,
db: rocket::State<KeyDatabase>,
mail_service: rocket::State<mail::Service>,
token_service: rocket::State<StatefulTokens>,
cont_type: &ContentType,
@ -57,7 +57,7 @@ pub fn vks_v1_publish_post(
}
}
pub fn handle_upload_without_verify(
db: rocket::State<Polymorphic>,
db: rocket::State<KeyDatabase>,
cont_type: &ContentType,
data: Data,
) -> Result<MyResponse> {
@ -66,7 +66,7 @@ pub fn handle_upload_without_verify(
// signature requires the request to have a `Content-Type`
pub fn handle_upload(
db: rocket::State<Polymorphic>, cont_type: &ContentType, data: Data,
db: rocket::State<KeyDatabase>, cont_type: &ContentType, data: Data,
services: Option<(rocket::State<mail::Service>, rocket::State<StatefulTokens>)>,
) -> Result<MyResponse> {
if cont_type.is_form_data() {
@ -119,7 +119,7 @@ pub fn handle_upload(
}
fn process_upload(
boundary: &str, data: Data, db: &Polymorphic,
boundary: &str, data: Data, db: &KeyDatabase,
services: Option<(rocket::State<mail::Service>, rocket::State<StatefulTokens>)>,
) -> Result<MyResponse> {
// saves all fields, any field longer than 10kB goes to a temporary directory
@ -137,7 +137,7 @@ fn process_upload(
}
fn process_multipart(
entries: Entries, db: &Polymorphic,
entries: Entries, db: &KeyDatabase,
services: Option<(rocket::State<mail::Service>, rocket::State<StatefulTokens>)>,
) -> Result<MyResponse> {
match entries.fields.get("keytext") {
@ -156,7 +156,7 @@ fn process_multipart(
fn process_key<R>(
reader: R,
db: &Polymorphic,
db: &KeyDatabase,
services: Option<(rocket::State<mail::Service>, rocket::State<StatefulTokens>)>,
) -> Result<MyResponse>
where