mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53: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>
75 lines
3.7 KiB
C++
75 lines
3.7 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <optional>
|
|
#include <set>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
#include "json.hpp"
|
|
|
|
namespace ApiKeys { struct Scope; }
|
|
namespace PermissionGrants { struct Held; }
|
|
|
|
/**
|
|
* What each GM level may do on the dashboard, and in the game for the slash commands paired with a permission. Every
|
|
* permission has a default minimum GM level; server owners can change it without rebuilding, on the Permissions page
|
|
* or with permission_<name>=<level> in dashboardconfig.ini (or the dashboard's PERMISSION_<NAME> environment
|
|
* variable). A level set on the page beats the file. Levels are 1-9, and GM 9 can always do everything, so an operator
|
|
* can never lock themselves out.
|
|
* The dashboard owns these settings. The other servers (the worlds, for their slash commands) read the values the
|
|
* dashboard stored in the database (ConfigSync), so every server sees the same levels and picks up changes when its
|
|
* settings reload.
|
|
*/
|
|
namespace Permissions {
|
|
constexpr uint8_t MIN_LEVEL = 1; // the lowest level a staff permission can be given to: players (GM 0) never get them
|
|
constexpr uint8_t PLAYER_LEVEL = 0; // what players can do (their own characters, leaderboards, the API) can be set this low
|
|
constexpr uint8_t MAX_LEVEL = 9;
|
|
// The server that owns the permission_* settings, and the file its values are stored under
|
|
constexpr std::string_view CONFIG_FILE = "dashboardconfig.ini";
|
|
constexpr std::string_view PREFIX = "permission_";
|
|
|
|
struct Permission {
|
|
std::string key; // lower_snake_case; the config setting is permission_<key>
|
|
std::string category;
|
|
std::string title;
|
|
std::string description;
|
|
uint8_t defaultLevel;
|
|
bool locked{}; // always the default (managing permissions themselves)
|
|
uint8_t minLevel{ MIN_LEVEL }; // the lowest level it can be set to: PLAYER_LEVEL for what players do
|
|
};
|
|
|
|
const std::vector<Permission>& All();
|
|
const Permission* Find(const std::string& key);
|
|
|
|
std::string ConfigName(const std::string& key);
|
|
|
|
inline bool IsPermissionSetting(std::string_view name) {
|
|
return name.starts_with(PREFIX);
|
|
}
|
|
|
|
// The minimum GM level for a permission right now; unknown permissions need more than GM 9 (nobody)
|
|
uint8_t Level(const std::string& key);
|
|
|
|
bool Allowed(uint8_t gmLevel, const std::string& key);
|
|
|
|
// For a request made with an API key: the owner's level must allow it AND the key's scope must name it.
|
|
// scope nullptr (a browser session) is the plain check. grants: the account's permission grants (PermissionGrants.h),
|
|
// which can allow what the level doesn't or deny what it does; nullptr: the level alone.
|
|
bool Allowed(uint8_t gmLevel, const std::string& key, const ApiKeys::Scope* scope, const PermissionGrants::Held* grants = nullptr);
|
|
|
|
// The permissions in a requested API key scope that a GM level may not give it (unknown ones, or ones it doesn't
|
|
// have): a key can never be made with more than its maker has. Empty: all of them may be given.
|
|
std::set<std::string> NotGrantable(uint8_t gmLevel, const std::set<std::string>& requested, const PermissionGrants::Held* grants = nullptr);
|
|
|
|
// characters_view for anyone's character, or own_characters for the viewer's own (account 0 owns nothing)
|
|
bool CanViewCharacter(uint8_t gmLevel, uint32_t viewerAccountId, uint32_t ownerAccountId, const ApiKeys::Scope* scope = nullptr, const PermissionGrants::Held* grants = nullptr);
|
|
|
|
// {key: bool} for every permission, for templates and scripts
|
|
nlohmann::json ForLevel(uint8_t gmLevel, const ApiKeys::Scope* scope = nullptr, const PermissionGrants::Held* grants = nullptr);
|
|
|
|
// Pure: the level a config value gives a permission (bad or out-of-range values fall back to the default)
|
|
uint8_t Resolve(const Permission& permission, const std::string& configValue);
|
|
}
|