getPost,
unfollowRemotes,
searchForUser,
- banUserFromSite,
+ banPersonFromSite,
searchPostLocal,
- banUserFromCommunity,
+ followCommunity,
+ banPersonFromCommunity,
} from './shared';
import { PostView, CommunityView } from 'lemmy-js-client';
};
use lemmy_db_schema::{
naive_now,
- source::{comment::Comment, community::*, moderator::*, post::Post, site::*, user::User_},
- source::{comment::Comment, community::*, moderator::*, post::Post, site::*},
++ source::{comment::Comment, community::*, moderator::*, person::Person, post::Post, site::*},
+ PersonId,
};
use lemmy_db_views::comment_view::CommentQueryBuilder;
use lemmy_db_views_actor::{
websocket_id: Option<ConnectionId>,
) -> Result<AddModToCommunityResponse, LemmyError> {
let data: &AddModToCommunity = &self;
- let user = get_user_from_jwt(&data.auth, context.pool()).await?;
+ let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
- let community_moderator_form = CommunityModeratorForm {
- community_id: data.community_id,
- person_id: data.person_id,
- };
-
let community_id = data.community_id;
// Verify that only mods or admins can add mod
- is_mod_or_admin(context.pool(), user.id, community_id).await?;
+ is_mod_or_admin(context.pool(), local_user_view.person.id, community_id).await?;
- user_id: data.user_id,
+ // Update in local database
+ let community_moderator_form = CommunityModeratorForm {
+ community_id: data.community_id,
++ person_id: data.person_id,
+ };
if data.added {
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
if blocking(context.pool(), join).await?.is_err() {
})
.await??;
- let updated_mod_id = data.user_id;
+ // Send to federated instances
- User_::read(conn, updated_mod_id)
++ let updated_mod_id = data.person_id;
+ let updated_mod = blocking(context.pool(), move |conn| {
- community.send_add_mod(&user, updated_mod, context).await?;
++ Person::read(conn, updated_mod_id)
+ })
+ .await??;
+ let community = blocking(context.pool(), move |conn| {
+ Community::read(conn, community_id)
+ })
+ .await??;
+ if data.added {
- .send_remove_mod(&user, updated_mod, context)
++ community
++ .send_add_mod(&local_user_view.person, updated_mod, context)
++ .await?;
+ } else {
+ community
++ .send_remove_mod(&local_user_view.person, updated_mod, context)
+ .await?;
+ }
+
+ // Note: in case a remote mod is added, this returns the old moderators list, it will only get
+ // updated once we receive an activity from the community (like `Announce/Add/Moderator`)
let community_id = data.community_id;
let moderators = blocking(context.pool(), move |conn| {
CommunityModeratorView::for_community(conn, community_id)
- use crate::{activities::receive::get_actor_as_user, objects::FromApub, ActorType, NoteExt};
+ use crate::{activities::receive::get_actor_as_person, objects::FromApub, ActorType, NoteExt};
use activitystreams::{
- activity::{ActorAndObjectRefExt, Create, Dislike, Like, Remove, Update},
+ activity::{ActorAndObjectRefExt, Create, Dislike, Like, Update},
base::ExtendsExt,
};
use anyhow::Context;
let note = NoteExt::from_any_base(create.object().to_owned().one().context(location_info!())?)?
.context(location_info!())?;
- let comment = Comment::from_apub(¬e, context, user.actor_id(), request_counter, false).await?;
- let comment = Comment::from_apub(¬e, context, person.actor_id(), request_counter).await?;
++ 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??;
) -> Result<(), LemmyError> {
let note = NoteExt::from_any_base(update.object().to_owned().one().context(location_info!())?)?
.context(location_info!())?;
- let user = get_actor_as_user(&update, context, request_counter).await?;
+ let person = get_actor_as_person(&update, context, request_counter).await?;
- let comment = Comment::from_apub(¬e, context, user.actor_id(), request_counter, false).await?;
- let comment = Comment::from_apub(¬e, context, person.actor_id(), 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;
-use crate::{activities::receive::get_actor_as_person, objects::FromApub, ActorType, PageExt};
+use crate::{
- activities::receive::get_actor_as_user,
++ activities::receive::get_actor_as_person,
+ inbox::receive_for_community::verify_mod_activity,
+ objects::FromApub,
+ ActorType,
+ PageExt,
+};
use activitystreams::{
- activity::{Create, Dislike, Like, Remove, Update},
+ activity::{Announce, Create, Dislike, Like, Update},
prelude::*,
};
use anyhow::Context;
let page = PageExt::from_any_base(create.object().to_owned().one().context(location_info!())?)?
.context(location_info!())?;
- let post = Post::from_apub(&page, context, user.actor_id(), request_counter, false).await?;
- let post = Post::from_apub(&page, context, person.actor_id(), request_counter).await?;
++ let post = Post::from_apub(&page, context, person.actor_id(), request_counter, false).await?;
// Refetch the view
let post_id = post.id;
let page = PageExt::from_any_base(update.object().to_owned().one().context(location_info!())?)?
.context(location_info!())?;
- let post = Post::from_apub(&page, context, person.actor_id(), request_counter).await?;
+ 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,
- user.actor_id(),
++ person.actor_id(),
+ request_counter,
+ mod_action_allowed,
+ )
+ .await?;
let post_id = post.id;
// Refetch the view
use crate::{
activities::send::generate_activity_id,
- activity_queue::{send_activity_single_dest, send_to_community_followers},
+ activity_queue::{send_activity_single_dest, send_to_community, send_to_community_followers},
check_is_apub_id_valid,
extensions::context::lemmy_context,
- fetcher::user::get_or_fetch_and_upsert_user,
+ fetcher::person::get_or_fetch_and_upsert_person,
+ generate_moderators_url,
+ insert_activity,
ActorType,
+ CommunityType,
};
use activitystreams::{
activity::{
use itertools::Itertools;
use lemmy_api_structs::blocking;
use lemmy_db_queries::DbPool;
- use lemmy_db_schema::source::{community::Community, user::User_};
-use lemmy_db_schema::source::community::Community;
++use lemmy_db_schema::source::{community::Community, person::Person};
use lemmy_db_views_actor::community_follower_view::CommunityFollowerView;
- use lemmy_utils::{location_info, LemmyError};
+ use lemmy_utils::{location_info, settings::structs::Settings, LemmyError};
use lemmy_websocket::LemmyContext;
use url::Url;
.unwrap_or_else(|| self.inbox_url.to_owned())
.into()
}
+}
- async fn send_follow(
- &self,
- _follow_actor_id: &Url,
- _context: &LemmyContext,
- ) -> Result<(), LemmyError> {
- unimplemented!()
- }
-
- async fn send_unfollow(
- &self,
- _follow_actor_id: &Url,
- _context: &LemmyContext,
- ) -> Result<(), LemmyError> {
- unimplemented!()
- }
-
+#[async_trait::async_trait(?Send)]
+impl CommunityType for Community {
- /// As a local community, accept the follow request from a remote user.
+ /// As a local community, accept the follow request from a remote person.
async fn send_accept_follow(
&self,
follow: Follow,
Ok(inboxes)
}
- actor: &User_,
- added_mod: User_,
+
+ async fn send_add_mod(
+ &self,
- actor: &User_,
- removed_mod: User_,
++ actor: &Person,
++ added_mod: Person,
+ context: &LemmyContext,
+ ) -> Result<(), LemmyError> {
+ let mut add = Add::new(
+ actor.actor_id.clone().into_inner(),
+ added_mod.actor_id.into_inner(),
+ );
+ add
+ .set_many_contexts(lemmy_context()?)
+ .set_id(generate_activity_id(AddType::Add)?)
+ .set_to(public())
+ .set_many_ccs(vec![self.actor_id()])
+ .set_target(generate_moderators_url(&self.actor_id)?.into_inner());
+
+ send_to_community(add, actor, self, context).await?;
+ Ok(())
+ }
+
+ async fn send_remove_mod(
+ &self,
++ actor: &Person,
++ removed_mod: Person,
+ context: &LemmyContext,
+ ) -> Result<(), LemmyError> {
+ let mut remove = Remove::new(
+ actor.actor_id.clone().into_inner(),
+ removed_mod.actor_id.into_inner(),
+ );
+ remove
+ .set_many_contexts(lemmy_context()?)
+ .set_id(generate_activity_id(RemoveType::Remove)?)
+ .set_to(public())
+ .set_many_ccs(vec![self.actor_id()])
+ .set_target(generate_moderators_url(&self.actor_id)?.into_inner());
+
+ send_to_community(remove, &actor, self, context).await?;
+ Ok(())
+ }
}
object::ObjectExt,
};
use lemmy_api_structs::blocking;
-use lemmy_db_queries::{ApubObject, DbPool, Followable};
+use lemmy_db_queries::{ApubObject, Followable};
use lemmy_db_schema::source::{
community::{Community, CommunityFollower, CommunityFollowerForm},
- user::User_,
+ person::Person,
};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
.unwrap_or_else(|| self.inbox_url.to_owned())
.into()
}
+}
- impl UserType for User_ {
- /// As a given local user, send out a follow request to a remote community.
+#[async_trait::async_trait(?Send)]
++impl UserType for Person {
+ /// As a given local person, send out a follow request to a remote community.
async fn send_follow(
&self,
follow_actor_id: &Url,
use crate::{
- fetcher::{
- fetch::fetch_remote_object,
- get_or_fetch_and_upsert_person,
- is_deleted,
- should_refetch_actor,
- },
+ fetcher::{fetch::fetch_remote_object, is_deleted, should_refetch_actor},
- inbox::user_inbox::receive_announce,
+ inbox::person_inbox::receive_announce,
objects::FromApub,
GroupExt,
};
return Ok(u);
}
- let user = User_::from_apub(
- let person =
- Person::from_apub(&person?, context, apub_id.to_owned(), recursion_counter).await?;
++ let person = Person::from_apub(
+ &person?,
+ context,
+ apub_id.to_owned(),
+ recursion_counter,
+ false,
+ )
+ .await?;
- let user_id = user.id;
+ let person_id = person.id;
blocking(context.pool(), move |conn| {
- User_::mark_as_updated(conn, user_id)
+ Person::mark_as_updated(conn, person_id)
})
.await??;
let person =
fetch_remote_object::<PersonExt>(context.client(), apub_id, recursion_counter).await?;
- let user = User_::from_apub(
- let person =
- Person::from_apub(&person, context, apub_id.to_owned(), recursion_counter).await?;
++ let person = Person::from_apub(
+ &person,
+ context,
+ apub_id.to_owned(),
+ recursion_counter,
+ false,
+ )
+ .await?;
- Ok(user)
+ Ok(person)
}
Err(e) => Err(e.into()),
}
user_name: String,
}
- /// Return the ActivityPub json representation of a local user over HTTP.
- pub(crate) async fn get_apub_user_http(
- info: web::Path<UserQuery>,
+ /// Return the ActivityPub json representation of a local person over HTTP.
-pub async fn get_apub_person_http(
++pub(crate) async fn get_apub_person_http(
+ info: web::Path<PersonQuery>,
context: web::Data<LemmyContext>,
) -> Result<HttpResponse<Body>, LemmyError> {
let user_name = info.into_inner().user_name;
}
}
- pub(crate) async fn get_apub_user_outbox(
- info: web::Path<UserQuery>,
-pub async fn get_apub_person_outbox(
++pub(crate) async fn get_apub_person_outbox(
+ info: web::Path<PersonQuery>,
context: web::Data<LemmyContext>,
) -> Result<HttpResponse<Body>, LemmyError> {
- let user = blocking(context.pool(), move |conn| {
- User_::read_from_name(&conn, &info.user_name)
+ let person = blocking(context.pool(), move |conn| {
+ Person::find_by_name(&conn, &info.user_name)
})
.await??;
- // TODO: populate the user outbox
+ // TODO: populate the person outbox
let mut collection = OrderedCollection::new();
collection
.set_many_items(Vec::<Url>::new())
Ok(create_apub_response(&collection))
}
- pub(crate) async fn get_apub_user_inbox(
- info: web::Path<UserQuery>,
-pub async fn get_apub_person_inbox(
++pub(crate) async fn get_apub_person_inbox(
+ info: web::Path<PersonQuery>,
context: web::Data<LemmyContext>,
) -> Result<HttpResponse<Body>, LemmyError> {
- let user = blocking(context.pool(), move |conn| {
- User_::read_from_name(&conn, &info.user_name)
+ let person = blocking(context.pool(), move |conn| {
+ Person::find_by_name(&conn, &info.user_name)
})
.await??;
let mut collection = OrderedCollection::new();
collection
- .set_id(user.inbox_url.into())
- .set_id(format!("{}/inbox", person.actor_id.into_inner()).parse()?)
++ .set_id(person.inbox_url.into())
.set_many_contexts(lemmy_context()?);
Ok(create_apub_response(&collection))
}
use url::Url;
pub mod community_inbox;
-mod receive_for_community;
+ pub mod person_inbox;
+pub(crate) mod receive_for_community;
pub mod shared_inbox;
- pub mod user_inbox;
pub(crate) fn get_activity_id<T, Kind>(activity: &T, creator_uri: &Url) -> Result<Url, LemmyError>
where
inbox_verify_http_signature,
is_activity_already_known,
is_addressed_to_community_followers,
- is_addressed_to_local_user,
+ is_addressed_to_local_person,
- is_addressed_to_public,
receive_for_community::{
+ receive_add_for_community,
receive_create_for_community,
receive_delete_for_community,
receive_dislike_for_community,
)
.await?;
}
- UserValidTypes::Announce => {
+ PersonValidTypes::Announce => {
receive_announce(&context, any_base, actor, request_counter).await?
}
- UserValidTypes::Create => {
+ PersonValidTypes::Create => {
receive_create(&context, any_base, actor_url, request_counter).await?
}
- UserValidTypes::Update => {
+ PersonValidTypes::Update => {
receive_update(&context, any_base, actor_url, request_counter).await?
}
- UserValidTypes::Delete => {
+ PersonValidTypes::Delete => {
receive_delete(context, any_base, &actor_url, request_counter).await?
}
- UserValidTypes::Undo => receive_undo(context, any_base, &actor_url, request_counter).await?,
- UserValidTypes::Remove => receive_remove(context, any_base, &actor_url).await?,
+ PersonValidTypes::Undo => receive_undo(context, any_base, &actor_url, request_counter).await?,
- PersonValidTypes::Remove => receive_remove_community(&context, any_base, &actor_url).await?,
++ PersonValidTypes::Remove => receive_remove(context, any_base, &actor_url).await?,
};
// TODO: would be logical to move websocket notification code here
receive_unhandled_activity,
verify_activity_domains_valid,
},
- fetcher::objects::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
+ fetcher::{
+ objects::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
- user::get_or_fetch_and_upsert_user,
++ person::get_or_fetch_and_upsert_person,
+ },
+ find_object_by_id,
find_post_or_comment_by_id,
- inbox::is_addressed_to_public,
+ generate_moderators_url,
+ inbox::verify_is_addressed_to_public,
+ ActorType,
+ CommunityType,
+ Object,
PostOrComment,
};
use activitystreams::{
- activity::{Create, Delete, Dislike, Like, Remove, Undo, Update},
+ activity::{
+ ActorAndObjectRef,
+ Add,
+ Announce,
+ Create,
+ Delete,
+ Dislike,
+ Like,
+ OptTargetRef,
+ Remove,
+ Undo,
+ Update,
+ },
base::AnyBase,
+ object::AsObject,
prelude::*,
};
-use anyhow::Context;
+use anyhow::{anyhow, Context};
use diesel::result::Error::NotFound;
use lemmy_api_structs::blocking;
-use lemmy_db_queries::Crud;
-use lemmy_db_schema::source::site::Site;
+use lemmy_db_queries::{source::community::CommunityModerator_, ApubObject, Crud, Joinable};
+use lemmy_db_schema::{
+ source::{
+ community::{Community, CommunityModerator, CommunityModeratorForm},
++ person::Person,
+ site::Site,
- user::User_,
+ },
+ DbUrl,
+};
+use lemmy_db_views_actor::community_view::CommunityView;
use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext;
use strum_macros::EnumString;
/// A post or comment being removed by a mod/admin
pub(in crate::inbox) async fn receive_remove_for_community(
context: &LemmyContext,
- activity: AnyBase,
- expected_domain: &Url,
+ remove_any_base: AnyBase,
+ announce: Option<Announce>,
+ request_counter: &mut i32,
) -> Result<(), LemmyError> {
- let remove = Remove::from_any_base(activity)?.context(location_info!())?;
- verify_activity_domains_valid(&remove, &expected_domain, false)?;
- is_addressed_to_public(&remove)?;
-
- let cc = remove
- .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 object = remove
- .object()
- .to_owned()
- .single_xsd_any_uri()
- .context(location_info!())?;
-
- // Ensure that remove activity comes from the same domain as the community
- remove.id(community_id.domain().context(location_info!())?)?;
-
- match find_post_or_comment_by_id(context, object).await {
- Ok(PostOrComment::Post(p)) => receive_remove_post(context, remove, *p).await,
- Ok(PostOrComment::Comment(c)) => receive_remove_comment(context, remove, *c).await,
- // if we dont have the object, no need to do anything
- Err(_) => Ok(()),
+ 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_user(&remove_mod, context, request_counter).await?;
++ let remove_mod = get_or_fetch_and_upsert_person(&remove_mod, context, request_counter).await?;
+ let form = CommunityModeratorForm {
+ community_id: community.id,
- user_id: remove_mod.id,
++ person_id: remove_mod.id,
+ };
+ blocking(context.pool(), move |conn| {
+ CommunityModerator::leave(conn, &form)
+ })
+ .await??;
+ community.send_announce(remove_any_base, 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(()),
+ }
}
}
}
}
- let new_mod = get_or_fetch_and_upsert_user(&new_mod, 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!())?;
- CommunityModerator::get_user_moderated_communities(conn, new_mod_id)
++ 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| {
- user_id: new_mod.id,
++ 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, 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,
Err(NotFound.into())
}
- User_::read_from_apub_id(&conn, &actor.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.
+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| {
- Object::User(u) => u.actor_id(),
++ 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,
+) -> 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 find_object_by_id(context, object_id.to_owned()).await? {
+ Object::Post(p) => p.ap_id.into_inner(),
+ Object::Comment(c) => c.ap_id.into_inner(),
+ Object::Community(c) => c.actor_id(),
++ Object::Person(p) => p.actor_id(),
+ Object::PrivateMessage(p) => p.ap_id.into_inner(),
+ };
+ 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(())
+}
};
use activitystreams::{
activity::Follow,
-- actor::{ApActor, Group, Person},
++ actor,
base::AnyBase,
object::{ApObject, Note, Page},
};
activity::Activity,
comment::Comment,
community::Community,
- person::Person as DbPerson,
++ person::{Person as DbPerson, Person},
post::Post,
private_message::PrivateMessage,
- user::User_,
},
DbUrl,
};
use url::{ParseError, Url};
/// Activitystreams type for community
--type GroupExt = Ext2<ApActor<ApObject<Group>>, GroupExtension, PublicKeyExtension>;
- /// Activitystreams type for user
- type PersonExt = Ext1<ApActor<ApObject<Person>>, PublicKeyExtension>;
++type GroupExt = Ext2<actor::ApActor<ApObject<actor::Group>>, GroupExtension, PublicKeyExtension>;
+ /// Activitystreams type for person
-type PersonExt = Ext1<ApActor<ApObject<Person>>, PublicKeyExtension>;
++type PersonExt = Ext1<actor::ApActor<ApObject<actor::Person>>, PublicKeyExtension>;
/// Activitystreams type for post
type PageExt = Ext1<ApObject<Page>, PageExtension>;
type NoteExt = ApObject<Note>;
}
}
- actor: &User_,
- added_mod: User_,
+#[async_trait::async_trait(?Send)]
+pub trait CommunityType {
+ async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError>;
+ async fn send_accept_follow(
+ &self,
+ follow: Follow,
+ context: &LemmyContext,
+ ) -> Result<(), LemmyError>;
+
+ async fn send_delete(&self, context: &LemmyContext) -> Result<(), LemmyError>;
+ async fn send_undo_delete(&self, context: &LemmyContext) -> Result<(), LemmyError>;
+
+ async fn send_remove(&self, context: &LemmyContext) -> Result<(), LemmyError>;
+ async fn send_undo_remove(&self, context: &LemmyContext) -> Result<(), LemmyError>;
+
+ async fn send_announce(
+ &self,
+ activity: AnyBase,
+ context: &LemmyContext,
+ ) -> Result<(), LemmyError>;
+
+ async fn send_add_mod(
+ &self,
- actor: &User_,
- removed_mod: User_,
++ actor: &Person,
++ added_mod: Person,
+ context: &LemmyContext,
+ ) -> Result<(), LemmyError>;
+ async fn send_remove_mod(
+ &self,
++ actor: &Person,
++ removed_mod: Person,
+ context: &LemmyContext,
+ ) -> Result<(), LemmyError>;
+}
+
+#[async_trait::async_trait(?Send)]
+pub trait UserType {
+ async fn send_follow(
+ &self,
+ follow_actor_id: &Url,
+ context: &LemmyContext,
+ ) -> Result<(), LemmyError>;
+ async fn send_unfollow(
+ &self,
+ follow_actor_id: &Url,
+ context: &LemmyContext,
+ ) -> Result<(), LemmyError>;
+}
+
pub enum EndpointType {
Community,
- User,
+ Person,
Post,
Comment,
PrivateMessage,
Err(NotFound.into())
}
+#[derive(Debug)]
pub(crate) enum Object {
- Comment(Comment),
- Post(Post),
- Community(Community),
- User(User_),
- PrivateMessage(PrivateMessage),
+ Comment(Box<Comment>),
+ Post(Box<Post>),
+ Community(Box<Community>),
+ Person(Box<DbPerson>),
+ PrivateMessage(Box<PrivateMessage>),
}
pub(crate) async fn find_object_by_id(
use crate::{
extensions::{context::lemmy_context, group_extensions::GroupExtension},
- fetcher::{community::fetch_community_mods, user::get_or_fetch_and_upsert_user},
- fetcher::person::get_or_fetch_and_upsert_person,
++ fetcher::{community::fetch_community_mods, person::get_or_fetch_and_upsert_person},
+ generate_moderators_url,
objects::{
check_object_domain,
create_tombstone,
context: &LemmyContext,
expected_domain: Url,
request_counter: &mut i32,
+ mod_action_allowed: bool,
) -> Result<Community, LemmyError> {
- get_object_from_apub(group, context, expected_domain, request_counter).await
+ let community: Community = get_object_from_apub(
+ group,
+ context,
+ expected_domain,
+ request_counter,
+ mod_action_allowed,
+ )
+ .await?;
+
+ let new_moderators = fetch_community_mods(context, group, request_counter).await?;
+ let community_id = community.id;
+ let current_moderators = blocking(context.pool(), move |conn| {
+ CommunityModeratorView::for_community(&conn, community_id)
+ })
+ .await??;
+ // Remove old mods from database which arent in the moderators collection anymore
+ for mod_user in ¤t_moderators {
+ if !new_moderators.contains(&&mod_user.moderator.actor_id.clone().into()) {
+ let community_moderator_form = CommunityModeratorForm {
+ community_id: mod_user.community.id,
- user_id: mod_user.moderator.id,
++ person_id: mod_user.moderator.id,
+ };
+ blocking(context.pool(), move |conn| {
+ CommunityModerator::leave(conn, &community_moderator_form)
+ })
+ .await??;
+ }
+ }
+
+ // Add new mods to database which have been added to moderators collection
+ for mod_uri in new_moderators {
- let mod_user = get_or_fetch_and_upsert_user(&mod_uri, context, request_counter).await?;
++ let mod_user = get_or_fetch_and_upsert_person(&mod_uri, context, request_counter).await?;
+ let current_mod_uris: Vec<DbUrl> = current_moderators
+ .clone()
+ .iter()
+ .map(|c| c.moderator.actor_id.clone())
+ .collect();
+ if !current_mod_uris.contains(&mod_user.actor_id) {
+ let community_moderator_form = CommunityModeratorForm {
+ community_id: community.id,
- user_id: mod_user.id,
++ person_id: mod_user.id,
+ };
+ blocking(context.pool(), move |conn| {
+ CommunityModerator::join(conn, &community_moderator_form)
+ })
+ .await??;
+ }
+ }
+
+ Ok(community)
}
}
context: &LemmyContext,
expected_domain: Url,
request_counter: &mut i32,
+ _mod_action_allowed: bool,
) -> Result<Self, LemmyError> {
- let creator_and_moderator_uris = group.inner.attributed_to().context(location_info!())?;
- let creator_uri = creator_and_moderator_uris
- .as_many()
- .context(location_info!())?
- .iter()
- .next()
- .context(location_info!())?
- .as_xsd_any_uri()
- .context(location_info!())?;
+ let moderator_uris = fetch_community_mods(context, group, request_counter).await?;
+ let creator_uri = moderator_uris.first().context(location_info!())?;
- let creator = get_or_fetch_and_upsert_user(creator_uri, context, request_counter).await?;
+ let creator = get_or_fetch_and_upsert_person(creator_uri, context, request_counter).await?;
let name = group
.inner
.preferred_username()
context: &LemmyContext,
expected_domain: Url,
request_counter: &mut i32,
- ) -> Result<User_, LemmyError> {
- let user_id = person.id_unchecked().context(location_info!())?.to_owned();
- let domain = user_id.domain().context(location_info!())?;
+ mod_action_allowed: bool,
+ ) -> Result<DbPerson, LemmyError> {
+ let person_id = person.id_unchecked().context(location_info!())?.to_owned();
+ let domain = person_id.domain().context(location_info!())?;
if domain == Settings::get().hostname() {
- let user = blocking(context.pool(), move |conn| {
- User_::read_from_apub_id(conn, &user_id.into())
+ let person = blocking(context.pool(), move |conn| {
+ DbPerson::read_from_apub_id(conn, &person_id.into())
})
.await??;
- Ok(user)
+ Ok(person)
} else {
- let user_form = UserForm::from_apub(
- let person_form =
- PersonForm::from_apub(person, context, expected_domain, request_counter).await?;
++ let person_form = PersonForm::from_apub(
+ person,
+ context,
+ expected_domain,
+ request_counter,
+ mod_action_allowed,
+ )
+ .await?;
- let user = blocking(context.pool(), move |conn| User_::upsert(conn, &user_form)).await??;
- Ok(user)
+ let person = blocking(context.pool(), move |conn| {
+ DbPerson::upsert(conn, &person_form)
+ })
+ .await??;
+ Ok(person)
}
}
}
use crate::{
+ check_is_apub_id_valid,
extensions::{context::lemmy_context, page_extension::PageExtension},
- fetcher::user::get_or_fetch_and_upsert_user,
+ fetcher::person::get_or_fetch_and_upsert_person,
objects::{
check_object_domain,
check_object_for_community_or_site_ban,
"/c/{community_name}/inbox",
web::get().to(get_apub_community_inbox),
)
- .route("/u/{user_name}", web::get().to(get_apub_user_http))
- .route("/u/{user_name}/outbox", web::get().to(get_apub_user_outbox))
- .route("/u/{user_name}/inbox", web::get().to(get_apub_user_inbox))
+ .route(
+ "/c/{community_name}/moderators",
+ web::get().to(get_apub_community_moderators),
+ )
+ .route("/u/{user_name}", web::get().to(get_apub_person_http))
+ .route(
+ "/u/{user_name}/outbox",
+ web::get().to(get_apub_person_outbox),
+ )
+ .route("/u/{user_name}/inbox", web::get().to(get_apub_person_inbox))
.route("/post/{post_id}", web::get().to(get_apub_post))
.route("/comment/{comment_id}", web::get().to(get_apub_comment))
.route("/activities/{type_}/{id}", web::get().to(get_activity)),
- ./volumes/pictrs_alpha:/mnt
lemmy-alpha-ui:
++<<<<<<< HEAD
+ image: lemmy-ui:test
++=======
+ image: dessalines/lemmy-ui:0.10.0-rc.5
++>>>>>>> main
environment:
- LEMMY_INTERNAL_HOST=lemmy-alpha:8541
- LEMMY_EXTERNAL_HOST=localhost:8541
- ./volumes/postgres_alpha:/var/lib/postgresql/data
lemmy-beta-ui:
++<<<<<<< HEAD
+ image: lemmy-ui:test
++=======
+ image: dessalines/lemmy-ui:0.10.0-rc.5
++>>>>>>> main
environment:
- LEMMY_INTERNAL_HOST=lemmy-beta:8551
- LEMMY_EXTERNAL_HOST=localhost:8551
- ./volumes/postgres_beta:/var/lib/postgresql/data
lemmy-gamma-ui:
++<<<<<<< HEAD
+ image: lemmy-ui:test
++=======
+ image: dessalines/lemmy-ui:0.10.0-rc.5
++>>>>>>> main
environment:
- LEMMY_INTERNAL_HOST=lemmy-gamma:8561
- LEMMY_EXTERNAL_HOST=localhost:8561