diff --git a/docs/conditions.md b/docs/conditions.md index 2517887d..70aa6b01 100644 --- a/docs/conditions.md +++ b/docs/conditions.md @@ -1551,3 +1551,31 @@ Note: it will show or hide spells in vehicle spell bar. - 0 (If target needs to be on land) +### Breadcrumb Quests + +Breadcrumb quests are optional quests that lead players to a quest hub or NPC but can be skipped if the player goes directly to the destination. Unlike mutually exclusive quests, a breadcrumb should only become unavailable after the player picks up or completes the main quest - NOT vice versa. + +**Do NOT use [ExclusiveGroup](quest_template_addon#exclusivegroup)** for breadcrumb quests. ExclusiveGroup makes quests mutually exclusive in both directions, which breaks quest progression if the player does the breadcrumb first. + +**Correct approach:** Use CONDITION_SOURCE_TYPE_QUEST_AVAILABLE (19) with CONDITION_QUESTTAKEN (9) and CONDITION_QUESTREWARDED (8) to hide the breadcrumb when the main quest is taken or completed. + +**Example:** Quest 11287 "Find Sage Mistwalker" is a breadcrumb leading to quest 11286 "The Artifacts of Steel Gate". Players can skip the breadcrumb and go directly to 11286. + +```sql +-- Make breadcrumb unavailable if main quest is taken or rewarded +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId` = 19 AND `SourceEntry` = 11287; +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(19, 0, 11287, 0, 0, 9, 0, 11286, 0, 0, 1, 0, 0, '', 'Find Sage Mistwalker - Not available if The Artifacts of Steel Gate is taken'), +(19, 0, 11287, 0, 0, 8, 0, 11286, 0, 0, 1, 0, 0, '', 'Find Sage Mistwalker - Not available if The Artifacts of Steel Gate is rewarded'); +``` + +**Explanation:** +- `SourceTypeOrReferenceId = 19`: CONDITION_SOURCE_TYPE_QUEST_AVAILABLE - controls when quest appears +- `SourceEntry = 11287`: The breadcrumb quest ID +- `ConditionTypeOrReference = 9`: CONDITION_QUESTTAKEN - checks if quest is in player's log +- `ConditionTypeOrReference = 8`: CONDITION_QUESTREWARDED - checks if quest was completed +- `ConditionValue1 = 11286`: The main quest ID to check against +- `NegativeCondition = 1`: Inverts the condition (quest NOT available if condition is true) + +Both conditions are in ElseGroup 0, so they act as a logical AND - the breadcrumb is only available if the player has NOT taken AND has NOT been rewarded the main quest. + diff --git a/docs/database-auth.md b/docs/database-auth.md index d7e3c9d5..8ad5cc54 100644 --- a/docs/database-auth.md +++ b/docs/database-auth.md @@ -20,9 +20,14 @@ ## M +- [module_rbac_permissions](module_rbac_permissions) - [motd](motd) ## R +- [rbac_account_permissions](rbac_account_permissions) +- [rbac_default_permissions](rbac_default_permissions) +- [rbac_linked_permissions](rbac_linked_permissions) +- [rbac_permissions](rbac_permissions) - [realmcharacters](realmcharacters) - [realmlist](realmlist) diff --git a/docs/documentation-index.md b/docs/documentation-index.md index d52e9959..9a8769f9 100644 --- a/docs/documentation-index.md +++ b/docs/documentation-index.md @@ -39,6 +39,7 @@ redirect_from: /documentation_index * [Monitoring AzerothCore with Grafana](monitoring-azerothcore-with-grafana) * [MySQL types (C++)](mysqltypescpp) * [Project Versioning](project-versioning) +* [RBAC](rbac) * [Remote Access](remote-access) * [Sniffing & Parsing](sniffing-and-parsing) * [Spell Effects Reference](spell-effects-reference) diff --git a/docs/module_rbac_permissions.md b/docs/module_rbac_permissions.md new file mode 100644 index 00000000..2b7a79c2 --- /dev/null +++ b/docs/module_rbac_permissions.md @@ -0,0 +1,121 @@ +# module\_rbac\_permissions + +[<-Back-to:Auth](database-auth) + +**The \`module\_rbac\_permissions\` table** + +This table allows modules to register their own RBAC permissions without risk of ID collisions with core permissions or other modules. Each module uses local IDs (1, 2, 3, ...) and the table auto-assigns global IDs starting at 100000. + +For a system overview, see [RBAC](rbac). + +**Table Structure** + +| Field | Type | Attributes | Key | Null | Default | Extra | Comment | +| -------------- | ------------ | ---------- | ------ | ---- | ------- | -------------- | ------------------------------------ | +| [module](#module) | VARCHAR(255) | SIGNED | PRI | NO | | | Module dir name, e.g. mod-cfbg | +| [id](#id) | INT | UNSIGNED | PRI | NO | | | Module-local permission id | +| [global_id](#globalid) | INT | UNSIGNED | UNIQUE | NO | | AUTO_INCREMENT | Auto-assigned global permission ID | +| [name](#name) | VARCHAR(100) | SIGNED | | NO | | | Permission name | + +The primary key is the composite `(module, id)`, which prevents cross-module ID collisions. The `global_id` column has a separate `UNIQUE` index and auto-increments starting at 100000. + +**Description of the fields** + +### module + +The module directory name (e.g. `mod-cfbg`, `mod-eluna`). This must match the module's directory name under `modules/`. + +### id + +A module-local permission ID. Each module manages its own ID sequence starting at 1. The same local ID can be used by different modules without conflict because the primary key includes the module name. + +### global\_id + +An auto-assigned globally unique permission ID used by the core RBAC system. The `AUTO_INCREMENT` starts at 100000 to avoid collisions with core permission IDs (1–924). Modules should never set this value manually — it is assigned by the database on insert. + +### name + +A human-readable name for the permission. By convention, command permissions use the format `Command: .commandname subcommand`. + +## Module Integration Guide + +### Step 1: Register permissions in SQL + +Create a SQL file in your module's `data/sql/db-auth/` directory: + +```sql +INSERT IGNORE INTO `module_rbac_permissions` (`module`, `id`, `name`) VALUES +('mod-example', 1, 'Command: .example hello'), +('mod-example', 2, 'Command: .example info'); +``` + +Use `INSERT IGNORE` so the SQL is safe to re-run. + +### Step 2: Look up global IDs in C++ + +In your module's C++ code, use `AccountMgr` to translate local IDs to global IDs: + +```cpp +#include "AccountMgr.h" + +uint32 globalId = sAccountMgr->GetModulePermission("mod-example", 1); +``` + +Returns 0 if the permission is not found in the database. + +### Step 3: Use in CommandScript + +When defining commands, use the global ID as the permission for each command: + +```cpp +static uint32 GetPermission(uint32 localId) +{ + return sAccountMgr->GetModulePermission("mod-example", localId); +} + +std::vector GetCommands() const override +{ + static std::vector exampleCommandTable = + { + { "hello", GetPermission(1), false, &HandleHelloCommand, "" }, + { "info", GetPermission(2), false, &HandleInfoCommand, "" }, + }; + + static std::vector commandTable = + { + { "example", SEC_PLAYER, false, nullptr, "", exampleCommandTable }, + }; + + return commandTable; +} +``` + +### Step 4: Assign permissions + +Module permissions need to be assigned to roles or accounts before anyone can use them. + +**Assign to a role** — link the module permissions into a command role via [rbac_linked_permissions](rbac_linked_permissions). Everyone who inherits that role will gain access: + +```sql +-- Grant all mod-example commands to the GM Commands role (197) +INSERT IGNORE INTO `rbac_linked_permissions` (`id`, `linkedId`) +SELECT 197, `global_id` +FROM `module_rbac_permissions` +WHERE `module` = 'mod-example'; +``` + +**Assign to a specific account** — insert into [rbac_account_permissions](rbac_account_permissions) or use the `.rbac` command in-game: + +```sql +-- Grant mod-example's first command to account 5 on all realms +INSERT INTO `rbac_account_permissions` (`accountId`, `permissionId`, `granted`, `realmId`) +SELECT 5, `global_id`, 1, -1 +FROM `module_rbac_permissions` +WHERE `module` = 'mod-example' AND `id` = 1; +``` + +Or in-game, once you know the global ID (e.g. 100001): + +``` +.rbac account grant 5 100001 +``` diff --git a/docs/quest_template_addon.md b/docs/quest_template_addon.md index 464c462d..c5c6e7b8 100644 --- a/docs/quest_template_addon.md +++ b/docs/quest_template_addon.md @@ -68,6 +68,8 @@ Allows to define a group of quests of which all must be completed and rewarded t Note: All quests that use an ExclusiveGroup must also have entries in [pool_template](pool_template) and [pool_quest](quest_template#examples-dealing-with-quests) for examples. +**Important:** ExclusiveGroup should NOT be used for breadcrumb quests. Use [conditions](conditions#breadcrumb-quests) instead to properly handle optional lead-in quests. + ### RewardMailTemplateID If the quest gives as a reward an item from a possible list of items, the ID here corresponds to the proper loot template in [quest_mail_loot_template](loot_template). According to the rules in that loot template, items "looted" will be sent by mail at the completion of the quest. diff --git a/docs/rbac.md b/docs/rbac.md new file mode 100644 index 00000000..a3dd6f9f --- /dev/null +++ b/docs/rbac.md @@ -0,0 +1,109 @@ +# RBAC + +[<-Back-to:Documentation Index](documentation-index) + +## Overview + +Role-Based Access Control (RBAC) is AzerothCore's permission system. It provides fine-grained control over what each account can do — from individual commands to gameplay privileges like skipping the login queue or joining battlegrounds. + +With RBAC you can: + +- Control access to every command individually +- Create roles that bundle related permissions together +- Override specific permissions per account without changing their overall rank +- Let modules register their own permissions without conflicting with core IDs + +## Architecture + +The RBAC system is built on four concepts: + +1. **Permissions** — Individual capabilities (e.g. "can use `.tele`", "skip queue", "join battlegrounds") +2. **Roles** — Permissions that link to other permissions, forming a group. Granting a role grants everything it contains +3. **Defaults** — Mappings from `account_access.gmlevel` to an initial RBAC role, so security levels map to permission sets automatically +4. **Account Overrides** — Per-account grants or denies that fine-tune access beyond the defaults + +### Database Tables + +| Table | Purpose | +| ----- | ------- | +| [rbac_permissions](rbac_permissions) | Defines all available permissions | +| [rbac_linked_permissions](rbac_linked_permissions) | Links roles to their child permissions | +| [rbac_default_permissions](rbac_default_permissions) | Maps security levels to default roles | +| [rbac_account_permissions](rbac_account_permissions) | Per-account overrides (grant/deny) | +| [module_rbac_permissions](module_rbac_permissions) | Module-registered permissions | + +A convenience view `vw_rbac` joins the linked and default tables for easier querying. + +## Permission ID Ranges + +| Range | Purpose | Examples | +| ----- | ------- | ------- | +| 1–53 | Gameplay permissions | Instant logout, skip queue, join BG/arena/dungeon finder | +| 192–195 | Security-level roles | Administrator (192), Gamemaster (193), Moderator (194), Player (195) | +| 196–199 | Command roles | Admin Commands (196), GM Commands (197), Mod Commands (198), Player Commands (199) | +| 200–924 | Individual command permissions | One per `.command` | +| 100000+ | Module permissions | Auto-assigned via [module_rbac_permissions](module_rbac_permissions) | + +## Role Hierarchy + +Roles inherit from each other through linked permissions. Each higher role links to the one below it, so an Administrator automatically receives every permission a Player has. + +``` +Administrator (192) +├── Core admin perms (7, 21, 42, 43) +├── Admin Commands (196) +└── Gamemaster (193) + ├── Core GM perms (45, 48, 52, 53) + ├── GM Commands (197) + └── Moderator (194) + ├── Core mod perms (1, 2, 9, 11, 13–47, 51, ...) + ├── Mod Commands (198) + └── Player (195) + ├── Core player perms (3, 4, 5, 6, 24, 49, 50) + └── Player Commands (199) +``` + +### Default Security Level Mapping + +When a player logs in, their `account_access.gmlevel` determines which role they receive: + +| gmlevel | Role | Permission ID | +| ------- | ---- | ------------- | +| 3 | Administrator | 192 | +| 2 | Gamemaster | 193 | +| 1 | Moderator | 194 | +| 0 | Player | 195 | + +These defaults are stored in [rbac_default_permissions](rbac_default_permissions). + +## Permission Resolution + +When an account's permissions are calculated, the following steps occur: + +1. **Collect grants** — Account-specific grants from [rbac_account_permissions](rbac_account_permissions) plus defaults from [rbac_default_permissions](rbac_default_permissions) +2. **Expand grants** — Each granted permission is expanded recursively through [rbac_linked_permissions](rbac_linked_permissions). If permission 192 links to 193, and 193 links to 194, all are included +3. **Collect denies** — Account-specific denies from [rbac_account_permissions](rbac_account_permissions) +4. **Expand denies** — Denied permissions are also expanded through linked permissions +5. **Subtract** — Final permissions = Expanded Grants − Expanded Denies + +This means denying a role denies everything that role contains. + +## Commands + +The `.rbac` commands allow live management of account permissions without restarting the server. + +| Command | Permission | Description | +| ------- | ---------- | ----------- | +| `.rbac account list ` | 202 | List granted, denied, and default permissions for an account | +| `.rbac account grant [realmId]` | 203 | Grant a permission to an account | +| `.rbac account deny [realmId]` | 204 | Deny a permission for an account | +| `.rbac account revoke [realmId]` | 205 | Revoke a previously granted or denied permission | +| `.rbac list [permId]` | 206 | List all permissions, or show details for a specific permission | + +Changes take effect immediately for online players. + +## Module Integration + +Modules can register their own RBAC permissions using the [module_rbac_permissions](module_rbac_permissions) table. Each module uses local IDs (1, 2, 3, ...) that are automatically mapped to global IDs starting at 100000, avoiding conflicts with core permission IDs and between modules. + +See [module_rbac_permissions](module_rbac_permissions) for the full integration guide. diff --git a/docs/rbac_account_permissions.md b/docs/rbac_account_permissions.md new file mode 100644 index 00000000..27c34ecc --- /dev/null +++ b/docs/rbac_account_permissions.md @@ -0,0 +1,48 @@ +# rbac\_account\_permissions + +[<-Back-to:Auth](database-auth) + +**The \`rbac\_account\_permissions\` table** + +This table stores per-account permission overrides. Use it to grant or deny specific permissions to individual accounts, beyond what their default security level provides. + +For a system overview, see [RBAC](rbac). + +**Table Structure** + +| Field | Type | Attributes | Key | Null | Default | Extra | Comment | +| ----------------- | ---------- | ---------- | --- | ---- | ------- | ----- | ------------------------ | +| [accountId](#accountid) | INT | UNSIGNED | PRI | NO | | | Account id | +| [permissionId](#permissionid) | INT | UNSIGNED | PRI | NO | | | Permission id | +| [granted](#granted) | TINYINT(1) | SIGNED | | NO | 1 | | Granted = 1, Denied = 0 | +| [realmId](#realmid) | INT | SIGNED | PRI | NO | -1 | | Realm Id, -1 means all | + +The `accountId` field has a foreign key to [account.id](account#id) with `ON DELETE CASCADE`. +The `permissionId` field has a foreign key to [rbac_permissions.id](rbac_permissions#id) with `ON DELETE CASCADE`. + +**Description of the fields** + +### accountId + +The [account ID](account#id). + +### permissionId + +The permission ID from [rbac_permissions](rbac_permissions). This can be an individual permission or a role. Granting/denying a role affects all permissions linked to it. + +### granted + +Controls whether this entry is a grant or a deny: + +| Value | Meaning | +| ----- | ------- | +| 1 | **Grant** — adds this permission to the account | +| 0 | **Deny** — removes this permission (and its linked permissions) from the account | + +During [permission resolution](rbac#permission-resolution), denies are subtracted from grants after expansion. This means denying a role denies everything that role contains. + +### realmId + +The [realm ID](realmlist#id) this override applies to. Use `-1` to apply to all realms. + +These overrides can be managed in-game with the [.rbac commands](rbac#commands). diff --git a/docs/rbac_default_permissions.md b/docs/rbac_default_permissions.md new file mode 100644 index 00000000..267e5cac --- /dev/null +++ b/docs/rbac_default_permissions.md @@ -0,0 +1,42 @@ +# rbac\_default\_permissions + +[<-Back-to:Auth](database-auth) + +**The \`rbac\_default\_permissions\` table** + +This table maps [account_access.gmlevel](account_access#gmlevel) security levels to default RBAC roles. When a player logs in, their security level determines which role they receive automatically. + +For a system overview, see [RBAC](rbac). + +**Table Structure** + +| Field | Type | Attributes | Key | Null | Default | Extra | Comment | +| ----------------- | ------- | ---------- | --- | ---- | ------- | ----- | ------------------------ | +| [secId](#secid) | INT | UNSIGNED | PRI | NO | | | Security Level id | +| [permissionId](#permissionid) | INT | UNSIGNED | PRI | NO | | | Permission id | +| [realmId](#realmid) | INT | SIGNED | PRI | NO | -1 | | Realm Id, -1 means all | + +The `permissionId` field has a foreign key to [rbac_permissions.id](rbac_permissions#id). + +**Description of the fields** + +### secId + +The security level from [account_access.gmlevel](account_access#gmlevel). + +### permissionId + +The RBAC permission (role) to grant by default for this security level. The default assignments are: + +| secId | permissionId | Role | +| ----- | ------------ | ---- | +| 0 | 195 | Player | +| 1 | 194 | Moderator | +| 2 | 193 | Gamemaster | +| 3 | 192 | Administrator | + +Because roles chain through [rbac_linked_permissions](rbac_linked_permissions), granting Administrator (192) automatically includes Gamemaster, Moderator, and Player permissions. + +### realmId + +The [realm ID](realmlist#id) this default applies to. Use `-1` to apply to all realms. diff --git a/docs/rbac_linked_permissions.md b/docs/rbac_linked_permissions.md new file mode 100644 index 00000000..0e9c4842 --- /dev/null +++ b/docs/rbac_linked_permissions.md @@ -0,0 +1,39 @@ +# rbac\_linked\_permissions + +[<-Back-to:Auth](database-auth) + +**The \`rbac\_linked\_permissions\` table** + +This table defines the parent-child relationships between permissions. When a permission (typically a role) is granted, all of its linked permissions are also granted. This is how role inheritance works in the [RBAC](rbac) system. + +**Table Structure** + +| Field | Type | Attributes | Key | Null | Default | Extra | Comment | +| ------------- | ---- | ---------- | --- | ---- | ------- | ----- | -------------------- | +| [id](#id) | INT | UNSIGNED | PRI | NO | | | Permission id | +| [linkedId](#linkedid) | INT | UNSIGNED | PRI | NO | | | Linked Permission id | + +Both fields have a foreign key to [rbac_permissions.id](rbac_permissions#id) with `ON DELETE CASCADE`. + +**Description of the fields** + +### id + +The parent permission (role) that contains other permissions. Typically one of the role IDs: + +| ID | Role | +| -- | ---- | +| 192 | Administrator | +| 193 | Gamemaster | +| 194 | Moderator | +| 195 | Player | +| 196 | Admin Commands | +| 197 | GM Commands | +| 198 | Mod Commands | +| 199 | Player Commands | + +### linkedId + +The child permission that is granted when the parent (`id`) is granted. Can be any permission from [rbac_permissions](rbac_permissions), including another role — this is how the hierarchy chains together (e.g. Administrator 192 links to Gamemaster 193, which links to Moderator 194, and so on). + +Linked permissions are expanded recursively during [permission resolution](rbac#permission-resolution). diff --git a/docs/rbac_permissions.md b/docs/rbac_permissions.md new file mode 100644 index 00000000..02fb67bc --- /dev/null +++ b/docs/rbac_permissions.md @@ -0,0 +1,38 @@ +# rbac\_permissions + +[<-Back-to:Auth](database-auth) + +**The \`rbac\_permissions\` table** + +This table defines all available RBAC permissions. Each permission represents a single capability — a gameplay privilege, a command, or a role that bundles other permissions together. + +For a system overview, see [RBAC](rbac). + +**Table Structure** + +| Field | Type | Attributes | Key | Null | Default | Extra | Comment | +| -------- | ------------ | ---------- | --- | ---- | ------- | ----- | ------------- | +| [id](#id) | INT | UNSIGNED | PRI | NO | 0 | | Permission id | +| [name](#name) | VARCHAR(100) | SIGNED | | NO | | | Permission name | + +**Description of the fields** + +### id + +The unique permission identifier. ID ranges are: + +| Range | Purpose | +| ----- | ------- | +| 1–53 | Gameplay permissions (instant logout, skip queue, join BG, etc.) | +| 192–195 | Security-level roles (Administrator, Gamemaster, Moderator, Player) | +| 196–199 | Command roles (Admin Commands, GM Commands, Mod Commands, Player Commands) | +| 200–924 | Individual command permissions (one per `.command`) | +| 100000+ | Module permissions (auto-assigned via [module_rbac_permissions](module_rbac_permissions)) | + +### name + +A human-readable name describing the permission. Follows conventions: + +- Gameplay permissions: descriptive name (e.g. `Instant logout`, `Skip Queue`) +- Roles: prefixed with `Role:` (e.g. `Role: Sec Level Administrator`) +- Commands: prefixed with `Command:` (e.g. `Command: rbac account list`)