Changes to make Lemmy work with 0.4 (#29)
* Make it work with Lemmy * working but needs cleanup * almost everything working * debug * stack overflow fix
This commit is contained in:
parent
6b3a4f8942
commit
6a65fa7c98
31 changed files with 427 additions and 269 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -4,7 +4,7 @@ version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "activitypub_federation"
|
name = "activitypub_federation"
|
||||||
version = "0.4.0-rc1"
|
version = "0.4.0-rc2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activitystreams-kinds",
|
"activitystreams-kinds",
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
|
|
|
@ -9,7 +9,7 @@ The next step is to allow other servers to fetch our actors and objects. For thi
|
||||||
# use activitypub_federation::axum::json::ApubJson;
|
# use activitypub_federation::axum::json::ApubJson;
|
||||||
# use anyhow::Error;
|
# use anyhow::Error;
|
||||||
# use activitypub_federation::traits::tests::Person;
|
# use activitypub_federation::traits::tests::Person;
|
||||||
# use activitypub_federation::config::RequestData;
|
# use activitypub_federation::config::Data;
|
||||||
# use activitypub_federation::traits::tests::DbConnection;
|
# use activitypub_federation::traits::tests::DbConnection;
|
||||||
# use axum::extract::Path;
|
# use axum::extract::Path;
|
||||||
# use activitypub_federation::config::ApubMiddleware;
|
# use activitypub_federation::config::ApubMiddleware;
|
||||||
|
@ -20,7 +20,7 @@ The next step is to allow other servers to fetch our actors and objects. For thi
|
||||||
# use axum::TypedHeader;
|
# use axum::TypedHeader;
|
||||||
# use axum::response::IntoResponse;
|
# use axum::response::IntoResponse;
|
||||||
# use http::HeaderMap;
|
# use http::HeaderMap;
|
||||||
# async fn generate_user_html(_: String, _: RequestData<DbConnection>) -> axum::response::Response { todo!() }
|
# async fn generate_user_html(_: String, _: Data<DbConnection>) -> axum::response::Response { todo!() }
|
||||||
|
|
||||||
#[actix_rt::main]
|
#[actix_rt::main]
|
||||||
async fn main() -> Result<(), Error> {
|
async fn main() -> Result<(), Error> {
|
||||||
|
@ -44,7 +44,7 @@ async fn main() -> Result<(), Error> {
|
||||||
async fn http_get_user(
|
async fn http_get_user(
|
||||||
header_map: HeaderMap,
|
header_map: HeaderMap,
|
||||||
Path(name): Path<String>,
|
Path(name): Path<String>,
|
||||||
data: RequestData<DbConnection>,
|
data: Data<DbConnection>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let accept = header_map.get("accept").map(|v| v.to_str().unwrap());
|
let accept = header_map.get("accept").map(|v| v.to_str().unwrap());
|
||||||
if accept == Some(APUB_JSON_CONTENT_TYPE) {
|
if accept == Some(APUB_JSON_CONTENT_TYPE) {
|
||||||
|
@ -71,7 +71,7 @@ To do this we can implement the following HTTP handler which must be bound to pa
|
||||||
```rust
|
```rust
|
||||||
# use serde::Deserialize;
|
# use serde::Deserialize;
|
||||||
# use axum::{extract::Query, Json};
|
# use axum::{extract::Query, Json};
|
||||||
# use activitypub_federation::config::RequestData;
|
# use activitypub_federation::config::Data;
|
||||||
# use activitypub_federation::fetch::webfinger::Webfinger;
|
# use activitypub_federation::fetch::webfinger::Webfinger;
|
||||||
# use anyhow::Error;
|
# use anyhow::Error;
|
||||||
# use activitypub_federation::traits::tests::DbConnection;
|
# use activitypub_federation::traits::tests::DbConnection;
|
||||||
|
@ -85,7 +85,7 @@ struct WebfingerQuery {
|
||||||
|
|
||||||
async fn webfinger(
|
async fn webfinger(
|
||||||
Query(query): Query<WebfingerQuery>,
|
Query(query): Query<WebfingerQuery>,
|
||||||
data: RequestData<DbConnection>,
|
data: Data<DbConnection>,
|
||||||
) -> Result<Json<Webfinger>, Error> {
|
) -> Result<Json<Webfinger>, Error> {
|
||||||
let name = extract_webfinger_name(&query.resource, &data)?;
|
let name = extract_webfinger_name(&query.resource, &data)?;
|
||||||
let db_user = data.read_local_user(name).await?;
|
let db_user = data.read_local_user(name).await?;
|
||||||
|
|
|
@ -13,7 +13,7 @@ let config = FederationConfig::builder()
|
||||||
.domain("example.com")
|
.domain("example.com")
|
||||||
.app_data(db_connection)
|
.app_data(db_connection)
|
||||||
.build()?;
|
.build()?;
|
||||||
let user_id = ObjectId::<DbUser>::new("https://mastodon.social/@LemmyDev")?;
|
let user_id = ObjectId::<DbUser>::parse("https://mastodon.social/@LemmyDev")?;
|
||||||
let data = config.to_request_data();
|
let data = config.to_request_data();
|
||||||
let user = user_id.dereference(&data).await;
|
let user = user_id.dereference(&data).await;
|
||||||
assert!(user.is_ok());
|
assert!(user.is_ok());
|
||||||
|
|
|
@ -11,7 +11,7 @@ Activitypub propagates actions across servers using `Activities`. For this each
|
||||||
# use activitypub_federation::traits::tests::{DbConnection, DbUser};
|
# use activitypub_federation::traits::tests::{DbConnection, DbUser};
|
||||||
# use activitystreams_kinds::activity::FollowType;
|
# use activitystreams_kinds::activity::FollowType;
|
||||||
# use activitypub_federation::traits::ActivityHandler;
|
# use activitypub_federation::traits::ActivityHandler;
|
||||||
# use activitypub_federation::config::RequestData;
|
# use activitypub_federation::config::Data;
|
||||||
# async fn send_accept() -> Result<(), Error> { Ok(()) }
|
# async fn send_accept() -> Result<(), Error> { Ok(()) }
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
|
@ -37,11 +37,11 @@ impl ActivityHandler for Follow {
|
||||||
self.actor.inner()
|
self.actor.inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn verify(&self, _data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
|
async fn verify(&self, _data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
|
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
let actor = self.actor.dereference(data).await?;
|
let actor = self.actor.dereference(data).await?;
|
||||||
let followed = self.object.dereference(data).await?;
|
let followed = self.object.dereference(data).await?;
|
||||||
data.add_follower(followed, actor).await?;
|
data.add_follower(followed, actor).await?;
|
||||||
|
@ -57,7 +57,7 @@ Next its time to setup the actual HTTP handler for the inbox. For this we first
|
||||||
```
|
```
|
||||||
# use axum::response::IntoResponse;
|
# use axum::response::IntoResponse;
|
||||||
# use activitypub_federation::axum::inbox::{ActivityData, receive_activity};
|
# use activitypub_federation::axum::inbox::{ActivityData, receive_activity};
|
||||||
# use activitypub_federation::config::RequestData;
|
# use activitypub_federation::config::Data;
|
||||||
# use activitypub_federation::protocol::context::WithContext;
|
# use activitypub_federation::protocol::context::WithContext;
|
||||||
# use activitypub_federation::traits::ActivityHandler;
|
# use activitypub_federation::traits::ActivityHandler;
|
||||||
# use activitypub_federation::traits::tests::{DbConnection, DbUser, Follow};
|
# use activitypub_federation::traits::tests::{DbConnection, DbUser, Follow};
|
||||||
|
@ -72,7 +72,7 @@ pub enum PersonAcceptedActivities {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn http_post_user_inbox(
|
async fn http_post_user_inbox(
|
||||||
data: RequestData<DbConnection>,
|
data: Data<DbConnection>,
|
||||||
activity_data: ActivityData,
|
activity_data: ActivityData,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
receive_activity::<WithContext<PersonAcceptedActivities>, DbUser, DbConnection>(
|
receive_activity::<WithContext<PersonAcceptedActivities>, DbUser, DbConnection>(
|
||||||
|
|
|
@ -17,17 +17,16 @@ To send an activity we need to initialize our previously defined struct, and pic
|
||||||
# .app_data(db_connection)
|
# .app_data(db_connection)
|
||||||
# .build()?;
|
# .build()?;
|
||||||
# let data = config.to_request_data();
|
# let data = config.to_request_data();
|
||||||
|
# let sender = DB_USER.clone();
|
||||||
# let recipient = DB_USER.clone();
|
# let recipient = DB_USER.clone();
|
||||||
// Each actor has a keypair. Generate it on signup and store it in the database.
|
|
||||||
let keypair = generate_actor_keypair()?;
|
|
||||||
let activity = Follow {
|
let activity = Follow {
|
||||||
actor: ObjectId::new("https://lemmy.ml/u/nutomic")?,
|
actor: ObjectId::parse("https://lemmy.ml/u/nutomic")?,
|
||||||
object: recipient.apub_id.clone().into(),
|
object: recipient.apub_id.clone().into(),
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
id: "https://lemmy.ml/activities/321".try_into()?
|
id: "https://lemmy.ml/activities/321".try_into()?
|
||||||
};
|
};
|
||||||
let inboxes = vec![recipient.shared_inbox_or_inbox()];
|
let inboxes = vec![recipient.shared_inbox_or_inbox()];
|
||||||
send_activity(activity, keypair.private_key, inboxes, &data).await?;
|
send_activity(activity, &sender, inboxes, &data).await?;
|
||||||
# Ok::<(), anyhow::Error>(())
|
# Ok::<(), anyhow::Error>(())
|
||||||
# }).unwrap()
|
# }).unwrap()
|
||||||
```
|
```
|
||||||
|
|
|
@ -9,7 +9,7 @@ It is sometimes necessary to fetch from a URL, but we don't know the exact type
|
||||||
# use activitypub_federation::config::FederationConfig;
|
# use activitypub_federation::config::FederationConfig;
|
||||||
# use serde::{Deserialize, Serialize};
|
# use serde::{Deserialize, Serialize};
|
||||||
# use activitypub_federation::traits::tests::DbConnection;
|
# use activitypub_federation::traits::tests::DbConnection;
|
||||||
# use activitypub_federation::config::RequestData;
|
# use activitypub_federation::config::Data;
|
||||||
# use url::Url;
|
# use url::Url;
|
||||||
# use activitypub_federation::traits::tests::{Person, Note};
|
# use activitypub_federation::traits::tests::{Person, Note};
|
||||||
|
|
||||||
|
@ -33,25 +33,25 @@ impl ApubObject for SearchableDbObjects {
|
||||||
|
|
||||||
async fn read_from_apub_id(
|
async fn read_from_apub_id(
|
||||||
object_id: Url,
|
object_id: Url,
|
||||||
data: &RequestData<Self::DataType>,
|
data: &Data<Self::DataType>,
|
||||||
) -> Result<Option<Self>, Self::Error> {
|
) -> Result<Option<Self>, Self::Error> {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn into_apub(
|
async fn into_apub(
|
||||||
self,
|
self,
|
||||||
data: &RequestData<Self::DataType>,
|
data: &Data<Self::DataType>,
|
||||||
) -> Result<Self::ApubType, Self::Error> {
|
) -> Result<Self::ApubType, Self::Error> {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn verify(apub: &Self::ApubType, expected_domain: &Url, _data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
|
async fn verify(apub: &Self::ApubType, expected_domain: &Url, _data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn from_apub(
|
async fn from_apub(
|
||||||
apub: Self::ApubType,
|
apub: Self::ApubType,
|
||||||
data: &RequestData<Self::DataType>,
|
data: &Data<Self::DataType>,
|
||||||
) -> Result<Self, Self::Error> {
|
) -> Result<Self, Self::Error> {
|
||||||
use SearchableDbObjects::*;
|
use SearchableDbObjects::*;
|
||||||
match apub {
|
match apub {
|
||||||
|
@ -66,7 +66,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
# let config = FederationConfig::builder().domain("example.com").app_data(DbConnection).build().unwrap();
|
# let config = FederationConfig::builder().domain("example.com").app_data(DbConnection).build().unwrap();
|
||||||
# let data = config.to_request_data();
|
# let data = config.to_request_data();
|
||||||
let query = "https://example.com/id/413";
|
let query = "https://example.com/id/413";
|
||||||
let query_result = ObjectId::<SearchableDbObjects>::new(query)?
|
let query_result = ObjectId::<SearchableDbObjects>::parse(query)?
|
||||||
.dereference(&data)
|
.dereference(&data)
|
||||||
.await?;
|
.await?;
|
||||||
match query_result {
|
match query_result {
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
activity_queue::send_activity,
|
activity_queue::send_activity,
|
||||||
config::RequestData,
|
config::Data,
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
kinds::activity::CreateType,
|
kinds::activity::CreateType,
|
||||||
protocol::{context::WithContext, helpers::deserialize_one_or_many},
|
protocol::{context::WithContext, helpers::deserialize_one_or_many},
|
||||||
|
@ -29,11 +29,7 @@ pub struct CreatePost {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreatePost {
|
impl CreatePost {
|
||||||
pub async fn send(
|
pub async fn send(note: Note, inbox: Url, data: &Data<DatabaseHandle>) -> Result<(), Error> {
|
||||||
note: Note,
|
|
||||||
inbox: Url,
|
|
||||||
data: &RequestData<DatabaseHandle>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
print!("Sending reply to {}", ¬e.attributed_to);
|
print!("Sending reply to {}", ¬e.attributed_to);
|
||||||
let create = CreatePost {
|
let create = CreatePost {
|
||||||
actor: note.attributed_to.clone(),
|
actor: note.attributed_to.clone(),
|
||||||
|
@ -43,11 +39,7 @@ impl CreatePost {
|
||||||
id: generate_object_id(data.domain())?,
|
id: generate_object_id(data.domain())?,
|
||||||
};
|
};
|
||||||
let create_with_context = WithContext::new_default(create);
|
let create_with_context = WithContext::new_default(create);
|
||||||
let private_key = data
|
send_activity(create_with_context, &data.local_user(), vec![inbox], data).await?;
|
||||||
.local_user()
|
|
||||||
.private_key
|
|
||||||
.expect("local user always has private key");
|
|
||||||
send_activity(create_with_context, private_key, vec![inbox], data).await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,12 +57,12 @@ impl ActivityHandler for CreatePost {
|
||||||
self.actor.inner()
|
self.actor.inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn verify(&self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
|
async fn verify(&self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
DbPost::verify(&self.object, &self.id, data).await?;
|
DbPost::verify(&self.object, &self.id, data).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
|
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
DbPost::from_apub(self.object, data).await?;
|
DbPost::from_apub(self.object, data).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use activitypub_federation::{
|
||||||
inbox::{receive_activity, ActivityData},
|
inbox::{receive_activity, ActivityData},
|
||||||
json::ApubJson,
|
json::ApubJson,
|
||||||
},
|
},
|
||||||
config::RequestData,
|
config::Data,
|
||||||
fetch::webfinger::{build_webfinger_response, extract_webfinger_name, Webfinger},
|
fetch::webfinger::{build_webfinger_response, extract_webfinger_name, Webfinger},
|
||||||
protocol::context::WithContext,
|
protocol::context::WithContext,
|
||||||
traits::ApubObject,
|
traits::ApubObject,
|
||||||
|
@ -31,7 +31,7 @@ impl IntoResponse for Error {
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
pub async fn http_get_user(
|
pub async fn http_get_user(
|
||||||
Path(name): Path<String>,
|
Path(name): Path<String>,
|
||||||
data: RequestData<DatabaseHandle>,
|
data: Data<DatabaseHandle>,
|
||||||
) -> Result<ApubJson<WithContext<Person>>, Error> {
|
) -> Result<ApubJson<WithContext<Person>>, Error> {
|
||||||
let db_user = data.read_user(&name)?;
|
let db_user = data.read_user(&name)?;
|
||||||
let apub_user = db_user.into_apub(&data).await?;
|
let apub_user = db_user.into_apub(&data).await?;
|
||||||
|
@ -40,7 +40,7 @@ pub async fn http_get_user(
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
pub async fn http_post_user_inbox(
|
pub async fn http_post_user_inbox(
|
||||||
data: RequestData<DatabaseHandle>,
|
data: Data<DatabaseHandle>,
|
||||||
activity_data: ActivityData,
|
activity_data: ActivityData,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
receive_activity::<WithContext<PersonAcceptedActivities>, DbUser, DatabaseHandle>(
|
receive_activity::<WithContext<PersonAcceptedActivities>, DbUser, DatabaseHandle>(
|
||||||
|
@ -58,7 +58,7 @@ pub struct WebfingerQuery {
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
pub async fn webfinger(
|
pub async fn webfinger(
|
||||||
Query(query): Query<WebfingerQuery>,
|
Query(query): Query<WebfingerQuery>,
|
||||||
data: RequestData<DatabaseHandle>,
|
data: Data<DatabaseHandle>,
|
||||||
) -> Result<Json<Webfinger>, Error> {
|
) -> Result<Json<Webfinger>, Error> {
|
||||||
let name = extract_webfinger_name(&query.resource, &data)?;
|
let name = extract_webfinger_name(&query.resource, &data)?;
|
||||||
let db_user = data.read_user(&name)?;
|
let db_user = data.read_user(&name)?;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{activities::create_post::CreatePost, database::DatabaseHandle, error::Error};
|
use crate::{activities::create_post::CreatePost, database::DatabaseHandle, error::Error};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::RequestData,
|
config::Data,
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
http_signatures::generate_actor_keypair,
|
http_signatures::generate_actor_keypair,
|
||||||
kinds::actor::PersonType,
|
kinds::actor::PersonType,
|
||||||
|
@ -75,7 +75,7 @@ impl ApubObject for DbUser {
|
||||||
|
|
||||||
async fn read_from_apub_id(
|
async fn read_from_apub_id(
|
||||||
object_id: Url,
|
object_id: Url,
|
||||||
data: &RequestData<Self::DataType>,
|
data: &Data<Self::DataType>,
|
||||||
) -> Result<Option<Self>, Self::Error> {
|
) -> Result<Option<Self>, Self::Error> {
|
||||||
let users = data.users.lock().unwrap();
|
let users = data.users.lock().unwrap();
|
||||||
let res = users
|
let res = users
|
||||||
|
@ -85,24 +85,20 @@ impl ApubObject for DbUser {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn into_apub(
|
async fn into_apub(self, _data: &Data<Self::DataType>) -> Result<Self::ApubType, Self::Error> {
|
||||||
self,
|
|
||||||
_data: &RequestData<Self::DataType>,
|
|
||||||
) -> Result<Self::ApubType, Self::Error> {
|
|
||||||
let public_key = PublicKey::new(self.ap_id.clone().into_inner(), self.public_key.clone());
|
|
||||||
Ok(Person {
|
Ok(Person {
|
||||||
preferred_username: self.name.clone(),
|
preferred_username: self.name.clone(),
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
id: self.ap_id.clone(),
|
id: self.ap_id.clone(),
|
||||||
inbox: self.inbox,
|
inbox: self.inbox.clone(),
|
||||||
public_key,
|
public_key: self.public_key(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn verify(
|
async fn verify(
|
||||||
apub: &Self::ApubType,
|
apub: &Self::ApubType,
|
||||||
expected_domain: &Url,
|
expected_domain: &Url,
|
||||||
_data: &RequestData<Self::DataType>,
|
_data: &Data<Self::DataType>,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
verify_domains_match(apub.id.inner(), expected_domain)?;
|
verify_domains_match(apub.id.inner(), expected_domain)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -110,7 +106,7 @@ impl ApubObject for DbUser {
|
||||||
|
|
||||||
async fn from_apub(
|
async fn from_apub(
|
||||||
apub: Self::ApubType,
|
apub: Self::ApubType,
|
||||||
_data: &RequestData<Self::DataType>,
|
_data: &Data<Self::DataType>,
|
||||||
) -> Result<Self, Self::Error> {
|
) -> Result<Self, Self::Error> {
|
||||||
Ok(DbUser {
|
Ok(DbUser {
|
||||||
name: apub.preferred_username,
|
name: apub.preferred_username,
|
||||||
|
@ -126,15 +122,19 @@ impl ApubObject for DbUser {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for DbUser {
|
impl Actor for DbUser {
|
||||||
|
fn id(&self) -> Url {
|
||||||
|
self.ap_id.inner().clone()
|
||||||
|
}
|
||||||
|
|
||||||
fn public_key_pem(&self) -> &str {
|
fn public_key_pem(&self) -> &str {
|
||||||
&self.public_key
|
&self.public_key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn private_key_pem(&self) -> Option<String> {
|
||||||
|
self.private_key.clone()
|
||||||
|
}
|
||||||
|
|
||||||
fn inbox(&self) -> Url {
|
fn inbox(&self) -> Url {
|
||||||
self.inbox.clone()
|
self.inbox.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> &Url {
|
|
||||||
self.ap_id.inner()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
||||||
objects::person::DbUser,
|
objects::person::DbUser,
|
||||||
};
|
};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::RequestData,
|
config::Data,
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
kinds::{object::NoteType, public},
|
kinds::{object::NoteType, public},
|
||||||
protocol::{helpers::deserialize_one_or_many, verification::verify_domains_match},
|
protocol::{helpers::deserialize_one_or_many, verification::verify_domains_match},
|
||||||
|
@ -53,22 +53,19 @@ impl ApubObject for DbPost {
|
||||||
|
|
||||||
async fn read_from_apub_id(
|
async fn read_from_apub_id(
|
||||||
_object_id: Url,
|
_object_id: Url,
|
||||||
_data: &RequestData<Self::DataType>,
|
_data: &Data<Self::DataType>,
|
||||||
) -> Result<Option<Self>, Self::Error> {
|
) -> Result<Option<Self>, Self::Error> {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn into_apub(
|
async fn into_apub(self, _data: &Data<Self::DataType>) -> Result<Self::ApubType, Self::Error> {
|
||||||
self,
|
|
||||||
_data: &RequestData<Self::DataType>,
|
|
||||||
) -> Result<Self::ApubType, Self::Error> {
|
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn verify(
|
async fn verify(
|
||||||
apub: &Self::ApubType,
|
apub: &Self::ApubType,
|
||||||
expected_domain: &Url,
|
expected_domain: &Url,
|
||||||
_data: &RequestData<Self::DataType>,
|
_data: &Data<Self::DataType>,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
verify_domains_match(apub.id.inner(), expected_domain)?;
|
verify_domains_match(apub.id.inner(), expected_domain)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -76,7 +73,7 @@ impl ApubObject for DbPost {
|
||||||
|
|
||||||
async fn from_apub(
|
async fn from_apub(
|
||||||
apub: Self::ApubType,
|
apub: Self::ApubType,
|
||||||
data: &RequestData<Self::DataType>,
|
data: &Data<Self::DataType>,
|
||||||
) -> Result<Self, Self::Error> {
|
) -> Result<Self, Self::Error> {
|
||||||
println!(
|
println!(
|
||||||
"Received post with content {} and id {}",
|
"Received post with content {} and id {}",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{activities::follow::Follow, instance::DatabaseHandle, objects::person::DbUser};
|
use crate::{activities::follow::Follow, instance::DatabaseHandle, objects::person::DbUser};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::RequestData,
|
config::Data,
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
kinds::activity::AcceptType,
|
kinds::activity::AcceptType,
|
||||||
traits::ActivityHandler,
|
traits::ActivityHandler,
|
||||||
|
@ -42,11 +42,11 @@ impl ActivityHandler for Accept {
|
||||||
self.actor.inner()
|
self.actor.inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn verify(&self, _data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
|
async fn verify(&self, _data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(self, _data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
|
async fn receive(self, _data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
||||||
DbPost,
|
DbPost,
|
||||||
};
|
};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::RequestData,
|
config::Data,
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
kinds::activity::CreateType,
|
kinds::activity::CreateType,
|
||||||
protocol::helpers::deserialize_one_or_many,
|
protocol::helpers::deserialize_one_or_many,
|
||||||
|
@ -50,12 +50,12 @@ impl ActivityHandler for CreatePost {
|
||||||
self.actor.inner()
|
self.actor.inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn verify(&self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
|
async fn verify(&self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
DbPost::verify(&self.object, &self.id, data).await?;
|
DbPost::verify(&self.object, &self.id, data).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
|
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
DbPost::from_apub(self.object, data).await?;
|
DbPost::from_apub(self.object, data).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
objects::person::DbUser,
|
objects::person::DbUser,
|
||||||
};
|
};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::RequestData,
|
config::Data,
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
kinds::activity::FollowType,
|
kinds::activity::FollowType,
|
||||||
traits::{ActivityHandler, Actor},
|
traits::{ActivityHandler, Actor},
|
||||||
|
@ -47,13 +47,13 @@ impl ActivityHandler for Follow {
|
||||||
self.actor.inner()
|
self.actor.inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn verify(&self, _data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
|
async fn verify(&self, _data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore clippy false positive: https://github.com/rust-lang/rust-clippy/issues/6446
|
// Ignore clippy false positive: https://github.com/rust-lang/rust-clippy/issues/6446
|
||||||
#[allow(clippy::await_holding_lock)]
|
#[allow(clippy::await_holding_lock)]
|
||||||
async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
|
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
// add to followers
|
// add to followers
|
||||||
let local_user = {
|
let local_user = {
|
||||||
let mut users = data.users.lock().unwrap();
|
let mut users = data.users.lock().unwrap();
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
actix_web::inbox::receive_activity,
|
actix_web::inbox::receive_activity,
|
||||||
config::{ApubMiddleware, FederationConfig, RequestData},
|
config::{ApubMiddleware, Data, FederationConfig},
|
||||||
fetch::webfinger::{build_webfinger_response, extract_webfinger_name},
|
fetch::webfinger::{build_webfinger_response, extract_webfinger_name},
|
||||||
protocol::context::WithContext,
|
protocol::context::WithContext,
|
||||||
traits::ApubObject,
|
traits::ApubObject,
|
||||||
|
@ -36,7 +36,7 @@ pub fn listen(config: &FederationConfig<DatabaseHandle>) -> Result<(), Error> {
|
||||||
/// Handles requests to fetch user json over HTTP
|
/// Handles requests to fetch user json over HTTP
|
||||||
pub async fn http_get_user(
|
pub async fn http_get_user(
|
||||||
user_name: web::Path<String>,
|
user_name: web::Path<String>,
|
||||||
data: RequestData<DatabaseHandle>,
|
data: Data<DatabaseHandle>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let db_user = data.local_user();
|
let db_user = data.local_user();
|
||||||
if user_name.into_inner() == db_user.name {
|
if user_name.into_inner() == db_user.name {
|
||||||
|
@ -53,7 +53,7 @@ pub async fn http_get_user(
|
||||||
pub async fn http_post_user_inbox(
|
pub async fn http_post_user_inbox(
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
body: Bytes,
|
body: Bytes,
|
||||||
data: RequestData<DatabaseHandle>,
|
data: Data<DatabaseHandle>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
receive_activity::<WithContext<PersonAcceptedActivities>, DbUser, DatabaseHandle>(
|
receive_activity::<WithContext<PersonAcceptedActivities>, DbUser, DatabaseHandle>(
|
||||||
request, body, &data,
|
request, body, &data,
|
||||||
|
@ -68,7 +68,7 @@ pub struct WebfingerQuery {
|
||||||
|
|
||||||
pub async fn webfinger(
|
pub async fn webfinger(
|
||||||
query: web::Query<WebfingerQuery>,
|
query: web::Query<WebfingerQuery>,
|
||||||
data: RequestData<DatabaseHandle>,
|
data: Data<DatabaseHandle>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let name = extract_webfinger_name(&query.resource, &data)?;
|
let name = extract_webfinger_name(&query.resource, &data)?;
|
||||||
let db_user = data.read_user(&name)?;
|
let db_user = data.read_user(&name)?;
|
||||||
|
|
|
@ -8,7 +8,7 @@ use activitypub_federation::{
|
||||||
inbox::{receive_activity, ActivityData},
|
inbox::{receive_activity, ActivityData},
|
||||||
json::ApubJson,
|
json::ApubJson,
|
||||||
},
|
},
|
||||||
config::{ApubMiddleware, FederationConfig, RequestData},
|
config::{ApubMiddleware, Data, FederationConfig},
|
||||||
fetch::webfinger::{build_webfinger_response, extract_webfinger_name, Webfinger},
|
fetch::webfinger::{build_webfinger_response, extract_webfinger_name, Webfinger},
|
||||||
protocol::context::WithContext,
|
protocol::context::WithContext,
|
||||||
traits::ApubObject,
|
traits::ApubObject,
|
||||||
|
@ -48,7 +48,7 @@ pub fn listen(config: &FederationConfig<DatabaseHandle>) -> Result<(), Error> {
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn http_get_user(
|
async fn http_get_user(
|
||||||
Path(name): Path<String>,
|
Path(name): Path<String>,
|
||||||
data: RequestData<DatabaseHandle>,
|
data: Data<DatabaseHandle>,
|
||||||
) -> Result<ApubJson<WithContext<Person>>, Error> {
|
) -> Result<ApubJson<WithContext<Person>>, Error> {
|
||||||
let db_user = data.read_user(&name)?;
|
let db_user = data.read_user(&name)?;
|
||||||
let apub_user = db_user.into_apub(&data).await?;
|
let apub_user = db_user.into_apub(&data).await?;
|
||||||
|
@ -57,7 +57,7 @@ async fn http_get_user(
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn http_post_user_inbox(
|
async fn http_post_user_inbox(
|
||||||
data: RequestData<DatabaseHandle>,
|
data: Data<DatabaseHandle>,
|
||||||
activity_data: ActivityData,
|
activity_data: ActivityData,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
receive_activity::<WithContext<PersonAcceptedActivities>, DbUser, DatabaseHandle>(
|
receive_activity::<WithContext<PersonAcceptedActivities>, DbUser, DatabaseHandle>(
|
||||||
|
@ -75,7 +75,7 @@ struct WebfingerQuery {
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn webfinger(
|
async fn webfinger(
|
||||||
Query(query): Query<WebfingerQuery>,
|
Query(query): Query<WebfingerQuery>,
|
||||||
data: RequestData<DatabaseHandle>,
|
data: Data<DatabaseHandle>,
|
||||||
) -> Result<Json<Webfinger>, Error> {
|
) -> Result<Json<Webfinger>, Error> {
|
||||||
let name = extract_webfinger_name(&query.resource, &data)?;
|
let name = extract_webfinger_name(&query.resource, &data)?;
|
||||||
let db_user = data.read_user(&name)?;
|
let db_user = data.read_user(&name)?;
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
activity_queue::send_activity,
|
activity_queue::send_activity,
|
||||||
config::RequestData,
|
config::Data,
|
||||||
fetch::{object_id::ObjectId, webfinger::webfinger_resolve_actor},
|
fetch::{object_id::ObjectId, webfinger::webfinger_resolve_actor},
|
||||||
http_signatures::generate_actor_keypair,
|
http_signatures::generate_actor_keypair,
|
||||||
kinds::actor::PersonType,
|
kinds::actor::PersonType,
|
||||||
|
@ -81,15 +81,7 @@ impl DbUser {
|
||||||
Ok(Url::parse(&format!("{}/followers", self.ap_id.inner()))?)
|
Ok(Url::parse(&format!("{}/followers", self.ap_id.inner()))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn public_key(&self) -> PublicKey {
|
pub async fn follow(&self, other: &str, data: &Data<DatabaseHandle>) -> Result<(), Error> {
|
||||||
PublicKey::new(self.ap_id.clone().into_inner(), self.public_key.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn follow(
|
|
||||||
&self,
|
|
||||||
other: &str,
|
|
||||||
data: &RequestData<DatabaseHandle>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let other: DbUser = webfinger_resolve_actor(other, data).await?;
|
let other: DbUser = webfinger_resolve_actor(other, data).await?;
|
||||||
let id = generate_object_id(data.domain())?;
|
let id = generate_object_id(data.domain())?;
|
||||||
let follow = Follow::new(self.ap_id.clone(), other.ap_id.clone(), id.clone());
|
let follow = Follow::new(self.ap_id.clone(), other.ap_id.clone(), id.clone());
|
||||||
|
@ -98,11 +90,7 @@ impl DbUser {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn post(
|
pub async fn post(&self, post: DbPost, data: &Data<DatabaseHandle>) -> Result<(), Error> {
|
||||||
&self,
|
|
||||||
post: DbPost,
|
|
||||||
data: &RequestData<DatabaseHandle>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let id = generate_object_id(data.domain())?;
|
let id = generate_object_id(data.domain())?;
|
||||||
let create = CreatePost::new(post.into_apub(data).await?, id.clone());
|
let create = CreatePost::new(post.into_apub(data).await?, id.clone());
|
||||||
let mut inboxes = vec![];
|
let mut inboxes = vec![];
|
||||||
|
@ -118,20 +106,14 @@ impl DbUser {
|
||||||
&self,
|
&self,
|
||||||
activity: Activity,
|
activity: Activity,
|
||||||
recipients: Vec<Url>,
|
recipients: Vec<Url>,
|
||||||
data: &RequestData<DatabaseHandle>,
|
data: &Data<DatabaseHandle>,
|
||||||
) -> Result<(), <Activity as ActivityHandler>::Error>
|
) -> Result<(), <Activity as ActivityHandler>::Error>
|
||||||
where
|
where
|
||||||
Activity: ActivityHandler + Serialize + Debug + Send + Sync,
|
Activity: ActivityHandler + Serialize + Debug + Send + Sync,
|
||||||
<Activity as ActivityHandler>::Error: From<anyhow::Error> + From<serde_json::Error>,
|
<Activity as ActivityHandler>::Error: From<anyhow::Error> + From<serde_json::Error>,
|
||||||
{
|
{
|
||||||
let activity = WithContext::new_default(activity);
|
let activity = WithContext::new_default(activity);
|
||||||
send_activity(
|
send_activity(activity, self, recipients, data).await?;
|
||||||
activity,
|
|
||||||
self.private_key.clone().unwrap(),
|
|
||||||
recipients,
|
|
||||||
data,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,7 +130,7 @@ impl ApubObject for DbUser {
|
||||||
|
|
||||||
async fn read_from_apub_id(
|
async fn read_from_apub_id(
|
||||||
object_id: Url,
|
object_id: Url,
|
||||||
data: &RequestData<Self::DataType>,
|
data: &Data<Self::DataType>,
|
||||||
) -> Result<Option<Self>, Self::Error> {
|
) -> Result<Option<Self>, Self::Error> {
|
||||||
let users = data.users.lock().unwrap();
|
let users = data.users.lock().unwrap();
|
||||||
let res = users
|
let res = users
|
||||||
|
@ -158,10 +140,7 @@ impl ApubObject for DbUser {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn into_apub(
|
async fn into_apub(self, _data: &Data<Self::DataType>) -> Result<Self::ApubType, Self::Error> {
|
||||||
self,
|
|
||||||
_data: &RequestData<Self::DataType>,
|
|
||||||
) -> Result<Self::ApubType, Self::Error> {
|
|
||||||
Ok(Person {
|
Ok(Person {
|
||||||
preferred_username: self.name.clone(),
|
preferred_username: self.name.clone(),
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
|
@ -174,7 +153,7 @@ impl ApubObject for DbUser {
|
||||||
async fn verify(
|
async fn verify(
|
||||||
apub: &Self::ApubType,
|
apub: &Self::ApubType,
|
||||||
expected_domain: &Url,
|
expected_domain: &Url,
|
||||||
_data: &RequestData<Self::DataType>,
|
_data: &Data<Self::DataType>,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
verify_domains_match(apub.id.inner(), expected_domain)?;
|
verify_domains_match(apub.id.inner(), expected_domain)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -182,7 +161,7 @@ impl ApubObject for DbUser {
|
||||||
|
|
||||||
async fn from_apub(
|
async fn from_apub(
|
||||||
apub: Self::ApubType,
|
apub: Self::ApubType,
|
||||||
data: &RequestData<Self::DataType>,
|
data: &Data<Self::DataType>,
|
||||||
) -> Result<Self, Self::Error> {
|
) -> Result<Self, Self::Error> {
|
||||||
let user = DbUser {
|
let user = DbUser {
|
||||||
name: apub.preferred_username,
|
name: apub.preferred_username,
|
||||||
|
@ -201,14 +180,18 @@ impl ApubObject for DbUser {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for DbUser {
|
impl Actor for DbUser {
|
||||||
fn id(&self) -> &Url {
|
fn id(&self) -> Url {
|
||||||
self.ap_id.inner()
|
self.ap_id.inner().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn public_key_pem(&self) -> &str {
|
fn public_key_pem(&self) -> &str {
|
||||||
&self.public_key
|
&self.public_key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn private_key_pem(&self) -> Option<String> {
|
||||||
|
self.private_key.clone()
|
||||||
|
}
|
||||||
|
|
||||||
fn inbox(&self) -> Url {
|
fn inbox(&self) -> Url {
|
||||||
self.inbox.clone()
|
self.inbox.clone()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{error::Error, generate_object_id, instance::DatabaseHandle, objects::person::DbUser};
|
use crate::{error::Error, generate_object_id, instance::DatabaseHandle, objects::person::DbUser};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::RequestData,
|
config::Data,
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
kinds::{object::NoteType, public},
|
kinds::{object::NoteType, public},
|
||||||
protocol::{helpers::deserialize_one_or_many, verification::verify_domains_match},
|
protocol::{helpers::deserialize_one_or_many, verification::verify_domains_match},
|
||||||
|
@ -49,7 +49,7 @@ impl ApubObject for DbPost {
|
||||||
|
|
||||||
async fn read_from_apub_id(
|
async fn read_from_apub_id(
|
||||||
object_id: Url,
|
object_id: Url,
|
||||||
data: &RequestData<Self::DataType>,
|
data: &Data<Self::DataType>,
|
||||||
) -> Result<Option<Self>, Self::Error> {
|
) -> Result<Option<Self>, Self::Error> {
|
||||||
let posts = data.posts.lock().unwrap();
|
let posts = data.posts.lock().unwrap();
|
||||||
let res = posts
|
let res = posts
|
||||||
|
@ -59,10 +59,7 @@ impl ApubObject for DbPost {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn into_apub(
|
async fn into_apub(self, data: &Data<Self::DataType>) -> Result<Self::ApubType, Self::Error> {
|
||||||
self,
|
|
||||||
data: &RequestData<Self::DataType>,
|
|
||||||
) -> Result<Self::ApubType, Self::Error> {
|
|
||||||
let creator = self.creator.dereference_local(data).await?;
|
let creator = self.creator.dereference_local(data).await?;
|
||||||
Ok(Note {
|
Ok(Note {
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
|
@ -76,7 +73,7 @@ impl ApubObject for DbPost {
|
||||||
async fn verify(
|
async fn verify(
|
||||||
apub: &Self::ApubType,
|
apub: &Self::ApubType,
|
||||||
expected_domain: &Url,
|
expected_domain: &Url,
|
||||||
_data: &RequestData<Self::DataType>,
|
_data: &Data<Self::DataType>,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
verify_domains_match(apub.id.inner(), expected_domain)?;
|
verify_domains_match(apub.id.inner(), expected_domain)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -84,7 +81,7 @@ impl ApubObject for DbPost {
|
||||||
|
|
||||||
async fn from_apub(
|
async fn from_apub(
|
||||||
apub: Self::ApubType,
|
apub: Self::ApubType,
|
||||||
data: &RequestData<Self::DataType>,
|
data: &Data<Self::DataType>,
|
||||||
) -> Result<Self, Self::Error> {
|
) -> Result<Self, Self::Error> {
|
||||||
let post = DbPost {
|
let post = DbPost {
|
||||||
text: apub.content,
|
text: apub.content,
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
#![doc = include_str!("../docs/09_sending_activities.md")]
|
#![doc = include_str!("../docs/09_sending_activities.md")]
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::RequestData,
|
config::Data,
|
||||||
error::Error,
|
error::Error,
|
||||||
http_signatures::sign_request,
|
http_signatures::sign_request,
|
||||||
reqwest_shim::ResponseExt,
|
reqwest_shim::ResponseExt,
|
||||||
traits::ActivityHandler,
|
traits::{ActivityHandler, Actor},
|
||||||
APUB_JSON_CONTENT_TYPE,
|
APUB_JSON_CONTENT_TYPE,
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
@ -40,21 +40,25 @@ use url::Url;
|
||||||
/// signature. Generated with [crate::http_signatures::generate_actor_keypair].
|
/// signature. Generated with [crate::http_signatures::generate_actor_keypair].
|
||||||
/// - `inboxes`: List of actor inboxes that should receive the activity. Should be built by calling
|
/// - `inboxes`: List of actor inboxes that should receive the activity. Should be built by calling
|
||||||
/// [crate::traits::Actor::shared_inbox_or_inbox] for each target actor.
|
/// [crate::traits::Actor::shared_inbox_or_inbox] for each target actor.
|
||||||
pub async fn send_activity<Activity, Datatype>(
|
pub async fn send_activity<Activity, Datatype, ActorType>(
|
||||||
activity: Activity,
|
activity: Activity,
|
||||||
private_key: String,
|
actor: &ActorType,
|
||||||
inboxes: Vec<Url>,
|
inboxes: Vec<Url>,
|
||||||
data: &RequestData<Datatype>,
|
data: &Data<Datatype>,
|
||||||
) -> Result<(), <Activity as ActivityHandler>::Error>
|
) -> Result<(), <Activity as ActivityHandler>::Error>
|
||||||
where
|
where
|
||||||
Activity: ActivityHandler + Serialize,
|
Activity: ActivityHandler + Serialize,
|
||||||
<Activity as ActivityHandler>::Error: From<anyhow::Error> + From<serde_json::Error>,
|
<Activity as ActivityHandler>::Error: From<anyhow::Error> + From<serde_json::Error>,
|
||||||
Datatype: Clone,
|
Datatype: Clone,
|
||||||
|
ActorType: Actor,
|
||||||
{
|
{
|
||||||
let config = &data.config;
|
let config = &data.config;
|
||||||
let actor_id = activity.actor();
|
let actor_id = activity.actor();
|
||||||
let activity_id = activity.id();
|
let activity_id = activity.id();
|
||||||
let activity_serialized = serde_json::to_string_pretty(&activity)?;
|
let activity_serialized = serde_json::to_string_pretty(&activity)?;
|
||||||
|
let private_key = actor
|
||||||
|
.private_key_pem()
|
||||||
|
.expect("Actor for sending activity has private key");
|
||||||
let inboxes: Vec<Url> = inboxes
|
let inboxes: Vec<Url> = inboxes
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.unique()
|
.unique()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Handles incoming activities, verifying HTTP signatures and other checks
|
//! Handles incoming activities, verifying HTTP signatures and other checks
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::RequestData,
|
config::Data,
|
||||||
error::Error,
|
error::Error,
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
http_signatures::{verify_inbox_hash, verify_signature},
|
http_signatures::{verify_inbox_hash, verify_signature},
|
||||||
|
@ -17,7 +17,7 @@ use tracing::debug;
|
||||||
pub async fn receive_activity<Activity, ActorT, Datatype>(
|
pub async fn receive_activity<Activity, ActorT, Datatype>(
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
body: Bytes,
|
body: Bytes,
|
||||||
data: &RequestData<Datatype>,
|
data: &Data<Datatype>,
|
||||||
) -> Result<HttpResponse, <Activity as ActivityHandler>::Error>
|
) -> Result<HttpResponse, <Activity as ActivityHandler>::Error>
|
||||||
where
|
where
|
||||||
Activity: ActivityHandler<DataType = Datatype> + DeserializeOwned + Send + 'static,
|
Activity: ActivityHandler<DataType = Datatype> + DeserializeOwned + Send + 'static,
|
||||||
|
@ -112,8 +112,8 @@ mod test {
|
||||||
let request_builder =
|
let request_builder =
|
||||||
ClientWithMiddleware::from(Client::default()).post("https://example.com/inbox");
|
ClientWithMiddleware::from(Client::default()).post("https://example.com/inbox");
|
||||||
let activity = Follow {
|
let activity = Follow {
|
||||||
actor: ObjectId::new("http://localhost:123").unwrap(),
|
actor: ObjectId::parse("http://localhost:123").unwrap(),
|
||||||
object: ObjectId::new("http://localhost:124").unwrap(),
|
object: ObjectId::parse("http://localhost:124").unwrap(),
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
id: "http://localhost:123/1".try_into().unwrap(),
|
id: "http://localhost:123/1".try_into().unwrap(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::config::{ApubMiddleware, FederationConfig, RequestData};
|
use crate::config::{ApubMiddleware, Data, FederationConfig};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
dev::{forward_ready, Payload, Service, ServiceRequest, ServiceResponse, Transform},
|
dev::{forward_ready, Payload, Service, ServiceRequest, ServiceResponse, Transform},
|
||||||
Error,
|
Error,
|
||||||
|
@ -29,7 +29,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Passes [FederationConfig] to HTTP handlers, converting it to [RequestData] in the process
|
/// Passes [FederationConfig] to HTTP handlers, converting it to [Data] in the process
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub struct ApubService<S, T: Clone>
|
pub struct ApubService<S, T: Clone>
|
||||||
where
|
where
|
||||||
|
@ -61,7 +61,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone + 'static> FromRequest for RequestData<T> {
|
impl<T: Clone + 'static> FromRequest for Data<T> {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Ready<Result<Self, Self::Error>>;
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
#![doc = include_str!("../../docs/08_receiving_activities.md")]
|
#![doc = include_str!("../../docs/08_receiving_activities.md")]
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::RequestData,
|
config::Data,
|
||||||
error::Error,
|
error::Error,
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
http_signatures::{verify_inbox_hash, verify_signature},
|
http_signatures::{verify_inbox_hash, verify_signature},
|
||||||
|
@ -23,7 +23,7 @@ use tracing::debug;
|
||||||
/// Handles incoming activities, verifying HTTP signatures and other checks
|
/// Handles incoming activities, verifying HTTP signatures and other checks
|
||||||
pub async fn receive_activity<Activity, ActorT, Datatype>(
|
pub async fn receive_activity<Activity, ActorT, Datatype>(
|
||||||
activity_data: ActivityData,
|
activity_data: ActivityData,
|
||||||
data: &RequestData<Datatype>,
|
data: &Data<Datatype>,
|
||||||
) -> Result<(), <Activity as ActivityHandler>::Error>
|
) -> Result<(), <Activity as ActivityHandler>::Error>
|
||||||
where
|
where
|
||||||
Activity: ActivityHandler<DataType = Datatype> + DeserializeOwned + Send + 'static,
|
Activity: ActivityHandler<DataType = Datatype> + DeserializeOwned + Send + 'static,
|
||||||
|
|
|
@ -5,10 +5,10 @@
|
||||||
//! # use axum::extract::Path;
|
//! # use axum::extract::Path;
|
||||||
//! # use activitypub_federation::axum::json::ApubJson;
|
//! # use activitypub_federation::axum::json::ApubJson;
|
||||||
//! # use activitypub_federation::protocol::context::WithContext;
|
//! # use activitypub_federation::protocol::context::WithContext;
|
||||||
//! # use activitypub_federation::config::RequestData;
|
//! # use activitypub_federation::config::Data;
|
||||||
//! # use activitypub_federation::traits::ApubObject;
|
//! # use activitypub_federation::traits::ApubObject;
|
||||||
//! # use activitypub_federation::traits::tests::{DbConnection, DbUser, Person};
|
//! # use activitypub_federation::traits::tests::{DbConnection, DbUser, Person};
|
||||||
//! async fn http_get_user(Path(name): Path<String>, data: RequestData<DbConnection>) -> Result<ApubJson<WithContext<Person>>, Error> {
|
//! async fn http_get_user(Path(name): Path<String>, data: Data<DbConnection>) -> Result<ApubJson<WithContext<Person>>, Error> {
|
||||||
//! let user: DbUser = data.read_local_user(name).await?;
|
//! let user: DbUser = data.read_local_user(name).await?;
|
||||||
//! let person = user.into_apub(&data).await?;
|
//! let person = user.into_apub(&data).await?;
|
||||||
//!
|
//!
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::config::{ApubMiddleware, FederationConfig, RequestData};
|
use crate::config::{ApubMiddleware, Data, FederationConfig};
|
||||||
use axum::{async_trait, body::Body, extract::FromRequestParts, http::Request, response::Response};
|
use axum::{async_trait, body::Body, extract::FromRequestParts, http::Request, response::Response};
|
||||||
use http::{request::Parts, StatusCode};
|
use http::{request::Parts, StatusCode};
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
@ -15,7 +15,7 @@ impl<S, T: Clone> Layer<S> for ApubMiddleware<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Passes [FederationConfig] to HTTP handlers, converting it to [RequestData] in the process
|
/// Passes [FederationConfig] to HTTP handlers, converting it to [Data] in the process
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ApubService<S, T: Clone> {
|
pub struct ApubService<S, T: Clone> {
|
||||||
|
@ -44,7 +44,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<S, T: Clone + 'static> FromRequestParts<S> for RequestData<T>
|
impl<S, T: Clone + 'static> FromRequestParts<S> for Data<T>
|
||||||
where
|
where
|
||||||
S: Send + Sync,
|
S: Send + Sync,
|
||||||
T: Send + Sync,
|
T: Send + Sync,
|
||||||
|
|
|
@ -28,7 +28,10 @@ use reqwest_middleware::ClientWithMiddleware;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use std::{
|
use std::{
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
sync::{atomic::AtomicI32, Arc},
|
sync::{
|
||||||
|
atomic::{AtomicU32, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -46,7 +49,7 @@ pub struct FederationConfig<T: Clone> {
|
||||||
/// Maximum number of outgoing HTTP requests per incoming HTTP request. See
|
/// Maximum number of outgoing HTTP requests per incoming HTTP request. See
|
||||||
/// [crate::fetch::object_id::ObjectId] for more details.
|
/// [crate::fetch::object_id::ObjectId] for more details.
|
||||||
#[builder(default = "20")]
|
#[builder(default = "20")]
|
||||||
pub(crate) http_fetch_limit: i32,
|
pub(crate) http_fetch_limit: u32,
|
||||||
#[builder(default = "reqwest::Client::default().into()")]
|
#[builder(default = "reqwest::Client::default().into()")]
|
||||||
/// HTTP client used for all outgoing requests. Middleware can be used to add functionality
|
/// HTTP client used for all outgoing requests. Middleware can be used to add functionality
|
||||||
/// like log tracing or retry of failed requests.
|
/// like log tracing or retry of failed requests.
|
||||||
|
@ -102,9 +105,9 @@ impl<T: Clone> FederationConfig<T> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create new [RequestData] from this. You should prefer to use a middleware if possible.
|
/// Create new [Data] from this. You should prefer to use a middleware if possible.
|
||||||
pub fn to_request_data(&self) -> RequestData<T> {
|
pub fn to_request_data(&self) -> Data<T> {
|
||||||
RequestData {
|
Data {
|
||||||
config: self.clone(),
|
config: self.clone(),
|
||||||
request_counter: Default::default(),
|
request_counter: Default::default(),
|
||||||
}
|
}
|
||||||
|
@ -127,6 +130,11 @@ impl<T: Clone> FederationConfig<T> {
|
||||||
_ => return Err(Error::UrlVerificationError("Invalid url scheme")),
|
_ => return Err(Error::UrlVerificationError("Invalid url scheme")),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Urls which use our local domain are not a security risk, no further verification needed
|
||||||
|
if self.is_local_url(url) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
if url.domain().is_none() {
|
if url.domain().is_none() {
|
||||||
return Err(Error::UrlVerificationError("Url must have a domain"));
|
return Err(Error::UrlVerificationError("Url must have a domain"));
|
||||||
}
|
}
|
||||||
|
@ -137,11 +145,6 @@ impl<T: Clone> FederationConfig<T> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Urls which use our local domain are not a security risk, no further verification needed
|
|
||||||
if self.is_local_url(url) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.url_verifier
|
self.url_verifier
|
||||||
.verify(url)
|
.verify(url)
|
||||||
.await
|
.await
|
||||||
|
@ -253,24 +256,36 @@ clone_trait_object!(UrlVerifier);
|
||||||
/// prevent denial of service attacks, where an attacker triggers fetching of recursive objects.
|
/// prevent denial of service attacks, where an attacker triggers fetching of recursive objects.
|
||||||
///
|
///
|
||||||
/// <https://www.w3.org/TR/activitypub/#security-recursive-objects>
|
/// <https://www.w3.org/TR/activitypub/#security-recursive-objects>
|
||||||
pub struct RequestData<T: Clone> {
|
pub struct Data<T: Clone> {
|
||||||
pub(crate) config: FederationConfig<T>,
|
pub(crate) config: FederationConfig<T>,
|
||||||
pub(crate) request_counter: AtomicI32,
|
pub(crate) request_counter: AtomicU32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone> RequestData<T> {
|
impl<T: Clone> Data<T> {
|
||||||
/// Returns the data which was stored in [FederationConfigBuilder::app_data]
|
/// Returns the data which was stored in [FederationConfigBuilder::app_data]
|
||||||
pub fn app_data(&self) -> &T {
|
pub fn app_data(&self) -> &T {
|
||||||
&self.config.app_data
|
&self.config.app_data
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the domain that was configured in [FederationConfig].
|
/// The domain that was configured in [FederationConfig].
|
||||||
pub fn domain(&self) -> &str {
|
pub fn domain(&self) -> &str {
|
||||||
&self.config.domain
|
&self.config.domain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a new instance of `Data` with request counter set to 0.
|
||||||
|
pub fn reset_request_count(&self) -> Self {
|
||||||
|
Data {
|
||||||
|
config: self.config.clone(),
|
||||||
|
request_counter: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Total number of outgoing HTTP requests made with this data.
|
||||||
|
pub fn request_count(&self) -> u32 {
|
||||||
|
self.request_counter.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone> Deref for RequestData<T> {
|
impl<T: Clone> Deref for Data<T> {
|
||||||
type Target = T;
|
type Target = T;
|
||||||
|
|
||||||
fn deref(&self) -> &T {
|
fn deref(&self) -> &T {
|
||||||
|
@ -278,7 +293,7 @@ impl<T: Clone> Deref for RequestData<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Middleware for HTTP handlers which provides access to [RequestData]
|
/// Middleware for HTTP handlers which provides access to [Data]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ApubMiddleware<T: Clone>(pub(crate) FederationConfig<T>);
|
pub struct ApubMiddleware<T: Clone>(pub(crate) FederationConfig<T>);
|
||||||
|
|
||||||
|
|
97
src/fetch/collection_id.rs
Normal file
97
src/fetch/collection_id.rs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
use crate::{config::Data, error::Error, fetch::fetch_object_http, traits::ApubCollection};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
fmt::{Debug, Display, Formatter},
|
||||||
|
marker::PhantomData,
|
||||||
|
};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
/// Typed wrapper for Activitypub Collection ID which helps with dereferencing.
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct CollectionId<Kind>(Box<Url>, PhantomData<Kind>)
|
||||||
|
where
|
||||||
|
Kind: ApubCollection,
|
||||||
|
for<'de2> <Kind as ApubCollection>::ApubType: Deserialize<'de2>;
|
||||||
|
|
||||||
|
impl<Kind> CollectionId<Kind>
|
||||||
|
where
|
||||||
|
Kind: ApubCollection,
|
||||||
|
for<'de2> <Kind as ApubCollection>::ApubType: Deserialize<'de2>,
|
||||||
|
{
|
||||||
|
/// Construct a new CollectionId instance
|
||||||
|
pub fn parse<T>(url: T) -> Result<Self, url::ParseError>
|
||||||
|
where
|
||||||
|
T: TryInto<Url>,
|
||||||
|
url::ParseError: From<<T as TryInto<Url>>::Error>,
|
||||||
|
{
|
||||||
|
Ok(Self(Box::new(url.try_into()?), PhantomData::<Kind>))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetches collection over HTTP
|
||||||
|
///
|
||||||
|
/// Unlike [ObjectId::fetch](crate::fetch::object_id::ObjectId::fetch) this method doesn't do
|
||||||
|
/// any caching.
|
||||||
|
pub async fn dereference(
|
||||||
|
&self,
|
||||||
|
owner: &<Kind as ApubCollection>::Owner,
|
||||||
|
data: &Data<<Kind as ApubCollection>::DataType>,
|
||||||
|
) -> Result<Kind, <Kind as ApubCollection>::Error>
|
||||||
|
where
|
||||||
|
<Kind as ApubCollection>::Error: From<Error>,
|
||||||
|
{
|
||||||
|
let apub = fetch_object_http(&self.0, data).await?;
|
||||||
|
Kind::verify(&apub, &self.0, data).await?;
|
||||||
|
Kind::from_apub(apub, owner, data).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Need to implement clone manually, to avoid requiring Kind to be Clone
|
||||||
|
impl<Kind> Clone for CollectionId<Kind>
|
||||||
|
where
|
||||||
|
Kind: ApubCollection,
|
||||||
|
for<'de2> <Kind as ApubCollection>::ApubType: serde::Deserialize<'de2>,
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
CollectionId(self.0.clone(), self.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Kind> Display for CollectionId<Kind>
|
||||||
|
where
|
||||||
|
Kind: ApubCollection,
|
||||||
|
for<'de2> <Kind as ApubCollection>::ApubType: serde::Deserialize<'de2>,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Kind> Debug for CollectionId<Kind>
|
||||||
|
where
|
||||||
|
Kind: ApubCollection,
|
||||||
|
for<'de2> <Kind as ApubCollection>::ApubType: serde::Deserialize<'de2>,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<Kind> From<CollectionId<Kind>> for Url
|
||||||
|
where
|
||||||
|
Kind: ApubCollection,
|
||||||
|
for<'de2> <Kind as ApubCollection>::ApubType: serde::Deserialize<'de2>,
|
||||||
|
{
|
||||||
|
fn from(id: CollectionId<Kind>) -> Self {
|
||||||
|
*id.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Kind> From<Url> for CollectionId<Kind>
|
||||||
|
where
|
||||||
|
Kind: ApubCollection + Send + 'static,
|
||||||
|
for<'de2> <Kind as ApubCollection>::ApubType: serde::Deserialize<'de2>,
|
||||||
|
{
|
||||||
|
fn from(url: Url) -> Self {
|
||||||
|
CollectionId(Box::new(url), PhantomData::<Kind>)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,13 +2,15 @@
|
||||||
//!
|
//!
|
||||||
#![doc = include_str!("../../docs/07_fetching_data.md")]
|
#![doc = include_str!("../../docs/07_fetching_data.md")]
|
||||||
|
|
||||||
use crate::{config::RequestData, error::Error, reqwest_shim::ResponseExt, APUB_JSON_CONTENT_TYPE};
|
use crate::{config::Data, error::Error, reqwest_shim::ResponseExt, APUB_JSON_CONTENT_TYPE};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
/// Typed wrapper for collection IDs
|
||||||
|
pub mod collection_id;
|
||||||
/// Typed wrapper for Activitypub Object ID which helps with dereferencing and caching
|
/// Typed wrapper for Activitypub Object ID which helps with dereferencing and caching
|
||||||
pub mod object_id;
|
pub mod object_id;
|
||||||
/// Resolves identifiers of the form `name@example.com`
|
/// Resolves identifiers of the form `name@example.com`
|
||||||
|
@ -24,9 +26,9 @@ pub mod webfinger;
|
||||||
/// If the value exceeds [FederationSettings.http_fetch_limit], the request is aborted with
|
/// If the value exceeds [FederationSettings.http_fetch_limit], the request is aborted with
|
||||||
/// [Error::RequestLimit]. This prevents denial of service attacks where an attack triggers
|
/// [Error::RequestLimit]. This prevents denial of service attacks where an attack triggers
|
||||||
/// infinite, recursive fetching of data.
|
/// infinite, recursive fetching of data.
|
||||||
async fn fetch_object_http<T: Clone, Kind: DeserializeOwned>(
|
pub async fn fetch_object_http<T: Clone, Kind: DeserializeOwned>(
|
||||||
url: &Url,
|
url: &Url,
|
||||||
data: &RequestData<T>,
|
data: &Data<T>,
|
||||||
) -> Result<Kind, Error> {
|
) -> Result<Kind, Error> {
|
||||||
let config = &data.config;
|
let config = &data.config;
|
||||||
// dont fetch local objects this way
|
// dont fetch local objects this way
|
||||||
|
|
|
@ -1,13 +1,25 @@
|
||||||
use crate::{config::RequestData, error::Error, fetch::fetch_object_http, traits::ApubObject};
|
use crate::{config::Data, error::Error, fetch::fetch_object_http, traits::ApubObject};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use chrono::{Duration as ChronoDuration, NaiveDateTime, Utc};
|
use chrono::{Duration as ChronoDuration, NaiveDateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{Debug, Display, Formatter},
|
fmt::{Debug, Display, Formatter},
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
|
str::FromStr,
|
||||||
};
|
};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
impl<T> FromStr for ObjectId<T>
|
||||||
|
where
|
||||||
|
T: ApubObject + Send + 'static,
|
||||||
|
for<'de2> <T as ApubObject>::ApubType: Deserialize<'de2>,
|
||||||
|
{
|
||||||
|
type Err = url::ParseError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
ObjectId::parse(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Typed wrapper for Activitypub Object ID which helps with dereferencing and caching.
|
/// Typed wrapper for Activitypub Object ID which helps with dereferencing and caching.
|
||||||
///
|
///
|
||||||
/// It provides convenient methods for fetching the object from remote server or local database.
|
/// It provides convenient methods for fetching the object from remote server or local database.
|
||||||
|
@ -32,7 +44,7 @@ use url::Url;
|
||||||
/// .app_data(db_connection)
|
/// .app_data(db_connection)
|
||||||
/// .build()?;
|
/// .build()?;
|
||||||
/// let request_data = config.to_request_data();
|
/// let request_data = config.to_request_data();
|
||||||
/// let object_id = ObjectId::<DbUser>::new("https://lemmy.ml/u/nutomic")?;
|
/// let object_id = ObjectId::<DbUser>::parse("https://lemmy.ml/u/nutomic")?;
|
||||||
/// // Attempt to fetch object from local database or fall back to remote server
|
/// // Attempt to fetch object from local database or fall back to remote server
|
||||||
/// let user = object_id.dereference(&request_data).await;
|
/// let user = object_id.dereference(&request_data).await;
|
||||||
/// assert!(user.is_ok());
|
/// assert!(user.is_ok());
|
||||||
|
@ -55,7 +67,7 @@ where
|
||||||
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
|
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
|
||||||
{
|
{
|
||||||
/// Construct a new objectid instance
|
/// Construct a new objectid instance
|
||||||
pub fn new<T>(url: T) -> Result<Self, url::ParseError>
|
pub fn parse<T>(url: T) -> Result<Self, url::ParseError>
|
||||||
where
|
where
|
||||||
T: TryInto<Url>,
|
T: TryInto<Url>,
|
||||||
url::ParseError: From<<T as TryInto<Url>>::Error>,
|
url::ParseError: From<<T as TryInto<Url>>::Error>,
|
||||||
|
@ -76,7 +88,7 @@ where
|
||||||
/// Fetches an activitypub object, either from local database (if possible), or over http.
|
/// Fetches an activitypub object, either from local database (if possible), or over http.
|
||||||
pub async fn dereference(
|
pub async fn dereference(
|
||||||
&self,
|
&self,
|
||||||
data: &RequestData<<Kind as ApubObject>::DataType>,
|
data: &Data<<Kind as ApubObject>::DataType>,
|
||||||
) -> Result<Kind, <Kind as ApubObject>::Error>
|
) -> Result<Kind, <Kind as ApubObject>::Error>
|
||||||
where
|
where
|
||||||
<Kind as ApubObject>::Error: From<Error> + From<anyhow::Error>,
|
<Kind as ApubObject>::Error: From<Error> + From<anyhow::Error>,
|
||||||
|
@ -111,7 +123,7 @@ where
|
||||||
/// the object is not found in the database.
|
/// the object is not found in the database.
|
||||||
pub async fn dereference_local(
|
pub async fn dereference_local(
|
||||||
&self,
|
&self,
|
||||||
data: &RequestData<<Kind as ApubObject>::DataType>,
|
data: &Data<<Kind as ApubObject>::DataType>,
|
||||||
) -> Result<Kind, <Kind as ApubObject>::Error>
|
) -> Result<Kind, <Kind as ApubObject>::Error>
|
||||||
where
|
where
|
||||||
<Kind as ApubObject>::Error: From<Error>,
|
<Kind as ApubObject>::Error: From<Error>,
|
||||||
|
@ -123,7 +135,7 @@ where
|
||||||
/// returning none means the object was not found in local db
|
/// returning none means the object was not found in local db
|
||||||
async fn dereference_from_db(
|
async fn dereference_from_db(
|
||||||
&self,
|
&self,
|
||||||
data: &RequestData<<Kind as ApubObject>::DataType>,
|
data: &Data<<Kind as ApubObject>::DataType>,
|
||||||
) -> Result<Option<Kind>, <Kind as ApubObject>::Error> {
|
) -> Result<Option<Kind>, <Kind as ApubObject>::Error> {
|
||||||
let id = self.0.clone();
|
let id = self.0.clone();
|
||||||
ApubObject::read_from_apub_id(*id, data).await
|
ApubObject::read_from_apub_id(*id, data).await
|
||||||
|
@ -131,7 +143,7 @@ where
|
||||||
|
|
||||||
async fn dereference_from_http(
|
async fn dereference_from_http(
|
||||||
&self,
|
&self,
|
||||||
data: &RequestData<<Kind as ApubObject>::DataType>,
|
data: &Data<<Kind as ApubObject>::DataType>,
|
||||||
db_object: Option<Kind>,
|
db_object: Option<Kind>,
|
||||||
) -> Result<Kind, <Kind as ApubObject>::Error>
|
) -> Result<Kind, <Kind as ApubObject>::Error>
|
||||||
where
|
where
|
||||||
|
@ -238,7 +250,7 @@ pub mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_deserialize() {
|
fn test_deserialize() {
|
||||||
let id = ObjectId::<DbUser>::new("http://test.com/").unwrap();
|
let id = ObjectId::<DbUser>::parse("http://test.com/").unwrap();
|
||||||
|
|
||||||
let string = serde_json::to_string(&id).unwrap();
|
let string = serde_json::to_string(&id).unwrap();
|
||||||
assert_eq!("\"http://test.com/\"", string);
|
assert_eq!("\"http://test.com/\"", string);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
config::RequestData,
|
config::Data,
|
||||||
error::{Error, Error::WebfingerResolveFailed},
|
error::{Error, Error::WebfingerResolveFailed},
|
||||||
fetch::{fetch_object_http, object_id::ObjectId},
|
fetch::{fetch_object_http, object_id::ObjectId},
|
||||||
traits::{Actor, ApubObject},
|
traits::{Actor, ApubObject},
|
||||||
|
@ -19,7 +19,7 @@ use url::Url;
|
||||||
/// is then fetched using [ObjectId::dereference], and the result returned.
|
/// is then fetched using [ObjectId::dereference], and the result returned.
|
||||||
pub async fn webfinger_resolve_actor<T: Clone, Kind>(
|
pub async fn webfinger_resolve_actor<T: Clone, Kind>(
|
||||||
identifier: &str,
|
identifier: &str,
|
||||||
data: &RequestData<T>,
|
data: &Data<T>,
|
||||||
) -> Result<Kind, <Kind as ApubObject>::Error>
|
) -> Result<Kind, <Kind as ApubObject>::Error>
|
||||||
where
|
where
|
||||||
Kind: ApubObject + Actor + Send + 'static + ApubObject<DataType = T>,
|
Kind: ApubObject + Actor + Send + 'static + ApubObject<DataType = T>,
|
||||||
|
@ -66,7 +66,7 @@ where
|
||||||
/// request. For a parameter of the form `acct:gargron@mastodon.social` it returns `gargron`.
|
/// request. For a parameter of the form `acct:gargron@mastodon.social` it returns `gargron`.
|
||||||
///
|
///
|
||||||
/// Returns an error if query doesn't match local domain.
|
/// Returns an error if query doesn't match local domain.
|
||||||
pub fn extract_webfinger_name<T>(query: &str, data: &RequestData<T>) -> Result<String, Error>
|
pub fn extract_webfinger_name<T>(query: &str, data: &Data<T>) -> Result<String, Error>
|
||||||
where
|
where
|
||||||
T: Clone,
|
T: Clone,
|
||||||
{
|
{
|
||||||
|
@ -118,7 +118,7 @@ pub fn build_webfinger_response(subject: String, url: Url) -> Webfinger {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A webfinger response with information about a `Person` or other type of actor.
|
/// A webfinger response with information about a `Person` or other type of actor.
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||||
pub struct Webfinger {
|
pub struct Webfinger {
|
||||||
/// The actor which is described here, for example `acct:LemmyDev@mastodon.social`
|
/// The actor which is described here, for example `acct:LemmyDev@mastodon.social`
|
||||||
pub subject: String,
|
pub subject: String,
|
||||||
|
@ -133,7 +133,7 @@ pub struct Webfinger {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A single link included as part of a [Webfinger] response.
|
/// A single link included as part of a [Webfinger] response.
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||||
pub struct WebfingerLink {
|
pub struct WebfingerLink {
|
||||||
/// Relationship of the link, such as `self` or `http://webfinger.net/rel/profile-page`
|
/// Relationship of the link, such as `self` or `http://webfinger.net/rel/profile-page`
|
||||||
pub rel: Option<String>,
|
pub rel: Option<String>,
|
||||||
|
|
|
@ -19,11 +19,7 @@
|
||||||
//! Ok::<(), serde_json::error::Error>(())
|
//! Ok::<(), serde_json::error::Error>(())
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use crate::{
|
use crate::{config::Data, protocol::helpers::deserialize_one_or_many, traits::ActivityHandler};
|
||||||
config::RequestData,
|
|
||||||
protocol::helpers::deserialize_one_or_many,
|
|
||||||
traits::ActivityHandler,
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -75,11 +71,23 @@ where
|
||||||
self.inner.actor()
|
self.inner.actor()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn verify(&self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
|
async fn verify(&self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
self.inner.verify(data).await
|
self.inner.verify(data).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
|
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
self.inner.receive(data).await
|
self.inner.receive(data).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Clone for WithContext<T>
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
context: self.context.clone(),
|
||||||
|
inner: self.inner.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ impl PublicKey {
|
||||||
/// Create a new [PublicKey] struct for the `owner` with `public_key_pem`.
|
/// Create a new [PublicKey] struct for the `owner` with `public_key_pem`.
|
||||||
///
|
///
|
||||||
/// It uses an standard key id of `{actor_id}#main-key`
|
/// It uses an standard key id of `{actor_id}#main-key`
|
||||||
pub fn new(owner: Url, public_key_pem: String) -> Self {
|
pub(crate) fn new(owner: Url, public_key_pem: String) -> Self {
|
||||||
let id = main_key_id(&owner);
|
let id = main_key_id(&owner);
|
||||||
PublicKey {
|
PublicKey {
|
||||||
id,
|
id,
|
||||||
|
|
228
src/traits.rs
228
src/traits.rs
|
@ -1,86 +1,94 @@
|
||||||
//! Traits which need to be implemented for federated data types
|
//! Traits which need to be implemented for federated data types
|
||||||
|
|
||||||
use crate::{config::RequestData, protocol::public_key::PublicKey};
|
use crate::{config::Data, protocol::public_key::PublicKey};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use std::ops::Deref;
|
use serde::Deserialize;
|
||||||
|
use std::{fmt::Debug, ops::Deref};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
/// Helper for converting between database structs and federated protocol structs.
|
/// Helper for converting between database structs and federated protocol structs.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
/// # use activitystreams_kinds::{object::NoteType, public};
|
||||||
/// # use chrono::{Local, NaiveDateTime};
|
/// # use chrono::{Local, NaiveDateTime};
|
||||||
|
/// # use serde::{Deserialize, Serialize};
|
||||||
/// # use url::Url;
|
/// # use url::Url;
|
||||||
/// # use activitypub_federation::protocol::public_key::PublicKey;
|
/// # use activitypub_federation::protocol::{public_key::PublicKey, helpers::deserialize_one_or_many};
|
||||||
/// # use activitypub_federation::config::RequestData;
|
/// # use activitypub_federation::config::Data;
|
||||||
/// use activitypub_federation::protocol::verification::verify_domains_match;
|
/// # use activitypub_federation::fetch::object_id::ObjectId;
|
||||||
/// # use activitypub_federation::traits::ApubObject;
|
/// # use activitypub_federation::protocol::verification::verify_domains_match;
|
||||||
/// # use activitypub_federation::traits::tests::{DbConnection, Person};
|
/// # use activitypub_federation::traits::{Actor, ApubObject};
|
||||||
/// # pub struct DbUser {
|
/// # use activitypub_federation::traits::tests::{DbConnection, DbUser};
|
||||||
/// # pub name: String,
|
/// #
|
||||||
/// # pub ap_id: Url,
|
/// /// How the post is read/written in the local database
|
||||||
/// # pub inbox: Url,
|
/// pub struct DbPost {
|
||||||
/// # pub public_key: String,
|
/// pub text: String,
|
||||||
/// # pub private_key: Option<String>,
|
/// pub ap_id: ObjectId<DbPost>,
|
||||||
/// # pub local: bool,
|
/// pub creator: ObjectId<DbUser>,
|
||||||
/// # pub last_refreshed_at: NaiveDateTime,
|
/// pub local: bool,
|
||||||
/// # }
|
/// }
|
||||||
|
///
|
||||||
|
/// /// How the post is serialized and represented as Activitypub JSON
|
||||||
|
/// #[derive(Deserialize, Serialize, Debug)]
|
||||||
|
/// #[serde(rename_all = "camelCase")]
|
||||||
|
/// pub struct Note {
|
||||||
|
/// #[serde(rename = "type")]
|
||||||
|
/// kind: NoteType,
|
||||||
|
/// id: ObjectId<DbPost>,
|
||||||
|
/// pub(crate) attributed_to: ObjectId<DbUser>,
|
||||||
|
/// #[serde(deserialize_with = "deserialize_one_or_many")]
|
||||||
|
/// pub(crate) to: Vec<Url>,
|
||||||
|
/// content: String,
|
||||||
|
/// }
|
||||||
///
|
///
|
||||||
/// #[async_trait::async_trait]
|
/// #[async_trait::async_trait]
|
||||||
/// impl ApubObject for DbUser {
|
/// impl ApubObject for DbPost {
|
||||||
/// type DataType = DbConnection;
|
/// type DataType = DbConnection;
|
||||||
/// type ApubType = Person;
|
/// type ApubType = Note;
|
||||||
/// type Error = anyhow::Error;
|
/// type Error = anyhow::Error;
|
||||||
///
|
///
|
||||||
/// fn last_refreshed_at(&self) -> Option<NaiveDateTime> {
|
/// async fn read_from_apub_id(object_id: Url, data: &Data<Self::DataType>) -> Result<Option<Self>, Self::Error> {
|
||||||
/// Some(self.last_refreshed_at)
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// async fn read_from_apub_id(object_id: Url, data: &RequestData<Self::DataType>) -> Result<Option<Self>, Self::Error> {
|
|
||||||
/// // Attempt to read object from local database. Return Ok(None) if not found.
|
/// // Attempt to read object from local database. Return Ok(None) if not found.
|
||||||
/// let user: Option<DbUser> = data.read_user_from_apub_id(object_id).await?;
|
/// let post: Option<DbPost> = data.read_post_from_apub_id(object_id).await?;
|
||||||
/// Ok(user)
|
/// Ok(post)
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// async fn into_apub(self, data: &RequestData<Self::DataType>) -> Result<Self::ApubType, Self::Error> {
|
/// async fn into_apub(self, data: &Data<Self::DataType>) -> Result<Self::ApubType, Self::Error> {
|
||||||
/// // Called when a local object gets sent out over Activitypub. Simply convert it to the
|
/// // Called when a local object gets sent out over Activitypub. Simply convert it to the
|
||||||
/// // protocol struct
|
/// // protocol struct
|
||||||
/// Ok(Person {
|
/// Ok(Note {
|
||||||
/// kind: Default::default(),
|
/// kind: Default::default(),
|
||||||
/// preferred_username: self.name,
|
|
||||||
/// id: self.ap_id.clone().into(),
|
/// id: self.ap_id.clone().into(),
|
||||||
/// inbox: self.inbox,
|
/// attributed_to: self.creator,
|
||||||
/// public_key: PublicKey::new(self.ap_id, self.public_key),
|
/// to: vec![public()],
|
||||||
|
/// content: self.text,
|
||||||
/// })
|
/// })
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// async fn verify(apub: &Self::ApubType, expected_domain: &Url, data: &RequestData<Self::DataType>,) -> Result<(), Self::Error> {
|
/// async fn verify(apub: &Self::ApubType, expected_domain: &Url, data: &Data<Self::DataType>,) -> Result<(), Self::Error> {
|
||||||
/// verify_domains_match(apub.id.inner(), expected_domain)?;
|
/// verify_domains_match(apub.id.inner(), expected_domain)?;
|
||||||
/// // additional application specific checks
|
/// // additional application specific checks
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// async fn from_apub(apub: Self::ApubType, data: &RequestData<Self::DataType>) -> Result<Self, Self::Error> {
|
/// async fn from_apub(apub: Self::ApubType, data: &Data<Self::DataType>) -> Result<Self, Self::Error> {
|
||||||
/// // Called when a remote object gets received over Activitypub. Validate and insert it
|
/// // Called when a remote object gets received over Activitypub. Validate and insert it
|
||||||
/// // into the database.
|
/// // into the database.
|
||||||
///
|
///
|
||||||
/// let user = DbUser {
|
/// let post = DbPost {
|
||||||
/// name: apub.preferred_username,
|
/// text: apub.content,
|
||||||
/// ap_id: apub.id.into_inner(),
|
/// ap_id: apub.id,
|
||||||
/// inbox: apub.inbox,
|
/// creator: apub.attributed_to,
|
||||||
/// public_key: apub.public_key.public_key_pem,
|
|
||||||
/// private_key: None,
|
|
||||||
/// local: false,
|
/// local: false,
|
||||||
/// last_refreshed_at: Local::now().naive_local(),
|
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// // Make sure not to overwrite any local object
|
/// // Here we need to persist the object in the local database. Note that Activitypub
|
||||||
/// if data.domain() == user.ap_id.domain().unwrap() {
|
/// // doesnt distinguish between creating and updating an object. Thats why we need to
|
||||||
/// // Activitypub doesnt distinguish between creating and updating an object. Thats why we
|
/// // use upsert functionality.
|
||||||
/// // need to use upsert functionality here
|
/// data.upsert(&post).await?;
|
||||||
/// data.upsert(&user).await?;
|
///
|
||||||
/// }
|
/// Ok(post)
|
||||||
/// Ok(user)
|
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// }
|
/// }
|
||||||
|
@ -112,13 +120,13 @@ pub trait ApubObject: Sized {
|
||||||
/// Should return `Ok(None)` if not found.
|
/// Should return `Ok(None)` if not found.
|
||||||
async fn read_from_apub_id(
|
async fn read_from_apub_id(
|
||||||
object_id: Url,
|
object_id: Url,
|
||||||
data: &RequestData<Self::DataType>,
|
data: &Data<Self::DataType>,
|
||||||
) -> Result<Option<Self>, Self::Error>;
|
) -> Result<Option<Self>, Self::Error>;
|
||||||
|
|
||||||
/// Mark remote object as deleted in local database.
|
/// Mark remote object as deleted in local database.
|
||||||
///
|
///
|
||||||
/// Called when a `Delete` activity is received, or if fetch returns a `Tombstone` object.
|
/// Called when a `Delete` activity is received, or if fetch returns a `Tombstone` object.
|
||||||
async fn delete(self, _data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
|
async fn delete(self, _data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,10 +134,7 @@ pub trait ApubObject: Sized {
|
||||||
///
|
///
|
||||||
/// Called when a local object gets fetched by another instance over HTTP, or when an object
|
/// Called when a local object gets fetched by another instance over HTTP, or when an object
|
||||||
/// gets sent in an activity.
|
/// gets sent in an activity.
|
||||||
async fn into_apub(
|
async fn into_apub(self, data: &Data<Self::DataType>) -> Result<Self::ApubType, Self::Error>;
|
||||||
self,
|
|
||||||
data: &RequestData<Self::DataType>,
|
|
||||||
) -> Result<Self::ApubType, Self::Error>;
|
|
||||||
|
|
||||||
/// Verifies that the received object is valid.
|
/// Verifies that the received object is valid.
|
||||||
///
|
///
|
||||||
|
@ -141,17 +146,17 @@ pub trait ApubObject: Sized {
|
||||||
async fn verify(
|
async fn verify(
|
||||||
apub: &Self::ApubType,
|
apub: &Self::ApubType,
|
||||||
expected_domain: &Url,
|
expected_domain: &Url,
|
||||||
data: &RequestData<Self::DataType>,
|
data: &Data<Self::DataType>,
|
||||||
) -> Result<(), Self::Error>;
|
) -> Result<(), Self::Error>;
|
||||||
|
|
||||||
/// Convert object from ActivityPub type to database type.
|
/// Convert object from ActivityPub type to database type.
|
||||||
///
|
///
|
||||||
/// Called when an object is received from HTTP fetch or as part of an activity. This method
|
/// Called when an object is received from HTTP fetch or as part of an activity. This method
|
||||||
/// should do verification and write the received object to database. Note that there is no
|
/// should write the received object to database. Note that there is no distinction between
|
||||||
/// distinction between create and update, so an `upsert` operation should be used.
|
/// create and update, so an `upsert` operation should be used.
|
||||||
async fn from_apub(
|
async fn from_apub(
|
||||||
apub: Self::ApubType,
|
apub: Self::ApubType,
|
||||||
data: &RequestData<Self::DataType>,
|
data: &Data<Self::DataType>,
|
||||||
) -> Result<Self, Self::Error>;
|
) -> Result<Self, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +166,7 @@ pub trait ApubObject: Sized {
|
||||||
/// # use activitystreams_kinds::activity::FollowType;
|
/// # use activitystreams_kinds::activity::FollowType;
|
||||||
/// # use url::Url;
|
/// # use url::Url;
|
||||||
/// # use activitypub_federation::fetch::object_id::ObjectId;
|
/// # use activitypub_federation::fetch::object_id::ObjectId;
|
||||||
/// # use activitypub_federation::config::RequestData;
|
/// # use activitypub_federation::config::Data;
|
||||||
/// # use activitypub_federation::traits::ActivityHandler;
|
/// # use activitypub_federation::traits::ActivityHandler;
|
||||||
/// # use activitypub_federation::traits::tests::{DbConnection, DbUser};
|
/// # use activitypub_federation::traits::tests::{DbConnection, DbUser};
|
||||||
/// #[derive(serde::Deserialize)]
|
/// #[derive(serde::Deserialize)]
|
||||||
|
@ -186,11 +191,11 @@ pub trait ApubObject: Sized {
|
||||||
/// self.actor.inner()
|
/// self.actor.inner()
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// async fn verify(&self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
|
/// async fn verify(&self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
|
/// async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
/// let local_user = self.object.dereference(data).await?;
|
/// let local_user = self.object.dereference(data).await?;
|
||||||
/// let follower = self.actor.dereference(data).await?;
|
/// let follower = self.actor.dereference(data).await?;
|
||||||
/// data.add_follower(local_user, follower).await?;
|
/// data.add_follower(local_user, follower).await?;
|
||||||
|
@ -217,19 +222,19 @@ pub trait ActivityHandler {
|
||||||
///
|
///
|
||||||
/// This needs to be a separate method, because it might be used for activities
|
/// This needs to be a separate method, because it might be used for activities
|
||||||
/// like `Undo/Follow`, which shouldn't perform any database write for the inner `Follow`.
|
/// like `Undo/Follow`, which shouldn't perform any database write for the inner `Follow`.
|
||||||
async fn verify(&self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error>;
|
async fn verify(&self, data: &Data<Self::DataType>) -> Result<(), Self::Error>;
|
||||||
|
|
||||||
/// Called when an activity is received.
|
/// Called when an activity is received.
|
||||||
///
|
///
|
||||||
/// Should perform validation and possibly write action to the database. In case the activity
|
/// Should perform validation and possibly write action to the database. In case the activity
|
||||||
/// has a nested `object` field, must call `object.from_apub` handler.
|
/// has a nested `object` field, must call `object.from_apub` handler.
|
||||||
async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error>;
|
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait to allow retrieving common Actor data.
|
/// Trait to allow retrieving common Actor data.
|
||||||
pub trait Actor: ApubObject {
|
pub trait Actor: ApubObject + Send + 'static {
|
||||||
/// `id` field of the actor
|
/// `id` field of the actor
|
||||||
fn id(&self) -> &Url;
|
fn id(&self) -> Url;
|
||||||
|
|
||||||
/// The actor's public key for verifying signatures of incoming activities.
|
/// The actor's public key for verifying signatures of incoming activities.
|
||||||
///
|
///
|
||||||
|
@ -237,12 +242,18 @@ pub trait Actor: ApubObject {
|
||||||
/// actor keypair.
|
/// actor keypair.
|
||||||
fn public_key_pem(&self) -> &str;
|
fn public_key_pem(&self) -> &str;
|
||||||
|
|
||||||
|
/// The actor's private key for signing outgoing activities.
|
||||||
|
///
|
||||||
|
/// Use [generate_actor_keypair](crate::http_signatures::generate_actor_keypair) to create the
|
||||||
|
/// actor keypair.
|
||||||
|
fn private_key_pem(&self) -> Option<String>;
|
||||||
|
|
||||||
/// The inbox where activities for this user should be sent to
|
/// The inbox where activities for this user should be sent to
|
||||||
fn inbox(&self) -> Url;
|
fn inbox(&self) -> Url;
|
||||||
|
|
||||||
/// Generates a public key struct for use in the actor json representation
|
/// Generates a public key struct for use in the actor json representation
|
||||||
fn public_key(&self) -> PublicKey {
|
fn public_key(&self) -> PublicKey {
|
||||||
PublicKey::new(self.id().clone(), self.public_key_pem().to_string())
|
PublicKey::new(self.id(), self.public_key_pem().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The actor's shared inbox, if any
|
/// The actor's shared inbox, if any
|
||||||
|
@ -273,15 +284,56 @@ where
|
||||||
self.deref().actor()
|
self.deref().actor()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn verify(&self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
|
async fn verify(&self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
(*self).verify(data).await
|
self.deref().verify(data).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(self, data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
|
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
(*self).receive(data).await
|
(*self).receive(data).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait for federating collections
|
||||||
|
#[async_trait]
|
||||||
|
pub trait ApubCollection: Sized {
|
||||||
|
/// Actor or object that this collection belongs to
|
||||||
|
type Owner;
|
||||||
|
/// App data type passed to handlers. Must be identical to
|
||||||
|
/// [crate::config::FederationConfigBuilder::app_data] type.
|
||||||
|
type DataType: Clone + Send + Sync;
|
||||||
|
/// The type of protocol struct which gets sent over network to federate this database struct.
|
||||||
|
type ApubType: for<'de2> Deserialize<'de2>;
|
||||||
|
/// Error type returned by handler methods
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
/// Reads local collection from database and returns it as Activitypub JSON.
|
||||||
|
async fn read_local(
|
||||||
|
owner: &Self::Owner,
|
||||||
|
data: &Data<Self::DataType>,
|
||||||
|
) -> Result<Self::ApubType, Self::Error>;
|
||||||
|
|
||||||
|
/// Verifies that the received object is valid.
|
||||||
|
///
|
||||||
|
/// You should check here that the domain of id matches `expected_domain`. Additionally you
|
||||||
|
/// should perform any application specific checks.
|
||||||
|
async fn verify(
|
||||||
|
apub: &Self::ApubType,
|
||||||
|
expected_domain: &Url,
|
||||||
|
data: &Data<Self::DataType>,
|
||||||
|
) -> Result<(), Self::Error>;
|
||||||
|
|
||||||
|
/// Convert object from ActivityPub type to database type.
|
||||||
|
///
|
||||||
|
/// Called when an object is received from HTTP fetch or as part of an activity. This method
|
||||||
|
/// should also write the received object to database. Note that there is no distinction
|
||||||
|
/// between create and update, so an `upsert` operation should be used.
|
||||||
|
async fn from_apub(
|
||||||
|
apub: Self::ApubType,
|
||||||
|
owner: &Self::Owner,
|
||||||
|
data: &Data<Self::DataType>,
|
||||||
|
) -> Result<Self, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
/// Some impls of these traits for use in tests. Dont use this from external crates.
|
/// Some impls of these traits for use in tests. Dont use this from external crates.
|
||||||
///
|
///
|
||||||
/// TODO: Should be using `cfg[doctest]` but blocked by <https://github.com/rust-lang/rust/issues/67295>
|
/// TODO: Should be using `cfg[doctest]` but blocked by <https://github.com/rust-lang/rust/issues/67295>
|
||||||
|
@ -303,7 +355,7 @@ pub mod tests {
|
||||||
pub struct DbConnection;
|
pub struct DbConnection;
|
||||||
|
|
||||||
impl DbConnection {
|
impl DbConnection {
|
||||||
pub async fn read_user_from_apub_id<T>(&self, _: Url) -> Result<Option<T>, Error> {
|
pub async fn read_post_from_apub_id<T>(&self, _: Url) -> Result<Option<T>, Error> {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
pub async fn read_local_user(&self, _: String) -> Result<DbUser, Error> {
|
pub async fn read_local_user(&self, _: String) -> Result<DbUser, Error> {
|
||||||
|
@ -346,7 +398,7 @@ pub mod tests {
|
||||||
apub_id: "https://localhost/123".parse().unwrap(),
|
apub_id: "https://localhost/123".parse().unwrap(),
|
||||||
inbox: "https://localhost/123/inbox".parse().unwrap(),
|
inbox: "https://localhost/123/inbox".parse().unwrap(),
|
||||||
public_key: DB_USER_KEYPAIR.public_key.clone(),
|
public_key: DB_USER_KEYPAIR.public_key.clone(),
|
||||||
private_key: None,
|
private_key: Some(DB_USER_KEYPAIR.private_key.clone()),
|
||||||
followers: vec![],
|
followers: vec![],
|
||||||
local: false,
|
local: false,
|
||||||
});
|
});
|
||||||
|
@ -359,29 +411,28 @@ pub mod tests {
|
||||||
|
|
||||||
async fn read_from_apub_id(
|
async fn read_from_apub_id(
|
||||||
_object_id: Url,
|
_object_id: Url,
|
||||||
_data: &RequestData<Self::DataType>,
|
_data: &Data<Self::DataType>,
|
||||||
) -> Result<Option<Self>, Self::Error> {
|
) -> Result<Option<Self>, Self::Error> {
|
||||||
Ok(Some(DB_USER.clone()))
|
Ok(Some(DB_USER.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn into_apub(
|
async fn into_apub(
|
||||||
self,
|
self,
|
||||||
_data: &RequestData<Self::DataType>,
|
_data: &Data<Self::DataType>,
|
||||||
) -> Result<Self::ApubType, Self::Error> {
|
) -> Result<Self::ApubType, Self::Error> {
|
||||||
let public_key = PublicKey::new(self.apub_id.clone(), self.public_key.clone());
|
|
||||||
Ok(Person {
|
Ok(Person {
|
||||||
preferred_username: self.name.clone(),
|
preferred_username: self.name.clone(),
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
id: self.apub_id.into(),
|
id: self.apub_id.clone().into(),
|
||||||
inbox: self.inbox,
|
inbox: self.inbox.clone(),
|
||||||
public_key,
|
public_key: self.public_key(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn verify(
|
async fn verify(
|
||||||
apub: &Self::ApubType,
|
apub: &Self::ApubType,
|
||||||
expected_domain: &Url,
|
expected_domain: &Url,
|
||||||
_data: &RequestData<Self::DataType>,
|
_data: &Data<Self::DataType>,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
verify_domains_match(apub.id.inner(), expected_domain)?;
|
verify_domains_match(apub.id.inner(), expected_domain)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -389,7 +440,7 @@ pub mod tests {
|
||||||
|
|
||||||
async fn from_apub(
|
async fn from_apub(
|
||||||
apub: Self::ApubType,
|
apub: Self::ApubType,
|
||||||
_data: &RequestData<Self::DataType>,
|
_data: &Data<Self::DataType>,
|
||||||
) -> Result<Self, Self::Error> {
|
) -> Result<Self, Self::Error> {
|
||||||
Ok(DbUser {
|
Ok(DbUser {
|
||||||
name: apub.preferred_username,
|
name: apub.preferred_username,
|
||||||
|
@ -404,14 +455,18 @@ pub mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for DbUser {
|
impl Actor for DbUser {
|
||||||
fn id(&self) -> &Url {
|
fn id(&self) -> Url {
|
||||||
&self.apub_id
|
self.apub_id.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn public_key_pem(&self) -> &str {
|
fn public_key_pem(&self) -> &str {
|
||||||
&self.public_key
|
&self.public_key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn private_key_pem(&self) -> Option<String> {
|
||||||
|
self.private_key.clone()
|
||||||
|
}
|
||||||
|
|
||||||
fn inbox(&self) -> Url {
|
fn inbox(&self) -> Url {
|
||||||
self.inbox.clone()
|
self.inbox.clone()
|
||||||
}
|
}
|
||||||
|
@ -440,11 +495,11 @@ pub mod tests {
|
||||||
self.actor.inner()
|
self.actor.inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn verify(&self, _: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
|
async fn verify(&self, _: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(self, _data: &RequestData<Self::DataType>) -> Result<(), Self::Error> {
|
async fn receive(self, _data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -463,29 +518,26 @@ pub mod tests {
|
||||||
|
|
||||||
async fn read_from_apub_id(
|
async fn read_from_apub_id(
|
||||||
_: Url,
|
_: Url,
|
||||||
_: &RequestData<Self::DataType>,
|
_: &Data<Self::DataType>,
|
||||||
) -> Result<Option<Self>, Self::Error> {
|
) -> Result<Option<Self>, Self::Error> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn into_apub(
|
async fn into_apub(self, _: &Data<Self::DataType>) -> Result<Self::ApubType, Self::Error> {
|
||||||
self,
|
|
||||||
_: &RequestData<Self::DataType>,
|
|
||||||
) -> Result<Self::ApubType, Self::Error> {
|
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn verify(
|
async fn verify(
|
||||||
_: &Self::ApubType,
|
_: &Self::ApubType,
|
||||||
_: &Url,
|
_: &Url,
|
||||||
_: &RequestData<Self::DataType>,
|
_: &Data<Self::DataType>,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn from_apub(
|
async fn from_apub(
|
||||||
_: Self::ApubType,
|
_: Self::ApubType,
|
||||||
_: &RequestData<Self::DataType>,
|
_: &Data<Self::DataType>,
|
||||||
) -> Result<Self, Self::Error> {
|
) -> Result<Self, Self::Error> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue