-
-
Notifications
You must be signed in to change notification settings - Fork 885
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixes #2900 - Checks slur regex to see if it is too permissive #3146
Changes from 11 commits
a51ffe6
ca6c519
69b9b79
6836080
c01e944
3569b0f
3526feb
aba61cf
753d50c
33eff7e
cf1f0e4
2613892
9b883f2
597d8f1
cfb1f0f
eea4b20
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,9 +8,7 @@ use lemmy_api_common::{ | |
generate_site_inbox_url, | ||
is_admin, | ||
local_site_rate_limit_to_rate_limit_config, | ||
local_site_to_slur_regex, | ||
local_user_view_from_jwt, | ||
site_description_length_check, | ||
}, | ||
}; | ||
use lemmy_db_schema::{ | ||
|
@@ -26,10 +24,15 @@ use lemmy_db_schema::{ | |
}; | ||
use lemmy_db_views::structs::SiteView; | ||
use lemmy_utils::{ | ||
error::LemmyError, | ||
error::{LemmyError, LemmyResult}, | ||
utils::{ | ||
slurs::{check_slurs, check_slurs_opt}, | ||
validation::is_valid_body_field, | ||
validation::{ | ||
build_and_check_regex, | ||
is_valid_body_field, | ||
site_description_length_check, | ||
site_name_length_check, | ||
}, | ||
}, | ||
}; | ||
use url::Url; | ||
|
@@ -41,32 +44,13 @@ impl PerformCrud for CreateSite { | |
#[tracing::instrument(skip(context))] | ||
async fn perform(&self, context: &Data<LemmyContext>) -> Result<SiteResponse, LemmyError> { | ||
let data: &CreateSite = self; | ||
|
||
let local_site = LocalSite::read(context.pool()).await?; | ||
|
||
if local_site.site_setup { | ||
return Err(LemmyError::from_message("site_already_exists")); | ||
}; | ||
|
||
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; | ||
let local_site = LocalSite::read(context.pool()).await?; | ||
|
||
let sidebar = diesel_option_overwrite(&data.sidebar); | ||
let description = diesel_option_overwrite(&data.description); | ||
let icon = diesel_option_overwrite_to_url(&data.icon)?; | ||
let banner = diesel_option_overwrite_to_url(&data.banner)?; | ||
|
||
let slur_regex = local_site_to_slur_regex(&local_site); | ||
check_slurs(&data.name, &slur_regex)?; | ||
check_slurs_opt(&data.description, &slur_regex)?; | ||
|
||
// Make sure user is an admin | ||
// Make sure user is an admin; other types of users should not create site data... | ||
is_admin(&local_user_view)?; | ||
|
||
if let Some(Some(desc)) = &description { | ||
site_description_length_check(desc)?; | ||
} | ||
|
||
is_valid_body_field(&data.sidebar)?; | ||
validate_create_payload(local_site.site_setup, local_site.slur_filter_regex, data)?; | ||
|
||
let application_question = diesel_option_overwrite(&data.application_question); | ||
check_application_question( | ||
|
@@ -81,10 +65,10 @@ impl PerformCrud for CreateSite { | |
let keypair = generate_actor_keypair()?; | ||
let site_form = SiteUpdateForm::builder() | ||
.name(Some(data.name.clone())) | ||
.sidebar(sidebar) | ||
.description(description) | ||
.icon(icon) | ||
.banner(banner) | ||
.sidebar(diesel_option_overwrite(&data.sidebar)) | ||
.description(diesel_option_overwrite(&data.description)) | ||
.icon(diesel_option_overwrite_to_url(&data.icon)?) | ||
.banner(diesel_option_overwrite_to_url(&data.banner)?) | ||
.actor_id(Some(actor_id)) | ||
.last_refreshed_at(Some(naive_now())) | ||
.inbox_url(inbox_url) | ||
|
@@ -157,3 +141,197 @@ impl PerformCrud for CreateSite { | |
}) | ||
} | ||
} | ||
|
||
fn validate_create_payload( | ||
is_site_setup: bool, | ||
local_site_slur_filter_regex: Option<String>, | ||
create_site: &CreateSite, | ||
) -> LemmyResult<()> { | ||
// Make sure the site hasn't already been set up... | ||
if is_site_setup { | ||
return Err(LemmyError::from_message("site_already_exists")); | ||
}; | ||
|
||
// Check that the slur regex compiles, and returns the regex if valid... | ||
// Prioritize using new slur regex from the request; if not provided, use the existing regex. | ||
let slur_regex = build_and_check_regex( | ||
&create_site | ||
.slur_filter_regex | ||
.as_deref() | ||
.or(local_site_slur_filter_regex.as_deref()), | ||
)?; | ||
|
||
// Validate the site name... | ||
site_name_length_check(&create_site.name)?; | ||
check_slurs(&create_site.name, &slur_regex)?; | ||
|
||
// If provided, validate the site description... | ||
if let Some(desc) = &create_site.description { | ||
site_description_length_check(desc)?; | ||
check_slurs_opt(&create_site.description, &slur_regex)?; | ||
} | ||
|
||
// Ensure that the sidebar has fewer than the max num characters... | ||
is_valid_body_field(&create_site.sidebar) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::site::create::validate_create_payload; | ||
use lemmy_api_common::site::CreateSite; | ||
|
||
#[test] | ||
fn test_validate_create() { | ||
fn create_payload( | ||
site_name: String, | ||
site_description: Option<String>, | ||
site_sidebar: Option<String>, | ||
site_slur_filter_regex: Option<String>, | ||
) -> CreateSite { | ||
CreateSite { | ||
name: site_name, | ||
sidebar: site_sidebar, | ||
description: site_description, | ||
icon: None, | ||
banner: None, | ||
enable_downvotes: None, | ||
enable_nsfw: None, | ||
community_creation_admin_only: None, | ||
require_email_verification: None, | ||
application_question: None, | ||
private_instance: None, | ||
default_theme: None, | ||
default_post_listing_type: None, | ||
legal_information: None, | ||
application_email_admins: None, | ||
hide_modlog_mod_names: None, | ||
discussion_languages: None, | ||
slur_filter_regex: site_slur_filter_regex, | ||
actor_name_max_length: None, | ||
rate_limit_message: None, | ||
rate_limit_message_per_second: None, | ||
rate_limit_post: None, | ||
rate_limit_post_per_second: None, | ||
rate_limit_register: None, | ||
rate_limit_register_per_second: None, | ||
rate_limit_image: None, | ||
rate_limit_image_per_second: None, | ||
rate_limit_comment: None, | ||
rate_limit_comment_per_second: None, | ||
rate_limit_search: None, | ||
rate_limit_search_per_second: None, | ||
federation_enabled: None, | ||
federation_debug: None, | ||
federation_worker_count: None, | ||
captcha_enabled: None, | ||
captcha_difficulty: None, | ||
allowed_instances: None, | ||
blocked_instances: None, | ||
taglines: None, | ||
registration_mode: None, | ||
auth: Default::default(), | ||
} | ||
} | ||
|
||
let invalid_payloads = [ | ||
( | ||
false, | ||
&Some(String::from("(foo|bar)")), | ||
&create_payload( | ||
String::from("foo site_name"), | ||
None::<String>, | ||
None::<String>, | ||
None::<String>, | ||
), | ||
"slurs", | ||
), | ||
( | ||
false, | ||
&Some(String::from("(foo|bar)")), | ||
&create_payload( | ||
String::from("zeta site_name"), | ||
None::<String>, | ||
None::<String>, | ||
Some(String::from("(zeta|alpha)")), | ||
), | ||
"slurs", | ||
), | ||
( | ||
true, | ||
&None::<String>, | ||
&create_payload( | ||
String::from("site_name"), | ||
None::<String>, | ||
None::<String>, | ||
None::<String>, | ||
), | ||
"site_already_exists", | ||
), | ||
]; | ||
let valid_payloads = [ | ||
( | ||
false, | ||
&None::<String>, | ||
&create_payload( | ||
String::from("site_name"), | ||
None::<String>, | ||
None::<String>, | ||
None::<String>, | ||
), | ||
), | ||
( | ||
false, | ||
&None::<String>, | ||
&create_payload( | ||
String::from("site_name"), | ||
Some(String::new()), | ||
Some(String::new()), | ||
None::<String>, | ||
), | ||
), | ||
( | ||
false, | ||
&Some(String::from("(foo|bar)")), | ||
&create_payload( | ||
String::from("foo site_name"), | ||
Some(String::new()), | ||
Some(String::new()), | ||
Some(String::new()), | ||
), | ||
), | ||
]; | ||
|
||
invalid_payloads.iter().enumerate().for_each( | ||
|(idx, &(is_site_setup, local_slur_filter_regex, create_site, expected_err))| { | ||
match validate_create_payload(is_site_setup, local_slur_filter_regex.clone(), create_site) { | ||
Ok(_) => { | ||
panic!( | ||
"Got Ok, but validation should have failed with error: {} for invalid_payloads.nth({})", | ||
expected_err, idx | ||
) | ||
} | ||
Err(error) => { | ||
assert!( | ||
error.message.eq(&Some(String::from(expected_err))), | ||
"Got Err {:?}, but should have failed with message: {} for invalid_payloads.nth({})", | ||
error.message, | ||
expected_err, | ||
idx | ||
) | ||
} | ||
} | ||
}, | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find it pretty confusing to loop through tests like this. Would be much clearer to have a helper function and call that explicitly with each set of params. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Nutomic Trying to understand your feedback here - are you saying you'd rather have a test per case, and call match validate_update_payload(local_site_slur_filter_regex.clone(), false, true, edit_site)
{
Ok(_) => {
panic!(
"Got Ok, but validation should have failed with error: {} for invalid_payloads.nth({})",
expected_err, idx
)
}
Err(error) => {
assert!(
error.message.eq(&Some(String::from(expected_err))),
"Got Err {:?}, but should have failed with message: {} for invalid_payloads.nth({})",
error.message,
expected_err,
idx
)
}
} as the helper function? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that you write it out, it doesnt seem to make much difference. Anyway the errors are being logged so it should be fine. Just resolve the conflicts and we can merge. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had started cleaning up the validation some more in anticipation of putting up a second PR, but at this point I just pushed it all up. I also updated the tests so that there's now a reason included in the tuples, which should make it easier to understand each case and not need to make a separate test for each one. |
||
|
||
valid_payloads.iter().enumerate().for_each( | ||
|(idx, &(is_site_setup, local_slur_filter_regex, create_site))| { | ||
assert!( | ||
validate_create_payload(is_site_setup, local_slur_filter_regex.clone(), create_site) | ||
.is_ok(), | ||
"Got Err, but should have got Ok for valid_payloads.nth({})", | ||
idx | ||
); | ||
}, | ||
) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to move this into the validation fn in the next PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good