//! Traits which need to be implemented for federated data types use crate::{config::Data, protocol::public_key::PublicKey}; use async_trait::async_trait; use chrono::{DateTime, Utc}; use serde::Deserialize; use std::{fmt::Debug, ops::Deref}; use url::Url; /// `Either` implementations for traits pub mod either; pub mod tests; /// Helper for converting between database structs and federated protocol structs. /// /// ``` /// # use activitystreams_kinds::{object::NoteType, public}; /// # use chrono::{Local, DateTime, Utc}; /// # use serde::{Deserialize, Serialize}; /// # use url::Url; /// # use activitypub_federation::protocol::{public_key::PublicKey, helpers::deserialize_one_or_many}; /// # 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, Object}; /// # use activitypub_federation::traits::tests::{DbConnection, DbUser}; /// # /// /// How the post is read/written in the local database /// #[derive(Debug)] /// pub struct DbPost { /// pub text: String, /// pub ap_id: ObjectId, /// pub creator: ObjectId, /// 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, /// pub(crate) attributed_to: ObjectId, /// #[serde(deserialize_with = "deserialize_one_or_many")] /// pub(crate) to: Vec, /// content: String, /// } /// /// #[async_trait::async_trait] /// impl Object for DbPost { /// type DataType = DbConnection; /// type Kind = Note; /// type Error = anyhow::Error; /// /// fn id(&self) -> &Url { self.ap_id.inner() } /// /// async fn read_from_id(object_id: Url, data: &Data) -> Result, Self::Error> { /// // Attempt to read object from local database. Return Ok(None) if not found. /// let post: Option = data.read_post_from_json_id(object_id).await?; /// Ok(post) /// } /// /// async fn into_json(self, data: &Data) -> Result { /// // Called when a local object gets sent out over Activitypub. Simply convert it to the /// // protocol struct /// Ok(Note { /// kind: Default::default(), /// id: self.ap_id.clone().into(), /// attributed_to: self.creator, /// to: vec![public()], /// content: self.text, /// }) /// } /// /// async fn verify(json: &Self::Kind, expected_domain: &Url, data: &Data,) -> Result<(), Self::Error> { /// verify_domains_match(json.id.inner(), expected_domain)?; /// // additional application specific checks /// Ok(()) /// } /// /// async fn from_json(json: Self::Kind, data: &Data) -> Result { /// // Called when a remote object gets received over Activitypub. Validate and insert it /// // into the database. /// /// let post = DbPost { /// text: json.content, /// ap_id: json.id, /// creator: json.attributed_to, /// local: false, /// }; /// /// // Here we need to persist the object in the local database. Note that Activitypub /// // doesnt distinguish between creating and updating an object. Thats why we need to /// // use upsert functionality. /// data.upsert(&post).await?; /// /// Ok(post) /// } /// /// } #[async_trait] pub trait Object: Sized + Debug { /// 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 Kind; /// Error type returned by handler methods type Error; /// `id` field of the object fn id(&self) -> &Url; /// Returns the last time this object was updated. /// /// If this returns `Some` and the value is too long ago, the object is refetched from the /// original instance. This should always be implemented for actors, because there is no active /// update mechanism prescribed. It is possible to send `Update/Person` activities for profile /// changes, but not all implementations do this, so `last_refreshed_at` is still necessary. /// /// The object is refetched if `last_refreshed_at` value is more than 24 hours ago. In debug /// mode this is reduced to 20 seconds. fn last_refreshed_at(&self) -> Option> { None } /// Try to read the object with given `id` from local database. /// /// Should return `Ok(None)` if not found. async fn read_from_id( object_id: Url, data: &Data, ) -> Result, Self::Error>; /// Mark remote object as deleted in local database. /// /// Called when a `Delete` activity is received, or if fetch returns a `Tombstone` object. async fn delete(self, _data: &Data) -> Result<(), Self::Error> { Ok(()) } /// Returns true if the object was deleted fn is_deleted(&self) -> bool { false } /// Convert database type to Activitypub type. /// /// Called when a local object gets fetched by another instance over HTTP, or when an object /// gets sent in an activity. async fn into_json(self, data: &Data) -> Result; /// 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. /// /// 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( json: &Self::Kind, expected_domain: &Url, data: &Data, ) -> 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 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_json(json: Self::Kind, data: &Data) -> Result; /// Generates HTTP response to serve the object for fetching from other instances. /// /// - If the object has a remote domain, sends a redirect to the original instance. /// - If [Object.is_deleted] returns true, returns a [crate::protocol::tombstone::Tombstone] instead. /// - Otherwise serves the object JSON using [Object.into_json] and pretty-print /// /// `federation_context` is the value of `@context`. #[cfg(feature = "actix-web")] async fn http_response( self, federation_context: &serde_json::Value, data: &Data, ) -> Result where Self::Error: From, Self::Kind: serde::Serialize + Send, { use crate::actix_web::response::{ create_http_response, create_tombstone_response, redirect_remote_object, }; let id = self.id(); let res = if data.config.is_local_url(id) { redirect_remote_object(id) } else if !self.is_deleted() { let json = self.into_json(data).await?; create_http_response(json, federation_context)? } else { create_tombstone_response(id.clone(), federation_context)? }; Ok(res) } } /// Handler for receiving incoming activities. /// /// ``` /// # use activitystreams_kinds::activity::FollowType; /// # use url::Url; /// # use activitypub_federation::fetch::object_id::ObjectId; /// # use activitypub_federation::config::Data; /// # use activitypub_federation::traits::Activity; /// # use activitypub_federation::traits::tests::{DbConnection, DbUser}; /// #[derive(serde::Deserialize)] /// struct Follow { /// actor: ObjectId, /// object: ObjectId, /// #[serde(rename = "type")] /// kind: FollowType, /// id: Url, /// } /// /// #[async_trait::async_trait] /// impl Activity for Follow { /// type DataType = DbConnection; /// type Error = anyhow::Error; /// /// fn id(&self) -> &Url { /// &self.id /// } /// /// fn actor(&self) -> &Url { /// self.actor.inner() /// } /// /// async fn verify(&self, data: &Data) -> Result<(), Self::Error> { /// Ok(()) /// } /// /// async fn receive(self, data: &Data) -> Result<(), Self::Error> { /// let local_user = self.object.dereference(data).await?; /// let follower = self.actor.dereference(data).await?; /// data.add_follower(local_user, follower).await?; /// Ok(()) /// } /// } /// ``` #[async_trait] #[enum_delegate::register] pub trait Activity { /// App data type passed to handlers. Must be identical to /// [crate::config::FederationConfigBuilder::app_data] type. type DataType: Clone + Send + Sync; /// Error type returned by handler methods type Error; /// `id` field of the activity fn id(&self) -> &Url; /// `actor` field of activity fn actor(&self) -> &Url; /// Verifies that the received activity is valid. /// /// 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`. async fn verify(&self, data: &Data) -> Result<(), Self::Error>; /// 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_json` handler. async fn receive(self, data: &Data) -> Result<(), Self::Error>; } /// Trait to allow retrieving common Actor data. pub trait Actor: Object + Send + 'static { /// The actor's public key for verifying signatures of incoming activities. /// /// Use [generate_actor_keypair](crate::http_signatures::generate_actor_keypair) to create the /// actor keypair. 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; /// The inbox where activities for this user should be sent to fn inbox(&self) -> Url; /// Generates a public key struct for use in the actor json representation fn public_key(&self) -> PublicKey { PublicKey::new(self.id().clone(), self.public_key_pem().to_string()) } /// The actor's shared inbox, if any fn shared_inbox(&self) -> Option { None } /// Returns shared inbox if it exists, normal inbox otherwise. fn shared_inbox_or_inbox(&self) -> Url { self.shared_inbox().unwrap_or_else(|| self.inbox()) } } /// Allow for boxing of enum variants #[async_trait] impl Activity for Box where T: Activity + Send + Sync, { type DataType = T::DataType; type Error = T::Error; fn id(&self) -> &Url { self.deref().id() } fn actor(&self) -> &Url { self.deref().actor() } async fn verify(&self, data: &Data) -> Result<(), Self::Error> { self.deref().verify(data).await } async fn receive(self, data: &Data) -> Result<(), Self::Error> { (*self).receive(data).await } } /// Trait for federating collections #[async_trait] 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 Kind: 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, ) -> Result; /// 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( json: &Self::Kind, expected_domain: &Url, data: &Data, ) -> 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_json( json: Self::Kind, owner: &Self::Owner, data: &Data, ) -> Result; }