API Keys): each acts as an agent, with that * agent's role and client access, until the day it expires. A key is only * as good as its agent - a disabled, archived or role-less one turns every * call away (api/v1/enforce_api_rbac.php). * * Shared by admin/api_keys.php, admin/modals/api/* and admin/post/api_keys.php. */ /** The keys, by name - the row with its agent's name, picture, type, status, archived and role. */ function apiKeyRows($search = '') { global $mysqli; $where = $search !== '' ? "WHERE api_key_name LIKE '%" . escapeSql($search) . "%' OR user_name LIKE '%" . escapeSql($search) . "%'" : ''; $rows = []; $sql = mysqli_query($mysqli, "SELECT api_key_id, api_key_name, api_key_secret, api_key_created_at, api_key_expire, api_key_user_id, api_key_last_used_at, user_name, user_avatar, user_type, user_status, user_archived_at, user_role_id FROM api_keys LEFT JOIN users ON user_id = api_key_user_id $where ORDER BY api_key_name ASC"); while ($row = mysqli_fetch_assoc($sql)) { $rows[intval($row['api_key_id'])] = $row; } return $rows; } /* One key as apiKeyRows() gives it, or null. */ function apiKeyRow($api_key_id) { global $mysqli; $api_key_id = intval($api_key_id); $row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT api_key_id, api_key_name, api_key_secret, api_key_created_at, api_key_expire, api_key_user_id, api_key_last_used_at, user_name, user_avatar, user_type, user_status, user_archived_at, user_role_id FROM api_keys LEFT JOIN users ON user_id = api_key_user_id WHERE api_key_id = $api_key_id")); return $row ?: null; } /* Has this key expired? It works until the start of its expiry date. */ function apiKeyExpired(array $row) { return (string) $row['api_key_expire'] <= date('Y-m-d'); } /* * Why a key's agent turns every call away - '' when they don't: no agent, * one who is disabled or archived, or one with no role. $role is their * roleRow() (or null). */ function apiKeyAgentProblem(array $row, $role) { $name = trim(str_replace(' (archived)', '', (string) ($row['user_name'] ?? ''))); if (!intval($row['api_key_user_id']) || $row['user_name'] === null || intval($row['user_type']) !== 1) { return 'it acts as nobody - pick an agent'; } if ($row['user_archived_at']) { return "$name is archived"; } if (intval($row['user_status']) !== 1) { return "$name is disabled"; } if (!$role) { return "$name has no role"; } return ''; } /* * A key's state as a pill - whether it works, and until when. Expired; * won't work because of its agent; expiring within two weeks; or working. * HTML. */ function apiKeyStatePill(array $row, $role) { $expire = date('M j, Y', strtotime($row['api_key_expire'])); if (apiKeyExpired($row)) { return 'Expired'; } $problem = apiKeyAgentProblem($row, $role); if ($problem !== '') { return 'Won\'t work - ' . escapeHtml($problem) . ''; } $days = (int) floor((strtotime($row['api_key_expire']) - strtotime(date('Y-m-d'))) / 86400); if ($days <= 14) { return 'Expires in ' . $days . ' day' . ($days === 1 ? '' : 's') . ''; } return 'Works until ' . $expire . ''; } /* The agents a key can act as - active ones, by name: [user_id => row with role_id]. */ function apiKeyAgents() { global $mysqli; $agents = []; $sql = mysqli_query($mysqli, "SELECT user_id, user_name, user_avatar, user_role_id FROM users WHERE user_type = 1 AND user_status = 1 AND user_archived_at IS NULL ORDER BY user_name ASC"); while ($row = mysqli_fetch_assoc($sql)) { $agents[intval($row['user_id'])] = $row; } return $agents; } /* * The New Key / Edit Key window's fields, checked - [fields, error]. * fields: name, expire (Y-m-d, after today - or the day it already has, so * an expired key can be renamed and stay expired) and user_id (an active * agent). */ function apiKeyFromInput(array $in, $current_expire = '') { $name = trim(mb_substr(strip_tags((string) ($in['name'] ?? '')), 0, 255)); if ($name === '') { return [null, 'A key needs a name.']; } $expire = (string) ($in['expire'] ?? ''); $date = DateTime::createFromFormat('!Y-m-d', $expire); if (!$date || $date->format('Y-m-d') !== $expire || $expire > '2999-12-31' || ($expire <= date('Y-m-d') && $expire !== (string) $current_expire)) { return [null, 'Pick the day it stops working - after today.']; } $user_id = intval($in['run_as_user'] ?? 0); if (!isset(apiKeyAgents()[$user_id])) { return [null, 'Pick an active agent for it to act as.']; } return [['name' => $name, 'expire' => $expire, 'user_id' => $user_id], '']; } /* * The "Act as" picker of the key windows, with what each agent can open * shown under it for the one picked (js in api_key_form). $selected: the * user_id, 0 for none. */ function apiKeyAgentFieldHtml($selected = 0) { $selected = intval($selected); $agents = apiKeyAgents(); $roles = roleRows(); $access = userClientAccess(array_keys($agents)); ob_start(); ?>
$agent) { $role = $roles[intval($agent['user_role_id'])] ?? null; ?>
>
Their role, ยท [], 'deny' => []], intval($role['role_is_admin']))) ?>
They have no role, so the key would be turned away from everything.
The key can do what they can - their role and the clients they see - and calls show in the audit log as them.