VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
Policy.hpp
Go to the documentation of this file.
1#pragma once
2
7#include <cstdint>
8#include <functional>
9#include <optional>
10#include <string_view>
11
12namespace VoltMod
13{
14
15/**
16 * @brief A caller/target pair that cleared @ref Policy::Authorize.
17 *
18 * Only @ref Policy::Authorize produces one, so a function taking an `Authorized` is stating in
19 * its signature that the check has already happened. Both players are connected for the length
20 * of the call; @ref Target is null when the action has no target.
21 */
23{
25 Player* Target = nullptr;
26};
27
28/**
29 * @brief The one permission and targeting gate, plus the plugin's reply callback.
30 *
31 * The runtime sets @ref HasPermission to ask the published @ref IPermissions; a plugin fills the
32 * others it enforces once in Plugin::Load. Every policy-aware framework subsystem - command
33 * dispatch, target resolution, menu rows - goes through @ref Authorize to reach them. An unset
34 * @ref CanTarget or @ref Reply means "no rule / no callback"; an unset @ref HasPermission denies,
35 * because there is then no trusted permission source.
36 *
37 * `Policy` is not assignable as a whole: it is constructed with the roster it resolves refs
38 * against. Assign the members you enforce.
39 */
40class Policy
41{
42public:
43 /** @p players must outlive the policy; the Runtime declares the roster above it. */
44 explicit Policy(PlayerManager& players) : _players(players) {}
45
46 Policy(const Policy&) = delete;
47 Policy& operator=(const Policy&) = delete;
48
49 /** Does @p steamId hold @p permission? Unset denies every permission-gated action. */
50 std::function<bool(int64_t steamId, std::string_view permission)> HasPermission;
51
52 /**
53 * May the caller act on the target (immunity, same-team rules)? Never consulted for the
54 * server console, which has no caller, nor for a caller targeting themselves.
55 *
56 * Takes SteamIDs rather than `Player&` so the same rule answers for an offline target -
57 * see @ref AuthorizeSteamId. An immunity comparison never needed the connected object.
58 */
60
61 /** Deliver a command result or error line (e.g. as a colored chat reply); unset falls back
62 * to a plain `runtime.Messages.Send`. */
63 std::function<void(int slot, std::string_view message)> Reply;
64
65 /**
66 * @brief The single gate. Commands, target resolution and menu rows call exactly this.
67 *
68 * Outcomes, in the order they are decided:
69 *
70 * | Condition | Result |
71 * | -------------------------------------------- | ---------------------------------------- |
72 * | @p caller is not connected | `ErrorCode::NotFound`, no Key |
73 * | @p target given but not connected | `ErrorCode::NotFound`, Key `target.noMatch` |
74 * | @p permission non-empty, @ref HasPermission unset | `ErrorCode::Denied`, Key `cmd.noPermission` (logged once) |
75 * | @ref HasPermission says no | `ErrorCode::Denied`, Key `cmd.noPermission` |
76 * | @ref CanTarget says no | `ErrorCode::Immune`, Key `target.immune` |
77 * | otherwise | the @ref Authorized pair |
78 *
79 * An empty @p permission skips the permission check. Targeting yourself is always allowed:
80 * the rule lives here rather than in each plugin's @ref CanTarget, so @ref CanTarget only
81 * ever answers "may this caller act on somebody else".
82 *
83 * Denial is a value. Nothing is nulled out to signal it, so a caller that ignores the
84 * @ref Result cannot accidentally run the action anyway.
85 */
86 Result<Authorized> Authorize(PlayerRef caller, std::optional<PlayerRef> target, std::string_view permission) const;
87
88 /**
89 * @brief @ref Authorize for a target that may be offline, addressed by SteamID.
90 *
91 * Same decision order minus the target-connected check. Returns @ref Status rather than
92 * @ref Authorized because an offline target has no `Player` to hand back: the answer is
93 * "allowed" or the @ref Error saying why not, and there is nothing to act *on* that the
94 * caller did not already have.
95 *
96 * Use this wherever a command binds `Args::PlayerOrSteamId` or a bare SteamID. Reaching
97 * past it to a plugin's own immunity table is what let offline targets skip the gate.
98 */
100
101private:
102 /** The permission half of both entry points, so "no HasPermission installed" cannot come to
103 * mean one thing for an online target and another for an offline one. */
104 Status CheckPermission(const Player& caller, std::string_view permission) const;
105
106 /** The immunity half of both entry points. Self-targeting is the framework's rule, not the
107 * plugin's: an immunity comparison has nothing sensible to say about a player and
108 * themselves, and every gate used to answer it differently. */
109 Status CheckImmunity(const Player& caller, int64_t targetSteamId) const;
110
111 PlayerManager& _players;
112 /** Set once the missing-HasPermission denial has been logged, so it does not repeat for
113 * every command a player types. */
114 mutable bool _missingPermissionWarned = false;
115};
116
117} // namespace VoltMod
The roster: every connected player, and the signals for the connection lifecycle.
One connected player, owned by PlayerManager for the length of the connection.
Definition Player.hpp:23
The one permission and targeting gate, plus the plugin's reply callback.
Definition Policy.hpp:41
Status AuthorizeSteamId(PlayerRef caller, int64_t targetSteamId, std::string_view permission) const
Authorize for a target that may be offline, addressed by SteamID.
Definition Policy.cpp:87
Policy & operator=(const Policy &)=delete
std::function< bool(int64_t steamId, std::string_view permission)> HasPermission
Definition Policy.hpp:50
Policy(PlayerManager &players)
Definition Policy.hpp:44
std::function< bool(int64_t callerSteamId, int64_t targetSteamId)> CanTarget
Definition Policy.hpp:59
Policy(const Policy &)=delete
std::function< void(int slot, std::string_view message)> Reply
Definition Policy.hpp:63
Result< Authorized > Authorize(PlayerRef caller, std::optional< PlayerRef > target, std::string_view permission) const
The single gate. Commands, target resolution and menu rows call exactly this.
Definition Policy.cpp:52
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56
std::expected< void, Error > Status
Definition Result.hpp:67
std::expected< T, Error > Result
Definition Result.hpp:64
A caller/target pair that cleared Policy::Authorize.
Definition Policy.hpp:23
A storable reference to a player: the slot plus the SteamID that occupied it.
Definition PlayerRef.hpp:19