mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'T-Dark-char-alias-slur-check' into 'master'
Char alias slur check Closes #580 See merge request veloren/veloren!1200
This commit is contained in:
115
server/src/alias_validator.rs
Normal file
115
server/src/alias_validator.rs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
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> {
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 = "Badplayery Mc WorsePlayeryFace";
|
||||||
|
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(()));
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
#![allow(clippy::option_map_unit_fn)]
|
#![allow(clippy::option_map_unit_fn)]
|
||||||
#![feature(drain_filter, option_zip)]
|
#![feature(drain_filter, option_zip)]
|
||||||
|
|
||||||
|
pub mod alias_validator;
|
||||||
pub mod auth_provider;
|
pub mod auth_provider;
|
||||||
pub mod chunk_generator;
|
pub mod chunk_generator;
|
||||||
pub mod client;
|
pub mod client;
|
||||||
@ -20,6 +21,7 @@ pub mod sys;
|
|||||||
pub use crate::{error::Error, events::Event, input::Input, settings::ServerSettings};
|
pub use crate::{error::Error, events::Event, input::Input, settings::ServerSettings};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
alias_validator::AliasValidator,
|
||||||
auth_provider::AuthProvider,
|
auth_provider::AuthProvider,
|
||||||
chunk_generator::ChunkGenerator,
|
chunk_generator::ChunkGenerator,
|
||||||
client::{Client, RegionSubscription},
|
client::{Client, RegionSubscription},
|
||||||
@ -137,6 +139,30 @@ impl Server {
|
|||||||
state.ecs_mut().register::<RegionSubscription>();
|
state.ecs_mut().register::<RegionSubscription>();
|
||||||
state.ecs_mut().register::<Client>();
|
state.ecs_mut().register::<Client>();
|
||||||
|
|
||||||
|
//Alias validator
|
||||||
|
let banned_words_paths = &settings.banned_words_files;
|
||||||
|
let mut banned_words = Vec::new();
|
||||||
|
for path in banned_words_paths {
|
||||||
|
let mut list = match std::fs::File::open(&path) {
|
||||||
|
Ok(file) => match ron::de::from_reader(&file) {
|
||||||
|
Ok(vec) => vec,
|
||||||
|
Err(error) => {
|
||||||
|
tracing::warn!(?error, ?file, "Couldn't deserialize banned words file");
|
||||||
|
return Err(Error::Other(error.to_string()));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
tracing::warn!(?error, ?path, "couldn't open banned words file");
|
||||||
|
return Err(Error::Other(error.to_string()));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
banned_words.append(&mut list);
|
||||||
|
}
|
||||||
|
let banned_words_count = banned_words.len();
|
||||||
|
tracing::debug!(?banned_words_count);
|
||||||
|
tracing::trace!(?banned_words);
|
||||||
|
state.ecs_mut().insert(AliasValidator::new(banned_words));
|
||||||
|
|
||||||
#[cfg(feature = "worldgen")]
|
#[cfg(feature = "worldgen")]
|
||||||
let world = World::generate(settings.world_seed, WorldOpts {
|
let world = World::generate(settings.world_seed, WorldOpts {
|
||||||
seed_elements: true,
|
seed_elements: true,
|
||||||
|
@ -25,6 +25,7 @@ pub struct ServerSettings {
|
|||||||
pub map_file: Option<FileOpts>,
|
pub map_file: Option<FileOpts>,
|
||||||
pub persistence_db_dir: String,
|
pub persistence_db_dir: String,
|
||||||
pub max_view_distance: Option<u32>,
|
pub max_view_distance: Option<u32>,
|
||||||
|
pub banned_words_files: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ServerSettings {
|
impl Default for ServerSettings {
|
||||||
@ -63,6 +64,7 @@ impl Default for ServerSettings {
|
|||||||
whitelist: Vec::new(),
|
whitelist: Vec::new(),
|
||||||
persistence_db_dir: "saves".to_owned(),
|
persistence_db_dir: "saves".to_owned(),
|
||||||
max_view_distance: Some(30),
|
max_view_distance: Some(30),
|
||||||
|
banned_words_files: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use super::SysTimer;
|
use super::SysTimer;
|
||||||
use crate::{
|
use crate::{
|
||||||
auth_provider::AuthProvider, client::Client, persistence::character::CharacterLoader,
|
alias_validator::AliasValidator, auth_provider::AuthProvider, client::Client,
|
||||||
ServerSettings, CLIENT_TIMEOUT,
|
persistence::character::CharacterLoader, ServerSettings, CLIENT_TIMEOUT,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{
|
comp::{
|
||||||
@ -55,6 +55,7 @@ impl Sys {
|
|||||||
players: &mut WriteStorage<'_, Player>,
|
players: &mut WriteStorage<'_, Player>,
|
||||||
controllers: &mut WriteStorage<'_, Controller>,
|
controllers: &mut WriteStorage<'_, Controller>,
|
||||||
settings: &Read<'_, ServerSettings>,
|
settings: &Read<'_, ServerSettings>,
|
||||||
|
alias_validator: &ReadExpect<'_, AliasValidator>,
|
||||||
) -> Result<(), crate::error::Error> {
|
) -> Result<(), crate::error::Error> {
|
||||||
loop {
|
loop {
|
||||||
let msg = client.recv().await?;
|
let msg = client.recv().await?;
|
||||||
@ -347,7 +348,14 @@ impl Sys {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
ClientMsg::CreateCharacter { alias, tool, body } => {
|
ClientMsg::CreateCharacter { alias, tool, body } => {
|
||||||
if let Some(player) = players.get(entity) {
|
if let Err(error) = alias_validator.validate(&alias) {
|
||||||
|
tracing::debug!(
|
||||||
|
?error,
|
||||||
|
?alias,
|
||||||
|
"denied alias as it contained a banned word"
|
||||||
|
);
|
||||||
|
client.notify(ServerMsg::CharacterActionError(error.to_string()));
|
||||||
|
} else if let Some(player) = players.get(entity) {
|
||||||
character_loader.create_character(
|
character_loader.create_character(
|
||||||
entity,
|
entity,
|
||||||
player.uuid().to_string(),
|
player.uuid().to_string(),
|
||||||
@ -413,6 +421,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
WriteStorage<'a, Client>,
|
WriteStorage<'a, Client>,
|
||||||
WriteStorage<'a, Controller>,
|
WriteStorage<'a, Controller>,
|
||||||
Read<'a, ServerSettings>,
|
Read<'a, ServerSettings>,
|
||||||
|
ReadExpect<'a, AliasValidator>,
|
||||||
);
|
);
|
||||||
|
|
||||||
#[allow(clippy::match_ref_pats)] // TODO: Pending review in #587
|
#[allow(clippy::match_ref_pats)] // TODO: Pending review in #587
|
||||||
@ -443,6 +452,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
mut clients,
|
mut clients,
|
||||||
mut controllers,
|
mut controllers,
|
||||||
settings,
|
settings,
|
||||||
|
alias_validator,
|
||||||
): Self::SystemData,
|
): Self::SystemData,
|
||||||
) {
|
) {
|
||||||
timer.start();
|
timer.start();
|
||||||
@ -502,6 +512,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
&mut players,
|
&mut players,
|
||||||
&mut controllers,
|
&mut controllers,
|
||||||
&settings,
|
&settings,
|
||||||
|
&alias_validator,
|
||||||
).fuse() => err,
|
).fuse() => err,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user