From 9b313958535d298bd21e929645f3895d0ec70c24 Mon Sep 17 00:00:00 2001 From: silverpill Date: Fri, 21 Oct 2022 21:32:01 +0000 Subject: [PATCH] Handle Move(Person) activity https://codeberg.org/silverpill/mitra/issues/10 --- FEDERATION.md | 1 + src/activitypub/deliverer.rs | 10 ++- src/activitypub/handlers/mod.rs | 1 + src/activitypub/handlers/move_person.rs | 103 ++++++++++++++++++++++++ src/activitypub/receiver.rs | 5 ++ src/activitypub/vocabulary.rs | 1 + 6 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 src/activitypub/handlers/move_person.rs diff --git a/FEDERATION.md b/FEDERATION.md index 095a4cd..2ac8929 100644 --- a/FEDERATION.md +++ b/FEDERATION.md @@ -16,6 +16,7 @@ The following activities are supported: - Update(Note) - Follow(Person) - Update(Person) +- Move(Person) - Delete(Person) - Add(Person) - Remove(Person) diff --git a/src/activitypub/deliverer.rs b/src/activitypub/deliverer.rs index af7d7ee..68a429b 100644 --- a/src/activitypub/deliverer.rs +++ b/src/activitypub/deliverer.rs @@ -187,11 +187,15 @@ impl OutgoingActivity { ).await } + pub async fn deliver_or_log(self) -> () { + self.deliver().await.unwrap_or_else(|err| { + log::error!("{}", err); + }); + } + pub fn spawn_deliver(self) -> () { tokio::spawn(async move { - self.deliver().await.unwrap_or_else(|err| { - log::error!("{}", err); - }); + self.deliver_or_log().await; }); } } diff --git a/src/activitypub/handlers/mod.rs b/src/activitypub/handlers/mod.rs index 13139a7..1e66315 100644 --- a/src/activitypub/handlers/mod.rs +++ b/src/activitypub/handlers/mod.rs @@ -10,6 +10,7 @@ pub mod create_note; pub mod delete; pub mod follow; pub mod like; +pub mod move_person; pub mod reject_follow; pub mod remove; pub mod undo; diff --git a/src/activitypub/handlers/move_person.rs b/src/activitypub/handlers/move_person.rs new file mode 100644 index 0000000..6876885 --- /dev/null +++ b/src/activitypub/handlers/move_person.rs @@ -0,0 +1,103 @@ +use tokio_postgres::GenericClient; + +use crate::activitypub::{ + activity::Activity, + builders::{ + follow::prepare_follow, + undo_follow::prepare_undo_follow, + }, + fetcher::helpers::get_or_import_profile_by_actor_id, + receiver::find_object_id, + vocabulary::PERSON, +}; +use crate::config::Config; +use crate::errors::{DatabaseError, ValidationError}; +use crate::models::{ + relationships::queries::{ + create_follow_request, + get_followers, + unfollow, + }, + users::queries::get_user_by_id, +}; +use super::HandlerResult; + +pub async fn handle_move_person( + config: &Config, + db_client: &mut impl GenericClient, + activity: Activity, +) -> HandlerResult { + let object_id = find_object_id(&activity.object)?; + if object_id != activity.actor { + return Err(ValidationError("actor ID mismatch").into()); + }; + let target_value = activity.target + .ok_or(ValidationError("target is missing"))?; + let target_id = find_object_id(&target_value)?; + + let instance = config.instance(); + let media_dir = config.media_dir(); + let old_profile = get_or_import_profile_by_actor_id( + db_client, + &instance, + &media_dir, + &activity.actor, + ).await?; + let old_actor = old_profile.actor_json.unwrap(); + let new_profile = get_or_import_profile_by_actor_id( + db_client, + &instance, + &media_dir, + &target_id, + ).await?; + let new_actor = new_profile.actor_json.unwrap(); + let maybe_also_known_as = new_actor.also_known_as.as_ref() + .and_then(|aliases| aliases.first()); + if maybe_also_known_as != Some(&old_actor.id) { + return Err(ValidationError("target ID is not an alias").into()); + }; + + let followers = get_followers(db_client, &old_profile.id).await?; + let mut activities = vec![]; + for follower in followers { + let follower = get_user_by_id(db_client, &follower.id).await?; + // Unfollow old profile + let maybe_follow_request_id = unfollow( + db_client, + &follower.id, + &old_profile.id, + ).await?; + // The target is remote profile, so follow request must exist + let follow_request_id = maybe_follow_request_id.unwrap(); + activities.push(prepare_undo_follow( + &instance, + &follower, + &old_actor, + &follow_request_id, + )); + // Follow new profile + match create_follow_request( + db_client, + &follower.id, + &new_profile.id, + ).await { + Ok(follow_request) => { + activities.push(prepare_follow( + &instance, + &follower, + &new_actor, + &follow_request.id, + )); + }, + Err(DatabaseError::AlreadyExists(_)) => (), // already following + Err(other_error) => return Err(other_error.into()), + }; + }; + tokio::spawn(async move { + for activity in activities { + activity.deliver_or_log().await; + }; + }); + + Ok(Some(PERSON)) +} diff --git a/src/activitypub/receiver.rs b/src/activitypub/receiver.rs index c820188..65243c9 100644 --- a/src/activitypub/receiver.rs +++ b/src/activitypub/receiver.rs @@ -15,6 +15,7 @@ use super::handlers::{ delete::handle_delete, follow::handle_follow, like::handle_like, + move_person::handle_move_person, reject_follow::handle_reject_follow, remove::handle_remove, undo::handle_undo, @@ -197,6 +198,10 @@ pub async fn receive_activity( require_actor_signature(&activity.actor, &signer_id)?; handle_update_person(config, db_client, activity).await? }, + (MOVE, _) => { + require_actor_signature(&activity.actor, &signer_id)?; + handle_move_person(config, db_client, activity).await? + }, (ADD, _) => { require_actor_signature(&activity.actor, &signer_id)?; handle_add(config, db_client, activity).await? diff --git a/src/activitypub/vocabulary.rs b/src/activitypub/vocabulary.rs index 668ded1..4e85450 100644 --- a/src/activitypub/vocabulary.rs +++ b/src/activitypub/vocabulary.rs @@ -9,6 +9,7 @@ pub const DELETE: &str = "Delete"; pub const EMOJI_REACT: &str = "EmojiReact"; pub const FOLLOW: &str = "Follow"; pub const LIKE: &str = "Like"; +pub const MOVE: &str = "Move"; pub const QUESTION: &str = "Question"; pub const REJECT: &str = "Reject"; pub const REMOVE: &str = "Remove";