Move resolved_permissions to user object via toArray()
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -285,10 +285,6 @@ abstract class Rsx_Bundle_Abstract
|
|||||||
$rsxapp_data['site'] = Session::get_site();
|
$rsxapp_data['site'] = Session::get_site();
|
||||||
$rsxapp_data['csrf'] = Session::get_csrf_token();
|
$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)
|
// Add browser error logging flag (enabled in both dev and production)
|
||||||
if (config('rsx.log_browser_errors', false)) {
|
if (config('rsx.log_browser_errors', false)) {
|
||||||
$rsxapp_data['log_browser_errors'] = true;
|
$rsxapp_data['log_browser_errors'] = true;
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
* Permission - Client-side permission checking
|
* Permission - Client-side permission checking
|
||||||
*
|
*
|
||||||
* Provides permission and role checking for JavaScript using pre-resolved
|
* 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.
|
* 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
|
* 1. Role default permissions
|
||||||
* 2. Supplementary GRANTs (added)
|
* 2. Supplementary GRANTs (added)
|
||||||
* 3. Supplementary DENYs (removed)
|
* 3. Supplementary DENYs (removed)
|
||||||
@@ -56,7 +57,7 @@ class Permission {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
static has_permission(permission) {
|
static has_permission(permission) {
|
||||||
const permissions = window.rsxapp?.resolved_permissions ?? [];
|
const permissions = window.rsxapp?.user?.resolved_permissions ?? [];
|
||||||
return permissions.includes(permission);
|
return permissions.includes(permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +68,7 @@ class Permission {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
static has_any_permission(permissions) {
|
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));
|
return permissions.some(p => resolved.includes(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +79,7 @@ class Permission {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
static has_all_permissions(permissions) {
|
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));
|
return permissions.every(p => resolved.includes(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +125,6 @@ class Permission {
|
|||||||
* @returns {number[]} Array of permission IDs
|
* @returns {number[]} Array of permission IDs
|
||||||
*/
|
*/
|
||||||
static get_resolved_permissions() {
|
static get_resolved_permissions() {
|
||||||
return window.rsxapp?.resolved_permissions ?? [];
|
return window.rsxapp?.user?.resolved_permissions ?? [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -506,4 +506,30 @@ class User_Model extends Rsx_Site_Model_Abstract
|
|||||||
|
|
||||||
return $data;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -309,9 +309,9 @@ JAVASCRIPT PERMISSION CLASS
|
|||||||
|
|
||||||
Data Source
|
Data Source
|
||||||
|
|
||||||
The Permission class reads from window.rsxapp.resolved_permissions,
|
The Permission class reads from window.rsxapp.user.resolved_permissions,
|
||||||
which is populated by the bundle renderer from the session user's
|
which is populated via User_Model::toArray() from get_resolved_permissions().
|
||||||
get_resolved_permissions() result. Empty array if not authenticated.
|
Returns empty array if user is not authenticated.
|
||||||
|
|
||||||
ROUTE PROTECTION
|
ROUTE PROTECTION
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ NAME
|
|||||||
|
|
||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
JavaScript:
|
JavaScript:
|
||||||
window.rsxapp.build_key // Manifest build hash
|
window.rsxapp.build_key // Manifest build hash
|
||||||
window.rsxapp.user // Current user model data
|
window.rsxapp.user // Current user model data
|
||||||
window.rsxapp.site // Current site model data
|
window.rsxapp.user.resolved_permissions // Pre-computed user permissions
|
||||||
window.rsxapp.resolved_permissions // Pre-computed user permissions
|
window.rsxapp.site // Current site model data
|
||||||
window.rsxapp.page_data // Custom page-specific data
|
window.rsxapp.page_data // Custom page-specific data
|
||||||
window.rsxapp.is_spa // Whether current page is SPA
|
window.rsxapp.is_spa // Whether current page is SPA
|
||||||
window.rsxapp.csrf // CSRF token for forms
|
window.rsxapp.csrf // CSRF token for forms
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
window.rsxapp is a global JavaScript object rendered with every page bundle.
|
window.rsxapp is a global JavaScript object rendered with every page bundle.
|
||||||
@@ -64,16 +64,17 @@ OBJECT STRUCTURE
|
|||||||
Session Data (when authenticated):
|
Session Data (when authenticated):
|
||||||
|
|
||||||
user Object. Current user model with all fields.
|
user Object. Current user model with all fields.
|
||||||
Includes enum properties (role_id__label, etc.)
|
Includes enum properties (role_id__label, etc.),
|
||||||
and __MODEL marker.
|
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.
|
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:
|
Time Synchronization:
|
||||||
|
|
||||||
server_time String. ISO 8601 UTC timestamp from server.
|
server_time String. ISO 8601 UTC timestamp from server.
|
||||||
@@ -120,6 +121,7 @@ EXAMPLE OUTPUT
|
|||||||
"email": "test@example.com",
|
"email": "test@example.com",
|
||||||
"role_id": 300,
|
"role_id": 300,
|
||||||
"role_id__label": "Site Owner",
|
"role_id__label": "Site Owner",
|
||||||
|
"resolved_permissions": [2, 3, 4, 5, 6, 7],
|
||||||
"__MODEL": "User_Model"
|
"__MODEL": "User_Model"
|
||||||
},
|
},
|
||||||
"site": {
|
"site": {
|
||||||
@@ -128,7 +130,6 @@ EXAMPLE OUTPUT
|
|||||||
"timezone": "America/Chicago",
|
"timezone": "America/Chicago",
|
||||||
"__MODEL": "Site_Model"
|
"__MODEL": "Site_Model"
|
||||||
},
|
},
|
||||||
"resolved_permissions": [2, 3, 4, 5, 6, 7],
|
|
||||||
"csrf": "f290180b609f8f353c3226accdc798961...",
|
"csrf": "f290180b609f8f353c3226accdc798961...",
|
||||||
"page_data": {
|
"page_data": {
|
||||||
"contact_internal_id": 17
|
"contact_internal_id": 17
|
||||||
@@ -195,7 +196,7 @@ FRAMEWORK CONSUMERS
|
|||||||
Rsx_Storage Reads session_hash, user.id, site.id, build_key for scoping
|
Rsx_Storage Reads session_hash, user.id, site.id, build_key for scoping
|
||||||
Ajax Reads csrf for request headers
|
Ajax Reads csrf for request headers
|
||||||
Spa Reads is_spa, params for routing
|
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
|
Debugger Reads console_debug for output filtering
|
||||||
|
|
||||||
SEE ALSO
|
SEE ALSO
|
||||||
|
|||||||
@@ -3,78 +3,44 @@ Date: 2026-01-13
|
|||||||
|
|
||||||
SUMMARY
|
SUMMARY
|
||||||
The framework now provides a client-side Permission class for JavaScript
|
The framework now provides a client-side Permission class for JavaScript
|
||||||
permission checking. This uses a new get_resolved_permissions() method on
|
permission checking. User_Model now includes resolved_permissions in its
|
||||||
User_Model that returns pre-computed permissions (role defaults with
|
toArray() output and removes role_id__permissions (which is now redundant).
|
||||||
supplementary grants/denies applied). The resolved permissions array is
|
|
||||||
automatically included in window.rsxapp.resolved_permissions.
|
|
||||||
|
|
||||||
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
|
AFFECTED FILES
|
||||||
Applications with a forked User_Model (e.g., /rsx/models/user_model.php)
|
- /rsx/models/user_model.php (if forked)
|
||||||
that extends the framework's User_Model need to ensure the parent class
|
- Any JavaScript files checking role_id__permissions directly
|
||||||
method is accessible.
|
- Any PHP files checking role_id__permissions directly
|
||||||
|
|
||||||
If your User_Model OVERRIDES has_permission(), update it to use the new
|
|
||||||
pattern.
|
|
||||||
|
|
||||||
CHANGES REQUIRED
|
CHANGES REQUIRED
|
||||||
|
|
||||||
1. If You Have NOT Forked User_Model
|
1. If You Have NOT Forked User_Model
|
||||||
|
|
||||||
No action required. The framework's User_Model already has the new
|
No action required. The framework's User_Model already has all new
|
||||||
method and your application will inherit it automatically.
|
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,
|
2. If You Have Forked User_Model - Add get_resolved_permissions()
|
||||||
update it to use get_resolved_permissions():
|
|
||||||
|
|
||||||
BEFORE (if overridden):
|
Add this method to your User_Model:
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
public function get_resolved_permissions(): array
|
||||||
{
|
{
|
||||||
// Return empty for disabled users
|
|
||||||
if ($this->role_id === self::ROLE_DISABLED) {
|
if ($this->role_id === self::ROLE_DISABLED) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start with role default permissions
|
|
||||||
$permissions = $this->role_id__permissions ?? [];
|
$permissions = $this->role_id__permissions ?? [];
|
||||||
|
|
||||||
// Add supplementary GRANTs
|
|
||||||
$supplementary = $this->_load_supplementary_permissions();
|
$supplementary = $this->_load_supplementary_permissions();
|
||||||
foreach ($supplementary['grants'] as $perm_id) {
|
foreach ($supplementary['grants'] as $perm_id) {
|
||||||
if (!in_array($perm_id, $permissions, true)) {
|
if (!in_array($perm_id, $permissions, true)) {
|
||||||
@@ -82,20 +48,70 @@ CHANGES REQUIRED
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove supplementary DENYs
|
|
||||||
$permissions = array_values(array_diff($permissions, $supplementary['denies']));
|
$permissions = array_values(array_diff($permissions, $supplementary['denies']));
|
||||||
|
|
||||||
sort($permissions);
|
sort($permissions);
|
||||||
|
|
||||||
return $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
|
public function has_permission(int $permission): bool
|
||||||
{
|
{
|
||||||
return in_array($permission, $this->get_resolved_permissions(), true);
|
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
|
NEW FEATURES AVAILABLE
|
||||||
|
|
||||||
PHP:
|
PHP:
|
||||||
@@ -110,15 +126,16 @@ NEW FEATURES AVAILABLE
|
|||||||
Permission.can_admin_role(User_Model.ROLE_USER)
|
Permission.can_admin_role(User_Model.ROLE_USER)
|
||||||
Permission.get_resolved_permissions()
|
Permission.get_resolved_permissions()
|
||||||
|
|
||||||
window.rsxapp:
|
window.rsxapp.user:
|
||||||
window.rsxapp.resolved_permissions // Array of permission IDs
|
window.rsxapp.user.resolved_permissions // Array of permission IDs
|
||||||
|
|
||||||
VERIFICATION
|
VERIFICATION
|
||||||
1. Load any authenticated page
|
1. Load any authenticated page
|
||||||
2. Open browser console
|
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
|
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
|
REFERENCE
|
||||||
php artisan rsx:man acls
|
php artisan rsx:man acls
|
||||||
|
|||||||
Reference in New Issue
Block a user