Skip to content

Commit

Permalink
Add community setting only_followers_can_vote
Browse files Browse the repository at this point in the history
  • Loading branch information
Nutomic committed Jan 22, 2024
1 parent 5a3a4d9 commit d8aa6b3
Show file tree
Hide file tree
Showing 15 changed files with 114 additions and 50 deletions.
19 changes: 13 additions & 6 deletions crates/api/src/comment/like.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ use lemmy_api_common::{
comment::{CommentResponse, CreateCommentLike},
context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{check_bot_account, check_community_user_action, check_downvotes_enabled},
utils::{check_community_user_action, check_vote_permission},
};
use lemmy_db_schema::{
newtypes::LocalUserId,
source::{
comment::{CommentLike, CommentLikeForm},
comment_reply::CommentReply,
community::Community,
local_site::LocalSite,
},
traits::Likeable,
traits::{Crud, Likeable},
};
use lemmy_db_views::structs::{CommentView, LocalUserView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
Expand All @@ -30,13 +31,19 @@ pub async fn like_comment(

let mut recipient_ids = Vec::<LocalUserId>::new();

// Don't do a downvote if site has downvotes disabled
check_downvotes_enabled(data.score, &local_site)?;
check_bot_account(&local_user_view.person)?;

let comment_id = data.comment_id;
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?;

let community = Community::read(&mut context.pool(), orig_comment.post.community_id).await?;
check_vote_permission(
data.score,
&local_site,
&local_user_view.person,
&community,
&context,
)
.await?;

check_community_user_action(
&local_user_view.person,
orig_comment.community.id,
Expand Down
22 changes: 12 additions & 10 deletions crates/api/src/post/like.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@ use lemmy_api_common::{
context::LemmyContext,
post::{CreatePostLike, PostResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{
check_bot_account,
check_community_user_action,
check_downvotes_enabled,
mark_post_as_read,
},
utils::{check_community_user_action, check_vote_permission, mark_post_as_read},
};
use lemmy_db_schema::{
source::{
Expand All @@ -32,14 +27,21 @@ pub async fn like_post(
) -> Result<Json<PostResponse>, LemmyError> {
let local_site = LocalSite::read(&mut context.pool()).await?;

// Don't do a downvote if site has downvotes disabled
check_downvotes_enabled(data.score, &local_site)?;
check_bot_account(&local_user_view.person)?;

// Check for a community ban
let post_id = data.post_id;
let post = Post::read(&mut context.pool(), post_id).await?;

// TODO: need to read community both here are for check_community_user_action()
let community = Community::read(&mut context.pool(), post.community_id).await?;
check_vote_permission(
data.score,
&local_site,
&local_user_view.person,
&community,
&context,
)
.await?;

check_community_user_action(
&local_user_view.person,
post.community_id,
Expand Down
51 changes: 31 additions & 20 deletions crates/api_common/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use lemmy_db_schema::{
newtypes::{CommunityId, DbUrl, InstanceId, PersonId, PostId},
source::{
comment::{Comment, CommentUpdateForm},
community::{Community, CommunityModerator, CommunityUpdateForm},
community::{Community, CommunityFollower, CommunityModerator, CommunityUpdateForm},
community_block::CommunityBlock,
email_verification::{EmailVerification, EmailVerificationForm},
instance::Instance,
Expand Down Expand Up @@ -286,25 +286,6 @@ pub async fn check_person_instance_community_block(
Ok(())
}

#[tracing::instrument(skip_all)]
pub fn check_downvotes_enabled(score: i16, local_site: &LocalSite) -> Result<(), LemmyError> {
if score == -1 && !local_site.enable_downvotes {
Err(LemmyErrorType::DownvotesAreDisabled)?
} else {
Ok(())
}
}

/// Dont allow bots to do certain actions, like voting
#[tracing::instrument(skip_all)]
pub fn check_bot_account(person: &Person) -> Result<(), LemmyError> {
if person.bot_account {
Err(LemmyErrorType::InvalidBotAction)?
} else {
Ok(())
}
}

#[tracing::instrument(skip_all)]
pub fn check_private_instance(
local_user_view: &Option<LocalUserView>,
Expand Down Expand Up @@ -835,6 +816,36 @@ fn limit_expire_time(expires: DateTime<Utc>) -> LemmyResult<Option<DateTime<Utc>
}
}

/// Enforce various voting restrictions:
/// - Bots are not allowed to vote
/// - Instances can disable downvotes
/// - Communities can prevent users who are not followers from voting
pub async fn check_vote_permission(
score: i16,
local_site: &LocalSite,
person: &Person,
community: &Community,
context: &LemmyContext,
) -> LemmyResult<()> {
// check downvotes enabled
if score == -1 && !local_site.enable_downvotes {
return Err(LemmyErrorType::DownvotesAreDisabled)?;
}

// prevent bots from voting
if person.bot_account {
return Err(LemmyErrorType::InvalidBotAction)?;
}

if community.only_followers_can_vote
&& !CommunityFollower::is_follower(&mut context.pool(), person.id, community.id).await?
{
// TODO: lemmynsfw code checks that follow was at least 10 minutes ago
Err(LemmyErrorType::DownvotesAreDisabled)?
}
Ok(())
}

#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
Expand Down
35 changes: 29 additions & 6 deletions crates/apub/src/activities/voting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@ use crate::{
use activitypub_federation::{config::Data, fetch::object_id::ObjectId};
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::{
newtypes::DbUrl,
newtypes::{CommunityId, DbUrl},
source::{
activity::ActivitySendTargets,
comment::{CommentLike, CommentLikeForm},
community::Community,
local_site::LocalSite,
person::Person,
post::{PostLike, PostLikeForm},
post::{Post, PostLike, PostLikeForm},
},
traits::Likeable,
traits::{Crud, Likeable},
};
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::{LemmyError, LemmyResult};

pub mod undo_vote;
pub mod vote;
Expand Down Expand Up @@ -67,6 +68,9 @@ async fn vote_comment(
score: vote_type.into(),
};
let person_id = actor.id;
// TODO: inefficient
let post = Post::read(&mut context.pool(), comment.post_id).await?;
check_vote_permission(Some(vote_type), &actor, post.community_id, context).await?;
CommentLike::remove(&mut context.pool(), person_id, comment_id).await?;
CommentLike::like(&mut context.pool(), &like_form).await?;
Ok(())
Expand All @@ -85,8 +89,9 @@ async fn vote_post(
person_id: actor.id,
score: vote_type.into(),
};
let person_id = actor.id;
PostLike::remove(&mut context.pool(), person_id, post_id).await?;

check_vote_permission(Some(vote_type), &actor, post.community_id, context).await?;
PostLike::remove(&mut context.pool(), actor.id, post_id).await?;
PostLike::like(&mut context.pool(), &like_form).await?;
Ok(())
}
Expand All @@ -99,6 +104,9 @@ async fn undo_vote_comment(
) -> Result<(), LemmyError> {
let comment_id = comment.id;
let person_id = actor.id;
// TODO: inefficient
let post = Post::read(&mut context.pool(), comment.post_id).await?;
check_vote_permission(None, &actor, post.community_id, context).await?;
CommentLike::remove(&mut context.pool(), person_id, comment_id).await?;
Ok(())
}
Expand All @@ -111,6 +119,21 @@ async fn undo_vote_post(
) -> Result<(), LemmyError> {
let post_id = post.id;
let person_id = actor.id;
check_vote_permission(None, &actor, post.community_id, context).await?;
PostLike::remove(&mut context.pool(), person_id, post_id).await?;
Ok(())
}

pub async fn check_vote_permission(
vote_type: Option<&VoteType>,
person: &Person,
community_id: CommunityId,
context: &LemmyContext,
) -> LemmyResult<()> {
let local_site = LocalSite::read(&mut context.pool()).await?;
let community = Community::read(&mut context.pool(), community_id).await?;
let score = vote_type.map(|v| v.into()).unwrap_or(0);
lemmy_api_common::utils::check_vote_permission(score, &local_site, &person, &community, &context)
.await?;
Ok(())
}
4 changes: 1 addition & 3 deletions crates/apub/src/activities/voting/vote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use activitypub_federation::{
traits::{ActivityHandler, Actor},
};
use anyhow::anyhow;
use lemmy_api_common::{context::LemmyContext, utils::check_bot_account};
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::source::local_site::LocalSite;
use lemmy_utils::error::LemmyError;
use url::Url;
Expand Down Expand Up @@ -75,8 +75,6 @@ impl ActivityHandler for Vote {
let actor = self.actor.dereference(context).await?;
let object = self.object.dereference(context).await?;

check_bot_account(&actor.0)?;

match object {
PostOrComment::Post(p) => vote_post(&self.kind, actor, &p, context).await,
PostOrComment::Comment(c) => vote_comment(&self.kind, actor, &c, context).await,
Expand Down
1 change: 1 addition & 0 deletions crates/apub/src/objects/community.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ impl Object for ApubCommunity {
updated: self.updated,
posting_restricted_to_mods: Some(self.posting_restricted_to_mods),
attributed_to: Some(generate_moderators_url(&self.actor_id)?.into()),
only_followers_can_vote: Some(self.only_followers_can_vote),
};
Ok(group)
}
Expand Down
3 changes: 3 additions & 0 deletions crates/apub/src/protocol/objects/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ pub struct Group {
pub(crate) language: Vec<LanguageTag>,
pub(crate) published: Option<DateTime<Utc>>,
pub(crate) updated: Option<DateTime<Utc>>,
pub(crate) only_followers_can_vote: Option<bool>,
}

impl Group {
Expand Down Expand Up @@ -122,6 +123,7 @@ impl Group {
posting_restricted_to_mods: self.posting_restricted_to_mods,
instance_id,
featured_url: self.featured.map(Into::into),
only_followers_can_vote: self.only_followers_can_vote,
}
}

Expand Down Expand Up @@ -152,6 +154,7 @@ impl Group {
moderators_url: self.attributed_to.map(Into::into),
posting_restricted_to_mods: self.posting_restricted_to_mods,
featured_url: self.featured.map(Into::into),
only_followers_can_vote: self.only_followers_can_vote,
}
}
}
17 changes: 15 additions & 2 deletions crates/db_schema/src/impls/community.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ use crate::{
use diesel::{
deserialize,
dsl,
dsl::insert_into,
dsl::{exists, insert_into},
pg::Pg,
result::Error,
select,
sql_types,
ExpressionMethods,
NullableExpressionMethods,
Expand Down Expand Up @@ -235,14 +236,26 @@ impl CommunityFollower {
remote_community_id: CommunityId,
) -> Result<bool, Error> {
use crate::schema::community_follower::dsl::{community_follower, community_id};
use diesel::dsl::{exists, select};
let conn = &mut get_conn(pool).await?;
select(exists(
community_follower.filter(community_id.eq(remote_community_id)),
))
.get_result(conn)
.await
}

pub async fn is_follower(
pool: &mut DbPool<'_>,
person_id: PersonId,
community_id: CommunityId,
) -> Result<bool, Error> {
use crate::schema::community_follower::dsl::community_follower;
let conn = &mut get_conn(pool).await?;

select(exists(community_follower.find((person_id, community_id))))
.get_result(conn)
.await
}
}

impl Queryable<sql_types::Nullable<sql_types::Bool>, Pg> for SubscribedType {
Expand Down
1 change: 1 addition & 0 deletions crates/db_schema/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ diesel::table! {
moderators_url -> Nullable<Varchar>,
#[max_length = 255]
featured_url -> Nullable<Varchar>,
only_followers_can_vote -> Bool,
}
}

Expand Down
3 changes: 3 additions & 0 deletions crates/db_schema/src/source/community.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ pub struct Community {
/// Url where featured posts collection is served over Activitypub
#[serde(skip)]
pub featured_url: Option<DbUrl>,
pub only_followers_can_vote: bool,
}

#[derive(Debug, Clone, TypedBuilder)]
Expand Down Expand Up @@ -99,6 +100,7 @@ pub struct CommunityInsertForm {
pub posting_restricted_to_mods: Option<bool>,
#[builder(!default)]
pub instance_id: InstanceId,
pub only_followers_can_vote: Option<bool>,
}

#[derive(Debug, Clone, Default)]
Expand Down Expand Up @@ -126,6 +128,7 @@ pub struct CommunityUpdateForm {
pub featured_url: Option<DbUrl>,
pub hidden: Option<bool>,
pub posting_restricted_to_mods: Option<bool>,
pub only_followers_can_vote: Option<bool>,
}

#[derive(PartialEq, Eq, Debug)]
Expand Down

This file was deleted.

This file was deleted.

2 changes: 2 additions & 0 deletions migrations/2024-01-22-105746_lemmynsfw-changes/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
alter table local_site drop column content_warning;
alter table community drop column only_followers_can_vote;
2 changes: 2 additions & 0 deletions migrations/2024-01-22-105746_lemmynsfw-changes/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
alter table local_site add column content_warning text;
alter table community add column only_followers_can_vote boolean not null default false;

0 comments on commit d8aa6b3

Please sign in to comment.