diff --git a/agent/reports/credential_rotation.php b/agent/reports/credential_rotation.php index 2579f0421..253c8afa1 100644 --- a/agent/reports/credential_rotation.php +++ b/agent/reports/credential_rotation.php @@ -16,6 +16,7 @@ $passwords_not_rotated_sql = mysqli_query($mysqli, FROM credentials LEFT JOIN clients ON credential_client_id = client_id WHERE DATE(credential_password_changed_at) < DATE_SUB(CURDATE(), INTERVAL $days DAY) + " . clientScopeSql('credential_client_id') . " ORDER BY client_name" ); diff --git a/api/v1/enforce_api_rbac.php b/api/v1/enforce_api_rbac.php index c36ac9e3c..ef2b5b01f 100644 --- a/api/v1/enforce_api_rbac.php +++ b/api/v1/enforce_api_rbac.php @@ -45,24 +45,10 @@ function apiUserCanAccessClient($client_id) { } // Client-scope SQL fragment for a read query, from the user's allow / deny lists. -// Admin and unrestricted users get no restriction. Column-aware, so it works on any -// resource. Returns " AND ..." or "" (used after a "WHERE 1=1" anchor). +// Thin wrapper over clientScopeSql() in functions/auth.php so the API and the UI share one +// implementation. Kept under the api* name because every endpoint already calls it. function apiClientScopeSql($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 = ''; - if (!empty($client_access_array)) { - $sql .= " AND $column IN (" . 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; + return clientScopeSql($column); } // --- Every key must be tied to a user (legacy keys were removed in the 2.4.7 migration) --- diff --git a/functions/auth.php b/functions/auth.php index f25cbb549..570091e2a 100644 --- a/functions/auth.php +++ b/functions/auth.php @@ -62,6 +62,39 @@ function enforceUserPermission($module, $check_access_level = 1) { } } +// 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 = ''; + + if (!empty($client_access_array)) { + $sql .= " AND $column IN (" . 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; +} + function enforceClientAccess($client_id = null) { global $mysqli, $session_user_id, $session_is_admin, $session_name;