PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.4
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.4
1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk 1.2.0 All 47 releases
fluent-cart / app / Modules / MCP / Support / PermissionGate.php

PermissionGate.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.4, at app/Modules/MCP/Support/PermissionGate.php

143 lines 4.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Modules\MCP\Support;
4
5 use FluentCart\Api\ModuleSettings;
6 use FluentCart\App\Services\Permission\PermissionManager;
7
8 /**
9 * Maps MCP abilities to FluentCart's existing capability model. The MCP user
10 * IS a WordPress user with a FluentCart shop role (super_admin / manager /
11 * worker / accountant), so we never invent a parallel permission system — we
12 * reuse PermissionManager, the same check the admin REST routes use.
13 *
14 * Two layers:
15 * - transport(): can this user reach the FluentCart MCP endpoint at all?
16 * - can()/canAny(): per-ability permission_callback gating.
17 *
18 * Annotations are UX hints only; THIS is the enforcement boundary.
19 */
20 class PermissionGate
21 {
22 /** Per-ability check. Used inside permission_callback closures. */
23 public static function can($permission)
24 {
25 return PermissionManager::hasPermission($permission);
26 }
27
28 /** True if the user holds ANY of the given capabilities. */
29 public static function canAny(array $permissions)
30 {
31 return PermissionManager::hasAnyPermission($permissions);
32 }
33
34 /**
35 * Transport gate for the `fluent-cart` server. Reaching the endpoint at all
36 * requires (a) the feature is enabled and (b) the user holds at least a
37 * read-level FluentCart role. The adapter's default is merely
38 * current_user_can('read'), which is too loose for commerce data.
39 *
40 * Per-ability permission_callback still runs on top — a viewer who reaches
41 * the endpoint still can't refund or mutate.
42 */
43 public static function transport($request = null)
44 {
45 if (!self::isEnabled()) {
46 return new \WP_Error(
47 'fluent_cart_mcp_disabled',
48 __('The FluentCart MCP server is disabled. Enable it in Settings → MCP.', 'fluent-cart')
49 );
50 }
51
52 if (!is_user_logged_in()) {
53 return new \WP_Error(
54 'fluent_cart_mcp_unauthorized',
55 __('Authentication required to access the FluentCart MCP server.', 'fluent-cart')
56 );
57 }
58
59 if (!PermissionManager::hasAnyPermission(self::readRoleCaps())) {
60 return new \WP_Error(
61 'fluent_cart_mcp_forbidden',
62 __('Your account does not have FluentCart access.', 'fluent-cart')
63 );
64 }
65
66 return true;
67 }
68
69 /**
70 * Any one of these means "has at least a FluentCart role." Kept as a method
71 * (not a const) so it can grow without touching call sites.
72 */
73 public static function readRoleCaps()
74 {
75 return [
76 'dashboard_stats/view',
77 'orders/view',
78 'products/view',
79 'customers/view',
80 'reports/view',
81 'subscriptions/view',
82 'coupons/view',
83 ];
84 }
85
86 /** Write-level caps — used by the optional two-server (admin) hardening. */
87 public static function writeRoleCaps()
88 {
89 return [
90 'orders/manage',
91 'orders/manage_statuses',
92 'orders/can_refund',
93 'products/edit',
94 'customers/manage',
95 'coupons/manage',
96 'subscriptions/manage',
97 ];
98 }
99
100 /**
101 * The master on/off switch. Ships OFF; enabled from Settings → Features &
102 * addon → MCP.
103 *
104 * Stored under the `mcp` key of the shared `fluent_cart_modules_settings`
105 * option (autoloaded, so reading it here costs no extra query on init —
106 * unlike a dedicated meta option, which the boot guard would query on every
107 * request). Persisted via setEnabled().
108 */
109 public static function isEnabled()
110 {
111 return ModuleSettings::isActive('mcp');
112 }
113
114 /**
115 * Persist the master switch into the shared modules blob. Reads the RAW
116 * option (not the defaults-merged view) and rewrites only the `mcp` key so
117 * no other module's settings are touched.
118 */
119 public static function setEnabled($enabled)
120 {
121 // Defense in depth: enabling MCP opens the whole tool surface. The REST
122 // route is is_super_admin-gated, but the fluent_kit/mcp_toggle_handlers
123 // path delegates auth to an external plugin — so re-check here. Both
124 // callers run in an admin request context (no CLI/system toggle exists).
125 if (!current_user_can('manage_options')) {
126 return false;
127 }
128
129 $settings = get_option(ModuleSettings::MODULE_SETTINGS_OPTION, []);
130 if (!is_array($settings)) {
131 $settings = [];
132 }
133
134 $mcp = (isset($settings['mcp']) && is_array($settings['mcp'])) ? $settings['mcp'] : [];
135 $mcp['active'] = $enabled ? 'yes' : 'no';
136 $settings['mcp'] = $mcp;
137
138 ModuleSettings::saveSettings($settings);
139
140 return (bool) $enabled;
141 }
142 }
143