Dont use apub in type names

This commit is contained in:
Felix Ableitner 2023-03-16 21:41:29 +01:00
parent bd3f17a4df
commit 072353fc41
30 changed files with 258 additions and 276 deletions

2
Cargo.lock generated
View file

@ -4,7 +4,7 @@ version = 3
[[package]]
name = "activitypub_federation"
version = "0.4.0-rc2"
version = "0.4.0-rc3"
dependencies = [
"activitystreams-kinds",
"actix-rt",

View file

@ -73,7 +73,7 @@ pub struct DbUser {
pub display_name: String,
pub password_hash: Option<String>,
pub email: Option<String>,
pub apub_id: Url,
pub federation_id: Url,
pub inbox: Url,
pub outbox: Url,
pub local: bool,
@ -83,8 +83,8 @@ pub struct DbUser {
}
```
Field names and other details of this type can be chosen freely according to your requirements. It only matters that the required data is being stored. Its important that this struct doesn't represent only local users who registered directly on our website, but also remote users that are registered on other instances and federated to us. The `local` column helps to easily distinguish both. It can also be distinguished from the domain of the `apub_id` URL, but that would be a much more expensive operation. All users have a `public_key`, but only local users have a `private_key`. On the other hand, `password_hash` and `email` are only present for local users. inbox` and `outbox` URLs need to be stored because each implementation is free to choose its own format for them, so they can't be regenerated on the fly.
Field names and other details of this type can be chosen freely according to your requirements. It only matters that the required data is being stored. Its important that this struct doesn't represent only local users who registered directly on our website, but also remote users that are registered on other instances and federated to us. The `local` column helps to easily distinguish both. It can also be distinguished from the domain of the `federation_id` URL, but that would be a much more expensive operation. All users have a `public_key`, but only local users have a `private_key`. On the other hand, `password_hash` and `email` are only present for local users. inbox` and `outbox` URLs need to be stored because each implementation is free to choose its own format for them, so they can't be regenerated on the fly.
In larger projects it makes sense to split this data in two. One for data relevant to local users (`password_hash`, `email` etc.) and one for data that is shared by both local and federated users (`apub_id`, `public_key` etc).
In larger projects it makes sense to split this data in two. One for data relevant to local users (`password_hash`, `email` etc.) and one for data that is shared by both local and federated users (`federation_id`, `public_key` etc).
Finally we need to implement the traits [ApubObject](crate::traits::ApubObject) and [Actor](crate::traits::Actor) for `DbUser`. These traits are used to convert between `Person` and `DbUser` types. [ApubObject::from_apub](crate::traits::ApubObject::from_apub) must store the received object in database, so that it can later be retrieved without network calls using [ApubObject::read_from_apub_id](crate::traits::ApubObject::read_from_apub_id). Refer to the documentation for more details.
Finally we need to implement the traits [Object](crate::traits::Object) and [Actor](crate::traits::Actor) for `DbUser`. These traits are used to convert between `Person` and `DbUser` types. [Object::from_json](crate::traits::Object::from_json) must store the received object in database, so that it can later be retrieved without network calls using [Object::read_from_id](crate::traits::Object::read_from_id). Refer to the documentation for more details.

View file

@ -25,4 +25,4 @@ The most important fields are:
- `attributedTo`: ID of the user who created this post
- `to`, `cc`: Who the object is for. The special "public" URL indicates that everyone can view it. It also gets delivered to followers of the LemmyDev account.
Just like for `Person` before, we need to implement a protocol type and a database type, then implement trait `ApubObject`. See the example for details.
Just like for `Person` before, we need to implement a protocol type and a database type, then implement trait `Object`. See the example for details.

View file

@ -6,17 +6,17 @@ The next step is to allow other servers to fetch our actors and objects. For thi
# use std::net::SocketAddr;
# use activitypub_federation::config::FederationConfig;
# use activitypub_federation::protocol::context::WithContext;
# use activitypub_federation::axum::json::ApubJson;
# use activitypub_federation::axum::json::FederationJson;
# use anyhow::Error;
# use activitypub_federation::traits::tests::Person;
# use activitypub_federation::config::Data;
# use activitypub_federation::traits::tests::DbConnection;
# use axum::extract::Path;
# use activitypub_federation::config::ApubMiddleware;
# use activitypub_federation::config::FederationMiddleware;
# use axum::routing::get;
# use crate::activitypub_federation::traits::ApubObject;
# use crate::activitypub_federation::traits::Object;
# use axum::headers::ContentType;
# use activitypub_federation::APUB_JSON_CONTENT_TYPE;
# use activitypub_federation::FEDERATION_CONTENT_TYPE;
# use axum::TypedHeader;
# use axum::response::IntoResponse;
# use http::HeaderMap;
@ -31,7 +31,7 @@ async fn main() -> Result<(), Error> {
let app = axum::Router::new()
.route("/user/:name", get(http_get_user))
.layer(ApubMiddleware::new(data));
.layer(FederationMiddleware::new(data));
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
tracing::debug!("listening on {}", addr);
@ -47,10 +47,10 @@ async fn http_get_user(
data: Data<DbConnection>,
) -> impl IntoResponse {
let accept = header_map.get("accept").map(|v| v.to_str().unwrap());
if accept == Some(APUB_JSON_CONTENT_TYPE) {
if accept == Some(FEDERATION_CONTENT_TYPE) {
let db_user = data.read_local_user(name).await.unwrap();
let apub_user = db_user.into_apub(&data).await.unwrap();
ApubJson(WithContext::new_default(apub_user)).into_response()
let json_user = db_user.into_json(&data).await.unwrap();
FederationJson(WithContext::new_default(json_user)).into_response()
}
else {
generate_user_html(name, data).await
@ -60,7 +60,7 @@ async fn http_get_user(
There are a couple of things going on here. Like before we are constructing the federation config with our domain and application data. We pass this to a middleware to make it available in request handlers, then listening on a port with the axum webserver.
The `http_get_user` method allows retrieving a user profile from `/user/:name`. It checks the `accept` header, and compares it to the one used by Activitypub (`application/activity+json`). If it matches, the user is read from database and converted to Activitypub json format. The `context` field is added (`WithContext` for `json-ld` compliance), and it is converted to a JSON response with header `content-type: application/activity+json` using `ApubJson`. It can now be retrieved with the command `curl -H 'Accept: application/activity+json' ...` introduced earlier, or with `ObjectId`.
The `http_get_user` method allows retrieving a user profile from `/user/:name`. It checks the `accept` header, and compares it to the one used by Activitypub (`application/activity+json`). If it matches, the user is read from database and converted to Activitypub json format. The `context` field is added (`WithContext` for `json-ld` compliance), and it is converted to a JSON response with header `content-type: application/activity+json` using `FederationJson`. It can now be retrieved with the command `curl -H 'Accept: application/activity+json' ...` introduced earlier, or with `ObjectId`.
If the `accept` header doesn't match, it renders the user profile as HTML for viewing in a web browser.
@ -89,6 +89,6 @@ async fn webfinger(
) -> Result<Json<Webfinger>, Error> {
let name = extract_webfinger_name(&query.resource, &data)?;
let db_user = data.read_local_user(name).await?;
Ok(Json(build_webfinger_response(query.resource, db_user.apub_id)))
Ok(Json(build_webfinger_response(query.resource, db_user.federation_id)))
}
```

View file

@ -21,7 +21,7 @@ assert!(user.is_ok());
}).unwrap()
```
`dereference` retrieves the object JSON at the given URL, and uses serde to convert it to `Person`. It then calls your method `ApubObject::from_apub` which inserts it in the database and returns a `DbUser` struct. `request_data` contains the federation config as well as a counter of outgoing HTTP requests. If this counter exceeds the configured maximum, further requests are aborted in order to avoid recursive fetching which could allow for a denial of service attack.
`dereference` retrieves the object JSON at the given URL, and uses serde to convert it to `Person`. It then calls your method `Object::from_json` which inserts it in the database and returns a `DbUser` struct. `request_data` contains the federation config as well as a counter of outgoing HTTP requests. If this counter exceeds the configured maximum, further requests are aborted in order to avoid recursive fetching which could allow for a denial of service attack.
After dereferencing a remote object, it is stored in the local database and can be retrieved using [ObjectId::dereference_local](crate::fetch::object_id::ObjectId::dereference_local) without any network requests. This is important for performance reasons and for searching.

View file

@ -21,7 +21,7 @@ To send an activity we need to initialize our previously defined struct, and pic
# let recipient = DB_USER.clone();
let activity = Follow {
actor: ObjectId::parse("https://lemmy.ml/u/nutomic")?,
object: recipient.apub_id.clone().into(),
object: recipient.federation_id.clone().into(),
kind: Default::default(),
id: "https://lemmy.ml/activities/321".try_into()?
};

View file

@ -5,7 +5,7 @@ It is sometimes necessary to fetch from a URL, but we don't know the exact type
```no_run
# use activitypub_federation::traits::tests::{DbUser, DbPost};
# use activitypub_federation::fetch::object_id::ObjectId;
# use activitypub_federation::traits::ApubObject;
# use activitypub_federation::traits::Object;
# use activitypub_federation::config::FederationConfig;
# use serde::{Deserialize, Serialize};
# use activitypub_federation::traits::tests::DbConnection;
@ -20,43 +20,43 @@ pub enum SearchableDbObjects {
#[derive(Deserialize, Serialize)]
#[serde(untagged)]
pub enum SearchableApubObjects {
pub enum SearchableObjects {
Person(Person),
Note(Note)
}
#[async_trait::async_trait]
impl ApubObject for SearchableDbObjects {
impl Object for SearchableDbObjects {
type DataType = DbConnection;
type ApubType = SearchableApubObjects;
type Kind = SearchableObjects;
type Error = anyhow::Error;
async fn read_from_apub_id(
async fn read_from_id(
object_id: Url,
data: &Data<Self::DataType>,
) -> Result<Option<Self>, Self::Error> {
Ok(None)
}
async fn into_apub(
async fn into_json(
self,
data: &Data<Self::DataType>,
) -> Result<Self::ApubType, Self::Error> {
) -> Result<Self::Kind, Self::Error> {
unimplemented!();
}
async fn verify(apub: &Self::ApubType, expected_domain: &Url, _data: &Data<Self::DataType>) -> Result<(), Self::Error> {
async fn verify(json: &Self::Kind, expected_domain: &Url, _data: &Data<Self::DataType>) -> Result<(), Self::Error> {
Ok(())
}
async fn from_apub(
apub: Self::ApubType,
async fn from_json(
json: Self::Kind,
data: &Data<Self::DataType>,
) -> Result<Self, Self::Error> {
use SearchableDbObjects::*;
match apub {
SearchableApubObjects::Person(p) => Ok(User(DbUser::from_apub(p, data).await?)),
SearchableApubObjects::Note(n) => Ok(Post(DbPost::from_apub(n, data).await?)),
match json {
SearchableObjects::Person(p) => Ok(User(DbUser::from_json(p, data).await?)),
SearchableObjects::Note(n) => Ok(Post(DbPost::from_json(n, data).await?)),
}
}
}

View file

@ -11,7 +11,7 @@ use activitypub_federation::{
fetch::object_id::ObjectId,
kinds::activity::CreateType,
protocol::{context::WithContext, helpers::deserialize_one_or_many},
traits::{ActivityHandler, ApubObject},
traits::{ActivityHandler, Object},
};
use serde::{Deserialize, Serialize};
use url::Url;
@ -63,7 +63,7 @@ impl ActivityHandler for CreatePost {
}
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
DbPost::from_apub(self.object, data).await?;
DbPost::from_json(self.object, data).await?;
Ok(())
}
}

View file

@ -6,12 +6,12 @@ use crate::{
use activitypub_federation::{
axum::{
inbox::{receive_activity, ActivityData},
json::ApubJson,
json::FederationJson,
},
config::Data,
fetch::webfinger::{build_webfinger_response, extract_webfinger_name, Webfinger},
protocol::context::WithContext,
traits::ApubObject,
traits::Object,
};
use axum::{
extract::{Path, Query},
@ -32,10 +32,10 @@ impl IntoResponse for Error {
pub async fn http_get_user(
Path(name): Path<String>,
data: Data<DatabaseHandle>,
) -> Result<ApubJson<WithContext<Person>>, Error> {
) -> Result<FederationJson<WithContext<Person>>, Error> {
let db_user = data.read_user(&name)?;
let apub_user = db_user.into_apub(&data).await?;
Ok(ApubJson(WithContext::new_default(apub_user)))
let json_user = db_user.into_json(&data).await?;
Ok(FederationJson(WithContext::new_default(json_user)))
}
#[debug_handler]

View file

@ -4,7 +4,7 @@ use crate::{
objects::{person::DbUser, post::DbPost},
utils::generate_object_id,
};
use activitypub_federation::config::{ApubMiddleware, FederationConfig};
use activitypub_federation::config::{FederationConfig, FederationMiddleware};
use axum::{
routing::{get, post},
Router,
@ -55,7 +55,7 @@ async fn main() -> Result<(), Error> {
.route("/:user", get(http_get_user))
.route("/:user/inbox", post(http_post_user_inbox))
.route("/.well-known/webfinger", get(webfinger))
.layer(ApubMiddleware::new(config));
.layer(FederationMiddleware::new(config));
let addr = BIND_ADDRESS
.to_socket_addrs()?

View file

@ -5,7 +5,7 @@ use activitypub_federation::{
http_signatures::generate_actor_keypair,
kinds::actor::PersonType,
protocol::{public_key::PublicKey, verification::verify_domains_match},
traits::{ActivityHandler, Actor, ApubObject},
traits::{ActivityHandler, Actor, Object},
};
use chrono::{Local, NaiveDateTime};
use serde::{Deserialize, Serialize};
@ -64,16 +64,16 @@ pub struct Person {
}
#[async_trait::async_trait]
impl ApubObject for DbUser {
impl Object for DbUser {
type DataType = DatabaseHandle;
type ApubType = Person;
type Kind = Person;
type Error = Error;
fn last_refreshed_at(&self) -> Option<NaiveDateTime> {
Some(self.last_refreshed_at)
}
async fn read_from_apub_id(
async fn read_from_id(
object_id: Url,
data: &Data<Self::DataType>,
) -> Result<Option<Self>, Self::Error> {
@ -85,7 +85,7 @@ impl ApubObject for DbUser {
Ok(res)
}
async fn into_apub(self, _data: &Data<Self::DataType>) -> Result<Self::ApubType, Self::Error> {
async fn into_json(self, _data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
Ok(Person {
preferred_username: self.name.clone(),
kind: Default::default(),
@ -96,23 +96,23 @@ impl ApubObject for DbUser {
}
async fn verify(
apub: &Self::ApubType,
json: &Self::Kind,
expected_domain: &Url,
_data: &Data<Self::DataType>,
) -> Result<(), Self::Error> {
verify_domains_match(apub.id.inner(), expected_domain)?;
verify_domains_match(json.id.inner(), expected_domain)?;
Ok(())
}
async fn from_apub(
apub: Self::ApubType,
async fn from_json(
json: Self::Kind,
_data: &Data<Self::DataType>,
) -> Result<Self, Self::Error> {
Ok(DbUser {
name: apub.preferred_username,
ap_id: apub.id,
inbox: apub.inbox,
public_key: apub.public_key.public_key_pem,
name: json.preferred_username,
ap_id: json.id,
inbox: json.inbox,
public_key: json.public_key.public_key_pem,
private_key: None,
last_refreshed_at: Local::now().naive_local(),
followers: vec![],

View file

@ -10,7 +10,7 @@ use activitypub_federation::{
fetch::object_id::ObjectId,
kinds::{object::NoteType, public},
protocol::{helpers::deserialize_one_or_many, verification::verify_domains_match},
traits::{Actor, ApubObject},
traits::{Actor, Object},
};
use activitystreams_kinds::link::MentionType;
use serde::{Deserialize, Serialize};
@ -46,44 +46,41 @@ pub struct Mention {
}
#[async_trait::async_trait]
impl ApubObject for DbPost {
impl Object for DbPost {
type DataType = DatabaseHandle;
type ApubType = Note;
type Kind = Note;
type Error = Error;
async fn read_from_apub_id(
async fn read_from_id(
_object_id: Url,
_data: &Data<Self::DataType>,
) -> Result<Option<Self>, Self::Error> {
Ok(None)
}
async fn into_apub(self, _data: &Data<Self::DataType>) -> Result<Self::ApubType, Self::Error> {
async fn into_json(self, _data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
unimplemented!()
}
async fn verify(
apub: &Self::ApubType,
json: &Self::Kind,
expected_domain: &Url,
_data: &Data<Self::DataType>,
) -> Result<(), Self::Error> {
verify_domains_match(apub.id.inner(), expected_domain)?;
verify_domains_match(json.id.inner(), expected_domain)?;
Ok(())
}
async fn from_apub(
apub: Self::ApubType,
data: &Data<Self::DataType>,
) -> Result<Self, Self::Error> {
async fn from_json(json: Self::Kind, data: &Data<Self::DataType>) -> Result<Self, Self::Error> {
println!(
"Received post with content {} and id {}",
&apub.content, &apub.id
&json.content, &json.id
);
let creator = apub.attributed_to.dereference(data).await?;
let creator = json.attributed_to.dereference(data).await?;
let post = DbPost {
text: apub.content,
ap_id: apub.id.clone(),
creator: apub.attributed_to.clone(),
text: json.content,
ap_id: json.id.clone(),
creator: json.attributed_to.clone(),
local: false,
};
@ -97,7 +94,7 @@ impl ApubObject for DbPost {
attributed_to: data.local_user().ap_id,
to: vec![public()],
content: format!("Hello {}", creator.name),
in_reply_to: Some(apub.id.clone()),
in_reply_to: Some(json.id.clone()),
tag: vec![mention],
};
CreatePost::send(note, creator.shared_inbox_or_inbox(), data).await?;

View file

@ -8,7 +8,7 @@ use activitypub_federation::{
fetch::object_id::ObjectId,
kinds::activity::CreateType,
protocol::helpers::deserialize_one_or_many,
traits::{ActivityHandler, ApubObject},
traits::{ActivityHandler, Object},
};
use serde::{Deserialize, Serialize};
use url::Url;
@ -56,7 +56,7 @@ impl ActivityHandler for CreatePost {
}
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
DbPost::from_apub(self.object, data).await?;
DbPost::from_json(self.object, data).await?;
Ok(())
}
}

View file

@ -5,11 +5,11 @@ use crate::{
};
use activitypub_federation::{
actix_web::inbox::receive_activity,
config::{ApubMiddleware, Data, FederationConfig},
config::{Data, FederationConfig, FederationMiddleware},
fetch::webfinger::{build_webfinger_response, extract_webfinger_name},
protocol::context::WithContext,
traits::ApubObject,
APUB_JSON_CONTENT_TYPE,
traits::Object,
FEDERATION_CONTENT_TYPE,
};
use actix_web::{web, web::Bytes, App, HttpRequest, HttpResponse, HttpServer};
use anyhow::anyhow;
@ -22,7 +22,7 @@ pub fn listen(config: &FederationConfig<DatabaseHandle>) -> Result<(), Error> {
let config = config.clone();
let server = HttpServer::new(move || {
App::new()
.wrap(ApubMiddleware::new(config.clone()))
.wrap(FederationMiddleware::new(config.clone()))
.route("/{user}", web::get().to(http_get_user))
.route("/{user}/inbox", web::post().to(http_post_user_inbox))
.route("/.well-known/webfinger", web::get().to(webfinger))
@ -40,10 +40,10 @@ pub async fn http_get_user(
) -> Result<HttpResponse, Error> {
let db_user = data.local_user();
if user_name.into_inner() == db_user.name {
let apub_user = db_user.into_apub(&data).await?;
let json_user = db_user.into_json(&data).await?;
Ok(HttpResponse::Ok()
.content_type(APUB_JSON_CONTENT_TYPE)
.json(WithContext::new_default(apub_user)))
.content_type(FEDERATION_CONTENT_TYPE)
.json(WithContext::new_default(json_user)))
} else {
Err(anyhow!("Invalid user").into())
}

View file

@ -6,12 +6,12 @@ use crate::{
use activitypub_federation::{
axum::{
inbox::{receive_activity, ActivityData},
json::ApubJson,
json::FederationJson,
},
config::{ApubMiddleware, Data, FederationConfig},
config::{Data, FederationConfig, FederationMiddleware},
fetch::webfinger::{build_webfinger_response, extract_webfinger_name, Webfinger},
protocol::context::WithContext,
traits::ApubObject,
traits::Object,
};
use axum::{
extract::{Path, Query},
@ -33,7 +33,7 @@ pub fn listen(config: &FederationConfig<DatabaseHandle>) -> Result<(), Error> {
.route("/:user/inbox", post(http_post_user_inbox))
.route("/:user", get(http_get_user))
.route("/.well-known/webfinger", get(webfinger))
.layer(ApubMiddleware::new(config));
.layer(FederationMiddleware::new(config));
let addr = hostname
.to_socket_addrs()?
@ -49,10 +49,10 @@ pub fn listen(config: &FederationConfig<DatabaseHandle>) -> Result<(), Error> {
async fn http_get_user(
Path(name): Path<String>,
data: Data<DatabaseHandle>,
) -> Result<ApubJson<WithContext<Person>>, Error> {
) -> Result<FederationJson<WithContext<Person>>, Error> {
let db_user = data.read_user(&name)?;
let apub_user = db_user.into_apub(&data).await?;
Ok(ApubJson(WithContext::new_default(apub_user)))
let json_user = db_user.into_json(&data).await?;
Ok(FederationJson(WithContext::new_default(json_user)))
}
#[debug_handler]

View file

@ -12,7 +12,7 @@ use activitypub_federation::{
http_signatures::generate_actor_keypair,
kinds::actor::PersonType,
protocol::{context::WithContext, public_key::PublicKey, verification::verify_domains_match},
traits::{ActivityHandler, Actor, ApubObject},
traits::{ActivityHandler, Actor, Object},
};
use chrono::{Local, NaiveDateTime};
use serde::{Deserialize, Serialize};
@ -92,7 +92,7 @@ impl DbUser {
pub async fn post(&self, post: DbPost, data: &Data<DatabaseHandle>) -> Result<(), Error> {
let id = generate_object_id(data.domain())?;
let create = CreatePost::new(post.into_apub(data).await?, id.clone());
let create = CreatePost::new(post.into_json(data).await?, id.clone());
let mut inboxes = vec![];
for f in self.followers.clone() {
let user: DbUser = ObjectId::from(f).dereference(data).await?;
@ -119,16 +119,16 @@ impl DbUser {
}
#[async_trait::async_trait]
impl ApubObject for DbUser {
impl Object for DbUser {
type DataType = DatabaseHandle;
type ApubType = Person;
type Kind = Person;
type Error = Error;
fn last_refreshed_at(&self) -> Option<NaiveDateTime> {
Some(self.last_refreshed_at)
}
async fn read_from_apub_id(
async fn read_from_id(
object_id: Url,
data: &Data<Self::DataType>,
) -> Result<Option<Self>, Self::Error> {
@ -140,7 +140,7 @@ impl ApubObject for DbUser {
Ok(res)
}
async fn into_apub(self, _data: &Data<Self::DataType>) -> Result<Self::ApubType, Self::Error> {
async fn into_json(self, _data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
Ok(Person {
preferred_username: self.name.clone(),
kind: Default::default(),
@ -151,23 +151,20 @@ impl ApubObject for DbUser {
}
async fn verify(
apub: &Self::ApubType,
json: &Self::Kind,
expected_domain: &Url,
_data: &Data<Self::DataType>,
) -> Result<(), Self::Error> {
verify_domains_match(apub.id.inner(), expected_domain)?;
verify_domains_match(json.id.inner(), expected_domain)?;
Ok(())
}
async fn from_apub(
apub: Self::ApubType,
data: &Data<Self::DataType>,
) -> Result<Self, Self::Error> {
async fn from_json(json: Self::Kind, data: &Data<Self::DataType>) -> Result<Self, Self::Error> {
let user = DbUser {
name: apub.preferred_username,
ap_id: apub.id,
inbox: apub.inbox,
public_key: apub.public_key.public_key_pem,
name: json.preferred_username,
ap_id: json.id,
inbox: json.inbox,
public_key: json.public_key.public_key_pem,
private_key: None,
last_refreshed_at: Local::now().naive_local(),
followers: vec![],

View file

@ -4,7 +4,7 @@ use activitypub_federation::{
fetch::object_id::ObjectId,
kinds::{object::NoteType, public},
protocol::{helpers::deserialize_one_or_many, verification::verify_domains_match},
traits::ApubObject,
traits::Object,
};
use serde::{Deserialize, Serialize};
use url::Url;
@ -42,12 +42,12 @@ pub struct Note {
}
#[async_trait::async_trait]
impl ApubObject for DbPost {
impl Object for DbPost {
type DataType = DatabaseHandle;
type ApubType = Note;
type Kind = Note;
type Error = Error;
async fn read_from_apub_id(
async fn read_from_id(
object_id: Url,
data: &Data<Self::DataType>,
) -> Result<Option<Self>, Self::Error> {
@ -59,7 +59,7 @@ impl ApubObject for DbPost {
Ok(res)
}
async fn into_apub(self, data: &Data<Self::DataType>) -> Result<Self::ApubType, Self::Error> {
async fn into_json(self, data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
let creator = self.creator.dereference_local(data).await?;
Ok(Note {
kind: Default::default(),
@ -71,22 +71,19 @@ impl ApubObject for DbPost {
}
async fn verify(
apub: &Self::ApubType,
json: &Self::Kind,
expected_domain: &Url,
_data: &Data<Self::DataType>,
) -> Result<(), Self::Error> {
verify_domains_match(apub.id.inner(), expected_domain)?;
verify_domains_match(json.id.inner(), expected_domain)?;
Ok(())
}
async fn from_apub(
apub: Self::ApubType,
data: &Data<Self::DataType>,
) -> Result<Self, Self::Error> {
async fn from_json(json: Self::Kind, data: &Data<Self::DataType>) -> Result<Self, Self::Error> {
let post = DbPost {
text: apub.content,
ap_id: apub.id,
creator: apub.attributed_to,
text: json.content,
ap_id: json.id,
creator: json.attributed_to,
local: false,
};

View file

@ -8,7 +8,7 @@ use crate::{
http_signatures::sign_request,
reqwest_shim::ResponseExt,
traits::{ActivityHandler, Actor},
APUB_JSON_CONTENT_TYPE,
FEDERATION_CONTENT_TYPE,
};
use anyhow::anyhow;
use background_jobs::{
@ -203,7 +203,7 @@ fn generate_request_headers(inbox_url: &Url) -> HeaderMap {
let mut headers = HeaderMap::new();
headers.insert(
HeaderName::from_static("content-type"),
HeaderValue::from_static(APUB_JSON_CONTENT_TYPE),
HeaderValue::from_static(FEDERATION_CONTENT_TYPE),
);
headers.insert(
HeaderName::from_static("host"),

View file

@ -5,7 +5,7 @@ use crate::{
error::Error,
fetch::object_id::ObjectId,
http_signatures::{verify_inbox_hash, verify_signature},
traits::{ActivityHandler, Actor, ApubObject},
traits::{ActivityHandler, Actor, Object},
};
use actix_web::{web::Bytes, HttpRequest, HttpResponse};
use serde::de::DeserializeOwned;
@ -21,13 +21,13 @@ pub async fn receive_activity<Activity, ActorT, Datatype>(
) -> Result<HttpResponse, <Activity as ActivityHandler>::Error>
where
Activity: ActivityHandler<DataType = Datatype> + DeserializeOwned + Send + 'static,
ActorT: ApubObject<DataType = Datatype> + Actor + Send + 'static,
for<'de2> <ActorT as ApubObject>::ApubType: serde::Deserialize<'de2>,
ActorT: Object<DataType = Datatype> + Actor + Send + 'static,
for<'de2> <ActorT as Object>::Kind: serde::Deserialize<'de2>,
<Activity as ActivityHandler>::Error: From<anyhow::Error>
+ From<Error>
+ From<<ActorT as ApubObject>::Error>
+ From<<ActorT as Object>::Error>
+ From<serde_json::Error>,
<ActorT as ApubObject>::Error: From<Error> + From<anyhow::Error>,
<ActorT as Object>::Error: From<Error> + From<anyhow::Error>,
Datatype: Clone,
{
verify_inbox_hash(request.headers().get("Digest"), &body)?;

View file

@ -1,4 +1,4 @@
use crate::config::{ApubMiddleware, Data, FederationConfig};
use crate::config::{Data, FederationConfig, FederationMiddleware};
use actix_web::{
dev::{forward_ready, Payload, Service, ServiceRequest, ServiceResponse, Transform},
Error,
@ -8,7 +8,7 @@ use actix_web::{
};
use std::future::{ready, Ready};
impl<S, B, T> Transform<S, ServiceRequest> for ApubMiddleware<T>
impl<S, B, T> Transform<S, ServiceRequest> for FederationMiddleware<T>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
@ -17,12 +17,12 @@ where
{
type Response = ServiceResponse<B>;
type Error = Error;
type Transform = ApubService<S, T>;
type Transform = FederationService<S, T>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(ApubService {
ready(Ok(FederationService {
service,
config: self.0.clone(),
}))
@ -31,7 +31,7 @@ where
/// Passes [FederationConfig] to HTTP handlers, converting it to [Data] in the process
#[doc(hidden)]
pub struct ApubService<S, T: Clone>
pub struct FederationService<S, T: Clone>
where
S: Service<ServiceRequest, Error = Error>,
S::Future: 'static,
@ -41,7 +41,7 @@ where
config: FederationConfig<T>,
}
impl<S, B, T> Service<ServiceRequest> for ApubService<S, T>
impl<S, B, T> Service<ServiceRequest> for FederationService<S, T>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
@ -69,7 +69,7 @@ impl<T: Clone + 'static> FromRequest for Data<T> {
ready(match req.extensions().get::<FederationConfig<T>>() {
Some(c) => Ok(c.to_request_data()),
None => Err(actix_web::error::ErrorBadRequest(
"Missing extension, did you register ApubMiddleware?",
"Missing extension, did you register FederationMiddleware?",
)),
})
}

View file

@ -7,7 +7,7 @@ use crate::{
error::Error,
fetch::object_id::ObjectId,
http_signatures::{verify_inbox_hash, verify_signature},
traits::{ActivityHandler, Actor, ApubObject},
traits::{ActivityHandler, Actor, Object},
};
use axum::{
async_trait,
@ -27,13 +27,13 @@ pub async fn receive_activity<Activity, ActorT, Datatype>(
) -> Result<(), <Activity as ActivityHandler>::Error>
where
Activity: ActivityHandler<DataType = Datatype> + DeserializeOwned + Send + 'static,
ActorT: ApubObject<DataType = Datatype> + Actor + Send + 'static,
for<'de2> <ActorT as ApubObject>::ApubType: serde::Deserialize<'de2>,
ActorT: Object<DataType = Datatype> + Actor + Send + 'static,
for<'de2> <ActorT as Object>::Kind: serde::Deserialize<'de2>,
<Activity as ActivityHandler>::Error: From<anyhow::Error>
+ From<Error>
+ From<<ActorT as ApubObject>::Error>
+ From<<ActorT as Object>::Error>
+ From<serde_json::Error>,
<ActorT as ApubObject>::Error: From<Error> + From<anyhow::Error>,
<ActorT as Object>::Error: From<Error> + From<anyhow::Error>,
Datatype: Clone,
{
verify_inbox_hash(activity_data.headers.get("Digest"), &activity_data.body)?;

View file

@ -3,34 +3,34 @@
//! ```
//! # use anyhow::Error;
//! # use axum::extract::Path;
//! # use activitypub_federation::axum::json::ApubJson;
//! # use activitypub_federation::axum::json::FederationJson;
//! # use activitypub_federation::protocol::context::WithContext;
//! # use activitypub_federation::config::Data;
//! # use activitypub_federation::traits::ApubObject;
//! # use activitypub_federation::traits::Object;
//! # use activitypub_federation::traits::tests::{DbConnection, DbUser, Person};
//! async fn http_get_user(Path(name): Path<String>, data: Data<DbConnection>) -> Result<ApubJson<WithContext<Person>>, Error> {
//! async fn http_get_user(Path(name): Path<String>, data: Data<DbConnection>) -> Result<FederationJson<WithContext<Person>>, Error> {
//! let user: DbUser = data.read_local_user(name).await?;
//! let person = user.into_apub(&data).await?;
//! let person = user.into_json(&data).await?;
//!
//! Ok(ApubJson(WithContext::new_default(person)))
//! Ok(FederationJson(WithContext::new_default(person)))
//! }
//! ```
use crate::APUB_JSON_CONTENT_TYPE;
use crate::FEDERATION_CONTENT_TYPE;
use axum::response::IntoResponse;
use http::header;
use serde::Serialize;
/// Wrapper struct to respond with `application/activity+json` in axum handlers
#[derive(Debug, Clone, Copy, Default)]
pub struct ApubJson<Json: Serialize>(pub Json);
pub struct FederationJson<Json: Serialize>(pub Json);
impl<Json: Serialize> IntoResponse for ApubJson<Json> {
impl<Json: Serialize> IntoResponse for FederationJson<Json> {
fn into_response(self) -> axum::response::Response {
let mut response = axum::response::Json(self.0).into_response();
response.headers_mut().insert(
header::CONTENT_TYPE,
APUB_JSON_CONTENT_TYPE
FEDERATION_CONTENT_TYPE
.parse()
.expect("Parsing 'application/activity+json' should never fail"),
);

View file

@ -1,14 +1,14 @@
use crate::config::{ApubMiddleware, Data, FederationConfig};
use crate::config::{Data, FederationConfig, FederationMiddleware};
use axum::{async_trait, body::Body, extract::FromRequestParts, http::Request, response::Response};
use http::{request::Parts, StatusCode};
use std::task::{Context, Poll};
use tower::{Layer, Service};
impl<S, T: Clone> Layer<S> for ApubMiddleware<T> {
type Service = ApubService<S, T>;
impl<S, T: Clone> Layer<S> for FederationMiddleware<T> {
type Service = FederationService<S, T>;
fn layer(&self, inner: S) -> Self::Service {
ApubService {
FederationService {
inner,
config: self.0.clone(),
}
@ -18,12 +18,12 @@ impl<S, T: Clone> Layer<S> for ApubMiddleware<T> {
/// Passes [FederationConfig] to HTTP handlers, converting it to [Data] in the process
#[doc(hidden)]
#[derive(Clone)]
pub struct ApubService<S, T: Clone> {
pub struct FederationService<S, T: Clone> {
inner: S,
config: FederationConfig<T>,
}
impl<S, T> Service<Request<Body>> for ApubService<S, T>
impl<S, T> Service<Request<Body>> for FederationService<S, T>
where
S: Service<Request<Body>, Response = Response> + Send + 'static,
S::Future: Send + 'static,
@ -56,7 +56,7 @@ where
Some(c) => Ok(c.to_request_data()),
None => Err((
StatusCode::INTERNAL_SERVER_ERROR,
"Missing extension, did you register ApubMiddleware?",
"Missing extension, did you register FederationMiddleware?",
)),
}
}

View file

@ -295,11 +295,11 @@ impl<T: Clone> Deref for Data<T> {
/// Middleware for HTTP handlers which provides access to [Data]
#[derive(Clone)]
pub struct ApubMiddleware<T: Clone>(pub(crate) FederationConfig<T>);
pub struct FederationMiddleware<T: Clone>(pub(crate) FederationConfig<T>);
impl<T: Clone> ApubMiddleware<T> {
impl<T: Clone> FederationMiddleware<T> {
/// Construct a new middleware instance
pub fn new(config: FederationConfig<T>) -> Self {
ApubMiddleware(config)
FederationMiddleware(config)
}
}

View file

@ -1,4 +1,4 @@
use crate::{config::Data, error::Error, fetch::fetch_object_http, traits::ApubCollection};
use crate::{config::Data, error::Error, fetch::fetch_object_http, traits::Collection};
use serde::{Deserialize, Serialize};
use std::{
fmt::{Debug, Display, Formatter},
@ -11,13 +11,13 @@ use url::Url;
#[serde(transparent)]
pub struct CollectionId<Kind>(Box<Url>, PhantomData<Kind>)
where
Kind: ApubCollection,
for<'de2> <Kind as ApubCollection>::ApubType: Deserialize<'de2>;
Kind: Collection,
for<'de2> <Kind as Collection>::Kind: Deserialize<'de2>;
impl<Kind> CollectionId<Kind>
where
Kind: ApubCollection,
for<'de2> <Kind as ApubCollection>::ApubType: Deserialize<'de2>,
Kind: Collection,
for<'de2> <Kind as Collection>::Kind: Deserialize<'de2>,
{
/// Construct a new CollectionId instance
pub fn parse<T>(url: T) -> Result<Self, url::ParseError>
@ -34,23 +34,23 @@ where
/// any caching.
pub async fn dereference(
&self,
owner: &<Kind as ApubCollection>::Owner,
data: &Data<<Kind as ApubCollection>::DataType>,
) -> Result<Kind, <Kind as ApubCollection>::Error>
owner: &<Kind as Collection>::Owner,
data: &Data<<Kind as Collection>::DataType>,
) -> Result<Kind, <Kind as Collection>::Error>
where
<Kind as ApubCollection>::Error: From<Error>,
<Kind as Collection>::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
let json = fetch_object_http(&self.0, data).await?;
Kind::verify(&json, &self.0, data).await?;
Kind::from_json(json, 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>,
Kind: Collection,
for<'de2> <Kind as Collection>::Kind: serde::Deserialize<'de2>,
{
fn clone(&self) -> Self {
CollectionId(self.0.clone(), self.1)
@ -59,8 +59,8 @@ where
impl<Kind> Display for CollectionId<Kind>
where
Kind: ApubCollection,
for<'de2> <Kind as ApubCollection>::ApubType: serde::Deserialize<'de2>,
Kind: Collection,
for<'de2> <Kind as Collection>::Kind: serde::Deserialize<'de2>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.as_str())
@ -69,8 +69,8 @@ where
impl<Kind> Debug for CollectionId<Kind>
where
Kind: ApubCollection,
for<'de2> <Kind as ApubCollection>::ApubType: serde::Deserialize<'de2>,
Kind: Collection,
for<'de2> <Kind as Collection>::Kind: serde::Deserialize<'de2>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.as_str())
@ -78,8 +78,8 @@ where
}
impl<Kind> From<CollectionId<Kind>> for Url
where
Kind: ApubCollection,
for<'de2> <Kind as ApubCollection>::ApubType: serde::Deserialize<'de2>,
Kind: Collection,
for<'de2> <Kind as Collection>::Kind: serde::Deserialize<'de2>,
{
fn from(id: CollectionId<Kind>) -> Self {
*id.0
@ -88,8 +88,8 @@ where
impl<Kind> From<Url> for CollectionId<Kind>
where
Kind: ApubCollection + Send + 'static,
for<'de2> <Kind as ApubCollection>::ApubType: serde::Deserialize<'de2>,
Kind: Collection + Send + 'static,
for<'de2> <Kind as Collection>::Kind: serde::Deserialize<'de2>,
{
fn from(url: Url) -> Self {
CollectionId(Box::new(url), PhantomData::<Kind>)

View file

@ -2,7 +2,7 @@
//!
#![doc = include_str!("../../docs/07_fetching_data.md")]
use crate::{config::Data, error::Error, reqwest_shim::ResponseExt, APUB_JSON_CONTENT_TYPE};
use crate::{config::Data, error::Error, reqwest_shim::ResponseExt, FEDERATION_CONTENT_TYPE};
use http::StatusCode;
use serde::de::DeserializeOwned;
use std::sync::atomic::Ordering;
@ -44,7 +44,7 @@ pub async fn fetch_object_http<T: Clone, Kind: DeserializeOwned>(
let res = config
.client
.get(url.as_str())
.header("Accept", APUB_JSON_CONTENT_TYPE)
.header("Accept", FEDERATION_CONTENT_TYPE)
.timeout(config.request_timeout)
.send()
.await

View file

@ -1,4 +1,4 @@
use crate::{config::Data, error::Error, fetch::fetch_object_http, traits::ApubObject};
use crate::{config::Data, error::Error, fetch::fetch_object_http, traits::Object};
use anyhow::anyhow;
use chrono::{Duration as ChronoDuration, NaiveDateTime, Utc};
use serde::{Deserialize, Serialize};
@ -11,8 +11,8 @@ use url::Url;
impl<T> FromStr for ObjectId<T>
where
T: ApubObject + Send + 'static,
for<'de2> <T as ApubObject>::ApubType: Deserialize<'de2>,
T: Object + Send + 'static,
for<'de2> <T as Object>::Kind: Deserialize<'de2>,
{
type Err = url::ParseError;
@ -58,13 +58,13 @@ where
#[serde(transparent)]
pub struct ObjectId<Kind>(Box<Url>, PhantomData<Kind>)
where
Kind: ApubObject,
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>;
Kind: Object,
for<'de2> <Kind as Object>::Kind: serde::Deserialize<'de2>;
impl<Kind> ObjectId<Kind>
where
Kind: ApubObject + Send + 'static,
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
Kind: Object + Send + 'static,
for<'de2> <Kind as Object>::Kind: serde::Deserialize<'de2>,
{
/// Construct a new objectid instance
pub fn parse<T>(url: T) -> Result<Self, url::ParseError>
@ -88,10 +88,10 @@ where
/// Fetches an activitypub object, either from local database (if possible), or over http.
pub async fn dereference(
&self,
data: &Data<<Kind as ApubObject>::DataType>,
) -> Result<Kind, <Kind as ApubObject>::Error>
data: &Data<<Kind as Object>::DataType>,
) -> Result<Kind, <Kind as Object>::Error>
where
<Kind as ApubObject>::Error: From<Error> + From<anyhow::Error>,
<Kind as Object>::Error: From<Error> + From<anyhow::Error>,
{
let db_object = self.dereference_from_db(data).await?;
@ -123,10 +123,10 @@ where
/// the object is not found in the database.
pub async fn dereference_local(
&self,
data: &Data<<Kind as ApubObject>::DataType>,
) -> Result<Kind, <Kind as ApubObject>::Error>
data: &Data<<Kind as Object>::DataType>,
) -> Result<Kind, <Kind as Object>::Error>
where
<Kind as ApubObject>::Error: From<Error>,
<Kind as Object>::Error: From<Error>,
{
let object = self.dereference_from_db(data).await?;
object.ok_or_else(|| Error::NotFound.into())
@ -135,19 +135,19 @@ where
/// returning none means the object was not found in local db
async fn dereference_from_db(
&self,
data: &Data<<Kind as ApubObject>::DataType>,
) -> Result<Option<Kind>, <Kind as ApubObject>::Error> {
data: &Data<<Kind as Object>::DataType>,
) -> Result<Option<Kind>, <Kind as Object>::Error> {
let id = self.0.clone();
ApubObject::read_from_apub_id(*id, data).await
Object::read_from_id(*id, data).await
}
async fn dereference_from_http(
&self,
data: &Data<<Kind as ApubObject>::DataType>,
data: &Data<<Kind as Object>::DataType>,
db_object: Option<Kind>,
) -> Result<Kind, <Kind as ApubObject>::Error>
) -> Result<Kind, <Kind as Object>::Error>
where
<Kind as ApubObject>::Error: From<Error> + From<anyhow::Error>,
<Kind as Object>::Error: From<Error> + From<anyhow::Error>,
{
let res = fetch_object_http(&self.0, data).await;
@ -161,15 +161,15 @@ where
let res2 = res?;
Kind::verify(&res2, self.inner(), data).await?;
Kind::from_apub(res2, data).await
Kind::from_json(res2, data).await
}
}
/// Need to implement clone manually, to avoid requiring Kind to be Clone
impl<Kind> Clone for ObjectId<Kind>
where
Kind: ApubObject,
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
Kind: Object,
for<'de2> <Kind as Object>::Kind: serde::Deserialize<'de2>,
{
fn clone(&self) -> Self {
ObjectId(self.0.clone(), self.1)
@ -195,8 +195,8 @@ fn should_refetch_object(last_refreshed: NaiveDateTime) -> bool {
impl<Kind> Display for ObjectId<Kind>
where
Kind: ApubObject,
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
Kind: Object,
for<'de2> <Kind as Object>::Kind: serde::Deserialize<'de2>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.as_str())
@ -205,8 +205,8 @@ where
impl<Kind> Debug for ObjectId<Kind>
where
Kind: ApubObject,
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
Kind: Object,
for<'de2> <Kind as Object>::Kind: serde::Deserialize<'de2>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.as_str())
@ -215,8 +215,8 @@ where
impl<Kind> From<ObjectId<Kind>> for Url
where
Kind: ApubObject,
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
Kind: Object,
for<'de2> <Kind as Object>::Kind: serde::Deserialize<'de2>,
{
fn from(id: ObjectId<Kind>) -> Self {
*id.0
@ -225,8 +225,8 @@ where
impl<Kind> From<Url> for ObjectId<Kind>
where
Kind: ApubObject + Send + 'static,
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
Kind: Object + Send + 'static,
for<'de2> <Kind as Object>::Kind: serde::Deserialize<'de2>,
{
fn from(url: Url) -> Self {
ObjectId(Box::new(url), PhantomData::<Kind>)
@ -235,8 +235,8 @@ where
impl<Kind> PartialEq for ObjectId<Kind>
where
Kind: ApubObject,
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
Kind: Object,
for<'de2> <Kind as Object>::Kind: serde::Deserialize<'de2>,
{
fn eq(&self, other: &Self) -> bool {
self.0.eq(&other.0) && self.1 == other.1

View file

@ -2,8 +2,8 @@ use crate::{
config::Data,
error::{Error, Error::WebfingerResolveFailed},
fetch::{fetch_object_http, object_id::ObjectId},
traits::{Actor, ApubObject},
APUB_JSON_CONTENT_TYPE,
traits::{Actor, Object},
FEDERATION_CONTENT_TYPE,
};
use anyhow::anyhow;
use itertools::Itertools;
@ -20,11 +20,11 @@ use url::Url;
pub async fn webfinger_resolve_actor<T: Clone, Kind>(
identifier: &str,
data: &Data<T>,
) -> Result<Kind, <Kind as ApubObject>::Error>
) -> Result<Kind, <Kind as Object>::Error>
where
Kind: ApubObject + Actor + Send + 'static + ApubObject<DataType = T>,
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
<Kind as ApubObject>::Error:
Kind: Object + Actor + Send + 'static + Object<DataType = T>,
for<'de2> <Kind as Object>::Kind: serde::Deserialize<'de2>,
<Kind as Object>::Error:
From<crate::error::Error> + From<anyhow::Error> + From<url::ParseError> + Send + Sync,
{
let (_, domain) = identifier
@ -107,7 +107,7 @@ pub fn build_webfinger_response(subject: String, url: Url) -> Webfinger {
},
WebfingerLink {
rel: Some("self".to_string()),
kind: Some(APUB_JSON_CONTENT_TYPE.to_string()),
kind: Some(FEDERATION_CONTENT_TYPE.to_string()),
href: Some(url),
properties: Default::default(),
},

View file

@ -26,4 +26,4 @@ pub mod traits;
pub use activitystreams_kinds as kinds;
/// Mime type for Activitypub data, used for `Accept` and `Content-Type` HTTP headers
pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json";
pub static FEDERATION_CONTENT_TYPE: &str = "application/activity+json";

View file

@ -18,7 +18,7 @@ use url::Url;
/// # use activitypub_federation::config::Data;
/// # use activitypub_federation::fetch::object_id::ObjectId;
/// # use activitypub_federation::protocol::verification::verify_domains_match;
/// # use activitypub_federation::traits::{Actor, ApubObject};
/// # use activitypub_federation::traits::{Actor, Object};
/// # use activitypub_federation::traits::tests::{DbConnection, DbUser};
/// #
/// /// How the post is read/written in the local database
@ -43,18 +43,18 @@ use url::Url;
/// }
///
/// #[async_trait::async_trait]
/// impl ApubObject for DbPost {
/// impl Object for DbPost {
/// type DataType = DbConnection;
/// type ApubType = Note;
/// type Kind = Note;
/// type Error = anyhow::Error;
///
/// async fn read_from_apub_id(object_id: Url, data: &Data<Self::DataType>) -> Result<Option<Self>, Self::Error> {
/// async fn read_from_id(object_id: Url, data: &Data<Self::DataType>) -> Result<Option<Self>, Self::Error> {
/// // Attempt to read object from local database. Return Ok(None) if not found.
/// let post: Option<DbPost> = data.read_post_from_apub_id(object_id).await?;
/// let post: Option<DbPost> = data.read_post_from_json_id(object_id).await?;
/// Ok(post)
/// }
///
/// async fn into_apub(self, data: &Data<Self::DataType>) -> Result<Self::ApubType, Self::Error> {
/// async fn into_json(self, data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
/// // Called when a local object gets sent out over Activitypub. Simply convert it to the
/// // protocol struct
/// Ok(Note {
@ -66,20 +66,20 @@ use url::Url;
/// })
/// }
///
/// async fn verify(apub: &Self::ApubType, expected_domain: &Url, data: &Data<Self::DataType>,) -> Result<(), Self::Error> {
/// verify_domains_match(apub.id.inner(), expected_domain)?;
/// async fn verify(json: &Self::Kind, expected_domain: &Url, data: &Data<Self::DataType>,) -> Result<(), Self::Error> {
/// verify_domains_match(json.id.inner(), expected_domain)?;
/// // additional application specific checks
/// Ok(())
/// }
///
/// async fn from_apub(apub: Self::ApubType, data: &Data<Self::DataType>) -> Result<Self, Self::Error> {
/// async fn from_json(json: Self::Kind, data: &Data<Self::DataType>) -> Result<Self, Self::Error> {
/// // Called when a remote object gets received over Activitypub. Validate and insert it
/// // into the database.
///
/// let post = DbPost {
/// text: apub.content,
/// ap_id: apub.id,
/// creator: apub.attributed_to,
/// text: json.content,
/// ap_id: json.id,
/// creator: json.attributed_to,
/// local: false,
/// };
///
@ -93,12 +93,12 @@ use url::Url;
///
/// }
#[async_trait]
pub trait ApubObject: Sized {
pub trait Object: Sized {
/// 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;
type Kind;
/// Error type returned by handler methods
type Error;
@ -118,7 +118,7 @@ pub trait ApubObject: Sized {
/// Try to read the object with given `id` from local database.
///
/// Should return `Ok(None)` if not found.
async fn read_from_apub_id(
async fn read_from_id(
object_id: Url,
data: &Data<Self::DataType>,
) -> Result<Option<Self>, Self::Error>;
@ -134,7 +134,7 @@ pub trait ApubObject: Sized {
///
/// Called when a local object gets fetched by another instance over HTTP, or when an object
/// gets sent in an activity.
async fn into_apub(self, data: &Data<Self::DataType>) -> Result<Self::ApubType, Self::Error>;
async fn into_json(self, data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error>;
/// Verifies that the received object is valid.
///
@ -144,7 +144,7 @@ pub trait ApubObject: Sized {
/// It is necessary to use a separate method for this, because it might be used for activities
/// like `Delete/Note`, which shouldn't perform any database write for the inner `Note`.
async fn verify(
apub: &Self::ApubType,
json: &Self::Kind,
expected_domain: &Url,
data: &Data<Self::DataType>,
) -> Result<(), Self::Error>;
@ -154,10 +154,7 @@ pub trait ApubObject: Sized {
/// Called when an object is received from HTTP fetch or as part of an activity. This method
/// should 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,
data: &Data<Self::DataType>,
) -> Result<Self, Self::Error>;
async fn from_json(json: Self::Kind, data: &Data<Self::DataType>) -> Result<Self, Self::Error>;
}
/// Handler for receiving incoming activities.
@ -227,12 +224,12 @@ pub trait ActivityHandler {
/// Called when an activity is received.
///
/// 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_json` handler.
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error>;
}
/// Trait to allow retrieving common Actor data.
pub trait Actor: ApubObject + Send + 'static {
pub trait Actor: Object + Send + 'static {
/// `id` field of the actor
fn id(&self) -> Url;
@ -295,14 +292,14 @@ where
/// Trait for federating collections
#[async_trait]
pub trait ApubCollection: Sized {
pub trait Collection: 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>;
type Kind: for<'de2> Deserialize<'de2>;
/// Error type returned by handler methods
type Error;
@ -310,14 +307,14 @@ pub trait ApubCollection: Sized {
async fn read_local(
owner: &Self::Owner,
data: &Data<Self::DataType>,
) -> Result<Self::ApubType, Self::Error>;
) -> Result<Self::Kind, 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,
json: &Self::Kind,
expected_domain: &Url,
data: &Data<Self::DataType>,
) -> Result<(), Self::Error>;
@ -327,8 +324,8 @@ pub trait ApubCollection: Sized {
/// 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,
async fn from_json(
json: Self::Kind,
owner: &Self::Owner,
data: &Data<Self::DataType>,
) -> Result<Self, Self::Error>;
@ -355,7 +352,7 @@ pub mod tests {
pub struct DbConnection;
impl DbConnection {
pub async fn read_post_from_apub_id<T>(&self, _: Url) -> Result<Option<T>, Error> {
pub async fn read_post_from_json_id<T>(&self, _: Url) -> Result<Option<T>, Error> {
Ok(None)
}
pub async fn read_local_user(&self, _: String) -> Result<DbUser, Error> {
@ -382,7 +379,7 @@ pub mod tests {
#[derive(Debug, Clone)]
pub struct DbUser {
pub name: String,
pub apub_id: Url,
pub federation_id: Url,
pub inbox: Url,
pub public_key: String,
#[allow(dead_code)]
@ -395,7 +392,7 @@ pub mod tests {
pub static DB_USER: Lazy<DbUser> = Lazy::new(|| DbUser {
name: String::new(),
apub_id: "https://localhost/123".parse().unwrap(),
federation_id: "https://localhost/123".parse().unwrap(),
inbox: "https://localhost/123/inbox".parse().unwrap(),
public_key: DB_USER_KEYPAIR.public_key.clone(),
private_key: Some(DB_USER_KEYPAIR.private_key.clone()),
@ -404,49 +401,46 @@ pub mod tests {
});
#[async_trait]
impl ApubObject for DbUser {
impl Object for DbUser {
type DataType = DbConnection;
type ApubType = Person;
type Kind = Person;
type Error = Error;
async fn read_from_apub_id(
async fn read_from_id(
_object_id: Url,
_data: &Data<Self::DataType>,
) -> Result<Option<Self>, Self::Error> {
Ok(Some(DB_USER.clone()))
}
async fn into_apub(
self,
_data: &Data<Self::DataType>,
) -> Result<Self::ApubType, Self::Error> {
async fn into_json(self, _data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
Ok(Person {
preferred_username: self.name.clone(),
kind: Default::default(),
id: self.apub_id.clone().into(),
id: self.federation_id.clone().into(),
inbox: self.inbox.clone(),
public_key: self.public_key(),
})
}
async fn verify(
apub: &Self::ApubType,
json: &Self::Kind,
expected_domain: &Url,
_data: &Data<Self::DataType>,
) -> Result<(), Self::Error> {
verify_domains_match(apub.id.inner(), expected_domain)?;
verify_domains_match(json.id.inner(), expected_domain)?;
Ok(())
}
async fn from_apub(
apub: Self::ApubType,
async fn from_json(
json: Self::Kind,
_data: &Data<Self::DataType>,
) -> Result<Self, Self::Error> {
Ok(DbUser {
name: apub.preferred_username,
apub_id: apub.id.into(),
inbox: apub.inbox,
public_key: apub.public_key.public_key_pem,
name: json.preferred_username,
federation_id: json.id.into(),
inbox: json.inbox,
public_key: json.public_key.public_key_pem,
private_key: None,
followers: vec![],
local: false,
@ -456,7 +450,7 @@ pub mod tests {
impl Actor for DbUser {
fn id(&self) -> Url {
self.apub_id.clone()
self.federation_id.clone()
}
fn public_key_pem(&self) -> &str {
@ -511,34 +505,31 @@ pub mod tests {
pub struct DbPost {}
#[async_trait]
impl ApubObject for DbPost {
impl Object for DbPost {
type DataType = DbConnection;
type ApubType = Note;
type Kind = Note;
type Error = Error;
async fn read_from_apub_id(
async fn read_from_id(
_: Url,
_: &Data<Self::DataType>,
) -> Result<Option<Self>, Self::Error> {
todo!()
}
async fn into_apub(self, _: &Data<Self::DataType>) -> Result<Self::ApubType, Self::Error> {
async fn into_json(self, _: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
todo!()
}
async fn verify(
_: &Self::ApubType,
_: &Self::Kind,
_: &Url,
_: &Data<Self::DataType>,
) -> Result<(), Self::Error> {
todo!()
}
async fn from_apub(
_: Self::ApubType,
_: &Data<Self::DataType>,
) -> Result<Self, Self::Error> {
async fn from_json(_: Self::Kind, _: &Data<Self::DataType>) -> Result<Self, Self::Error> {
todo!()
}
}