hyaenidae/profiles/src/apub/mod.rs

408 lines
11 KiB
Rust
Raw Normal View History

2021-01-04 17:34:31 +00:00
use activitystreams::base::AnyBase;
use sled::{Db, Tree};
use std::{fmt, sync::Arc};
use url::Url;
use uuid::Uuid;
2021-01-04 17:41:34 +00:00
mod actions;
2021-01-04 17:34:31 +00:00
mod keys;
2021-01-04 17:41:34 +00:00
pub(crate) use actions::ingest;
use keys::ExtendedPerson;
2021-01-04 17:34:31 +00:00
pub trait ApubIds {
fn gen_id(&self) -> Url;
fn public_key(&self, id: &Url) -> Option<Url>;
fn following(&self, id: &Url) -> Option<Url>;
fn followers(&self, id: &Url) -> Option<Url>;
fn inbox(&self, id: &Url) -> Option<Url>;
fn outbox(&self, id: &Url) -> Option<Url>;
fn shared_inbox(&self) -> Url;
}
#[derive(Debug, thiserror::Error)]
pub enum StoreError {
#[error("{0}")]
Sled(#[from] sled::Error),
#[error("{0}")]
Transaction(#[from] sled::transaction::TransactionError),
#[error("{0}")]
Json(#[from] serde_json::Error),
#[error("Error generating keys")]
Key,
}
#[derive(Clone)]
pub struct Store {
apub_id: Tree,
id_apub: Tree,
deleted: Tree,
objects: Tree,
seen: Tree,
keys: Tree,
profile_key: Tree,
endpoints: Tree,
info: Arc<dyn ApubIds + Send + Sync>,
}
#[derive(Clone, Debug)]
pub enum StoredRecords {
Profile(Uuid),
Submission(Uuid),
Comment(Uuid),
React(Uuid),
File(Uuid),
Block(Uuid),
Follow(Uuid),
FollowRequest(Uuid),
}
fn url_from_ivec(ivec: sled::IVec) -> Option<Url> {
String::from_utf8_lossy(&ivec).parse::<Url>().ok()
}
fn record_from_ivec(ivec: sled::IVec) -> Option<StoredRecords> {
StoredRecords::from_slice(&ivec).ok()
}
impl StoredRecords {
fn from_slice(slice: &[u8]) -> Result<Self, StoredRecordsError> {
String::from_utf8_lossy(slice).parse::<Self>()
}
pub fn profile(&self) -> Option<Uuid> {
match self {
Self::Profile(id) => Some(*id),
_ => None,
}
}
pub fn submission(&self) -> Option<Uuid> {
match self {
Self::Submission(id) => Some(*id),
_ => None,
}
}
pub fn comment(&self) -> Option<Uuid> {
match self {
Self::Comment(id) => Some(*id),
_ => None,
}
}
pub fn react(&self) -> Option<Uuid> {
match self {
Self::React(id) => Some(*id),
_ => None,
}
}
pub fn file(&self) -> Option<Uuid> {
match self {
Self::File(id) => Some(*id),
_ => None,
}
}
pub fn block(&self) -> Option<Uuid> {
match self {
Self::Block(id) => Some(*id),
_ => None,
}
}
pub fn follow(&self) -> Option<Uuid> {
match self {
Self::Follow(id) => Some(*id),
_ => None,
}
}
pub fn follow_request(&self) -> Option<Uuid> {
match self {
Self::FollowRequest(id) => Some(*id),
_ => None,
}
}
}
#[derive(Debug, thiserror::Error)]
#[error("Error generating key")]
struct KeyError;
#[derive(serde::Deserialize, serde::Serialize)]
struct Keys {
public: String,
private: Option<String>,
}
#[derive(serde::Deserialize, serde::Serialize)]
struct Endpoints {
inbox: Url,
outbox: Url,
following: Url,
followers: Url,
shared_inbox: Url,
public_key: Url,
}
impl Store {
pub(crate) fn build(
ids: impl ApubIds + Send + Sync + 'static,
db: &Db,
) -> Result<Self, sled::Error> {
Ok(Store {
id_apub: db.open_tree("/profiles/apub/id-apub")?,
apub_id: db.open_tree("/profiles/apub/apub-id")?,
deleted: db.open_tree("/profiles/apub/deleted")?,
objects: db.open_tree("/profiles/apub/objects")?,
seen: db.open_tree("/profiles/apub/seen")?,
keys: db.open_tree("/profiles/apub/keys")?,
profile_key: db.open_tree("/profiles/apub/profile/key")?,
endpoints: db.open_tree("/profiles/apub/endpoints")?,
info: Arc::new(ids),
})
}
pub(crate) fn key_for_profile(&self, profile_id: Uuid) -> Result<Option<Url>, StoreError> {
self.profile_key
.get(profile_key_id(profile_id))
.map(|opt| opt.and_then(url_from_ivec))
.map_err(StoreError::from)
}
pub(crate) fn public_key_for_id(&self, key_id: &Url) -> Result<Option<String>, StoreError> {
if let Some(ivec) = self.keys.get(key_id.as_str())? {
let keys: Keys = serde_json::from_slice(&ivec)?;
return Ok(Some(keys.public));
}
Ok(None)
}
pub(crate) fn store_public_key(
&self,
profile_id: Uuid,
key_id: &Url,
public_key: &str,
) -> Result<(), StoreError> {
let keys = Keys::from_public(public_key)?;
let keys_vec = serde_json::to_vec(&keys)?;
self.keys.insert(key_id.as_str(), keys_vec)?;
self.profile_key
.insert(profile_key_id(profile_id), key_id.as_str())?;
Ok(())
}
pub(crate) fn object(&self, id: &Url) -> Result<Option<AnyBase>, StoreError> {
if let Some(ivec) = self.objects.get(id.as_str())? {
let any_base: AnyBase = serde_json::from_slice(&ivec)?;
return Ok(Some(any_base));
}
Ok(None)
}
pub(crate) fn store_object(&self, any_base: AnyBase) -> Result<(), StoreError> {
if let Some(id) = any_base.id() {
let obj_vec = serde_json::to_vec(&any_base)?;
self.objects.insert(id.as_str(), obj_vec)?;
}
Ok(())
}
pub(crate) fn is_seen(&self, url: &Url) -> Result<bool, StoreError> {
Ok(self.seen.get(url.as_str())?.is_some())
}
pub(crate) fn seen(&self, url: &Url) -> Result<(), StoreError> {
self.seen.insert(url.as_str(), url.as_str())?;
Ok(())
}
pub(crate) fn delete_id(&self, id: StoredRecords) -> Result<(), StoreError> {
if let Some(url_ivec) = self.id_apub.remove(id.to_string())? {
self.apub_id.remove(url_ivec)?;
}
Ok(())
}
pub(crate) fn delete_apub(&self, apub_id: &Url) -> Result<(), StoreError> {
if let Some(record_ivec) = self.apub_id.remove(apub_id.as_str())? {
self.id_apub.remove(record_ivec)?;
}
Ok(())
}
pub(crate) fn submission(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
self.establish(apub_id, StoredRecords::Submission(id))
}
pub(crate) fn profile(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
self.establish(apub_id, StoredRecords::Profile(id))
}
pub(crate) fn comment(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
self.establish(apub_id, StoredRecords::Comment(id))
}
pub(crate) fn react(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
self.establish(apub_id, StoredRecords::React(id))
}
pub(crate) fn follow(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
self.establish(apub_id, StoredRecords::Follow(id))
}
pub(crate) fn follow_request(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
self.establish(apub_id, StoredRecords::FollowRequest(id))
}
pub(crate) fn block(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> {
self.establish(apub_id, StoredRecords::Block(id))
}
fn establish(&self, apub_id: &Url, id: StoredRecords) -> Result<(), StoreError> {
self.apub_id
.insert(apub_id.as_str(), id.to_string().as_bytes())?;
self.id_apub
.insert(id.to_string(), apub_id.as_str().as_bytes())?;
Ok(())
}
pub(crate) fn id_for_apub(&self, apub_id: &Url) -> Result<Option<StoredRecords>, StoreError> {
Ok(self
.apub_id
.get(apub_id.as_str())?
.and_then(record_from_ivec))
}
pub(crate) fn apub_for_id(&self, id: StoredRecords) -> Result<Option<Url>, StoreError> {
Ok(self.id_apub.get(id.to_string())?.and_then(url_from_ivec))
}
}
impl Keys {
fn from_public(public: &str) -> Result<Self, KeyError> {
use rsa::RSAPublicKey;
use rsa_pem::KeyExt;
RSAPublicKey::from_pem_pkcs8(public).map_err(|_| KeyError)?;
Ok(Keys {
public: public.to_owned(),
private: None,
})
}
fn generate() -> Result<Self, KeyError> {
use rsa::RSAPrivateKey;
use rsa_pem::KeyExt;
let mut rng = rand::thread_rng();
let private_key = RSAPrivateKey::new(&mut rng, 2048).map_err(|_| KeyError)?;
let private = private_key.to_pem_pkcs8().map_err(|_| KeyError)?;
let public_key = private_key.to_public_key();
let public = public_key.to_pem_pkcs8().map_err(|_| KeyError)?;
Ok(Keys {
public,
private: Some(private),
})
}
}
fn profile_key_id(profile_id: Uuid) -> String {
format!("/profile/{}/key-id", profile_id)
}
fn record_endpoints_key(record: StoredRecords) -> String {
format!("/object/{}/endpoints", record)
}
fn record_keys_key(record: StoredRecords) -> String {
format!("/object/{}/keys", record)
}
fn object_record_key(object_id: &Url) -> String {
format!("/object/{}/record", object_id)
}
fn owner_record_key(owner_id: &Url) -> String {
format!("/owner/{}/record", owner_id)
}
fn record_object_key(record: StoredRecords) -> String {
format!("/record/{}/object", record)
}
impl fmt::Debug for Store {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Store")
.field("relationship", &"Tree")
.field("keys", &"Tree")
.field("endpoints", &"Tree")
.field("info", &"Rc<dyn ApubIds>")
.finish()
}
}
#[derive(Debug, thiserror::Error)]
#[error("Failed to parse StoredRecord")]
pub struct StoredRecordsError;
impl std::str::FromStr for StoredRecords {
type Err = StoredRecordsError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let split_pos = s.find(':').ok_or(StoredRecordsError)?;
let (kind, uuid) = s.split_at(split_pos);
let uuid = uuid.trim_start_matches(':');
let id = uuid.parse::<Uuid>().map_err(|_| StoredRecordsError)?;
let record = match kind {
"profile" => StoredRecords::Profile(id),
"submission" => StoredRecords::Submission(id),
"comment" => StoredRecords::Comment(id),
"react" => StoredRecords::React(id),
"file" => StoredRecords::File(id),
"block" => StoredRecords::Block(id),
"follow" => StoredRecords::Follow(id),
"follow-request" => StoredRecords::FollowRequest(id),
_ => return Err(StoredRecordsError),
};
Ok(record)
}
}
impl fmt::Display for StoredRecords {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
StoredRecords::Profile(id) => write!(f, "profile:{}", id),
StoredRecords::Submission(id) => write!(f, "submission:{}", id),
StoredRecords::Comment(id) => write!(f, "comment:{}", id),
StoredRecords::React(id) => write!(f, "react:{}", id),
StoredRecords::File(id) => write!(f, "file:{}", id),
StoredRecords::Block(id) => write!(f, "block:{}", id),
StoredRecords::Follow(id) => write!(f, "follow:{}", id),
StoredRecords::FollowRequest(id) => write!(f, "follow-request:{}", id),
}
}
}
impl From<KeyError> for StoreError {
fn from(_: KeyError) -> Self {
StoreError::Key
}
}