Simplified and improved alias validation

This commit is contained in:
Joshua Barretto 2022-08-07 17:55:05 +01:00
parent 4d110a542c
commit 4e103c433f
4 changed files with 29 additions and 163 deletions

View File

@ -1,139 +0,0 @@
use common::character::MAX_NAME_LENGTH;
use std::fmt::{self, Display};
#[derive(Debug, Default)]
pub struct AliasValidator {
banned_substrings: Vec<String>,
}
impl AliasValidator {
pub fn new(banned_substrings: Vec<String>) -> Self {
let banned_substrings = banned_substrings
.iter()
.map(|string| string.to_lowercase())
.collect();
AliasValidator { banned_substrings }
}
pub fn validate(&self, alias: &str) -> Result<(), ValidatorError> {
if alias.len() > MAX_NAME_LENGTH {
return Err(ValidatorError::TooLong(alias.to_owned(), alias.len()));
}
let lowercase_alias = alias.to_lowercase();
for banned_word in self.banned_substrings.iter() {
if lowercase_alias.contains(banned_word) {
return Err(ValidatorError::Forbidden(
alias.to_owned(),
banned_word.to_owned(),
));
}
}
Ok(())
}
}
#[derive(Debug, PartialEq)]
pub enum ValidatorError {
Forbidden(String, String),
TooLong(String, usize),
}
impl Display for ValidatorError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Forbidden(name, _) => write!(
formatter,
"Character name \"{}\" contains a banned word",
name
),
Self::TooLong(name, _) => write!(formatter, "Character name \"{}\" too long", name),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn multiple_matches() {
let banned_substrings = vec!["bad".to_owned(), "worse".to_owned()];
let validator = AliasValidator::new(banned_substrings);
let bad_alias = "BadplayerMcWorseFace";
let result = validator.validate(bad_alias);
assert_eq!(
result,
Err(ValidatorError::Forbidden(
bad_alias.to_owned(),
"bad".to_owned()
))
);
}
#[test]
fn single_lowercase_match() {
let banned_substrings = vec!["blue".to_owned()];
let validator = AliasValidator::new(banned_substrings);
let bad_alias = "blueName";
let result = validator.validate(bad_alias);
assert_eq!(
result,
Err(ValidatorError::Forbidden(
bad_alias.to_owned(),
"blue".to_owned()
))
);
}
#[test]
fn single_case_insensitive_match() {
let banned_substrings = vec!["GrEEn".to_owned()];
let validator = AliasValidator::new(banned_substrings);
let bad_alias = "gReenName";
let result = validator.validate(bad_alias);
assert_eq!(
result,
Err(ValidatorError::Forbidden(
bad_alias.to_owned(),
"green".to_owned()
))
);
}
#[test]
fn mp_matches() {
let banned_substrings = vec!["orange".to_owned()];
let validator = AliasValidator::new(banned_substrings);
let good_alias = "ReasonableName";
let result = validator.validate(good_alias);
assert_eq!(result, Ok(()));
}
#[test]
fn too_long() {
let banned_substrings = vec!["orange".to_owned()];
let validator = AliasValidator::new(banned_substrings);
let bad_alias = "Thisnameistoolong Muchtoolong MuchTooLongByFar";
let result = validator.validate(bad_alias);
assert_eq!(
result,
Err(ValidatorError::TooLong(
bad_alias.to_owned(),
bad_alias.chars().count()
))
);
}
}

View File

@ -3,7 +3,10 @@ use authc::Uuid;
use censor::Censor;
use common::comp::AdminRole;
use hashbrown::HashMap;
use std::time::{Duration, Instant};
use std::{
sync::Arc,
time::{Duration, Instant},
};
use tracing::info;
pub const MAX_BYTES_CHAT_MSG: usize = 256;
@ -20,12 +23,12 @@ pub enum ActionErr {
pub struct AutoMod {
settings: ModerationSettings,
censor: Censor,
censor: Arc<Censor>,
players: HashMap<Uuid, PlayerState>,
}
impl AutoMod {
pub fn new(settings: &ModerationSettings, banned_words: Vec<String>) -> Self {
pub fn new(settings: &ModerationSettings, censor: Arc<Censor>) -> Self {
if settings.automod {
info!(
"Automod enabled, players{} will be subject to automated spam/content filters",
@ -41,7 +44,7 @@ impl AutoMod {
Self {
settings: settings.clone(),
censor: Censor::Custom(banned_words.into_iter().collect()),
censor,
players: HashMap::default(),
}
}

View File

@ -14,7 +14,6 @@
)]
#![cfg_attr(not(feature = "worldgen"), feature(const_panic))]
pub mod alias_validator;
pub mod automod;
mod character_creator;
pub mod chunk_generator;
@ -55,7 +54,6 @@ pub use crate::{
#[cfg(feature = "persistent_world")]
use crate::terrain_persistence::TerrainPersistence;
use crate::{
alias_validator::AliasValidator,
automod::AutoMod,
chunk_generator::ChunkGenerator,
client::Client,
@ -70,6 +68,7 @@ use crate::{
state_ext::StateExt,
sys::sentinel::{DeletedEntities, TrackedStorages},
};
use censor::Censor;
#[cfg(not(feature = "worldgen"))]
use common::grid::Grid;
use common::{
@ -340,17 +339,15 @@ impl Server {
state.ecs_mut().register::<login_provider::PendingLogin>();
state.ecs_mut().register::<RepositionOnChunkLoad>();
// Load banned words list
let banned_words = settings.moderation.load_banned_words(data_dir);
//Alias validator
state
.ecs_mut()
.insert(AliasValidator::new(banned_words.clone()));
let censor = Arc::new(Censor::Custom(banned_words.into_iter().collect()));
state.ecs_mut().insert(Arc::clone(&censor));
// Init automod
state
.ecs_mut()
.insert(AutoMod::new(&settings.moderation, banned_words));
.insert(AutoMod::new(&settings.moderation, censor));
#[cfg(feature = "worldgen")]
let (world, index) = World::generate(

View File

@ -1,5 +1,4 @@
use crate::{
alias_validator::AliasValidator,
automod::AutoMod,
character_creator,
client::Client,
@ -15,7 +14,7 @@ use common::{
use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::{ClientGeneral, ServerGeneral};
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, WriteExpect};
use std::sync::atomic::Ordering;
use std::sync::{atomic::Ordering, Arc};
use tracing::debug;
impl Sys {
@ -30,7 +29,7 @@ impl Sys {
admins: &ReadStorage<'_, Admin>,
presences: &ReadStorage<'_, Presence>,
editable_settings: &ReadExpect<'_, EditableSettings>,
alias_validator: &ReadExpect<'_, AliasValidator>,
censor: &ReadExpect<'_, Arc<censor::Censor>>,
automod: &AutoMod,
msg: ClientGeneral,
) -> Result<(), crate::error::Error> {
@ -138,9 +137,12 @@ impl Sys {
offhand,
body,
} => {
if let Err(error) = alias_validator.validate(&alias) {
debug!(?error, ?alias, "denied alias as it contained a banned word");
client.send(ServerGeneral::CharacterActionError(error.to_string()))?;
if censor.check(&alias) {
debug!(?alias, "denied alias as it contained a banned word");
client.send(ServerGeneral::CharacterActionError(format!(
"Alias '{}' contains a banned word",
alias
)))?;
} else if let Some(player) = players.get(entity) {
if let Err(error) = character_creator::create_character(
entity,
@ -163,9 +165,12 @@ impl Sys {
}
},
ClientGeneral::EditCharacter { id, alias, body } => {
if let Err(error) = alias_validator.validate(&alias) {
debug!(?error, ?alias, "denied alias as it contained a banned word");
client.send(ServerGeneral::CharacterActionError(error.to_string()))?;
if censor.check(&alias) {
debug!(?alias, "denied alias as it contained a banned word");
client.send(ServerGeneral::CharacterActionError(format!(
"Alias '{}' contains a banned word",
alias
)))?;
} else if let Some(player) = players.get(entity) {
if let Err(error) = character_creator::edit_character(
entity,
@ -220,7 +225,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Admin>,
ReadStorage<'a, Presence>,
ReadExpect<'a, EditableSettings>,
ReadExpect<'a, AliasValidator>,
ReadExpect<'a, Arc<censor::Censor>>,
ReadExpect<'a, AutoMod>,
);
@ -241,7 +246,7 @@ impl<'a> System<'a> for Sys {
admins,
presences,
editable_settings,
alias_validator,
censor,
automod,
): Self::SystemData,
) {
@ -260,7 +265,7 @@ impl<'a> System<'a> for Sys {
&admins,
&presences,
&editable_settings,
&alias_validator,
&censor,
&automod,
msg,
)