diff --git a/api.php b/api.php index d35aec8..0bca993 100644 --- a/api.php +++ b/api.php @@ -122,16 +122,19 @@ function requireCompany(): string { // Kuten requireCompany(), mutta sallii company_id:n overriden GET-parametrista // Käytetään tiketti-endpointeissa jotta toisen yrityksen tikettejä voi avata +// EI muuta sessiota pysyvästi — palauttaa vain company_id:n function requireCompanyOrParam(): string { $paramCompany = $_GET['company_id'] ?? ''; if (!empty($paramCompany)) { $userCompanies = $_SESSION['companies'] ?? []; - if (!in_array($paramCompany, $userCompanies)) { + $isSuperadmin = ($_SESSION['role'] ?? '') === 'superadmin'; + if (!$isSuperadmin && !in_array($paramCompany, $userCompanies)) { http_response_code(403); echo json_encode(['error' => 'Ei oikeutta tähän yritykseen']); exit; } - $_SESSION['company_id'] = $paramCompany; + // Palauta parametrin company_id ILMAN session muutosta + return $paramCompany; } return requireCompany(); } @@ -1487,6 +1490,14 @@ switch ($action) { echo json_encode(['error' => 'Virheellinen company_id']); break; } + // Tarkista yrityksen oikeudet (vain superadmin tai oman yrityksen admin) + $isSA = ($_SESSION['role'] ?? '') === 'superadmin'; + $userCompanies = $_SESSION['companies'] ?? []; + if (!$isSA && !in_array($companyId, $userCompanies)) { + http_response_code(403); + echo json_encode(['error' => 'Ei oikeutta tähän yritykseen']); + break; + } if (!isset($_FILES['logo']) || $_FILES['logo']['error'] !== UPLOAD_ERR_OK) { http_response_code(400); echo json_encode(['error' => 'Logotiedosto puuttuu tai virhe uploadissa']); @@ -2210,7 +2221,7 @@ switch ($action) { } if ($archived) { dbArchiveCustomer($companyId, $archived); - dbDeleteCustomer($id); + dbDeleteCustomer($id, $companyId); dbAddLog($companyId, currentUser(), 'customer_archive', $archived['id'], $archived['yritys'], 'Arkistoi asiakkaan'); } echo json_encode(['success' => true]); @@ -2314,7 +2325,7 @@ switch ($action) { $devices = dbLoadDevices($companyId); $deviceName = ''; foreach ($devices as $d) { if ($d['id'] === $id) { $deviceName = $d['nimi']; break; } } - dbDeleteDevice($id); + dbDeleteDevice($id, $companyId); dbAddLog($companyId, currentUser(), 'device_delete', $id, $deviceName, 'Poisti laitteen'); echo json_encode(['success' => true]); break; @@ -2416,7 +2427,7 @@ switch ($action) { $all = dbLoadIpam($companyId); $entryName = ''; foreach ($all as $e) { if ($e['id'] === $id) { $entryName = ($e['tyyppi'] === 'vlan' ? 'VLAN ' . $e['vlan_id'] : $e['verkko']) . ' ' . $e['nimi']; break; } } - dbDeleteIpam($id); + dbDeleteIpam($id, $companyId); dbAddLog($companyId, currentUser(), 'ipam_delete', $id, $entryName, 'Poisti IPAM-merkinnän'); echo json_encode(['success' => true]); break; @@ -2474,7 +2485,7 @@ switch ($action) { $input = json_decode(file_get_contents('php://input'), true); $id = $input['id'] ?? ''; $guide = dbLoadGuide($id); - dbDeleteGuide($id); + dbDeleteGuide($id, $companyId); dbAddLog($companyId, currentUser(), 'guide_delete', $id, $guide ? $guide['title'] : '', 'Poisti ohjeen'); echo json_encode(['success' => true]); break; @@ -2511,7 +2522,7 @@ switch ($action) { $companyId = requireCompany(); if ($method !== 'POST') break; $input = json_decode(file_get_contents('php://input'), true); - dbDeleteGuideCategory($input['id'] ?? ''); + dbDeleteGuideCategory($input['id'] ?? '', $companyId); echo json_encode(['success' => true]); break; @@ -2649,7 +2660,7 @@ switch ($action) { $id = $input['id'] ?? ''; $todo = dbLoadTodo($id); if ($todo) { - dbDeleteTodo($id); + dbDeleteTodo($id, $companyId); dbAddLog($companyId, currentUser(), 'todo_delete', $id, $todo['title'] ?? '', ''); } echo json_encode(['success' => true]); @@ -2719,6 +2730,13 @@ switch ($action) { if ($method !== 'POST') break; $input = json_decode(file_get_contents('php://input'), true); $todoId = $input['todo_id'] ?? ''; + // Tarkista kuuluuko todo yritykseen + $todoCheck = _dbFetchOne("SELECT company_id FROM todos WHERE id = ?", [$todoId]); + if (!$todoCheck || $todoCheck['company_id'] !== $companyId) { + http_response_code(403); + echo json_encode(['error' => 'Ei oikeutta']); + break; + } $body = trim($input['body'] ?? ''); if (empty($body)) { http_response_code(400); @@ -2741,8 +2759,13 @@ switch ($action) { if ($method !== 'POST') break; $input = json_decode(file_get_contents('php://input'), true); $commentId = $input['id'] ?? ''; - // Tarkista onko oma kommentti tai admin - $rows = _dbFetchAll("SELECT author FROM todo_comments WHERE id = ?", [$commentId]); + // Tarkista onko oma kommentti tai admin + kuuluuko todo yritykseen + $rows = _dbFetchAll("SELECT tc.author, t.company_id FROM todo_comments tc JOIN todos t ON t.id = tc.todo_id WHERE tc.id = ?", [$commentId]); + if (!empty($rows) && $rows[0]['company_id'] !== $companyId) { + http_response_code(403); + echo json_encode(['error' => 'Ei oikeutta']); + break; + } if (!empty($rows) && ($rows[0]['author'] === currentUser() || isCompanyAdmin())) { dbDeleteTodoComment($commentId); echo json_encode(['success' => true]); @@ -2758,6 +2781,13 @@ switch ($action) { if ($method !== 'POST') break; $input = json_decode(file_get_contents('php://input'), true); $todoId = $input['todo_id'] ?? ''; + // Tarkista kuuluuko todo yritykseen + $todoCheck = _dbFetchOne("SELECT company_id FROM todos WHERE id = ?", [$todoId]); + if (!$todoCheck || $todoCheck['company_id'] !== $companyId) { + http_response_code(403); + echo json_encode(['error' => 'Ei oikeutta']); + break; + } $hours = floatval($input['hours'] ?? 0); if ($hours <= 0) { http_response_code(400); @@ -2782,7 +2812,12 @@ switch ($action) { if ($method !== 'POST') break; $input = json_decode(file_get_contents('php://input'), true); $entryId = $input['id'] ?? ''; - $rows = _dbFetchAll("SELECT user FROM todo_time_entries WHERE id = ?", [$entryId]); + $rows = _dbFetchAll("SELECT te.user, t.company_id FROM todo_time_entries te JOIN todos t ON t.id = te.todo_id WHERE te.id = ?", [$entryId]); + if (!empty($rows) && $rows[0]['company_id'] !== $companyId) { + http_response_code(403); + echo json_encode(['error' => 'Ei oikeutta']); + break; + } if (!empty($rows) && ($rows[0]['user'] === currentUser() || isCompanyAdmin())) { dbDeleteTodoTimeEntry($entryId); echo json_encode(['success' => true]); @@ -2818,12 +2853,15 @@ switch ($action) { case 'todo_subtask_toggle': requireAuth(); - requireCompany(); + $companyId = requireCompany(); if ($method !== 'POST') { echo json_encode(['error' => 'POST required']); break; } try { $input = json_decode(file_get_contents('php://input'), true); $subtaskId = $input['id'] ?? ''; if (!$subtaskId) { echo json_encode(['error' => 'id vaaditaan']); break; } + // Tarkista kuuluuko subtaskin todo yritykseen + $stCheck = _dbFetchOne("SELECT t.company_id FROM todo_subtasks s JOIN todos t ON t.id = s.todo_id WHERE s.id = ?", [$subtaskId]); + if (!$stCheck || $stCheck['company_id'] !== $companyId) { http_response_code(403); echo json_encode(['error' => 'Ei oikeutta']); break; } $completed = dbToggleTodoSubtask($subtaskId); echo json_encode(['success' => true, 'completed' => $completed]); } catch (\Throwable $e) { @@ -2834,12 +2872,15 @@ switch ($action) { case 'todo_subtask_delete': requireAuth(); - requireCompany(); + $companyId = requireCompany(); if ($method !== 'POST') { echo json_encode(['error' => 'POST required']); break; } try { $input = json_decode(file_get_contents('php://input'), true); $subtaskId = $input['id'] ?? ''; if (!$subtaskId) { echo json_encode(['error' => 'id vaaditaan']); break; } + // Tarkista kuuluuko subtaskin todo yritykseen + $stCheck = _dbFetchOne("SELECT t.company_id FROM todo_subtasks s JOIN todos t ON t.id = s.todo_id WHERE s.id = ?", [$subtaskId]); + if (!$stCheck || $stCheck['company_id'] !== $companyId) { http_response_code(403); echo json_encode(['error' => 'Ei oikeutta']); break; } $rows = _dbFetchAll("SELECT created_by FROM todo_subtasks WHERE id = ?", [$subtaskId]); if (!empty($rows) && ($rows[0]['created_by'] === currentUser() || isCompanyAdmin())) { dbDeleteTodoSubtask($subtaskId); @@ -2888,7 +2929,7 @@ switch ($action) { foreach ($archive as $c) { if ($c['id'] === $id) { $deleted = $c; break; } } - dbDeleteArchive($id); + dbDeleteArchive($id, $companyId); $filesDir = getCompanyDir() . '/files/' . $id; if (is_dir($filesDir)) { array_map('unlink', glob($filesDir . '/*')); @@ -2973,7 +3014,7 @@ switch ($action) { foreach ($leads as $l) { if ($l['id'] === $id) { $deleted = $l; break; } } - dbDeleteLead($id); + dbDeleteLead($id, $companyId); if ($deleted) dbAddLog($companyId, currentUser(), 'lead_delete', $id, $deleted['yritys'] ?? '', 'Poisti liidin'); echo json_encode(['success' => true]); break; @@ -3014,7 +3055,7 @@ switch ($action) { ]; dbSaveCustomer($companyId, $customer); // Poista liidi - dbDeleteLead($id); + dbDeleteLead($id, $companyId); dbAddLog($companyId, currentUser(), 'lead_to_customer', $customer['id'], $customer['yritys'], 'Muutti liidin asiakkaaksi'); echo json_encode($customer); break; @@ -3828,7 +3869,7 @@ switch ($action) { foreach ($tickets as $t) { if ($t['id'] === $id) { $deleted = $t; break; } } - dbDeleteTicket($id); + dbDeleteTicket($id, $companyId); if ($deleted) dbAddLog($companyId, currentUser(), 'ticket_delete', $id, $deleted['subject'] ?? '', 'Poisti tiketin'); echo json_encode(['success' => true]); break; @@ -3935,7 +3976,7 @@ switch ($action) { $ids = $input['ids'] ?? []; $deleted = 0; foreach ($ids as $ticketId) { - dbDeleteTicket($ticketId); + dbDeleteTicket($ticketId, $companyId); $deleted++; } dbAddLog($companyId, currentUser(), 'ticket_delete', '', '', "Massapoisto: $deleted tikettiä"); @@ -3948,7 +3989,7 @@ switch ($action) { if ($method !== 'POST') break; $input = json_decode(file_get_contents('php://input'), true); $ruleId = $input['id'] ?? ''; - dbDeleteTicketRule($ruleId); + dbDeleteTicketRule($ruleId, $companyId); echo json_encode(['success' => true]); break; @@ -4065,7 +4106,7 @@ switch ($action) { $companyId = requireCompany(); if ($method !== 'POST') break; $input = json_decode(file_get_contents('php://input'), true); - dbDeleteTemplate($input['id'] ?? ''); + dbDeleteTemplate($input['id'] ?? '', $companyId); echo json_encode(['success' => true]); break; @@ -4362,7 +4403,7 @@ switch ($action) { // Delete all existing mailboxes and re-save $existingMailboxes = dbLoadMailboxes($companyId); foreach ($existingMailboxes as $existing) { - dbDeleteMailbox($existing['id']); + dbDeleteMailbox($existing['id'], $companyId); } foreach ($input['mailboxes'] as $mb) { if (empty($mb['id'])) $mb['id'] = generateId(); @@ -4373,7 +4414,7 @@ switch ($action) { // Delete all existing rules and re-save $existingRules = dbLoadTicketRules($companyId); foreach ($existingRules as $existing) { - dbDeleteTicketRule($existing['id']); + dbDeleteTicketRule($existing['id'], $companyId); } foreach ($input['ticket_rules'] as $rule) { if (empty($rule['id'])) $rule['id'] = generateId(); @@ -4402,7 +4443,7 @@ switch ($action) { $companyId = requireCompany(); $input = json_decode(file_get_contents('php://input'), true); $mailboxId = $input['mailbox_id'] ?? ''; - $mailbox = dbGetMailbox($mailboxId); + $mailbox = dbGetMailbox($mailboxId, $companyId); if (!$mailbox) { echo json_encode(['error' => 'Postilaatikkoa ei löydy']); break; @@ -4571,7 +4612,7 @@ switch ($action) { 'auto_reply_body' => trim($input['auto_reply_body'] ?? ''), ]; // Hae vanha mailbox salasanojen vertailua varten - $existingMb = dbGetMailbox($mb['id']); + $existingMb = dbGetMailbox($mb['id'], $companyId); // IMAP-salasana: jos ******** -> pidä vanha, muuten päivitä if (isset($input['imap_password']) && $input['imap_password'] !== '********') { $mb['imap_password'] = $input['imap_password']; @@ -4608,7 +4649,7 @@ switch ($action) { if ($method !== 'POST') break; $input = json_decode(file_get_contents('php://input'), true); $mbId = $input['id'] ?? ''; - dbDeleteMailbox($mbId); + dbDeleteMailbox($mbId, $companyId); echo json_encode(['success' => true]); break; @@ -5597,7 +5638,7 @@ switch ($action) { break; } // Varmista että tiketti kuuluu yritykselle - $ticket = _dbFetch("SELECT * FROM tickets WHERE id = ? AND company_id = ?", [$ticketId, $companyId]); + $ticket = _dbFetchOne("SELECT * FROM tickets WHERE id = ? AND company_id = ?", [$ticketId, $companyId]); if (!$ticket || empty($ticket['zammad_ticket_id'])) { http_response_code(404); echo json_encode(['error' => 'Tikettiä ei löydy']); diff --git a/db.php b/db.php index 0d5645b..11436b7 100644 --- a/db.php +++ b/db.php @@ -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 {