mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
feat(db): permission_grants, permissions and commands given to one account or character
A new table, permission_grants (MySQL migration 96, SQLite 79), and the IPermissionGrants interface with MySQL, SQLite and TestSQL implementations. A row grants (or with deny, takes away) a dashboard permission, a slash command, a permission category or every command up to a GM level, for one account or one character, with an optional expiry, who granted it and when, and a note. Rows are never deleted: removing one sets revoked_at/revoked_by, so the table is also the history. Nothing reads it yet. Check: both migrations run on a fresh and an existing database; DatabaseParityTests PermissionGrants passes against MariaDB (DLU_TEST_MYSQL_HOST) and SQLite gives the same results. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -54,6 +54,7 @@
|
||||
#include "IServerTraffic.h"
|
||||
#include "IApiKeys.h"
|
||||
#include "IUgcLookup.h"
|
||||
#include "IPermissionGrants.h"
|
||||
|
||||
#ifdef _DEBUG
|
||||
# define DLU_SQL_TRY_CATCH_RETHROW(x) do { try { x; } catch (std::exception& ex) { LOG("SQL Error: %s", ex.what()); throw; } } while(0)
|
||||
@@ -67,7 +68,7 @@ class GameDatabase :
|
||||
public IPropertyContents, public IProperty, public IPetNames, public ICharXml,
|
||||
public IMigrationHistory, public IUgc, public IFriends, public ICharInfo,
|
||||
public IAccounts, public IActivityLog, public IAccountsRewardCodes, public IIgnoreList,
|
||||
public IBehaviors, public IUgcModularBuild, public IAuditLog, public IDashboardStats, public IAccountEmails, public IDashboardMaintenance, public IEconomyLedger, public IDashboardAdmin, public IServerConfig, public IScheduledTasks, public ICharacterSnapshots, public IAccountNotes, public IServerHealth, public IRelatedData, public IChatLog, public IAccountStrikes, public ISlashCommands, public IModeration, public IServerOperations, public IPlayerPositions, public IAiSuggestions, public ILiveOps, public IFeaturedProperties, public IMessageCaptures, public IContraband, public IPropertyRent, public IPropertyReputation, public IBbbAutosave, public IServerTraffic, public IApiKeys, public IUgcLookup {
|
||||
public IBehaviors, public IUgcModularBuild, public IAuditLog, public IDashboardStats, public IAccountEmails, public IDashboardMaintenance, public IEconomyLedger, public IDashboardAdmin, public IServerConfig, public IScheduledTasks, public ICharacterSnapshots, public IAccountNotes, public IServerHealth, public IRelatedData, public IChatLog, public IAccountStrikes, public ISlashCommands, public IModeration, public IServerOperations, public IPlayerPositions, public IAiSuggestions, public ILiveOps, public IFeaturedProperties, public IMessageCaptures, public IContraband, public IPropertyRent, public IPropertyReputation, public IBbbAutosave, public IServerTraffic, public IApiKeys, public IUgcLookup, public IPermissionGrants {
|
||||
public:
|
||||
virtual ~GameDatabase() = default;
|
||||
// TODO: These should be made private.
|
||||
|
||||
43
dDatabase/GameDatabase/ITables/IPermissionGrants.h
Normal file
43
dDatabase/GameDatabase/ITables/IPermissionGrants.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef __IPERMISSIONGRANTS__H__
|
||||
#define __IPERMISSIONGRANTS__H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* Permissions and slash commands granted to (or denied from) one account or one character, on top of what its GM level
|
||||
* allows (PermissionGrants.h). Rows are never deleted: removing a grant sets revokedAt, so the rows are also the history.
|
||||
*/
|
||||
class IPermissionGrants {
|
||||
public:
|
||||
struct Grant {
|
||||
uint64_t id{};
|
||||
std::string targetType; // "account" or "character"
|
||||
int64_t targetId{}; // the account's ID, or the character's charinfo ID
|
||||
std::string kind; // "permission", "command", "permission_group" or "command_group"
|
||||
std::string name; // the permission key, command name, permission category or GM level
|
||||
bool deny{};
|
||||
int64_t expiresAt{}; // 0: never
|
||||
std::string note;
|
||||
int64_t grantedAt{};
|
||||
uint32_t grantedById{}; // the account that granted it
|
||||
std::string grantedBy;
|
||||
int64_t revokedAt{}; // 0: not removed
|
||||
std::string revokedBy;
|
||||
};
|
||||
|
||||
virtual uint64_t InsertPermissionGrant(const Grant& grant) = 0;
|
||||
virtual std::optional<Grant> GetPermissionGrant(uint64_t id) = 0;
|
||||
// Every grant of one target, removed and expired ones too, newest first
|
||||
virtual std::vector<Grant> GetPermissionGrants(const std::string& targetType, int64_t targetId) = 0;
|
||||
// The grants in force at `now` (not removed, not expired) of an account and, when characterId isn't 0, one character
|
||||
virtual std::vector<Grant> GetActivePermissionGrants(uint32_t accountId, int64_t characterId, int64_t now) = 0;
|
||||
// Every target's grants, newest first: only the ones in force at `now`, or all of them (the history)
|
||||
virtual std::vector<Grant> GetRecentPermissionGrants(bool activeOnly, int64_t now, uint32_t limit) = 0;
|
||||
// Whether it was in force until now (false: no such grant, or already removed)
|
||||
virtual bool RevokePermissionGrant(uint64_t id, const std::string& revokedBy, int64_t time) = 0;
|
||||
};
|
||||
|
||||
#endif //!__IPERMISSIONGRANTS__H__
|
||||
@@ -227,6 +227,13 @@ public:
|
||||
std::vector<UgcPlacement> GetUgcPlacements(const std::vector<LWOOBJID>& ugcIds) override;
|
||||
std::vector<UgcMail> GetUgcMail(const std::vector<LWOOBJID>& subkeys, const LOT modelItemLot) override;
|
||||
|
||||
// IPermissionGrants
|
||||
uint64_t InsertPermissionGrant(const Grant& grant) override;
|
||||
std::optional<Grant> GetPermissionGrant(uint64_t id) override;
|
||||
std::vector<Grant> GetPermissionGrants(const std::string& targetType, int64_t targetId) override;
|
||||
std::vector<Grant> GetActivePermissionGrants(uint32_t accountId, int64_t characterId, int64_t now) override;
|
||||
std::vector<Grant> GetRecentPermissionGrants(bool activeOnly, int64_t now, uint32_t limit) override;
|
||||
bool RevokePermissionGrant(uint64_t id, const std::string& revokedBy, int64_t time) override;
|
||||
// IApiKeys
|
||||
uint64_t InsertApiKey(const ApiKey& key) override;
|
||||
std::optional<ApiKey> GetApiKey(uint64_t id) override;
|
||||
|
||||
@@ -12,6 +12,7 @@ set(DDATABASES_DATABASES_MYSQL_TABLES_SOURCES
|
||||
"AccountNotes.cpp"
|
||||
"AccountStrikes.cpp"
|
||||
"ApiKeys.cpp"
|
||||
"PermissionGrants.cpp"
|
||||
"Moderation.cpp"
|
||||
"ServerHealth.cpp"
|
||||
"PlayerPositions.cpp"
|
||||
|
||||
64
dDatabase/GameDatabase/MySQL/Tables/PermissionGrants.cpp
Normal file
64
dDatabase/GameDatabase/MySQL/Tables/PermissionGrants.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include "MySQLDatabase.h"
|
||||
|
||||
namespace {
|
||||
template<typename Result> IPermissionGrants::Grant ReadGrant(Result& result) {
|
||||
IPermissionGrants::Grant grant;
|
||||
grant.id = result->getUInt64("id");
|
||||
grant.targetType = result->getString("target_type").c_str();
|
||||
grant.targetId = result->getInt64("target_id");
|
||||
grant.kind = result->getString("kind").c_str();
|
||||
grant.name = result->getString("name").c_str();
|
||||
grant.deny = result->getInt("deny") != 0;
|
||||
grant.expiresAt = result->getInt64("expires_at");
|
||||
grant.note = result->getString("note").c_str();
|
||||
grant.grantedAt = result->getInt64("granted_at");
|
||||
grant.grantedById = result->getUInt("granted_by_id");
|
||||
grant.grantedBy = result->getString("granted_by").c_str();
|
||||
grant.revokedAt = result->getInt64("revoked_at");
|
||||
grant.revokedBy = result->getString("revoked_by").c_str();
|
||||
return grant;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t MySQLDatabase::InsertPermissionGrant(const Grant& grant) {
|
||||
ExecuteInsert("INSERT INTO permission_grants (target_type, target_id, kind, name, deny, expires_at, note, granted_at, granted_by_id, granted_by) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
grant.targetType, grant.targetId, grant.kind, grant.name, grant.deny, grant.expiresAt, grant.note, grant.grantedAt, grant.grantedById, grant.grantedBy);
|
||||
auto result = ExecuteSelect("SELECT LAST_INSERT_ID() AS id;");
|
||||
return result->next() ? result->getUInt64("id") : 0;
|
||||
}
|
||||
|
||||
std::optional<IPermissionGrants::Grant> MySQLDatabase::GetPermissionGrant(uint64_t id) {
|
||||
auto result = ExecuteSelect("SELECT * FROM permission_grants WHERE id = ?;", id);
|
||||
if (!result->next()) return std::nullopt;
|
||||
return ReadGrant(result);
|
||||
}
|
||||
|
||||
std::vector<IPermissionGrants::Grant> MySQLDatabase::GetPermissionGrants(const std::string& targetType, int64_t targetId) {
|
||||
std::vector<Grant> grants;
|
||||
auto result = ExecuteSelect("SELECT * FROM permission_grants WHERE target_type = ? AND target_id = ? ORDER BY id DESC;", targetType, targetId);
|
||||
while (result->next()) grants.push_back(ReadGrant(result));
|
||||
return grants;
|
||||
}
|
||||
|
||||
std::vector<IPermissionGrants::Grant> MySQLDatabase::GetActivePermissionGrants(uint32_t accountId, int64_t characterId, int64_t now) {
|
||||
std::vector<Grant> grants;
|
||||
auto result = ExecuteSelect("SELECT * FROM permission_grants WHERE revoked_at = 0 AND (expires_at = 0 OR expires_at > ?) AND "
|
||||
"((target_type = 'account' AND target_id = ?) OR (? <> 0 AND target_type = 'character' AND target_id = ?)) ORDER BY id DESC;",
|
||||
now, static_cast<int64_t>(accountId), characterId, characterId);
|
||||
while (result->next()) grants.push_back(ReadGrant(result));
|
||||
return grants;
|
||||
}
|
||||
|
||||
std::vector<IPermissionGrants::Grant> MySQLDatabase::GetRecentPermissionGrants(bool activeOnly, int64_t now, uint32_t limit) {
|
||||
std::vector<Grant> grants;
|
||||
auto result = activeOnly
|
||||
? ExecuteSelect("SELECT * FROM permission_grants WHERE revoked_at = 0 AND (expires_at = 0 OR expires_at > ?) ORDER BY id DESC LIMIT ?;", now, limit)
|
||||
: ExecuteSelect("SELECT * FROM permission_grants ORDER BY id DESC LIMIT ?;", limit);
|
||||
while (result->next()) grants.push_back(ReadGrant(result));
|
||||
return grants;
|
||||
}
|
||||
|
||||
bool MySQLDatabase::RevokePermissionGrant(uint64_t id, const std::string& revokedBy, int64_t time) {
|
||||
return ExecuteUpdate("UPDATE permission_grants SET revoked_at = ?, revoked_by = ? WHERE id = ? AND revoked_at = 0;", time, revokedBy, id) > 0;
|
||||
}
|
||||
@@ -211,6 +211,13 @@ public:
|
||||
std::vector<UgcPlacement> GetUgcPlacements(const std::vector<LWOOBJID>& ugcIds) override;
|
||||
std::vector<UgcMail> GetUgcMail(const std::vector<LWOOBJID>& subkeys, const LOT modelItemLot) override;
|
||||
|
||||
// IPermissionGrants
|
||||
uint64_t InsertPermissionGrant(const Grant& grant) override;
|
||||
std::optional<Grant> GetPermissionGrant(uint64_t id) override;
|
||||
std::vector<Grant> GetPermissionGrants(const std::string& targetType, int64_t targetId) override;
|
||||
std::vector<Grant> GetActivePermissionGrants(uint32_t accountId, int64_t characterId, int64_t now) override;
|
||||
std::vector<Grant> GetRecentPermissionGrants(bool activeOnly, int64_t now, uint32_t limit) override;
|
||||
bool RevokePermissionGrant(uint64_t id, const std::string& revokedBy, int64_t time) override;
|
||||
// IApiKeys
|
||||
uint64_t InsertApiKey(const ApiKey& key) override;
|
||||
std::optional<ApiKey> GetApiKey(uint64_t id) override;
|
||||
|
||||
@@ -12,6 +12,7 @@ set(DDATABASES_DATABASES_SQLITE_TABLES_SOURCES
|
||||
"AccountNotes.cpp"
|
||||
"AccountStrikes.cpp"
|
||||
"ApiKeys.cpp"
|
||||
"PermissionGrants.cpp"
|
||||
"Moderation.cpp"
|
||||
"ServerHealth.cpp"
|
||||
"PlayerPositions.cpp"
|
||||
|
||||
64
dDatabase/GameDatabase/SQLite/Tables/PermissionGrants.cpp
Normal file
64
dDatabase/GameDatabase/SQLite/Tables/PermissionGrants.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include "SQLiteDatabase.h"
|
||||
|
||||
namespace {
|
||||
IPermissionGrants::Grant ReadGrant(CppSQLite3Query& result) {
|
||||
IPermissionGrants::Grant grant;
|
||||
grant.id = static_cast<uint64_t>(result.getInt64Field("id"));
|
||||
grant.targetType = result.getStringField("target_type");
|
||||
grant.targetId = result.getInt64Field("target_id");
|
||||
grant.kind = result.getStringField("kind");
|
||||
grant.name = result.getStringField("name");
|
||||
grant.deny = result.getIntField("deny") != 0;
|
||||
grant.expiresAt = result.getInt64Field("expires_at");
|
||||
grant.note = result.getStringField("note");
|
||||
grant.grantedAt = result.getInt64Field("granted_at");
|
||||
grant.grantedById = static_cast<uint32_t>(result.getInt64Field("granted_by_id"));
|
||||
grant.grantedBy = result.getStringField("granted_by");
|
||||
grant.revokedAt = result.getInt64Field("revoked_at");
|
||||
grant.revokedBy = result.getStringField("revoked_by");
|
||||
return grant;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t SQLiteDatabase::InsertPermissionGrant(const Grant& grant) {
|
||||
ExecuteInsert("INSERT INTO permission_grants (target_type, target_id, kind, name, deny, expires_at, note, granted_at, granted_by_id, granted_by) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
grant.targetType, grant.targetId, grant.kind, grant.name, grant.deny, grant.expiresAt, grant.note, grant.grantedAt, grant.grantedById, grant.grantedBy);
|
||||
auto [_, result] = ExecuteSelect("SELECT last_insert_rowid() AS id;");
|
||||
return result.eof() ? 0 : static_cast<uint64_t>(result.getInt64Field("id"));
|
||||
}
|
||||
|
||||
std::optional<IPermissionGrants::Grant> SQLiteDatabase::GetPermissionGrant(uint64_t id) {
|
||||
auto [_, result] = ExecuteSelect("SELECT * FROM permission_grants WHERE id = ?;", static_cast<int64_t>(id));
|
||||
if (result.eof()) return std::nullopt;
|
||||
return ReadGrant(result);
|
||||
}
|
||||
|
||||
std::vector<IPermissionGrants::Grant> SQLiteDatabase::GetPermissionGrants(const std::string& targetType, int64_t targetId) {
|
||||
std::vector<Grant> grants;
|
||||
auto [_, result] = ExecuteSelect("SELECT * FROM permission_grants WHERE target_type = ? AND target_id = ? ORDER BY id DESC;", targetType, targetId);
|
||||
for (; !result.eof(); result.nextRow()) grants.push_back(ReadGrant(result));
|
||||
return grants;
|
||||
}
|
||||
|
||||
std::vector<IPermissionGrants::Grant> SQLiteDatabase::GetActivePermissionGrants(uint32_t accountId, int64_t characterId, int64_t now) {
|
||||
std::vector<Grant> grants;
|
||||
auto [_, result] = ExecuteSelect("SELECT * FROM permission_grants WHERE revoked_at = 0 AND (expires_at = 0 OR expires_at > ?) AND "
|
||||
"((target_type = 'account' AND target_id = ?) OR (? <> 0 AND target_type = 'character' AND target_id = ?)) ORDER BY id DESC;",
|
||||
now, static_cast<int64_t>(accountId), characterId, characterId);
|
||||
for (; !result.eof(); result.nextRow()) grants.push_back(ReadGrant(result));
|
||||
return grants;
|
||||
}
|
||||
|
||||
std::vector<IPermissionGrants::Grant> SQLiteDatabase::GetRecentPermissionGrants(bool activeOnly, int64_t now, uint32_t limit) {
|
||||
std::vector<Grant> grants;
|
||||
auto [_, result] = activeOnly
|
||||
? ExecuteSelect("SELECT * FROM permission_grants WHERE revoked_at = 0 AND (expires_at = 0 OR expires_at > ?) ORDER BY id DESC LIMIT ?;", now, limit)
|
||||
: ExecuteSelect("SELECT * FROM permission_grants ORDER BY id DESC LIMIT ?;", limit);
|
||||
for (; !result.eof(); result.nextRow()) grants.push_back(ReadGrant(result));
|
||||
return grants;
|
||||
}
|
||||
|
||||
bool SQLiteDatabase::RevokePermissionGrant(uint64_t id, const std::string& revokedBy, int64_t time) {
|
||||
return ExecuteUpdate("UPDATE permission_grants SET revoked_at = ?, revoked_by = ? WHERE id = ? AND revoked_at = 0;", time, revokedBy, static_cast<int64_t>(id)) > 0;
|
||||
}
|
||||
@@ -214,6 +214,12 @@ class TestSQLDatabase : public GameDatabase {
|
||||
std::vector<AccountNote> GetAccountNotes(uint32_t accountId) override { return {}; }
|
||||
std::optional<AccountNote> GetAccountNote(uint64_t id) override { return {}; }
|
||||
void DeleteAccountNote(uint64_t id) override {}
|
||||
uint64_t InsertPermissionGrant(const Grant& grant) override { return 0; }
|
||||
std::optional<Grant> GetPermissionGrant(uint64_t id) override { return {}; }
|
||||
std::vector<Grant> GetPermissionGrants(const std::string& targetType, int64_t targetId) override { return {}; }
|
||||
std::vector<Grant> GetActivePermissionGrants(uint32_t accountId, int64_t characterId, int64_t now) override { return {}; }
|
||||
std::vector<Grant> GetRecentPermissionGrants(bool activeOnly, int64_t now, uint32_t limit) override { return {}; }
|
||||
bool RevokePermissionGrant(uint64_t id, const std::string& revokedBy, int64_t time) override { return false; }
|
||||
uint64_t InsertApiKey(const ApiKey& key) override { return 0; }
|
||||
std::optional<ApiKey> GetApiKey(uint64_t id) override { return {}; }
|
||||
std::optional<ApiKey> GetApiKeyByHash(const std::string& keyHash) override { return {}; }
|
||||
|
||||
Reference in New Issue
Block a user