From bb9046af1b87e2cf8df58b3914950ec586504dc6 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 13 Jan 2026 09:04:15 +0000 Subject: [PATCH] Move resolved_permissions to user object via toArray() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Core/Bundle/Rsx_Bundle_Abstract.php | 4 - app/RSpade/Core/Js/Permission.js | 13 +- app/RSpade/Core/Models/User_Model.php | 26 ++++ app/RSpade/man/acls.txt | 6 +- app/RSpade/man/rsxapp.txt | 33 ++--- .../permission_resolved_01_13.txt | 135 ++++++++++-------- 6 files changed, 129 insertions(+), 88 deletions(-) diff --git a/app/RSpade/Core/Bundle/Rsx_Bundle_Abstract.php b/app/RSpade/Core/Bundle/Rsx_Bundle_Abstract.php index e1806d74b..09a288412 100644 --- a/app/RSpade/Core/Bundle/Rsx_Bundle_Abstract.php +++ b/app/RSpade/Core/Bundle/Rsx_Bundle_Abstract.php @@ -285,10 +285,6 @@ abstract class Rsx_Bundle_Abstract $rsxapp_data['site'] = Session::get_site(); $rsxapp_data['csrf'] = Session::get_csrf_token(); - // Add resolved permissions for client-side permission checks - $user = Session::get_user(); - $rsxapp_data['resolved_permissions'] = $user ? $user->get_resolved_permissions() : []; - // Add browser error logging flag (enabled in both dev and production) if (config('rsx.log_browser_errors', false)) { $rsxapp_data['log_browser_errors'] = true; diff --git a/app/RSpade/Core/Js/Permission.js b/app/RSpade/Core/Js/Permission.js index 861bed308..2ee19b954 100755 --- a/app/RSpade/Core/Js/Permission.js +++ b/app/RSpade/Core/Js/Permission.js @@ -2,10 +2,11 @@ * Permission - Client-side permission checking * * Provides permission and role checking for JavaScript using pre-resolved - * permissions from window.rsxapp.resolved_permissions. This mirrors the + * permissions from window.rsxapp.user.resolved_permissions. This mirrors the * PHP Permission class functionality for client-side UI logic. * - * The resolved_permissions array is computed server-side by applying: + * The resolved_permissions array is computed server-side via User_Model::toArray() + * by applying: * 1. Role default permissions * 2. Supplementary GRANTs (added) * 3. Supplementary DENYs (removed) @@ -56,7 +57,7 @@ class Permission { * @returns {boolean} */ static has_permission(permission) { - const permissions = window.rsxapp?.resolved_permissions ?? []; + const permissions = window.rsxapp?.user?.resolved_permissions ?? []; return permissions.includes(permission); } @@ -67,7 +68,7 @@ class Permission { * @returns {boolean} */ static has_any_permission(permissions) { - const resolved = window.rsxapp?.resolved_permissions ?? []; + const resolved = window.rsxapp?.user?.resolved_permissions ?? []; return permissions.some(p => resolved.includes(p)); } @@ -78,7 +79,7 @@ class Permission { * @returns {boolean} */ static has_all_permissions(permissions) { - const resolved = window.rsxapp?.resolved_permissions ?? []; + const resolved = window.rsxapp?.user?.resolved_permissions ?? []; return permissions.every(p => resolved.includes(p)); } @@ -124,6 +125,6 @@ class Permission { * @returns {number[]} Array of permission IDs */ static get_resolved_permissions() { - return window.rsxapp?.resolved_permissions ?? []; + return window.rsxapp?.user?.resolved_permissions ?? []; } } diff --git a/app/RSpade/Core/Models/User_Model.php b/app/RSpade/Core/Models/User_Model.php index 9465daebd..87936c8fe 100644 --- a/app/RSpade/Core/Models/User_Model.php +++ b/app/RSpade/Core/Models/User_Model.php @@ -506,4 +506,30 @@ class User_Model extends Rsx_Site_Model_Abstract return $data; } + + // ========================================================================= + // SERIALIZATION + // ========================================================================= + + /** + * Convert model to array with resolved permissions + * + * Adds resolved_permissions and removes role_id__permissions + * (which is redundant since resolved_permissions includes it with + * supplementary grants/denies applied). + * + * @return array + */ + public function toArray() + { + $data = parent::toArray(); + + // Add resolved permissions + $data['resolved_permissions'] = $this->get_resolved_permissions(); + + // Remove role_id__permissions (redundant, use resolved_permissions instead) + unset($data['role_id__permissions']); + + return $data; + } } \ No newline at end of file diff --git a/app/RSpade/man/acls.txt b/app/RSpade/man/acls.txt index 1631c1d80..3ebb511b3 100755 --- a/app/RSpade/man/acls.txt +++ b/app/RSpade/man/acls.txt @@ -309,9 +309,9 @@ JAVASCRIPT PERMISSION CLASS Data Source - The Permission class reads from window.rsxapp.resolved_permissions, - which is populated by the bundle renderer from the session user's - get_resolved_permissions() result. Empty array if not authenticated. + The Permission class reads from window.rsxapp.user.resolved_permissions, + which is populated via User_Model::toArray() from get_resolved_permissions(). + Returns empty array if user is not authenticated. ROUTE PROTECTION diff --git a/app/RSpade/man/rsxapp.txt b/app/RSpade/man/rsxapp.txt index 9026c3de4..896569815 100755 --- a/app/RSpade/man/rsxapp.txt +++ b/app/RSpade/man/rsxapp.txt @@ -5,13 +5,13 @@ NAME SYNOPSIS JavaScript: - window.rsxapp.build_key // Manifest build hash - window.rsxapp.user // Current user model data - window.rsxapp.site // Current site model data - window.rsxapp.resolved_permissions // Pre-computed user permissions - window.rsxapp.page_data // Custom page-specific data - window.rsxapp.is_spa // Whether current page is SPA - window.rsxapp.csrf // CSRF token for forms + window.rsxapp.build_key // Manifest build hash + window.rsxapp.user // Current user model data + window.rsxapp.user.resolved_permissions // Pre-computed user permissions + window.rsxapp.site // Current site model data + window.rsxapp.page_data // Custom page-specific data + window.rsxapp.is_spa // Whether current page is SPA + window.rsxapp.csrf // CSRF token for forms DESCRIPTION window.rsxapp is a global JavaScript object rendered with every page bundle. @@ -64,16 +64,17 @@ OBJECT STRUCTURE Session Data (when authenticated): user Object. Current user model with all fields. - Includes enum properties (role_id__label, etc.) - and __MODEL marker. + Includes enum properties (role_id__label, etc.), + resolved_permissions array, and __MODEL marker. + + user.resolved_permissions + Array. Pre-computed permission IDs for current user. + Includes role defaults plus supplementary grants, + minus supplementary denies. Use Permission class + for checking: Permission.has_permission(perm_id). site Object. Current site model. - resolved_permissions Array. Pre-computed permission IDs for current user. - Includes role defaults plus supplementary grants, - minus supplementary denies. Empty array if not - authenticated. Use Permission.has_permission() to check. - Time Synchronization: server_time String. ISO 8601 UTC timestamp from server. @@ -120,6 +121,7 @@ EXAMPLE OUTPUT "email": "test@example.com", "role_id": 300, "role_id__label": "Site Owner", + "resolved_permissions": [2, 3, 4, 5, 6, 7], "__MODEL": "User_Model" }, "site": { @@ -128,7 +130,6 @@ EXAMPLE OUTPUT "timezone": "America/Chicago", "__MODEL": "Site_Model" }, - "resolved_permissions": [2, 3, 4, 5, 6, 7], "csrf": "f290180b609f8f353c3226accdc798961...", "page_data": { "contact_internal_id": 17 @@ -195,7 +196,7 @@ FRAMEWORK CONSUMERS Rsx_Storage Reads session_hash, user.id, site.id, build_key for scoping Ajax Reads csrf for request headers Spa Reads is_spa, params for routing - Permission Reads resolved_permissions for access control checks + Permission Reads user.resolved_permissions for access control checks Debugger Reads console_debug for output filtering SEE ALSO diff --git a/app/RSpade/upstream_changes/permission_resolved_01_13.txt b/app/RSpade/upstream_changes/permission_resolved_01_13.txt index a0bf9a983..3a1b1a96c 100755 --- a/app/RSpade/upstream_changes/permission_resolved_01_13.txt +++ b/app/RSpade/upstream_changes/permission_resolved_01_13.txt @@ -3,78 +3,44 @@ Date: 2026-01-13 SUMMARY The framework now provides a client-side Permission class for JavaScript - permission checking. This uses a new get_resolved_permissions() method on - User_Model that returns pre-computed permissions (role defaults with - supplementary grants/denies applied). The resolved permissions array is - automatically included in window.rsxapp.resolved_permissions. + permission checking. User_Model now includes resolved_permissions in its + toArray() output and removes role_id__permissions (which is now redundant). - If your application has forked User_Model, you must add the new method. + The resolved_permissions array contains the user's final permissions after + applying role defaults, supplementary grants, and supplementary denies. + + If your application has forked User_Model, you must: + 1. Add get_resolved_permissions() method + 2. Add or update toArray() to include resolved_permissions + 3. Update any code that checks role_id__permissions directly AFFECTED FILES - Applications with a forked User_Model (e.g., /rsx/models/user_model.php) - that extends the framework's User_Model need to ensure the parent class - method is accessible. - - If your User_Model OVERRIDES has_permission(), update it to use the new - pattern. + - /rsx/models/user_model.php (if forked) + - Any JavaScript files checking role_id__permissions directly + - Any PHP files checking role_id__permissions directly CHANGES REQUIRED 1. If You Have NOT Forked User_Model - No action required. The framework's User_Model already has the new - method and your application will inherit it automatically. + No action required. The framework's User_Model already has all new + methods and your application will inherit them automatically. - 2. If You Have Forked User_Model (extends framework User_Model) + HOWEVER: Search your codebase for role_id__permissions usage (see + section 5 below). - Ensure your class does NOT override has_permission() or if it does, - update it to use get_resolved_permissions(): + 2. If You Have Forked User_Model - Add get_resolved_permissions() - BEFORE (if overridden): - public function has_permission(int $permission): bool - { - // Custom logic - if ($this->role_id === self::ROLE_DISABLED) { - return false; - } - if ($this->has_supplementary_deny($permission)) { - return false; - } - if ($this->has_supplementary_grant($permission)) { - return true; - } - return in_array($permission, $this->role_id__permissions ?? [], true); - } + Add this method to your User_Model: - AFTER: - public function has_permission(int $permission): bool - { - return in_array($permission, $this->get_resolved_permissions(), true); - } - - If you need custom permission logic, override get_resolved_permissions() - instead of has_permission(). - - 3. If You Have Completely Replaced User_Model (not extending framework) - - Add the get_resolved_permissions() method to your model: - - /** - * Get all resolved permissions for this user - * - * @return array Array of permission IDs the user has - */ public function get_resolved_permissions(): array { - // Return empty for disabled users if ($this->role_id === self::ROLE_DISABLED) { return []; } - // Start with role default permissions $permissions = $this->role_id__permissions ?? []; - // Add supplementary GRANTs $supplementary = $this->_load_supplementary_permissions(); foreach ($supplementary['grants'] as $perm_id) { if (!in_array($perm_id, $permissions, true)) { @@ -82,20 +48,70 @@ CHANGES REQUIRED } } - // Remove supplementary DENYs $permissions = array_values(array_diff($permissions, $supplementary['denies'])); - sort($permissions); + return $permissions; } - Then update has_permission() to use it: + 3. If You Have Forked User_Model - Add/Update toArray() + + If you DON'T have a toArray() override, add one: + + public function toArray() + { + $data = parent::toArray(); + $data['resolved_permissions'] = $this->get_resolved_permissions(); + unset($data['role_id__permissions']); + return $data; + } + + If you ALREADY have a toArray() override, add these lines: + + $data['resolved_permissions'] = $this->get_resolved_permissions(); + unset($data['role_id__permissions']); + + 4. If You Have Forked User_Model - Update has_permission() + + If you override has_permission(), simplify it: public function has_permission(int $permission): bool { return in_array($permission, $this->get_resolved_permissions(), true); } + 5. REQUIRED: Search and Replace role_id__permissions Usage + + Search your /rsx/ directory for any direct usage of role_id__permissions: + + grep -r "role_id__permissions" rsx/ + + For each match, update to use the new pattern: + + JAVASCRIPT - BEFORE: + if (window.rsxapp.user.role_id__permissions.includes(User_Model.PERM_EDIT_DATA)) { + // or + const perms = user.role_id__permissions; + + JAVASCRIPT - AFTER: + if (Permission.has_permission(User_Model.PERM_EDIT_DATA)) { + // or + const perms = Permission.get_resolved_permissions(); + // or + const perms = window.rsxapp.user.resolved_permissions; + + PHP - BEFORE: + if (in_array($perm, $user->role_id__permissions)) { + + PHP - AFTER: + if ($user->has_permission($perm)) { + // or + if (in_array($perm, $user->get_resolved_permissions())) { + + IMPORTANT: role_id__permissions only contains role defaults. It does NOT + include supplementary grants or respect supplementary denies. Always use + resolved_permissions or the Permission class for accurate permission checks. + NEW FEATURES AVAILABLE PHP: @@ -110,15 +126,16 @@ NEW FEATURES AVAILABLE Permission.can_admin_role(User_Model.ROLE_USER) Permission.get_resolved_permissions() - window.rsxapp: - window.rsxapp.resolved_permissions // Array of permission IDs + window.rsxapp.user: + window.rsxapp.user.resolved_permissions // Array of permission IDs VERIFICATION 1. Load any authenticated page 2. Open browser console - 3. Verify: window.rsxapp.resolved_permissions is an array of integers + 3. Verify: window.rsxapp.user.resolved_permissions is an array of integers 4. Verify: Permission.has_permission(User_Model.PERM_VIEW_DATA) returns boolean - 5. If you have a forked User_Model, verify no PHP errors on page load + 5. Verify: window.rsxapp.user.role_id__permissions is undefined + 6. If you have a forked User_Model, verify no PHP errors on page load REFERENCE php artisan rsx:man acls