source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
+[[package]]
+name = "dissimilar"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc4b29f4b9bb94bf267d57269fd0706d343a160937108e9619fe380645428abb"
+
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189"
+[[package]]
+name = "glob"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+
[[package]]
name = "h2"
version = "0.3.3"
"uuid",
]
+[[package]]
+name = "lemmy_apub_lib"
+version = "0.1.0"
+dependencies = [
+ "activitystreams",
+ "activitystreams-ext",
+ "async-trait",
+ "lemmy_apub_lib_derive",
+ "lemmy_utils",
+ "lemmy_websocket",
+ "serde",
+ "url",
+]
+
+[[package]]
+name = "lemmy_apub_lib_derive"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2 1.0.27",
+ "quote 1.0.9",
+ "syn 1.0.73",
+ "trybuild",
+]
+
[[package]]
name = "lemmy_apub_receive"
version = "0.1.0"
"itertools",
"lemmy_api_common",
"lemmy_apub",
+ "lemmy_apub_lib",
"lemmy_db_queries",
"lemmy_db_schema",
"lemmy_db_views",
"tokio",
]
+[[package]]
+name = "toml"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "tower-service"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
+[[package]]
+name = "trybuild"
+version = "1.0.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1768998d9a3b179411618e377dbb134c58a88cda284b0aa71c42c40660127d46"
+dependencies = [
+ "dissimilar",
+ "glob",
+ "lazy_static",
+ "serde",
+ "serde_json",
+ "termcolor",
+ "toml",
+]
+
[[package]]
name = "twoway"
version = "0.2.2"
"crates/api",
"crates/api_crud",
"crates/api_common",
+ "crates/apub_lib",
+ "crates/apub_lib_derive",
"crates/apub",
"crates/apub_receive",
"crates/utils",
export LEMMY_TEST_SEND_SYNC=1
export RUST_BACKTRACE=1
+export RUST_LOG="warn,lemmy_server=debug,lemmy_api=debug,lemmy_api_common=debug,lemmy_api_crud=debug,lemmy_apub=debug,lemmy_apub_receive=debug,lemmy_db_queries=debug,lemmy_db_schema=debug,lemmy_db_views=debug,lemmy_db_views_actor=debug,lemmy_db_views_moderator=debug,lemmy_routes=debug,lemmy_utils=debug,lemmy_websocket=debug"
for INSTANCE in lemmy_alpha lemmy_beta lemmy_gamma lemmy_delta lemmy_epsilon; do
psql "${LEMMY_DATABASE_URL}/lemmy" -c "DROP DATABASE IF EXISTS $INSTANCE"
use lemmy_websocket::LemmyContext;
use url::Url;
-#[async_trait::async_trait(?Send)]
impl ActorType for Community {
fn is_local(&self) -> bool {
self.local
fn actor_id(&self) -> Url {
self.actor_id.to_owned().into_inner()
}
+ fn name(&self) -> String {
+ self.name.clone()
+ }
fn public_key(&self) -> Option<String> {
self.public_key.to_owned()
}
use lemmy_websocket::LemmyContext;
use url::Url;
-#[async_trait::async_trait(?Send)]
impl ActorType for Person {
fn is_local(&self) -> bool {
self.local
fn actor_id(&self) -> Url {
self.actor_id.to_owned().into_inner()
}
+ fn name(&self) -> String {
+ self.name.clone()
+ }
fn public_key(&self) -> Option<String> {
self.public_key.to_owned()
if check_is_apub_id_valid(&inbox, false).is_ok() {
debug!(
"Sending activity {:?} to {}",
- &activity.id_unchecked(),
+ &activity.id_unchecked().map(ToString::to_string),
&inbox
);
send_activity_internal(context, activity, creator, vec![inbox], true, true).await?;
.collect();
debug!(
"Sending activity {:?} to followers of {}",
- &activity.id_unchecked().map(|i| i.to_string()),
+ &activity.id_unchecked().map(ToString::to_string),
&community.actor_id
);
check_is_apub_id_valid(&inbox, false)?;
debug!(
"Sending activity {:?} to community {}",
- &activity.id_unchecked(),
+ &activity.id_unchecked().map(ToString::to_string),
&community.actor_id
);
// dont send to object_actor here, as that is responsibility of the community itself
-use crate::ActorType;
use activitystreams::unparsed::UnparsedMutExt;
use activitystreams_ext::UnparsedExtension;
use actix_web::HttpRequest;
-use anyhow::{anyhow, Context};
+use anyhow::anyhow;
use http::{header::HeaderName, HeaderMap, HeaderValue};
use http_signature_normalization_actix::Config as ConfigActix;
use http_signature_normalization_reqwest::prelude::{Config, SignExt};
-use lemmy_utils::{location_info, LemmyError};
+use lemmy_utils::LemmyError;
use log::debug;
use openssl::{
hash::MessageDigest,
}
/// Verifies the HTTP signature on an incoming inbox request.
-pub fn verify_signature(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), LemmyError> {
- let public_key = actor.public_key().context(location_info!())?;
+pub fn verify_signature(request: &HttpRequest, public_key: &str) -> Result<(), LemmyError> {
let verified = CONFIG2
.begin_verify(
request.method(),
-use crate::{fetcher::fetch::fetch_remote_object, objects::FromApub, NoteExt, PageExt};
+use crate::{
+ fetcher::fetch::fetch_remote_object,
+ objects::FromApub,
+ NoteExt,
+ PageExt,
+ PostOrComment,
+};
use anyhow::anyhow;
use diesel::result::Error::NotFound;
use lemmy_api_common::blocking;
Err(e) => Err(e.into()),
}
}
+
+pub async fn get_or_fetch_and_insert_post_or_comment(
+ ap_id: &Url,
+ context: &LemmyContext,
+ recursion_counter: &mut i32,
+) -> Result<PostOrComment, LemmyError> {
+ Ok(
+ match get_or_fetch_and_insert_post(ap_id, context, recursion_counter).await {
+ Ok(p) => PostOrComment::Post(Box::new(p)),
+ Err(_) => {
+ let c = get_or_fetch_and_insert_comment(ap_id, context, recursion_counter).await?;
+ PostOrComment::Comment(Box::new(c))
+ }
+ },
+ )
+}
/// Activitystreams type for person
type PersonExt =
Ext2<actor::ApActor<ApObject<actor::Actor<UserTypes>>>, PersonExtension, PublicKeyExtension>;
+pub type SiteExt = actor::ApActor<ApObject<actor::Service>>;
/// Activitystreams type for post
pub type PageExt = Ext1<ApObject<Page>, PageExtension>;
pub type NoteExt = ApObject<Note>;
/// Common methods provided by ActivityPub actors (community and person). Not all methods are
/// implemented by all actors.
-#[async_trait::async_trait(?Send)]
pub trait ActorType {
fn is_local(&self) -> bool;
fn actor_id(&self) -> Url;
+ fn name(&self) -> String;
// TODO: every actor should have a public key, so this shouldnt be an option (needs to be fixed in db)
fn public_key(&self) -> Option<String>;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
check_object_for_community_or_site_ban(note, post.community_id, context, request_counter)
.await?;
- if post.locked {
- // This is not very efficient because a comment gets inserted just to be deleted right
- // afterwards, but it seems to be the easiest way to implement it.
- blocking(context.pool(), move |conn| {
- Comment::delete(conn, comment.id)
- })
- .await??;
- Err(anyhow!("Post is locked").into())
- } else {
- Ok(comment)
- }
+ Ok(comment)
}
}
request_counter,
))
.await?;
+ if post.locked {
+ return Err(anyhow!("Post is locked").into());
+ }
// The 2nd item, if it exists, is the parent comment apub_id
// For deeply nested comments, FromApub automatically gets called recursively
--- /dev/null
+[package]
+name = "lemmy_apub_lib"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
+lemmy_utils = { path = "../utils" }
+lemmy_websocket = { path = "../websocket" }
+lemmy_apub_lib_derive = { path = "../apub_lib_derive" }
+activitystreams = "0.7.0-alpha.11"
+activitystreams-ext = "0.1.0-alpha.2"
+serde = { version = "1.0.123", features = ["derive"] }
+async-trait = "0.1.42"
+url = { version = "2.2.1", features = ["serde"] }
--- /dev/null
+use activitystreams::{
+ base::AnyBase,
+ error::DomainError,
+ primitives::OneOrMany,
+ unparsed::Unparsed,
+};
+pub use lemmy_apub_lib_derive::*;
+use lemmy_utils::LemmyError;
+use lemmy_websocket::LemmyContext;
+use url::Url;
+
+#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
+pub enum PublicUrl {
+ #[serde(rename = "https://www.w3.org/ns/activitystreams#Public")]
+ Public,
+}
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ActivityCommonFields {
+ #[serde(rename = "@context")]
+ pub context: OneOrMany<AnyBase>,
+ id: Url,
+ pub actor: Url,
+
+ // unparsed fields
+ #[serde(flatten)]
+ pub unparsed: Unparsed,
+}
+
+impl ActivityCommonFields {
+ pub fn id_unchecked(&self) -> &Url {
+ &self.id
+ }
+}
+
+#[async_trait::async_trait(?Send)]
+pub trait ActivityHandler {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError>;
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError>;
+ fn common(&self) -> &ActivityCommonFields;
+}
+
+pub fn verify_domains_match(a: &Url, b: &Url) -> Result<(), LemmyError> {
+ if a.domain() != b.domain() {
+ return Err(DomainError.into());
+ }
+ Ok(())
+}
+
+pub fn verify_domains_match_opt(a: &Url, b: Option<&Url>) -> Result<(), LemmyError> {
+ if let Some(b2) = b {
+ return verify_domains_match(a, b2);
+ }
+ Ok(())
+}
+
+pub fn verify_urls_match(a: &Url, b: &Url) -> Result<(), LemmyError> {
+ if a != b {
+ return Err(DomainError.into());
+ }
+ Ok(())
+}
--- /dev/null
+[package]
+name = "lemmy_apub_lib_derive"
+version = "0.1.0"
+edition = "2018"
+
+[lib]
+proc-macro = true
+
+[dev-dependencies]
+trybuild = { version = "1.0", features = ["diff"] }
+
+[dependencies]
+proc-macro2 = "1.0"
+syn = "1.0"
+quote = "1.0"
\ No newline at end of file
--- /dev/null
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{parse_macro_input, Data, DeriveInput};
+
+/// Generates implementation ActivityHandler for an enum, which looks like the following (handling
+/// all enum variants).
+///
+/// Based on this code:
+/// ```ignore
+/// #[derive(serde::Deserialize, serde::Serialize, ActivityHandler)]
+/// #[serde(untagged)]
+/// pub enum PersonInboxActivities {
+/// CreateNote(CreateNote),
+/// UpdateNote(UpdateNote),
+/// ```
+/// It will generate this:
+/// ```ignore
+/// impl ActivityHandler for PersonInboxActivities {
+///
+/// async fn verify(
+/// &self,
+/// context: &LemmyContext,
+/// request_counter: &mut i32,
+/// ) -> Result<(), LemmyError> {
+/// match self {
+/// PersonInboxActivities::CreateNote(a) => a.verify(context, request_counter).await,
+/// PersonInboxActivities::UpdateNote(a) => a.verify(context, request_counter).await,
+/// }
+/// }
+///
+/// async fn receive(
+/// &self,
+/// context: &LemmyContext,
+/// request_counter: &mut i32,
+/// ) -> Result<(), LemmyError> {
+/// match self {
+/// PersonInboxActivities::CreateNote(a) => a.receive(context, request_counter).await,
+/// PersonInboxActivities::UpdateNote(a) => a.receive(context, request_counter).await,
+/// }
+/// }
+/// fn common(&self) -> &ActivityCommonFields {
+/// match self {
+/// PersonInboxActivities::CreateNote(a) => a.common(),
+/// PersonInboxActivities::UpdateNote(a) => a.common(),
+/// }
+/// }
+///
+/// ```
+///
+/// TODO: consider replacing this macro with https://crates.io/crates/typetag crate, though it
+/// doesnt support untagged enums which we need for apub.
+#[proc_macro_derive(ActivityHandler)]
+pub fn derive_activity_handler(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ // Parse the input tokens into a syntax tree.
+ let input = parse_macro_input!(input as DeriveInput);
+
+ // Used in the quasi-quotation below as `#name`.
+ let name = input.ident;
+
+ let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
+
+ let input_enum = if let Data::Enum(d) = input.data {
+ d
+ } else {
+ unimplemented!()
+ };
+
+ let impl_verify = input_enum
+ .variants
+ .iter()
+ .map(|variant| variant_impl_verify(&name, variant));
+ let impl_receive = input_enum
+ .variants
+ .iter()
+ .map(|variant| variant_impl_receive(&name, variant));
+ let impl_common = input_enum
+ .variants
+ .iter()
+ .map(|variant| variant_impl_common(&name, variant));
+
+ // The generated impl.
+ let expanded = quote! {
+ #[async_trait::async_trait(?Send)]
+ impl #impl_generics lemmy_apub_lib::ActivityHandler for #name #ty_generics #where_clause {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ match self {
+ #(#impl_verify)*
+ }
+ }
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ match self {
+ #(#impl_receive)*
+ }
+ }
+ fn common(&self) -> &ActivityCommonFields {
+ match self {
+ #(#impl_common)*
+ }
+ }
+ }
+ };
+
+ // Hand the output tokens back to the compiler.
+ proc_macro::TokenStream::from(expanded)
+}
+
+fn variant_impl_common(name: &syn::Ident, variant: &syn::Variant) -> TokenStream {
+ let id = &variant.ident;
+ match &variant.fields {
+ syn::Fields::Unnamed(_) => {
+ quote! {
+ #name::#id(a) => a.common(),
+ }
+ }
+ _ => unimplemented!(),
+ }
+}
+
+fn variant_impl_verify(name: &syn::Ident, variant: &syn::Variant) -> TokenStream {
+ let id = &variant.ident;
+ match &variant.fields {
+ syn::Fields::Unnamed(_) => {
+ quote! {
+ #name::#id(a) => a.verify(context, request_counter).await,
+ }
+ }
+ _ => unimplemented!(),
+ }
+}
+
+fn variant_impl_receive(name: &syn::Ident, variant: &syn::Variant) -> TokenStream {
+ let id = &variant.ident;
+ match &variant.fields {
+ syn::Fields::Unnamed(_) => {
+ quote! {
+ #name::#id(a) => a.receive(context, request_counter).await,
+ }
+ }
+ _ => unimplemented!(),
+ }
+}
[dependencies]
lemmy_utils = { path = "../utils" }
+lemmy_apub_lib = { path = "../apub_lib" }
lemmy_apub = { path = "../apub" }
lemmy_db_queries = { path = "../db_queries" }
lemmy_db_schema = { path = "../db_schema" }
--- /dev/null
+use crate::activities::{
+ comment::{get_notif_recipients, send_websocket_message},
+ verify_activity,
+ verify_person_in_community,
+};
+use activitystreams::{activity::kind::CreateType, base::BaseExt};
+use lemmy_apub::{objects::FromApub, NoteExt};
+use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_db_schema::source::comment::Comment;
+use lemmy_utils::LemmyError;
+use lemmy_websocket::{LemmyContext, UserOperationCrud};
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CreateComment {
+ to: PublicUrl,
+ object: NoteExt,
+ cc: Vec<Url>,
+ #[serde(rename = "type")]
+ kind: CreateType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for CreateComment {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+ verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
+ // TODO: should add a check that the correct community is in cc (probably needs changes to
+ // comment deserialization)
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let comment = Comment::from_apub(
+ &self.object,
+ context,
+ self.common.actor.clone(),
+ request_counter,
+ false,
+ )
+ .await?;
+ let recipients =
+ get_notif_recipients(&self.common.actor, &comment, context, request_counter).await?;
+ send_websocket_message(
+ comment.id,
+ recipients,
+ UserOperationCrud::CreateComment,
+ context,
+ )
+ .await
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use lemmy_api_common::{blocking, comment::CommentResponse, send_local_notifs};
+use lemmy_apub::fetcher::person::get_or_fetch_and_upsert_person;
+use lemmy_db_queries::Crud;
+use lemmy_db_schema::{
+ source::{comment::Comment, post::Post},
+ CommentId,
+ LocalUserId,
+};
+use lemmy_db_views::comment_view::CommentView;
+use lemmy_utils::{utils::scrape_text_for_mentions, LemmyError};
+use lemmy_websocket::{messages::SendComment, LemmyContext};
+use url::Url;
+
+pub mod create;
+pub mod update;
+
+async fn get_notif_recipients(
+ actor: &Url,
+ comment: &Comment,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+) -> Result<Vec<LocalUserId>, LemmyError> {
+ let post_id = comment.post_id;
+ let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
+ let actor = get_or_fetch_and_upsert_person(actor, context, request_counter).await?;
+
+ // Note:
+ // Although mentions could be gotten from the post tags (they are included there), or the ccs,
+ // Its much easier to scrape them from the comment body, since the API has to do that
+ // anyway.
+ // TODO: for compatibility with other projects, it would be much better to read this from cc or tags
+ let mentions = scrape_text_for_mentions(&comment.content);
+ send_local_notifs(mentions, comment.clone(), actor, post, context.pool(), true).await
+}
+
+// TODO: in many call sites we are setting an empty vec for recipient_ids, we should get the actual
+// recipient actors from somewhere
+pub(crate) async fn send_websocket_message<
+ OP: ToString + Send + lemmy_websocket::OperationType + 'static,
+>(
+ comment_id: CommentId,
+ recipient_ids: Vec<LocalUserId>,
+ op: OP,
+ context: &LemmyContext,
+) -> Result<(), LemmyError> {
+ // Refetch the view
+ let comment_view = blocking(context.pool(), move |conn| {
+ CommentView::read(conn, comment_id, None)
+ })
+ .await??;
+
+ let res = CommentResponse {
+ comment_view,
+ recipient_ids,
+ form_id: None,
+ };
+
+ context.chat_server().do_send(SendComment {
+ op,
+ comment: res,
+ websocket_id: None,
+ });
+
+ Ok(())
+}
--- /dev/null
+use crate::activities::{comment::send_websocket_message, verify_mod_action};
+use activitystreams::activity::kind::RemoveType;
+use lemmy_api_common::blocking;
+use lemmy_apub::{check_is_apub_id_valid, fetcher::objects::get_or_fetch_and_insert_comment};
+use lemmy_apub_lib::{verify_domains_match, ActivityCommonFields, ActivityHandlerNew, PublicUrl};
+use lemmy_db_queries::source::comment::Comment_;
+use lemmy_db_schema::source::comment::Comment;
+use lemmy_utils::LemmyError;
+use lemmy_websocket::{LemmyContext, UserOperationCrud};
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct RemoveComment {
+ to: PublicUrl,
+ pub(in crate::activities::comment) object: Url,
+ cc: [Url; 1],
+ #[serde(rename = "type")]
+ kind: RemoveType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandlerNew for RemoveComment {
+ async fn verify(&self, context: &LemmyContext, _: &mut i32) -> Result<(), LemmyError> {
+ verify_domains_match(&self.common.actor, self.common.id_unchecked())?;
+ check_is_apub_id_valid(&self.common.actor, false)?;
+ verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let comment = get_or_fetch_and_insert_comment(&self.object, context, request_counter).await?;
+
+ let removed_comment = blocking(context.pool(), move |conn| {
+ Comment::update_removed(conn, comment.id, true)
+ })
+ .await??;
+
+ send_websocket_message(
+ removed_comment.id,
+ vec![],
+ UserOperationCrud::EditComment,
+ context,
+ )
+ .await
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use crate::activities::{
+ comment::{remove::RemoveComment, send_websocket_message},
+ verify_mod_action,
+};
+use activitystreams::activity::kind::UndoType;
+use lemmy_api_common::blocking;
+use lemmy_apub::{check_is_apub_id_valid, fetcher::objects::get_or_fetch_and_insert_comment};
+use lemmy_apub_lib::{verify_domains_match, ActivityCommonFields, ActivityHandlerNew, PublicUrl};
+use lemmy_db_queries::source::comment::Comment_;
+use lemmy_db_schema::source::comment::Comment;
+use lemmy_utils::LemmyError;
+use lemmy_websocket::{LemmyContext, UserOperationCrud};
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct UndoRemoveComment {
+ to: PublicUrl,
+ object: RemoveComment,
+ cc: [Url; 1],
+ #[serde(rename = "type")]
+ kind: UndoType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandlerNew for UndoRemoveComment {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_domains_match(&self.common.actor, self.common.id_unchecked())?;
+ check_is_apub_id_valid(&self.common.actor, false)?;
+ verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
+ self.object.verify(context, request_counter).await
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let comment =
+ get_or_fetch_and_insert_comment(&self.object.object, context, request_counter).await?;
+
+ let removed_comment = blocking(context.pool(), move |conn| {
+ Comment::update_removed(conn, comment.id, false)
+ })
+ .await??;
+
+ send_websocket_message(
+ removed_comment.id,
+ vec![],
+ UserOperationCrud::EditComment,
+ context,
+ )
+ .await
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use crate::activities::{
+ comment::{get_notif_recipients, send_websocket_message},
+ verify_activity,
+ verify_person_in_community,
+};
+use activitystreams::{activity::kind::UpdateType, base::BaseExt};
+use lemmy_apub::{objects::FromApub, NoteExt};
+use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_db_schema::source::comment::Comment;
+use lemmy_utils::LemmyError;
+use lemmy_websocket::{LemmyContext, UserOperationCrud};
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct UpdateComment {
+ to: PublicUrl,
+ object: NoteExt,
+ cc: Vec<Url>,
+ #[serde(rename = "type")]
+ kind: UpdateType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for UpdateComment {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+ verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let comment = Comment::from_apub(
+ &self.object,
+ context,
+ self.common.actor.clone(),
+ request_counter,
+ false,
+ )
+ .await?;
+
+ let recipients =
+ get_notif_recipients(&self.common.actor, &comment, context, request_counter).await?;
+ send_websocket_message(
+ comment.id,
+ recipients,
+ UserOperationCrud::EditComment,
+ context,
+ )
+ .await
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use crate::activities::{
+ verify_activity,
+ verify_add_remove_moderator_target,
+ verify_mod_action,
+ verify_person_in_community,
+};
+use activitystreams::{activity::kind::AddType, base::AnyBase};
+use lemmy_api_common::blocking;
+use lemmy_apub::{
+ fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person},
+ CommunityType,
+};
+use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_db_queries::{source::community::CommunityModerator_, Joinable};
+use lemmy_db_schema::source::community::{CommunityModerator, CommunityModeratorForm};
+use lemmy_utils::LemmyError;
+use lemmy_websocket::LemmyContext;
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct AddMod {
+ to: PublicUrl,
+ object: Url,
+ target: Url,
+ cc: [Url; 1],
+ #[serde(rename = "type")]
+ kind: AddType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for AddMod {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+ verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
+ verify_add_remove_moderator_target(&self.target, self.cc[0].clone())?;
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let community =
+ get_or_fetch_and_upsert_community(&self.cc[0], context, request_counter).await?;
+ let new_mod = get_or_fetch_and_upsert_person(&self.object, context, request_counter).await?;
+
+ // If we had to refetch the community while parsing the activity, then the new mod has already
+ // been added. Skip it here as it would result in a duplicate key error.
+ let new_mod_id = new_mod.id;
+ let moderated_communities = blocking(context.pool(), move |conn| {
+ CommunityModerator::get_person_moderated_communities(conn, new_mod_id)
+ })
+ .await??;
+ if !moderated_communities.contains(&community.id) {
+ let form = CommunityModeratorForm {
+ community_id: community.id,
+ person_id: new_mod.id,
+ };
+ blocking(context.pool(), move |conn| {
+ CommunityModerator::join(conn, &form)
+ })
+ .await??;
+ }
+ if community.local {
+ let anybase = AnyBase::from_arbitrary_json(serde_json::to_string(self)?)?;
+ community
+ .send_announce(anybase, Some(self.object.clone()), context)
+ .await?;
+ }
+ // TODO: send websocket notification about added mod
+ Ok(())
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use crate::{
+ activities::{
+ comment::{create::CreateComment, update::UpdateComment},
+ community::{
+ add_mod::AddMod,
+ block_user::BlockUserFromCommunity,
+ undo_block_user::UndoBlockUserFromCommunity,
+ },
+ deletion::{
+ delete::DeletePostCommentOrCommunity,
+ undo_delete::UndoDeletePostCommentOrCommunity,
+ },
+ post::{create::CreatePost, update::UpdatePost},
+ removal::{
+ remove::RemovePostCommentCommunityOrMod,
+ undo_remove::UndoRemovePostCommentOrCommunity,
+ },
+ verify_activity,
+ verify_community,
+ voting::{
+ dislike::DislikePostOrComment,
+ like::LikePostOrComment,
+ undo_dislike::UndoDislikePostOrComment,
+ undo_like::UndoLikePostOrComment,
+ },
+ },
+ http::is_activity_already_known,
+};
+use activitystreams::activity::kind::AnnounceType;
+use lemmy_apub::insert_activity;
+use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_utils::LemmyError;
+use lemmy_websocket::LemmyContext;
+use serde::{Deserialize, Serialize};
+use url::Url;
+
+#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)]
+#[serde(untagged)]
+pub enum AnnouncableActivities {
+ CreateComment(CreateComment),
+ UpdateComment(UpdateComment),
+ CreatePost(CreatePost),
+ UpdatePost(UpdatePost),
+ LikePostOrComment(LikePostOrComment),
+ DislikePostOrComment(DislikePostOrComment),
+ UndoLikePostOrComment(UndoLikePostOrComment),
+ UndoDislikePostOrComment(UndoDislikePostOrComment),
+ DeletePostCommentOrCommunity(DeletePostCommentOrCommunity),
+ UndoDeletePostCommentOrCommunity(UndoDeletePostCommentOrCommunity),
+ RemovePostCommentCommunityOrMod(RemovePostCommentCommunityOrMod),
+ UndoRemovePostCommentOrCommunity(UndoRemovePostCommentOrCommunity),
+ BlockUserFromCommunity(BlockUserFromCommunity),
+ UndoBlockUserFromCommunity(UndoBlockUserFromCommunity),
+ AddMod(AddMod),
+}
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct AnnounceActivity {
+ to: PublicUrl,
+ object: AnnouncableActivities,
+ cc: Vec<Url>,
+ #[serde(rename = "type")]
+ kind: AnnounceType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for AnnounceActivity {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ verify_community(&self.common.actor, context, request_counter).await?;
+ self.object.verify(context, request_counter).await?;
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ if is_activity_already_known(context.pool(), self.object.common().id_unchecked()).await? {
+ return Ok(());
+ }
+ insert_activity(
+ self.object.common().id_unchecked(),
+ self.object.clone(),
+ false,
+ true,
+ context.pool(),
+ )
+ .await?;
+ self.object.receive(context, request_counter).await
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use crate::activities::{verify_activity, verify_mod_action, verify_person_in_community};
+use activitystreams::activity::kind::BlockType;
+use lemmy_api_common::blocking;
+use lemmy_apub::fetcher::{
+ community::get_or_fetch_and_upsert_community,
+ person::get_or_fetch_and_upsert_person,
+};
+use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_db_queries::{Bannable, Followable};
+use lemmy_db_schema::source::community::{
+ CommunityFollower,
+ CommunityFollowerForm,
+ CommunityPersonBan,
+ CommunityPersonBanForm,
+};
+use lemmy_utils::LemmyError;
+use lemmy_websocket::LemmyContext;
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BlockUserFromCommunity {
+ to: PublicUrl,
+ pub(in crate::activities::community) object: Url,
+ cc: [Url; 1],
+ #[serde(rename = "type")]
+ kind: BlockType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for BlockUserFromCommunity {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+ verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let community =
+ get_or_fetch_and_upsert_community(&self.cc[0], context, request_counter).await?;
+ let blocked_user =
+ get_or_fetch_and_upsert_person(&self.object, context, request_counter).await?;
+
+ let community_user_ban_form = CommunityPersonBanForm {
+ community_id: community.id,
+ person_id: blocked_user.id,
+ };
+
+ blocking(context.pool(), move |conn: &'_ _| {
+ CommunityPersonBan::ban(conn, &community_user_ban_form)
+ })
+ .await??;
+
+ // Also unsubscribe them from the community, if they are subscribed
+ let community_follower_form = CommunityFollowerForm {
+ community_id: community.id,
+ person_id: blocked_user.id,
+ pending: false,
+ };
+ blocking(context.pool(), move |conn: &'_ _| {
+ CommunityFollower::unfollow(conn, &community_follower_form)
+ })
+ .await?
+ .ok();
+
+ Ok(())
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use lemmy_api_common::{blocking, community::CommunityResponse};
+use lemmy_db_schema::CommunityId;
+use lemmy_db_views_actor::community_view::CommunityView;
+use lemmy_utils::LemmyError;
+use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext};
+
+pub mod add_mod;
+pub mod announce;
+pub mod block_user;
+pub mod undo_block_user;
+pub mod update;
+
+pub(crate) async fn send_websocket_message<
+ OP: ToString + Send + lemmy_websocket::OperationType + 'static,
+>(
+ community_id: CommunityId,
+ op: OP,
+ context: &LemmyContext,
+) -> Result<(), LemmyError> {
+ let community_view = blocking(context.pool(), move |conn| {
+ CommunityView::read(conn, community_id, None)
+ })
+ .await??;
+
+ let res = CommunityResponse { community_view };
+
+ context.chat_server().do_send(SendCommunityRoomMessage {
+ op,
+ response: res,
+ community_id,
+ websocket_id: None,
+ });
+
+ Ok(())
+}
--- /dev/null
+use crate::activities::{
+ community::block_user::BlockUserFromCommunity,
+ verify_activity,
+ verify_mod_action,
+ verify_person_in_community,
+};
+use activitystreams::activity::kind::UndoType;
+use lemmy_api_common::blocking;
+use lemmy_apub::fetcher::{
+ community::get_or_fetch_and_upsert_community,
+ person::get_or_fetch_and_upsert_person,
+};
+use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_db_queries::Bannable;
+use lemmy_db_schema::source::community::{CommunityPersonBan, CommunityPersonBanForm};
+use lemmy_utils::LemmyError;
+use lemmy_websocket::LemmyContext;
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct UndoBlockUserFromCommunity {
+ to: PublicUrl,
+ object: BlockUserFromCommunity,
+ cc: [Url; 1],
+ #[serde(rename = "type")]
+ kind: UndoType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for UndoBlockUserFromCommunity {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+ verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
+ self.object.verify(context, request_counter).await?;
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let community =
+ get_or_fetch_and_upsert_community(&self.cc[0], context, request_counter).await?;
+ let blocked_user =
+ get_or_fetch_and_upsert_person(&self.object.object, context, request_counter).await?;
+
+ let community_user_ban_form = CommunityPersonBanForm {
+ community_id: community.id,
+ person_id: blocked_user.id,
+ };
+
+ blocking(context.pool(), move |conn: &'_ _| {
+ CommunityPersonBan::unban(conn, &community_user_ban_form)
+ })
+ .await??;
+
+ Ok(())
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use crate::activities::{
+ community::send_websocket_message,
+ verify_activity,
+ verify_mod_action,
+ verify_person_in_community,
+};
+use activitystreams::activity::kind::UpdateType;
+use lemmy_api_common::blocking;
+use lemmy_apub::{objects::FromApubToForm, GroupExt};
+use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_db_queries::{ApubObject, Crud};
+use lemmy_db_schema::source::community::{Community, CommunityForm};
+use lemmy_utils::LemmyError;
+use lemmy_websocket::{LemmyContext, UserOperationCrud};
+use url::Url;
+
+/// This activity is received from a remote community mod, and updates the description or other
+/// fields of a local community.
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct UpdateCommunity {
+ to: PublicUrl,
+ object: GroupExt,
+ cc: [Url; 1],
+ #[serde(rename = "type")]
+ kind: UpdateType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for UpdateCommunity {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+ verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let cc = self.cc[0].clone().into();
+ let community = blocking(context.pool(), move |conn| {
+ Community::read_from_apub_id(conn, &cc)
+ })
+ .await??;
+
+ let updated_community = CommunityForm::from_apub(
+ &self.object,
+ context,
+ community.actor_id.clone().into(),
+ request_counter,
+ false,
+ )
+ .await?;
+ let cf = CommunityForm {
+ name: updated_community.name,
+ title: updated_community.title,
+ description: updated_community.description,
+ nsfw: updated_community.nsfw,
+ // TODO: icon and banner would be hosted on the other instance, ideally we would copy it to ours
+ icon: updated_community.icon,
+ banner: updated_community.banner,
+ ..CommunityForm::default()
+ };
+ let updated_community = blocking(context.pool(), move |conn| {
+ Community::update(conn, community.id, &cf)
+ })
+ .await??;
+
+ send_websocket_message(
+ updated_community.id,
+ UserOperationCrud::EditCommunity,
+ context,
+ )
+ .await
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use crate::activities::{
+ comment::send_websocket_message as send_comment_message,
+ community::send_websocket_message as send_community_message,
+ post::send_websocket_message as send_post_message,
+ verify_activity,
+ verify_mod_action,
+ verify_person_in_community,
+};
+use activitystreams::activity::kind::DeleteType;
+use lemmy_api_common::blocking;
+use lemmy_apub::{
+ fetcher::{
+ community::get_or_fetch_and_upsert_community,
+ objects::get_or_fetch_and_insert_post_or_comment,
+ person::get_or_fetch_and_upsert_person,
+ },
+ ActorType,
+ CommunityType,
+ PostOrComment,
+};
+use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_db_queries::{
+ source::{comment::Comment_, community::Community_, post::Post_},
+ Crud,
+};
+use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post};
+use lemmy_utils::LemmyError;
+use lemmy_websocket::{LemmyContext, UserOperationCrud};
+use url::Url;
+
+/// This is very confusing, because there are four distinct cases to handle:
+/// - user deletes their post
+/// - user deletes their comment
+/// - remote community mod deletes local community
+/// - remote community deletes itself (triggered by a mod)
+///
+/// TODO: we should probably change how community deletions work to simplify this. Probably by
+/// wrapping it in an announce just like other activities, instead of having the community send it.
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct DeletePostCommentOrCommunity {
+ to: PublicUrl,
+ pub(in crate::activities::deletion) object: Url,
+ cc: [Url; 1],
+ #[serde(rename = "type")]
+ kind: DeleteType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for DeletePostCommentOrCommunity {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ let object_community =
+ get_or_fetch_and_upsert_community(&self.object, context, request_counter).await;
+ // deleting a community (set counter 0 to only fetch from local db)
+ if object_community.is_ok() {
+ verify_mod_action(&self.common.actor, self.object.clone(), context).await?;
+ }
+ // deleting a post or comment
+ else {
+ verify_person_in_community(&self.common().actor, &self.cc, context, request_counter).await?;
+ let object_creator =
+ get_post_or_comment_actor_id(&self.object, context, request_counter).await?;
+ verify_urls_match(&self.common.actor, &object_creator)?;
+ }
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let object_community =
+ get_or_fetch_and_upsert_community(&self.object, context, request_counter).await;
+ // deleting a community
+ if let Ok(community) = object_community {
+ if community.local {
+ // repeat these checks just to be sure
+ verify_person_in_community(&self.common().actor, &self.cc, context, request_counter)
+ .await?;
+ verify_mod_action(&self.common.actor, self.object.clone(), context).await?;
+ let mod_ =
+ get_or_fetch_and_upsert_person(&self.common.actor, context, request_counter).await?;
+ community.send_delete(mod_, context).await?;
+ }
+ let deleted_community = blocking(context.pool(), move |conn| {
+ Community::update_deleted(conn, community.id, true)
+ })
+ .await??;
+
+ send_community_message(
+ deleted_community.id,
+ UserOperationCrud::DeleteCommunity,
+ context,
+ )
+ .await
+ }
+ // deleting a post or comment
+ else {
+ match get_or_fetch_and_insert_post_or_comment(&self.object, context, request_counter).await? {
+ PostOrComment::Post(post) => {
+ let deleted_post = blocking(context.pool(), move |conn| {
+ Post::update_deleted(conn, post.id, true)
+ })
+ .await??;
+ send_post_message(deleted_post.id, UserOperationCrud::EditPost, context).await
+ }
+ PostOrComment::Comment(comment) => {
+ let deleted_comment = blocking(context.pool(), move |conn| {
+ Comment::update_deleted(conn, comment.id, true)
+ })
+ .await??;
+ send_comment_message(
+ deleted_comment.id,
+ vec![],
+ UserOperationCrud::EditComment,
+ context,
+ )
+ .await
+ }
+ }
+ }
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
+
+async fn get_post_or_comment_actor_id(
+ object: &Url,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+) -> Result<Url, LemmyError> {
+ let actor_id =
+ match get_or_fetch_and_insert_post_or_comment(object, context, request_counter).await? {
+ PostOrComment::Post(post) => {
+ let creator_id = post.creator_id;
+ blocking(context.pool(), move |conn| Person::read(conn, creator_id))
+ .await??
+ .actor_id()
+ }
+ PostOrComment::Comment(comment) => {
+ let creator_id = comment.creator_id;
+ blocking(context.pool(), move |conn| Person::read(conn, creator_id))
+ .await??
+ .actor_id()
+ }
+ };
+ Ok(actor_id)
+}
--- /dev/null
+pub mod delete;
+pub mod undo_delete;
--- /dev/null
+use crate::activities::{
+ comment::send_websocket_message as send_comment_message,
+ community::send_websocket_message as send_community_message,
+ deletion::delete::DeletePostCommentOrCommunity,
+ post::send_websocket_message as send_post_message,
+ verify_activity,
+ verify_mod_action,
+ verify_person_in_community,
+};
+use activitystreams::activity::kind::UndoType;
+use lemmy_api_common::blocking;
+use lemmy_apub::{
+ fetcher::{
+ community::get_or_fetch_and_upsert_community,
+ objects::get_or_fetch_and_insert_post_or_comment,
+ person::get_or_fetch_and_upsert_person,
+ },
+ CommunityType,
+ PostOrComment,
+};
+use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_db_queries::source::{comment::Comment_, community::Community_, post::Post_};
+use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post};
+use lemmy_utils::LemmyError;
+use lemmy_websocket::{LemmyContext, UserOperationCrud};
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct UndoDeletePostCommentOrCommunity {
+ to: PublicUrl,
+ object: DeletePostCommentOrCommunity,
+ cc: [Url; 1],
+ #[serde(rename = "type")]
+ kind: UndoType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for UndoDeletePostCommentOrCommunity {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ self.object.verify(context, request_counter).await?;
+ let object_community =
+ get_or_fetch_and_upsert_community(&self.object.object, context, request_counter).await;
+ // restoring a community
+ if object_community.is_ok() {
+ verify_mod_action(&self.common.actor, self.object.object.clone(), context).await?;
+ }
+ // restoring a post or comment
+ else {
+ verify_person_in_community(&self.common().actor, &self.cc, context, request_counter).await?;
+ verify_urls_match(&self.common.actor, &self.object.common().actor)?;
+ }
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let object_community =
+ get_or_fetch_and_upsert_community(&self.object.object, context, request_counter).await;
+ // restoring a community
+ if let Ok(community) = object_community {
+ if community.local {
+ // repeat these checks just to be sure
+ verify_person_in_community(&self.common().actor, &self.cc, context, request_counter)
+ .await?;
+ verify_mod_action(&self.common.actor, self.object.object.clone(), context).await?;
+ let mod_ =
+ get_or_fetch_and_upsert_person(&self.common.actor, context, request_counter).await?;
+ community.send_undo_delete(mod_, context).await?;
+ }
+ let deleted_community = blocking(context.pool(), move |conn| {
+ Community::update_deleted(conn, community.id, false)
+ })
+ .await??;
+
+ send_community_message(
+ deleted_community.id,
+ UserOperationCrud::EditCommunity,
+ context,
+ )
+ .await
+ }
+ // restoring a post or comment
+ else {
+ match get_or_fetch_and_insert_post_or_comment(&self.object.object, context, request_counter)
+ .await?
+ {
+ PostOrComment::Post(post) => {
+ let deleted_post = blocking(context.pool(), move |conn| {
+ Post::update_deleted(conn, post.id, false)
+ })
+ .await??;
+ send_post_message(deleted_post.id, UserOperationCrud::EditPost, context).await
+ }
+ PostOrComment::Comment(comment) => {
+ let deleted_comment = blocking(context.pool(), move |conn| {
+ Comment::update_deleted(conn, comment.id, false)
+ })
+ .await??;
+ send_comment_message(
+ deleted_comment.id,
+ vec![],
+ UserOperationCrud::EditComment,
+ context,
+ )
+ .await
+ }
+ }
+ }
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use crate::activities::{following::follow::FollowCommunity, verify_activity, verify_community};
+use activitystreams::activity::kind::AcceptType;
+use lemmy_api_common::blocking;
+use lemmy_apub::fetcher::{
+ community::get_or_fetch_and_upsert_community,
+ person::get_or_fetch_and_upsert_person,
+};
+use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler};
+use lemmy_db_queries::Followable;
+use lemmy_db_schema::source::community::CommunityFollower;
+use lemmy_utils::LemmyError;
+use lemmy_websocket::LemmyContext;
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct AcceptFollowCommunity {
+ to: Url,
+ object: FollowCommunity,
+ #[serde(rename = "type")]
+ kind: AcceptType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+/// Handle accepted follows
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for AcceptFollowCommunity {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ verify_urls_match(&self.to, &self.object.common.actor)?;
+ verify_urls_match(&self.common.actor, &self.object.to)?;
+ verify_community(&self.common.actor, context, request_counter).await?;
+ self.object.verify(context, request_counter).await?;
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let actor =
+ get_or_fetch_and_upsert_community(&self.common.actor, context, request_counter).await?;
+ let to = get_or_fetch_and_upsert_person(&self.to, context, request_counter).await?;
+ // This will throw an error if no follow was requested
+ blocking(context.pool(), move |conn| {
+ CommunityFollower::follow_accepted(conn, actor.id, to.id)
+ })
+ .await??;
+
+ Ok(())
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use crate::activities::{verify_activity, verify_person};
+use activitystreams::{
+ activity::{kind::FollowType, Follow},
+ base::{AnyBase, ExtendsExt},
+};
+use anyhow::Context;
+use lemmy_api_common::blocking;
+use lemmy_apub::{
+ fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person},
+ CommunityType,
+};
+use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler};
+use lemmy_db_queries::Followable;
+use lemmy_db_schema::source::community::{CommunityFollower, CommunityFollowerForm};
+use lemmy_utils::{location_info, LemmyError};
+use lemmy_websocket::LemmyContext;
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct FollowCommunity {
+ pub(in crate::activities::following) to: Url,
+ pub(in crate::activities::following) object: Url,
+ #[serde(rename = "type")]
+ kind: FollowType,
+ #[serde(flatten)]
+ pub(in crate::activities::following) common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for FollowCommunity {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ verify_urls_match(&self.to, &self.object)?;
+ verify_person(&self.common.actor, context, request_counter).await?;
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let actor =
+ get_or_fetch_and_upsert_person(&self.common.actor, context, request_counter).await?;
+ let community =
+ get_or_fetch_and_upsert_community(&self.object, context, request_counter).await?;
+ let community_follower_form = CommunityFollowerForm {
+ community_id: community.id,
+ person_id: actor.id,
+ pending: false,
+ };
+
+ // This will fail if they're already a follower, but ignore the error.
+ blocking(context.pool(), move |conn| {
+ CommunityFollower::follow(conn, &community_follower_form).ok()
+ })
+ .await?;
+
+ // TODO: avoid the conversion and pass our own follow struct directly
+ let anybase = AnyBase::from_arbitrary_json(serde_json::to_string(self)?)?;
+ let anybase = Follow::from_any_base(anybase)?.context(location_info!())?;
+ community.send_accept_follow(anybase, context).await
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+pub mod accept;
+pub mod follow;
+pub mod undo;
--- /dev/null
+use crate::activities::{following::follow::FollowCommunity, verify_activity, verify_person};
+use activitystreams::activity::kind::UndoType;
+use lemmy_api_common::blocking;
+use lemmy_apub::fetcher::{
+ community::get_or_fetch_and_upsert_community,
+ person::get_or_fetch_and_upsert_person,
+};
+use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler};
+use lemmy_db_queries::Followable;
+use lemmy_db_schema::source::community::{CommunityFollower, CommunityFollowerForm};
+use lemmy_utils::LemmyError;
+use lemmy_websocket::LemmyContext;
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct UndoFollowCommunity {
+ to: Url,
+ object: FollowCommunity,
+ #[serde(rename = "type")]
+ kind: UndoType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for UndoFollowCommunity {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ verify_urls_match(&self.to, &self.object.object)?;
+ verify_urls_match(&self.common.actor, &self.object.common.actor)?;
+ verify_person(&self.common.actor, context, request_counter).await?;
+ self.object.verify(context, request_counter).await?;
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let actor =
+ get_or_fetch_and_upsert_person(&self.common.actor, context, request_counter).await?;
+ let community = get_or_fetch_and_upsert_community(&self.to, context, request_counter).await?;
+
+ let community_follower_form = CommunityFollowerForm {
+ community_id: community.id,
+ person_id: actor.id,
+ pending: false,
+ };
+
+ // This will fail if they aren't a follower, but ignore the error.
+ blocking(context.pool(), move |conn| {
+ CommunityFollower::unfollow(conn, &community_follower_form).ok()
+ })
+ .await?;
+ Ok(())
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
-pub(crate) mod receive;
+use anyhow::anyhow;
+use lemmy_api_common::blocking;
+use lemmy_apub::{
+ check_community_or_site_ban,
+ check_is_apub_id_valid,
+ fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person},
+ generate_moderators_url,
+};
+use lemmy_apub_lib::{verify_domains_match, ActivityCommonFields};
+use lemmy_db_queries::ApubObject;
+use lemmy_db_schema::{
+ source::{community::Community, person::Person},
+ DbUrl,
+};
+use lemmy_db_views_actor::community_view::CommunityView;
+use lemmy_utils::LemmyError;
+use lemmy_websocket::LemmyContext;
+use url::Url;
+
+pub mod comment;
+pub mod community;
+pub mod deletion;
+pub mod following;
+pub mod post;
+pub mod private_message;
+pub mod removal;
+pub mod voting;
+
+/// Checks that the specified Url actually identifies a Person (by fetching it), and that the person
+/// doesn't have a site ban.
+async fn verify_person(
+ person_id: &Url,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+) -> Result<(), LemmyError> {
+ let person = get_or_fetch_and_upsert_person(person_id, context, request_counter).await?;
+ if person.banned {
+ return Err(anyhow!("Person {} is banned", person_id).into());
+ }
+ Ok(())
+}
+
+/// Fetches the person and community to verify their type, then checks if person is banned from site
+/// or community.
+async fn verify_person_in_community(
+ person_id: &Url,
+ cc: &[Url],
+ context: &LemmyContext,
+ request_counter: &mut i32,
+) -> Result<Community, LemmyError> {
+ let person = get_or_fetch_and_upsert_person(person_id, context, request_counter).await?;
+ let mut cc_iter = cc.iter();
+ let community: Community = loop {
+ if let Some(cid) = cc_iter.next() {
+ if let Ok(c) = get_or_fetch_and_upsert_community(cid, context, request_counter).await {
+ break c;
+ }
+ } else {
+ return Err(anyhow!("No community found in cc").into());
+ }
+ };
+ check_community_or_site_ban(&person, community.id, context.pool()).await?;
+ Ok(community)
+}
+
+/// Simply check that the url actually refers to a valid group.
+async fn verify_community(
+ community_id: &Url,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+) -> Result<(), LemmyError> {
+ get_or_fetch_and_upsert_community(community_id, context, request_counter).await?;
+ Ok(())
+}
+
+fn verify_activity(common: &ActivityCommonFields) -> Result<(), LemmyError> {
+ check_is_apub_id_valid(&common.actor, false)?;
+ verify_domains_match(common.id_unchecked(), &common.actor)?;
+ Ok(())
+}
+
+async fn verify_mod_action(
+ actor_id: &Url,
+ activity_cc: Url,
+ context: &LemmyContext,
+) -> Result<(), LemmyError> {
+ let community = blocking(context.pool(), move |conn| {
+ Community::read_from_apub_id(conn, &activity_cc.into())
+ })
+ .await??;
+
+ if community.local {
+ let actor_id: DbUrl = actor_id.clone().into();
+ let actor = blocking(context.pool(), move |conn| {
+ Person::read_from_apub_id(conn, &actor_id)
+ })
+ .await??;
+
+ // Note: this will also return true for admins in addition to mods, but as we dont know about
+ // remote admins, it doesnt make any difference.
+ let community_id = community.id;
+ let actor_id = actor.id;
+ let is_mod_or_admin = blocking(context.pool(), move |conn| {
+ CommunityView::is_mod_or_admin(conn, actor_id, community_id)
+ })
+ .await?;
+ if !is_mod_or_admin {
+ return Err(anyhow!("Not a mod").into());
+ }
+ }
+ Ok(())
+}
+
+/// For Add/Remove community moderator activities, check that the target field actually contains
+/// /c/community/moderators. Any different values are unsupported.
+fn verify_add_remove_moderator_target(target: &Url, community: Url) -> Result<(), LemmyError> {
+ if target != &generate_moderators_url(&community.into())?.into_inner() {
+ return Err(anyhow!("Unkown target url").into());
+ }
+ Ok(())
+}
--- /dev/null
+use crate::activities::{
+ post::send_websocket_message,
+ verify_activity,
+ verify_person_in_community,
+};
+use activitystreams::{activity::kind::CreateType, base::BaseExt};
+use lemmy_apub::{
+ fetcher::person::get_or_fetch_and_upsert_person,
+ objects::FromApub,
+ ActorType,
+ PageExt,
+};
+use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_db_schema::source::post::Post;
+use lemmy_utils::LemmyError;
+use lemmy_websocket::{LemmyContext, UserOperationCrud};
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CreatePost {
+ to: PublicUrl,
+ object: PageExt,
+ cc: Vec<Url>,
+ #[serde(rename = "type")]
+ kind: CreateType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for CreatePost {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+ verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let actor =
+ get_or_fetch_and_upsert_person(&self.common.actor, context, request_counter).await?;
+ let post = Post::from_apub(
+ &self.object,
+ context,
+ actor.actor_id(),
+ request_counter,
+ false,
+ )
+ .await?;
+
+ send_websocket_message(post.id, UserOperationCrud::CreatePost, context).await
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use lemmy_api_common::{blocking, post::PostResponse};
+use lemmy_db_schema::PostId;
+use lemmy_db_views::post_view::PostView;
+use lemmy_utils::LemmyError;
+use lemmy_websocket::{messages::SendPost, LemmyContext};
+
+pub mod create;
+pub mod update;
+
+pub(crate) async fn send_websocket_message<
+ OP: ToString + Send + lemmy_websocket::OperationType + 'static,
+>(
+ post_id: PostId,
+ op: OP,
+ context: &LemmyContext,
+) -> Result<(), LemmyError> {
+ let post_view = blocking(context.pool(), move |conn| {
+ PostView::read(conn, post_id, None)
+ })
+ .await??;
+
+ let res = PostResponse { post_view };
+
+ context.chat_server().do_send(SendPost {
+ op,
+ post: res,
+ websocket_id: None,
+ });
+
+ Ok(())
+}
--- /dev/null
+use crate::activities::{
+ post::send_websocket_message,
+ verify_activity,
+ verify_mod_action,
+ verify_person_in_community,
+};
+use activitystreams::{activity::kind::UpdateType, base::BaseExt};
+use anyhow::Context;
+use lemmy_api_common::blocking;
+use lemmy_apub::{
+ objects::{FromApub, FromApubToForm},
+ ActorType,
+ PageExt,
+};
+use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_db_queries::ApubObject;
+use lemmy_db_schema::{
+ source::post::{Post, PostForm},
+ DbUrl,
+};
+use lemmy_utils::{location_info, LemmyError};
+use lemmy_websocket::{LemmyContext, UserOperationCrud};
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct UpdatePost {
+ to: PublicUrl,
+ object: PageExt,
+ cc: Vec<Url>,
+ #[serde(rename = "type")]
+ kind: UpdateType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for UpdatePost {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ let community =
+ verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+
+ let temp_post = PostForm::from_apub(
+ &self.object,
+ context,
+ self.common.actor.clone(),
+ request_counter,
+ true,
+ )
+ .await?;
+ let post_id: DbUrl = temp_post.ap_id.context(location_info!())?;
+ let old_post = blocking(context.pool(), move |conn| {
+ Post::read_from_apub_id(conn, &post_id)
+ })
+ .await??;
+ let stickied = temp_post.stickied.context(location_info!())?;
+ let locked = temp_post.locked.context(location_info!())?;
+ // community mod changed locked/sticky status
+ if (stickied != old_post.stickied) || (locked != old_post.locked) {
+ verify_mod_action(&self.common.actor, community.actor_id(), context).await?;
+ }
+ // user edited their own post
+ else {
+ verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
+ }
+
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let post = Post::from_apub(
+ &self.object,
+ context,
+ self.common.actor.clone(),
+ request_counter,
+ // TODO: we already check here if the mod action is valid, can remove that check param
+ true,
+ )
+ .await?;
+
+ send_websocket_message(post.id, UserOperationCrud::EditPost, context).await
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use crate::activities::{private_message::send_websocket_message, verify_activity, verify_person};
+use activitystreams::{activity::kind::CreateType, base::BaseExt};
+use lemmy_apub::{objects::FromApub, NoteExt};
+use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler};
+use lemmy_db_schema::source::private_message::PrivateMessage;
+use lemmy_utils::LemmyError;
+use lemmy_websocket::{LemmyContext, UserOperationCrud};
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CreatePrivateMessage {
+ to: Url,
+ object: NoteExt,
+ #[serde(rename = "type")]
+ kind: CreateType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for CreatePrivateMessage {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ verify_person(&self.common.actor, context, request_counter).await?;
+ verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let private_message = PrivateMessage::from_apub(
+ &self.object,
+ context,
+ self.common.actor.clone(),
+ request_counter,
+ false,
+ )
+ .await?;
+
+ send_websocket_message(
+ private_message.id,
+ UserOperationCrud::CreatePrivateMessage,
+ context,
+ )
+ .await?;
+
+ Ok(())
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use crate::activities::{private_message::send_websocket_message, verify_activity, verify_person};
+use activitystreams::activity::kind::DeleteType;
+use lemmy_api_common::blocking;
+use lemmy_apub_lib::{verify_domains_match, ActivityCommonFields, ActivityHandler};
+use lemmy_db_queries::{source::private_message::PrivateMessage_, ApubObject};
+use lemmy_db_schema::source::private_message::PrivateMessage;
+use lemmy_utils::LemmyError;
+use lemmy_websocket::{LemmyContext, UserOperationCrud};
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct DeletePrivateMessage {
+ to: Url,
+ pub(in crate::activities::private_message) object: Url,
+ #[serde(rename = "type")]
+ kind: DeleteType,
+ #[serde(flatten)]
+ pub(in crate::activities::private_message) common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for DeletePrivateMessage {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ verify_person(&self.common.actor, context, request_counter).await?;
+ verify_domains_match(&self.common.actor, &self.object)?;
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ _request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let ap_id = self.object.clone();
+ let private_message = blocking(context.pool(), move |conn| {
+ PrivateMessage::read_from_apub_id(conn, &ap_id.into())
+ })
+ .await??;
+ let deleted_private_message = blocking(context.pool(), move |conn| {
+ PrivateMessage::update_deleted(conn, private_message.id, true)
+ })
+ .await??;
+
+ send_websocket_message(
+ deleted_private_message.id,
+ UserOperationCrud::DeletePrivateMessage,
+ context,
+ )
+ .await?;
+
+ Ok(())
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use lemmy_api_common::{blocking, person::PrivateMessageResponse};
+use lemmy_db_schema::PrivateMessageId;
+use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
+use lemmy_utils::LemmyError;
+use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperationCrud};
+
+pub mod create;
+pub mod delete;
+pub mod undo_delete;
+pub mod update;
+
+async fn send_websocket_message(
+ private_message_id: PrivateMessageId,
+ op: UserOperationCrud,
+ context: &LemmyContext,
+) -> Result<(), LemmyError> {
+ let message = blocking(context.pool(), move |conn| {
+ PrivateMessageView::read(conn, private_message_id)
+ })
+ .await??;
+ let res = PrivateMessageResponse {
+ private_message_view: message,
+ };
+
+ // Send notifications to the local recipient, if one exists
+ let recipient_id = res.private_message_view.recipient.id;
+ let local_recipient_id = blocking(context.pool(), move |conn| {
+ LocalUserView::read_person(conn, recipient_id)
+ })
+ .await??
+ .local_user
+ .id;
+
+ context.chat_server().do_send(SendUserRoomMessage {
+ op,
+ response: res,
+ local_recipient_id,
+ websocket_id: None,
+ });
+
+ Ok(())
+}
--- /dev/null
+use crate::activities::{
+ private_message::{delete::DeletePrivateMessage, send_websocket_message},
+ verify_activity,
+ verify_person,
+};
+use activitystreams::activity::kind::UndoType;
+use lemmy_api_common::blocking;
+use lemmy_apub_lib::{
+ verify_domains_match,
+ verify_urls_match,
+ ActivityCommonFields,
+ ActivityHandler,
+};
+use lemmy_db_queries::{source::private_message::PrivateMessage_, ApubObject};
+use lemmy_db_schema::source::private_message::PrivateMessage;
+use lemmy_utils::LemmyError;
+use lemmy_websocket::{LemmyContext, UserOperationCrud};
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct UndoDeletePrivateMessage {
+ to: Url,
+ object: DeletePrivateMessage,
+ #[serde(rename = "type")]
+ kind: UndoType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for UndoDeletePrivateMessage {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ verify_person(&self.common.actor, context, request_counter).await?;
+ verify_urls_match(&self.common.actor, &self.object.common.actor)?;
+ verify_domains_match(&self.common.actor, &self.object.object)?;
+ self.object.verify(context, request_counter).await?;
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ _request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let ap_id = self.object.object.clone();
+ let private_message = blocking(context.pool(), move |conn| {
+ PrivateMessage::read_from_apub_id(conn, &ap_id.into())
+ })
+ .await??;
+
+ let deleted_private_message = blocking(context.pool(), move |conn| {
+ PrivateMessage::update_deleted(conn, private_message.id, false)
+ })
+ .await??;
+
+ send_websocket_message(
+ deleted_private_message.id,
+ UserOperationCrud::EditPrivateMessage,
+ context,
+ )
+ .await?;
+
+ Ok(())
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use crate::activities::{private_message::send_websocket_message, verify_activity, verify_person};
+use activitystreams::{activity::kind::UpdateType, base::BaseExt};
+use lemmy_apub::{objects::FromApub, NoteExt};
+use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler};
+use lemmy_db_schema::source::private_message::PrivateMessage;
+use lemmy_utils::LemmyError;
+use lemmy_websocket::{LemmyContext, UserOperationCrud};
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct UpdatePrivateMessage {
+ to: Url,
+ object: NoteExt,
+ #[serde(rename = "type")]
+ kind: UpdateType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for UpdatePrivateMessage {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ verify_person(&self.common.actor, context, request_counter).await?;
+ verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let private_message = PrivateMessage::from_apub(
+ &self.object,
+ context,
+ self.common.actor.clone(),
+ request_counter,
+ false,
+ )
+ .await?;
+
+ send_websocket_message(
+ private_message.id,
+ UserOperationCrud::EditPrivateMessage,
+ context,
+ )
+ .await?;
+
+ Ok(())
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
+++ /dev/null
-use crate::activities::receive::get_actor_as_person;
-use activitystreams::{
- activity::{ActorAndObjectRefExt, Create, Dislike, Like, Update},
- base::ExtendsExt,
-};
-use anyhow::Context;
-use lemmy_api_common::{blocking, comment::CommentResponse, send_local_notifs};
-use lemmy_apub::{objects::FromApub, ActorType, NoteExt};
-use lemmy_db_queries::{source::comment::Comment_, Crud, Likeable};
-use lemmy_db_schema::source::{
- comment::{Comment, CommentLike, CommentLikeForm},
- post::Post,
-};
-use lemmy_db_views::comment_view::CommentView;
-use lemmy_utils::{location_info, utils::scrape_text_for_mentions, LemmyError};
-use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation, UserOperationCrud};
-
-pub(crate) async fn receive_create_comment(
- create: Create,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let person = get_actor_as_person(&create, context, request_counter).await?;
- let note = NoteExt::from_any_base(create.object().to_owned().one().context(location_info!())?)?
- .context(location_info!())?;
-
- let comment =
- Comment::from_apub(¬e, context, person.actor_id(), request_counter, false).await?;
-
- let post_id = comment.post_id;
- let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
-
- // Note:
- // Although mentions could be gotten from the post tags (they are included there), or the ccs,
- // Its much easier to scrape them from the comment body, since the API has to do that
- // anyway.
- let mentions = scrape_text_for_mentions(&comment.content);
- let recipient_ids = send_local_notifs(
- mentions,
- comment.clone(),
- person,
- post,
- context.pool(),
- true,
- )
- .await?;
-
- // Refetch the view
- let comment_view = blocking(context.pool(), move |conn| {
- CommentView::read(conn, comment.id, None)
- })
- .await??;
-
- let res = CommentResponse {
- comment_view,
- recipient_ids,
- form_id: None,
- };
-
- context.chat_server().do_send(SendComment {
- op: UserOperationCrud::CreateComment,
- comment: res,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_update_comment(
- update: Update,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let note = NoteExt::from_any_base(update.object().to_owned().one().context(location_info!())?)?
- .context(location_info!())?;
- let person = get_actor_as_person(&update, context, request_counter).await?;
-
- let comment =
- Comment::from_apub(¬e, context, person.actor_id(), request_counter, false).await?;
-
- let comment_id = comment.id;
- let post_id = comment.post_id;
- let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
-
- let mentions = scrape_text_for_mentions(&comment.content);
- let recipient_ids =
- send_local_notifs(mentions, comment, person, post, context.pool(), false).await?;
-
- // Refetch the view
- let comment_view = blocking(context.pool(), move |conn| {
- CommentView::read(conn, comment_id, None)
- })
- .await??;
-
- let res = CommentResponse {
- comment_view,
- recipient_ids,
- form_id: None,
- };
-
- context.chat_server().do_send(SendComment {
- op: UserOperationCrud::EditComment,
- comment: res,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_like_comment(
- like: Like,
- comment: Comment,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let person = get_actor_as_person(&like, context, request_counter).await?;
-
- let comment_id = comment.id;
- let like_form = CommentLikeForm {
- comment_id,
- post_id: comment.post_id,
- person_id: person.id,
- score: 1,
- };
- let person_id = person.id;
- blocking(context.pool(), move |conn| {
- CommentLike::remove(conn, person_id, comment_id)?;
- CommentLike::like(conn, &like_form)
- })
- .await??;
-
- // Refetch the view
- let comment_view = blocking(context.pool(), move |conn| {
- CommentView::read(conn, comment_id, None)
- })
- .await??;
-
- // TODO get those recipient actor ids from somewhere
- let recipient_ids = vec![];
- let res = CommentResponse {
- comment_view,
- recipient_ids,
- form_id: None,
- };
-
- context.chat_server().do_send(SendComment {
- op: UserOperation::CreateCommentLike,
- comment: res,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_dislike_comment(
- dislike: Dislike,
- comment: Comment,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let person = get_actor_as_person(&dislike, context, request_counter).await?;
-
- let comment_id = comment.id;
- let like_form = CommentLikeForm {
- comment_id,
- post_id: comment.post_id,
- person_id: person.id,
- score: -1,
- };
- let person_id = person.id;
- blocking(context.pool(), move |conn| {
- CommentLike::remove(conn, person_id, comment_id)?;
- CommentLike::like(conn, &like_form)
- })
- .await??;
-
- // Refetch the view
- let comment_view = blocking(context.pool(), move |conn| {
- CommentView::read(conn, comment_id, None)
- })
- .await??;
-
- // TODO get those recipient actor ids from somewhere
- let recipient_ids = vec![];
- let res = CommentResponse {
- comment_view,
- recipient_ids,
- form_id: None,
- };
-
- context.chat_server().do_send(SendComment {
- op: UserOperation::CreateCommentLike,
- comment: res,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_delete_comment(
- context: &LemmyContext,
- comment: Comment,
-) -> Result<(), LemmyError> {
- let deleted_comment = blocking(context.pool(), move |conn| {
- Comment::update_deleted(conn, comment.id, true)
- })
- .await??;
-
- // Refetch the view
- let comment_id = deleted_comment.id;
- let comment_view = blocking(context.pool(), move |conn| {
- CommentView::read(conn, comment_id, None)
- })
- .await??;
-
- // TODO get those recipient actor ids from somewhere
- let recipient_ids = vec![];
- let res = CommentResponse {
- comment_view,
- recipient_ids,
- form_id: None,
- };
- context.chat_server().do_send(SendComment {
- op: UserOperationCrud::EditComment,
- comment: res,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_remove_comment(
- context: &LemmyContext,
- comment: Comment,
-) -> Result<(), LemmyError> {
- let removed_comment = blocking(context.pool(), move |conn| {
- Comment::update_removed(conn, comment.id, true)
- })
- .await??;
-
- // Refetch the view
- let comment_id = removed_comment.id;
- let comment_view = blocking(context.pool(), move |conn| {
- CommentView::read(conn, comment_id, None)
- })
- .await??;
-
- // TODO get those recipient actor ids from somewhere
- let recipient_ids = vec![];
- let res = CommentResponse {
- comment_view,
- recipient_ids,
- form_id: None,
- };
- context.chat_server().do_send(SendComment {
- op: UserOperationCrud::EditComment,
- comment: res,
- websocket_id: None,
- });
-
- Ok(())
-}
+++ /dev/null
-use crate::activities::receive::get_actor_as_person;
-use activitystreams::activity::{Dislike, Like};
-use lemmy_api_common::{blocking, comment::CommentResponse};
-use lemmy_db_queries::{source::comment::Comment_, Likeable};
-use lemmy_db_schema::source::comment::{Comment, CommentLike};
-use lemmy_db_views::comment_view::CommentView;
-use lemmy_utils::LemmyError;
-use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation, UserOperationCrud};
-
-pub(crate) async fn receive_undo_like_comment(
- like: &Like,
- comment: Comment,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let person = get_actor_as_person(like, context, request_counter).await?;
-
- let comment_id = comment.id;
- let person_id = person.id;
- blocking(context.pool(), move |conn| {
- CommentLike::remove(conn, person_id, comment_id)
- })
- .await??;
-
- // Refetch the view
- let comment_view = blocking(context.pool(), move |conn| {
- CommentView::read(conn, comment_id, None)
- })
- .await??;
-
- // TODO get those recipient actor ids from somewhere
- let recipient_ids = vec![];
- let res = CommentResponse {
- comment_view,
- recipient_ids,
- form_id: None,
- };
-
- context.chat_server().do_send(SendComment {
- op: UserOperation::CreateCommentLike,
- comment: res,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_undo_dislike_comment(
- dislike: &Dislike,
- comment: Comment,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let person = get_actor_as_person(dislike, context, request_counter).await?;
-
- let comment_id = comment.id;
- let person_id = person.id;
- blocking(context.pool(), move |conn| {
- CommentLike::remove(conn, person_id, comment_id)
- })
- .await??;
-
- // Refetch the view
- let comment_view = blocking(context.pool(), move |conn| {
- CommentView::read(conn, comment_id, None)
- })
- .await??;
-
- // TODO get those recipient actor ids from somewhere
- let recipient_ids = vec![];
- let res = CommentResponse {
- comment_view,
- recipient_ids,
- form_id: None,
- };
-
- context.chat_server().do_send(SendComment {
- op: UserOperation::CreateCommentLike,
- comment: res,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_undo_delete_comment(
- context: &LemmyContext,
- comment: Comment,
-) -> Result<(), LemmyError> {
- let deleted_comment = blocking(context.pool(), move |conn| {
- Comment::update_deleted(conn, comment.id, false)
- })
- .await??;
-
- // Refetch the view
- let comment_id = deleted_comment.id;
- let comment_view = blocking(context.pool(), move |conn| {
- CommentView::read(conn, comment_id, None)
- })
- .await??;
-
- // TODO get those recipient actor ids from somewhere
- let recipient_ids = vec![];
- let res = CommentResponse {
- comment_view,
- recipient_ids,
- form_id: None,
- };
-
- context.chat_server().do_send(SendComment {
- op: UserOperationCrud::EditComment,
- comment: res,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_undo_remove_comment(
- context: &LemmyContext,
- comment: Comment,
-) -> Result<(), LemmyError> {
- let removed_comment = blocking(context.pool(), move |conn| {
- Comment::update_removed(conn, comment.id, false)
- })
- .await??;
-
- // Refetch the view
- let comment_id = removed_comment.id;
- let comment_view = blocking(context.pool(), move |conn| {
- CommentView::read(conn, comment_id, None)
- })
- .await??;
-
- // TODO get those recipient actor ids from somewhere
- let recipient_ids = vec![];
- let res = CommentResponse {
- comment_view,
- recipient_ids,
- form_id: None,
- };
-
- context.chat_server().do_send(SendComment {
- op: UserOperationCrud::EditComment,
- comment: res,
- websocket_id: None,
- });
-
- Ok(())
-}
+++ /dev/null
-use crate::{
- activities::receive::get_actor_as_person,
- inbox::receive_for_community::verify_actor_is_community_mod,
-};
-use activitystreams::{
- activity::{ActorAndObjectRefExt, Delete, Undo, Update},
- base::ExtendsExt,
-};
-use anyhow::{anyhow, Context};
-use lemmy_api_common::{blocking, community::CommunityResponse};
-use lemmy_apub::{
- get_community_from_to_or_cc,
- objects::FromApubToForm,
- ActorType,
- CommunityType,
- GroupExt,
-};
-use lemmy_db_queries::{source::community::Community_, Crud};
-use lemmy_db_schema::source::{
- community::{Community, CommunityForm},
- person::Person,
-};
-use lemmy_db_views_actor::{
- community_moderator_view::CommunityModeratorView,
- community_view::CommunityView,
-};
-use lemmy_utils::{location_info, LemmyError};
-use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperationCrud};
-
-/// This activity is received from a remote community mod, and updates the description or other
-/// fields of a local community.
-pub(crate) async fn receive_remote_mod_update_community(
- update: Update,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let community = get_community_from_to_or_cc(&update, context, request_counter).await?;
- verify_actor_is_community_mod(&update, &community, context).await?;
- let group = GroupExt::from_any_base(update.object().to_owned().one().context(location_info!())?)?
- .context(location_info!())?;
- let updated_community = CommunityForm::from_apub(
- &group,
- context,
- community.actor_id(),
- request_counter,
- false,
- )
- .await?;
- let cf = CommunityForm {
- name: updated_community.name,
- title: updated_community.title,
- description: updated_community.description,
- nsfw: updated_community.nsfw,
- // TODO: icon and banner would be hosted on the other instance, ideally we would copy it to ours
- icon: updated_community.icon,
- banner: updated_community.banner,
- ..CommunityForm::default()
- };
- blocking(context.pool(), move |conn| {
- Community::update(conn, community.id, &cf)
- })
- .await??;
-
- Ok(())
-}
-
-pub(crate) async fn receive_remote_mod_delete_community(
- delete: Delete,
- community: Community,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- verify_actor_is_community_mod(&delete, &community, context).await?;
- let actor = get_actor_as_person(&delete, context, request_counter).await?;
- verify_is_remote_community_creator(&actor, &community, context).await?;
- let community_id = community.id;
- blocking(context.pool(), move |conn| {
- Community::update_deleted(conn, community_id, true)
- })
- .await??;
- community.send_delete(actor, context).await
-}
-
-pub(crate) async fn receive_delete_community(
- context: &LemmyContext,
- community: Community,
-) -> Result<(), LemmyError> {
- let deleted_community = blocking(context.pool(), move |conn| {
- Community::update_deleted(conn, community.id, true)
- })
- .await??;
-
- let community_id = deleted_community.id;
- let res = CommunityResponse {
- community_view: blocking(context.pool(), move |conn| {
- CommunityView::read(conn, community_id, None)
- })
- .await??,
- };
-
- let community_id = res.community_view.community.id;
- context.chat_server().do_send(SendCommunityRoomMessage {
- op: UserOperationCrud::EditCommunity,
- response: res,
- community_id,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_remove_community(
- context: &LemmyContext,
- community: Community,
-) -> Result<(), LemmyError> {
- let removed_community = blocking(context.pool(), move |conn| {
- Community::update_removed(conn, community.id, true)
- })
- .await??;
-
- let community_id = removed_community.id;
- let res = CommunityResponse {
- community_view: blocking(context.pool(), move |conn| {
- CommunityView::read(conn, community_id, None)
- })
- .await??,
- };
-
- let community_id = res.community_view.community.id;
- context.chat_server().do_send(SendCommunityRoomMessage {
- op: UserOperationCrud::EditCommunity,
- response: res,
- community_id,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_remote_mod_undo_delete_community(
- undo: Undo,
- community: Community,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- verify_actor_is_community_mod(&undo, &community, context).await?;
- let actor = get_actor_as_person(&undo, context, request_counter).await?;
- verify_is_remote_community_creator(&actor, &community, context).await?;
- let community_id = community.id;
- blocking(context.pool(), move |conn| {
- Community::update_deleted(conn, community_id, false)
- })
- .await??;
- community.send_undo_delete(actor, context).await
-}
-
-pub(crate) async fn receive_undo_delete_community(
- context: &LemmyContext,
- community: Community,
-) -> Result<(), LemmyError> {
- let deleted_community = blocking(context.pool(), move |conn| {
- Community::update_deleted(conn, community.id, false)
- })
- .await??;
-
- let community_id = deleted_community.id;
- let res = CommunityResponse {
- community_view: blocking(context.pool(), move |conn| {
- CommunityView::read(conn, community_id, None)
- })
- .await??,
- };
-
- let community_id = res.community_view.community.id;
- context.chat_server().do_send(SendCommunityRoomMessage {
- op: UserOperationCrud::EditCommunity,
- response: res,
- community_id,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_undo_remove_community(
- context: &LemmyContext,
- community: Community,
-) -> Result<(), LemmyError> {
- let removed_community = blocking(context.pool(), move |conn| {
- Community::update_removed(conn, community.id, false)
- })
- .await??;
-
- let community_id = removed_community.id;
- let res = CommunityResponse {
- community_view: blocking(context.pool(), move |conn| {
- CommunityView::read(conn, community_id, None)
- })
- .await??,
- };
-
- let community_id = res.community_view.community.id;
-
- context.chat_server().do_send(SendCommunityRoomMessage {
- op: UserOperationCrud::EditCommunity,
- response: res,
- community_id,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-/// Checks if the remote user is creator of the local community. This can only happen if a community
-/// is created by a local user, and then transferred to a remote user.
-async fn verify_is_remote_community_creator(
- user: &Person,
- community: &Community,
- context: &LemmyContext,
-) -> Result<(), LemmyError> {
- let community_id = community.id;
- let community_mods = blocking(context.pool(), move |conn| {
- CommunityModeratorView::for_community(conn, community_id)
- })
- .await??;
-
- if user.id != community_mods[0].moderator.id {
- Err(anyhow!("Actor is not community creator").into())
- } else {
- Ok(())
- }
-}
+++ /dev/null
-use activitystreams::{
- activity::{ActorAndObjectRef, ActorAndObjectRefExt},
- base::{AsBase, BaseExt},
- error::DomainError,
-};
-use anyhow::{anyhow, Context};
-use lemmy_apub::fetcher::person::get_or_fetch_and_upsert_person;
-use lemmy_db_schema::source::person::Person;
-use lemmy_utils::{location_info, LemmyError};
-use lemmy_websocket::LemmyContext;
-use log::debug;
-use std::fmt::Debug;
-use url::Url;
-
-pub(crate) mod comment;
-pub(crate) mod comment_undo;
-pub(crate) mod community;
-pub(crate) mod post;
-pub(crate) mod post_undo;
-pub(crate) mod private_message;
-
-/// Return HTTP 501 for unsupported activities in inbox.
-pub(crate) fn receive_unhandled_activity<A>(activity: A) -> Result<(), LemmyError>
-where
- A: Debug,
-{
- debug!("received unhandled activity type: {:?}", activity);
- Err(anyhow!("Activity not supported").into())
-}
-
-/// Reads the actor field of an activity and returns the corresponding `Person`.
-pub(crate) async fn get_actor_as_person<T, A>(
- activity: &T,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<Person, LemmyError>
-where
- T: AsBase<A> + ActorAndObjectRef,
-{
- let actor = activity.actor()?;
- let person_uri = actor.as_single_xsd_any_uri().context(location_info!())?;
- get_or_fetch_and_upsert_person(person_uri, context, request_counter).await
-}
-
-/// Ensure that the ID of an incoming activity comes from the same domain as the actor. Optionally
-/// also checks the ID of the inner object.
-///
-/// The reason that this starts with the actor ID is that it was already confirmed as correct by the
-/// HTTP signature.
-pub(crate) fn verify_activity_domains_valid<T, Kind>(
- activity: &T,
- actor_id: &Url,
- object_domain_must_match: bool,
-) -> Result<(), LemmyError>
-where
- T: AsBase<Kind> + ActorAndObjectRef,
-{
- let expected_domain = actor_id.domain().context(location_info!())?;
-
- activity.id(expected_domain)?;
-
- let object_id = match activity.object().to_owned().single_xsd_any_uri() {
- // object is just an ID
- Some(id) => id,
- // object is something like an activity, a comment or a post
- None => activity
- .object()
- .to_owned()
- .one()
- .context(location_info!())?
- .id()
- .context(location_info!())?
- .to_owned(),
- };
-
- if object_domain_must_match && object_id.domain() != Some(expected_domain) {
- return Err(DomainError.into());
- }
-
- Ok(())
-}
+++ /dev/null
-use crate::{
- activities::receive::get_actor_as_person,
- inbox::receive_for_community::verify_mod_activity,
-};
-use activitystreams::{
- activity::{Announce, Create, Dislike, Like, Update},
- prelude::*,
-};
-use anyhow::Context;
-use lemmy_api_common::{blocking, post::PostResponse};
-use lemmy_apub::{objects::FromApub, ActorType, PageExt};
-use lemmy_db_queries::{source::post::Post_, ApubObject, Crud, Likeable};
-use lemmy_db_schema::{
- source::{
- community::Community,
- post::{Post, PostLike, PostLikeForm},
- },
- DbUrl,
-};
-use lemmy_db_views::post_view::PostView;
-use lemmy_utils::{location_info, LemmyError};
-use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation, UserOperationCrud};
-
-pub(crate) async fn receive_create_post(
- create: Create,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let person = get_actor_as_person(&create, context, request_counter).await?;
- let page = PageExt::from_any_base(create.object().to_owned().one().context(location_info!())?)?
- .context(location_info!())?;
-
- let post = Post::from_apub(&page, context, person.actor_id(), request_counter, false).await?;
-
- // Refetch the view
- let post_id = post.id;
- let post_view = blocking(context.pool(), move |conn| {
- PostView::read(conn, post_id, None)
- })
- .await??;
-
- let res = PostResponse { post_view };
-
- context.chat_server().do_send(SendPost {
- op: UserOperationCrud::CreatePost,
- post: res,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_update_post(
- update: Update,
- announce: Option<Announce>,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let person = get_actor_as_person(&update, context, request_counter).await?;
- let page = PageExt::from_any_base(update.object().to_owned().one().context(location_info!())?)?
- .context(location_info!())?;
-
- let post_id: DbUrl = page
- .id_unchecked()
- .context(location_info!())?
- .to_owned()
- .into();
- let old_post = blocking(context.pool(), move |conn| {
- Post::read_from_apub_id(conn, &post_id)
- })
- .await??;
-
- // If sticked or locked state was changed, make sure the actor is a mod
- let stickied = page.ext_one.stickied.context(location_info!())?;
- let locked = !page.ext_one.comments_enabled.context(location_info!())?;
- let mut mod_action_allowed = false;
- if (stickied != old_post.stickied) || (locked != old_post.locked) {
- let community = blocking(context.pool(), move |conn| {
- Community::read(conn, old_post.community_id)
- })
- .await??;
- // Only check mod status if the community is local, otherwise we trust that it was sent correctly.
- if community.local {
- verify_mod_activity(&update, announce, &community, context).await?;
- }
- mod_action_allowed = true;
- }
-
- let post = Post::from_apub(
- &page,
- context,
- person.actor_id(),
- request_counter,
- mod_action_allowed,
- )
- .await?;
-
- let post_id = post.id;
- // Refetch the view
- let post_view = blocking(context.pool(), move |conn| {
- PostView::read(conn, post_id, None)
- })
- .await??;
-
- let res = PostResponse { post_view };
-
- context.chat_server().do_send(SendPost {
- op: UserOperationCrud::EditPost,
- post: res,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_like_post(
- like: Like,
- post: Post,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let person = get_actor_as_person(&like, context, request_counter).await?;
-
- let post_id = post.id;
- let like_form = PostLikeForm {
- post_id,
- person_id: person.id,
- score: 1,
- };
- let person_id = person.id;
- blocking(context.pool(), move |conn| {
- PostLike::remove(conn, person_id, post_id)?;
- PostLike::like(conn, &like_form)
- })
- .await??;
-
- // Refetch the view
- let post_view = blocking(context.pool(), move |conn| {
- PostView::read(conn, post_id, None)
- })
- .await??;
-
- let res = PostResponse { post_view };
-
- context.chat_server().do_send(SendPost {
- op: UserOperation::CreatePostLike,
- post: res,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_dislike_post(
- dislike: Dislike,
- post: Post,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let person = get_actor_as_person(&dislike, context, request_counter).await?;
-
- let post_id = post.id;
- let like_form = PostLikeForm {
- post_id,
- person_id: person.id,
- score: -1,
- };
- let person_id = person.id;
- blocking(context.pool(), move |conn| {
- PostLike::remove(conn, person_id, post_id)?;
- PostLike::like(conn, &like_form)
- })
- .await??;
-
- // Refetch the view
- let post_view = blocking(context.pool(), move |conn| {
- PostView::read(conn, post_id, None)
- })
- .await??;
-
- let res = PostResponse { post_view };
-
- context.chat_server().do_send(SendPost {
- op: UserOperation::CreatePostLike,
- post: res,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_delete_post(
- context: &LemmyContext,
- post: Post,
-) -> Result<(), LemmyError> {
- let deleted_post = blocking(context.pool(), move |conn| {
- Post::update_deleted(conn, post.id, true)
- })
- .await??;
-
- // Refetch the view
- let post_id = deleted_post.id;
- let post_view = blocking(context.pool(), move |conn| {
- PostView::read(conn, post_id, None)
- })
- .await??;
-
- let res = PostResponse { post_view };
- context.chat_server().do_send(SendPost {
- op: UserOperationCrud::EditPost,
- post: res,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_remove_post(
- context: &LemmyContext,
- post: Post,
-) -> Result<(), LemmyError> {
- let removed_post = blocking(context.pool(), move |conn| {
- Post::update_removed(conn, post.id, true)
- })
- .await??;
-
- // Refetch the view
- let post_id = removed_post.id;
- let post_view = blocking(context.pool(), move |conn| {
- PostView::read(conn, post_id, None)
- })
- .await??;
-
- let res = PostResponse { post_view };
- context.chat_server().do_send(SendPost {
- op: UserOperationCrud::EditPost,
- post: res,
- websocket_id: None,
- });
-
- Ok(())
-}
+++ /dev/null
-use crate::activities::receive::get_actor_as_person;
-use activitystreams::activity::{Dislike, Like};
-use lemmy_api_common::{blocking, post::PostResponse};
-use lemmy_db_queries::{source::post::Post_, Likeable};
-use lemmy_db_schema::source::post::{Post, PostLike};
-use lemmy_db_views::post_view::PostView;
-use lemmy_utils::LemmyError;
-use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation, UserOperationCrud};
-
-pub(crate) async fn receive_undo_like_post(
- like: &Like,
- post: Post,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let person = get_actor_as_person(like, context, request_counter).await?;
-
- let post_id = post.id;
- let person_id = person.id;
- blocking(context.pool(), move |conn| {
- PostLike::remove(conn, person_id, post_id)
- })
- .await??;
-
- // Refetch the view
- let post_view = blocking(context.pool(), move |conn| {
- PostView::read(conn, post_id, None)
- })
- .await??;
-
- let res = PostResponse { post_view };
-
- context.chat_server().do_send(SendPost {
- op: UserOperation::CreatePostLike,
- post: res,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_undo_dislike_post(
- dislike: &Dislike,
- post: Post,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let person = get_actor_as_person(dislike, context, request_counter).await?;
-
- let post_id = post.id;
- let person_id = person.id;
- blocking(context.pool(), move |conn| {
- PostLike::remove(conn, person_id, post_id)
- })
- .await??;
-
- // Refetch the view
- let post_view = blocking(context.pool(), move |conn| {
- PostView::read(conn, post_id, None)
- })
- .await??;
-
- let res = PostResponse { post_view };
-
- context.chat_server().do_send(SendPost {
- op: UserOperation::CreatePostLike,
- post: res,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_undo_delete_post(
- context: &LemmyContext,
- post: Post,
-) -> Result<(), LemmyError> {
- let deleted_post = blocking(context.pool(), move |conn| {
- Post::update_deleted(conn, post.id, false)
- })
- .await??;
-
- // Refetch the view
- let post_id = deleted_post.id;
- let post_view = blocking(context.pool(), move |conn| {
- PostView::read(conn, post_id, None)
- })
- .await??;
-
- let res = PostResponse { post_view };
- context.chat_server().do_send(SendPost {
- op: UserOperationCrud::EditPost,
- post: res,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_undo_remove_post(
- context: &LemmyContext,
- post: Post,
-) -> Result<(), LemmyError> {
- let removed_post = blocking(context.pool(), move |conn| {
- Post::update_removed(conn, post.id, false)
- })
- .await??;
-
- // Refetch the view
- let post_id = removed_post.id;
- let post_view = blocking(context.pool(), move |conn| {
- PostView::read(conn, post_id, None)
- })
- .await??;
-
- let res = PostResponse { post_view };
-
- context.chat_server().do_send(SendPost {
- op: UserOperationCrud::EditPost,
- post: res,
- websocket_id: None,
- });
-
- Ok(())
-}
+++ /dev/null
-use crate::activities::receive::verify_activity_domains_valid;
-use activitystreams::{
- activity::{ActorAndObjectRefExt, Create, Delete, Undo, Update},
- base::{AsBase, ExtendsExt},
- object::AsObject,
- public,
-};
-use anyhow::{anyhow, Context};
-use lemmy_api_common::{blocking, person::PrivateMessageResponse};
-use lemmy_apub::{
- check_is_apub_id_valid,
- fetcher::person::get_or_fetch_and_upsert_person,
- get_activity_to_and_cc,
- objects::FromApub,
- NoteExt,
-};
-use lemmy_db_queries::source::private_message::PrivateMessage_;
-use lemmy_db_schema::source::private_message::PrivateMessage;
-use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
-use lemmy_utils::{location_info, LemmyError};
-use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperationCrud};
-use url::Url;
-
-pub(crate) async fn receive_create_private_message(
- context: &LemmyContext,
- create: Create,
- expected_domain: Url,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- check_private_message_activity_valid(&create, context, request_counter).await?;
-
- let note = NoteExt::from_any_base(
- create
- .object()
- .as_one()
- .context(location_info!())?
- .to_owned(),
- )?
- .context(location_info!())?;
-
- let private_message =
- PrivateMessage::from_apub(¬e, context, expected_domain, request_counter, false).await?;
-
- let message = blocking(context.pool(), move |conn| {
- PrivateMessageView::read(conn, private_message.id)
- })
- .await??;
-
- let res = PrivateMessageResponse {
- private_message_view: message,
- };
-
- // Send notifications to the local recipient, if one exists
- let recipient_id = res.private_message_view.recipient.id;
- let local_recipient_id = blocking(context.pool(), move |conn| {
- LocalUserView::read_person(conn, recipient_id)
- })
- .await??
- .local_user
- .id;
-
- context.chat_server().do_send(SendUserRoomMessage {
- op: UserOperationCrud::CreatePrivateMessage,
- response: res,
- local_recipient_id,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_update_private_message(
- context: &LemmyContext,
- update: Update,
- expected_domain: Url,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- check_private_message_activity_valid(&update, context, request_counter).await?;
-
- let object = update
- .object()
- .as_one()
- .context(location_info!())?
- .to_owned();
- let note = NoteExt::from_any_base(object)?.context(location_info!())?;
-
- let private_message =
- PrivateMessage::from_apub(¬e, context, expected_domain, request_counter, false).await?;
-
- let private_message_id = private_message.id;
- let message = blocking(context.pool(), move |conn| {
- PrivateMessageView::read(conn, private_message_id)
- })
- .await??;
-
- let res = PrivateMessageResponse {
- private_message_view: message,
- };
-
- let recipient_id = res.private_message_view.recipient.id;
- let local_recipient_id = blocking(context.pool(), move |conn| {
- LocalUserView::read_person(conn, recipient_id)
- })
- .await??
- .local_user
- .id;
-
- context.chat_server().do_send(SendUserRoomMessage {
- op: UserOperationCrud::EditPrivateMessage,
- response: res,
- local_recipient_id,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_delete_private_message(
- context: &LemmyContext,
- delete: Delete,
- private_message: PrivateMessage,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- check_private_message_activity_valid(&delete, context, request_counter).await?;
-
- let deleted_private_message = blocking(context.pool(), move |conn| {
- PrivateMessage::update_deleted(conn, private_message.id, true)
- })
- .await??;
-
- let message = blocking(context.pool(), move |conn| {
- PrivateMessageView::read(conn, deleted_private_message.id)
- })
- .await??;
-
- let res = PrivateMessageResponse {
- private_message_view: message,
- };
-
- let recipient_id = res.private_message_view.recipient.id;
- let local_recipient_id = blocking(context.pool(), move |conn| {
- LocalUserView::read_person(conn, recipient_id)
- })
- .await??
- .local_user
- .id;
-
- context.chat_server().do_send(SendUserRoomMessage {
- op: UserOperationCrud::EditPrivateMessage,
- response: res,
- local_recipient_id,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-pub(crate) async fn receive_undo_delete_private_message(
- context: &LemmyContext,
- undo: Undo,
- expected_domain: &Url,
- private_message: PrivateMessage,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- check_private_message_activity_valid(&undo, context, request_counter).await?;
- let object = undo.object().to_owned().one().context(location_info!())?;
- let delete = Delete::from_any_base(object)?.context(location_info!())?;
- verify_activity_domains_valid(&delete, expected_domain, true)?;
- check_private_message_activity_valid(&delete, context, request_counter).await?;
-
- let deleted_private_message = blocking(context.pool(), move |conn| {
- PrivateMessage::update_deleted(conn, private_message.id, false)
- })
- .await??;
-
- let message = blocking(context.pool(), move |conn| {
- PrivateMessageView::read(conn, deleted_private_message.id)
- })
- .await??;
-
- let res = PrivateMessageResponse {
- private_message_view: message,
- };
-
- let recipient_id = res.private_message_view.recipient.id;
- let local_recipient_id = blocking(context.pool(), move |conn| {
- LocalUserView::read_person(conn, recipient_id)
- })
- .await??
- .local_user
- .id;
-
- context.chat_server().do_send(SendUserRoomMessage {
- op: UserOperationCrud::EditPrivateMessage,
- response: res,
- local_recipient_id,
- websocket_id: None,
- });
-
- Ok(())
-}
-
-async fn check_private_message_activity_valid<T, Kind>(
- activity: &T,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<(), LemmyError>
-where
- T: AsBase<Kind> + AsObject<Kind> + ActorAndObjectRefExt,
-{
- let to_and_cc = get_activity_to_and_cc(activity);
- if to_and_cc.len() != 1 {
- return Err(anyhow!("Private message can only be addressed to one person").into());
- }
- if to_and_cc.contains(&public()) {
- return Err(anyhow!("Private message cant be public").into());
- }
- let person_id = activity
- .actor()?
- .to_owned()
- .single_xsd_any_uri()
- .context(location_info!())?;
- check_is_apub_id_valid(&person_id, false)?;
- // check that the sender is a person, not a community
- get_or_fetch_and_upsert_person(&person_id, context, request_counter).await?;
-
- Ok(())
-}
--- /dev/null
+pub mod remove;
+pub mod undo_remove;
--- /dev/null
+use crate::activities::{
+ comment::send_websocket_message as send_comment_message,
+ community::send_websocket_message as send_community_message,
+ post::send_websocket_message as send_post_message,
+ verify_activity,
+ verify_add_remove_moderator_target,
+ verify_mod_action,
+ verify_person_in_community,
+};
+use activitystreams::{activity::kind::RemoveType, base::AnyBase};
+use anyhow::anyhow;
+use lemmy_api_common::blocking;
+use lemmy_apub::{
+ fetcher::{
+ community::get_or_fetch_and_upsert_community,
+ objects::get_or_fetch_and_insert_post_or_comment,
+ person::get_or_fetch_and_upsert_person,
+ },
+ CommunityType,
+ PostOrComment,
+};
+use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_db_queries::{
+ source::{comment::Comment_, community::Community_, post::Post_},
+ Joinable,
+};
+use lemmy_db_schema::source::{
+ comment::Comment,
+ community::{Community, CommunityModerator, CommunityModeratorForm},
+ post::Post,
+};
+use lemmy_utils::LemmyError;
+use lemmy_websocket::{LemmyContext, UserOperationCrud};
+use url::Url;
+
+// TODO: we can probably deduplicate a bunch of code between this and DeletePostCommentOrCommunity
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct RemovePostCommentCommunityOrMod {
+ to: PublicUrl,
+ pub(in crate::activities::removal) object: Url,
+ cc: [Url; 1],
+ #[serde(rename = "type")]
+ kind: RemoveType,
+ // if target is set, this is means remove mod from community
+ target: Option<Url>,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for RemovePostCommentCommunityOrMod {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ let object_community =
+ get_or_fetch_and_upsert_community(&self.object, context, request_counter).await;
+ // removing a community
+ if object_community.is_ok() {
+ verify_mod_action(&self.common.actor, self.object.clone(), context).await?;
+ }
+ // removing community mod
+ else if let Some(target) = &self.target {
+ verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+ verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
+ verify_add_remove_moderator_target(target, self.cc[0].clone())?;
+ }
+ // removing a post or comment
+ else {
+ verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+ verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
+ }
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let object_community =
+ get_or_fetch_and_upsert_community(&self.object, context, request_counter).await;
+ // removing a community
+ if let Ok(community) = object_community {
+ if community.local {
+ return Err(anyhow!("Only local admin can remove community").into());
+ }
+ let deleted_community = blocking(context.pool(), move |conn| {
+ Community::update_removed(conn, community.id, true)
+ })
+ .await??;
+
+ send_community_message(
+ deleted_community.id,
+ UserOperationCrud::RemoveCommunity,
+ context,
+ )
+ .await
+ }
+ // removing community mod
+ else if self.target.is_some() {
+ let community =
+ get_or_fetch_and_upsert_community(&self.cc[0], context, request_counter).await?;
+ let remove_mod =
+ get_or_fetch_and_upsert_person(&self.object, context, request_counter).await?;
+
+ let form = CommunityModeratorForm {
+ community_id: community.id,
+ person_id: remove_mod.id,
+ };
+ blocking(context.pool(), move |conn| {
+ CommunityModerator::leave(conn, &form)
+ })
+ .await??;
+ let anybase = AnyBase::from_arbitrary_json(serde_json::to_string(self)?)?;
+ community
+ .send_announce(anybase, Some(self.object.clone()), context)
+ .await?;
+ // TODO: send websocket notification about removed mod
+ Ok(())
+ }
+ // removing a post or comment
+ else {
+ match get_or_fetch_and_insert_post_or_comment(&self.object, context, request_counter).await? {
+ PostOrComment::Post(post) => {
+ let removed_post = blocking(context.pool(), move |conn| {
+ Post::update_removed(conn, post.id, true)
+ })
+ .await??;
+ send_post_message(removed_post.id, UserOperationCrud::EditPost, context).await
+ }
+ PostOrComment::Comment(comment) => {
+ let removed_comment = blocking(context.pool(), move |conn| {
+ Comment::update_removed(conn, comment.id, true)
+ })
+ .await??;
+ send_comment_message(
+ removed_comment.id,
+ vec![],
+ UserOperationCrud::EditComment,
+ context,
+ )
+ .await
+ }
+ }
+ }
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use crate::activities::{
+ comment::send_websocket_message as send_comment_message,
+ community::send_websocket_message as send_community_message,
+ post::send_websocket_message as send_post_message,
+ removal::remove::RemovePostCommentCommunityOrMod,
+ verify_activity,
+ verify_mod_action,
+ verify_person_in_community,
+};
+use activitystreams::activity::kind::UndoType;
+use anyhow::anyhow;
+use lemmy_api_common::blocking;
+use lemmy_apub::{
+ fetcher::{
+ community::get_or_fetch_and_upsert_community,
+ objects::get_or_fetch_and_insert_post_or_comment,
+ },
+ PostOrComment,
+};
+use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_db_queries::source::{comment::Comment_, community::Community_, post::Post_};
+use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post};
+use lemmy_utils::LemmyError;
+use lemmy_websocket::{LemmyContext, UserOperationCrud};
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct UndoRemovePostCommentOrCommunity {
+ to: PublicUrl,
+ object: RemovePostCommentCommunityOrMod,
+ cc: [Url; 1],
+ #[serde(rename = "type")]
+ kind: UndoType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for UndoRemovePostCommentOrCommunity {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ let object_community =
+ get_or_fetch_and_upsert_community(&self.object.object, context, request_counter).await;
+ // removing a community
+ if object_community.is_ok() {
+ verify_mod_action(&self.common.actor, self.object.object.clone(), context).await?;
+ }
+ // removing a post or comment
+ else {
+ verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+ verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
+ }
+ self.object.verify(context, request_counter).await?;
+ // dont check that actor and object.actor are identical, so that one mod can
+ // undo the action of another
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ let object_community =
+ get_or_fetch_and_upsert_community(&self.object.object, context, request_counter).await;
+ // restoring a community
+ if let Ok(community) = object_community {
+ if community.local {
+ return Err(anyhow!("Only local admin can undo remove community").into());
+ }
+ let deleted_community = blocking(context.pool(), move |conn| {
+ Community::update_removed(conn, community.id, false)
+ })
+ .await??;
+
+ send_community_message(
+ deleted_community.id,
+ UserOperationCrud::EditCommunity,
+ context,
+ )
+ .await
+ }
+ // restoring a post or comment
+ else {
+ match get_or_fetch_and_insert_post_or_comment(&self.object.object, context, request_counter)
+ .await?
+ {
+ PostOrComment::Post(post) => {
+ let removed_post = blocking(context.pool(), move |conn| {
+ Post::update_removed(conn, post.id, false)
+ })
+ .await??;
+ send_post_message(removed_post.id, UserOperationCrud::EditPost, context).await
+ }
+ PostOrComment::Comment(comment) => {
+ let removed_comment = blocking(context.pool(), move |conn| {
+ Comment::update_removed(conn, comment.id, false)
+ })
+ .await??;
+ send_comment_message(
+ removed_comment.id,
+ vec![],
+ UserOperationCrud::EditComment,
+ context,
+ )
+ .await
+ }
+ }
+ }
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use crate::activities::{
+ verify_activity,
+ verify_person_in_community,
+ voting::receive_like_or_dislike,
+};
+use activitystreams::activity::kind::DislikeType;
+use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_utils::LemmyError;
+use lemmy_websocket::LemmyContext;
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct DislikePostOrComment {
+ to: PublicUrl,
+ pub(in crate::activities) object: Url,
+ cc: [Url; 1],
+ #[serde(rename = "type")]
+ kind: DislikeType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for DislikePostOrComment {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ receive_like_or_dislike(
+ -1,
+ &self.common.actor,
+ &self.object,
+ context,
+ request_counter,
+ )
+ .await
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use crate::activities::{
+ verify_activity,
+ verify_person_in_community,
+ voting::receive_like_or_dislike,
+};
+use activitystreams::activity::kind::LikeType;
+use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_utils::LemmyError;
+use lemmy_websocket::LemmyContext;
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct LikePostOrComment {
+ to: PublicUrl,
+ pub(in crate::activities::voting) object: Url,
+ cc: [Url; 1],
+ #[serde(rename = "type")]
+ kind: LikeType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for LikePostOrComment {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ receive_like_or_dislike(
+ 1,
+ &self.common.actor,
+ &self.object,
+ context,
+ request_counter,
+ )
+ .await
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use crate::activities::{
+ comment::send_websocket_message as send_comment_message,
+ post::send_websocket_message as send_post_message,
+};
+use lemmy_api_common::blocking;
+use lemmy_apub::{
+ fetcher::{
+ objects::get_or_fetch_and_insert_post_or_comment,
+ person::get_or_fetch_and_upsert_person,
+ },
+ PostOrComment,
+};
+use lemmy_db_queries::Likeable;
+use lemmy_db_schema::source::{
+ comment::{Comment, CommentLike, CommentLikeForm},
+ post::{Post, PostLike, PostLikeForm},
+};
+use lemmy_utils::LemmyError;
+use lemmy_websocket::{LemmyContext, UserOperation};
+use std::ops::Deref;
+use url::Url;
+
+pub mod dislike;
+pub mod like;
+pub mod undo_dislike;
+pub mod undo_like;
+
+pub(in crate::activities::voting) async fn receive_like_or_dislike(
+ score: i16,
+ actor: &Url,
+ object: &Url,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+) -> Result<(), LemmyError> {
+ match get_or_fetch_and_insert_post_or_comment(object, context, request_counter).await? {
+ PostOrComment::Post(p) => {
+ like_or_dislike_post(score, actor, p.deref(), context, request_counter).await
+ }
+ PostOrComment::Comment(c) => {
+ like_or_dislike_comment(score, actor, c.deref(), context, request_counter).await
+ }
+ }
+}
+
+async fn like_or_dislike_comment(
+ score: i16,
+ actor: &Url,
+ comment: &Comment,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+) -> Result<(), LemmyError> {
+ let actor = get_or_fetch_and_upsert_person(actor, context, request_counter).await?;
+
+ let comment_id = comment.id;
+ let like_form = CommentLikeForm {
+ comment_id,
+ post_id: comment.post_id,
+ person_id: actor.id,
+ score,
+ };
+ let person_id = actor.id;
+ blocking(context.pool(), move |conn| {
+ CommentLike::remove(conn, person_id, comment_id)?;
+ CommentLike::like(conn, &like_form)
+ })
+ .await??;
+
+ send_comment_message(
+ comment_id,
+ vec![],
+ UserOperation::CreateCommentLike,
+ context,
+ )
+ .await
+}
+
+async fn like_or_dislike_post(
+ score: i16,
+ actor: &Url,
+ post: &Post,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+) -> Result<(), LemmyError> {
+ let actor = get_or_fetch_and_upsert_person(actor, context, request_counter).await?;
+
+ let post_id = post.id;
+ let like_form = PostLikeForm {
+ post_id: post.id,
+ person_id: actor.id,
+ score,
+ };
+ let person_id = actor.id;
+ blocking(context.pool(), move |conn| {
+ PostLike::remove(conn, person_id, post_id)?;
+ PostLike::like(conn, &like_form)
+ })
+ .await??;
+
+ send_post_message(post.id, UserOperation::CreatePostLike, context).await
+}
+
+pub(in crate::activities::voting) async fn receive_undo_like_or_dislike(
+ actor: &Url,
+ object: &Url,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+) -> Result<(), LemmyError> {
+ match get_or_fetch_and_insert_post_or_comment(object, context, request_counter).await? {
+ PostOrComment::Post(p) => {
+ undo_like_or_dislike_post(actor, p.deref(), context, request_counter).await
+ }
+ PostOrComment::Comment(c) => {
+ undo_like_or_dislike_comment(actor, c.deref(), context, request_counter).await
+ }
+ }
+}
+
+async fn undo_like_or_dislike_comment(
+ actor: &Url,
+ comment: &Comment,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+) -> Result<(), LemmyError> {
+ let actor = get_or_fetch_and_upsert_person(actor, context, request_counter).await?;
+
+ let comment_id = comment.id;
+ let person_id = actor.id;
+ blocking(context.pool(), move |conn| {
+ CommentLike::remove(conn, person_id, comment_id)
+ })
+ .await??;
+
+ send_comment_message(
+ comment.id,
+ vec![],
+ UserOperation::CreateCommentLike,
+ context,
+ )
+ .await
+}
+
+async fn undo_like_or_dislike_post(
+ actor: &Url,
+ post: &Post,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+) -> Result<(), LemmyError> {
+ let actor = get_or_fetch_and_upsert_person(actor, context, request_counter).await?;
+
+ let post_id = post.id;
+ let person_id = actor.id;
+ blocking(context.pool(), move |conn| {
+ PostLike::remove(conn, person_id, post_id)
+ })
+ .await??;
+ send_post_message(post.id, UserOperation::CreatePostLike, context).await
+}
--- /dev/null
+use crate::activities::{
+ verify_activity,
+ verify_person_in_community,
+ voting::{dislike::DislikePostOrComment, receive_undo_like_or_dislike},
+};
+use activitystreams::activity::kind::UndoType;
+use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_utils::LemmyError;
+use lemmy_websocket::LemmyContext;
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct UndoDislikePostOrComment {
+ to: PublicUrl,
+ object: DislikePostOrComment,
+ cc: [Url; 1],
+ #[serde(rename = "type")]
+ kind: UndoType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for UndoDislikePostOrComment {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+ verify_urls_match(&self.common.actor, &self.object.common().actor)?;
+ self.object.verify(context, request_counter).await?;
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ receive_undo_like_or_dislike(
+ &self.common.actor,
+ &self.object.object,
+ context,
+ request_counter,
+ )
+ .await
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
--- /dev/null
+use crate::activities::{
+ verify_activity,
+ verify_person_in_community,
+ voting::{like::LikePostOrComment, receive_undo_like_or_dislike},
+};
+use activitystreams::activity::kind::UndoType;
+use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_utils::LemmyError;
+use lemmy_websocket::LemmyContext;
+use url::Url;
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct UndoLikePostOrComment {
+ to: PublicUrl,
+ object: LikePostOrComment,
+ cc: [Url; 1],
+ #[serde(rename = "type")]
+ kind: UndoType,
+ #[serde(flatten)]
+ common: ActivityCommonFields,
+}
+
+#[async_trait::async_trait(?Send)]
+impl ActivityHandler for UndoLikePostOrComment {
+ async fn verify(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ verify_activity(self.common())?;
+ verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+ verify_urls_match(&self.common.actor, &self.object.common().actor)?;
+ self.object.verify(context, request_counter).await?;
+ Ok(())
+ }
+
+ async fn receive(
+ &self,
+ context: &LemmyContext,
+ request_counter: &mut i32,
+ ) -> Result<(), LemmyError> {
+ receive_undo_like_or_dislike(
+ &self.common.actor,
+ &self.object.object,
+ context,
+ request_counter,
+ )
+ .await
+ }
+
+ fn common(&self) -> &ActivityCommonFields {
+ &self.common
+ }
+}
-use crate::http::{create_apub_response, create_apub_tombstone_response};
+use crate::http::{
+ create_apub_response,
+ create_apub_tombstone_response,
+ inbox_enums::GroupInboxActivities,
+ payload_to_string,
+ receive_activity,
+};
use activitystreams::{
base::{AnyBase, BaseExt},
collection::{CollectionExt, OrderedCollection, UnorderedCollection},
url::Url,
};
-use actix_web::{body::Body, web, HttpResponse};
+use actix_web::{body::Body, web, web::Payload, HttpRequest, HttpResponse};
use lemmy_api_common::blocking;
use lemmy_apub::{
extensions::context::lemmy_context,
}
}
+/// Handler for all incoming receive to community inboxes.
+pub async fn community_inbox(
+ request: HttpRequest,
+ payload: Payload,
+ _path: web::Path<String>,
+ context: web::Data<LemmyContext>,
+) -> Result<HttpResponse, LemmyError> {
+ let unparsed = payload_to_string(payload).await?;
+ receive_activity::<GroupInboxActivities>(request, &unparsed, context).await
+}
+
/// Returns an empty followers collection, only populating the size (for privacy).
pub(crate) async fn get_apub_community_followers(
info: web::Path<CommunityQuery>,
--- /dev/null
+use crate::activities::{
+ comment::{create::CreateComment, update::UpdateComment},
+ community::{
+ add_mod::AddMod,
+ announce::AnnounceActivity,
+ block_user::BlockUserFromCommunity,
+ undo_block_user::UndoBlockUserFromCommunity,
+ update::UpdateCommunity,
+ },
+ deletion::{delete::DeletePostCommentOrCommunity, undo_delete::UndoDeletePostCommentOrCommunity},
+ following::{accept::AcceptFollowCommunity, follow::FollowCommunity, undo::UndoFollowCommunity},
+ post::{create::CreatePost, update::UpdatePost},
+ private_message::{
+ create::CreatePrivateMessage,
+ delete::DeletePrivateMessage,
+ undo_delete::UndoDeletePrivateMessage,
+ update::UpdatePrivateMessage,
+ },
+ removal::{
+ remove::RemovePostCommentCommunityOrMod,
+ undo_remove::UndoRemovePostCommentOrCommunity,
+ },
+ voting::{
+ dislike::DislikePostOrComment,
+ like::LikePostOrComment,
+ undo_dislike::UndoDislikePostOrComment,
+ undo_like::UndoLikePostOrComment,
+ },
+};
+use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler};
+use lemmy_utils::LemmyError;
+use lemmy_websocket::LemmyContext;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)]
+#[serde(untagged)]
+pub enum PersonInboxActivities {
+ AcceptFollowCommunity(AcceptFollowCommunity),
+ CreatePrivateMessage(CreatePrivateMessage),
+ UpdatePrivateMessage(UpdatePrivateMessage),
+ DeletePrivateMessage(DeletePrivateMessage),
+ UndoDeletePrivateMessage(UndoDeletePrivateMessage),
+ AnnounceActivity(Box<AnnounceActivity>),
+}
+
+#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)]
+#[serde(untagged)]
+pub enum GroupInboxActivities {
+ FollowCommunity(FollowCommunity),
+ UndoFollowCommunity(UndoFollowCommunity),
+ CreateComment(CreateComment),
+ UpdateComment(UpdateComment),
+ CreatePost(CreatePost),
+ UpdatePost(UpdatePost),
+ LikePostOrComment(LikePostOrComment),
+ DislikePostOrComment(DislikePostOrComment),
+ UndoLikePostOrComment(UndoLikePostOrComment),
+ UndoDislikePostOrComment(UndoDislikePostOrComment),
+ DeletePostCommentOrCommunity(DeletePostCommentOrCommunity),
+ UndoDeletePostCommentOrCommunity(UndoDeletePostCommentOrCommunity),
+ RemovePostCommentOrCommunity(RemovePostCommentCommunityOrMod),
+ UndoRemovePostCommentOrCommunity(UndoRemovePostCommentOrCommunity),
+ UpdateCommunity(Box<UpdateCommunity>),
+ BlockUserFromCommunity(BlockUserFromCommunity),
+ UndoBlockUserFromCommunity(UndoBlockUserFromCommunity),
+ AddMod(AddMod),
+}
+
+#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)]
+#[serde(untagged)]
+pub enum SharedInboxActivities {
+ // received by group
+ FollowCommunity(FollowCommunity),
+ UndoFollowCommunity(UndoFollowCommunity),
+ CreateComment(CreateComment),
+ UpdateComment(UpdateComment),
+ CreatePost(CreatePost),
+ UpdatePost(UpdatePost),
+ LikePostOrComment(LikePostOrComment),
+ DislikePostOrComment(DislikePostOrComment),
+ UndoDislikePostOrComment(UndoDislikePostOrComment),
+ UndoLikePostOrComment(UndoLikePostOrComment),
+ DeletePostCommentOrCommunity(DeletePostCommentOrCommunity),
+ UndoDeletePostCommentOrCommunity(UndoDeletePostCommentOrCommunity),
+ RemovePostCommentOrCommunity(RemovePostCommentCommunityOrMod),
+ UndoRemovePostCommentOrCommunity(UndoRemovePostCommentOrCommunity),
+ UpdateCommunity(Box<UpdateCommunity>),
+ BlockUserFromCommunity(BlockUserFromCommunity),
+ UndoBlockUserFromCommunity(UndoBlockUserFromCommunity),
+ AddMod(AddMod),
+ // received by person
+ AcceptFollowCommunity(AcceptFollowCommunity),
+ // Note, pm activities need to be at the end, otherwise comments will end up here. We can probably
+ // avoid this problem by replacing createpm.object with our own struct, instead of NoteExt.
+ CreatePrivateMessage(CreatePrivateMessage),
+ UpdatePrivateMessage(UpdatePrivateMessage),
+ DeletePrivateMessage(DeletePrivateMessage),
+ UndoDeletePrivateMessage(UndoDeletePrivateMessage),
+ AnnounceActivity(Box<AnnounceActivity>),
+}
-use actix_web::{body::Body, web, HttpResponse};
+use crate::http::inbox_enums::SharedInboxActivities;
+use actix_web::{
+ body::Body,
+ web,
+ web::{Bytes, BytesMut, Payload},
+ HttpRequest,
+ HttpResponse,
+};
+use anyhow::{anyhow, Context};
+use futures::StreamExt;
use http::StatusCode;
use lemmy_api_common::blocking;
-use lemmy_apub::APUB_JSON_CONTENT_TYPE;
-use lemmy_db_queries::source::activity::Activity_;
+use lemmy_apub::{
+ check_is_apub_id_valid,
+ extensions::signatures::verify_signature,
+ fetcher::get_or_fetch_and_upsert_actor,
+ insert_activity,
+ APUB_JSON_CONTENT_TYPE,
+};
+use lemmy_apub_lib::ActivityHandler;
+use lemmy_db_queries::{source::activity::Activity_, DbPool};
use lemmy_db_schema::source::activity::Activity;
-use lemmy_utils::{settings::structs::Settings, LemmyError};
+use lemmy_utils::{location_info, settings::structs::Settings, LemmyError};
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
+use std::{fmt::Debug, io::Read};
use url::Url;
-pub mod comment;
-pub mod community;
-pub mod person;
-pub mod post;
+mod comment;
+mod community;
+mod inbox_enums;
+mod person;
+mod post;
+pub mod routes;
+
+pub async fn shared_inbox(
+ request: HttpRequest,
+ payload: Payload,
+ context: web::Data<LemmyContext>,
+) -> Result<HttpResponse, LemmyError> {
+ let unparsed = payload_to_string(payload).await?;
+ receive_activity::<SharedInboxActivities>(request, &unparsed, context).await
+}
+
+async fn payload_to_string(mut payload: Payload) -> Result<String, LemmyError> {
+ let mut bytes = BytesMut::new();
+ while let Some(item) = payload.next().await {
+ bytes.extend_from_slice(&item?);
+ }
+ let mut unparsed = String::new();
+ Bytes::from(bytes).as_ref().read_to_string(&mut unparsed)?;
+ Ok(unparsed)
+}
+
+// TODO: move most of this code to library
+async fn receive_activity<'a, T>(
+ request: HttpRequest,
+ activity: &'a str,
+ context: web::Data<LemmyContext>,
+) -> Result<HttpResponse, LemmyError>
+where
+ T: ActivityHandler + Clone + Deserialize<'a> + Serialize + std::fmt::Debug + Send + 'static,
+{
+ let activity = serde_json::from_str::<T>(activity)?;
+ let activity_data = activity.common();
+
+ let request_counter = &mut 0;
+ let actor =
+ get_or_fetch_and_upsert_actor(&activity_data.actor, &context, request_counter).await?;
+ verify_signature(&request, &actor.public_key().context(location_info!())?)?;
+
+ // Do nothing if we received the same activity before
+ if is_activity_already_known(context.pool(), activity_data.id_unchecked()).await? {
+ return Ok(HttpResponse::Ok().finish());
+ }
+ check_is_apub_id_valid(&activity_data.actor, false)?;
+ println!(
+ "Verifying activity {}",
+ activity_data.id_unchecked().to_string()
+ );
+ activity.verify(&context, request_counter).await?;
+ assert_activity_not_local(&activity)?;
+
+ // Log the activity, so we avoid receiving and parsing it twice. Note that this could still happen
+ // if we receive the same activity twice in very quick succession.
+ insert_activity(
+ activity_data.id_unchecked(),
+ activity.clone(),
+ false,
+ true,
+ context.pool(),
+ )
+ .await?;
+
+ println!(
+ "Receiving activity {}",
+ activity_data.id_unchecked().to_string()
+ );
+ activity.receive(&context, request_counter).await?;
+ Ok(HttpResponse::Ok().finish())
+}
/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
/// headers.
}
#[derive(Deserialize)]
-pub struct CommunityQuery {
+pub struct ActivityQuery {
type_: String,
id: String,
}
-/// Return the ActivityPub json representation of a local community over HTTP.
+/// Return the ActivityPub json representation of a local activity over HTTP.
pub(crate) async fn get_activity(
- info: web::Path<CommunityQuery>,
+ info: web::Path<ActivityQuery>,
context: web::Data<LemmyContext>,
) -> Result<HttpResponse<Body>, LemmyError> {
let settings = Settings::get();
Ok(create_apub_response(&activity.data))
}
}
+
+pub(crate) async fn is_activity_already_known(
+ pool: &DbPool,
+ activity_id: &Url,
+) -> Result<bool, LemmyError> {
+ let activity_id = activity_id.to_owned().into();
+ let existing = blocking(pool, move |conn| {
+ Activity::read_from_apub_id(conn, &activity_id)
+ })
+ .await?;
+ match existing {
+ Ok(_) => Ok(true),
+ Err(_) => Ok(false),
+ }
+}
+
+fn assert_activity_not_local<T: Debug + ActivityHandler>(activity: &T) -> Result<(), LemmyError> {
+ let activity_domain = activity
+ .common()
+ .id_unchecked()
+ .domain()
+ .context(location_info!())?;
+
+ if activity_domain == Settings::get().hostname() {
+ return Err(
+ anyhow!(
+ "Error: received activity which was sent by local instance: {:?}",
+ activity
+ )
+ .into(),
+ );
+ }
+ Ok(())
+}
-use crate::http::{create_apub_response, create_apub_tombstone_response};
+use crate::http::{
+ create_apub_response,
+ create_apub_tombstone_response,
+ inbox_enums::PersonInboxActivities,
+ payload_to_string,
+ receive_activity,
+};
use activitystreams::{
base::BaseExt,
collection::{CollectionExt, OrderedCollection},
};
-use actix_web::{body::Body, web, HttpResponse};
+use actix_web::{body::Body, web, web::Payload, HttpRequest, HttpResponse};
use lemmy_api_common::blocking;
use lemmy_apub::{extensions::context::lemmy_context, objects::ToApub, ActorType};
use lemmy_db_queries::source::person::Person_;
}
}
+pub async fn person_inbox(
+ request: HttpRequest,
+ payload: Payload,
+ _path: web::Path<String>,
+ context: web::Data<LemmyContext>,
+) -> Result<HttpResponse, LemmyError> {
+ let unparsed = payload_to_string(payload).await?;
+ receive_activity::<PersonInboxActivities>(request, &unparsed, context).await
+}
+
pub(crate) async fn get_apub_person_outbox(
info: web::Path<PersonQuery>,
context: web::Data<LemmyContext>,
-use crate::{
- http::{
- comment::get_apub_comment,
- community::{
- get_apub_community_followers,
- get_apub_community_http,
- get_apub_community_inbox,
- get_apub_community_moderators,
- get_apub_community_outbox,
- },
- get_activity,
- person::{get_apub_person_http, get_apub_person_inbox, get_apub_person_outbox},
- post::get_apub_post,
- },
- inbox::{
- community_inbox::community_inbox,
- person_inbox::person_inbox,
- shared_inbox::shared_inbox,
+use crate::http::{
+ comment::get_apub_comment,
+ community::{
+ community_inbox,
+ get_apub_community_followers,
+ get_apub_community_http,
+ get_apub_community_inbox,
+ get_apub_community_moderators,
+ get_apub_community_outbox,
},
+ get_activity,
+ person::{get_apub_person_http, get_apub_person_inbox, get_apub_person_outbox, person_inbox},
+ post::get_apub_post,
+ shared_inbox,
};
use actix_web::*;
use http_signature_normalization_actix::digest::middleware::VerifyDigest;
+++ /dev/null
-use crate::{
- activities::receive::verify_activity_domains_valid,
- inbox::{
- assert_activity_not_local,
- get_activity_id,
- inbox_verify_http_signature,
- is_activity_already_known,
- receive_for_community::{
- receive_add_for_community,
- receive_block_user_for_community,
- receive_create_for_community,
- receive_delete_for_community,
- receive_dislike_for_community,
- receive_like_for_community,
- receive_remove_for_community,
- receive_undo_for_community,
- receive_update_for_community,
- },
- verify_is_addressed_to_public,
- },
-};
-use activitystreams::{
- activity::{kind::FollowType, ActorAndObject, Follow, Undo},
- base::AnyBase,
- prelude::*,
-};
-use actix_web::{web, HttpRequest, HttpResponse};
-use anyhow::{anyhow, Context};
-use lemmy_api_common::blocking;
-use lemmy_apub::{
- check_community_or_site_ban,
- get_activity_to_and_cc,
- insert_activity,
- ActorType,
- CommunityType,
-};
-use lemmy_db_queries::{source::community::Community_, ApubObject, Followable};
-use lemmy_db_schema::source::{
- community::{Community, CommunityFollower, CommunityFollowerForm},
- person::Person,
-};
-use lemmy_utils::{location_info, LemmyError};
-use lemmy_websocket::LemmyContext;
-use log::info;
-use serde::{Deserialize, Serialize};
-use std::fmt::Debug;
-use url::Url;
-
-/// Allowed activities for community inbox.
-#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
-#[serde(rename_all = "PascalCase")]
-pub enum CommunityValidTypes {
- Follow, // follow request from a person
- Undo, // unfollow from a person
- Create, // create post or comment
- Update, // update post or comment
- Like, // upvote post or comment
- Dislike, // downvote post or comment
- Delete, // post or comment deleted by creator
- Remove, // post or comment removed by mod or admin, or mod removed from community
- Add, // mod added to community
- Block, // user blocked by community
-}
-
-pub type CommunityAcceptedActivities = ActorAndObject<CommunityValidTypes>;
-
-/// Handler for all incoming receive to community inboxes.
-pub async fn community_inbox(
- request: HttpRequest,
- input: web::Json<CommunityAcceptedActivities>,
- path: web::Path<String>,
- context: web::Data<LemmyContext>,
-) -> Result<HttpResponse, LemmyError> {
- let activity = input.into_inner();
- // First of all check the http signature
- let request_counter = &mut 0;
- let actor = inbox_verify_http_signature(&activity, &context, request, request_counter).await?;
-
- // Do nothing if we received the same activity before
- let activity_id = get_activity_id(&activity, &actor.actor_id())?;
- if is_activity_already_known(context.pool(), &activity_id).await? {
- return Ok(HttpResponse::Ok().finish());
- }
-
- // Check if the activity is actually meant for us
- let path = path.into_inner();
- let community = blocking(context.pool(), move |conn| {
- Community::read_from_name(conn, &path)
- })
- .await??;
- let to_and_cc = get_activity_to_and_cc(&activity);
- if !to_and_cc.contains(&community.actor_id()) {
- return Err(anyhow!("Activity delivered to wrong community").into());
- }
-
- assert_activity_not_local(&activity)?;
- insert_activity(&activity_id, activity.clone(), false, true, context.pool()).await?;
-
- community_receive_message(
- activity.clone(),
- community.clone(),
- actor.as_ref(),
- &context,
- request_counter,
- )
- .await
-}
-
-/// Receives Follow, Undo/Follow, post actions, comment actions (including votes)
-pub(crate) async fn community_receive_message(
- activity: CommunityAcceptedActivities,
- to_community: Community,
- actor: &dyn ActorType,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<HttpResponse, LemmyError> {
- // Only persons can send activities to the community, so we can get the actor as person
- // unconditionally.
- let actor_id = actor.actor_id();
- let person = blocking(context.pool(), move |conn| {
- Person::read_from_apub_id(conn, &actor_id.into())
- })
- .await??;
- check_community_or_site_ban(&person, to_community.id, context.pool()).await?;
-
- info!(
- "Community {} received activity {} from {}",
- to_community.name,
- &activity
- .id_unchecked()
- .context(location_info!())?
- .to_string(),
- &person.actor_id().to_string()
- );
-
- let any_base = activity.clone().into_any_base()?;
- let actor_url = actor.actor_id();
- let activity_kind = activity.kind().context(location_info!())?;
- let do_announce = match activity_kind {
- CommunityValidTypes::Follow => {
- Box::pin(handle_follow(
- any_base.clone(),
- person,
- &to_community,
- context,
- ))
- .await?;
- false
- }
- CommunityValidTypes::Undo => {
- Box::pin(handle_undo(
- context,
- activity.clone(),
- actor_url,
- &to_community,
- request_counter,
- ))
- .await?
- }
- CommunityValidTypes::Create => {
- Box::pin(receive_create_for_community(
- context,
- any_base.clone(),
- &actor_url,
- request_counter,
- ))
- .await?;
- true
- }
- CommunityValidTypes::Update => {
- Box::pin(receive_update_for_community(
- context,
- any_base.clone(),
- None,
- &actor_url,
- request_counter,
- ))
- .await?;
- true
- }
- CommunityValidTypes::Like => {
- Box::pin(receive_like_for_community(
- context,
- any_base.clone(),
- &actor_url,
- request_counter,
- ))
- .await?;
- true
- }
- CommunityValidTypes::Dislike => {
- Box::pin(receive_dislike_for_community(
- context,
- any_base.clone(),
- &actor_url,
- request_counter,
- ))
- .await?;
- true
- }
- CommunityValidTypes::Delete => {
- Box::pin(receive_delete_for_community(
- context,
- any_base.clone(),
- None,
- &actor_url,
- request_counter,
- ))
- .await?;
- true
- }
- CommunityValidTypes::Add => {
- Box::pin(receive_add_for_community(
- context,
- any_base.clone(),
- None,
- request_counter,
- ))
- .await?;
- true
- }
- CommunityValidTypes::Remove => {
- Box::pin(receive_remove_for_community(
- context,
- any_base.clone(),
- None,
- request_counter,
- ))
- .await?;
- true
- }
- CommunityValidTypes::Block => {
- Box::pin(receive_block_user_for_community(
- context,
- any_base.clone(),
- None,
- request_counter,
- ))
- .await?;
- true
- }
- };
-
- if do_announce {
- // Check again that the activity is public, just to be sure
- verify_is_addressed_to_public(&activity)?;
- let mut object_actor = activity.object().clone().single_xsd_any_uri();
- // If activity is something like Undo/Block, we need to access activity.object.object
- if object_actor.is_none() {
- object_actor = activity
- .object()
- .as_one()
- .map(|a| ActorAndObject::from_any_base(a.to_owned()).ok())
- .flatten()
- .flatten()
- .map(|a: ActorAndObject<CommunityValidTypes>| a.object().to_owned().single_xsd_any_uri())
- .flatten();
- }
- to_community
- .send_announce(activity.into_any_base()?, object_actor, context)
- .await?;
- }
-
- Ok(HttpResponse::Ok().finish())
-}
-
-/// Handle a follow request from a remote person, adding the person as follower and returning an
-/// Accept activity.
-async fn handle_follow(
- activity: AnyBase,
- person: Person,
- community: &Community,
- context: &LemmyContext,
-) -> Result<HttpResponse, LemmyError> {
- let follow = Follow::from_any_base(activity)?.context(location_info!())?;
- verify_activity_domains_valid(&follow, &person.actor_id(), false)?;
-
- let community_follower_form = CommunityFollowerForm {
- community_id: community.id,
- person_id: person.id,
- pending: false,
- };
-
- // This will fail if they're already a follower, but ignore the error.
- blocking(context.pool(), move |conn| {
- CommunityFollower::follow(conn, &community_follower_form).ok()
- })
- .await?;
-
- community.send_accept_follow(follow, context).await?;
-
- Ok(HttpResponse::Ok().finish())
-}
-
-async fn handle_undo(
- context: &LemmyContext,
- activity: CommunityAcceptedActivities,
- actor_url: Url,
- to_community: &Community,
- request_counter: &mut i32,
-) -> Result<bool, LemmyError> {
- let inner_kind = activity
- .object()
- .is_single_kind(&FollowType::Follow.to_string());
- let any_base = activity.into_any_base()?;
- if inner_kind {
- handle_undo_follow(any_base, actor_url, to_community, context).await?;
- Ok(false)
- } else {
- receive_undo_for_community(context, any_base, None, &actor_url, request_counter).await?;
- Ok(true)
- }
-}
-
-/// Handle `Undo/Follow` from a person, removing the person from followers list.
-async fn handle_undo_follow(
- activity: AnyBase,
- person_url: Url,
- community: &Community,
- context: &LemmyContext,
-) -> Result<(), LemmyError> {
- let undo = Undo::from_any_base(activity)?.context(location_info!())?;
- verify_activity_domains_valid(&undo, &person_url, true)?;
-
- let object = undo.object().to_owned().one().context(location_info!())?;
- let follow = Follow::from_any_base(object)?.context(location_info!())?;
- verify_activity_domains_valid(&follow, &person_url, false)?;
-
- let person = blocking(context.pool(), move |conn| {
- Person::read_from_apub_id(conn, &person_url.into())
- })
- .await??;
- let community_follower_form = CommunityFollowerForm {
- community_id: community.id,
- person_id: person.id,
- pending: false,
- };
-
- // This will fail if they aren't a follower, but ignore the error.
- blocking(context.pool(), move |conn| {
- CommunityFollower::unfollow(conn, &community_follower_form).ok()
- })
- .await?;
-
- Ok(())
-}
+++ /dev/null
-use activitystreams::{
- activity::ActorAndObjectRefExt,
- base::{AsBase, BaseExt, Extends},
- object::AsObject,
- public,
-};
-use actix_web::HttpRequest;
-use anyhow::{anyhow, Context};
-use lemmy_api_common::blocking;
-use lemmy_apub::{
- check_is_apub_id_valid,
- extensions::signatures::verify_signature,
- fetcher::get_or_fetch_and_upsert_actor,
- get_activity_to_and_cc,
- ActorType,
-};
-use lemmy_db_queries::{
- source::{activity::Activity_, community::Community_},
- ApubObject,
- DbPool,
-};
-use lemmy_db_schema::source::{activity::Activity, community::Community, person::Person};
-use lemmy_utils::{location_info, settings::structs::Settings, LemmyError};
-use lemmy_websocket::LemmyContext;
-use serde::Serialize;
-use std::fmt::Debug;
-use url::Url;
-
-pub mod community_inbox;
-pub mod person_inbox;
-pub(crate) mod receive_for_community;
-pub mod shared_inbox;
-
-pub(crate) fn get_activity_id<T, Kind>(activity: &T, creator_uri: &Url) -> Result<Url, LemmyError>
-where
- T: BaseExt<Kind> + Extends<Kind> + Debug,
- Kind: Serialize,
- <T as Extends<Kind>>::Error: From<serde_json::Error> + Send + Sync + 'static,
-{
- let creator_domain = creator_uri.host_str().context(location_info!())?;
- let activity_id = activity.id(creator_domain)?;
- Ok(activity_id.context(location_info!())?.to_owned())
-}
-
-pub(crate) async fn is_activity_already_known(
- pool: &DbPool,
- activity_id: &Url,
-) -> Result<bool, LemmyError> {
- let activity_id = activity_id.to_owned().into();
- let existing = blocking(pool, move |conn| {
- Activity::read_from_apub_id(conn, &activity_id)
- })
- .await?;
- match existing {
- Ok(_) => Ok(true),
- Err(_) => Ok(false),
- }
-}
-
-pub(crate) fn verify_is_addressed_to_public<T, Kind>(activity: &T) -> Result<(), LemmyError>
-where
- T: AsBase<Kind> + AsObject<Kind> + ActorAndObjectRefExt,
-{
- let to_and_cc = get_activity_to_and_cc(activity);
- if to_and_cc.contains(&public()) {
- Ok(())
- } else {
- Err(anyhow!("Activity is not addressed to public").into())
- }
-}
-
-pub(crate) async fn inbox_verify_http_signature<T, Kind>(
- activity: &T,
- context: &LemmyContext,
- request: HttpRequest,
- request_counter: &mut i32,
-) -> Result<Box<dyn ActorType>, LemmyError>
-where
- T: AsObject<Kind> + ActorAndObjectRefExt + Extends<Kind> + AsBase<Kind>,
- Kind: Serialize,
- <T as Extends<Kind>>::Error: From<serde_json::Error> + Send + Sync + 'static,
-{
- let actor_id = activity
- .actor()?
- .to_owned()
- .single_xsd_any_uri()
- .context(location_info!())?;
- check_is_apub_id_valid(&actor_id, false)?;
- let actor = get_or_fetch_and_upsert_actor(&actor_id, context, request_counter).await?;
- verify_signature(&request, actor.as_ref())?;
- Ok(actor)
-}
-
-/// Returns true if `to_and_cc` contains at least one local user.
-pub(crate) async fn is_addressed_to_local_person(
- to_and_cc: &[Url],
- pool: &DbPool,
-) -> Result<bool, LemmyError> {
- for url in to_and_cc {
- let url = url.to_owned();
- let person = blocking(pool, move |conn| {
- Person::read_from_apub_id(conn, &url.into())
- })
- .await?;
- if let Ok(u) = person {
- if u.local {
- return Ok(true);
- }
- }
- }
- Ok(false)
-}
-
-/// If `to_and_cc` contains the followers collection of a remote community, returns this community
-/// (like `https://example.com/c/main/followers`)
-pub(crate) async fn is_addressed_to_community_followers(
- to_and_cc: &[Url],
- pool: &DbPool,
-) -> Result<Option<Community>, LemmyError> {
- for url in to_and_cc {
- let url = url.to_owned().into();
- let community = blocking(pool, move |conn| {
- // ignore errors here, because the current url might not actually be a followers url
- Community::read_from_followers_url(conn, &url).ok()
- })
- .await?;
- if let Some(c) = community {
- if !c.local {
- return Ok(Some(c));
- }
- }
- }
- Ok(None)
-}
-
-pub(in crate::inbox) fn assert_activity_not_local<T, Kind>(activity: &T) -> Result<(), LemmyError>
-where
- T: BaseExt<Kind> + Debug,
-{
- let id = activity.id_unchecked().context(location_info!())?;
- let activity_domain = id.domain().context(location_info!())?;
-
- if activity_domain == Settings::get().hostname() {
- return Err(
- anyhow!(
- "Error: received activity which was sent by local instance: {:?}",
- activity
- )
- .into(),
- );
- }
- Ok(())
-}
+++ /dev/null
-use crate::{
- activities::receive::{
- comment::{receive_create_comment, receive_update_comment},
- community::{
- receive_delete_community,
- receive_remove_community,
- receive_undo_delete_community,
- receive_undo_remove_community,
- },
- private_message::{
- receive_create_private_message,
- receive_delete_private_message,
- receive_undo_delete_private_message,
- receive_update_private_message,
- },
- receive_unhandled_activity,
- verify_activity_domains_valid,
- },
- inbox::{
- assert_activity_not_local,
- get_activity_id,
- inbox_verify_http_signature,
- is_activity_already_known,
- is_addressed_to_community_followers,
- is_addressed_to_local_person,
- receive_for_community::{
- receive_add_for_community,
- receive_block_user_for_community,
- receive_create_for_community,
- receive_delete_for_community,
- receive_dislike_for_community,
- receive_like_for_community,
- receive_remove_for_community,
- receive_undo_for_community,
- receive_update_for_community,
- },
- verify_is_addressed_to_public,
- },
-};
-use activitystreams::{
- activity::{Accept, ActorAndObject, Announce, Create, Delete, Follow, Remove, Undo, Update},
- base::AnyBase,
- prelude::*,
-};
-use actix_web::{web, HttpRequest, HttpResponse};
-use anyhow::{anyhow, Context};
-use diesel::NotFound;
-use lemmy_api_common::blocking;
-use lemmy_apub::{
- check_is_apub_id_valid,
- fetcher::community::get_or_fetch_and_upsert_community,
- get_activity_to_and_cc,
- insert_activity,
- ActorType,
-};
-use lemmy_db_queries::{source::person::Person_, ApubObject, Followable};
-use lemmy_db_schema::source::{
- community::{Community, CommunityFollower},
- person::Person,
- private_message::PrivateMessage,
-};
-use lemmy_utils::{location_info, LemmyError};
-use lemmy_websocket::LemmyContext;
-use log::info;
-use serde::{Deserialize, Serialize};
-use std::fmt::Debug;
-use strum_macros::EnumString;
-use url::Url;
-
-/// Allowed activities for person inbox.
-#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
-#[serde(rename_all = "PascalCase")]
-pub enum PersonValidTypes {
- Accept, // community accepted our follow request
- Create, // create private message
- Update, // edit private message
- Delete, // private message or community deleted by creator
- Undo, // private message or community restored
- Remove, // community removed by admin
- Announce, // post, comment or vote in community
-}
-
-pub type PersonAcceptedActivities = ActorAndObject<PersonValidTypes>;
-
-/// Handler for all incoming activities to person inboxes.
-pub async fn person_inbox(
- request: HttpRequest,
- input: web::Json<PersonAcceptedActivities>,
- path: web::Path<String>,
- context: web::Data<LemmyContext>,
-) -> Result<HttpResponse, LemmyError> {
- let activity = input.into_inner();
- // First of all check the http signature
- let request_counter = &mut 0;
- let actor = inbox_verify_http_signature(&activity, &context, request, request_counter).await?;
-
- // Do nothing if we received the same activity before
- let activity_id = get_activity_id(&activity, &actor.actor_id())?;
- if is_activity_already_known(context.pool(), &activity_id).await? {
- return Ok(HttpResponse::Ok().finish());
- }
-
- // Check if the activity is actually meant for us
- let username = path.into_inner();
- let person = blocking(context.pool(), move |conn| {
- Person::find_by_name(conn, &username)
- })
- .await??;
- let to_and_cc = get_activity_to_and_cc(&activity);
- // TODO: we should also accept activities that are sent to community followers
- if !to_and_cc.contains(&person.actor_id()) {
- return Err(anyhow!("Activity delivered to wrong person").into());
- }
-
- assert_activity_not_local(&activity)?;
- insert_activity(&activity_id, activity.clone(), false, true, context.pool()).await?;
-
- person_receive_message(
- activity.clone(),
- Some(person.clone()),
- actor.as_ref(),
- &context,
- request_counter,
- )
- .await
-}
-
-/// Receives Accept/Follow, Announce, private messages and community (undo) remove, (undo) delete
-pub(crate) async fn person_receive_message(
- activity: PersonAcceptedActivities,
- to_person: Option<Person>,
- actor: &dyn ActorType,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<HttpResponse, LemmyError> {
- is_for_person_inbox(context, &activity).await?;
-
- info!(
- "User received activity {:?} from {}",
- &activity
- .id_unchecked()
- .context(location_info!())?
- .to_string(),
- &actor.actor_id().to_string()
- );
-
- let any_base = activity.clone().into_any_base()?;
- let kind = activity.kind().context(location_info!())?;
- let actor_url = actor.actor_id();
- match kind {
- PersonValidTypes::Accept => {
- receive_accept(
- context,
- any_base,
- actor,
- to_person.expect("person provided"),
- request_counter,
- )
- .await?;
- }
- PersonValidTypes::Announce => {
- Box::pin(receive_announce(context, any_base, actor, request_counter)).await?
- }
- PersonValidTypes::Create => {
- Box::pin(receive_create(
- context,
- any_base,
- actor_url,
- request_counter,
- ))
- .await?
- }
- PersonValidTypes::Update => {
- Box::pin(receive_update(
- context,
- any_base,
- actor_url,
- request_counter,
- ))
- .await?
- }
- PersonValidTypes::Delete => {
- Box::pin(receive_delete(
- context,
- any_base,
- &actor_url,
- request_counter,
- ))
- .await?
- }
- PersonValidTypes::Undo => {
- Box::pin(receive_undo(context, any_base, &actor_url, request_counter)).await?
- }
- PersonValidTypes::Remove => Box::pin(receive_remove(context, any_base, &actor_url)).await?,
- };
-
- // TODO: would be logical to move websocket notification code here
-
- Ok(HttpResponse::Ok().finish())
-}
-
-/// Returns true if the activity is addressed directly to one or more local persons, or if it is
-/// addressed to the followers collection of a remote community, and at least one local person follows
-/// it.
-async fn is_for_person_inbox(
- context: &LemmyContext,
- activity: &PersonAcceptedActivities,
-) -> Result<(), LemmyError> {
- let to_and_cc = get_activity_to_and_cc(activity);
- // Check if it is addressed directly to any local person
- if is_addressed_to_local_person(&to_and_cc, context.pool()).await? {
- return Ok(());
- }
-
- // Check if it is addressed to any followers collection of a remote community, and that the
- // community has local followers.
- let community = is_addressed_to_community_followers(&to_and_cc, context.pool()).await?;
- if let Some(c) = community {
- let community_id = c.id;
- let has_local_followers = blocking(context.pool(), move |conn| {
- CommunityFollower::has_local_followers(conn, community_id)
- })
- .await??;
- if c.local {
- return Err(
- anyhow!("Remote activity cant be addressed to followers of local community").into(),
- );
- }
- if has_local_followers {
- return Ok(());
- }
- }
-
- Err(anyhow!("Not addressed for any local person").into())
-}
-
-/// Handle accepted follows.
-async fn receive_accept(
- context: &LemmyContext,
- activity: AnyBase,
- actor: &dyn ActorType,
- person: Person,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let accept = Accept::from_any_base(activity)?.context(location_info!())?;
- verify_activity_domains_valid(&accept, &actor.actor_id(), false)?;
-
- let object = accept.object().to_owned().one().context(location_info!())?;
- let follow = Follow::from_any_base(object)?.context(location_info!())?;
- verify_activity_domains_valid(&follow, &person.actor_id(), false)?;
-
- let community_uri = accept
- .actor()?
- .to_owned()
- .single_xsd_any_uri()
- .context(location_info!())?;
-
- let community =
- get_or_fetch_and_upsert_community(&community_uri, context, request_counter).await?;
-
- let community_id = community.id;
- let person_id = person.id;
- // This will throw an error if no follow was requested
- blocking(context.pool(), move |conn| {
- CommunityFollower::follow_accepted(conn, community_id, person_id)
- })
- .await??;
-
- Ok(())
-}
-
-#[derive(EnumString)]
-enum AnnouncableActivities {
- Create,
- Update,
- Like,
- Dislike,
- Delete,
- Remove,
- Undo,
- Add,
- Block,
-}
-
-/// Takes an announce and passes the inner activity to the appropriate handler.
-pub async fn receive_announce(
- context: &LemmyContext,
- activity: AnyBase,
- actor: &dyn ActorType,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let announce = Announce::from_any_base(activity)?.context(location_info!())?;
- verify_activity_domains_valid(&announce, &actor.actor_id(), false)?;
- verify_is_addressed_to_public(&announce)?;
-
- let kind = announce
- .object()
- .as_single_kind_str()
- .and_then(|s| s.parse().ok());
- let inner_activity = announce
- .object()
- .to_owned()
- .one()
- .context(location_info!())?;
-
- let inner_id = inner_activity.id().context(location_info!())?.to_owned();
- check_is_apub_id_valid(&inner_id, false)?;
- if is_activity_already_known(context.pool(), &inner_id).await? {
- return Ok(());
- }
-
- use AnnouncableActivities::*;
- match kind {
- Some(Create) => {
- receive_create_for_community(context, inner_activity, &inner_id, request_counter).await
- }
- Some(Update) => {
- receive_update_for_community(
- context,
- inner_activity,
- Some(announce),
- &inner_id,
- request_counter,
- )
- .await
- }
- Some(Like) => {
- receive_like_for_community(context, inner_activity, &inner_id, request_counter).await
- }
- Some(Dislike) => {
- receive_dislike_for_community(context, inner_activity, &inner_id, request_counter).await
- }
- Some(Delete) => {
- receive_delete_for_community(
- context,
- inner_activity,
- Some(announce),
- &inner_id,
- request_counter,
- )
- .await
- }
- Some(Remove) => {
- receive_remove_for_community(context, inner_activity, Some(announce), request_counter).await
- }
- Some(Undo) => {
- receive_undo_for_community(
- context,
- inner_activity,
- Some(announce),
- &inner_id,
- request_counter,
- )
- .await
- }
- Some(Add) => {
- receive_add_for_community(context, inner_activity, Some(announce), request_counter).await
- }
- Some(Block) => {
- receive_block_user_for_community(context, inner_activity, Some(announce), request_counter)
- .await
- }
- _ => receive_unhandled_activity(inner_activity),
- }
-}
-
-/// Receive either a new private message, or a new comment mention. We distinguish them by checking
-/// whether the activity is public.
-async fn receive_create(
- context: &LemmyContext,
- activity: AnyBase,
- expected_domain: Url,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let create = Create::from_any_base(activity)?.context(location_info!())?;
- verify_activity_domains_valid(&create, &expected_domain, true)?;
- if verify_is_addressed_to_public(&create).is_ok() {
- receive_create_comment(create, context, request_counter).await
- } else {
- receive_create_private_message(context, create, expected_domain, request_counter).await
- }
-}
-
-/// Receive either an updated private message, or an updated comment mention. We distinguish
-/// them by checking whether the activity is public.
-async fn receive_update(
- context: &LemmyContext,
- activity: AnyBase,
- expected_domain: Url,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let update = Update::from_any_base(activity)?.context(location_info!())?;
- verify_activity_domains_valid(&update, &expected_domain, true)?;
- if verify_is_addressed_to_public(&update).is_ok() {
- receive_update_comment(update, context, request_counter).await
- } else {
- receive_update_private_message(context, update, expected_domain, request_counter).await
- }
-}
-
-async fn receive_delete(
- context: &LemmyContext,
- any_base: AnyBase,
- expected_domain: &Url,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- use CommunityOrPrivateMessage::*;
-
- let delete = Delete::from_any_base(any_base.clone())?.context(location_info!())?;
- verify_activity_domains_valid(&delete, expected_domain, true)?;
- let object_uri = delete
- .object()
- .to_owned()
- .single_xsd_any_uri()
- .context(location_info!())?;
-
- match find_community_or_private_message_by_id(context, object_uri).await? {
- Community(c) => receive_delete_community(context, c).await,
- PrivateMessage(p) => receive_delete_private_message(context, delete, p, request_counter).await,
- }
-}
-
-async fn receive_remove(
- context: &LemmyContext,
- any_base: AnyBase,
- expected_domain: &Url,
-) -> Result<(), LemmyError> {
- let remove = Remove::from_any_base(any_base.clone())?.context(location_info!())?;
- verify_activity_domains_valid(&remove, expected_domain, true)?;
- let object_uri = remove
- .object()
- .to_owned()
- .single_xsd_any_uri()
- .context(location_info!())?;
- let community = blocking(context.pool(), move |conn| {
- Community::read_from_apub_id(conn, &object_uri.into())
- })
- .await??;
- receive_remove_community(context, community).await
-}
-
-async fn receive_undo(
- context: &LemmyContext,
- any_base: AnyBase,
- expected_domain: &Url,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let undo = Undo::from_any_base(any_base)?.context(location_info!())?;
- verify_activity_domains_valid(&undo, expected_domain, true)?;
-
- let inner_activity = undo.object().to_owned().one().context(location_info!())?;
- let kind = inner_activity.kind_str();
- match kind {
- Some("Delete") => {
- let delete = Delete::from_any_base(inner_activity)?.context(location_info!())?;
- verify_activity_domains_valid(&delete, expected_domain, true)?;
- let object_uri = delete
- .object()
- .to_owned()
- .single_xsd_any_uri()
- .context(location_info!())?;
- use CommunityOrPrivateMessage::*;
- match find_community_or_private_message_by_id(context, object_uri).await? {
- Community(c) => receive_undo_delete_community(context, c).await,
- PrivateMessage(p) => {
- receive_undo_delete_private_message(context, undo, expected_domain, p, request_counter)
- .await
- }
- }
- }
- Some("Remove") => {
- let remove = Remove::from_any_base(inner_activity)?.context(location_info!())?;
- let object_uri = remove
- .object()
- .to_owned()
- .single_xsd_any_uri()
- .context(location_info!())?;
- let community = blocking(context.pool(), move |conn| {
- Community::read_from_apub_id(conn, &object_uri.into())
- })
- .await??;
- receive_undo_remove_community(context, community).await
- }
- _ => receive_unhandled_activity(undo),
- }
-}
-enum CommunityOrPrivateMessage {
- Community(Community),
- PrivateMessage(PrivateMessage),
-}
-
-async fn find_community_or_private_message_by_id(
- context: &LemmyContext,
- apub_id: Url,
-) -> Result<CommunityOrPrivateMessage, LemmyError> {
- let ap_id = apub_id.to_owned();
- let community = blocking(context.pool(), move |conn| {
- Community::read_from_apub_id(conn, &ap_id.into())
- })
- .await?;
- if let Ok(c) = community {
- return Ok(CommunityOrPrivateMessage::Community(c));
- }
-
- let ap_id = apub_id.to_owned();
- let private_message = blocking(context.pool(), move |conn| {
- PrivateMessage::read_from_apub_id(conn, &ap_id.into())
- })
- .await?;
- if let Ok(p) = private_message {
- return Ok(CommunityOrPrivateMessage::PrivateMessage(p));
- }
-
- Err(NotFound.into())
-}
+++ /dev/null
-use crate::{
- activities::receive::{
- comment::{
- receive_create_comment,
- receive_delete_comment,
- receive_dislike_comment,
- receive_like_comment,
- receive_remove_comment,
- receive_update_comment,
- },
- comment_undo::{
- receive_undo_delete_comment,
- receive_undo_dislike_comment,
- receive_undo_like_comment,
- receive_undo_remove_comment,
- },
- community::{
- receive_remote_mod_delete_community,
- receive_remote_mod_undo_delete_community,
- receive_remote_mod_update_community,
- },
- post::{
- receive_create_post,
- receive_delete_post,
- receive_dislike_post,
- receive_like_post,
- receive_remove_post,
- receive_update_post,
- },
- post_undo::{
- receive_undo_delete_post,
- receive_undo_dislike_post,
- receive_undo_like_post,
- receive_undo_remove_post,
- },
- receive_unhandled_activity,
- verify_activity_domains_valid,
- },
- inbox::verify_is_addressed_to_public,
-};
-use activitystreams::{
- activity::{
- ActorAndObjectRef,
- Add,
- Announce,
- Block,
- Create,
- Delete,
- Dislike,
- Like,
- OptTargetRef,
- Remove,
- Undo,
- Update,
- },
- base::AnyBase,
- object::AsObject,
- prelude::*,
-};
-use anyhow::{anyhow, Context};
-use diesel::result::Error::NotFound;
-use lemmy_api_common::blocking;
-use lemmy_apub::{
- fetcher::{
- objects::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
- person::get_or_fetch_and_upsert_person,
- },
- find_object_by_id,
- find_post_or_comment_by_id,
- generate_moderators_url,
- ActorType,
- CommunityType,
- Object,
- PostOrComment,
-};
-use lemmy_db_queries::{
- source::community::CommunityModerator_,
- ApubObject,
- Bannable,
- Crud,
- Followable,
- Joinable,
-};
-use lemmy_db_schema::{
- source::{
- community::{
- Community,
- CommunityFollower,
- CommunityFollowerForm,
- CommunityModerator,
- CommunityModeratorForm,
- CommunityPersonBan,
- CommunityPersonBanForm,
- },
- person::Person,
- site::Site,
- },
- DbUrl,
-};
-use lemmy_db_views_actor::community_view::CommunityView;
-use lemmy_utils::{location_info, LemmyError};
-use lemmy_websocket::LemmyContext;
-use strum_macros::EnumString;
-use url::Url;
-
-#[derive(EnumString)]
-enum PageOrNote {
- Page,
- Note,
-}
-
-#[derive(EnumString)]
-enum ObjectTypes {
- Page,
- Note,
- Group,
- Person,
-}
-
-/// This file is for post/comment activities received by the community, and for post/comment
-/// activities announced by the community and received by the person.
-
-/// A post or comment being created
-pub(in crate::inbox) async fn receive_create_for_community(
- context: &LemmyContext,
- activity: AnyBase,
- expected_domain: &Url,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let create = Create::from_any_base(activity)?.context(location_info!())?;
- verify_activity_domains_valid(&create, expected_domain, true)?;
- verify_is_addressed_to_public(&create)?;
-
- let kind = create
- .object()
- .as_single_kind_str()
- .and_then(|s| s.parse().ok());
- match kind {
- Some(ObjectTypes::Page) => receive_create_post(create, context, request_counter).await,
- Some(ObjectTypes::Note) => receive_create_comment(create, context, request_counter).await,
- _ => receive_unhandled_activity(create),
- }
-}
-
-/// A post or comment being edited
-pub(in crate::inbox) async fn receive_update_for_community(
- context: &LemmyContext,
- activity: AnyBase,
- announce: Option<Announce>,
- expected_domain: &Url,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let update = Update::from_any_base(activity.to_owned())?.context(location_info!())?;
- verify_activity_domains_valid(&update, expected_domain, false)?;
- verify_is_addressed_to_public(&update)?;
- verify_modification_actor_instance(&update, &announce, context, request_counter).await?;
-
- let kind = update
- .object()
- .as_single_kind_str()
- .and_then(|s| s.parse().ok());
- match kind {
- Some(ObjectTypes::Page) => {
- receive_update_post(update, announce, context, request_counter).await
- }
- Some(ObjectTypes::Note) => receive_update_comment(update, context, request_counter).await,
- Some(ObjectTypes::Group) => {
- receive_remote_mod_update_community(update, context, request_counter).await
- }
- _ => receive_unhandled_activity(update),
- }
-}
-
-/// A post or comment being upvoted
-pub(in crate::inbox) async fn receive_like_for_community(
- context: &LemmyContext,
- activity: AnyBase,
- expected_domain: &Url,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let like = Like::from_any_base(activity)?.context(location_info!())?;
- verify_activity_domains_valid(&like, expected_domain, false)?;
- verify_is_addressed_to_public(&like)?;
-
- let object_id = like
- .object()
- .as_single_xsd_any_uri()
- .context(location_info!())?;
- match fetch_post_or_comment_by_id(object_id, context, request_counter).await? {
- PostOrComment::Post(post) => receive_like_post(like, *post, context, request_counter).await,
- PostOrComment::Comment(comment) => {
- receive_like_comment(like, *comment, context, request_counter).await
- }
- }
-}
-
-/// A post or comment being downvoted
-pub(in crate::inbox) async fn receive_dislike_for_community(
- context: &LemmyContext,
- activity: AnyBase,
- expected_domain: &Url,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let enable_downvotes = blocking(context.pool(), move |conn| {
- Site::read(conn, 1).map(|s| s.enable_downvotes)
- })
- .await??;
- if !enable_downvotes {
- return Ok(());
- }
-
- let dislike = Dislike::from_any_base(activity)?.context(location_info!())?;
- verify_activity_domains_valid(&dislike, expected_domain, false)?;
- verify_is_addressed_to_public(&dislike)?;
-
- let object_id = dislike
- .object()
- .as_single_xsd_any_uri()
- .context(location_info!())?;
- match fetch_post_or_comment_by_id(object_id, context, request_counter).await? {
- PostOrComment::Post(post) => {
- receive_dislike_post(dislike, *post, context, request_counter).await
- }
- PostOrComment::Comment(comment) => {
- receive_dislike_comment(dislike, *comment, context, request_counter).await
- }
- }
-}
-
-/// A post or comment being deleted by its creator
-pub(in crate::inbox) async fn receive_delete_for_community(
- context: &LemmyContext,
- activity: AnyBase,
- announce: Option<Announce>,
- expected_domain: &Url,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let delete = Delete::from_any_base(activity)?.context(location_info!())?;
- // TODO: skip this check if action is done by remote mod
- verify_is_addressed_to_public(&delete)?;
- verify_modification_actor_instance(&delete, &announce, context, request_counter).await?;
-
- let object = delete
- .object()
- .to_owned()
- .single_xsd_any_uri()
- .context(location_info!())?;
-
- match find_object_by_id(context, object).await {
- Ok(Object::Post(p)) => {
- verify_activity_domains_valid(&delete, expected_domain, true)?;
- receive_delete_post(context, *p).await
- }
- Ok(Object::Comment(c)) => {
- verify_activity_domains_valid(&delete, expected_domain, true)?;
- receive_delete_comment(context, *c).await
- }
- Ok(Object::Community(c)) => {
- receive_remote_mod_delete_community(delete, *c, context, request_counter).await
- }
- // if we dont have the object or dont support its deletion, no need to do anything
- _ => Ok(()),
- }
-}
-
-/// A post or comment being removed by a mod/admin
-pub(in crate::inbox) async fn receive_remove_for_community(
- context: &LemmyContext,
- remove_any_base: AnyBase,
- announce: Option<Announce>,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let remove = Remove::from_any_base(remove_any_base.to_owned())?.context(location_info!())?;
- let community = extract_community_from_cc(&remove, context).await?;
-
- verify_mod_activity(&remove, announce, &community, context).await?;
- verify_is_addressed_to_public(&remove)?;
-
- if remove.target().is_some() {
- let remove_mod = remove
- .object()
- .as_single_xsd_any_uri()
- .context(location_info!())?;
- let remove_mod = get_or_fetch_and_upsert_person(remove_mod, context, request_counter).await?;
- let form = CommunityModeratorForm {
- community_id: community.id,
- person_id: remove_mod.id,
- };
- blocking(context.pool(), move |conn| {
- CommunityModerator::leave(conn, &form)
- })
- .await??;
- community
- .send_announce(
- remove_any_base,
- remove.object().clone().single_xsd_any_uri(),
- context,
- )
- .await?;
- // TODO: send websocket notification about removed mod
- Ok(())
- }
- // Remove a post or comment
- else {
- let object = remove
- .object()
- .to_owned()
- .single_xsd_any_uri()
- .context(location_info!())?;
-
- match find_post_or_comment_by_id(context, object).await {
- Ok(PostOrComment::Post(p)) => receive_remove_post(context, *p).await,
- Ok(PostOrComment::Comment(c)) => receive_remove_comment(context, *c).await,
- // if we dont have the object, no need to do anything
- Err(_) => Ok(()),
- }
- }
-}
-
-#[derive(EnumString)]
-enum UndoableActivities {
- Delete,
- Remove,
- Like,
- Dislike,
- Block,
-}
-
-/// A post/comment action being reverted (either a delete, remove, upvote or downvote)
-pub(in crate::inbox) async fn receive_undo_for_community(
- context: &LemmyContext,
- activity: AnyBase,
- announce: Option<Announce>,
- expected_domain: &Url,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let undo = Undo::from_any_base(activity)?.context(location_info!())?;
- verify_activity_domains_valid(&undo, &expected_domain.to_owned(), true)?;
- verify_is_addressed_to_public(&undo)?;
-
- use UndoableActivities::*;
- match undo
- .object()
- .as_single_kind_str()
- .and_then(|s| s.parse().ok())
- {
- Some(Delete) => {
- receive_undo_delete_for_community(context, undo, expected_domain, request_counter).await
- }
- Some(Remove) => {
- receive_undo_remove_for_community(context, undo, announce, expected_domain).await
- }
- Some(Like) => {
- receive_undo_like_for_community(context, undo, expected_domain, request_counter).await
- }
- Some(Dislike) => {
- receive_undo_dislike_for_community(context, undo, expected_domain, request_counter).await
- }
- Some(Block) => {
- receive_undo_block_user_for_community(
- context,
- undo,
- announce,
- expected_domain,
- request_counter,
- )
- .await
- }
- _ => receive_unhandled_activity(undo),
- }
-}
-
-/// A post, comment or community deletion being reverted
-pub(in crate::inbox) async fn receive_undo_delete_for_community(
- context: &LemmyContext,
- undo: Undo,
- expected_domain: &Url,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let delete = Delete::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
- .context(location_info!())?;
- verify_is_addressed_to_public(&delete)?;
-
- let object = delete
- .object()
- .to_owned()
- .single_xsd_any_uri()
- .context(location_info!())?;
- match find_object_by_id(context, object).await {
- Ok(Object::Post(p)) => {
- verify_activity_domains_valid(&delete, expected_domain, true)?;
- receive_undo_delete_post(context, *p).await
- }
- Ok(Object::Comment(c)) => {
- verify_activity_domains_valid(&delete, expected_domain, true)?;
- receive_undo_delete_comment(context, *c).await
- }
- Ok(Object::Community(c)) => {
- verify_actor_is_community_mod(&undo, &c, context).await?;
- receive_remote_mod_undo_delete_community(undo, *c, context, request_counter).await
- }
- // if we dont have the object or dont support its deletion, no need to do anything
- _ => Ok(()),
- }
-}
-
-/// A post or comment removal being reverted
-pub(in crate::inbox) async fn receive_undo_remove_for_community(
- context: &LemmyContext,
- undo: Undo,
- announce: Option<Announce>,
- expected_domain: &Url,
-) -> Result<(), LemmyError> {
- let remove = Remove::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
- .context(location_info!())?;
- verify_activity_domains_valid(&remove, expected_domain, false)?;
- verify_is_addressed_to_public(&remove)?;
- verify_undo_remove_actor_instance(&undo, &remove, &announce, context).await?;
-
- let object = remove
- .object()
- .to_owned()
- .single_xsd_any_uri()
- .context(location_info!())?;
- match find_post_or_comment_by_id(context, object).await {
- Ok(PostOrComment::Post(p)) => receive_undo_remove_post(context, *p).await,
- Ok(PostOrComment::Comment(c)) => receive_undo_remove_comment(context, *c).await,
- // if we dont have the object, no need to do anything
- Err(_) => Ok(()),
- }
-}
-
-/// A post or comment upvote being reverted
-pub(in crate::inbox) async fn receive_undo_like_for_community(
- context: &LemmyContext,
- undo: Undo,
- expected_domain: &Url,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let like = Like::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
- .context(location_info!())?;
- verify_activity_domains_valid(&like, expected_domain, false)?;
- verify_is_addressed_to_public(&like)?;
-
- let object_id = like
- .object()
- .as_single_xsd_any_uri()
- .context(location_info!())?;
- match fetch_post_or_comment_by_id(object_id, context, request_counter).await? {
- PostOrComment::Post(post) => {
- receive_undo_like_post(&like, *post, context, request_counter).await
- }
- PostOrComment::Comment(comment) => {
- receive_undo_like_comment(&like, *comment, context, request_counter).await
- }
- }
-}
-
-/// Add a new mod to the community (can only be done by an existing mod).
-pub(in crate::inbox) async fn receive_add_for_community(
- context: &LemmyContext,
- add_any_base: AnyBase,
- announce: Option<Announce>,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let add = Add::from_any_base(add_any_base.to_owned())?.context(location_info!())?;
- let community = extract_community_from_cc(&add, context).await?;
-
- verify_mod_activity(&add, announce, &community, context).await?;
- verify_is_addressed_to_public(&add)?;
- verify_add_remove_moderator_target(&add, &community)?;
-
- let new_mod = add
- .object()
- .as_single_xsd_any_uri()
- .context(location_info!())?;
- let new_mod = get_or_fetch_and_upsert_person(new_mod, context, request_counter).await?;
-
- // If we had to refetch the community while parsing the activity, then the new mod has already
- // been added. Skip it here as it would result in a duplicate key error.
- let new_mod_id = new_mod.id;
- let moderated_communities = blocking(context.pool(), move |conn| {
- CommunityModerator::get_person_moderated_communities(conn, new_mod_id)
- })
- .await??;
- if !moderated_communities.contains(&community.id) {
- let form = CommunityModeratorForm {
- community_id: community.id,
- person_id: new_mod.id,
- };
- blocking(context.pool(), move |conn| {
- CommunityModerator::join(conn, &form)
- })
- .await??;
- }
- if community.local {
- community
- .send_announce(
- add_any_base,
- add.object().clone().single_xsd_any_uri(),
- context,
- )
- .await?;
- }
- // TODO: send websocket notification about added mod
- Ok(())
-}
-
-/// A post or comment downvote being reverted
-pub(in crate::inbox) async fn receive_undo_dislike_for_community(
- context: &LemmyContext,
- undo: Undo,
- expected_domain: &Url,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let dislike = Dislike::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
- .context(location_info!())?;
- verify_activity_domains_valid(&dislike, expected_domain, false)?;
- verify_is_addressed_to_public(&dislike)?;
-
- let object_id = dislike
- .object()
- .as_single_xsd_any_uri()
- .context(location_info!())?;
- match fetch_post_or_comment_by_id(object_id, context, request_counter).await? {
- PostOrComment::Post(post) => {
- receive_undo_dislike_post(&dislike, *post, context, request_counter).await
- }
- PostOrComment::Comment(comment) => {
- receive_undo_dislike_comment(&dislike, *comment, context, request_counter).await
- }
- }
-}
-
-pub(crate) async fn receive_block_user_for_community(
- context: &LemmyContext,
- block_any_base: AnyBase,
- announce: Option<Announce>,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let block = Block::from_any_base(block_any_base.to_owned())?.context(location_info!())?;
- let community = extract_community_from_cc(&block, context).await?;
-
- verify_mod_activity(&block, announce, &community, context).await?;
- verify_is_addressed_to_public(&block)?;
-
- let blocked_user = block
- .object()
- .as_single_xsd_any_uri()
- .context(location_info!())?;
- let blocked_user = get_or_fetch_and_upsert_person(blocked_user, context, request_counter).await?;
-
- let community_user_ban_form = CommunityPersonBanForm {
- community_id: community.id,
- person_id: blocked_user.id,
- };
-
- blocking(context.pool(), move |conn: &'_ _| {
- CommunityPersonBan::ban(conn, &community_user_ban_form)
- })
- .await??;
-
- // Also unsubscribe them from the community, if they are subscribed
- let community_follower_form = CommunityFollowerForm {
- community_id: community.id,
- person_id: blocked_user.id,
- pending: false,
- };
- blocking(context.pool(), move |conn: &'_ _| {
- CommunityFollower::unfollow(conn, &community_follower_form)
- })
- .await?
- .ok();
-
- Ok(())
-}
-
-pub(crate) async fn receive_undo_block_user_for_community(
- context: &LemmyContext,
- undo: Undo,
- announce: Option<Announce>,
- expected_domain: &Url,
- request_counter: &mut i32,
-) -> Result<(), LemmyError> {
- let object = undo.object().clone().one().context(location_info!())?;
- let block = Block::from_any_base(object)?.context(location_info!())?;
- let community = extract_community_from_cc(&block, context).await?;
-
- verify_activity_domains_valid(&block, expected_domain, false)?;
- verify_is_addressed_to_public(&block)?;
- verify_undo_remove_actor_instance(&undo, &block, &announce, context).await?;
-
- let blocked_user = block
- .object()
- .as_single_xsd_any_uri()
- .context(location_info!())?;
- let blocked_user = get_or_fetch_and_upsert_person(blocked_user, context, request_counter).await?;
-
- let community_user_ban_form = CommunityPersonBanForm {
- community_id: community.id,
- person_id: blocked_user.id,
- };
-
- blocking(context.pool(), move |conn: &'_ _| {
- CommunityPersonBan::unban(conn, &community_user_ban_form)
- })
- .await??;
-
- Ok(())
-}
-
-async fn fetch_post_or_comment_by_id(
- apub_id: &Url,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<PostOrComment, LemmyError> {
- if let Ok(post) = get_or_fetch_and_insert_post(apub_id, context, request_counter).await {
- return Ok(PostOrComment::Post(Box::new(post)));
- }
-
- if let Ok(comment) = get_or_fetch_and_insert_comment(apub_id, context, request_counter).await {
- return Ok(PostOrComment::Comment(Box::new(comment)));
- }
-
- Err(NotFound.into())
-}
-
-/// Searches the activity's cc field for a Community ID, and returns the community.
-async fn extract_community_from_cc<T, Kind>(
- activity: &T,
- context: &LemmyContext,
-) -> Result<Community, LemmyError>
-where
- T: AsObject<Kind>,
-{
- let cc = activity
- .cc()
- .map(|c| c.as_many())
- .flatten()
- .context(location_info!())?;
- let community_id = cc
- .first()
- .map(|c| c.as_xsd_any_uri())
- .flatten()
- .context(location_info!())?;
- let community_id: DbUrl = community_id.to_owned().into();
- let community = blocking(context.pool(), move |conn| {
- Community::read_from_apub_id(conn, &community_id)
- })
- .await??;
- Ok(community)
-}
-
-/// Checks that a moderation activity was sent by a user who is listed as mod for the community.
-/// This is only used in the case of remote mods, as local mod actions don't go through the
-/// community inbox.
-///
-/// This method should only be used for activities received by the community, not for activities
-/// used by community followers.
-pub(crate) async fn verify_actor_is_community_mod<T, Kind>(
- activity: &T,
- community: &Community,
- context: &LemmyContext,
-) -> Result<(), LemmyError>
-where
- T: ActorAndObjectRef + BaseExt<Kind>,
-{
- let actor = activity
- .actor()?
- .as_single_xsd_any_uri()
- .context(location_info!())?
- .to_owned();
- let actor = blocking(context.pool(), move |conn| {
- Person::read_from_apub_id(conn, &actor.into())
- })
- .await??;
-
- // Note: this will also return true for admins in addition to mods, but as we dont know about
- // remote admins, it doesnt make any difference.
- let community_id = community.id;
- let actor_id = actor.id;
- let is_mod_or_admin = blocking(context.pool(), move |conn| {
- CommunityView::is_mod_or_admin(conn, actor_id, community_id)
- })
- .await?;
- if !is_mod_or_admin {
- return Err(anyhow!("Not a mod").into());
- }
-
- Ok(())
-}
-
-/// This method behaves differently, depending if it is called via community inbox (activity
-/// received by community from a remote user), or via user inbox (activity received by user from
-/// community). We distinguish the cases by checking if the activity is wrapper in an announce
-/// (only true when sent from user to community).
-///
-/// In the first case, we check that the actor is listed as community mod. In the second case, we
-/// only check that the announce comes from the same domain as the activity. We trust the
-/// community's instance to have validated the inner activity correctly. We can't do this validation
-/// here, because we don't know who the instance admins are. Plus this allows for compatibility with
-/// software that uses different rules for mod actions.
-pub(crate) async fn verify_mod_activity<T, Kind>(
- mod_action: &T,
- announce: Option<Announce>,
- community: &Community,
- context: &LemmyContext,
-) -> Result<(), LemmyError>
-where
- T: ActorAndObjectRef + BaseExt<Kind>,
-{
- match announce {
- None => verify_actor_is_community_mod(mod_action, community, context).await?,
- Some(a) => verify_activity_domains_valid(&a, &community.actor_id.to_owned().into(), false)?,
- }
-
- Ok(())
-}
-
-/// For Add/Remove community moderator activities, check that the target field actually contains
-/// /c/community/moderators. Any different values are unsupported.
-fn verify_add_remove_moderator_target<T, Kind>(
- activity: &T,
- community: &Community,
-) -> Result<(), LemmyError>
-where
- T: ActorAndObjectRef + BaseExt<Kind> + OptTargetRef,
-{
- let target = activity
- .target()
- .map(|t| t.as_single_xsd_any_uri())
- .flatten()
- .context(location_info!())?;
- if target != &generate_moderators_url(&community.actor_id)?.into_inner() {
- return Err(anyhow!("Unkown target url").into());
- }
- Ok(())
-}
-
-/// For activities like Update, Delete or Remove, check that the actor is from the same instance
-/// as the original object itself (or is a remote mod).
-///
-/// Note: This is only needed for mod actions. Normal user actions (edit post, undo vote etc) are
-/// already verified with `expected_domain`, so this serves as an additional check.
-async fn verify_modification_actor_instance<T, Kind>(
- activity: &T,
- announce: &Option<Announce>,
- context: &LemmyContext,
- request_counter: &mut i32,
-) -> Result<(), LemmyError>
-where
- T: ActorAndObjectRef + BaseExt<Kind> + AsObject<Kind>,
-{
- let actor_id = activity
- .actor()?
- .to_owned()
- .single_xsd_any_uri()
- .context(location_info!())?;
- let object_id = activity
- .object()
- .as_one()
- .map(|o| o.id())
- .flatten()
- .context(location_info!())?;
- let original_id = match fetch_post_or_comment_by_id(object_id, context, request_counter).await {
- Ok(PostOrComment::Post(p)) => p.ap_id.into_inner(),
- Ok(PostOrComment::Comment(c)) => c.ap_id.into_inner(),
- Err(_) => {
- // We can also receive Update activity from remote mod for local activity
- let object_id = object_id.to_owned().into();
- blocking(context.pool(), move |conn| {
- Community::read_from_apub_id(conn, &object_id)
- })
- .await??
- .actor_id()
- }
- };
- if actor_id.domain() != original_id.domain() {
- let community = extract_community_from_cc(activity, context).await?;
- verify_mod_activity(activity, announce.to_owned(), &community, context).await?;
- }
-
- Ok(())
-}
-
-pub(crate) async fn verify_undo_remove_actor_instance<T, Kind>(
- undo: &Undo,
- inner: &T,
- announce: &Option<Announce>,
- context: &LemmyContext,
-) -> Result<(), LemmyError>
-where
- T: ActorAndObjectRef + BaseExt<Kind> + AsObject<Kind>,
-{
- if announce.is_none() {
- let community = extract_community_from_cc(undo, context).await?;
- verify_mod_activity(undo, announce.to_owned(), &community, context).await?;
- verify_mod_activity(inner, announce.to_owned(), &community, context).await?;
- }
-
- Ok(())
-}
+++ /dev/null
-use crate::inbox::{
- assert_activity_not_local,
- community_inbox::{community_receive_message, CommunityAcceptedActivities},
- get_activity_id,
- inbox_verify_http_signature,
- is_activity_already_known,
- is_addressed_to_community_followers,
- is_addressed_to_local_person,
- person_inbox::{person_receive_message, PersonAcceptedActivities},
-};
-use activitystreams::{activity::ActorAndObject, prelude::*};
-use actix_web::{web, HttpRequest, HttpResponse};
-use anyhow::Context;
-use lemmy_api_common::blocking;
-use lemmy_apub::{get_activity_to_and_cc, insert_activity};
-use lemmy_db_queries::{ApubObject, DbPool};
-use lemmy_db_schema::source::community::Community;
-use lemmy_utils::{location_info, LemmyError};
-use lemmy_websocket::LemmyContext;
-use serde::{Deserialize, Serialize};
-use std::fmt::Debug;
-use url::Url;
-
-/// Allowed activity types for shared inbox.
-#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
-#[serde(rename_all = "PascalCase")]
-pub enum ValidTypes {
- Create,
- Update,
- Like,
- Dislike,
- Delete,
- Undo,
- Remove,
- Announce,
- Add,
- Block,
-}
-
-// TODO: this isnt entirely correct, cause some of these receive are not ActorAndObject,
-// but it still works due to the anybase conversion
-pub type AcceptedActivities = ActorAndObject<ValidTypes>;
-
-/// Handler for all incoming requests to shared inbox.
-pub async fn shared_inbox(
- request: HttpRequest,
- input: web::Json<AcceptedActivities>,
- context: web::Data<LemmyContext>,
-) -> Result<HttpResponse, LemmyError> {
- let activity = input.into_inner();
- // First of all check the http signature
- let request_counter = &mut 0;
- let actor = inbox_verify_http_signature(&activity, &context, request, request_counter).await?;
-
- // Do nothing if we received the same activity before
- let actor_id = actor.actor_id();
- let activity_id = get_activity_id(&activity, &actor_id)?;
- if is_activity_already_known(context.pool(), &activity_id).await? {
- return Ok(HttpResponse::Ok().finish());
- }
-
- assert_activity_not_local(&activity)?;
- // Log the activity, so we avoid receiving and parsing it twice. Note that this could still happen
- // if we receive the same activity twice in very quick succession.
- insert_activity(&activity_id, activity.clone(), false, true, context.pool()).await?;
-
- let activity_any_base = activity.clone().into_any_base()?;
- let mut res: Option<HttpResponse> = None;
- let to_and_cc = get_activity_to_and_cc(&activity);
- // Handle community first, so in case the sender is banned by the community, it will error out.
- // If we handled the person receive first, the activity would be inserted to the database before the
- // community could check for bans.
- // Note that an activity can be addressed to a community and to a person (or multiple persons) at the
- // same time. In this case we still only handle it once, to avoid duplicate websocket
- // notifications.
- let community = extract_local_community_from_destinations(&to_and_cc, context.pool()).await?;
- if let Some(community) = community {
- let community_activity = CommunityAcceptedActivities::from_any_base(activity_any_base.clone())?
- .context(location_info!())?;
- res = Some(
- Box::pin(community_receive_message(
- community_activity,
- community,
- actor.as_ref(),
- &context,
- request_counter,
- ))
- .await?,
- );
- } else if is_addressed_to_local_person(&to_and_cc, context.pool()).await? {
- let person_activity = PersonAcceptedActivities::from_any_base(activity_any_base.clone())?
- .context(location_info!())?;
- // `to_person` is only used for follow activities (which we dont receive here), so no need to pass
- // it in
- Box::pin(person_receive_message(
- person_activity,
- None,
- actor.as_ref(),
- &context,
- request_counter,
- ))
- .await?;
- } else if is_addressed_to_community_followers(&to_and_cc, context.pool())
- .await?
- .is_some()
- {
- let person_activity = PersonAcceptedActivities::from_any_base(activity_any_base.clone())?
- .context(location_info!())?;
- res = Some(
- Box::pin(person_receive_message(
- person_activity,
- None,
- actor.as_ref(),
- &context,
- request_counter,
- ))
- .await?,
- );
- }
-
- // If none of those, throw an error
- if let Some(r) = res {
- Ok(r)
- } else {
- Ok(HttpResponse::NotImplemented().finish())
- }
-}
-
-/// If `to_and_cc` contains the ID of a local community, return that community, otherwise return
-/// None.
-///
-/// This doesnt handle the case where an activity is addressed to multiple communities (because
-/// Lemmy doesnt generate such activities).
-async fn extract_local_community_from_destinations(
- to_and_cc: &[Url],
- pool: &DbPool,
-) -> Result<Option<Community>, LemmyError> {
- for url in to_and_cc {
- let url = url.to_owned();
- let community = blocking(pool, move |conn| {
- Community::read_from_apub_id(conn, &url.into())
- })
- .await?;
- if let Ok(c) = community {
- if c.local {
- return Ok(Some(c));
- }
- }
- }
- Ok(None)
-}
mod activities;
-mod http;
-mod inbox;
-pub mod routes;
+pub mod http;
sudo chown -R 991:991 volumes/pictrs_$Item
done
-sudo docker-compose pull --ignore-pull-failures || true
+#sudo docker-compose pull --ignore-pull-failures || true
sudo docker-compose up
echo "cargo clean"
# to benchmark incremental compilation time, do a full build with the same compiler version first,
# and use the following clean command:
- #cargo clean -p lemmy_utils
- cargo clean
+ cargo clean -p lemmy_utils
+ #cargo clean
echo "cargo build"
start=$(date +%s.%N)
RUSTC_WRAPPER='' cargo build -q
average=$(bc <<< "scale=0; $duration / $times")
-echo "Average compilation time over $times runs is $average seconds"
\ No newline at end of file
+echo "Average compilation time over $times runs is $average seconds"
.app_data(Data::new(context))
// The routes
.configure(|cfg| api_routes::config(cfg, &rate_limiter))
- .configure(lemmy_apub_receive::routes::config)
+ .configure(lemmy_apub_receive::http::routes::config)
.configure(feeds::config)
.configure(|cfg| images::config(cfg, &rate_limiter))
.configure(nodeinfo::config)