Roles."; } /* * Turn the agent away, in the way that fits what they asked for: a window * says why in the window; a form or link goes back to its page, which says * why; a page already being drawn says why in its place; ajax.php answers * JSON with a 403. $message is plain text. Doesn't return. */ function permissionRefuse($message) { $html = escapeHtml($message); // A window - includes/modal_header.php if (defined('ITFLOW_MODAL')) { while (ob_get_level()) { ob_end_clean(); } echo json_encode(['content' => '' . '' . '']); exit; } if (basename($_SERVER['SCRIPT_NAME'] ?? '') === 'ajax.php') { if (!headers_sent()) { http_response_code(403); header('Content-Type: application/json'); } echo json_encode(['ok' => false, 'error' => $message]); exit; } // A handler, or a page that checks before drawing anything - back where they came from, which says why if (defined('FROM_POST_HANDLER') || !headers_sent()) { flashAlert($html, 'error'); redirect(permissionBackUrl()); } // A page already under way - why, where the page would be, then the rest of the layout so its menus work echo '
' . '' . '

Not for your role

' . $html . '

' . 'Back' . '
'; extract($GLOBALS, EXTR_SKIP); require $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'; exit; } /* Where to send someone turned away - the page they came from on this site, unless it is this one; the dashboard otherwise. */ function permissionBackUrl() { $referer = (string) ($_SERVER['HTTP_REFERER'] ?? ''); $from = parse_url($referer); $here = parse_url((string) ($_SERVER['REQUEST_URI'] ?? '')); $host = parse_url('http://' . ($_SERVER['HTTP_HOST'] ?? ($_SERVER['SERVER_NAME'] ?? '')), PHP_URL_HOST); if ($referer !== '' && ($from['host'] ?? '') === $host && ($from['path'] ?? '') !== ($here['path'] ?? '')) { return $referer; } return '/agent/dashboard.php'; } // Client-scope SQL fragment for a list query, built from the signed-in user's allow / deny lists. // Admin and unrestricted users get no restriction. This is the list-level counterpart to // enforceClientAccess(), which gates a single record. // // Column-aware on purpose: it scopes on the resource's OWN client column rather than a joined // clients.client_id, so a row with no client (column = 0) is judged on its real value instead of // becoming NULL through a LEFT JOIN and silently dropping out of the result set. // // Returns " AND ..." or "" - append it after a WHERE clause (add "WHERE 1=1" if there isn't one). function clientScopeSql($column) { global $session_is_admin, $client_access_array, $client_deny_array; if ($session_is_admin) { return ''; } if (empty($client_access_array) && empty($client_deny_array)) { return ''; // Unrestricted user - all clients } $sql = ''; // 0 is included deliberately: a record with no client isn't any client's data, so a // restricted user keeps seeing it. This also matches the deny branch below, where 0 // already passes NOT IN, and the old hand-rolled ticket override that did IN (0,...). if (!empty($client_access_array)) { $sql .= " AND $column IN (0," . implode(',', array_map('intval', $client_access_array)) . ")"; } if (!empty($client_deny_array)) { $sql .= " AND $column NOT IN (" . implode(',', array_map('intval', $client_deny_array)) . ")"; } return $sql; } /* * Can this user reach this client? Answers yes or no instead of redirecting, for places * that need to decide whether to render something - enforceClientAccess() below is the * gate that protects the page itself and still has to be called there. * * Reads the permission arrays that includes/load_user_session.php already loaded, so this * costs no query and can be called from the sidebar on every page. The rules match * clientScopeSql() above: a deny wins, an allow list restricts to its members, and no * rules at all means unrestricted. */ function hasClientAccess($client_id) { global $session_is_admin, $client_access_array, $client_deny_array; $client_id = intval($client_id); if ($session_is_admin) { return true; } if (in_array($client_id, array_map('intval', $client_deny_array))) { return false; } if (empty($client_access_array)) { return true; } return in_array($client_id, array_map('intval', $client_access_array)); } function enforceClientAccess($client_id = null) { global $mysqli, $session_user_id, $session_is_admin, $session_name; // Use global $client_id if none passed if ($client_id === null) { global $client_id; } if ($session_is_admin) { return true; } $client_id = (int) $client_id; $session_user_id = (int) $session_user_id; if (empty($client_id) || empty($session_user_id)) { flashAlert('Access Denied.', 'error'); redirect('clients.php'); } // Deny list wins: an explicit deny blocks access regardless of any allow rule $deny_sql = "SELECT client_id FROM user_client_permissions WHERE user_id = $session_user_id AND client_id = $client_id AND permission_type = 'deny' LIMIT 1"; $deny_result = mysqli_query($mysqli, $deny_sql); if ($deny_result && mysqli_num_rows($deny_result) > 0) { logAudit('Client', 'Denied', "$session_name was denied permission from accessing client", $client_id, $client_id); flashAlert('Access Denied - You do not have permission to access that client!', 'error'); redirect('clients.php'); } // Check if this user has any client permissions set $permissions_sql = "SELECT client_id FROM user_client_permissions WHERE user_id = $session_user_id AND permission_type = 'allow' LIMIT 1"; $permissions_result = mysqli_query($mysqli, $permissions_sql); // If no permission rows exist for this user, allow access by default if ($permissions_result && mysqli_num_rows($permissions_result) == 0) { return true; } // If permission rows exist, require this client $access_sql = "SELECT client_id FROM user_client_permissions WHERE user_id = $session_user_id AND client_id = $client_id AND permission_type = 'allow' LIMIT 1"; $access_result = mysqli_query($mysqli, $access_sql); if ($access_result && mysqli_num_rows($access_result) > 0) { return true; } logAudit( 'Client', 'Denied', "$session_name was denied permission from accessing client", $client_id, $client_id ); flashAlert('Access Denied - You do not have permission to access that client!', 'error'); redirect('clients.php'); }