Security: defense-in-depth company isolation for all operations

Critical fixes:
- company_logo_upload: validate user has access to target company
- All delete functions (db.php): accept optional company_id parameter
  for defense-in-depth filtering (customers, devices, ipam, guides,
  leads, tickets, archives, mailboxes, rules, templates, todos)
- All API delete calls now pass company_id to db layer
- ticket_bulk_delete: per-ticket company_id filtering
- todo_comment/time/subtask operations: verify todo belongs to company
- dbGetMailbox: optional company_id scoping, used in smtp_test
- requireCompanyOrParam: no longer mutates session permanently
- Fix _dbFetch typo in zammad_attachment (was runtime error)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 18:27:13 +02:00
parent a73cce678e
commit 45de863d07
2 changed files with 109 additions and 52 deletions

68
db.php
View File

@@ -1171,8 +1171,9 @@ function dbSaveCustomer(string $companyId, array $customer): void {
}
}
function dbDeleteCustomer(string $customerId): void {
_dbExecute("DELETE FROM customers WHERE id = ?", [$customerId]);
function dbDeleteCustomer(string $customerId, string $companyId = ''): void {
if ($companyId) _dbExecute("DELETE FROM customers WHERE id = ? AND company_id = ?", [$customerId, $companyId]);
else _dbExecute("DELETE FROM customers WHERE id = ?", [$customerId]);
}
// ==================== SIJAINNIT (SITES) — POISTETTU, KÄYTETÄÄN LAITETILOJA ====================
@@ -1224,8 +1225,9 @@ function dbSaveDevice(string $companyId, array $device): void {
]);
}
function dbDeleteDevice(string $deviceId): void {
_dbExecute("DELETE FROM devices WHERE id = ?", [$deviceId]);
function dbDeleteDevice(string $deviceId, string $companyId = ''): void {
if ($companyId) _dbExecute("DELETE FROM devices WHERE id = ? AND company_id = ?", [$deviceId, $companyId]);
else _dbExecute("DELETE FROM devices WHERE id = ?", [$deviceId]);
}
// ==================== IPAM ====================
@@ -1270,8 +1272,9 @@ function dbSaveIpam(string $companyId, array $entry): void {
]);
}
function dbDeleteIpam(string $id): void {
_dbExecute("DELETE FROM ipam WHERE id = ?", [$id]);
function dbDeleteIpam(string $id, string $companyId = ''): void {
if ($companyId) _dbExecute("DELETE FROM ipam WHERE id = ? AND company_id = ?", [$id, $companyId]);
else _dbExecute("DELETE FROM ipam WHERE id = ?", [$id]);
}
// ==================== OHJEET (GUIDES) ====================
@@ -1288,8 +1291,9 @@ function dbSaveGuideCategory(string $companyId, array $cat): void {
", [$cat['id'], $companyId, $cat['nimi'] ?? '', $cat['sort_order'] ?? 0]);
}
function dbDeleteGuideCategory(string $catId): void {
_dbExecute("DELETE FROM guide_categories WHERE id = ?", [$catId]);
function dbDeleteGuideCategory(string $catId, string $companyId = ''): void {
if ($companyId) _dbExecute("DELETE FROM guide_categories WHERE id = ? AND company_id = ?", [$catId, $companyId]);
else _dbExecute("DELETE FROM guide_categories WHERE id = ?", [$catId]);
}
function dbLoadGuides(string $companyId): array {
@@ -1334,8 +1338,9 @@ function dbSaveGuide(string $companyId, array $g): void {
]);
}
function dbDeleteGuide(string $guideId): void {
_dbExecute("DELETE FROM guides WHERE id = ?", [$guideId]);
function dbDeleteGuide(string $guideId, string $companyId = ''): void {
if ($companyId) _dbExecute("DELETE FROM guides WHERE id = ? AND company_id = ?", [$guideId, $companyId]);
else _dbExecute("DELETE FROM guides WHERE id = ?", [$guideId]);
}
// ==================== LIIDIT ====================
@@ -1376,8 +1381,9 @@ function dbSaveLead(string $companyId, array $lead): void {
]);
}
function dbDeleteLead(string $leadId): void {
_dbExecute("DELETE FROM leads WHERE id = ?", [$leadId]);
function dbDeleteLead(string $leadId, string $companyId = ''): void {
if ($companyId) _dbExecute("DELETE FROM leads WHERE id = ? AND company_id = ?", [$leadId, $companyId]);
else _dbExecute("DELETE FROM leads WHERE id = ?", [$leadId]);
}
// ==================== TIKETIT ====================
@@ -1508,8 +1514,9 @@ function dbSaveTicket(string $companyId, array $ticket): void {
}
}
function dbDeleteTicket(string $ticketId): void {
_dbExecute("DELETE FROM tickets WHERE id = ?", [$ticketId]);
function dbDeleteTicket(string $ticketId, string $companyId = ''): void {
if ($companyId) _dbExecute("DELETE FROM tickets WHERE id = ? AND company_id = ?", [$ticketId, $companyId]);
else _dbExecute("DELETE FROM tickets WHERE id = ?", [$ticketId]);
}
function dbFindTicketByMessageId(string $companyId, string $messageId): ?array {
@@ -1550,8 +1557,9 @@ function dbRestoreArchive(string $archiveId): ?array {
return json_decode($row['data'], true);
}
function dbDeleteArchive(string $archiveId): void {
_dbExecute("DELETE FROM archives WHERE id = ?", [$archiveId]);
function dbDeleteArchive(string $archiveId, string $companyId = ''): void {
if ($companyId) _dbExecute("DELETE FROM archives WHERE id = ? AND company_id = ?", [$archiveId, $companyId]);
else _dbExecute("DELETE FROM archives WHERE id = ?", [$archiveId]);
}
// ==================== CHANGELOG ====================
@@ -1624,12 +1632,17 @@ function dbSaveMailbox(string $companyId, array $mailbox): void {
]);
}
function dbDeleteMailbox(string $mailboxId): void {
_dbExecute("DELETE FROM mailboxes WHERE id = ?", [$mailboxId]);
function dbDeleteMailbox(string $mailboxId, string $companyId = ''): void {
if ($companyId) _dbExecute("DELETE FROM mailboxes WHERE id = ? AND company_id = ?", [$mailboxId, $companyId]);
else _dbExecute("DELETE FROM mailboxes WHERE id = ?", [$mailboxId]);
}
function dbGetMailbox(string $mailboxId): ?array {
$b = _dbFetchOne("SELECT * FROM mailboxes WHERE id = ?", [$mailboxId]);
function dbGetMailbox(string $mailboxId, string $companyId = ''): ?array {
if ($companyId) {
$b = _dbFetchOne("SELECT * FROM mailboxes WHERE id = ? AND company_id = ?", [$mailboxId, $companyId]);
} else {
$b = _dbFetchOne("SELECT * FROM mailboxes WHERE id = ?", [$mailboxId]);
}
if ($b) {
$b['aktiivinen'] = (bool)$b['aktiivinen'];
$b['imap_port'] = (int)$b['imap_port'];
@@ -1679,8 +1692,9 @@ function dbSaveTicketRule(string $companyId, array $rule): void {
]);
}
function dbDeleteTicketRule(string $ruleId): void {
_dbExecute("DELETE FROM ticket_rules WHERE id = ?", [$ruleId]);
function dbDeleteTicketRule(string $ruleId, string $companyId = ''): void {
if ($companyId) _dbExecute("DELETE FROM ticket_rules WHERE id = ? AND company_id = ?", [$ruleId, $companyId]);
else _dbExecute("DELETE FROM ticket_rules WHERE id = ?", [$ruleId]);
}
// ==================== TIKETTITYYPIT ====================
@@ -1801,8 +1815,9 @@ function dbSaveTemplate(string $companyId, array $tpl): void {
]);
}
function dbDeleteTemplate(string $templateId): void {
_dbExecute("DELETE FROM reply_templates WHERE id = ?", [$templateId]);
function dbDeleteTemplate(string $templateId, string $companyId = ''): void {
if ($companyId) _dbExecute("DELETE FROM reply_templates WHERE id = ? AND company_id = ?", [$templateId, $companyId]);
else _dbExecute("DELETE FROM reply_templates WHERE id = ?", [$templateId]);
}
// ==================== PRIORITY EMAILS (ASIAKKUUDET) ====================
@@ -1888,8 +1903,9 @@ function dbSaveTodo(string $companyId, array $todo): void {
]);
}
function dbDeleteTodo(string $todoId): void {
_dbExecute("DELETE FROM todos WHERE id = ?", [$todoId]);
function dbDeleteTodo(string $todoId, string $companyId = ''): void {
if ($companyId) _dbExecute("DELETE FROM todos WHERE id = ? AND company_id = ?", [$todoId, $companyId]);
else _dbExecute("DELETE FROM todos WHERE id = ?", [$todoId]);
}
function dbAddTodoComment(string $todoId, array $comment): void {