Implement RBAC migration and role management enhancements

- Added AJAX functionality to fetch role permissions for copying.
- Introduced system role management with permission checks for updates.
- Implemented role deletion with confirmation modal and backend handling.
- Enhanced user role assignment migration scripts to transition from legacy profiles to RBAC.
- Created SQL migration scripts for user roles and permissions mapping.
- Updated user interface to support new role management features including copy permissions and system role indicators.
This commit is contained in:
“VeLiTi”
2026-01-27 15:10:21 +01:00
parent aeda4e4cb9
commit f7a91737bc
30 changed files with 1285 additions and 236 deletions

View File

@@ -24,6 +24,17 @@ $update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_S
$update_allowed_edit = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');
$system_role_allowed = isAllowed('user_roles' ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
//Handle AJAX request for role permissions (copy functionality)
if (isset($_GET['action']) && $_GET['action'] === 'get_role_permissions' && isset($_GET['source_role_id'])) {
header('Content-Type: application/json');
$source_role_id = intval($_GET['source_role_id']);
$api_url = '/v2/role_access_permissions/role_id='.$source_role_id;
$role_perms = ioServer($api_url,'');
echo $role_perms;
exit;
}
//GET Details from URL
$GET_VALUES = urlGETdetails($_GET) ?? '';
@@ -63,17 +74,28 @@ $assignments = ioServer($api_url,'');
//Decode Payload
if (!empty($assignments)){$assignments = json_decode($assignments);}else{$assignments = null;}
//CALL TO API FOR All User Roles (for copy dropdown)
$api_url = '/v2/user_roles/status=1&all=';
$all_roles = ioServer($api_url,'');
//Decode Payload
if (!empty($all_roles)){$all_roles = json_decode($all_roles);}else{$all_roles = null;}
//------------------------------
// Handle POST for inline edit
//------------------------------
if (isset($_POST['save_permissions']) && $update_allowed_edit === 1) {
// Update role info (name, description, status)
$role_data = json_encode([
// Update role info (name, description, status, system role)
$role_data_array = [
'rowID' => $role_id,
'name' => $_POST['name'] ?? '',
'description' => $_POST['description'] ?? '',
'is_active' => $_POST['is_active'] ?? 1
], JSON_UNESCAPED_UNICODE);
];
// Only allow is_system to be changed if user has delete permission on user_roles
if ($system_role_allowed === 1) {
$role_data_array['is_system'] = isset($_POST['is_system']) ? 1 : 0;
}
$role_data = json_encode($role_data_array, JSON_UNESCAPED_UNICODE);
ioServer('/v2/user_roles', $role_data);
// Process permission updates
@@ -132,6 +154,21 @@ if (isset($_POST['save_permissions']) && $update_allowed_edit === 1) {
exit;
}
//------------------------------
// Handle POST for delete
//------------------------------
if (isset($_POST['delete_role']) && $delete_allowed === 1) {
$role_data = json_encode([
'rowID' => $role_id,
'delete' => 'delete'
], JSON_UNESCAPED_UNICODE);
ioServer('/v2/user_roles', $role_data);
// Redirect to roles list with success message
header('Location: index.php?page='.$_SESSION['origin'].'&success_msg=3');
exit;
}
//------------------------------
//Variables
@@ -161,7 +198,11 @@ $view = '
if ($update_allowed_edit === 1){
$view .= '<a href="javascript:void(0);" id="editBtn" class="btn mar-right-2" onclick="togglePermissionsEdit()">✏️</a>';
$view .= '<button type="submit" form="permissionsForm" id="saveBtn" class="btn" style="display:none;">💾</button>';
$view .= '<button type="submit" form="permissionsForm" id="saveBtn" class="btn mar-right-2" style="display:none;">💾</button>';
}
if ($delete_allowed === 1){
$view .= '<a href="javascript:void(0);" id="deleteBtn" class="btn alt" onclick="confirmDeleteRole()">🗑️</a>';
}
$view .= '</div>';
@@ -174,6 +215,32 @@ if (isset($success_msg)){
</div>';
}
// Delete form (hidden)
if ($delete_allowed === 1){
$view .= '<form id="deleteRoleForm" action="" method="post" style="display:none;">
<input type="hidden" name="delete_role" value="1">
<input type="hidden" name="rowID" value="'.$role_id.'">
</form>';
// Delete confirmation modal
$view .= '<div id="deleteModal" class="modal" style="display:none;">
<div class="modal-content">
<div class="modal-header">
<h3><i class="fa-solid fa-triangle-exclamation" style="color:#e74c3c;"></i> '.($confirm_delete_title ?? 'Confirm Delete').'</h3>
</div>
<div class="modal-body">
<p>'.($confirm_delete_role ?? 'Are you sure you want to delete this role?').'</p>
<p><strong>'.$responses->name.'</strong></p>
<p class="warning-text" style="color:#e74c3c; margin-top:10px;">'.($delete_role_warning ?? 'This will also remove all permissions and user assignments associated with this role.').'</p>
</div>
<div class="modal-footer">
<button type="button" class="btn alt" onclick="closeDeleteModal()">'.($cancel ?? 'Cancel').'</button>
<button type="button" class="btn danger" onclick="executeDeleteRole()">'.($delete ?? 'Delete').'</button>
</div>
</div>
</div>';
}
// Start form wrapper for edit mode
$view .= '<form id="permissionsForm" action="" method="post">
<input type="hidden" name="save_permissions" value="1">
@@ -210,6 +277,31 @@ $view .= ' <div class="content-block order-details">
<textarea class="edit-mode" name="description" style="display:none;">'.($responses->description ?? '').'</textarea>
</p>
</div>
<div class="order-detail">
<h3>'.($role_system ?? 'System Role').'</h3>
<p>
<span class="'.($system_role_allowed === 1 ? 'view-mode' : '').'">'.($responses->is_system == 1 ? '<i class="fa-solid fa-check" style="color:green;"></i> '.($yes ?? 'Yes') : '<i class="fa-solid fa-times" style="color:red;"></i> '.($no ?? 'No')).'</span>
'.($system_role_allowed === 1 ? '<label class="edit-mode" style="display:none;">
<input type="checkbox" name="is_system" value="1"'.($responses->is_system == 1 ? ' checked' : '').'> '.($role_system_label ?? 'Mark as system role').'
</label>' : '').'
</p>
</div>
<div class="order-detail edit-mode-block" style="display:none;">
<h3>'.($copy_from_role ?? 'Copy Permissions From').'</h3>
<p>
<select id="copyFromRole" onchange="copyPermissionsFromRole(this.value)" style="width:100%; max-width:300px;">
<option value="">'.($select_role ?? '-- Select Role --').'</option>';
if (!empty($all_roles)){
foreach ($all_roles as $r){
if ($r->rowID != $role_id){
$view .= '<option value="'.$r->rowID.'">'.$r->name.'</option>';
}
}
}
$view .= ' </select>
<small style="display:block; margin-top:5px; color:#666;">'.($copy_permissions_hint ?? 'Selecting a role will override current permissions').'</small>
</p>
</div>
</div>
';
@@ -350,17 +442,20 @@ function togglePermissionsEdit() {
var saveBtn = document.getElementById("saveBtn");
var viewElements = document.querySelectorAll(".view-mode");
var editElements = document.querySelectorAll(".edit-mode");
var editBlockElements = document.querySelectorAll(".edit-mode-block");
var editOnlyRows = document.querySelectorAll(".edit-only-row");
var i;
if (permissionsEditMode) {
for (i = 0; i < viewElements.length; i++) { viewElements[i].style.display = "none"; }
for (i = 0; i < editElements.length; i++) { editElements[i].style.display = "inline"; }
for (i = 0; i < editBlockElements.length; i++) { editBlockElements[i].style.display = "block"; }
for (i = 0; i < editOnlyRows.length; i++) { editOnlyRows[i].style.display = "table-row"; }
editBtn.style.display = "none";
saveBtn.style.display = "inline-block";
} else {
for (i = 0; i < viewElements.length; i++) { viewElements[i].style.display = "inline"; }
for (i = 0; i < editElements.length; i++) { editElements[i].style.display = "none"; }
for (i = 0; i < editBlockElements.length; i++) { editBlockElements[i].style.display = "none"; }
for (i = 0; i < editOnlyRows.length; i++) { editOnlyRows[i].style.display = "none"; }
editBtn.style.display = "inline-block";
saveBtn.style.display = "none";
@@ -378,6 +473,66 @@ function toggleColumn(type) {
for (var i = 0; i < checkboxes.length; i++) {
checkboxes[i].checked = !allChecked;
}
}
async function copyPermissionsFromRole(roleId) {
if (!roleId) return;
if (!confirm("'.($confirm_copy_permissions ?? 'This will override all current permission settings. Continue?').'")) {
document.getElementById("copyFromRole").value = "";
return;
}
try {
// Call PHP page which will use ioServer to get permissions
const response = await fetch("index.php?page=user_role&action=get_role_permissions&source_role_id=" + roleId);
if (!response.ok) throw new Error("Failed to fetch");
const permissions = await response.json();
// Create a lookup map of permissions by access_id
var permMap = {};
if (permissions && permissions.length > 0) {
for (var i = 0; i < permissions.length; i++) {
permMap[permissions[i].access_id] = permissions[i];
}
}
// Get all permission checkboxes and reset them
var allCheckboxes = document.querySelectorAll("input[type=checkbox][name^=\\"permissions[\\"]");
for (var i = 0; i < allCheckboxes.length; i++) {
allCheckboxes[i].checked = false;
}
// Apply copied permissions
for (var accessId in permMap) {
var perm = permMap[accessId];
var cbCreate = document.querySelector("input[name=\\"permissions[" + accessId + "][C]\\"]");
var cbRead = document.querySelector("input[name=\\"permissions[" + accessId + "][R]\\"]");
var cbUpdate = document.querySelector("input[name=\\"permissions[" + accessId + "][U]\\"]");
var cbDelete = document.querySelector("input[name=\\"permissions[" + accessId + "][D]\\"]");
if (cbCreate && perm.can_create == 1) cbCreate.checked = true;
if (cbRead && perm.can_read == 1) cbRead.checked = true;
if (cbUpdate && perm.can_update == 1) cbUpdate.checked = true;
if (cbDelete && perm.can_delete == 1) cbDelete.checked = true;
}
// Reset dropdown
document.getElementById("copyFromRole").value = "";
} catch (error) {
console.error("Error copying permissions:", error);
alert("'.($error_copy_permissions ?? 'Failed to copy permissions. Please try again.').'");
document.getElementById("copyFromRole").value = "";
}
}
function confirmDeleteRole() {
document.getElementById("deleteModal").style.display = "flex";
}
function closeDeleteModal() {
document.getElementById("deleteModal").style.display = "none";
}
function executeDeleteRole() {
document.getElementById("deleteRoleForm").submit();
}';
template_footer($js);