2 activities::receive::verify_activity_domains_valid,
4 assert_activity_not_local,
6 get_activity_to_and_cc,
7 inbox_verify_http_signature,
8 is_activity_already_known,
9 receive_for_community::{
10 receive_add_for_community,
11 receive_create_for_community,
12 receive_delete_for_community,
13 receive_dislike_for_community,
14 receive_like_for_community,
15 receive_remove_for_community,
16 receive_undo_for_community,
17 receive_update_for_community,
19 verify_is_addressed_to_public,
25 use activitystreams::{
26 activity::{kind::FollowType, ActorAndObject, Follow, Undo},
30 use actix_web::{web, HttpRequest, HttpResponse};
31 use anyhow::{anyhow, Context};
32 use lemmy_api_structs::blocking;
33 use lemmy_db_queries::{source::community::Community_, ApubObject, DbPool, Followable};
34 use lemmy_db_schema::source::{
35 community::{Community, CommunityFollower, CommunityFollowerForm},
38 use lemmy_db_views_actor::community_user_ban_view::CommunityUserBanView;
39 use lemmy_utils::{location_info, LemmyError};
40 use lemmy_websocket::LemmyContext;
42 use serde::{Deserialize, Serialize};
46 /// Allowed activities for community inbox.
47 #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
48 #[serde(rename_all = "PascalCase")]
49 pub enum CommunityValidTypes {
50 Follow, // follow request from a user
51 Undo, // unfollow from a user
52 Create, // create post or comment
53 Update, // update post or comment
54 Like, // upvote post or comment
55 Dislike, // downvote post or comment
56 Delete, // post or comment deleted by creator
57 Remove, // post or comment removed by mod or admin, or mod removed from community
58 Add, // mod added to community
61 pub type CommunityAcceptedActivities = ActorAndObject<CommunityValidTypes>;
63 /// Handler for all incoming receive to community inboxes.
64 pub async fn community_inbox(
66 input: web::Json<CommunityAcceptedActivities>,
67 path: web::Path<String>,
68 context: web::Data<LemmyContext>,
69 ) -> Result<HttpResponse, LemmyError> {
70 let activity = input.into_inner();
71 // First of all check the http signature
72 let request_counter = &mut 0;
73 let actor = inbox_verify_http_signature(&activity, &context, request, request_counter).await?;
75 // Do nothing if we received the same activity before
76 let activity_id = get_activity_id(&activity, &actor.actor_id())?;
77 if is_activity_already_known(context.pool(), &activity_id).await? {
78 return Ok(HttpResponse::Ok().finish());
81 // Check if the activity is actually meant for us
82 let path = path.into_inner();
83 let community = blocking(&context.pool(), move |conn| {
84 Community::read_from_name(&conn, &path)
87 let to_and_cc = get_activity_to_and_cc(&activity);
88 if !to_and_cc.contains(&&community.actor_id()) {
89 return Err(anyhow!("Activity delivered to wrong community").into());
92 assert_activity_not_local(&activity)?;
93 insert_activity(&activity_id, activity.clone(), false, true, context.pool()).await?;
96 "Community {} received activity {:?} from {}",
98 &activity.id_unchecked(),
102 community_receive_message(
112 /// Receives Follow, Undo/Follow, post actions, comment actions (including votes)
113 pub(crate) async fn community_receive_message(
114 activity: CommunityAcceptedActivities,
115 to_community: Community,
116 actor: &dyn ActorType,
117 context: &LemmyContext,
118 request_counter: &mut i32,
119 ) -> Result<HttpResponse, LemmyError> {
120 // Only users can send activities to the community, so we can get the actor as user
122 let actor_id = actor.actor_id();
123 let user = blocking(&context.pool(), move |conn| {
124 User_::read_from_apub_id(&conn, &actor_id.into())
127 check_community_or_site_ban(&user, to_community.id, context.pool()).await?;
129 let any_base = activity.clone().into_any_base()?;
130 let actor_url = actor.actor_id();
131 let activity_kind = activity.kind().context(location_info!())?;
132 let do_announce = match activity_kind {
133 CommunityValidTypes::Follow => {
134 handle_follow(any_base.clone(), user, &to_community, &context).await?;
137 CommunityValidTypes::Undo => {
147 CommunityValidTypes::Create => {
148 receive_create_for_community(context, any_base.clone(), &actor_url, request_counter).await?;
151 CommunityValidTypes::Update => {
152 receive_update_for_community(context, any_base.clone(), None, &actor_url, request_counter)
156 CommunityValidTypes::Like => {
157 receive_like_for_community(context, any_base.clone(), &actor_url, request_counter).await?;
160 CommunityValidTypes::Dislike => {
161 receive_dislike_for_community(context, any_base.clone(), &actor_url, request_counter).await?;
164 CommunityValidTypes::Delete => {
165 receive_delete_for_community(context, any_base.clone(), None, &actor_url).await?;
168 CommunityValidTypes::Add => {
169 receive_add_for_community(context, any_base.clone(), None, request_counter).await?;
172 CommunityValidTypes::Remove => {
173 receive_remove_for_community(context, any_base.clone(), None, request_counter).await?;
179 // Check again that the activity is public, just to be sure
180 verify_is_addressed_to_public(&activity)?;
182 .send_announce(activity.into_any_base()?, context)
186 Ok(HttpResponse::Ok().finish())
189 /// Handle a follow request from a remote user, adding the user as follower and returning an
191 async fn handle_follow(
194 community: &Community,
195 context: &LemmyContext,
196 ) -> Result<HttpResponse, LemmyError> {
197 let follow = Follow::from_any_base(activity)?.context(location_info!())?;
198 verify_activity_domains_valid(&follow, &user.actor_id(), false)?;
200 let community_follower_form = CommunityFollowerForm {
201 community_id: community.id,
206 // This will fail if they're already a follower, but ignore the error.
207 blocking(&context.pool(), move |conn| {
208 CommunityFollower::follow(&conn, &community_follower_form).ok()
212 community.send_accept_follow(follow, context).await?;
214 Ok(HttpResponse::Ok().finish())
217 async fn handle_undo(
218 context: &LemmyContext,
219 activity: CommunityAcceptedActivities,
221 to_community: &Community,
222 request_counter: &mut i32,
223 ) -> Result<bool, LemmyError> {
224 let inner_kind = activity
226 .is_single_kind(&FollowType::Follow.to_string());
227 let any_base = activity.into_any_base()?;
229 handle_undo_follow(any_base, actor_url, to_community, &context).await?;
232 receive_undo_for_community(context, any_base, None, &actor_url, request_counter).await?;
237 /// Handle `Undo/Follow` from a user, removing the user from followers list.
238 async fn handle_undo_follow(
241 community: &Community,
242 context: &LemmyContext,
243 ) -> Result<(), LemmyError> {
244 let undo = Undo::from_any_base(activity)?.context(location_info!())?;
245 verify_activity_domains_valid(&undo, &user_url, true)?;
247 let object = undo.object().to_owned().one().context(location_info!())?;
248 let follow = Follow::from_any_base(object)?.context(location_info!())?;
249 verify_activity_domains_valid(&follow, &user_url, false)?;
251 let user = blocking(&context.pool(), move |conn| {
252 User_::read_from_apub_id(&conn, &user_url.into())
255 let community_follower_form = CommunityFollowerForm {
256 community_id: community.id,
261 // This will fail if they aren't a follower, but ignore the error.
262 blocking(&context.pool(), move |conn| {
263 CommunityFollower::unfollow(&conn, &community_follower_form).ok()
270 pub(crate) async fn check_community_or_site_ban(
274 ) -> Result<(), LemmyError> {
276 return Err(anyhow!("User is banned from site").into());
278 let user_id = user.id;
279 let is_banned = move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
280 if blocking(pool, is_banned).await? {
281 return Err(anyhow!("User is banned from community").into());