Handle invalid ban expires values (fixes #4045) (#4046)

* Handle invalid ban expires values (fixes #4045)

* Adding a few missing expire time checks. Fixing up time conversions. (#4051)

* Adding a few missing expire time checks. Fixing up time conversions.

* Increase settings export wait time.

* get rid of RemoveCommunity.expires

* fmt

* tests

---------

Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
This commit is contained in:
Nutomic 2023-10-17 19:25:35 +02:00 committed by GitHub
parent d827af725a
commit 6d27bfed08
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 91 additions and 62 deletions

View file

@ -4,7 +4,7 @@ use lemmy_api_common::{
community::{BanFromCommunity, BanFromCommunityResponse}, community::{BanFromCommunity, BanFromCommunityResponse},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData}, send_activity::{ActivityChannel, SendActivityData},
utils::{check_community_mod_action, remove_user_data_in_community}, utils::{check_community_mod_action, check_expire_time, remove_user_data_in_community},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
@ -22,7 +22,7 @@ use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::PersonView; use lemmy_db_views_actor::structs::PersonView;
use lemmy_utils::{ use lemmy_utils::{
error::{LemmyError, LemmyErrorExt, LemmyErrorType}, error::{LemmyError, LemmyErrorExt, LemmyErrorType},
utils::{time::naive_from_unix, validation::is_valid_body_field}, utils::validation::is_valid_body_field,
}; };
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
@ -33,7 +33,7 @@ pub async fn ban_from_community(
) -> Result<Json<BanFromCommunityResponse>, LemmyError> { ) -> Result<Json<BanFromCommunityResponse>, LemmyError> {
let banned_person_id = data.person_id; let banned_person_id = data.person_id;
let remove_data = data.remove_data.unwrap_or(false); let remove_data = data.remove_data.unwrap_or(false);
let expires = data.expires.map(naive_from_unix); let expires = check_expire_time(data.expires)?;
// Verify that only mods or admins can ban // Verify that only mods or admins can ban
check_community_mod_action( check_community_mod_action(

View file

@ -4,7 +4,7 @@ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
person::{BanPerson, BanPersonResponse}, person::{BanPerson, BanPersonResponse},
send_activity::{ActivityChannel, SendActivityData}, send_activity::{ActivityChannel, SendActivityData},
utils::{is_admin, remove_user_data}, utils::{check_expire_time, is_admin, remove_user_data},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
@ -18,8 +18,9 @@ use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::PersonView; use lemmy_db_views_actor::structs::PersonView;
use lemmy_utils::{ use lemmy_utils::{
error::{LemmyError, LemmyErrorExt, LemmyErrorType}, error::{LemmyError, LemmyErrorExt, LemmyErrorType},
utils::{time::naive_from_unix, validation::is_valid_body_field}, utils::validation::is_valid_body_field,
}; };
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
pub async fn ban_from_site( pub async fn ban_from_site(
data: Json<BanPerson>, data: Json<BanPerson>,
@ -31,7 +32,7 @@ pub async fn ban_from_site(
is_valid_body_field(&data.reason, false)?; is_valid_body_field(&data.reason, false)?;
let expires = data.expires.map(naive_from_unix); let expires = check_expire_time(data.expires)?;
let person = Person::update( let person = Person::update(
&mut context.pool(), &mut context.pool(),

View file

@ -180,7 +180,6 @@ pub struct RemoveCommunity {
pub community_id: CommunityId, pub community_id: CommunityId,
pub removed: bool, pub removed: bool,
pub reason: Option<String>, pub reason: Option<String>,
pub expires: Option<i64>,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default)]

View file

@ -6,6 +6,7 @@ use crate::{
}; };
use actix_web::cookie::{Cookie, SameSite}; use actix_web::cookie::{Cookie, SameSite};
use anyhow::Context; use anyhow::Context;
use chrono::{DateTime, Days, Local, TimeZone, Utc};
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::{CommunityId, DbUrl, PersonId, PostId}, newtypes::{CommunityId, DbUrl, PersonId, PostId},
source::{ source::{
@ -761,12 +762,40 @@ pub fn create_login_cookie(jwt: Sensitive<String>) -> Cookie<'static> {
cookie cookie
} }
/// Ensure that ban/block expiry is in valid range. If its in past, throw error. If its more
/// than 10 years in future, convert to permanent ban. Otherwise return the same value.
pub fn check_expire_time(expires_unix_opt: Option<i64>) -> LemmyResult<Option<DateTime<Utc>>> {
if let Some(expires_unix) = expires_unix_opt {
let expires = Utc
.timestamp_opt(expires_unix, 0)
.single()
.ok_or(LemmyErrorType::InvalidUnixTime)?;
limit_expire_time(expires)
} else {
Ok(None)
}
}
fn limit_expire_time(expires: DateTime<Utc>) -> LemmyResult<Option<DateTime<Utc>>> {
const MAX_BAN_TERM: Days = Days::new(10 * 365);
if expires < Local::now() {
Err(LemmyErrorType::BanExpirationInPast)?
} else if expires > Local::now() + MAX_BAN_TERM {
Ok(None)
} else {
Ok(Some(expires))
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)] #![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)] #![allow(clippy::indexing_slicing)]
use crate::utils::{honeypot_check, password_length_check}; use crate::utils::{honeypot_check, limit_expire_time, password_length_check};
use chrono::{Days, Utc};
#[test] #[test]
#[rustfmt::skip] #[rustfmt::skip]
@ -784,4 +813,25 @@ mod tests {
assert!(honeypot_check(&Some("1".to_string())).is_err()); assert!(honeypot_check(&Some("1".to_string())).is_err());
assert!(honeypot_check(&Some("message".to_string())).is_err()); assert!(honeypot_check(&Some("message".to_string())).is_err());
} }
#[test]
fn test_limit_ban_term() {
// Ban expires in past, should throw error
assert!(limit_expire_time(Utc::now() - Days::new(5)).is_err());
// Legitimate ban term, return same value
let fourteen_days = Utc::now() + Days::new(14);
assert_eq!(
limit_expire_time(fourteen_days).unwrap(),
Some(fourteen_days)
);
let nine_years = Utc::now() + Days::new(365 * 9);
assert_eq!(limit_expire_time(nine_years).unwrap(), Some(nine_years));
// Too long ban term, changes to None (permanent ban)
assert_eq!(
limit_expire_time(Utc::now() + Days::new(365 * 11)).unwrap(),
None
);
}
} }

View file

@ -15,10 +15,7 @@ use lemmy_db_schema::{
traits::Crud, traits::Crud,
}; };
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::{ use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
utils::time::naive_from_unix,
};
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
pub async fn remove_community( pub async fn remove_community(
@ -52,13 +49,11 @@ pub async fn remove_community(
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?; .with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?;
// Mod tables // Mod tables
let expires = data.expires.map(naive_from_unix);
let form = ModRemoveCommunityForm { let form = ModRemoveCommunityForm {
mod_person_id: local_user_view.person.id, mod_person_id: local_user_view.person.id,
community_id: data.community_id, community_id: data.community_id,
removed: Some(removed), removed: Some(removed),
reason: data.reason.clone(), reason: data.reason.clone(),
expires,
}; };
ModRemoveCommunity::create(&mut context.pool(), &form).await?; ModRemoveCommunity::create(&mut context.pool(), &form).await?;

View file

@ -11,7 +11,12 @@ use activitypub_federation::{
traits::{Actor, Object}, traits::{Actor, Object},
}; };
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use lemmy_api_common::{community::BanFromCommunity, context::LemmyContext, person::BanPerson}; use lemmy_api_common::{
community::BanFromCommunity,
context::LemmyContext,
person::BanPerson,
utils::check_expire_time,
};
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::CommunityId, newtypes::CommunityId,
source::{community::Community, person::Person, site::Site}, source::{community::Community, person::Person, site::Site},
@ -19,10 +24,7 @@ use lemmy_db_schema::{
utils::DbPool, utils::DbPool,
}; };
use lemmy_db_views::structs::SiteView; use lemmy_db_views::structs::SiteView;
use lemmy_utils::{ use lemmy_utils::error::{LemmyError, LemmyResult};
error::{LemmyError, LemmyResult},
utils::time::naive_from_unix,
};
use serde::Deserialize; use serde::Deserialize;
use url::Url; use url::Url;
@ -137,7 +139,7 @@ pub(crate) async fn send_ban_from_site(
context: Data<LemmyContext>, context: Data<LemmyContext>,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let site = SiteOrCommunity::Site(SiteView::read_local(&mut context.pool()).await?.site.into()); let site = SiteOrCommunity::Site(SiteView::read_local(&mut context.pool()).await?.site.into());
let expires = data.expires.map(naive_from_unix); let expires = check_expire_time(data.expires)?;
// if the action affects a local user, federate to other instances // if the action affects a local user, federate to other instances
if banned_user.local { if banned_user.local {
@ -177,7 +179,7 @@ pub(crate) async fn send_ban_from_community(
let community: ApubCommunity = Community::read(&mut context.pool(), community_id) let community: ApubCommunity = Community::read(&mut context.pool(), community_id)
.await? .await?
.into(); .into();
let expires = data.expires.map(naive_from_unix); let expires = check_expire_time(data.expires)?;
if data.ban { if data.ban {
BlockUser::send( BlockUser::send(

View file

@ -115,7 +115,6 @@ pub(in crate::activities) async fn receive_remove_action(
community_id: community.id, community_id: community.id,
removed: Some(true), removed: Some(true),
reason, reason,
expires: None,
}; };
ModRemoveCommunity::create(&mut context.pool(), &form).await?; ModRemoveCommunity::create(&mut context.pool(), &form).await?;
Community::update( Community::update(

View file

@ -107,7 +107,6 @@ impl UndoDelete {
community_id: community.id, community_id: community.id,
removed: Some(false), removed: Some(false),
reason: None, reason: None,
expires: None,
}; };
ModRemoveCommunity::create(&mut context.pool(), &form).await?; ModRemoveCommunity::create(&mut context.pool(), &form).await?;
Community::update( Community::update(

View file

@ -29,7 +29,7 @@ use lemmy_db_schema::{
}; };
use lemmy_utils::{ use lemmy_utils::{
error::{LemmyError, LemmyErrorType}, error::{LemmyError, LemmyErrorType},
utils::{markdown::markdown_to_html, slurs::remove_slurs, time::convert_datetime}, utils::{markdown::markdown_to_html, slurs::remove_slurs},
}; };
use std::ops::Deref; use std::ops::Deref;
use url::Url; use url::Url;
@ -113,8 +113,8 @@ impl Object for ApubComment {
media_type: Some(MediaTypeMarkdownOrHtml::Html), media_type: Some(MediaTypeMarkdownOrHtml::Html),
source: Some(Source::new(self.content.clone())), source: Some(Source::new(self.content.clone())),
in_reply_to, in_reply_to,
published: Some(convert_datetime(self.published)), published: Some(self.published),
updated: self.updated.map(convert_datetime), updated: self.updated,
tag: maa.tags, tag: maa.tags,
distinguished: Some(self.distinguished), distinguished: Some(self.distinguished),
language, language,

View file

@ -28,10 +28,7 @@ use lemmy_db_schema::{
traits::{ApubActor, Crud}, traits::{ApubActor, Crud},
}; };
use lemmy_db_views_actor::structs::CommunityFollowerView; use lemmy_db_views_actor::structs::CommunityFollowerView;
use lemmy_utils::{ use lemmy_utils::{error::LemmyError, utils::markdown::markdown_to_html};
error::LemmyError,
utils::{markdown::markdown_to_html, time::convert_datetime},
};
use std::ops::Deref; use std::ops::Deref;
use tracing::debug; use tracing::debug;
use url::Url; use url::Url;
@ -109,8 +106,8 @@ impl Object for ApubCommunity {
}), }),
public_key: self.public_key(), public_key: self.public_key(),
language, language,
published: Some(convert_datetime(self.published)), published: Some(self.published),
updated: self.updated.map(convert_datetime), updated: self.updated,
posting_restricted_to_mods: Some(self.posting_restricted_to_mods), posting_restricted_to_mods: Some(self.posting_restricted_to_mods),
attributed_to: Some(generate_moderators_url(&self.actor_id)?.into()), attributed_to: Some(generate_moderators_url(&self.actor_id)?.into()),
}; };

View file

@ -34,7 +34,6 @@ use lemmy_utils::{
utils::{ utils::{
markdown::markdown_to_html, markdown::markdown_to_html,
slurs::{check_slurs, check_slurs_opt}, slurs::{check_slurs, check_slurs_opt},
time::convert_datetime,
}, },
}; };
use std::ops::Deref; use std::ops::Deref;
@ -103,8 +102,8 @@ impl Object for ApubSite {
outbox: Url::parse(&format!("{}/site_outbox", self.actor_id))?, outbox: Url::parse(&format!("{}/site_outbox", self.actor_id))?,
public_key: self.public_key(), public_key: self.public_key(),
language, language,
published: convert_datetime(self.published), published: self.published,
updated: self.updated.map(convert_datetime), updated: self.updated,
}; };
Ok(instance) Ok(instance)
} }

View file

@ -35,7 +35,6 @@ use lemmy_utils::{
utils::{ utils::{
markdown::markdown_to_html, markdown::markdown_to_html,
slurs::{check_slurs, check_slurs_opt}, slurs::{check_slurs, check_slurs_opt},
time::convert_datetime,
}, },
}; };
use std::ops::Deref; use std::ops::Deref;
@ -107,13 +106,13 @@ impl Object for ApubPerson {
icon: self.avatar.clone().map(ImageObject::new), icon: self.avatar.clone().map(ImageObject::new),
image: self.banner.clone().map(ImageObject::new), image: self.banner.clone().map(ImageObject::new),
matrix_user_id: self.matrix_user_id.clone(), matrix_user_id: self.matrix_user_id.clone(),
published: Some(convert_datetime(self.published)), published: Some(self.published),
outbox: generate_outbox_url(&self.actor_id)?.into(), outbox: generate_outbox_url(&self.actor_id)?.into(),
endpoints: self.shared_inbox_url.clone().map(|s| Endpoints { endpoints: self.shared_inbox_url.clone().map(|s| Endpoints {
shared_inbox: s.into(), shared_inbox: s.into(),
}), }),
public_key: self.public_key(), public_key: self.public_key(),
updated: self.updated.map(convert_datetime), updated: self.updated,
inbox: self.inbox_url.clone().into(), inbox: self.inbox_url.clone().into(),
}; };
Ok(person) Ok(person)

View file

@ -43,7 +43,6 @@ use lemmy_utils::{
utils::{ utils::{
markdown::markdown_to_html, markdown::markdown_to_html,
slurs::{check_slurs_opt, remove_slurs}, slurs::{check_slurs_opt, remove_slurs},
time::convert_datetime,
validation::check_url_scheme, validation::check_url_scheme,
}, },
}; };
@ -127,8 +126,8 @@ impl Object for ApubPost {
comments_enabled: Some(!self.locked), comments_enabled: Some(!self.locked),
sensitive: Some(self.nsfw), sensitive: Some(self.nsfw),
language, language,
published: Some(convert_datetime(self.published)), published: Some(self.published),
updated: self.updated.map(convert_datetime), updated: self.updated,
audience: Some(community.actor_id.into()), audience: Some(community.actor_id.into()),
in_reply_to: None, in_reply_to: None,
}; };

View file

@ -22,7 +22,7 @@ use lemmy_db_schema::{
}; };
use lemmy_utils::{ use lemmy_utils::{
error::{LemmyError, LemmyErrorType}, error::{LemmyError, LemmyErrorType},
utils::{markdown::markdown_to_html, time::convert_datetime}, utils::markdown::markdown_to_html,
}; };
use std::ops::Deref; use std::ops::Deref;
use url::Url; use url::Url;
@ -86,8 +86,8 @@ impl Object for ApubPrivateMessage {
content: markdown_to_html(&self.content), content: markdown_to_html(&self.content),
media_type: Some(MediaTypeHtml::Html), media_type: Some(MediaTypeHtml::Html),
source: Some(Source::new(self.content.clone())), source: Some(Source::new(self.content.clone())),
published: Some(convert_datetime(self.published)), published: Some(self.published),
updated: self.updated.map(convert_datetime), updated: self.updated,
}; };
Ok(note) Ok(note)
} }

View file

@ -651,7 +651,6 @@ mod tests {
community_id: inserted_community.id, community_id: inserted_community.id,
reason: None, reason: None,
removed: None, removed: None,
expires: None,
}; };
let inserted_mod_remove_community = let inserted_mod_remove_community =
ModRemoveCommunity::create(pool, &mod_remove_community_form) ModRemoveCommunity::create(pool, &mod_remove_community_form)
@ -667,7 +666,6 @@ mod tests {
mod_person_id: inserted_mod.id, mod_person_id: inserted_mod.id,
reason: None, reason: None,
removed: true, removed: true,
expires: None,
when_: inserted_mod_remove_community.when_, when_: inserted_mod_remove_community.when_,
}; };

View file

@ -563,7 +563,6 @@ diesel::table! {
community_id -> Int4, community_id -> Int4,
reason -> Nullable<Text>, reason -> Nullable<Text>,
removed -> Bool, removed -> Bool,
expires -> Nullable<Timestamptz>,
when_ -> Timestamptz, when_ -> Timestamptz,
} }
} }

View file

@ -127,7 +127,6 @@ pub struct ModRemoveCommunity {
pub community_id: CommunityId, pub community_id: CommunityId,
pub reason: Option<String>, pub reason: Option<String>,
pub removed: bool, pub removed: bool,
pub expires: Option<DateTime<Utc>>,
pub when_: DateTime<Utc>, pub when_: DateTime<Utc>,
} }
@ -138,7 +137,6 @@ pub struct ModRemoveCommunityForm {
pub community_id: CommunityId, pub community_id: CommunityId,
pub reason: Option<String>, pub reason: Option<String>,
pub removed: Option<bool>, pub removed: Option<bool>,
pub expires: Option<DateTime<Utc>>,
} }
#[skip_serializing_none] #[skip_serializing_none]

View file

@ -221,6 +221,8 @@ pub enum LemmyErrorType {
/// Thrown when an API call is submitted with more than 1000 array elements, see [[MAX_API_PARAM_ELEMENTS]] /// Thrown when an API call is submitted with more than 1000 array elements, see [[MAX_API_PARAM_ELEMENTS]]
TooManyItems, TooManyItems,
CommunityHasNoFollowers, CommunityHasNoFollowers,
BanExpirationInPast,
InvalidUnixTime,
Unknown(String), Unknown(String),
} }

View file

@ -1,5 +1,4 @@
pub mod markdown; pub mod markdown;
pub mod mention; pub mod mention;
pub mod slurs; pub mod slurs;
pub mod time;
pub mod validation; pub mod validation;

View file

@ -1,12 +0,0 @@
use chrono::{DateTime, TimeZone, Utc};
pub fn naive_from_unix(time: i64) -> DateTime<Utc> {
Utc
.timestamp_opt(time, 0)
.single()
.expect("convert datetime")
}
pub fn convert_datetime(datetime: DateTime<Utc>) -> DateTime<Utc> {
datetime
}

View file

@ -0,0 +1,3 @@
ALTER TABLE mod_remove_community
ADD COLUMN expires timestamp;

View file

@ -0,0 +1,3 @@
ALTER TABLE mod_remove_community
DROP COLUMN expires;