diff --git a/dDashboardServer/routes/GrantRoutes.cpp b/dDashboardServer/routes/GrantRoutes.cpp index 666351a32..40c7f1258 100644 --- a/dDashboardServer/routes/GrantRoutes.cpp +++ b/dDashboardServer/routes/GrantRoutes.cpp @@ -8,6 +8,8 @@ #include "eHTTPMethod.h" #include "Game.h" #include "GeneralUtils.h" +#include "master/PlayerAction.h" +#include "PlayerActions.h" #include "HTTPContext.h" #include "PermissionGrants.h" #include "Permissions.h" @@ -168,10 +170,23 @@ namespace { std::map manageable; }; - // Grants apply at once: the dashboard's open pages get their account's rights again - void Applied(const Target& target) { + /** + * Grants apply at once: the dashboard's open pages get their account's rights again, and the world servers read the + * grants of the account's online sessions (or the character, if it is loaded) again, as for a GM level change. + * Returns the request ID of the refresh (the page shows its result). + */ + uint32_t Applied(const HTTPContext& context, const Target& target) { Game::web.RecheckWebSockets(target.accountId); BroadcastTableChanged("grants", std::to_string(target.accountId)); + PlayerActionRequest request; + const bool account = target.type == PermissionGrants::ACCOUNT; + request.action = account ? ePlayerAction::REFRESH_ACCOUNT : ePlayerAction::REFRESH_CHARACTER; + request.accountId = target.accountId; + request.characterId = account ? 0 : target.id; + return PlayerActions::Request(request, context.accountId, [account](const PlayerActionResult& result) { + if (result.affected == 0) return PlayerActions::Outcome{ true, account ? "The account isn't online; it applies when they next play" : "The character isn't online; it applies when they next play" }; + return PlayerActions::Outcome{ true, "Applied in game at once" }; + }); } } @@ -287,8 +302,8 @@ void GrantRoutes::RegisterRoutes() { const auto description = std::string(deny ? "Took away " : "Granted ") + what + (deny ? " from " : " to ") + target->Describe() + (expiresAt ? " until " + UtcTime(expiresAt) : "") + (note.empty() ? "" : ": " + note) + OwnAccountNote(context.accountId, target->accountId); Audit(context, deny ? "deny_permission" : "grant_permission", description, target->Audit()); - Applied(*target); - JsonSuccess(reply, { {"id", grant.id}, {"message", std::string(deny ? "Took away " : "Granted ") + what} }); + const auto requestId = Applied(context, *target); + JsonSuccess(reply, { {"id", grant.id}, {"requestId", requestId}, {"message", std::string(deny ? "Took away " : "Granted ") + what} }); }); Route(eHTTPMethod::POST, "/api/grants/:id/remove", Perm(MANAGE), @@ -311,7 +326,7 @@ void GrantRoutes::RegisterRoutes() { const auto what = PermissionGrants::Describe(*kind, grant->name); Audit(context, "remove_grant", std::string("Removed the ") + (grant->deny ? "deny of " : "grant of ") + what + (grant->deny ? " from " : " to ") + target->Describe() + " (given by " + grant->grantedBy + ")" + OwnAccountNote(context.accountId, target->accountId), target->Audit()); - Applied(*target); - JsonSuccess(reply, { {"message", std::string("Removed the ") + (grant->deny ? "deny of " : "grant of ") + what} }); + const auto requestId = Applied(context, *target); + JsonSuccess(reply, { {"requestId", requestId}, {"message", std::string("Removed the ") + (grant->deny ? "deny of " : "grant of ") + what} }); }); } diff --git a/dGame/dUtilities/DashboardActions.cpp b/dGame/dUtilities/DashboardActions.cpp index 408c407e3..cf32bcb7d 100644 --- a/dGame/dUtilities/DashboardActions.cpp +++ b/dGame/dUtilities/DashboardActions.cpp @@ -48,10 +48,12 @@ namespace { return static_cast(users.size()); } - // Pick up a GM level change: lower the in-game level if it now exceeds the account's maximum + // Pick up a GM level change: lower the in-game level if it now exceeds the account's maximum. Also a change of the + // account's permission grants: they are read again the next time a command is used uint32_t RefreshAccount(uint32_t accountId) { const auto users = UserManager::Instance()->GetUsersForAccount(accountId); if (users.empty()) return 0; + for (auto* user : users) user->ForgetGrants(); const auto info = Database::Get()->GetAccountInfo(users.front()->GetUsername()); if (!info) return 0; @@ -83,10 +85,12 @@ namespace { return static_cast(users.size()); } + // Restrictions or permission grants of a character changed uint32_t RefreshCharacter(LWOOBJID characterId) { auto* entity = PlayerManager::GetPlayer(characterId); auto* character = entity ? entity->GetCharacter() : nullptr; if (!character) return 0; + if (auto* user = character->GetParentUser()) user->ForgetGrants(); const auto info = Database::Get()->GetCharacterInfo(characterId); if (!info) return 0; diff --git a/dNet/master/PlayerAction.h b/dNet/master/PlayerAction.h index 1f31e2c0d..96b484f69 100644 --- a/dNet/master/PlayerAction.h +++ b/dNet/master/PlayerAction.h @@ -18,8 +18,8 @@ */ enum class ePlayerAction : uint8_t { KICK_ACCOUNT, // Disconnect every session of accountId - REFRESH_ACCOUNT, // Reload account data (GM level) for online sessions of accountId - REFRESH_CHARACTER, // Reload character data (restrictions) for characterId if loaded + REFRESH_ACCOUNT, // Reload account data (GM level, permission grants) for online sessions of accountId + REFRESH_CHARACTER, // Reload character data (restrictions, permission grants) for characterId if loaded RESCUE_CHARACTER, // Transfer characterId to zoneId if it is loaded in a world, landing on spawn point `text` (empty: the zone's default) // Tell a player a moderator decided on something they asked for (approved = yes/no, text = name or reason) NAME_MODERATED, // characterId's requested name; text is the name