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');
}