Merge branch 'tylerlowrey/add-ban-unban-kick-cmds' into 'master'

Tylerlowrey/add ban unban kick cmds

See merge request veloren/veloren!1382
This commit is contained in:
Joshua Yanovski 2020-09-14 07:18:33 +00:00
commit 8d829f9c69
14 changed files with 273 additions and 28 deletions

View File

@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Beehives and bees
- Fireflies
- Fullscreen modes now show two options (exclusive and borderless)
- Added banlist and `/ban`, `/unban`, and `/kick` commands for admins
### Changed

View File

@ -141,6 +141,8 @@ https://account.veloren.net."#,
"main.login.invalid_character": "The selected character is invalid",
"main.login.client_crashed": "Client crashed",
"main.login.not_on_whitelist": "You need a Whitelist entry by an Admin to join",
"main.login.banned": "You have been banned with the following reason",
"main.login.kicked": "You have been kicked with the following reason",
/// End Main screen section

View File

@ -16,6 +16,7 @@ pub enum Error {
AuthErr(String),
AuthClientError(AuthClientError),
AuthServerNotTrusted,
Banned(String),
/// Persisted character data is invalid or missing
InvalidCharacter,
//TODO: InvalidAlias,

View File

@ -23,9 +23,9 @@ use common::{
InventoryManip, InventoryUpdateEvent,
},
msg::{
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, InviteAnswer,
Notification, PlayerInfo, PlayerListUpdate, RegisterError, RequestStateError, ServerInfo,
ServerMsg, MAX_BYTES_CHAT_MSG,
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, DisconnectReason,
InviteAnswer, Notification, PlayerInfo, PlayerListUpdate, RegisterError, RequestStateError,
ServerInfo, ServerMsg, MAX_BYTES_CHAT_MSG,
},
outcome::Outcome,
recipe::RecipeBook,
@ -59,6 +59,7 @@ pub enum Event {
Disconnect,
DisconnectionNotification(u64),
InventoryUpdated(InventoryUpdateEvent),
Kicked(String),
Notification(Notification),
SetViewDistance(u32),
Outcome(Outcome),
@ -455,6 +456,7 @@ impl Client {
RegisterError::AuthError(err) => Error::AuthErr(err),
RegisterError::InvalidCharacter => Error::InvalidCharacter,
RegisterError::NotOnWhitelist => Error::NotOnWhitelist,
RegisterError::Banned(reason) => Error::Banned(reason),
});
},
ServerMsg::StateAnswer(Ok(ClientState::Registered)) => break Ok(()),
@ -1108,7 +1110,20 @@ impl Client {
ServerMsg::TooManyPlayers => {
return Err(Error::ServerWentMad);
},
ServerMsg::Shutdown => return Err(Error::ServerShutdown),
ServerMsg::Disconnect(reason) => match reason {
DisconnectReason::Shutdown => return Err(Error::ServerShutdown),
DisconnectReason::Requested => {
debug!("finally sending ClientMsg::Terminate");
frontend_events.push(Event::Disconnect);
self.singleton_stream.send(ClientMsg::Terminate)?;
break Ok(());
},
DisconnectReason::Kicked(reason) => {
debug!("sending ClientMsg::Terminate because we got kicked");
frontend_events.push(Event::Kicked(reason.clone()));
self.singleton_stream.send(ClientMsg::Terminate)?;
},
},
ServerMsg::InitialSync { .. } => return Err(Error::ServerWentMad),
ServerMsg::PlayerListUpdate(PlayerListUpdate::Init(list)) => {
self.player_list = list
@ -1388,12 +1403,6 @@ impl Client {
error, state
);
},
ServerMsg::Disconnect => {
debug!("finally sending ClientMsg::Terminate");
frontend_events.push(Event::Disconnect);
self.singleton_stream.send(ClientMsg::Terminate)?;
break Ok(());
},
ServerMsg::CharacterListUpdate(character_list) => {
self.character_list.characters = character_list;
self.character_list.loading = false;

View File

@ -37,6 +37,7 @@ impl ChatCommandData {
pub enum ChatCommand {
Adminify,
Alias,
Ban,
Build,
Campfire,
Debug,
@ -52,6 +53,7 @@ pub enum ChatCommand {
Help,
JoinFaction,
Jump,
Kick,
Kill,
KillNpcs,
Lantern,
@ -70,6 +72,7 @@ pub enum ChatCommand {
Tell,
Time,
Tp,
Unban,
Version,
Waypoint,
Whitelist,
@ -80,6 +83,7 @@ pub enum ChatCommand {
pub static CHAT_COMMANDS: &[ChatCommand] = &[
ChatCommand::Adminify,
ChatCommand::Alias,
ChatCommand::Ban,
ChatCommand::Build,
ChatCommand::Campfire,
ChatCommand::Debug,
@ -95,6 +99,7 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[
ChatCommand::Help,
ChatCommand::JoinFaction,
ChatCommand::Jump,
ChatCommand::Kick,
ChatCommand::Kill,
ChatCommand::KillNpcs,
ChatCommand::Lantern,
@ -113,6 +118,7 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[
ChatCommand::Tell,
ChatCommand::Time,
ChatCommand::Tp,
ChatCommand::Unban,
ChatCommand::Version,
ChatCommand::Waypoint,
ChatCommand::Whitelist,
@ -193,6 +199,11 @@ impl ChatCommand {
Admin,
),
ChatCommand::Alias => cmd(vec![Any("name", Required)], "Change your alias", NoAdmin),
ChatCommand::Ban => cmd(
vec![Any("username", Required), Message(Optional)],
"Ban a player with a given username",
Admin,
),
ChatCommand::Build => cmd(vec![], "Toggles build mode on and off", Admin),
ChatCommand::Campfire => cmd(vec![], "Spawns a campfire", Admin),
ChatCommand::Debug => cmd(vec![], "Place all debug items into your pack.", Admin),
@ -263,6 +274,11 @@ impl ChatCommand {
"Offset your current position",
Admin,
),
ChatCommand::Kick => cmd(
vec![Any("username", Required), Message(Optional)],
"Kick a player with a given username",
Admin,
),
ChatCommand::Kill => cmd(vec![], "Kill yourself", NoAdmin),
ChatCommand::KillNpcs => cmd(vec![], "Kill the NPCs", Admin),
ChatCommand::Lantern => cmd(
@ -357,6 +373,11 @@ impl ChatCommand {
"Teleport to another player",
Admin,
),
ChatCommand::Unban => cmd(
vec![Any("username", Required)],
"Remove the ban for the given username",
Admin,
),
ChatCommand::Version => cmd(vec![], "Prints server version", NoAdmin),
ChatCommand::Waypoint => {
cmd(vec![], "Set your waypoint to your current position", Admin)
@ -379,6 +400,7 @@ impl ChatCommand {
match self {
ChatCommand::Adminify => "adminify",
ChatCommand::Alias => "alias",
ChatCommand::Ban => "ban",
ChatCommand::Build => "build",
ChatCommand::Campfire => "campfire",
ChatCommand::Debug => "debug",
@ -394,6 +416,7 @@ impl ChatCommand {
ChatCommand::JoinFaction => "join_faction",
ChatCommand::Help => "help",
ChatCommand::Jump => "jump",
ChatCommand::Kick => "kick",
ChatCommand::Kill => "kill",
ChatCommand::KillNpcs => "kill_npcs",
ChatCommand::Lantern => "lantern",
@ -412,6 +435,7 @@ impl ChatCommand {
ChatCommand::Tell => "tell",
ChatCommand::Time => "time",
ChatCommand::Tp => "tp",
ChatCommand::Unban => "unban",
ChatCommand::Version => "version",
ChatCommand::Waypoint => "waypoint",
ChatCommand::Whitelist => "whitelist",

View File

@ -7,8 +7,8 @@ pub use self::{
client::ClientMsg,
ecs_packet::EcsCompPacket,
server::{
CharacterInfo, InviteAnswer, Notification, PlayerInfo, PlayerListUpdate, RegisterError,
RequestStateError, ServerInfo, ServerMsg,
CharacterInfo, DisconnectReason, InviteAnswer, Notification, PlayerInfo, PlayerListUpdate,
RegisterError, RequestStateError, ServerInfo, ServerMsg,
},
};
use serde::{Deserialize, Serialize};

View File

@ -182,6 +182,16 @@ pub enum Notification {
WaypointSaved,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DisconnectReason {
/// Server shut down
Shutdown,
/// Client sent disconnect message
Requested,
/// Client was kicked
Kicked(String),
}
/// Messages sent from the server to the client
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ServerMsg {
@ -238,8 +248,7 @@ pub enum ServerMsg {
chunk: Result<Box<TerrainChunk>, ()>,
},
TerrainBlockUpdates(HashMap<Vec3<i32>, Block>),
Disconnect,
Shutdown,
Disconnect(DisconnectReason),
TooManyPlayers,
/// Send a popup notification such as "Waypoint Saved"
Notification(Notification),
@ -260,6 +269,7 @@ pub enum RequestStateError {
pub enum RegisterError {
AlreadyLoggedIn,
AuthError(String),
Banned(String),
InvalidCharacter,
NotOnWhitelist,
//TODO: InvalidAlias,

View File

@ -9,7 +9,7 @@ use common::{
cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS},
comp::{self, item::ItemAsset, ChatType, Item, LightEmitter, WaypointArea},
event::{EventBus, ServerEvent},
msg::{Notification, PlayerListUpdate, ServerMsg},
msg::{DisconnectReason, Notification, PlayerListUpdate, ServerMsg},
npc::{self, get_npc_name},
state::TimeOfDay,
sync::{Uid, WorldSyncExt},
@ -24,6 +24,7 @@ use std::convert::TryFrom;
use vek::*;
use world::util::Sampler;
use crate::login_provider::LoginProvider;
use scan_fmt::{scan_fmt, scan_fmt_some};
use tracing::error;
@ -65,6 +66,7 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler {
match cmd {
ChatCommand::Adminify => handle_adminify,
ChatCommand::Alias => handle_alias,
ChatCommand::Ban => handle_ban,
ChatCommand::Build => handle_build,
ChatCommand::Campfire => handle_spawn_campfire,
ChatCommand::Debug => handle_debug,
@ -80,6 +82,7 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler {
ChatCommand::Help => handle_help,
ChatCommand::JoinFaction => handle_join_faction,
ChatCommand::Jump => handle_jump,
ChatCommand::Kick => handle_kick,
ChatCommand::Kill => handle_kill,
ChatCommand::KillNpcs => handle_kill_npcs,
ChatCommand::Lantern => handle_lantern,
@ -98,6 +101,7 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler {
ChatCommand::Tell => handle_tell,
ChatCommand::Time => handle_time,
ChatCommand::Tp => handle_tp,
ChatCommand::Unban => handle_unban,
ChatCommand::Version => handle_version,
ChatCommand::Waypoint => handle_waypoint,
ChatCommand::Whitelist => handle_whitelist,
@ -1810,3 +1814,159 @@ fn handle_whitelist(
);
}
}
fn kick_player(server: &mut Server, target_player: EcsEntity, reason: &str) {
server
.state
.ecs()
.read_resource::<EventBus<ServerEvent>>()
.emit_now(ServerEvent::ClientDisconnect(target_player));
server.notify_client(
target_player,
ServerMsg::Disconnect(DisconnectReason::Kicked(reason.to_string())),
);
}
fn handle_kick(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: String,
action: &ChatCommand,
) {
if let (Some(target_alias), reason_opt) =
scan_fmt_some!(&args, &action.arg_fmt(), String, String)
{
let reason = reason_opt.unwrap_or_default();
let ecs = server.state.ecs();
let target_player_opt = (&ecs.entities(), &ecs.read_storage::<comp::Player>())
.join()
.find(|(_, player)| player.alias == target_alias)
.map(|(entity, _)| entity);
if let Some(target_player) = target_player_opt {
kick_player(server, target_player, &reason);
server.notify_client(
client,
ChatType::CommandInfo.server_msg(format!(
"Kicked {} from the server with reason: {}",
target_alias, reason
)),
);
} else {
server.notify_client(
client,
ChatType::CommandError
.server_msg(format!("Player with alias {} not found", target_alias)),
)
}
} else {
server.notify_client(
client,
ChatType::CommandError.server_msg(action.help_string()),
);
}
}
fn handle_ban(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: String,
action: &ChatCommand,
) {
if let (Some(target_alias), reason_opt) =
scan_fmt_some!(&args, &action.arg_fmt(), String, String)
{
let reason = reason_opt.unwrap_or_default();
let uuid_result = server
.state
.ecs()
.read_resource::<LoginProvider>()
.username_to_uuid(&target_alias);
if let Ok(uuid) = uuid_result {
if server.settings().banlist.contains_key(&uuid) {
server.notify_client(
client,
ChatType::CommandError
.server_msg(format!("{} is already on the banlist", target_alias)),
)
} else {
server.settings_mut().edit(|s| {
s.banlist
.insert(uuid, (target_alias.clone(), reason.clone()));
});
server.notify_client(
client,
ChatType::CommandInfo.server_msg(format!(
"Added {} to the banlist with reason: {}",
target_alias, reason
)),
);
// If the player is online kick them
let ecs = server.state.ecs();
let target_player_opt = (&ecs.entities(), &ecs.read_storage::<comp::Player>())
.join()
.find(|(_, player)| player.alias == target_alias)
.map(|(entity, _)| entity);
if let Some(target_player) = target_player_opt {
kick_player(server, target_player, &reason);
}
}
} else {
server.notify_client(
client,
ChatType::CommandError.server_msg(format!(
"Unable to determine UUID for username \"{}\"",
target_alias
)),
)
}
} else {
server.notify_client(
client,
ChatType::CommandError.server_msg(action.help_string()),
);
}
}
fn handle_unban(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: String,
action: &ChatCommand,
) {
if let Ok(username) = scan_fmt!(&args, &action.arg_fmt(), String) {
let uuid_result = server
.state
.ecs()
.read_resource::<LoginProvider>()
.username_to_uuid(&username);
if let Ok(uuid) = uuid_result {
server.settings_mut().edit(|s| {
s.banlist.remove(&uuid);
});
server.notify_client(
client,
ChatType::CommandInfo.server_msg(format!("{} was successfully unbanned", username)),
);
} else {
server.notify_client(
client,
ChatType::CommandError.server_msg(format!(
"Unable to determine UUID for username \"{}\"",
username
)),
)
}
} else {
server.notify_client(
client,
ChatType::CommandError.server_msg(action.help_string()),
);
}
}

View File

@ -34,7 +34,7 @@ use common::{
cmd::ChatCommand,
comp::{self, ChatType},
event::{EventBus, ServerEvent},
msg::{server::WorldMapMsg, ClientState, ServerInfo, ServerMsg},
msg::{server::WorldMapMsg, ClientState, DisconnectReason, ServerInfo, ServerMsg},
outcome::Outcome,
recipe::default_recipe_book,
state::{State, TimeOfDay},
@ -817,5 +817,8 @@ impl Server {
}
impl Drop for Server {
fn drop(&mut self) { self.state.notify_registered_clients(ServerMsg::Shutdown); }
fn drop(&mut self) {
self.state
.notify_registered_clients(ServerMsg::Disconnect(DisconnectReason::Shutdown));
}
}

View File

@ -1,4 +1,4 @@
use authc::{AuthClient, AuthToken, Uuid};
use authc::{AuthClient, AuthClientError, AuthToken, Uuid};
use common::msg::RegisterError;
use hashbrown::HashMap;
use std::str::FromStr;
@ -53,12 +53,19 @@ impl LoginProvider {
&mut self,
username_or_token: &str,
whitelist: &[String],
banlist: &HashMap<Uuid, (String, String)>,
) -> Result<(String, Uuid), RegisterError> {
self
// resolve user information
.query(username_or_token)
// if found, check name against whitelist or if user is admin
.and_then(|(username, uuid)| {
// user cannot join if they are listed on the banlist
if let Some(ban_record) = banlist.get(&uuid) {
// Pull reason string out of ban record and send a copy of it
return Err(RegisterError::Banned(ban_record.1.clone()));
}
// user can only join if he is admin, the whitelist is empty (everyone can join)
// or his name is in the whitelist
if !whitelist.is_empty() && !whitelist.contains(&username) {
@ -96,4 +103,11 @@ impl LoginProvider {
},
}
}
pub fn username_to_uuid(&self, username: &str) -> Result<Uuid, AuthClientError> {
self.auth_server.as_ref().map_or_else(
|| Ok(derive_uuid(username)),
|auth| auth.username_to_uuid(&username),
)
}
}

View File

@ -1,3 +1,5 @@
use authc::Uuid;
use hashbrown::HashMap;
use portpicker::pick_unused_port;
use serde::{Deserialize, Serialize};
use std::{fs, io::prelude::*, net::SocketAddr, path::PathBuf, time::Duration};
@ -20,6 +22,7 @@ pub struct ServerSettings {
pub start_time: f64,
pub admins: Vec<String>,
pub whitelist: Vec<String>,
pub banlist: HashMap<Uuid, (String, String)>,
/// When set to None, loads the default map file (if available); otherwise,
/// uses the value of the file options to decide how to proceed.
pub map_file: Option<FileOpts>,
@ -44,6 +47,7 @@ impl Default for ServerSettings {
map_file: None,
admins: Vec::new(),
whitelist: Vec::new(),
banlist: HashMap::new(),
persistence_db_dir: "saves".to_owned(),
max_view_distance: Some(30),
banned_words_files: Vec::new(),

View File

@ -11,7 +11,8 @@ use common::{
event::{EventBus, ServerEvent},
msg::{
validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientMsg, ClientState,
PlayerInfo, PlayerListUpdate, RequestStateError, ServerMsg, MAX_BYTES_CHAT_MSG,
DisconnectReason, PlayerInfo, PlayerListUpdate, RequestStateError, ServerMsg,
MAX_BYTES_CHAT_MSG,
},
span,
state::{BlockChange, Time},
@ -88,14 +89,17 @@ impl Sys {
view_distance,
token_or_username,
} => {
let (username, uuid) =
match login_provider.try_login(&token_or_username, &settings.whitelist) {
Err(err) => {
client.error_state(RequestStateError::RegisterDenied(err));
break Ok(());
},
Ok((username, uuid)) => (username, uuid),
};
let (username, uuid) = match login_provider.try_login(
&token_or_username,
&settings.whitelist,
&settings.banlist,
) {
Err(err) => {
client.error_state(RequestStateError::RegisterDenied(err));
break Ok(());
},
Ok((username, uuid)) => (username, uuid),
};
let vd =
view_distance.map(|vd| vd.min(settings.max_view_distance.unwrap_or(vd)));
@ -339,7 +343,7 @@ impl Sys {
ClientMsg::Ping => client.notify(ServerMsg::Pong),
ClientMsg::Pong => {},
ClientMsg::Disconnect => {
client.notify(ServerMsg::Disconnect);
client.notify(ServerMsg::Disconnect(DisconnectReason::Requested));
},
ClientMsg::Terminate => {
debug!(?entity, "Client send message to termitate session");

View File

@ -125,6 +125,11 @@ impl PlayState for MainMenuState {
client::Error::NotOnWhitelist => {
localized_strings.get("main.login.not_on_whitelist").into()
},
client::Error::Banned(reason) => format!(
"{}: {}",
localized_strings.get("main.login.banned"),
reason
),
client::Error::InvalidCharacter => {
localized_strings.get("main.login.invalid_character").into()
},

View File

@ -163,6 +163,14 @@ impl SessionState {
message,
});
},
client::Event::Kicked(reason) => {
global_state.info_message = Some(format!(
"{}: {}",
self.voxygen_i18n.get("main.login.kicked").to_string(),
reason
));
return Ok(TickAction::Disconnect);
},
client::Event::Notification(n) => {
self.hud.new_notification(n);
},