Add IngestFactory for more dynamic ingest structures
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
9abb291475
commit
a1ad76b485
|
@ -13,7 +13,7 @@ use actix_web::{
|
||||||
};
|
};
|
||||||
use apub_core::{
|
use apub_core::{
|
||||||
digest::{Digest, DigestBuilder, DigestFactory},
|
digest::{Digest, DigestBuilder, DigestFactory},
|
||||||
ingest::{is_local, Authority, Ingest},
|
ingest::{is_local, Authority, Ingest, IngestFactory},
|
||||||
repo::{Dereference, Repo, RepoFactory},
|
repo::{Dereference, Repo, RepoFactory},
|
||||||
session::SessionFactory,
|
session::SessionFactory,
|
||||||
signature::{PrivateKeyBuilder, Verify, VerifyBuilder, VerifyFactory},
|
signature::{PrivateKeyBuilder, Verify, VerifyBuilder, VerifyFactory},
|
||||||
|
@ -137,17 +137,17 @@ type PrivKeyError<R> = <<R as RepoFactory>::Crypto as PrivateKeyBuilder>::Error;
|
||||||
/// ```
|
/// ```
|
||||||
pub fn inbox<A, I, V, E>(
|
pub fn inbox<A, I, V, E>(
|
||||||
config: Config,
|
config: Config,
|
||||||
ingest: I,
|
ingest_factory: I,
|
||||||
verifier: V,
|
verifier: V,
|
||||||
signature_config: SignatureConfig,
|
signature_config: SignatureConfig,
|
||||||
require_signature: bool,
|
require_signature: bool,
|
||||||
) -> impl FnOnce(&mut ServiceConfig)
|
) -> impl FnOnce(&mut ServiceConfig)
|
||||||
where
|
where
|
||||||
A: for<'de> serde::de::Deserialize<'de> + 'static,
|
A: for<'de> serde::de::Deserialize<'de> + 'static,
|
||||||
I: Ingest<A> + PrivateKeyRepoFactory + RepoFactory + SessionFactory + 'static,
|
I: IngestFactory<A> + PrivateKeyRepoFactory + RepoFactory + SessionFactory + 'static,
|
||||||
I::PrivateKeyRepo: PrivateKeyRepo<PrivateKey = I::Crypto>,
|
I::PrivateKeyRepo: PrivateKeyRepo<PrivateKey = I::Crypto>,
|
||||||
I::ActorId: FromRequest + AsRef<Url>,
|
<I::Ingest as Ingest<A>>::ActorId: FromRequest + AsRef<Url>,
|
||||||
<I as Ingest<A>>::Error: From<<I::Repo as Repo>::Error>,
|
<I::Ingest as Ingest<A>>::Error: From<<I::Repo as Repo>::Error>,
|
||||||
V: RepoFactory
|
V: RepoFactory
|
||||||
+ SessionFactory
|
+ SessionFactory
|
||||||
+ PublicKeyRepoFactory
|
+ PublicKeyRepoFactory
|
||||||
|
@ -183,7 +183,7 @@ where
|
||||||
|
|
||||||
service_config.service(
|
service_config.service(
|
||||||
web::scope("")
|
web::scope("")
|
||||||
.app_data(web::Data::new(ingest))
|
.app_data(web::Data::new(ingest_factory))
|
||||||
.wrap(digest)
|
.wrap(digest)
|
||||||
.wrap(signature)
|
.wrap(signature)
|
||||||
.route("", web::post().to(inbox_handler::<A, I>)),
|
.route("", web::post().to(inbox_handler::<A, I>)),
|
||||||
|
@ -273,29 +273,30 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn inbox_handler<A, I>(
|
async fn inbox_handler<A, I>(
|
||||||
ingest: web::Data<I>,
|
ingest_factory: web::Data<I>,
|
||||||
authority: Option<SignatureVerified>,
|
authority: Option<SignatureVerified>,
|
||||||
activity: web::Json<A>,
|
activity: web::Json<A>,
|
||||||
actor_id: I::ActorId,
|
actor_id: <I::Ingest as Ingest<A>>::ActorId,
|
||||||
) -> HttpResponse
|
) -> HttpResponse
|
||||||
where
|
where
|
||||||
A: for<'de> serde::de::Deserialize<'de> + 'static,
|
A: for<'de> serde::de::Deserialize<'de> + 'static,
|
||||||
I: Ingest<A> + PrivateKeyRepoFactory + RepoFactory + SessionFactory,
|
I: IngestFactory<A> + PrivateKeyRepoFactory + RepoFactory + SessionFactory,
|
||||||
I::PrivateKeyRepo: PrivateKeyRepo<PrivateKey = I::Crypto>,
|
I::PrivateKeyRepo: PrivateKeyRepo<PrivateKey = I::Crypto>,
|
||||||
I::ActorId: FromRequest + AsRef<Url>,
|
<I::Ingest as Ingest<A>>::ActorId: FromRequest + AsRef<Url>,
|
||||||
I::Error: From<<I::Repo as Repo>::Error>,
|
<I::Ingest as Ingest<A>>::Error: From<<I::Repo as Repo>::Error>,
|
||||||
{
|
{
|
||||||
let url = actor_id.as_ref();
|
let url = actor_id.as_ref();
|
||||||
|
|
||||||
let private_key_repo = ingest.build_private_key_repo();
|
let private_key_repo = ingest_factory.build_private_key_repo();
|
||||||
let private_key = match private_key_repo.fetch(url).await {
|
let private_key = match private_key_repo.fetch(url).await {
|
||||||
Ok(private_key) => private_key,
|
Ok(private_key) => private_key,
|
||||||
Err(_) => return HttpResponse::BadRequest().finish(),
|
Err(_) => return HttpResponse::BadRequest().finish(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let activity = activity.into_inner();
|
let activity = activity.into_inner();
|
||||||
let remote_repo = ingest.build_repo(private_key);
|
let remote_repo = ingest_factory.build_repo(private_key);
|
||||||
let mut session = ingest.build_session();
|
let mut session = ingest_factory.build_session();
|
||||||
|
let ingest = ingest_factory.build_ingest();
|
||||||
|
|
||||||
if let Some(auth) = authority {
|
if let Some(auth) = authority {
|
||||||
if let Ok(url) = auth.key_id().parse() {
|
if let Ok(url) = auth.key_id().parse() {
|
||||||
|
|
|
@ -107,6 +107,15 @@ pub trait Ingest<Object> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Describes a type that can produce an Ingest
|
||||||
|
pub trait IngestFactory<A> {
|
||||||
|
/// The Ingest type
|
||||||
|
type Ingest: Ingest<A>;
|
||||||
|
|
||||||
|
/// Build the ingest type
|
||||||
|
fn build_ingest(&self) -> Self::Ingest;
|
||||||
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Authority {
|
impl std::fmt::Display for Authority {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
|
130
examples/actix-web-example/src/ingest.rs
Normal file
130
examples/actix-web-example/src/ingest.rs
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
use crate::{MemoryRepo, PrivateKeyStore, ServerError};
|
||||||
|
use actix_web::FromRequest;
|
||||||
|
use apub::{
|
||||||
|
activitypub::{keys::PrivateKeyRepoFactory, simple::SimpleActor},
|
||||||
|
clients::{awc::SignatureConfig as AwcSignatureConfig, AwcClient},
|
||||||
|
cryptography::Rustcrypto,
|
||||||
|
ingest::{
|
||||||
|
validate_authority, validate_hosts, validate_inbox,
|
||||||
|
validators::{InboxType, ValidateAuthority, ValidateHosts, ValidateInbox},
|
||||||
|
Authority, IngestFactory,
|
||||||
|
},
|
||||||
|
servers::actix_web::Config,
|
||||||
|
session::{BreakerSession, RequestCountSession, SessionFactory},
|
||||||
|
Dereference, Ingest, Repo, RepoFactory, Session,
|
||||||
|
};
|
||||||
|
use example_types::AcceptedActivity;
|
||||||
|
use std::future::{ready, Ready};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(super) struct ActivityIngester {
|
||||||
|
pub(super) repo: MemoryRepo,
|
||||||
|
pub(super) private_key_store: PrivateKeyStore,
|
||||||
|
pub(super) session: BreakerSession,
|
||||||
|
pub(super) client: awc::Client,
|
||||||
|
pub(super) signature_config: AwcSignatureConfig,
|
||||||
|
pub(super) config: Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct InboxActor(pub(super) Url);
|
||||||
|
|
||||||
|
pub(super) struct ActorId(pub(super) Url);
|
||||||
|
|
||||||
|
impl PrivateKeyRepoFactory for ActivityIngester {
|
||||||
|
type PrivateKeyRepo = PrivateKeyStore;
|
||||||
|
|
||||||
|
fn build_private_key_repo(&self) -> Self::PrivateKeyRepo {
|
||||||
|
self.private_key_store.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IngestFactory<AcceptedActivity> for ActivityIngester {
|
||||||
|
type Ingest = ValidateHosts<ValidateInbox<ValidateAuthority<ActivityIngester>>>;
|
||||||
|
|
||||||
|
fn build_ingest(&self) -> Self::Ingest {
|
||||||
|
validate_hosts(validate_inbox(validate_authority(self.clone())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Ingest<AcceptedActivity> for ActivityIngester {
|
||||||
|
type Local = MemoryRepo;
|
||||||
|
type Error = ServerError;
|
||||||
|
type ActorId = InboxActor;
|
||||||
|
|
||||||
|
fn local_repo(&self) -> &Self::Local {
|
||||||
|
&self.repo
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_local(&self, url: &Url) -> bool {
|
||||||
|
self.config.is_local(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ingest<R: Repo, S: Session>(
|
||||||
|
&self,
|
||||||
|
_authority: Authority,
|
||||||
|
_actor_id: Self::ActorId,
|
||||||
|
activity: &AcceptedActivity,
|
||||||
|
_remote_repo: R,
|
||||||
|
_session: S,
|
||||||
|
) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
Self::Error: From<R::Error>,
|
||||||
|
{
|
||||||
|
Ok(self.repo.insert(activity.id().clone(), activity)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RepoFactory for ActivityIngester {
|
||||||
|
type Crypto = Rustcrypto;
|
||||||
|
type Repo = AwcClient<Rustcrypto>;
|
||||||
|
|
||||||
|
fn build_repo(&self, crypto: Self::Crypto) -> Self::Repo {
|
||||||
|
AwcClient::new(self.client.clone(), self.signature_config.clone(), crypto)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionFactory for ActivityIngester {
|
||||||
|
type Session = (RequestCountSession, BreakerSession);
|
||||||
|
|
||||||
|
fn build_session(&self) -> Self::Session {
|
||||||
|
(RequestCountSession::max(25), self.session.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromRequest for InboxActor {
|
||||||
|
type Error = actix_web::Error;
|
||||||
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
|
|
||||||
|
fn from_request(_: &actix_web::HttpRequest, _: &mut actix_web::dev::Payload) -> Self::Future {
|
||||||
|
ready(Ok(InboxActor(
|
||||||
|
Url::parse("http://localhost:8008/actors/localhost").unwrap(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InboxType for InboxActor {
|
||||||
|
fn is_shared(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<Url> for InboxActor {
|
||||||
|
fn as_ref(&self) -> &Url {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dereference for ActorId {
|
||||||
|
type Output = SimpleActor;
|
||||||
|
fn url(&self) -> &Url {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Url> for ActorId {
|
||||||
|
fn from(url: Url) -> Self {
|
||||||
|
ActorId(url)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use actix_web::{middleware::Logger, web, App, FromRequest, HttpServer, ResponseError};
|
use actix_web::{middleware::Logger, web, App, HttpServer, ResponseError};
|
||||||
use apub::{
|
use apub::{
|
||||||
activitypub::{
|
activitypub::{
|
||||||
keys::{
|
keys::{
|
||||||
|
@ -16,27 +16,27 @@ use apub::{
|
||||||
signature::PrivateKeyBuilder,
|
signature::PrivateKeyBuilder,
|
||||||
DigestFactory, PrivateKey, Rustcrypto, VerifyFactory,
|
DigestFactory, PrivateKey, Rustcrypto, VerifyFactory,
|
||||||
},
|
},
|
||||||
ingest::Authority,
|
ingest::validators::{AuthorityError, HostError, InboxError},
|
||||||
servers::actix_web::{
|
servers::actix_web::{
|
||||||
inbox, serve_objects, Config, SignatureConfig as ActixWebSignatureConfig, VerifyError,
|
inbox, serve_objects, Config, SignatureConfig as ActixWebSignatureConfig, VerifyError,
|
||||||
},
|
},
|
||||||
session::{BreakerSession, RequestCountSession, SessionFactory},
|
session::{BreakerSession, RequestCountSession, SessionFactory},
|
||||||
Dereference, Ingest, Repo, RepoFactory, Session,
|
Dereference, Repo, RepoFactory, Session,
|
||||||
};
|
};
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use example_types::{AcceptedActivity, ActivityType, ObjectId};
|
use example_types::{ActivityType, ObjectId};
|
||||||
use rsa::{
|
use rsa::{
|
||||||
pkcs8::{ToPrivateKey, ToPublicKey},
|
pkcs8::{ToPrivateKey, ToPublicKey},
|
||||||
RsaPrivateKey,
|
RsaPrivateKey,
|
||||||
};
|
};
|
||||||
use serde_json::Map;
|
use serde_json::Map;
|
||||||
use std::{
|
use std::{sync::Arc, time::Duration};
|
||||||
future::{ready, Ready},
|
|
||||||
sync::Arc,
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
use url::{Host, Url};
|
use url::{Host, Url};
|
||||||
|
|
||||||
|
mod ingest;
|
||||||
|
|
||||||
|
use ingest::{ActivityIngester, ActorId};
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
struct MemoryRepo {
|
struct MemoryRepo {
|
||||||
inner: Arc<DashMap<Url, serde_json::Value>>,
|
inner: Arc<DashMap<Url, serde_json::Value>>,
|
||||||
|
@ -56,16 +56,6 @@ struct RequestVerifier {
|
||||||
signature_config: AwcSignatureConfig,
|
signature_config: AwcSignatureConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct ActivityIngester {
|
|
||||||
repo: MemoryRepo,
|
|
||||||
private_key_store: PrivateKeyStore,
|
|
||||||
session: BreakerSession,
|
|
||||||
client: awc::Client,
|
|
||||||
signature_config: AwcSignatureConfig,
|
|
||||||
config: Config,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
enum ServerError {
|
enum ServerError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
@ -85,6 +75,15 @@ enum ServerError {
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Key(#[from] KeyError),
|
Key(#[from] KeyError),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
Authority(#[from] AuthorityError),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
Inbox(#[from] InboxError),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
Hosts(#[from] HostError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PublicKeyError<serde_json::Error, AwcError<RustcryptoError>>> for ServerError {
|
impl From<PublicKeyError<serde_json::Error, AwcError<RustcryptoError>>> for ServerError {
|
||||||
|
@ -203,14 +202,6 @@ impl PrivateKeyRepoFactory for RequestVerifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrivateKeyRepoFactory for ActivityIngester {
|
|
||||||
type PrivateKeyRepo = PrivateKeyStore;
|
|
||||||
|
|
||||||
fn build_private_key_repo(&self) -> Self::PrivateKeyRepo {
|
|
||||||
self.private_key_store.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RepoFactory for MemoryRepo {
|
impl RepoFactory for MemoryRepo {
|
||||||
type Crypto = ();
|
type Crypto = ();
|
||||||
type Repo = MemoryRepo;
|
type Repo = MemoryRepo;
|
||||||
|
@ -253,84 +244,6 @@ impl DigestFactory for RequestVerifier {
|
||||||
type Digest = Sha256Digest;
|
type Digest = Sha256Digest;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InboxActor(Url);
|
|
||||||
|
|
||||||
impl FromRequest for InboxActor {
|
|
||||||
type Error = actix_web::Error;
|
|
||||||
type Future = Ready<Result<Self, Self::Error>>;
|
|
||||||
|
|
||||||
fn from_request(_: &actix_web::HttpRequest, _: &mut actix_web::dev::Payload) -> Self::Future {
|
|
||||||
ready(Ok(InboxActor(
|
|
||||||
Url::parse("http://localhost:8008/actors/localhost").unwrap(),
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<Url> for InboxActor {
|
|
||||||
fn as_ref(&self) -> &Url {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
|
||||||
impl Ingest<AcceptedActivity> for ActivityIngester {
|
|
||||||
type Local = MemoryRepo;
|
|
||||||
type Error = ServerError;
|
|
||||||
type ActorId = InboxActor;
|
|
||||||
|
|
||||||
fn local_repo(&self) -> &Self::Local {
|
|
||||||
&self.repo
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_local(&self, url: &Url) -> bool {
|
|
||||||
self.config.is_local(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn ingest<R: Repo, S: Session>(
|
|
||||||
&self,
|
|
||||||
_authority: Authority,
|
|
||||||
_actor_id: Self::ActorId,
|
|
||||||
activity: &AcceptedActivity,
|
|
||||||
_remote_repo: R,
|
|
||||||
_session: S,
|
|
||||||
) -> Result<(), Self::Error>
|
|
||||||
where
|
|
||||||
Self::Error: From<R::Error>,
|
|
||||||
{
|
|
||||||
Ok(self.repo.insert(activity.id().clone(), activity)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RepoFactory for ActivityIngester {
|
|
||||||
type Crypto = Rustcrypto;
|
|
||||||
type Repo = AwcClient<Rustcrypto>;
|
|
||||||
|
|
||||||
fn build_repo(&self, crypto: Self::Crypto) -> Self::Repo {
|
|
||||||
AwcClient::new(self.client.clone(), self.signature_config.clone(), crypto)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SessionFactory for ActivityIngester {
|
|
||||||
type Session = (RequestCountSession, BreakerSession);
|
|
||||||
|
|
||||||
fn build_session(&self) -> Self::Session {
|
|
||||||
(RequestCountSession::max(25), self.session.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ActorId(Url);
|
|
||||||
impl Dereference for ActorId {
|
|
||||||
type Output = SimpleActor;
|
|
||||||
fn url(&self) -> &Url {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<Url> for ActorId {
|
|
||||||
fn from(url: Url) -> Self {
|
|
||||||
ActorId(url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if std::env::var("RUST_LOG").is_ok() {
|
if std::env::var("RUST_LOG").is_ok() {
|
||||||
|
|
|
@ -132,7 +132,7 @@ pub mod clients {
|
||||||
pub mod ingest {
|
pub mod ingest {
|
||||||
//! Utilities for ingesting activities
|
//! Utilities for ingesting activities
|
||||||
|
|
||||||
pub use apub_core::ingest::Authority;
|
pub use apub_core::ingest::{Authority, IngestFactory};
|
||||||
|
|
||||||
#[cfg(feature = "apub-ingest")]
|
#[cfg(feature = "apub-ingest")]
|
||||||
pub use apub_ingest::{validate_authority, validate_hosts, validate_inbox};
|
pub use apub_ingest::{validate_authority, validate_hosts, validate_inbox};
|
||||||
|
|
Loading…
Reference in a new issue