mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
What someone may do on the dashboard is now their GM level's permissions plus the grants on their account, minus its denies (PermissionGrants.h). A deny beats a grant; denies never apply to GM 9, and settings and permissions_manage stay GM 9 only. The account's grants are read with every request (like its GM level), so a change applies at once, and they are passed through every check: RouteUtils::Can, CanViewCharacter, the rank rules (self_* and manage_equal_rank), routes guarded by a permission, the templates' `can`, the API documentation, API access, API key scopes (a key never does more than its owner may now) and WebSocket subscriptions. New permission grants_manage (GM 9 by default) and the API to manage grants: GET /api/grants/catalog, GET /api/grants, POST /api/grants, POST /api/grants/:id/remove. Nobody grants or takes away what they don't hold themselves (a permission, every permission of a group, a command they may use, every command up to their own GM level), and only on accounts the rank rules let them manage (their own with self_moderation). Commands with a fixed level or a floor above GM 1 (/execute) can't be granted. Every change goes in the audit log (grant_permission, deny_permission, remove_grant). Also: the Showcase gate and the traffic subscription now check their permission by name. Check: grant a GM 2 account accounts_ban (it can ban, and the Ban button shows); deny a GM 8 account accounts_view (the accounts list is refused); give an expiry a minute ahead and see it stop; try to grant a permission your account doesn't have (refused); dWebTests PermissionGrantsTests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
117 lines
6.1 KiB
C++
117 lines
6.1 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <string>
|
|
|
|
namespace ApiKeys { struct Scope; }
|
|
namespace PermissionGrants { struct Held; }
|
|
|
|
/**
|
|
* Who staff may use their tools on: the self and rank rules shared by the dashboard (RouteUtils) and the in-game slash
|
|
* commands (SlashCommandHandler). The functions here are pure; Permissions.h decides the self_* and manage_equal_rank
|
|
* levels they are given.
|
|
*/
|
|
namespace AccountRules {
|
|
// GM 9: may do anything to anyone, including themselves and other GM 9s
|
|
constexpr uint8_t OPERATOR_LEVEL = 9;
|
|
|
|
/**
|
|
* What a staff action on an account does. It decides which permission a staff member below GM 9 needs to use the
|
|
* tool on their own account or characters (they still need the tool's own permission too).
|
|
*/
|
|
enum class eAccountAction : uint8_t {
|
|
TOOLS, // nothing is gained: rescue or move a character, kick, sign out everywhere, email a reset link (self_tools)
|
|
ITEMS, // gives items, coins or progress: edit or restore characters, missions, mail items (self_items)
|
|
MODERATION, // changes a moderation record or account security: ban, mute, lock, strikes, GM level, password, delete (self_moderation)
|
|
};
|
|
|
|
// The permission that lets staff below GM 9 do this kind of action to their own account
|
|
inline const char* SelfPermission(eAccountAction action) {
|
|
switch (action) {
|
|
case eAccountAction::TOOLS: return "self_tools";
|
|
case eAccountAction::ITEMS: return "self_items";
|
|
default: return "self_moderation";
|
|
}
|
|
}
|
|
|
|
// The permission that lets staff below GM 9 act on accounts with their own GM level
|
|
constexpr const char* EQUAL_RANK_PERMISSION = "manage_equal_rank";
|
|
|
|
// Why an actor may not act on an account (NONE: they may)
|
|
enum class eManageDenial : uint8_t { NONE, SELF, EQUAL_RANK, HIGHER_RANK };
|
|
|
|
/**
|
|
* Whether an actor may act on a target account. GM 9 may act on anyone, themselves included. Below GM 9: their own
|
|
* account only with selfAllowed (the self_* permission for the action), an account at their own level only with
|
|
* equalAllowed (manage_equal_rank), and never an account above their own level, whatever the permissions say.
|
|
*/
|
|
inline eManageDenial ManageDenial(uint8_t actorLevel, uint32_t actorAccountId, uint8_t targetLevel, uint32_t targetAccountId, bool selfAllowed, bool equalAllowed) {
|
|
if (actorLevel >= OPERATOR_LEVEL) return eManageDenial::NONE;
|
|
if (actorAccountId != 0 && actorAccountId == targetAccountId) return selfAllowed ? eManageDenial::NONE : eManageDenial::SELF;
|
|
if (targetLevel > actorLevel) return eManageDenial::HIGHER_RANK;
|
|
if (targetLevel == actorLevel && !equalAllowed) return eManageDenial::EQUAL_RANK;
|
|
return eManageDenial::NONE;
|
|
}
|
|
|
|
inline bool CanManageAccount(uint8_t actorLevel, uint32_t actorAccountId, uint8_t targetLevel, uint32_t targetAccountId, bool selfAllowed, bool equalAllowed) {
|
|
return ManageDenial(actorLevel, actorAccountId, targetLevel, targetAccountId, selfAllowed, equalAllowed) == eManageDenial::NONE;
|
|
}
|
|
|
|
/**
|
|
* Safety rail for the server, not a limit on the operator: demoting, banning, locking or deleting a GM 9 account is
|
|
* refused when no other GM 9 account that can still sign in (not banned or locked) would be left.
|
|
*/
|
|
inline bool RemovesLastOperator(uint8_t targetLevel, uint32_t otherActiveOperators) {
|
|
return targetLevel >= OPERATOR_LEVEL && otherActiveOperators == 0;
|
|
}
|
|
|
|
// Whether an actor may grant a GM level: never above their own, and only operators may create peers
|
|
inline bool CanGrantGmLevel(uint8_t actorLevel, uint8_t newLevel) {
|
|
if (newLevel > OPERATOR_LEVEL) return false;
|
|
return actorLevel >= OPERATOR_LEVEL || newLevel < actorLevel;
|
|
}
|
|
|
|
// Why an action was refused, for the person who tried it (empty for NONE)
|
|
inline std::string DenialMessage(eManageDenial denial, eAccountAction action) {
|
|
switch (denial) {
|
|
case eManageDenial::SELF:
|
|
return std::string("Your GM level may not do this to your own account or characters (the ") + SelfPermission(action) + " permission)";
|
|
case eManageDenial::EQUAL_RANK:
|
|
return std::string("You cannot manage an account with the same GM level as yours (the ") + EQUAL_RANK_PERMISSION + " permission)";
|
|
case eManageDenial::HIGHER_RANK:
|
|
return "You cannot manage an account with a higher GM level than yours";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
// The last-operator refusal, e.g. what = "banned"
|
|
inline std::string LastOperatorMessage(const std::string& what) {
|
|
return "This is the last GM 9 account that can sign in, so it can't be " + what +
|
|
". Make another GM 9 account first, so the server always has someone who can manage it.";
|
|
}
|
|
|
|
/**
|
|
* ManageDenial with the self_* and manage_equal_rank levels as the Permissions page (or the config) sets them now.
|
|
* Used by the dashboard and the world servers alike.
|
|
*/
|
|
eManageDenial ManageDenialNow(uint8_t actorLevel, uint32_t actorAccountId, uint8_t targetLevel, uint32_t targetAccountId, eAccountAction action);
|
|
|
|
/**
|
|
* The self and rank rules for an API key, on top of what its owner may do (ownerDenial, from ManageDenial). The key
|
|
* needs the self_* permission in its scope to act on its owner's own account, and manage_equal_rank to act on an
|
|
* account at (or, for GM 9, also at) the owner's level - even when the owner is GM 9, who needs neither.
|
|
*/
|
|
inline eManageDenial ScopedManageDenial(eManageDenial ownerDenial, uint8_t actorLevel, uint32_t actorAccountId, uint8_t targetLevel, uint32_t targetAccountId,
|
|
bool scopeSelf, bool scopeEqual) {
|
|
if (ownerDenial != eManageDenial::NONE) return ownerDenial;
|
|
if (actorAccountId != 0 && actorAccountId == targetAccountId) return scopeSelf ? eManageDenial::NONE : eManageDenial::SELF;
|
|
if (targetLevel >= actorLevel && !scopeEqual) return eManageDenial::EQUAL_RANK;
|
|
return eManageDenial::NONE;
|
|
}
|
|
|
|
// ManageDenialNow for a request made with an API key (scope nullptr: a browser session, the plain rules). grants: the
|
|
// actor's permission grants (PermissionGrants.h), which count for self_* and manage_equal_rank; nullptr: the level alone.
|
|
eManageDenial ManageDenialNow(uint8_t actorLevel, uint32_t actorAccountId, uint8_t targetLevel, uint32_t targetAccountId, eAccountAction action, const ApiKeys::Scope* scope, const PermissionGrants::Held* grants = nullptr);
|
|
}
|