Add simple activitypub traits/types, reorganize facade
This commit is contained in:
parent
83bc589bc1
commit
cfca60a966
|
@ -13,7 +13,7 @@ with-background-jobs = ["apub-background-jobs"]
|
|||
with-openssl = ["apub-openssl"]
|
||||
with-reqwest = ["apub-reqwest"]
|
||||
with-rustcrypto = ["apub-rustcrypto"]
|
||||
utils = ["apub-breaker-session", "apub-deref-client"]
|
||||
utils = ["apub-breaker-session", "apub-deref-client", "apub-publickey", "apub-simple-activitypub"]
|
||||
|
||||
[dependencies]
|
||||
apub-actix-web = { version = "0.1.0", path = "./apub-actix-web/", optional = true }
|
||||
|
@ -23,9 +23,10 @@ apub-breaker-session = { version = "0.1.0", path = "./apub-breaker-session/", op
|
|||
apub-core = { version = "0.1.0", path = "./apub-core/" }
|
||||
apub-deref-client = { version = "0.1.0", path = "./apub-deref-client/", optional = true }
|
||||
apub-openssl = { version = "0.1.0", path = "./apub-openssl/", optional = true }
|
||||
apub-publickey = { version = "0.1.0", path = "./apub-publickey/" }
|
||||
apub-publickey = { version = "0.1.0", path = "./apub-publickey/", optional = true }
|
||||
apub-reqwest = { version = "0.1.0", path = "./apub-reqwest/", optional = true }
|
||||
apub-rustcrypto = { version = "0.1.0", path = "./apub-rustcrypto/", optional = true }
|
||||
apub-simple-activitypub = { version = "0.1.0", path = "./apub-simple-activitypub/", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
async-trait = "0.1.51"
|
||||
|
@ -49,6 +50,7 @@ members = [
|
|||
"apub-publickey",
|
||||
"apub-reqwest",
|
||||
"apub-rustcrypto",
|
||||
"apub-simple-activitypub",
|
||||
"examples/actix-web-example",
|
||||
"examples/awc-example",
|
||||
"examples/background-jobs-example",
|
||||
|
|
|
@ -12,7 +12,7 @@ use actix_web::{
|
|||
FromRequest, HttpRequest, HttpResponse, ResponseError,
|
||||
};
|
||||
use apub_core::{
|
||||
deref::{Dereference, Repo, RepoFactory},
|
||||
repo::{Dereference, Repo, RepoFactory},
|
||||
digest::{Digest, DigestBuilder, DigestFactory},
|
||||
ingest::{Authority, Ingest},
|
||||
session::SessionFactory,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
use actix_http::error::BlockingError;
|
||||
use apub_core::{
|
||||
deliver::Deliver,
|
||||
deref::{Dereference, Repo},
|
||||
repo::{Dereference, Repo},
|
||||
digest::{Digest, DigestBuilder, DigestFactory},
|
||||
session::{Session, SessionError},
|
||||
signature::{Sign, SignFactory},
|
||||
|
|
93
apub-core/src/activitystreams.rs
Normal file
93
apub-core/src/activitystreams.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
//! Traits desccribing basic activitystreams types
|
||||
|
||||
use url::Url;
|
||||
|
||||
/// A type that represents a Public Key
|
||||
pub trait PublicKey {
|
||||
/// The Public Key's ID
|
||||
fn id(&self) -> &Url;
|
||||
|
||||
/// The Public Key's Owner
|
||||
fn owner(&self) -> &Url;
|
||||
|
||||
/// The Public Key's pem-encoded value
|
||||
fn public_key_pem(&self) -> &str;
|
||||
}
|
||||
|
||||
/// A type that represents an Object
|
||||
pub trait Object {
|
||||
/// The Object's ID
|
||||
fn id(&self) -> &Url;
|
||||
|
||||
/// The kind of Object
|
||||
fn kind(&self) -> &str;
|
||||
|
||||
/// Who the object is intended for
|
||||
fn to(&self) -> &[Url];
|
||||
|
||||
/// Additional actors the object should be delivered to
|
||||
fn cc(&self) -> &[Url];
|
||||
}
|
||||
|
||||
/// A type that represents an Activity
|
||||
pub trait Activity {
|
||||
/// The Actor performing the activity
|
||||
fn actor_id(&self) -> &Url;
|
||||
|
||||
/// The object being performed upon
|
||||
fn object_id(&self) -> &Url;
|
||||
}
|
||||
|
||||
/// A type that represents an Actor
|
||||
pub trait Actor {
|
||||
/// An Actor has a Public Key
|
||||
type PublicKey: PublicKey;
|
||||
|
||||
/// The ID of the actor
|
||||
fn id(&self) -> &Url;
|
||||
|
||||
/// The kind of actor
|
||||
fn kind(&self) -> &str;
|
||||
|
||||
/// The actor's inbox
|
||||
fn inbox(&self) -> &Url;
|
||||
|
||||
/// The actor's outbox
|
||||
fn outbox(&self) -> &Url;
|
||||
|
||||
/// The actor's preferred username (handle)
|
||||
fn preferred_username(&self) -> &str;
|
||||
|
||||
/// The actor's public key
|
||||
fn public_key(&self) -> &Self::PublicKey;
|
||||
|
||||
/// The actor's display name
|
||||
fn name(&self) -> Option<&str>;
|
||||
|
||||
/// The actor's shared inbox
|
||||
fn shared_inbox(&self) -> Option<&Url>;
|
||||
}
|
||||
|
||||
static PUBLIC: &'static str = "https://www.w3.org/ns/activitystreams#Public";
|
||||
|
||||
/// Determine the name of an actor
|
||||
pub fn canonical_name(act: &impl Actor) -> &str {
|
||||
act.name().unwrap_or_else(|| act.preferred_username())
|
||||
}
|
||||
|
||||
/// Determine whether an object is public
|
||||
pub fn is_public(obj: &impl Object) -> bool {
|
||||
obj.to()
|
||||
.iter()
|
||||
.chain(obj.cc())
|
||||
.any(|url| url.as_str() == PUBLIC)
|
||||
}
|
||||
|
||||
/// Determine which inbox an object should be delievered to
|
||||
pub fn delivery_inbox<'a>(actor: &'a impl Actor, obj: &impl Object) -> &'a Url {
|
||||
if is_public(obj) {
|
||||
actor.shared_inbox().unwrap_or_else(|| actor.inbox())
|
||||
} else {
|
||||
actor.inbox()
|
||||
}
|
||||
}
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
pub mod activitystreams;
|
||||
pub mod deliver;
|
||||
pub mod deref;
|
||||
pub mod digest;
|
||||
pub mod ingest;
|
||||
pub mod object_id;
|
||||
pub mod repo;
|
||||
pub mod session;
|
||||
pub mod signature;
|
||||
|
|
|
@ -14,7 +14,7 @@ use url::Url;
|
|||
/// type.
|
||||
///
|
||||
/// ```rust
|
||||
/// use apub_core::deref::Dereference;
|
||||
/// use apub_core::repo::Dereference;
|
||||
/// use url::Url;
|
||||
///
|
||||
/// #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
|
|
|
@ -11,7 +11,7 @@ use url::Url;
|
|||
/// for fetching local objects from a database
|
||||
///
|
||||
/// ```rust
|
||||
/// use apub_core::{deref::{Dereference, Repo}, session::Session};
|
||||
/// use apub_core::{repo::{Dereference, Repo}, session::Session};
|
||||
/// use std::{collections::HashMap, sync::{Arc, Mutex}};
|
||||
/// use url::Url;
|
||||
///
|
||||
|
@ -61,7 +61,7 @@ pub trait RepoFactory {
|
|||
/// dereference implementation, can be passed to a Repository to fetch the T object it represents
|
||||
///
|
||||
/// ```rust
|
||||
/// use apub_core::deref::{Dereference, Repo};
|
||||
/// use apub_core::repo::{Dereference, Repo};
|
||||
/// use std::borrow::Cow;
|
||||
/// use url::Url;
|
||||
///
|
|
@ -1,7 +1,7 @@
|
|||
//! A type to combine a remote Repo with a local Repo
|
||||
//!
|
||||
//! ```rust
|
||||
//! use apub_core::{deref::{Dereference, Repo}, session::Session};
|
||||
//! use apub_core::{repo::{Dereference, Repo}, session::Session};
|
||||
//! use apub_deref_client::Client;
|
||||
//! use apub_openssl::OpenSsl;
|
||||
//! use apub_reqwest::{ReqwestClient, SignatureConfig};
|
||||
|
@ -108,7 +108,7 @@
|
|||
#![deny(missing_docs)]
|
||||
|
||||
use apub_core::{
|
||||
deref::{Dereference, Repo},
|
||||
repo::{Dereference, Repo},
|
||||
session::Session,
|
||||
};
|
||||
use url::{Host, Url};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use apub_core::{
|
||||
deref::{Dereference, Repo},
|
||||
activitystreams::PublicKey,
|
||||
repo::{Dereference, Repo},
|
||||
session::Session,
|
||||
};
|
||||
use std::{
|
||||
|
@ -12,9 +13,12 @@ use url::{Host, Url};
|
|||
pub trait PublicKeyRepo {
|
||||
type Error: std::error::Error;
|
||||
|
||||
async fn store(&self, public_key: &PublicKey) -> Result<(), Self::Error>;
|
||||
async fn store(&self, public_key: &SimplePublicKey) -> Result<(), Self::Error>;
|
||||
|
||||
async fn fetch(&self, public_key_id: &PublicKeyId) -> Result<Option<PublicKey>, Self::Error>;
|
||||
async fn fetch(
|
||||
&self,
|
||||
public_key_id: &PublicKeyId,
|
||||
) -> Result<Option<SimplePublicKey>, Self::Error>;
|
||||
}
|
||||
|
||||
pub trait PublicKeyRepoFactory {
|
||||
|
@ -42,6 +46,40 @@ pub enum PublicKeyError<E, R> {
|
|||
RemoteRepo(#[source] R),
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize,
|
||||
)]
|
||||
#[serde(transparent)]
|
||||
pub struct PublicKeyId(pub Url);
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SimplePublicKey {
|
||||
pub id: PublicKeyId,
|
||||
pub owner: Url,
|
||||
pub public_key_pem: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum PublicKeyResponse {
|
||||
/// I dream of the day when asking for a Public Key gives me a public key
|
||||
PublicKey(SimplePublicKey),
|
||||
|
||||
/// Getting an Actor from a PublicKey fetch is a bug IMO but mastodon does it so :shrug:
|
||||
Actor {
|
||||
id: Url,
|
||||
public_key: SimplePublicKey,
|
||||
},
|
||||
}
|
||||
|
||||
impl PublicKeyId {
|
||||
pub fn into_inner(this: Self) -> Url {
|
||||
this.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<LocalRepo, RemoteRepo> PublicKeyClient<LocalRepo, RemoteRepo>
|
||||
where
|
||||
LocalRepo: PublicKeyRepo,
|
||||
|
@ -65,7 +103,7 @@ where
|
|||
&self,
|
||||
public_key_id: &PublicKeyId,
|
||||
session: S,
|
||||
) -> Result<Option<PublicKey>, PublicKeyError<LocalRepo::Error, RemoteRepo::Error>> {
|
||||
) -> Result<Option<SimplePublicKey>, PublicKeyError<LocalRepo::Error, RemoteRepo::Error>> {
|
||||
let opt = self
|
||||
.local
|
||||
.fetch(public_key_id)
|
||||
|
@ -113,37 +151,6 @@ fn borrow_host(host: &Host<String>) -> Host<&str> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize,
|
||||
)]
|
||||
#[serde(transparent)]
|
||||
pub struct PublicKeyId(pub Url);
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PublicKey {
|
||||
pub id: PublicKeyId,
|
||||
pub owner: Url,
|
||||
pub public_key_pem: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum PublicKeyResponse {
|
||||
/// I dream of the day when asking for a Public Key gives me a public key
|
||||
PublicKey(PublicKey),
|
||||
|
||||
/// Getting an Actor from a PublicKey fetch is a bug IMO but mastodon does it so :shrug:
|
||||
Actor { id: Url, public_key: PublicKey },
|
||||
}
|
||||
|
||||
impl PublicKeyId {
|
||||
pub fn into_inner(this: Self) -> Url {
|
||||
this.0
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PublicKeyId {
|
||||
type Err = <Url as FromStr>::Err;
|
||||
|
||||
|
@ -191,3 +198,17 @@ impl From<Url> for PublicKeyId {
|
|||
PublicKeyId(url)
|
||||
}
|
||||
}
|
||||
|
||||
impl PublicKey for SimplePublicKey {
|
||||
fn id(&self) -> &Url {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn owner(&self) -> &Url {
|
||||
&self.owner
|
||||
}
|
||||
|
||||
fn public_key_pem(&self) -> &str {
|
||||
&self.public_key_pem
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#![deny(missing_docs)]
|
||||
use apub_core::{
|
||||
deliver::Deliver,
|
||||
deref::{Dereference, Repo},
|
||||
repo::{Dereference, Repo},
|
||||
digest::{Digest, DigestBuilder, DigestFactory},
|
||||
session::{Session, SessionError},
|
||||
signature::{Sign, SignFactory},
|
||||
|
|
13
apub-simple-activitypub/Cargo.toml
Normal file
13
apub-simple-activitypub/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "apub-simple-activitypub"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
apub-core = { version = "0.1.0", path = "../apub-core/" }
|
||||
apub-publickey = { version = "0.1.0", path = "../apub-publickey/" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
url = { version = "2", features = ["serde"] }
|
254
apub-simple-activitypub/src/lib.rs
Normal file
254
apub-simple-activitypub/src/lib.rs
Normal file
|
@ -0,0 +1,254 @@
|
|||
use apub_core::activitystreams::{Activity, Actor, Object};
|
||||
use apub_publickey::SimplePublicKey;
|
||||
use serde_json::Map;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SimpleObject {
|
||||
pub id: Url,
|
||||
|
||||
#[serde(rename = "type")]
|
||||
pub kind: String,
|
||||
|
||||
#[serde(deserialize_with = "one_or_many")]
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub to: Vec<Url>,
|
||||
|
||||
#[serde(deserialize_with = "one_or_many")]
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub cc: Vec<Url>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub rest: Map<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SimpleActivity {
|
||||
pub id: Url,
|
||||
|
||||
#[serde(rename = "type")]
|
||||
pub kind: String,
|
||||
|
||||
#[serde(deserialize_with = "one_or_many")]
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub to: Vec<Url>,
|
||||
|
||||
#[serde(deserialize_with = "one_or_many")]
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub cc: Vec<Url>,
|
||||
|
||||
pub actor: Either<Url, SimpleActor>,
|
||||
|
||||
pub object: Either<Url, Either<Box<SimpleActivity>, SimpleObject>>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub rest: Map<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SimpleActor {
|
||||
pub id: Url,
|
||||
|
||||
#[serde(rename = "type")]
|
||||
pub kind: String,
|
||||
|
||||
pub inbox: Url,
|
||||
|
||||
pub outbox: Url,
|
||||
|
||||
pub preferred_username: String,
|
||||
|
||||
pub public_key: SimplePublicKey,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub endpoints: Option<SimpleEndpoints>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub rest: Map<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SimpleEndpoints {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub shared_inbox: Option<Url>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Either<Left, Right> {
|
||||
Left(Left),
|
||||
Right(Right),
|
||||
}
|
||||
|
||||
impl Object for SimpleObject {
|
||||
fn id(&self) -> &Url {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn kind(&self) -> &str {
|
||||
&self.kind
|
||||
}
|
||||
|
||||
fn to(&self) -> &[Url] {
|
||||
&self.to
|
||||
}
|
||||
|
||||
fn cc(&self) -> &[Url] {
|
||||
&self.cc
|
||||
}
|
||||
}
|
||||
|
||||
impl Object for SimpleActivity {
|
||||
fn id(&self) -> &Url {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn kind(&self) -> &str {
|
||||
&self.kind
|
||||
}
|
||||
|
||||
fn to(&self) -> &[Url] {
|
||||
&self.to
|
||||
}
|
||||
|
||||
fn cc(&self) -> &[Url] {
|
||||
&self.cc
|
||||
}
|
||||
}
|
||||
|
||||
impl Activity for SimpleActivity {
|
||||
fn actor_id(&self) -> &Url {
|
||||
match &self.actor {
|
||||
Either::Left(ref id) => id,
|
||||
Either::Right(obj) => obj.id(),
|
||||
}
|
||||
}
|
||||
|
||||
fn object_id(&self) -> &Url {
|
||||
match &self.object {
|
||||
Either::Left(ref id) => id,
|
||||
Either::Right(Either::Left(activity)) => activity.id(),
|
||||
Either::Right(Either::Right(object)) => object.id(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Actor for SimpleActor {
|
||||
type PublicKey = SimplePublicKey;
|
||||
|
||||
fn id(&self) -> &Url {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn kind(&self) -> &str {
|
||||
&self.kind
|
||||
}
|
||||
|
||||
fn inbox(&self) -> &Url {
|
||||
&self.inbox
|
||||
}
|
||||
|
||||
fn outbox(&self) -> &Url {
|
||||
&self.outbox
|
||||
}
|
||||
|
||||
fn preferred_username(&self) -> &str {
|
||||
&self.preferred_username
|
||||
}
|
||||
|
||||
fn public_key(&self) -> &Self::PublicKey {
|
||||
&self.public_key
|
||||
}
|
||||
|
||||
fn name(&self) -> Option<&str> {
|
||||
self.name.as_deref()
|
||||
}
|
||||
|
||||
fn shared_inbox(&self) -> Option<&Url> {
|
||||
self.endpoints
|
||||
.as_ref()
|
||||
.and_then(|endpoints| endpoints.shared_inbox.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
fn one_or_many<'de, D>(deserializer: D) -> Result<Vec<Url>, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>,
|
||||
{
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum OneOrMany {
|
||||
Single(Url),
|
||||
Many(Vec<Url>),
|
||||
}
|
||||
|
||||
let one_or_many: Option<OneOrMany> = serde::de::Deserialize::deserialize(deserializer)?;
|
||||
|
||||
let v = match one_or_many {
|
||||
Some(OneOrMany::Many(v)) => v,
|
||||
Some(OneOrMany::Single(o)) => vec![o],
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use apub_core::activitystreams::{is_public, PublicKey};
|
||||
|
||||
static ASONIX: &'static str = r#"{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","toot":"http://joinmastodon.org/ns#","featured":{"@id":"toot:featured","@type":"@id"},"featuredTags":{"@id":"toot:featuredTags","@type":"@id"},"alsoKnownAs":{"@id":"as:alsoKnownAs","@type":"@id"},"movedTo":{"@id":"as:movedTo","@type":"@id"},"schema":"http://schema.org#","PropertyValue":"schema:PropertyValue","value":"schema:value","IdentityProof":"toot:IdentityProof","discoverable":"toot:discoverable","Device":"toot:Device","Ed25519Signature":"toot:Ed25519Signature","Ed25519Key":"toot:Ed25519Key","Curve25519Key":"toot:Curve25519Key","EncryptedMessage":"toot:EncryptedMessage","publicKeyBase64":"toot:publicKeyBase64","deviceId":"toot:deviceId","claim":{"@type":"@id","@id":"toot:claim"},"fingerprintKey":{"@type":"@id","@id":"toot:fingerprintKey"},"identityKey":{"@type":"@id","@id":"toot:identityKey"},"devices":{"@type":"@id","@id":"toot:devices"},"messageFranking":"toot:messageFranking","messageType":"toot:messageType","cipherText":"toot:cipherText","suspended":"toot:suspended","focalPoint":{"@container":"@list","@id":"toot:focalPoint"}}],"id":"https://masto.asonix.dog/users/asonix","type":"Person","following":"https://masto.asonix.dog/users/asonix/following","followers":"https://masto.asonix.dog/users/asonix/followers","inbox":"https://masto.asonix.dog/users/asonix/inbox","outbox":"https://masto.asonix.dog/users/asonix/outbox","featured":"https://masto.asonix.dog/users/asonix/collections/featured","featuredTags":"https://masto.asonix.dog/users/asonix/collections/tags","preferredUsername":"asonix","name":"Alleged Cat Crime Committer","summary":"\u003cp\u003elocal liom, friend, rust (lang) stan, bi \u003c/p\u003e\u003cp\u003eicon by \u003cspan class=\"h-card\"\u003e\u003ca href=\"https://t.me/dropmutt\" target=\"blank\" rel=\"noopener noreferrer\" class=\"u-url mention\"\u003e@\u003cspan\u003edropmutt@telegram.org\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e\u003cbr /\u003eheader by \u003cspan class=\"h-card\"\u003e\u003ca href=\"https://furaffinity.net/user/tronixx\" target=\"blank\" rel=\"noopener noreferrer\" class=\"u-url mention\"\u003e@\u003cspan\u003etronixx@furaffinity.net\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e\u003c/p\u003e\u003cp\u003eTestimonials:\u003c/p\u003e\u003cp\u003eStand: LIONS\u003cbr /\u003eStand User: AODE\u003cbr /\u003e- Keris (not on here)\u003c/p\u003e","url":"https://masto.asonix.dog/@asonix","manuallyApprovesFollowers":true,"discoverable":false,"published":"2021-02-09T00:00:00Z","devices":"https://masto.asonix.dog/users/asonix/collections/devices","publicKey":{"id":"https://masto.asonix.dog/users/asonix#main-key","owner":"https://masto.asonix.dog/users/asonix","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm+YpyXb3bUp5EyryHqRA\npKSvl4RamJh6CLlngYYPFU8lcx92oQR8nFlqOwInAczGPoCoIfojQpZfqV4hFq1I\nlETy6jHHeoO/YkUsH2dYtz6gjEqiZFCFpoWuGxUQO3lwfmPYpxl2/GFEDR4MrUNp\n9fPn9jHUlKydiDkFQqluajqSJgv0BCwnUGBanTEfeQKahnc3OqPTi4xNbsd2cbAW\nZtJ6VYepphQCRHElvkzefe1ra5qm5i8YBdan3Z3oo5wN1vo3u41tqjVGhDptKZkv\nwBevdL0tedoLp5Lj1l/HLTSBP0D0ZT/HUFuo6Zq27PCq/4ZgJaZkMi7YCVVtpjim\nmQIDAQAB\n-----END PUBLIC KEY-----\n"},"tag":[],"attachment":[{"type":"PropertyValue","name":"pronouns","value":"he/they"},{"type":"PropertyValue","name":"software","value":"bad"},{"type":"PropertyValue","name":"gitea","value":"\u003ca href=\"https://git.asonix.dog\" rel=\"me nofollow noopener noreferrer\" target=\"_blank\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003egit.asonix.dog\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e"},{"type":"PropertyValue","name":"join my","value":"relay"}],"endpoints":{"sharedInbox":"https://masto.asonix.dog/inbox"},"icon":{"type":"Image","mediaType":"image/png","url":"https://masto.asonix.dog/system/accounts/avatars/000/000/001/original/4f8d8f520ca26354.png"},"image":{"type":"Image","mediaType":"image/png","url":"https://masto.asonix.dog/system/accounts/headers/000/000/001/original/8122ce3e5a745385.png"}}"#;
|
||||
static CREATE: &'static str = r#"{"id":"https://masto.asonix.dog/users/asonix/statuses/107355727205658651/activity","type":"Create","actor":"https://masto.asonix.dog/users/asonix","published":"2021-11-28T16:53:16Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://masto.asonix.dog/users/asonix/followers"],"object":{"id":"https://masto.asonix.dog/users/asonix/statuses/107355727205658651","type":"Note","summary":null,"inReplyTo":null,"published":"2021-11-28T16:53:16Z","url":"https://masto.asonix.dog/@asonix/107355727205658651","attributedTo":"https://masto.asonix.dog/users/asonix","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://masto.asonix.dog/users/asonix/followers"],"sensitive":false,"atomUri":"https://masto.asonix.dog/users/asonix/statuses/107355727205658651","inReplyToAtomUri":null,"conversation":"tag:masto.asonix.dog,2021-11-28:objectId=671618:objectType=Conversation","content":"\u003cp\u003eY\u0026apos;all it\u0026apos;s Masto MSunday\u003c/p\u003e","contentMap":{"lion":"\u003cp\u003eY\u0026apos;all it\u0026apos;s Masto MSunday\u003c/p\u003e"},"attachment":[],"tag":[],"replies":{"id":"https://masto.asonix.dog/users/asonix/statuses/107355727205658651/replies","type":"Collection","first":{"type":"CollectionPage","next":"https://masto.asonix.dog/users/asonix/statuses/107355727205658651/replies?only_other_accounts=true\u0026page=true","partOf":"https://masto.asonix.dog/users/asonix/statuses/107355727205658651/replies","items":[]}}}}"#;
|
||||
|
||||
#[test]
|
||||
fn simple_actor() {
|
||||
let actor: SimpleActor = serde_json::from_str(ASONIX).unwrap();
|
||||
|
||||
assert_eq!(actor.id().as_str(), "https://masto.asonix.dog/users/asonix");
|
||||
assert_eq!(
|
||||
actor.shared_inbox().unwrap().as_str(),
|
||||
"https://masto.asonix.dog/inbox"
|
||||
);
|
||||
assert_eq!(
|
||||
actor.inbox().as_str(),
|
||||
"https://masto.asonix.dog/users/asonix/inbox"
|
||||
);
|
||||
assert_eq!(
|
||||
actor.outbox().as_str(),
|
||||
"https://masto.asonix.dog/users/asonix/outbox"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
actor.public_key().id().as_str(),
|
||||
"https://masto.asonix.dog/users/asonix#main-key"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_activity() {
|
||||
let activity: SimpleActivity = serde_json::from_str(CREATE).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
activity.id().as_str(),
|
||||
"https://masto.asonix.dog/users/asonix/statuses/107355727205658651/activity"
|
||||
);
|
||||
assert_eq!(
|
||||
activity.actor_id().as_str(),
|
||||
"https://masto.asonix.dog/users/asonix"
|
||||
);
|
||||
assert_eq!(
|
||||
activity.object_id().as_str(),
|
||||
"https://masto.asonix.dog/users/asonix/statuses/107355727205658651"
|
||||
);
|
||||
|
||||
assert!(is_public(&activity));
|
||||
}
|
||||
}
|
|
@ -1,14 +1,16 @@
|
|||
use actix_web::{middleware::Logger, web, App, HttpServer, ResponseError};
|
||||
use apub::{
|
||||
clients::{AwcClient, AwcError, AwcSignatureConfig, Dereference, Repo, RepoFactory},
|
||||
crypto::{
|
||||
DigestFactory, RsaVerifier, Rustcrypto, RustcryptoError, Sha256Digest, VerifyFactory,
|
||||
},
|
||||
servers::{
|
||||
inbox, serve_objects, ActixWebSignatureConfig, Authority, Config, Ingest, PublicKey,
|
||||
PublicKeyError, PublicKeyId, PublicKeyRepo, PublicKeyRepoFactory, VerifyError,
|
||||
activitypub::SimplePublicKey,
|
||||
actix_web::{
|
||||
inbox, serve_objects, Config, SignatureConfig as ActixWebSignatureConfig, VerifyError,
|
||||
},
|
||||
awc::{AwcClient, AwcError, SignatureConfig as AwcSignatureConfig},
|
||||
digest::{DigestFactory, Sha256Digest},
|
||||
ingest::{Authority, Ingest, PublicKeyError, PublicKeyId},
|
||||
ingest::{PublicKeyRepo, PublicKeyRepoFactory},
|
||||
repo::{Dereference, Repo, RepoFactory},
|
||||
session::{BreakerSession, RequestCountSession, Session, SessionFactory},
|
||||
signature::{RsaVerifier, Rustcrypto, RustcryptoError, VerifyFactory},
|
||||
};
|
||||
use dashmap::DashMap;
|
||||
use example_types::{AcceptedActivity, ActivityType, ObjectId};
|
||||
|
@ -98,7 +100,10 @@ impl Repo for MemoryRepo {
|
|||
impl PublicKeyRepo for MemoryRepo {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
async fn fetch(&self, public_key_id: &PublicKeyId) -> Result<Option<PublicKey>, Self::Error> {
|
||||
async fn fetch(
|
||||
&self,
|
||||
public_key_id: &PublicKeyId,
|
||||
) -> Result<Option<SimplePublicKey>, Self::Error> {
|
||||
if let Some(obj_ref) = self.inner.get(&public_key_id) {
|
||||
serde_json::from_value(obj_ref.clone()).map(Some)
|
||||
} else {
|
||||
|
@ -106,7 +111,7 @@ impl PublicKeyRepo for MemoryRepo {
|
|||
}
|
||||
}
|
||||
|
||||
async fn store(&self, public_key: &PublicKey) -> Result<(), Self::Error> {
|
||||
async fn store(&self, public_key: &SimplePublicKey) -> Result<(), Self::Error> {
|
||||
let value = serde_json::to_value(public_key)?;
|
||||
|
||||
self.inner.insert(public_key.id.as_ref().clone(), value);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use apub::{
|
||||
clients::{Activity, AwcClient, AwcSignatureConfig},
|
||||
crypto::Rustcrypto,
|
||||
deliver::Activity,
|
||||
awc::{AwcClient, SignatureConfig},
|
||||
signature::Rustcrypto,
|
||||
session::{BreakerSession, RequestCountSession},
|
||||
};
|
||||
use example_types::{object_id, NoteType, ObjectId};
|
||||
|
@ -11,7 +12,7 @@ use std::time::Duration;
|
|||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let private_key = RsaPrivateKey::new(&mut rand::thread_rng(), 1024)?;
|
||||
let crypto = Rustcrypto::new("key-id".to_string(), private_key);
|
||||
let config = AwcSignatureConfig::default();
|
||||
let config = SignatureConfig::default();
|
||||
|
||||
let mut breakers = BreakerSession::limit(10, Duration::from_secs(60 * 60));
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use apub::{
|
||||
background::{client, ClientFactory, DeliverJob},
|
||||
clients::{Activity, AwcClient, AwcSignatureConfig},
|
||||
crypto::Rustcrypto,
|
||||
background_jobs::{client, ClientFactory, DeliverJob},
|
||||
awc::{AwcClient, SignatureConfig},
|
||||
deliver::Activity,
|
||||
signature::Rustcrypto,
|
||||
session::{BreakerSession, RequestCountSession, SessionFactory},
|
||||
};
|
||||
use background_jobs::{memory_storage::Storage, WorkerConfig};
|
||||
|
@ -33,7 +34,7 @@ pub fn init_tracing() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
#[derive(Clone)]
|
||||
struct State {
|
||||
config: AwcSignatureConfig,
|
||||
config: SignatureConfig,
|
||||
breakers: BreakerSession,
|
||||
client: awc::Client,
|
||||
}
|
||||
|
@ -59,7 +60,7 @@ impl SessionFactory for State {
|
|||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
init_tracing()?;
|
||||
|
||||
let config = AwcSignatureConfig::default();
|
||||
let config = SignatureConfig::default();
|
||||
let breakers = BreakerSession::limit(3, Duration::from_secs(60 * 60));
|
||||
|
||||
let storage = Storage::new();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use apub::{
|
||||
clients::{Activity, Dereference, Repo},
|
||||
repo::{Dereference, Repo},
|
||||
deliver::Activity,
|
||||
session::Session,
|
||||
};
|
||||
use std::{
|
||||
|
@ -43,13 +44,13 @@ impl Dereference for ObjectId<ActivityType> {
|
|||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct ObjectId<Kind>(apub::clients::ObjectId<'static, Kind>);
|
||||
pub struct ObjectId<Kind>(apub::object_id::ObjectId<'static, Kind>);
|
||||
|
||||
pub fn object_id<Kind>(id: Url) -> ObjectId<Kind>
|
||||
where
|
||||
ObjectId<Kind>: Dereference,
|
||||
{
|
||||
ObjectId(apub::clients::ObjectId::new_owned(id))
|
||||
ObjectId(apub::object_id::ObjectId::new_owned(id))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use apub::{
|
||||
clients::{Activity, ReqwestClient, ReqwestSignatureConfig},
|
||||
crypto::OpenSsl,
|
||||
deliver::Activity,
|
||||
reqwest::{ReqwestClient, SignatureConfig},
|
||||
signature::OpenSsl,
|
||||
session::{BreakerSession, RequestCountSession},
|
||||
};
|
||||
use example_types::{object_id, NoteType, ObjectId};
|
||||
|
@ -11,7 +12,7 @@ use std::time::Duration;
|
|||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let private_key = PKey::from_rsa(Rsa::generate(1024)?)?;
|
||||
let crypto = OpenSsl::new("key-id".to_string(), private_key);
|
||||
let config = ReqwestSignatureConfig::default();
|
||||
let config = SignatureConfig::default();
|
||||
|
||||
let mut breakers = BreakerSession::limit(10, Duration::from_secs(60 * 60));
|
||||
|
||||
|
|
107
src/lib.rs
107
src/lib.rs
|
@ -1,27 +1,5 @@
|
|||
pub mod clients {
|
||||
//! Types and traits for fetching and delivering objects
|
||||
//!
|
||||
//! Notable types are [`CombinedClient`] behind the `utils` feature, [`ReqwestClient`] behind
|
||||
//! the `with-reqwest` feature, and [`AwcClient`] behind the `awc` feature. Each of these types
|
||||
//! implements the [`Repo`] trait, which describes fetching an object from a given repository.
|
||||
//! While [`ReqwestClient`] and [`AwcClient`] both describe fetching objects from activitypub
|
||||
//! remotes, [`CombinedClient`] is designed to easily fetch an object from a local repository,
|
||||
//! such as a database or in-memory store, and fall back to an HTTP client for requests it can't
|
||||
//! satisfy itself.
|
||||
//!
|
||||
//! [`ReqwestClient`] and [`AwcClient`] also implement the [`Client`] trait, which describes
|
||||
//! delivering objects to activitypub remotes.
|
||||
//!
|
||||
//! Both [`ReqwestClient`] and [`AwcClient`] are built to use a generic [`Session`]
|
||||
//! implementation and a generic [`SignFactory`] impelementation to extend request behavior and
|
||||
//! support HTTP Signatures and HTTP Digests
|
||||
//!
|
||||
//! [`Session`]: crate::session::Session
|
||||
//! [`SignFactory`]: crate::crypto::SignFactory
|
||||
|
||||
pub use apub_core::deliver::{Activity, Deliver};
|
||||
pub use apub_core::deref::{Dereference, Repo, RepoFactory};
|
||||
pub use apub_core::object_id::ObjectId;
|
||||
pub mod repo {
|
||||
pub use apub_core::repo::{Dereference, Repo, RepoFactory};
|
||||
|
||||
#[cfg(feature = "apub-deref-client")]
|
||||
/// The type that combines Local and Remote repos
|
||||
|
@ -30,15 +8,17 @@ pub mod clients {
|
|||
///
|
||||
/// ```rust
|
||||
/// use apub::{
|
||||
/// clients::{
|
||||
/// repo::{
|
||||
/// CombinedClient,
|
||||
/// Dereference,
|
||||
/// Repo,
|
||||
/// ReqwestClient,
|
||||
/// ReqwestSignatureConfig,
|
||||
/// },
|
||||
/// crypto::OpenSsl,
|
||||
/// reqwest::{
|
||||
/// ReqwestClient,
|
||||
/// SignatureConfig,
|
||||
/// },
|
||||
/// session::Session,
|
||||
/// signature::OpenSsl,
|
||||
/// };
|
||||
/// use dashmap::DashMap;
|
||||
/// use openssl::{
|
||||
|
@ -107,7 +87,7 @@ pub mod clients {
|
|||
/// existing_object,
|
||||
/// );
|
||||
///
|
||||
/// let signature_config = ReqwestSignatureConfig::default();
|
||||
/// let signature_config = SignatureConfig::default();
|
||||
/// let private_key = PKey::from_rsa(Rsa::generate(1024)?)?;
|
||||
/// let crypto = OpenSsl::new("key-id".to_string(), private_key);
|
||||
/// let client = reqwest::Client::new();
|
||||
|
@ -142,33 +122,53 @@ pub mod clients {
|
|||
|
||||
#[cfg(feature = "apub-deref-client")]
|
||||
pub use apub_deref_client::ClientError as CombinedError;
|
||||
}
|
||||
|
||||
#[cfg(feature = "apub-awc")]
|
||||
pub mod deliver {
|
||||
pub use apub_core::deliver::{Activity, Deliver};
|
||||
}
|
||||
|
||||
pub mod object_id {
|
||||
pub use apub_core::object_id::ObjectId;
|
||||
}
|
||||
|
||||
#[cfg(feature = "apub-awc")]
|
||||
pub mod awc {
|
||||
pub use apub_awc::{
|
||||
AwcClient, AwcError, InvalidHeaderValue, PrepareSignError,
|
||||
SignatureConfig as AwcSignatureConfig, SignatureError as AwcSignatureError,
|
||||
};
|
||||
|
||||
#[cfg(feature = "apub-reqwest")]
|
||||
pub use apub_reqwest::{
|
||||
ReqwestClient, ReqwestError, SignatureConfig as ReqwestSignatureConfig,
|
||||
SignatureError as ReqwestSignatureError,
|
||||
AwcClient, AwcError, InvalidHeaderValue, PrepareSignError, SignatureConfig, SignatureError,
|
||||
};
|
||||
}
|
||||
|
||||
pub mod servers {
|
||||
#[cfg(feature = "apub-reqwest")]
|
||||
pub mod reqwest {
|
||||
pub use apub_reqwest::{ReqwestClient, ReqwestError, SignatureConfig, SignatureError};
|
||||
}
|
||||
|
||||
pub mod ingest {
|
||||
pub use apub_core::ingest::{Authority, Ingest};
|
||||
pub use apub_publickey::{
|
||||
PublicKey, PublicKeyError, PublicKeyId, PublicKeyRepo, PublicKeyRepoFactory,
|
||||
};
|
||||
|
||||
#[cfg(feature = "apub-actix-web")]
|
||||
pub use apub_actix_web::{
|
||||
inbox, serve_objects, Config, SignatureConfig as ActixWebSignatureConfig, VerifyError,
|
||||
#[cfg(feature = "apub-publickey")]
|
||||
pub use apub_publickey::{
|
||||
PublicKeyClient, PublicKeyError, PublicKeyId, PublicKeyRepo, PublicKeyRepoFactory,
|
||||
};
|
||||
}
|
||||
|
||||
pub mod background {
|
||||
#[cfg(feature = "apub-actix-web")]
|
||||
pub mod actix_web {
|
||||
pub use apub_actix_web::{inbox, serve_objects, Config, SignatureConfig, VerifyError};
|
||||
}
|
||||
|
||||
pub mod activitypub {
|
||||
pub use apub_core::activitystreams::{Activity, Actor, Object, PublicKey};
|
||||
|
||||
#[cfg(feature = "apub-publickey")]
|
||||
pub use apub_publickey::SimplePublicKey;
|
||||
|
||||
#[cfg(feature = "apub-simple-activitypub")]
|
||||
pub use apub_simple_activitypub::{SimpleActivity, SimpleActor, SimpleObject};
|
||||
}
|
||||
|
||||
pub mod background_jobs {
|
||||
#[cfg(feature = "apub-background-jobs")]
|
||||
pub use apub_background_jobs::{client, ClientFactory, DeliverJob, EnqueueError, JobClient};
|
||||
}
|
||||
|
@ -180,13 +180,22 @@ pub mod session {
|
|||
pub use apub_breaker_session::BreakerSession;
|
||||
}
|
||||
|
||||
pub mod crypto {
|
||||
pub mod digest {
|
||||
pub use apub_core::digest::{Digest, DigestBuilder, DigestFactory};
|
||||
|
||||
#[cfg(feature = "apub-openssl")]
|
||||
pub use apub_openssl::OpenSslDigest;
|
||||
|
||||
#[cfg(feature = "apub-rustcrypto")]
|
||||
pub use apub_rustcrypto::Sha256Digest;
|
||||
}
|
||||
|
||||
pub mod signature {
|
||||
pub use apub_core::signature::{Sign, SignFactory, Verify, VerifyBuilder, VerifyFactory};
|
||||
|
||||
#[cfg(feature = "apub-openssl")]
|
||||
pub use apub_openssl::{OpenSsl, OpenSslDigest, OpenSslSigner, OpenSslVerifier};
|
||||
pub use apub_openssl::{OpenSsl, OpenSslSigner, OpenSslVerifier};
|
||||
|
||||
#[cfg(feature = "apub-rustcrypto")]
|
||||
pub use apub_rustcrypto::{RsaSigner, RsaVerifier, Rustcrypto, RustcryptoError, Sha256Digest};
|
||||
pub use apub_rustcrypto::{RsaSigner, RsaVerifier, Rustcrypto, RustcryptoError};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue