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

276
user.php
View File

@@ -12,33 +12,25 @@ include_once './settings/settings_redirector.php';
//SET ORIGIN FOR NAVIGATION
$_SESSION['prev_origin_user'] = $_SERVER['REQUEST_URI'];
$page = 'user';
//Check if allowed
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php');
exit;
}
//PAGE Security
$page_manage = 'user_manage';
$update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$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');
//GET Details from URL
$user_ID = $_GET['id'] ?? '';
if ($user_ID == ''){
header('location: index.php?page=users');
exit;
}
//CALL TO API FOR User information
$api_url = '/v2/users/id='.$user_ID;
$responses = ioServer($api_url,'');
//Decode Payload
if (!empty($responses)){$responses = json_decode($responses);}else{$responses = null;}
$user = $responses[0];
// Determine if this is a new user creation
$is_new_user = empty($user_ID);
//Helper function to convert service hex string to 1/0
function isServiceActive($service) {
@@ -49,13 +41,53 @@ function isServiceActive($service) {
return 0;
}
$service_active = isServiceActive($user->service);
if ($is_new_user) {
// Check create permission for new users
if ($create_allowed !== 1) {
header('location: index.php?page=users');
exit;
}
// Create empty user object with default values
$user = (object)[
'rowID' => '',
'username' => '',
'email' => '',
'userkey' => '1',
'view' => 3,
'settings' => '',
'service' => 0,
'language' => '',
'login_count' => 0,
'partnerhierarchy' => json_encode($_SESSION['authorization']['partnerhierarchy'] ?? new stdClass()),
'created' => null,
'updated' => null,
'lastlogin' => null,
'updatedby' => null
];
$service_active = 0;
$role_assignments = null;
} else {
//CALL TO API FOR User information
$api_url = '/v2/users/id='.$user_ID;
$responses = ioServer($api_url,'');
//Decode Payload
if (!empty($responses)){$responses = json_decode($responses);}else{$responses = null;}
$user = $responses[0] ?? null;
//CALL TO API FOR User Role Assignments
$api_url = '/v2/user_role_assignments/user_id='.$user_ID;
$role_assignments = ioServer($api_url,'');
//Decode Payload
if (!empty($role_assignments)){$role_assignments = json_decode($role_assignments);}else{$role_assignments = null;}
// If user not found, redirect
if ($user === null) {
header('location: index.php?page=users');
exit;
}
$service_active = isServiceActive($user->service);
//CALL TO API FOR User Role Assignments
$api_url = '/v2/user_role_assignments/user_id='.$user_ID;
$role_assignments = ioServer($api_url,'');
//Decode Payload
if (!empty($role_assignments)){$role_assignments = json_decode($role_assignments);}else{$role_assignments = null;}
}
//CALL TO API FOR All Available Roles
$api_url = '/v2/user_roles/status=1&p=1';
@@ -70,10 +102,56 @@ if (!empty($all_roles_response)){
$all_roles = [];
}
//------------------------------
// Handle POST for creating new user
//------------------------------
if (isset($_POST['create_user']) && $create_allowed === 1 && $is_new_user) {
// Build user data for new user
$user_data = [
'userkey' => $_POST['userkey'] ?? 1,
'username' => $_POST['username'] ?? '',
'email' => $_POST['email'] ?? '',
'view' => $_POST['view'] ?? 3,
'settings' => $_POST['settings'] ?? '',
'service' => $_POST['service'] ?? 0,
'language' => $_POST['language'] ?? '',
'login_count' => 0,
'salesid' => $_POST['salesid'] ?? '',
'soldto' => $_POST['soldto'] ?? '',
'shipto' => $_POST['shipto'] ?? '',
'location' => $_POST['location'] ?? ''
];
$data = json_encode($user_data, JSON_UNESCAPED_UNICODE);
$response = ioServer('/v2/users', $data);
// Get the new user ID from the response
$new_user = json_decode($response);
$new_user_id = $new_user->id ?? null;
// Save role assignments for new user if we have an ID and roles are selected
if ($new_user_id && !empty($_POST['roles'])) {
$role_data = [
'batch_update' => true,
'user_id' => (int)$new_user_id,
'roles' => array_map('intval', $_POST['roles'])
];
$data = json_encode($role_data, JSON_UNESCAPED_UNICODE);
ioServer('/v2/user_role_assignments', $data);
}
if ($new_user_id) {
header('Location: index.php?page=user&id='.$new_user_id.'&success_msg=1');
} else {
header('Location: index.php?page=users&success_msg=1');
}
exit;
}
//------------------------------
// Handle POST for inline edit (user AND roles)
//------------------------------
if (isset($_POST['save_user']) && $update_allowed_edit === 1) {
if (isset($_POST['save_user']) && $update_allowed === 1 && !$is_new_user) {
// Build user data using existing field names
$user_data = [
'id' => $user_ID,
@@ -109,7 +187,7 @@ if (isset($_POST['save_user']) && $update_allowed_edit === 1) {
}
// Handle password reset
if (isset($_POST['reset']) && $update_allowed_edit === 1) {
if (isset($_POST['reset']) && $update_allowed === 1) {
$data = json_encode(['id' => $user_ID, 'reset' => 'reset'], JSON_UNESCAPED_UNICODE);
ioServer('/v2/users', $data);
header('Location: index.php?page=user&id='.$user_ID.'&success_msg=4');
@@ -117,7 +195,7 @@ if (isset($_POST['reset']) && $update_allowed_edit === 1) {
}
// Handle unblock
if (isset($_POST['unblock']) && $update_allowed_edit === 1) {
if (isset($_POST['unblock']) && $update_allowed === 1) {
$data = json_encode(['id' => $user_ID, 'login_count' => '0'], JSON_UNESCAPED_UNICODE);
ioServer('/v2/users', $data);
header('Location: index.php?page=user&id='.$user_ID.'&success_msg=5');
@@ -172,13 +250,24 @@ if (isset($_GET['success_msg'])) {
}
template_header(($user_title ?? 'User'), 'user', 'view');
if ($is_new_user) {
$page_title = ($user_new ?? 'New User');
} else {
$page_title = ($user_h2 ?? 'User').' - '.$user->username;
}
$view = '
<div class="content-title responsive-flex-wrap responsive-pad-bot-3">
<h2 class="responsive-width-100">'.($user_h2 ?? 'User').' - '.$user->username.'</h2>
<h2 class="responsive-width-100">'.$page_title.'</h2>
<a href="index.php?page='.($_SESSION['origin'] ?? 'users').'&p='.($_SESSION['p'] ?? '1').($_SESSION['status'] ?? '').($_SESSION['sort'] ?? '').($_SESSION['search'] ?? '').'" class="btn alt mar-right-2">←</a>
';
if ($update_allowed_edit === 1){
if ($is_new_user) {
// New user mode - show save button directly
$view .= '<button type="submit" form="userForm" id="saveBtn" class="btn">💾</button>';
} elseif ($update_allowed === 1) {
// Edit mode - show edit/save toggle
$view .= '<a href="javascript:void(0);" id="editBtn" class="btn mar-right-2" onclick="toggleUserEdit()">✏️</a>';
$view .= '<button type="submit" form="userForm" id="saveBtn" class="btn" style="display:none;">💾</button>';
}
@@ -194,12 +283,17 @@ if (isset($success_msg)){
}
// Start form wrapper for edit mode
$form_action = $is_new_user ? 'create_user' : 'save_user';
$view .= '<form id="userForm" action="" method="post">
<input type="hidden" name="save_user" value="1">
<input type="hidden" name="'.$form_action.'" value="1">
<input type="hidden" name="id" value="'.$user_ID.'">';
$view .= '<div class="content-block-wrapper">';
// Display styles for view/edit mode (new users start in edit mode)
$view_style = $is_new_user ? 'display:none;' : '';
$edit_style = $is_new_user ? '' : 'display:none;';
// User Information Block
$view .= ' <div class="content-block order-details">
<div class="block-header">
@@ -208,8 +302,8 @@ $view .= ' <div class="content-block order-details">
<div class="order-detail">
<h3>'.($general_status ?? 'Status').'</h3>
<p>
<span class="view-mode status '.$status_class.'">'.$status_text.'</span>
<select class="edit-mode" name="userkey" style="display:none;">
<span class="view-mode status '.$status_class.'" style="'.$view_style.'">'.$status_text.'</span>
<select class="edit-mode" name="userkey" style="'.$edit_style.'">
<option value="1"'.($is_active ? ' selected' : '').'>'.($enabled ?? 'Active').'</option>
<option value="0"'.(!$is_active ? ' selected' : '').'>'.($disabled ?? 'Inactive').'</option>
</select>
@@ -218,22 +312,22 @@ $view .= ' <div class="content-block order-details">
<div class="order-detail">
<h3>'.($User_username ?? 'Username').'</h3>
<p>
<span class="view-mode">'.$user->username.'</span>
<input type="text" class="edit-mode" name="username" value="'.$user->username.'" style="display:none;" pattern="^\S+$" required>
<span class="view-mode" style="'.$view_style.'">'.$user->username.'</span>
<input type="text" class="edit-mode" name="username" value="'.$user->username.'" style="'.$edit_style.'" pattern="^\S+$" required>
</p>
</div>
<div class="order-detail">
<h3>'.($User_email ?? 'Email').'</h3>
<p>
<span class="view-mode">'.$user->email.'</span>
<input type="email" class="edit-mode" name="email" value="'.$user->email.'" style="display:none;" required>
<span class="view-mode" style="'.$view_style.'">'.$user->email.'</span>
<input type="email" class="edit-mode" name="email" value="'.$user->email.'" style="'.$edit_style.'" required>
</p>
</div>
<div class="order-detail">
<h3>'.($User_language ?? 'Language').'</h3>
<p>
<span class="view-mode">'.($user->language ?? '-').'</span>
<select class="edit-mode" name="language" style="display:none;">
<span class="view-mode" style="'.$view_style.'">'.($user->language ?? '-').'</span>
<select class="edit-mode" name="language" style="'.$edit_style.'">
<option value="">-</option>';
foreach ($supportedLanguages as $language){
$view .= '<option value="'.$language.'"'.(($user->language == $language) ? ' selected' : '').'>'.$language.'</option>';
@@ -249,7 +343,7 @@ $view .='<div class="content-block order-details" id="rolesBlock">
<div class="block-header">
<i class="fa-solid fa-user-shield fa-sm"></i>'.($view_user_roles ?? 'Assigned Roles').'
</div>
<div class="view-mode-roles">';
<div class="view-mode-roles" style="'.($is_new_user ? 'display:none;' : '').'">';
// Get list of already assigned role IDs
$assigned_role_ids = [];
@@ -290,9 +384,9 @@ if (!empty($role_assignments)){
$view .= '</div>'; // Close view-mode-roles
// EDIT MODE - Show all roles with checkboxes (only if user has edit permission)
if ($update_allowed_edit === 1 && !empty($all_roles)){
$view .= '<div class="edit-mode-roles" style="display:none;">';
// EDIT MODE - Show all roles with checkboxes (only if user has edit permission or is new user)
if (($update_allowed === 1 || $is_new_user) && !empty($all_roles)){
$view .= '<div class="edit-mode-roles" style="'.($is_new_user ? '' : 'display:none;').'">';
foreach ($all_roles as $role){
$is_checked = in_array($role->rowID, $assigned_role_ids) ? ' checked' : '';
@@ -326,7 +420,7 @@ $view .= '<div class="content-block">
<tr>
<td style="width:25%;">'.($User_permission ?? 'Permission Level').'</td>
<td>
<span class="view-mode">';
<span class="view-mode" style="'.$view_style.'">';
// Display permission level text
switch($user->view){
@@ -339,7 +433,7 @@ switch($user->view){
}
$view .= '</span>
<select class="edit-mode" name="view" style="display:none;">
<select class="edit-mode" name="view" style="'.$edit_style.'">
<option value="3"'.($user->view == 3 ? ' selected' : '').'>'.($permission3 ?? 'Admin').'</option>
<option value="2"'.($user->view == 2 ? ' selected' : '').'>'.($permission2 ?? 'Edit').'</option>
<option value="1"'.($user->view == 1 ? ' selected' : '').'>'.($permission1 ?? 'View').'</option>';
@@ -358,10 +452,10 @@ $view .= ' </select>
<tr>
<td style="width:25%;">'.($User_profile ?? 'Profile').'</td>
<td>
<span class="view-mode">'.($user->settings ?? '-').'</span>';
<span class="view-mode" style="'.$view_style.'">'.($user->settings ?? '-').'</span>';
if ($_SESSION['authorization']['permission'] == 3 || $_SESSION['authorization']['permission'] == 4){
$view .= '<select class="edit-mode" name="settings" style="display:none;">
$view .= '<select class="edit-mode" name="settings" style="'.$edit_style.'">
<option value="">-</option>';
foreach ($all_profiles as $profile) {
$view .= '<option value="'.$profile.'"'.($user->settings == $profile ? ' selected' : '').'>'.$profile.'</option>';
@@ -376,8 +470,8 @@ $view .= ' </td>
<tr>
<td style="width:25%;">'.($User_service ?? 'Service Access').'</td>
<td>
<span class="view-mode">'.(($service_active == 1) ? ($enabled ?? 'Enabled') : ($disabled ?? 'Disabled')).'</span>
<select class="edit-mode" name="service" style="display:none;">
<span class="view-mode" style="'.$view_style.'">'.(($service_active == 1) ? ($enabled ?? 'Enabled') : ($disabled ?? 'Disabled')).'</span>
<select class="edit-mode" name="service" style="'.$edit_style.'">
<option value="1"'.(($service_active == 1) ? ' selected' : '').'>'.($enabled ?? 'Enabled').'</option>
<option value="0"'.(($service_active == 0) ? ' selected' : '').'>'.($disabled ?? 'Disabled').'</option>
</select>
@@ -404,15 +498,15 @@ if ($_SESSION['authorization']['permission'] == 3 || $_SESSION['authorization'][
$view .= '<tr>
<td style="width:25%;">'.($general_salesid ?? 'Sales ID').'</td>
<td>
<span class="view-mode">'.($partner_data->salesid ?? '-').'</span>
<span class="edit-mode" style="display:none;">'.$salesid_dropdown.'</span>
<span class="view-mode" style="'.$view_style.'">'.($partner_data->salesid ?? '-').'</span>
<span class="edit-mode" style="'.$edit_style.'">'.$salesid_dropdown.'</span>
</td>
</tr>
<tr>
<td style="width:25%;">'.($general_soldto ?? 'Sold To').'</td>
<td>
<span class="view-mode">'.($partner_data->soldto ?? '-').'</span>
<span class="edit-mode" style="display:none;">'.$soldto_dropdown.'</span>
<span class="view-mode" style="'.$view_style.'">'.($partner_data->soldto ?? '-').'</span>
<span class="edit-mode" style="'.$edit_style.'">'.$soldto_dropdown.'</span>
</td>
</tr>';
}
@@ -423,65 +517,67 @@ $location_dropdown = listPartner('location', $_SESSION['authorization']['permiss
$view .= '<tr>
<td style="width:25%;">'.($general_shipto ?? 'Ship To').'</td>
<td>
<span class="view-mode">'.($partner_data->shipto ?? '-').'</span>
<span class="edit-mode" style="display:none;">'.$shipto_dropdown.'</span>
<span class="view-mode" style="'.$view_style.'">'.($partner_data->shipto ?? '-').'</span>
<span class="edit-mode" style="'.$edit_style.'">'.$shipto_dropdown.'</span>
</td>
</tr>
<tr>
<td style="width:25%;">'.($general_location ?? 'Location').'</td>
<td>
<span class="view-mode">'.($partner_data->location ?? '-').'</span>
<span class="edit-mode" style="display:none;">'.$location_dropdown.'</span>
<span class="view-mode" style="'.$view_style.'">'.($partner_data->location ?? '-').'</span>
<span class="edit-mode" style="'.$edit_style.'">'.$location_dropdown.'</span>
</td>
</tr>
</table>
</div>
</div>';
// Metadata Block
$view .= '<div class="content-block">
<div class="block-header">
<i class="fa-solid fa-bars fa-sm"></i>'.($tab3 ?? 'Details').'
</div>
<div class="table order-table">
<table>
<tr>
<td style="width:25%;">'.($general_created ?? 'Created').'</td>
<td>'.getRelativeTime($user->created).'</td>
</tr>
<tr>
<td style="width:25%;">'.($User_lastlogin ?? 'Last Login').'</td>
<td>'.($user->lastlogin ? getRelativeTime($user->lastlogin) : '-').'</td>
</tr>
<tr>
<td style="width:25%;">'.($general_updated ?? 'Updated').'</td>
<td>'.($user->updated ? getRelativeTime($user->updated) : '-').'</td>
</tr>
<tr>
<td style="width:25%;">'.($general_updatedby ?? 'Updated By').'</td>
<td>'.($user->updatedby ?? '-').'</td>
</tr>
<tr>
<td style="width:25%;">'.($User_pw_login_count ?? 'Login Attempts').'</td>
<td>
<span class="view-mode">'.$user->login_count.'</span>';
// Metadata Block (hide for new users)
if (!$is_new_user) {
$view .= '<div class="content-block">
<div class="block-header">
<i class="fa-solid fa-bars fa-sm"></i>'.($tab3 ?? 'Details').'
</div>
<div class="table order-table">
<table>
<tr>
<td style="width:25%;">'.($general_created ?? 'Created').'</td>
<td>'.getRelativeTime($user->created).'</td>
</tr>
<tr>
<td style="width:25%;">'.($User_lastlogin ?? 'Last Login').'</td>
<td>'.($user->lastlogin ? getRelativeTime($user->lastlogin) : '-').'</td>
</tr>
<tr>
<td style="width:25%;">'.($general_updated ?? 'Updated').'</td>
<td>'.($user->updated ? getRelativeTime($user->updated) : '-').'</td>
</tr>
<tr>
<td style="width:25%;">'.($general_updatedby ?? 'Updated By').'</td>
<td>'.($user->updatedby ?? '-').'</td>
</tr>
<tr>
<td style="width:25%;">'.($User_pw_login_count ?? 'Login Attempts').'</td>
<td>
<span class="view-mode">'.$user->login_count.'</span>';
if ($_SESSION['authorization']['permission'] == 3 || $_SESSION['authorization']['permission'] == 4){
$view .= '<input type="number" class="edit-mode" name="login_count" value="'.$user->login_count.'" style="display:none; width: 80px;">';
} else {
$view .= '<input type="hidden" name="login_count" value="'.$user->login_count.'">';
if ($_SESSION['authorization']['permission'] == 3 || $_SESSION['authorization']['permission'] == 4){
$view .= '<input type="number" class="edit-mode" name="login_count" value="'.$user->login_count.'" style="display:none; width: 80px;">';
} else {
$view .= '<input type="hidden" name="login_count" value="'.$user->login_count.'">';
}
$view .= ' </td>
</tr>
</table>
</div>
</div>';
}
$view .= ' </td>
</tr>
</table>
</div>
</div>
</form>
$view .= '</form>
';
// Actions Block (outside form for separate actions)
if ($update_allowed_edit === 1){
// Actions Block (outside form for separate actions, hide for new users)
if ($update_allowed === 1 && !$is_new_user){
$view .= '<div class="content-block">
<div class="block-header">
<i class="fa-solid fa-bolt fa-sm"></i>'.($general_actions ?? 'Actions').'