mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
feat(dashboard): permission grants count in every dashboard permission check
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>
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
#include "Permissions.h"
|
||||
#include "AccountRules.h"
|
||||
#include "ApiKeyScope.h"
|
||||
#include "PermissionGrants.h"
|
||||
|
||||
#include <ctime>
|
||||
|
||||
#include <map>
|
||||
|
||||
@@ -107,6 +110,7 @@ namespace {
|
||||
{ "email_settings", "Server", "Email settings", "Connect the mail account and send test emails", 9 },
|
||||
{ "settings", "Server", "Server settings", "Change any server setting on the Settings page (always GM 9)", 9, true },
|
||||
{ "permissions_manage", "Server", "Permissions", "Change what each GM level may do (always GM 9)", 9, true },
|
||||
{ "grants_manage", "Accounts", "Grant permissions", "Give or take away dashboard permissions and in-game commands for one account or character, with an optional expiry (only ones they have themselves, and only on accounts they may manage)", 9 },
|
||||
|
||||
{ "dev_message_inspector", "Developer tools", "Game message inspector", "Capture the game messages an online player sends and receives, live (every capture is audited)", 8 },
|
||||
{ "dev_cdclient", "Developer tools", "CDClient browser", "Read the game's CDClient tables as they are: page, sort, search and filter any table", 8 },
|
||||
@@ -153,38 +157,42 @@ namespace Permissions {
|
||||
return gmLevel >= Level(key);
|
||||
}
|
||||
|
||||
bool Allowed(uint8_t gmLevel, const std::string& key, const ApiKeys::Scope* scope) {
|
||||
return Allowed(gmLevel, key) && (!scope || scope->Has(key));
|
||||
bool Allowed(uint8_t gmLevel, const std::string& key, const ApiKeys::Scope* scope, const PermissionGrants::Held* grants) {
|
||||
bool allowed = Allowed(gmLevel, key);
|
||||
if (grants && Find(key)) {
|
||||
allowed = PermissionGrants::Decide(allowed, PermissionGrants::ForPermission(*grants, key, static_cast<int64_t>(std::time(nullptr))), gmLevel);
|
||||
}
|
||||
return allowed && (!scope || scope->Has(key));
|
||||
}
|
||||
|
||||
std::set<std::string> NotGrantable(uint8_t gmLevel, const std::set<std::string>& requested) {
|
||||
std::set<std::string> NotGrantable(uint8_t gmLevel, const std::set<std::string>& requested, const PermissionGrants::Held* grants) {
|
||||
std::set<std::string> refused;
|
||||
for (const auto& permission : requested) {
|
||||
if (!Find(permission) || !Allowed(gmLevel, permission)) refused.insert(permission);
|
||||
if (!Find(permission) || !Allowed(gmLevel, permission, nullptr, grants)) refused.insert(permission);
|
||||
}
|
||||
return refused;
|
||||
}
|
||||
|
||||
bool CanViewCharacter(uint8_t gmLevel, uint32_t viewerAccountId, uint32_t ownerAccountId, const ApiKeys::Scope* scope) {
|
||||
bool CanViewCharacter(uint8_t gmLevel, uint32_t viewerAccountId, uint32_t ownerAccountId, const ApiKeys::Scope* scope, const PermissionGrants::Held* grants) {
|
||||
const bool own = viewerAccountId != 0 && viewerAccountId == ownerAccountId;
|
||||
return Allowed(gmLevel, "characters_view", scope) || (own && Allowed(gmLevel, "own_characters", scope));
|
||||
return Allowed(gmLevel, "characters_view", scope, grants) || (own && Allowed(gmLevel, "own_characters", scope, grants));
|
||||
}
|
||||
|
||||
nlohmann::json ForLevel(uint8_t gmLevel, const ApiKeys::Scope* scope) {
|
||||
nlohmann::json ForLevel(uint8_t gmLevel, const ApiKeys::Scope* scope, const PermissionGrants::Held* grants) {
|
||||
nlohmann::json can = nlohmann::json::object();
|
||||
for (const auto& permission : PERMISSIONS) can[permission.key] = Allowed(gmLevel, permission.key, scope);
|
||||
for (const auto& permission : PERMISSIONS) can[permission.key] = Allowed(gmLevel, permission.key, scope, grants);
|
||||
return can;
|
||||
}
|
||||
}
|
||||
|
||||
namespace AccountRules {
|
||||
eManageDenial ManageDenialNow(uint8_t actorLevel, uint32_t actorAccountId, uint8_t targetLevel, uint32_t targetAccountId, eAccountAction action) {
|
||||
return ManageDenial(actorLevel, actorAccountId, targetLevel, targetAccountId,
|
||||
Permissions::Allowed(actorLevel, SelfPermission(action)), Permissions::Allowed(actorLevel, EQUAL_RANK_PERMISSION));
|
||||
return ManageDenialNow(actorLevel, actorAccountId, targetLevel, targetAccountId, action, nullptr, nullptr);
|
||||
}
|
||||
|
||||
eManageDenial ManageDenialNow(uint8_t actorLevel, uint32_t actorAccountId, uint8_t targetLevel, uint32_t targetAccountId, eAccountAction action, const ApiKeys::Scope* scope) {
|
||||
const auto denial = ManageDenialNow(actorLevel, actorAccountId, targetLevel, targetAccountId, action);
|
||||
eManageDenial ManageDenialNow(uint8_t actorLevel, uint32_t actorAccountId, uint8_t targetLevel, uint32_t targetAccountId, eAccountAction action, const ApiKeys::Scope* scope, const PermissionGrants::Held* grants) {
|
||||
const auto denial = ManageDenial(actorLevel, actorAccountId, targetLevel, targetAccountId,
|
||||
Permissions::Allowed(actorLevel, SelfPermission(action), nullptr, grants), Permissions::Allowed(actorLevel, EQUAL_RANK_PERMISSION, nullptr, grants));
|
||||
if (!scope) return denial;
|
||||
return ScopedManageDenial(denial, actorLevel, actorAccountId, targetLevel, targetAccountId,
|
||||
scope->Has(SelfPermission(action)), scope->Has(EQUAL_RANK_PERMISSION));
|
||||
|
||||
Reference in New Issue
Block a user