network/room: Moderation implementation
Currently consist of 4 moderation commands (kick, ban, unban and get ban list).
This commit is contained in:
parent
6c29d441f4
commit
38f86cce94
|
@ -48,6 +48,10 @@ public:
|
|||
mutable std::mutex member_mutex; ///< Mutex for locking the members list
|
||||
/// This should be a std::shared_mutex as soon as C++17 is supported
|
||||
|
||||
UsernameBanList username_ban_list; ///< List of banned usernames
|
||||
IPBanList ip_ban_list; ///< List of banned IP addresses
|
||||
mutable std::mutex ban_list_mutex; ///< Mutex for the ban lists
|
||||
|
||||
RoomImpl()
|
||||
: random_gen(std::random_device()()), NintendoOUI{0x00, 0x1F, 0x32, 0x00, 0x00, 0x00} {}
|
||||
|
||||
|
@ -68,6 +72,30 @@ public:
|
|||
*/
|
||||
void HandleJoinRequest(const ENetEvent* event);
|
||||
|
||||
/**
|
||||
* Parses and answers a kick request from a client.
|
||||
* Validates the permissions and that the given user exists and then kicks the member.
|
||||
*/
|
||||
void HandleModKickPacket(const ENetEvent* event);
|
||||
|
||||
/**
|
||||
* Parses and answers a ban request from a client.
|
||||
* Validates the permissions and bans the user (by forum username or IP).
|
||||
*/
|
||||
void HandleModBanPacket(const ENetEvent* event);
|
||||
|
||||
/**
|
||||
* Parses and answers a unban request from a client.
|
||||
* Validates the permissions and unbans the address.
|
||||
*/
|
||||
void HandleModUnbanPacket(const ENetEvent* event);
|
||||
|
||||
/**
|
||||
* Parses and answers a get ban list request from a client.
|
||||
* Validates the permissions and returns the ban list.
|
||||
*/
|
||||
void HandleModGetBanListPacket(const ENetEvent* event);
|
||||
|
||||
/**
|
||||
* Returns whether the nickname is valid, ie. isn't already taken by someone else in the room.
|
||||
*/
|
||||
|
@ -85,6 +113,11 @@ public:
|
|||
*/
|
||||
bool IsValidConsoleId(const std::string& console_id_hash) const;
|
||||
|
||||
/**
|
||||
* Returns whether a user has mod permissions.
|
||||
*/
|
||||
bool HasModPermission(const ENetPeer* client) const;
|
||||
|
||||
/**
|
||||
* Sends a ID_ROOM_IS_FULL message telling the client that the room is full.
|
||||
*/
|
||||
|
@ -122,6 +155,32 @@ public:
|
|||
*/
|
||||
void SendJoinSuccess(ENetPeer* client, MacAddress mac_address);
|
||||
|
||||
/**
|
||||
* Sends a IdHostKicked message telling the client that they have been kicked.
|
||||
*/
|
||||
void SendUserKicked(ENetPeer* client);
|
||||
|
||||
/**
|
||||
* Sends a IdHostBanned message telling the client that they have been banned.
|
||||
*/
|
||||
void SendUserBanned(ENetPeer* client);
|
||||
|
||||
/**
|
||||
* Sends a IdModPermissionDenied message telling the client that they do not have mod
|
||||
* permission.
|
||||
*/
|
||||
void SendModPermissionDenied(ENetPeer* client);
|
||||
|
||||
/**
|
||||
* Sends a IdModNoSuchUser message telling the client that the given user could not be found.
|
||||
*/
|
||||
void SendModNoSuchUser(ENetPeer* client);
|
||||
|
||||
/**
|
||||
* Sends the ban list in response to a client's request for getting ban list.
|
||||
*/
|
||||
void SendModBanListResponse(ENetPeer* client);
|
||||
|
||||
/**
|
||||
* Notifies the members that the room is closed,
|
||||
*/
|
||||
|
@ -202,6 +261,19 @@ void Room::RoomImpl::ServerLoop() {
|
|||
case IdChatMessage:
|
||||
HandleChatPacket(&event);
|
||||
break;
|
||||
// Moderation
|
||||
case IdModKick:
|
||||
HandleModKickPacket(&event);
|
||||
break;
|
||||
case IdModBan:
|
||||
HandleModBanPacket(&event);
|
||||
break;
|
||||
case IdModUnban:
|
||||
HandleModUnbanPacket(&event);
|
||||
break;
|
||||
case IdModGetBanList:
|
||||
HandleModGetBanListPacket(&event);
|
||||
break;
|
||||
}
|
||||
enet_packet_destroy(event.packet);
|
||||
break;
|
||||
|
@ -296,6 +368,29 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
|
|||
}
|
||||
member.user_data = verify_backend->LoadUserData(uid, token);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(ban_list_mutex);
|
||||
|
||||
// Check username ban
|
||||
if (!member.user_data.username.empty() &&
|
||||
std::find(username_ban_list.begin(), username_ban_list.end(),
|
||||
member.user_data.username) != username_ban_list.end()) {
|
||||
|
||||
SendUserBanned(event->peer);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check IP ban
|
||||
char ip_raw[256];
|
||||
enet_address_get_host_ip(&event->peer->address, ip_raw, sizeof(ip_raw) - 1);
|
||||
std::string ip = ip_raw;
|
||||
|
||||
if (std::find(ip_ban_list.begin(), ip_ban_list.end(), ip) != ip_ban_list.end()) {
|
||||
SendUserBanned(event->peer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Notify everyone that the user has joined.
|
||||
SendStatusMessage(IdMemberJoin, member.nickname, member.user_data.username);
|
||||
|
||||
|
@ -309,6 +404,153 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
|
|||
SendJoinSuccess(event->peer, preferred_mac);
|
||||
}
|
||||
|
||||
void Room::RoomImpl::HandleModKickPacket(const ENetEvent* event) {
|
||||
if (!HasModPermission(event->peer)) {
|
||||
SendModPermissionDenied(event->peer);
|
||||
return;
|
||||
}
|
||||
|
||||
Packet packet;
|
||||
packet.Append(event->packet->data, event->packet->dataLength);
|
||||
packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
|
||||
|
||||
std::string nickname;
|
||||
packet >> nickname;
|
||||
|
||||
std::string username;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(member_mutex);
|
||||
const auto target_member =
|
||||
std::find_if(members.begin(), members.end(),
|
||||
[&nickname](const auto& member) { return member.nickname == nickname; });
|
||||
if (target_member == members.end()) {
|
||||
SendModNoSuchUser(event->peer);
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify the kicked member
|
||||
SendUserKicked(target_member->peer);
|
||||
|
||||
username = target_member->user_data.username;
|
||||
|
||||
enet_peer_disconnect(target_member->peer, 0);
|
||||
members.erase(target_member);
|
||||
}
|
||||
|
||||
// Announce the change to all clients.
|
||||
SendStatusMessage(IdMemberKicked, nickname, username);
|
||||
BroadcastRoomInformation();
|
||||
}
|
||||
|
||||
void Room::RoomImpl::HandleModBanPacket(const ENetEvent* event) {
|
||||
if (!HasModPermission(event->peer)) {
|
||||
SendModPermissionDenied(event->peer);
|
||||
return;
|
||||
}
|
||||
|
||||
Packet packet;
|
||||
packet.Append(event->packet->data, event->packet->dataLength);
|
||||
packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
|
||||
|
||||
std::string nickname;
|
||||
packet >> nickname;
|
||||
|
||||
std::string username;
|
||||
std::string ip;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(member_mutex);
|
||||
const auto target_member =
|
||||
std::find_if(members.begin(), members.end(),
|
||||
[&nickname](const auto& member) { return member.nickname == nickname; });
|
||||
if (target_member == members.end()) {
|
||||
SendModNoSuchUser(event->peer);
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify the banned member
|
||||
SendUserBanned(target_member->peer);
|
||||
|
||||
nickname = target_member->nickname;
|
||||
username = target_member->user_data.username;
|
||||
|
||||
char ip_raw[256];
|
||||
enet_address_get_host_ip(&target_member->peer->address, ip_raw, sizeof(ip_raw) - 1);
|
||||
ip = ip_raw;
|
||||
|
||||
enet_peer_disconnect(target_member->peer, 0);
|
||||
members.erase(target_member);
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(ban_list_mutex);
|
||||
|
||||
if (!username.empty()) {
|
||||
// Ban the forum username
|
||||
if (std::find(username_ban_list.begin(), username_ban_list.end(), username) ==
|
||||
username_ban_list.end()) {
|
||||
|
||||
username_ban_list.emplace_back(username);
|
||||
}
|
||||
}
|
||||
|
||||
// Ban the member's IP as well
|
||||
if (std::find(ip_ban_list.begin(), ip_ban_list.end(), ip) == ip_ban_list.end()) {
|
||||
ip_ban_list.emplace_back(ip);
|
||||
}
|
||||
}
|
||||
|
||||
// Announce the change to all clients.
|
||||
SendStatusMessage(IdMemberBanned, nickname, username);
|
||||
BroadcastRoomInformation();
|
||||
}
|
||||
|
||||
void Room::RoomImpl::HandleModUnbanPacket(const ENetEvent* event) {
|
||||
if (!HasModPermission(event->peer)) {
|
||||
SendModPermissionDenied(event->peer);
|
||||
return;
|
||||
}
|
||||
|
||||
Packet packet;
|
||||
packet.Append(event->packet->data, event->packet->dataLength);
|
||||
packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
|
||||
|
||||
std::string address;
|
||||
packet >> address;
|
||||
|
||||
bool unbanned = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(ban_list_mutex);
|
||||
|
||||
auto it = std::find(username_ban_list.begin(), username_ban_list.end(), address);
|
||||
if (it != username_ban_list.end()) {
|
||||
unbanned = true;
|
||||
username_ban_list.erase(it);
|
||||
}
|
||||
|
||||
it = std::find(ip_ban_list.begin(), ip_ban_list.end(), address);
|
||||
if (it != ip_ban_list.end()) {
|
||||
unbanned = true;
|
||||
ip_ban_list.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
if (unbanned) {
|
||||
SendStatusMessage(IdAddressUnbanned, address, "");
|
||||
} else {
|
||||
SendModNoSuchUser(event->peer);
|
||||
}
|
||||
}
|
||||
|
||||
void Room::RoomImpl::HandleModGetBanListPacket(const ENetEvent* event) {
|
||||
if (!HasModPermission(event->peer)) {
|
||||
SendModPermissionDenied(event->peer);
|
||||
return;
|
||||
}
|
||||
|
||||
SendModBanListResponse(event->peer);
|
||||
}
|
||||
|
||||
bool Room::RoomImpl::IsValidNickname(const std::string& nickname) const {
|
||||
// A nickname is valid if it matches the regex and is not already taken by anybody else in the
|
||||
// room.
|
||||
|
@ -336,6 +578,22 @@ bool Room::RoomImpl::IsValidConsoleId(const std::string& console_id_hash) const
|
|||
});
|
||||
}
|
||||
|
||||
bool Room::RoomImpl::HasModPermission(const ENetPeer* client) const {
|
||||
if (room_information.host_username.empty())
|
||||
return false; // This room does not support moderation
|
||||
std::lock_guard<std::mutex> lock(member_mutex);
|
||||
const auto sending_member =
|
||||
std::find_if(members.begin(), members.end(),
|
||||
[client](const auto& member) { return member.peer == client; });
|
||||
if (sending_member == members.end()) {
|
||||
return false;
|
||||
}
|
||||
if (sending_member->user_data.username != room_information.host_username) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Room::RoomImpl::SendNameCollision(ENetPeer* client) {
|
||||
Packet packet;
|
||||
packet << static_cast<u8>(IdNameCollision);
|
||||
|
@ -407,6 +665,61 @@ void Room::RoomImpl::SendJoinSuccess(ENetPeer* client, MacAddress mac_address) {
|
|||
enet_host_flush(server);
|
||||
}
|
||||
|
||||
void Room::RoomImpl::SendUserKicked(ENetPeer* client) {
|
||||
Packet packet;
|
||||
packet << static_cast<u8>(IdHostKicked);
|
||||
|
||||
ENetPacket* enet_packet =
|
||||
enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
|
||||
enet_peer_send(client, 0, enet_packet);
|
||||
enet_host_flush(server);
|
||||
}
|
||||
|
||||
void Room::RoomImpl::SendUserBanned(ENetPeer* client) {
|
||||
Packet packet;
|
||||
packet << static_cast<u8>(IdHostBanned);
|
||||
|
||||
ENetPacket* enet_packet =
|
||||
enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
|
||||
enet_peer_send(client, 0, enet_packet);
|
||||
enet_host_flush(server);
|
||||
}
|
||||
|
||||
void Room::RoomImpl::SendModPermissionDenied(ENetPeer* client) {
|
||||
Packet packet;
|
||||
packet << static_cast<u8>(IdModPermissionDenied);
|
||||
|
||||
ENetPacket* enet_packet =
|
||||
enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
|
||||
enet_peer_send(client, 0, enet_packet);
|
||||
enet_host_flush(server);
|
||||
}
|
||||
|
||||
void Room::RoomImpl::SendModNoSuchUser(ENetPeer* client) {
|
||||
Packet packet;
|
||||
packet << static_cast<u8>(IdModNoSuchUser);
|
||||
|
||||
ENetPacket* enet_packet =
|
||||
enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
|
||||
enet_peer_send(client, 0, enet_packet);
|
||||
enet_host_flush(server);
|
||||
}
|
||||
|
||||
void Room::RoomImpl::SendModBanListResponse(ENetPeer* client) {
|
||||
Packet packet;
|
||||
packet << static_cast<u8>(IdModBanListResponse);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(ban_list_mutex);
|
||||
packet << username_ban_list;
|
||||
packet << ip_ban_list;
|
||||
}
|
||||
|
||||
ENetPacket* enet_packet =
|
||||
enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
|
||||
enet_peer_send(client, 0, enet_packet);
|
||||
enet_host_flush(server);
|
||||
}
|
||||
|
||||
void Room::RoomImpl::SendCloseMessage() {
|
||||
Packet packet;
|
||||
packet << static_cast<u8>(IdCloseRoom);
|
||||
|
@ -450,6 +763,7 @@ void Room::RoomImpl::BroadcastRoomInformation() {
|
|||
packet << room_information.member_slots;
|
||||
packet << room_information.port;
|
||||
packet << room_information.preferred_game;
|
||||
packet << room_information.host_username;
|
||||
|
||||
packet << static_cast<u32>(members.size());
|
||||
{
|
||||
|
@ -625,8 +939,10 @@ Room::~Room() = default;
|
|||
|
||||
bool Room::Create(const std::string& name, const std::string& description,
|
||||
const std::string& server_address, u16 server_port, const std::string& password,
|
||||
const u32 max_connections, const std::string& preferred_game,
|
||||
u64 preferred_game_id, std::unique_ptr<VerifyUser::Backend> verify_backend) {
|
||||
const u32 max_connections, const std::string& host_username,
|
||||
const std::string& preferred_game, u64 preferred_game_id,
|
||||
std::unique_ptr<VerifyUser::Backend> verify_backend,
|
||||
const Room::BanList& ban_list) {
|
||||
ENetAddress address;
|
||||
address.host = ENET_HOST_ANY;
|
||||
if (!server_address.empty()) {
|
||||
|
@ -648,8 +964,11 @@ bool Room::Create(const std::string& name, const std::string& description,
|
|||
room_impl->room_information.port = server_port;
|
||||
room_impl->room_information.preferred_game = preferred_game;
|
||||
room_impl->room_information.preferred_game_id = preferred_game_id;
|
||||
room_impl->room_information.host_username = host_username;
|
||||
room_impl->password = password;
|
||||
room_impl->verify_backend = std::move(verify_backend);
|
||||
room_impl->username_ban_list = ban_list.first;
|
||||
room_impl->ip_ban_list = ban_list.second;
|
||||
|
||||
room_impl->StartLoop();
|
||||
return true;
|
||||
|
@ -668,6 +987,11 @@ std::string Room::GetVerifyUID() const {
|
|||
return room_impl->verify_UID;
|
||||
}
|
||||
|
||||
Room::BanList Room::GetBanList() const {
|
||||
std::lock_guard<std::mutex> lock(room_impl->ban_list_mutex);
|
||||
return {room_impl->username_ban_list, room_impl->ip_ban_list};
|
||||
}
|
||||
|
||||
std::vector<Room::Member> Room::GetRoomMemberList() const {
|
||||
std::vector<Room::Member> member_list;
|
||||
std::lock_guard<std::mutex> lock(room_impl->member_mutex);
|
||||
|
|
|
@ -31,6 +31,7 @@ struct RoomInformation {
|
|||
u16 port; ///< The port of this room
|
||||
std::string preferred_game; ///< Game to advertise that you want to play
|
||||
u64 preferred_game_id; ///< Title ID for the advertised game
|
||||
std::string host_username; ///< Forum username of the host
|
||||
};
|
||||
|
||||
struct GameInfo {
|
||||
|
@ -62,12 +63,26 @@ enum RoomMessageTypes : u8 {
|
|||
IdRoomIsFull,
|
||||
IdConsoleIdCollision,
|
||||
IdStatusMessage,
|
||||
IdHostKicked,
|
||||
IdHostBanned,
|
||||
/// Moderation requests
|
||||
IdModKick,
|
||||
IdModBan,
|
||||
IdModUnban,
|
||||
IdModGetBanList,
|
||||
// Moderation responses
|
||||
IdModBanListResponse,
|
||||
IdModPermissionDenied,
|
||||
IdModNoSuchUser,
|
||||
};
|
||||
|
||||
/// Types of system status messages
|
||||
enum StatusMessageTypes : u8 {
|
||||
IdMemberJoin = 1, ///< Member joining
|
||||
IdMemberLeave, ///< Member leaving
|
||||
IdMemberJoin = 1, ///< Member joining
|
||||
IdMemberLeave, ///< Member leaving
|
||||
IdMemberKicked, ///< A member is kicked from the room
|
||||
IdMemberBanned, ///< A member is banned from the room
|
||||
IdAddressUnbanned, ///< A username / ip address is unbanned from the room
|
||||
};
|
||||
|
||||
/// This is what a server [person creating a server] would use.
|
||||
|
@ -115,6 +130,11 @@ public:
|
|||
*/
|
||||
bool HasPassword() const;
|
||||
|
||||
using UsernameBanList = std::vector<std::string>;
|
||||
using IPBanList = std::vector<std::string>;
|
||||
|
||||
using BanList = std::pair<UsernameBanList, IPBanList>;
|
||||
|
||||
/**
|
||||
* Creates the socket for this room. Will bind to default address if
|
||||
* server is empty string.
|
||||
|
@ -123,14 +143,21 @@ public:
|
|||
const std::string& server = "", u16 server_port = DefaultRoomPort,
|
||||
const std::string& password = "",
|
||||
const u32 max_connections = MaxConcurrentConnections,
|
||||
const std::string& preferred_game = "", u64 preferred_game_id = 0,
|
||||
std::unique_ptr<VerifyUser::Backend> verify_backend = nullptr);
|
||||
const std::string& host_username = "", const std::string& preferred_game = "",
|
||||
u64 preferred_game_id = 0,
|
||||
std::unique_ptr<VerifyUser::Backend> verify_backend = nullptr,
|
||||
const BanList& ban_list = {});
|
||||
|
||||
/**
|
||||
* Sets the verification GUID of the room.
|
||||
*/
|
||||
void SetVerifyUID(const std::string& uid);
|
||||
|
||||
/**
|
||||
* Gets the ban list (including banned forum usernames and IPs) of the room.
|
||||
*/
|
||||
BanList GetBanList() const;
|
||||
|
||||
/**
|
||||
* Destroys the socket
|
||||
*/
|
||||
|
|
Reference in New Issue