Skip to content

Commit

Permalink
Refactor admin status, initial support for admins changing settings v…
Browse files Browse the repository at this point in the history
…ia UI

Store admin status as a per-player flag
  • Loading branch information
past-due committed Oct 25, 2024
1 parent 4468c15 commit 4cf8005
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 25 deletions.
6 changes: 6 additions & 0 deletions lib/netplay/netplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "src/console.h"
#include "src/component.h" // FIXME: we need to handle this better
#include "src/modding.h" // FIXME: we need to handle this better
#include "src/multilobbycommands.h"

#include <time.h> // for stats
#include <physfs.h>
Expand Down Expand Up @@ -668,6 +669,7 @@ void NET_InitPlayer(uint32_t i, bool initPosition, bool initTeams, bool initSpec
{
NetPlay.players[i].isSpectator = false;
}
NetPlay.players[i].isAdmin = false;
}

uint8_t NET_numHumanPlayers(void)
Expand Down Expand Up @@ -736,6 +738,7 @@ static void NETSendNPlayerInfoTo(uint32_t *index, uint32_t indexLen, unsigned to
NETint8_t(reinterpret_cast<int8_t*>(&NetPlay.players[index[n]].difficulty));
NETuint8_t(reinterpret_cast<uint8_t *>(&NetPlay.players[index[n]].faction));
NETbool(&NetPlay.players[index[n]].isSpectator);
NETbool(&NetPlay.players[index[n]].isAdmin);
}
NETend();
ActivityManager::instance().updateMultiplayGameData(game, ingame, NETGameIsLocked());
Expand Down Expand Up @@ -2533,6 +2536,7 @@ static bool NETprocessSystemMessage(NETQUEUE playerQueue, uint8_t *type)
int8_t difficulty = 0;
uint8_t faction = FACTION_NORMAL;
bool isSpectator = false;
bool isAdmin = false;
bool error = false;

NETbeginDecode(playerQueue, NET_PLAYER_INFO);
Expand Down Expand Up @@ -2577,6 +2581,7 @@ static bool NETprocessSystemMessage(NETQUEUE playerQueue, uint8_t *type)
NETint8_t(&difficulty);
NETuint8_t(&faction);
NETbool(&isSpectator);
NETbool(&isAdmin);

auto newFactionId = uintToFactionID(faction);
if (!newFactionId.has_value())
Expand All @@ -2596,6 +2601,7 @@ static bool NETprocessSystemMessage(NETQUEUE playerQueue, uint8_t *type)
NetPlay.players[index].difficulty = static_cast<AIDifficulty>(difficulty);
NetPlay.players[index].faction = newFactionId.value();
NetPlay.players[index].isSpectator = isSpectator;
NetPlay.players[index].isAdmin = isAdmin;
}

debug(LOG_NET, "%s for player %u (%s)", n == 0 ? "Receiving MSG_PLAYER_INFO" : " and", (unsigned int)index, NetPlay.players[index].allocated ? "human" : "AI");
Expand Down
2 changes: 2 additions & 0 deletions lib/netplay/netplay.h
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ struct PLAYER
bool autoGame; ///< if we are running a autogame (AI controls us)
FactionID faction; ///< which faction the player has
bool isSpectator; ///< whether this slot is a spectator slot
bool isAdmin; ///< whether this slot has admin privs

// used on host-ONLY (not transmitted to other clients):
std::shared_ptr<std::vector<WZFile>> wzFiles = std::make_shared<std::vector<WZFile>>(); ///< for each player, we keep track of map/mod download progress
Expand Down Expand Up @@ -302,6 +303,7 @@ struct PLAYER
IPtextAddress[0] = '\0';
faction = FACTION_NORMAL;
isSpectator = false;
isAdmin = false;
}
};

Expand Down
47 changes: 32 additions & 15 deletions src/multiint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1443,13 +1443,18 @@ static void addGameOptions()
updateLimitIcons();
}

static bool isHostOrAdmin()
{
return NetPlay.isHost || NetPlay.players[selectedPlayer].isAdmin;
}

// ////////////////////////////////////////////////////////////////////////////
// Colour functions

static bool safeToUseColour(unsigned player, unsigned otherPlayer)
{
// Player wants to take the colour from otherPlayer. May not take from a human otherPlayer, unless we're the host.
return player == otherPlayer || NetPlay.isHost || !isHumanPlayer(otherPlayer);
// Player wants to take the colour from otherPlayer. May not take from a human otherPlayer, unless we're the host/admin.
return player == otherPlayer || isHostOrAdmin() || !isHumanPlayer(otherPlayer);
}

static int getPlayerTeam(int i)
Expand Down Expand Up @@ -1982,7 +1987,7 @@ static std::set<uint32_t> validPlayerIdxTargetsForPlayerPositionMove(uint32_t pl
for (uint32_t i = 0; i < game.maxPlayers; i++)
{
if (player != i
&& (NetPlay.isHost || !isHumanPlayer(i)) // host can move a player to any slot, player can only move to empty slots
&& (isHostOrAdmin() || !isHumanPlayer(i)) // host/admin can move a player to any slot, player can only move to empty slots
&& !isSpectatorOnlySlot(i)) // target cannot be a spectator only slot (for player position changes)
{
validTargetPlayerIdx.insert(i);
Expand Down Expand Up @@ -2114,9 +2119,9 @@ void WzMultiplayerOptionsTitleUI::openTeamChooser(uint32_t player)

UDWORD i;
int disallow = allPlayersOnSameTeam(player);
if (bIsTrueMultiplayerGame && NetPlay.isHost)
if (bIsTrueMultiplayerGame && isHostOrAdmin())
{
// allow configuration of all teams in true multiplayer mode (by host), even if they would block the game starting
// allow configuration of all teams in true multiplayer mode (by host/admin), even if they would block the game starting
// (i.e. even if all players would be configured to be on the same team)
disallow = -1;
}
Expand Down Expand Up @@ -2513,7 +2518,7 @@ bool recvTeamRequest(NETQUEUE queue)
return false;
}

if (whosResponsible(player) != queue.index)
if (whosResponsible(player) != queue.index && !NetPlay.players[queue.index].isAdmin)
{
HandleBadParam("NET_TEAMREQUEST given incorrect params.", player, queue.index);
return false;
Expand Down Expand Up @@ -2616,6 +2621,7 @@ bool changeReadyStatus(UBYTE player, bool bReady)

static bool changePosition(UBYTE player, UBYTE position)
{
ASSERT_HOST_ONLY(return false);
ASSERT(player < MAX_PLAYERS, "Invalid player idx: %" PRIu8, player);
int i;

Expand Down Expand Up @@ -2767,7 +2773,7 @@ bool recvFactionRequest(NETQUEUE queue)
return false;
}

if (whosResponsible(player) != queue.index)
if (whosResponsible(player) != queue.index && !NetPlay.players[queue.index].isAdmin)
{
HandleBadParam("NET_FACTIONREQUEST given incorrect params.", player, queue.index);
return false;
Expand Down Expand Up @@ -2805,15 +2811,15 @@ bool recvColourRequest(NETQUEUE queue)
return false;
}

if (whosResponsible(player) != queue.index)
if (whosResponsible(player) != queue.index && !NetPlay.players[queue.index].isAdmin)
{
HandleBadParam("NET_COLOURREQUEST given incorrect params.", player, queue.index);
return false;
}

resetReadyStatus(false, true);

return changeColour(player, col, false);
return changeColour(player, col, NetPlay.players[queue.index].isAdmin);
}

bool recvPositionRequest(NETQUEUE queue)
Expand All @@ -2835,7 +2841,7 @@ bool recvPositionRequest(NETQUEUE queue)
return false;
}

if (whosResponsible(player) != queue.index)
if (whosResponsible(player) != queue.index && !NetPlay.players[queue.index].isAdmin)
{
HandleBadParam("NET_POSITIONREQUEST given incorrect params.", player, queue.index);
return false;
Expand Down Expand Up @@ -3269,7 +3275,7 @@ static SwapPlayerIndexesResult recvSwapPlayerIndexes(NETQUEUE queue, const std::

static bool canChooseTeamFor(int i)
{
return (i == selectedPlayer || NetPlay.isHost);
return (i == selectedPlayer || isHostOrAdmin());
}

// ////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -3996,7 +4002,7 @@ class WzPlayerRow : public WIDGET
widget->colorButton->addOnClickHandler([playerIdx, titleUI](W_BUTTON& button){
auto strongTitleUI = titleUI.lock();
ASSERT_OR_RETURN(, strongTitleUI != nullptr, "Title UI is gone?");
if (playerIdx == selectedPlayer || NetPlay.isHost)
if (playerIdx == selectedPlayer || isHostOrAdmin())
{
if (!NetPlay.players[playerIdx].isSpectator) // not a spectator
{
Expand Down Expand Up @@ -4031,11 +4037,12 @@ class WzPlayerRow : public WIDGET
widget->playerInfo->addOnClickHandler([playerIdx, titleUI](W_BUTTON& button){
auto strongTitleUI = titleUI.lock();
ASSERT_OR_RETURN(, strongTitleUI != nullptr, "Title UI is gone?");
if (playerIdx == selectedPlayer || NetPlay.isHost)
if (playerIdx == selectedPlayer || isHostOrAdmin())
{
uint32_t player = playerIdx;
// host can move any player, clients can request to move themselves if there are available slots
if (((player == selectedPlayer && validPlayerIdxTargetsForPlayerPositionMove(player).size() > 0) || (NetPlay.players[player].allocated && NetPlay.isHost))
// host/admin can move any player, clients can request to move themselves if there are available slots
if (((player == selectedPlayer && validPlayerIdxTargetsForPlayerPositionMove(player).size() > 0) ||
(NetPlay.players[player].allocated && isHostOrAdmin()))
&& !locked.position
&& player < MAX_PLAYERS
&& !isSpectatorOnlySlot(player))
Expand Down Expand Up @@ -8827,6 +8834,7 @@ inline void to_json(nlohmann::json& j, const PLAYER& p) {
// Do not persist IPtextAddress
j["faction"] = static_cast<uint8_t>(p.faction);
j["isSpectator"] = p.isSpectator;
j["isAdmin"] = p.isAdmin;
}

inline void from_json(const nlohmann::json& j, PLAYER& p) {
Expand Down Expand Up @@ -8857,6 +8865,15 @@ inline void from_json(const nlohmann::json& j, PLAYER& p) {
auto factionUint = j.at("faction").get<uint8_t>();
p.faction = static_cast<FactionID>(factionUint); // TODO CHECK
p.isSpectator = j.at("isSpectator").get<bool>();
if (j.contains("isAdmin"))
{
p.isAdmin = j.at("isAdmin").get<bool>();
}
else
{
// default to the old (pre-4.5.4) default value of false
p.isAdmin = false;
}
}

static nlohmann::json DataHashToJSON()
Expand Down
17 changes: 9 additions & 8 deletions src/multilobbycommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ bool removeLobbyAdminPublicKey(const std::string& publicKeyB64Str)
return lobbyAdminPublicKeys.erase(publicKeyB64Str) > 0;
}

// checks for specific identity being an admin
bool identityMatchesAdmin(const EcKey& identity)
{
std::string senderIdentityHash = identity.publicHashString();
std::string senderPublicKeyB64 = base64Encode(identity.toBytes(EcKey::Public));
return lobbyAdminPublicKeys.count(senderPublicKeyB64) != 0 || lobbyAdminPublicHashStrings.count(senderIdentityHash) != 0;
}

// NOTE: **IMPORTANT** this should *NOT* be used for determining whether a sender has permission to execute admin commands
// (Use senderHasLobbyCommandAdminPrivs instead)
static bool senderApparentlyMatchesAdmin(uint32_t playerIdx)
Expand All @@ -75,14 +83,7 @@ static bool senderApparentlyMatchesAdmin(uint32_t playerIdx)
{
return false;
}
std::string senderIdentityHash = identity.publicHashString();
std::string senderPublicKeyB64 = base64Encode(identity.toBytes(EcKey::Public));
if (lobbyAdminPublicKeys.count(senderPublicKeyB64) == 0 && lobbyAdminPublicHashStrings.count(senderIdentityHash) == 0)
{
return false; // identity hash is not in permitted lists
}

return true;
return identityMatchesAdmin(identity);
}

// **THIS** is the function that should be used to determine whether a sender currently has permission to execute admin commands
Expand Down
2 changes: 2 additions & 0 deletions src/multilobbycommands.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ void cmdInterfaceLogChatMsg(const NetworkTextMessage& message, const char* log_p

bool processChatLobbySlashCommands(const NetworkTextMessage& message, HostLobbyOperationsInterface& cmdInterface);

bool identityMatchesAdmin(const EcKey& identity);

bool addLobbyAdminIdentityHash(const std::string& playerIdentityHash);
bool removeLobbyAdminIdentityHash(const std::string& playerIdentityHash);

Expand Down
11 changes: 9 additions & 2 deletions src/multiplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,7 @@ HandleMessageAction getMessageHandlingAction(NETQUEUE& queue, uint8_t type)
}

bool senderIsSpectator = NetPlay.players[queue.index].isSpectator;
bool senderIsAdmin = NetPlay.players[queue.index].isAdmin;

if (type > NET_MIN_TYPE && type < NET_MAX_TYPE)
{
Expand All @@ -1049,10 +1050,16 @@ HandleMessageAction getMessageHandlingAction(NETQUEUE& queue, uint8_t type)
}
break;
case NET_KICK:
case NET_AITEXTMSG:
case NET_BEACONMSG:
case NET_TEAMREQUEST: // spectators should not be allowed to request a team / non-spectator slot status
case NET_FACTIONREQUEST:
case NET_POSITIONREQUEST:
if (senderIsSpectator && !senderIsAdmin)
{
return HandleMessageAction::Disallow_And_Kick_Sender;
}
break;
case NET_AITEXTMSG:
case NET_BEACONMSG:
if (senderIsSpectator)
{
return HandleMessageAction::Disallow_And_Kick_Sender;
Expand Down
15 changes: 15 additions & 0 deletions src/multistat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include "multistat.h"
#include "urlrequest.h"
#include "stdinreader.h"
#include "multilobbycommands.h"

#include <utility>
#include <memory>
Expand Down Expand Up @@ -377,12 +378,26 @@ bool multiStatsSetIdentity(uint32_t playerIndex, const EcKey::Key &identity, boo
{
// Record that the identity has been verified
ingame.VerifiedIdentity[playerIndex] = true;
if (NetPlay.isHost)
{
NetPlay.players[playerIndex].isAdmin = identityMatchesAdmin(playerStats[playerIndex].identity);
// Do not broadcast player info here, as it's assumed caller will do it
}

// Do *not* output to stdinterface here - the join event is still being processed
}
else
{
ingame.VerifiedIdentity[playerIndex] = false;
if (NetPlay.isHost)
{
// when verified identity is cleared, so is admin status (until new identity is verified)
if (NetPlay.players[playerIndex].isAdmin)
{
NetPlay.players[playerIndex].isAdmin = false;
NETBroadcastPlayerInfo(playerIndex);
}
}

// Output to stdinterface, if enabled
if (!identity.empty())
Expand Down
13 changes: 13 additions & 0 deletions src/multisync.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "multistat.h"
#include "multirecv.h"
#include "stdinreader.h"
#include "multilobbycommands.h"

#include <array>

Expand Down Expand Up @@ -330,6 +331,18 @@ bool recvPing(NETQUEUE queue)
// Record that they've verified the identity
ingame.VerifiedIdentity[sender] = true;

if (NetPlay.isHost)
{
// check if verified identity is an admin, and handle changes to admin status
bool oldIsAdminStatus = NetPlay.players[sender].isAdmin;
NetPlay.players[sender].isAdmin = identityMatchesAdmin(senderIdentity);
if (oldIsAdminStatus != NetPlay.players[sender].isAdmin)
{
// then send info about admin status changes to all players
NETBroadcastPlayerInfo(sender);
}
}

// Output to stdinterface, if enabled
std::string senderPublicKeyB64 = base64Encode(senderIdentity.toBytes(EcKey::Public));
std::string senderIdentityHash = senderIdentity.publicHashString();
Expand Down

0 comments on commit 4cf8005

Please sign in to comment.