When receiving activity, dont read community from cc (for pleroma compat and better verification)

This commit is contained in:
Felix Ableitner 2021-10-28 13:46:48 +02:00
parent 74523fb534
commit 271785b7fb
28 changed files with 386 additions and 185 deletions

View file

@ -2,8 +2,10 @@ use crate::{
activities::{
check_community_deleted_or_removed,
comment::{collect_non_local_mentions, get_notif_recipients},
community::{announce::AnnouncableActivities, send_to_community},
extract_community,
community::{
announce::{AnnouncableActivities, GetCommunity},
send_to_community,
},
generate_activity_id,
verify_activity,
verify_is_public,
@ -108,12 +110,11 @@ impl ActivityHandler for CreateOrUpdateComment {
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
let community = extract_community(&self.cc, context, request_counter).await?;
let community_id = ObjectId::new(community.actor_id());
let post = self.object.get_parents(context, request_counter).await?.0;
let community = self.get_community(context, request_counter).await?;
verify_activity(self, &context.settings())?;
verify_person_in_community(&self.actor, &community_id, context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
verify_domains_match(self.actor.inner(), self.object.id_unchecked())?;
check_community_deleted_or_removed(&community)?;
check_post_deleted_or_removed(&post)?;
@ -144,6 +145,22 @@ impl ActivityHandler for CreateOrUpdateComment {
}
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for CreateOrUpdateComment {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
let post = self.object.get_parents(context, request_counter).await?.0;
let community = blocking(context.pool(), move |conn| {
Community::read(conn, post.community_id)
})
.await??;
Ok(community.into())
}
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -1,6 +1,10 @@
use crate::{
activities::{
community::{announce::AnnouncableActivities, send_to_community},
community::{
announce::{AnnouncableActivities, GetCommunity},
get_community_from_moderators_url,
send_to_community,
},
generate_activity_id,
verify_activity,
verify_add_remove_moderator_target,
@ -91,7 +95,8 @@ impl ActivityHandler for AddMod {
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
verify_mod_action(&self.actor, &self.cc[0], context, request_counter).await?;
verify_add_remove_moderator_target(&self.target, &self.cc[0])?;
Ok(())
@ -102,7 +107,7 @@ impl ActivityHandler for AddMod {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community = self.cc[0].dereference(context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
let new_mod = self.object.dereference(context, request_counter).await?;
// If we had to refetch the community while parsing the activity, then the new mod has already
@ -126,3 +131,14 @@ impl ActivityHandler for AddMod {
Ok(())
}
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for AddMod {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
get_community_from_moderators_url(&self.target, context, request_counter).await
}
}

View file

@ -13,7 +13,6 @@ use crate::{
generate_activity_id,
post::create_or_update::CreateOrUpdatePost,
verify_activity,
verify_community,
verify_is_public,
voting::{undo_vote::UndoVote, vote::Vote},
},
@ -23,7 +22,6 @@ use crate::{
insert_activity,
objects::community::ApubCommunity,
send_lemmy_activity,
CommunityType,
};
use activitystreams::{
activity::kind::AnnounceType,
@ -35,6 +33,7 @@ use activitystreams::{
use lemmy_apub_lib::{
data::Data,
traits::{ActivityFields, ActivityHandler, ActorType},
verify::verify_urls_match,
};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
@ -58,6 +57,41 @@ pub enum AnnouncableActivities {
RemoveMod(RemoveMod),
}
#[async_trait::async_trait(?Send)]
pub(crate) trait GetCommunity {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError>;
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for AnnouncableActivities {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
use AnnouncableActivities::*;
let community = match self {
CreateOrUpdateComment(a) => a.get_community(context, request_counter).await?,
CreateOrUpdatePost(a) => a.get_community(context, request_counter).await?,
Vote(a) => a.get_community(context, request_counter).await?,
UndoVote(a) => a.get_community(context, request_counter).await?,
Delete(a) => a.get_community(context, request_counter).await?,
UndoDelete(a) => a.get_community(context, request_counter).await?,
UpdateCommunity(a) => a.get_community(context, request_counter).await?,
BlockUserFromCommunity(a) => a.get_community(context, request_counter).await?,
UndoBlockUserFromCommunity(a) => a.get_community(context, request_counter).await?,
AddMod(a) => a.get_community(context, request_counter).await?,
RemoveMod(a) => a.get_community(context, request_counter).await?,
};
verify_urls_match(self.actor(), &community.actor_id())?;
Ok(community)
}
}
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
#[serde(rename_all = "camelCase")]
pub struct AnnounceActivity {
@ -85,7 +119,7 @@ impl AnnounceActivity {
actor: ObjectId::new(community.actor_id()),
to: vec![public()],
object,
cc: vec![community.followers_url()],
cc: vec![community.followers_url.clone().into_inner()],
kind: AnnounceType::Announce,
id: generate_activity_id(
&AnnounceType::Announce,
@ -109,7 +143,6 @@ impl ActivityHandler for AnnounceActivity {
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_community(&self.actor, context, request_counter).await?;
self.object.verify(context, request_counter).await?;
Ok(())
}

View file

@ -1,6 +1,9 @@
use crate::{
activities::{
community::{announce::AnnouncableActivities, send_to_community},
community::{
announce::{AnnouncableActivities, GetCommunity},
send_to_community,
},
generate_activity_id,
verify_activity,
verify_is_public,
@ -44,6 +47,7 @@ pub struct BlockUserFromCommunity {
to: Vec<Url>,
pub(in crate::activities::community) object: ObjectId<ApubPerson>,
cc: [ObjectId<ApubCommunity>; 1],
target: ObjectId<ApubCommunity>,
#[serde(rename = "type")]
kind: BlockType,
id: Url,
@ -65,6 +69,7 @@ impl BlockUserFromCommunity {
to: vec![public()],
object: ObjectId::new(target.actor_id()),
cc: [ObjectId::new(community.actor_id())],
target: ObjectId::new(community.actor_id()),
kind: BlockType::Block,
id: generate_activity_id(
BlockType::Block,
@ -100,7 +105,8 @@ impl ActivityHandler for BlockUserFromCommunity {
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
verify_mod_action(&self.actor, &self.cc[0], context, request_counter).await?;
Ok(())
}
@ -110,7 +116,7 @@ impl ActivityHandler for BlockUserFromCommunity {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community = self.cc[0].dereference(context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
let blocked_user = self.object.dereference(context, request_counter).await?;
let community_user_ban_form = CommunityPersonBanForm {
@ -138,3 +144,14 @@ impl ActivityHandler for BlockUserFromCommunity {
Ok(())
}
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for BlockUserFromCommunity {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
self.target.dereference(context, request_counter).await
}
}

View file

@ -1,10 +1,10 @@
use crate::{
activities::community::announce::{AnnouncableActivities, AnnounceActivity},
check_is_apub_id_valid,
fetcher::object_id::ObjectId,
insert_activity,
objects::community::ApubCommunity,
send_lemmy_activity,
CommunityType,
};
use itertools::Itertools;
use lemmy_apub_lib::traits::ActorType;
@ -61,3 +61,14 @@ pub(crate) async fn send_to_community<T: ActorType>(
Ok(())
}
async fn get_community_from_moderators_url(
moderators: &Url,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
let community_id = Url::parse(&moderators.to_string().replace("/moderators", ""))?;
ObjectId::new(community_id)
.dereference(context, request_counter)
.await
}

View file

@ -1,6 +1,10 @@
use crate::{
activities::{
community::{announce::AnnouncableActivities, send_to_community},
community::{
announce::{AnnouncableActivities, GetCommunity},
get_community_from_moderators_url,
send_to_community,
},
generate_activity_id,
verify_activity,
verify_add_remove_moderator_target,
@ -43,7 +47,6 @@ pub struct RemoveMod {
cc: [ObjectId<ApubCommunity>; 1],
#[serde(rename = "type")]
kind: RemoveType,
// if target is set, this is means remove mod from community
pub(in crate::activities) target: Url,
id: Url,
#[serde(rename = "@context")]
@ -91,7 +94,8 @@ impl ActivityHandler for RemoveMod {
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
verify_mod_action(&self.actor, &self.cc[0], context, request_counter).await?;
verify_add_remove_moderator_target(&self.target, &self.cc[0])?;
Ok(())
@ -102,7 +106,7 @@ impl ActivityHandler for RemoveMod {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community = self.cc[0].dereference(context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
let remove_mod = self.object.dereference(context, request_counter).await?;
let form = CommunityModeratorForm {
@ -117,3 +121,14 @@ impl ActivityHandler for RemoveMod {
Ok(())
}
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for RemoveMod {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
get_community_from_moderators_url(&self.target, context, request_counter).await
}
}

View file

@ -1,7 +1,7 @@
use crate::{
activities::{
community::{
announce::AnnouncableActivities,
announce::{AnnouncableActivities, GetCommunity},
block_user::BlockUserFromCommunity,
send_to_community,
},
@ -92,7 +92,8 @@ impl ActivityHandler for UndoBlockUserFromCommunity {
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
verify_mod_action(&self.actor, &self.cc[0], context, request_counter).await?;
self.object.verify(context, request_counter).await?;
Ok(())
@ -103,7 +104,7 @@ impl ActivityHandler for UndoBlockUserFromCommunity {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community = self.cc[0].dereference(context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
let blocked_user = self
.object
.object
@ -123,3 +124,14 @@ impl ActivityHandler for UndoBlockUserFromCommunity {
Ok(())
}
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for UndoBlockUserFromCommunity {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
self.object.get_community(context, request_counter).await
}
}

View file

@ -1,6 +1,9 @@
use crate::{
activities::{
community::{announce::AnnouncableActivities, send_to_community},
community::{
announce::{AnnouncableActivities, GetCommunity},
send_to_community,
},
generate_activity_id,
verify_activity,
verify_is_public,
@ -90,7 +93,8 @@ impl ActivityHandler for UpdateCommunity {
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
verify_mod_action(&self.actor, &self.cc[0], context, request_counter).await?;
Ok(())
}
@ -100,8 +104,7 @@ impl ActivityHandler for UpdateCommunity {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let cc = self.cc[0].clone();
let community = cc.dereference(context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
let updated_community = Group::from_apub_to_form(
&self.object,
@ -135,3 +138,15 @@ impl ActivityHandler for UpdateCommunity {
Ok(())
}
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for UpdateCommunity {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
let cid = ObjectId::new(self.object.id.clone());
cid.dereference(context, request_counter).await
}
}

View file

@ -1,6 +1,9 @@
use crate::{
activities::{
community::{announce::AnnouncableActivities, send_to_community},
community::{
announce::{AnnouncableActivities, GetCommunity},
send_to_community,
},
deletion::{
receive_delete_action,
verify_delete_activity,
@ -245,3 +248,26 @@ pub(in crate::activities) async fn receive_remove_action(
}
Ok(())
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for Delete {
async fn get_community(
&self,
context: &LemmyContext,
_request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
let community_id = match DeletableObjects::read_from_db(&self.object, context).await? {
DeletableObjects::Community(c) => c.id,
DeletableObjects::Comment(c) => {
let post = blocking(context.pool(), move |conn| Post::read(conn, c.post_id)).await??;
post.community_id
}
DeletableObjects::Post(p) => p.community_id,
};
let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id)
})
.await??;
Ok(community.into())
}
}

View file

@ -94,7 +94,7 @@ pub(in crate::activities) async fn verify_delete_activity(
if c.local {
// can only do this check for local community, in remote case it would try to fetch the
// deleted community (which fails)
verify_person_in_community(&actor, community_id, context, request_counter).await?;
verify_person_in_community(&actor, &c, context, request_counter).await?;
}
// community deletion is always a mod (or admin) action
verify_mod_action(
@ -140,7 +140,8 @@ async fn verify_delete_activity_post_or_comment(
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let actor = ObjectId::new(activity.actor().clone());
verify_person_in_community(&actor, community_id, context, request_counter).await?;
let community = community_id.dereference(context, request_counter).await?;
verify_person_in_community(&actor, &community, context, request_counter).await?;
if is_mod_action {
verify_mod_action(&actor, community_id, context, request_counter).await?;
} else {

View file

@ -1,6 +1,9 @@
use crate::{
activities::{
community::{announce::AnnouncableActivities, send_to_community},
community::{
announce::{AnnouncableActivities, GetCommunity},
send_to_community,
},
deletion::{
delete::Delete,
receive_delete_action,
@ -166,3 +169,14 @@ impl UndoDelete {
Ok(())
}
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for UndoDelete {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
self.object.get_community(context, request_counter).await
}
}

View file

@ -1,10 +1,5 @@
use crate::{
activities::{
following::follow::FollowCommunity,
generate_activity_id,
verify_activity,
verify_community,
},
activities::{following::follow::FollowCommunity, generate_activity_id, verify_activity},
context::lemmy_context,
fetcher::object_id::ObjectId,
objects::{community::ApubCommunity, person::ApubPerson},
@ -84,7 +79,6 @@ impl ActivityHandler for AcceptFollowCommunity {
verify_activity(self, &context.settings())?;
verify_urls_match(self.to[0].inner(), self.object.actor())?;
verify_urls_match(self.actor(), self.object.to[0].inner())?;
verify_community(&self.actor, context, request_counter).await?;
self.object.verify(context, request_counter).await?;
Ok(())
}

View file

@ -4,6 +4,7 @@ use crate::{
generate_activity_id,
verify_activity,
verify_person,
verify_person_in_community,
},
context::lemmy_context,
fetcher::object_id::ObjectId,
@ -97,6 +98,8 @@ impl ActivityHandler for FollowCommunity {
verify_activity(self, &context.settings())?;
verify_urls_match(self.to[0].inner(), self.object.inner())?;
verify_person(&self.actor, context, request_counter).await?;
let community = self.to[0].dereference(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
Ok(())
}

View file

@ -1,5 +1,4 @@
use crate::{
check_community_or_site_ban,
check_is_apub_id_valid,
fetcher::object_id::ObjectId,
generate_moderators_url,
@ -10,11 +9,13 @@ use anyhow::anyhow;
use lemmy_api_common::blocking;
use lemmy_apub_lib::{traits::ActivityFields, verify::verify_domains_match};
use lemmy_db_schema::source::community::Community;
use lemmy_db_views_actor::community_view::CommunityView;
use lemmy_db_views_actor::{
community_person_ban_view::CommunityPersonBanView,
community_view::CommunityView,
};
use lemmy_utils::{settings::structs::Settings, LemmyError};
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
use std::ops::Deref;
use strum_macros::ToString;
use url::{ParseError, Url};
use uuid::Uuid;
@ -48,44 +49,26 @@ async fn verify_person(
Ok(())
}
pub(crate) async fn extract_community(
cc: &[Url],
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
let mut cc_iter = cc.iter();
loop {
if let Some(cid) = cc_iter.next() {
let cid = ObjectId::new(cid.clone());
if let Ok(c) = cid.dereference(context, request_counter).await {
break Ok(c);
}
} else {
return Err(anyhow!("No community found in cc").into());
}
}
}
/// Fetches the person and community to verify their type, then checks if person is banned from site
/// or community.
pub(crate) async fn verify_person_in_community(
person_id: &ObjectId<ApubPerson>,
community_id: &ObjectId<ApubCommunity>,
community: &ApubCommunity,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community = community_id.dereference(context, request_counter).await?;
let person = person_id.dereference(context, request_counter).await?;
check_community_or_site_ban(person.deref(), community.id, context.pool()).await
}
if person.banned {
return Err(anyhow!("Person is banned from site").into());
}
let person_id = person.id;
let community_id = community.id;
let is_banned =
move |conn: &'_ _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
if blocking(context.pool(), is_banned).await? {
return Err(anyhow!("Person is banned from community").into());
}
/// Simply check that the url actually refers to a valid group.
async fn verify_community(
community_id: &ObjectId<ApubCommunity>,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
community_id.dereference(context, request_counter).await?;
Ok(())
}

View file

@ -1,7 +1,10 @@
use crate::{
activities::{
check_community_deleted_or_removed,
community::{announce::AnnouncableActivities, send_to_community},
community::{
announce::{AnnouncableActivities, GetCommunity},
send_to_community,
},
generate_activity_id,
verify_activity,
verify_is_public,
@ -99,8 +102,8 @@ impl ActivityHandler for CreateOrUpdatePost {
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
let community = self.cc[0].dereference(context, request_counter).await?;
verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
check_community_deleted_or_removed(&community)?;
match self.kind {
@ -148,3 +151,17 @@ impl ActivityHandler for CreateOrUpdatePost {
Ok(())
}
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for CreateOrUpdatePost {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
self
.object
.extract_community(context, request_counter)
.await
}
}

View file

@ -4,7 +4,7 @@ use crate::{
fetcher::object_id::ObjectId,
objects::{
person::ApubPerson,
private_message::{ApubPrivateMessage, Note},
private_message::{ApubPrivateMessage, ChatMessage},
},
send_lemmy_activity,
};
@ -29,7 +29,7 @@ pub struct CreateOrUpdatePrivateMessage {
id: Url,
actor: ObjectId<ApubPerson>,
to: [ObjectId<ApubPerson>; 1],
object: Note,
object: ChatMessage,
#[serde(rename = "type")]
kind: CreateOrUpdateType,
#[serde(flatten)]

View file

@ -91,7 +91,8 @@ impl ActivityHandler for Report {
request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_activity(self, &context.settings())?;
verify_person_in_community(&self.actor, &self.to[0], context, request_counter).await?;
let community = self.to[0].dereference(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
Ok(())
}

View file

@ -1,6 +1,9 @@
use crate::{
activities::{
community::{announce::AnnouncableActivities, send_to_community},
community::{
announce::{AnnouncableActivities, GetCommunity},
send_to_community,
},
generate_activity_id,
verify_activity,
verify_is_public,
@ -96,7 +99,8 @@ impl ActivityHandler for UndoVote {
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
verify_urls_match(self.actor(), self.object.actor())?;
self.object.verify(context, request_counter).await?;
Ok(())
@ -119,3 +123,14 @@ impl ActivityHandler for UndoVote {
}
}
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for UndoVote {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
self.object.get_community(context, request_counter).await
}
}

View file

@ -1,6 +1,9 @@
use crate::{
activities::{
community::{announce::AnnouncableActivities, send_to_community},
community::{
announce::{AnnouncableActivities, GetCommunity},
send_to_community,
},
generate_activity_id,
verify_activity,
verify_is_public,
@ -19,7 +22,11 @@ use lemmy_apub_lib::{
data::Data,
traits::{ActivityFields, ActivityHandler, ActorType},
};
use lemmy_db_schema::{newtypes::CommunityId, source::community::Community, traits::Crud};
use lemmy_db_schema::{
newtypes::CommunityId,
source::{community::Community, post::Post},
traits::Crud,
};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
@ -120,7 +127,8 @@ impl ActivityHandler for Vote {
) -> Result<(), LemmyError> {
verify_is_public(&self.to)?;
verify_activity(self, &context.settings())?;
verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
Ok(())
}
@ -137,3 +145,24 @@ impl ActivityHandler for Vote {
}
}
}
#[async_trait::async_trait(?Send)]
impl GetCommunity for Vote {
async fn get_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
let object = self.object.dereference(context, request_counter).await?;
let cid = match object {
PostOrComment::Post(p) => p.community_id,
PostOrComment::Comment(c) => {
blocking(context.pool(), move |conn| Post::read(conn, c.post_id))
.await??
.community_id
}
};
let community = blocking(context.pool(), move |conn| Community::read(conn, cid)).await??;
Ok(community.into())
}
}

View file

@ -6,8 +6,11 @@ use crate::{
objects::{person::ApubPerson, post::ApubPost},
};
use activitystreams::{
base::AnyBase, chrono::NaiveDateTime, collection::kind::OrderedCollectionType,
primitives::OneOrMany, url::Url,
base::AnyBase,
chrono::NaiveDateTime,
collection::kind::OrderedCollectionType,
primitives::OneOrMany,
url::Url,
};
use lemmy_api_common::blocking;
use lemmy_apub_lib::{

View file

@ -1,9 +1,9 @@
use crate::{
activities::{
community::announce::{AnnouncableActivities, AnnounceActivity},
extract_community,
community::announce::{AnnouncableActivities, AnnounceActivity, GetCommunity},
following::{follow::FollowCommunity, undo::UndoFollowCommunity},
report::Report,
verify_person_in_community,
},
collections::{
community_moderators::ApubCommunityModerators,
@ -27,7 +27,10 @@ use activitystreams::{
};
use actix_web::{body::Body, web, web::Payload, HttpRequest, HttpResponse};
use lemmy_api_common::blocking;
use lemmy_apub_lib::traits::{ActivityFields, ActivityHandler, ApubObject};
use lemmy_apub_lib::{
traits::{ActivityFields, ActivityHandler, ActorType, ApubObject},
verify::verify_domains_match,
};
use lemmy_db_schema::source::community::Community;
use lemmy_db_views_actor::community_follower_view::CommunityFollowerView;
use lemmy_utils::LemmyError;
@ -92,12 +95,17 @@ pub(in crate::http) async fn receive_group_inbox(
context: &LemmyContext,
) -> Result<HttpResponse, LemmyError> {
let res = receive_activity(request, activity.clone(), context).await;
if let GroupInboxActivities::AnnouncableActivities(announcable) = activity.clone() {
let community = extract_community(&announcable.cc(), context, &mut 0).await?;
if let GroupInboxActivities::AnnouncableActivities(announcable) = activity {
let community = announcable.get_community(context, &mut 0).await?;
let actor_id = ObjectId::new(announcable.actor().clone());
verify_domains_match(&community.actor_id(), announcable.id_unchecked())?;
verify_person_in_community(&actor_id, &community, context, &mut 0).await?;
if community.local {
AnnounceActivity::send(announcable, &community, vec![], context).await?;
}
}
res
}

View file

@ -17,12 +17,7 @@ use lemmy_apub_lib::{
traits::ActorType,
webfinger::{webfinger_resolve_actor, WebfingerType},
};
use lemmy_db_schema::{
newtypes::{CommunityId, DbUrl},
source::{activity::Activity, person::Person},
DbPool,
};
use lemmy_db_views_actor::community_person_ban_view::CommunityPersonBanView;
use lemmy_db_schema::{newtypes::DbUrl, source::activity::Activity, DbPool};
use lemmy_utils::{location_info, settings::structs::Settings, LemmyError};
use lemmy_websocket::LemmyContext;
use log::info;
@ -96,16 +91,6 @@ pub(crate) fn check_is_apub_id_valid(
Ok(())
}
#[async_trait::async_trait(?Send)]
pub trait CommunityType {
fn followers_url(&self) -> Url;
async fn get_follower_inboxes(
&self,
pool: &DbPool,
settings: &Settings,
) -> Result<Vec<Url>, LemmyError>;
}
pub enum EndpointType {
Community,
Person,
@ -211,24 +196,6 @@ where
Ok(())
}
async fn check_community_or_site_ban(
person: &Person,
community_id: CommunityId,
pool: &DbPool,
) -> Result<(), LemmyError> {
if person.banned {
return Err(anyhow!("Person is banned from site").into());
}
let person_id = person.id;
let is_banned =
move |conn: &'_ _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
if blocking(pool, is_banned).await? {
return Err(anyhow!("Person is banned from community").into());
}
Ok(())
}
pub(crate) async fn send_lemmy_activity<T: Serialize>(
context: &LemmyContext,
activity: &T,

View file

@ -2,7 +2,13 @@ use crate::{
activities::{verify_is_public, verify_person_in_community},
context::lemmy_context,
fetcher::object_id::ObjectId,
objects::{person::ApubPerson, post::ApubPost, tombstone::Tombstone, Source},
objects::{
community::ApubCommunity,
person::ApubPerson,
post::ApubPost,
tombstone::Tombstone,
Source,
},
PostOrComment,
};
use activitystreams::{
@ -121,22 +127,17 @@ impl Note {
) -> Result<(), LemmyError> {
let (post, _parent_comment_id) = self.get_parents(context, request_counter).await?;
let community_id = post.community_id;
let community = blocking(context.pool(), move |conn| {
let community: ApubCommunity = blocking(context.pool(), move |conn| {
Community::read(conn, community_id)
})
.await??;
.await??
.into();
if post.locked {
return Err(anyhow!("Post is locked").into());
}
verify_domains_match(self.attributed_to.inner(), &self.id)?;
verify_person_in_community(
&self.attributed_to,
&ObjectId::new(community.actor_id),
context,
request_counter,
)
.await?;
verify_person_in_community(&self.attributed_to, &community, context, request_counter).await?;
verify_is_public(&self.to)?;
Ok(())
}
@ -247,6 +248,18 @@ impl ApubObject for ApubComment {
.dereference(context, request_counter)
.await?;
let (post, parent_comment_id) = note.get_parents(context, request_counter).await?;
let community_id = post.community_id;
let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id)
})
.await??;
verify_person_in_community(
&note.attributed_to,
&community.into(),
context,
request_counter,
)
.await?;
if post.locked {
return Err(anyhow!("Post is locked").into());
}

View file

@ -10,7 +10,6 @@ use crate::{
generate_moderators_url,
generate_outbox_url,
objects::{get_summary_from_string_or_source, tombstone::Tombstone, ImageObject, Source},
CommunityType,
};
use activitystreams::{
actor::{kind::GroupType, Endpoints},
@ -55,7 +54,7 @@ pub struct Group {
context: OneOrMany<AnyBase>,
#[serde(rename = "type")]
kind: GroupType,
id: Url,
pub(crate) id: Url,
/// username, set at account creation and can never be changed
preferred_username: String,
/// title (can be changed at any time)
@ -81,16 +80,12 @@ pub struct Group {
}
impl Group {
pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
verify_domains_match(&self.id, expected_domain)?;
Ok(&self.id)
}
pub(crate) async fn from_apub_to_form(
group: &Group,
expected_domain: &Url,
settings: &Settings,
) -> Result<CommunityForm, LemmyError> {
let actor_id = Some(group.id(expected_domain)?.clone().into());
verify_domains_match(expected_domain, &group.id)?;
let name = group.preferred_username.clone();
let title = group.name.clone();
let description = get_summary_from_string_or_source(&group.summary, &group.source);
@ -110,7 +105,7 @@ impl Group {
updated: group.updated.map(|u| u.naive_local()),
deleted: None,
nsfw: Some(group.sensitive.unwrap_or(false)),
actor_id,
actor_id: Some(group.id.clone().into()),
local: Some(false),
private_key: None,
public_key: Some(group.public_key.public_key_pem.clone()),
@ -283,14 +278,9 @@ impl ActorType for ApubCommunity {
}
}
#[async_trait::async_trait(?Send)]
impl CommunityType for Community {
fn followers_url(&self) -> Url {
self.followers_url.clone().into()
}
impl ApubCommunity {
/// For a given community, returns the inboxes of all followers.
async fn get_follower_inboxes(
pub(crate) async fn get_follower_inboxes(
&self,
pool: &DbPool,
settings: &Settings,

View file

@ -1,8 +1,14 @@
use crate::{
activities::{extract_community, verify_is_public, verify_person_in_community},
activities::{verify_is_public, verify_person_in_community},
context::lemmy_context,
fetcher::object_id::ObjectId,
objects::{person::ApubPerson, tombstone::Tombstone, ImageObject, Source},
objects::{
community::ApubCommunity,
person::ApubPerson,
tombstone::Tombstone,
ImageObject,
Source,
},
};
use activitystreams::{
base::AnyBase,
@ -11,10 +17,11 @@ use activitystreams::{
public,
unparsed::Unparsed,
};
use anyhow::anyhow;
use chrono::{DateTime, FixedOffset, NaiveDateTime};
use lemmy_api_common::blocking;
use lemmy_apub_lib::{
traits::{ActorType, ApubObject},
traits::ApubObject,
values::{MediaTypeHtml, MediaTypeMarkdown},
verify::verify_domains_match,
};
@ -94,20 +101,32 @@ impl Page {
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let community = extract_community(&self.to, context, request_counter).await?;
let community = self.extract_community(context, request_counter).await?;
check_slurs(&self.name, &context.settings().slur_regex())?;
verify_domains_match(self.attributed_to.inner(), &self.id.clone())?;
verify_person_in_community(
&self.attributed_to,
&ObjectId::new(community.actor_id()),
context,
request_counter,
)
.await?;
verify_person_in_community(&self.attributed_to, &community, context, request_counter).await?;
verify_is_public(&self.to.clone())?;
Ok(())
}
pub(crate) async fn extract_community(
&self,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
let mut to_iter = self.to.iter();
loop {
if let Some(cid) = to_iter.next() {
let cid = ObjectId::new(cid.clone());
if let Ok(c) = cid.dereference(context, request_counter).await {
break Ok(c);
}
} else {
return Err(anyhow!("No community found in cc").into());
}
}
}
}
#[derive(Clone, Debug)]
@ -223,7 +242,8 @@ impl ApubObject for ApubPost {
.attributed_to
.dereference(context, request_counter)
.await?;
let community = extract_community(&page.to, context, request_counter).await?;
let community = page.extract_community(context, request_counter).await?;
verify_person_in_community(&page.attributed_to, &community, context, request_counter).await?;
let thumbnail_url: Option<Url> = page.image.clone().map(|i| i.url);
let (metadata_res, pictrs_thumbnail) = if let Some(url) = &page.url {

View file

@ -36,7 +36,7 @@ use url::Url;
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Note {
pub struct ChatMessage {
#[serde(rename = "@context")]
context: OneOrMany<AnyBase>,
r#type: ChatMessageType,
@ -58,7 +58,7 @@ pub enum ChatMessageType {
ChatMessage,
}
impl Note {
impl ChatMessage {
pub(crate) fn id_unchecked(&self) -> &Url {
&self.id
}
@ -103,7 +103,7 @@ impl From<PrivateMessage> for ApubPrivateMessage {
#[async_trait::async_trait(?Send)]
impl ApubObject for ApubPrivateMessage {
type DataType = LemmyContext;
type ApubType = Note;
type ApubType = ChatMessage;
type TombstoneType = Tombstone;
fn last_refreshed_at(&self) -> Option<NaiveDateTime> {
@ -128,7 +128,7 @@ impl ApubObject for ApubPrivateMessage {
unimplemented!()
}
async fn to_apub(&self, context: &LemmyContext) -> Result<Note, LemmyError> {
async fn to_apub(&self, context: &LemmyContext) -> Result<ChatMessage, LemmyError> {
let creator_id = self.creator_id;
let creator = blocking(context.pool(), move |conn| Person::read(conn, creator_id)).await??;
@ -136,7 +136,7 @@ impl ApubObject for ApubPrivateMessage {
let recipient =
blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
let note = Note {
let note = ChatMessage {
context: lemmy_context(),
r#type: ChatMessageType::ChatMessage,
id: self.ap_id.clone().into(),
@ -160,7 +160,7 @@ impl ApubObject for ApubPrivateMessage {
}
async fn from_apub(
note: &Note,
note: &ChatMessage,
context: &LemmyContext,
expected_domain: &Url,
request_counter: &mut i32,

View file

@ -8,7 +8,6 @@ use url::Url;
pub trait ActivityFields {
fn id_unchecked(&self) -> &Url;
fn actor(&self) -> &Url;
fn cc(&self) -> Vec<Url>;
}
#[async_trait::async_trait(?Send)]

View file

@ -145,36 +145,18 @@ pub fn derive_activity_fields(input: proc_macro::TokenStream) -> proc_macro::Tok
let impl_actor = variants
.iter()
.map(|v| generate_match_arm(&name, v, &quote! {a.actor()}));
let impl_cc = variants
.iter()
.map(|v| generate_match_arm(&name, v, &quote! {a.cc()}));
quote! {
impl #impl_generics lemmy_apub_lib::traits::ActivityFields for #name #ty_generics #where_clause {
fn id_unchecked(&self) -> &url::Url { match self { #(#impl_id)* } }
fn actor(&self) -> &url::Url { match self { #(#impl_actor)* } }
fn cc(&self) -> Vec<url::Url> { match self { #(#impl_cc)* } }
}
}
}
Data::Struct(s) => {
// check if the struct has a field "cc", and generate impl for cc() function depending on that
let has_cc = if let syn::Fields::Named(n) = s.fields {
n.named
.iter()
.any(|i| format!("{}", i.ident.as_ref().unwrap()) == "cc")
} else {
unimplemented!()
};
let cc_impl = if has_cc {
quote! {self.cc.iter().map(|i| i.clone().into()).collect()}
} else {
quote! {vec![]}
};
Data::Struct(_) => {
quote! {
impl #impl_generics lemmy_apub_lib::traits::ActivityFields for #name #ty_generics #where_clause {
fn id_unchecked(&self) -> &url::Url { &self.id }
fn actor(&self) -> &url::Url { &self.actor.inner() }
fn cc(&self) -> Vec<url::Url> { #cc_impl }
}
}
}