Add explicit CompanyId filter to tenant-scoped GetAllAsync calls
Multi-tenant defense-in-depth sweep (GetAllAsync vector). These queries relied solely on the global tenant query filter, so a SuperAdmin session (filter bypassed) would have merged/exported every tenant's data. Now each filters explicitly by CompanyId: - ToolsController: all 11 CSV export methods (Customers, Quotes, Jobs, Invoices, Payments, Purchase Orders x2, Inventory, Maintenance, Appointments, Expenses) — converted GetAllAsync -> FindAsync(CompanyId). Highest impact: these are bulk data exports. - KioskController.Intakes: kiosk sessions (+ linked customer/job). - BillsController recurring-bill detection: companyId was computed but never applied to the query. Verified safe (no change): the ignoreQueryFilters cases (SKU-sequence generators read only SKU strings; the SuperAdmin category repair), OvenScheduler (indirectly scoped by the company's job-item ids), and the platform/SuperAdmin controllers (cross-company by design). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1198,7 +1198,7 @@ public class BillsController : Controller
|
||||
var companyId = int.TryParse(User.FindFirst("CompanyId")?.Value, out var cid) ? cid : 0;
|
||||
var cutoff = DateTime.Today.AddMonths(-12);
|
||||
|
||||
var bills = (await _unitOfWork.Bills.GetAllAsync(false, b => b.Vendor))
|
||||
var bills = (await _unitOfWork.Bills.FindAsync(b => b.CompanyId == companyId, false, b => b.Vendor))
|
||||
.Where(b => b.Status != BillStatus.Voided && b.BillDate >= cutoff)
|
||||
.ToList();
|
||||
|
||||
|
||||
@@ -582,7 +582,9 @@ public class KioskController : Controller
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Intakes(string? filter)
|
||||
{
|
||||
var sessions = await _unitOfWork.KioskSessions.GetAllAsync(false,
|
||||
var companyId = GetCurrentCompanyId();
|
||||
var sessions = await _unitOfWork.KioskSessions.FindAsync(
|
||||
s => s.CompanyId == companyId, false,
|
||||
s => s.LinkedCustomer,
|
||||
s => s.LinkedJob);
|
||||
|
||||
|
||||
@@ -2267,7 +2267,7 @@ public class ToolsController : Controller
|
||||
}
|
||||
|
||||
// 15. Purchase Orders
|
||||
var purchaseOrders = await _unitOfWork.PurchaseOrders.GetAllAsync(false, po => po.Vendor);
|
||||
var purchaseOrders = await _unitOfWork.PurchaseOrders.FindAsync(po => po.CompanyId == companyId, false, po => po.Vendor);
|
||||
var purchaseOrdersCsv = GeneratePurchaseOrdersCsv(purchaseOrders);
|
||||
var purchaseOrdersEntry = archive.CreateEntry($"purchase_orders_{timestamp}.csv");
|
||||
using (var entryStream = purchaseOrdersEntry.Open())
|
||||
@@ -2330,7 +2330,7 @@ public class ToolsController : Controller
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
var customers = await _unitOfWork.Customers.GetAllAsync(false, c => c.PricingTier);
|
||||
var customers = await _unitOfWork.Customers.FindAsync(c => c.CompanyId == companyId, false, c => c.PricingTier);
|
||||
var csv = GenerateCustomersCsv(customers);
|
||||
var fileName = $"customers_export_{DateTime.UtcNow:yyyyMMddHHmmss}.csv";
|
||||
|
||||
@@ -2364,7 +2364,7 @@ public class ToolsController : Controller
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
var quotes = await _unitOfWork.Quotes.GetAllAsync(false, q => q.Customer, q => q.QuoteStatus);
|
||||
var quotes = await _unitOfWork.Quotes.FindAsync(q => q.CompanyId == companyId, false, q => q.Customer, q => q.QuoteStatus);
|
||||
var csv = GenerateQuotesCsv(quotes);
|
||||
var fileName = $"quotes_export_{DateTime.UtcNow:yyyyMMddHHmmss}.csv";
|
||||
|
||||
@@ -2397,7 +2397,7 @@ public class ToolsController : Controller
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
var jobs = await _unitOfWork.Jobs.GetAllAsync(false, j => j.Customer, j => j.JobStatus, j => j.JobPriority);
|
||||
var jobs = await _unitOfWork.Jobs.FindAsync(j => j.CompanyId == companyId, false, j => j.Customer, j => j.JobStatus, j => j.JobPriority);
|
||||
var csv = GenerateJobsCsv(jobs);
|
||||
var fileName = $"jobs_export_{DateTime.UtcNow:yyyyMMddHHmmss}.csv";
|
||||
|
||||
@@ -2429,7 +2429,7 @@ public class ToolsController : Controller
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
var appointments = await _unitOfWork.Appointments.GetAllAsync(false,
|
||||
var appointments = await _unitOfWork.Appointments.FindAsync(a => a.CompanyId == companyId, false,
|
||||
a => a.Customer, a => a.AppointmentType, a => a.AppointmentStatus);
|
||||
var csv = GenerateAppointmentsCsv(appointments);
|
||||
var fileName = $"appointments_export_{DateTime.UtcNow:yyyyMMddHHmmss}.csv";
|
||||
@@ -2499,7 +2499,7 @@ public class ToolsController : Controller
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
var inventoryItems = await _unitOfWork.InventoryItems.GetAllAsync(false, i => i.PrimaryVendor);
|
||||
var inventoryItems = await _unitOfWork.InventoryItems.FindAsync(i => i.CompanyId == companyId, false, i => i.PrimaryVendor);
|
||||
var csv = GenerateInventoryCsv(inventoryItems);
|
||||
var fileName = $"inventory_export_{DateTime.UtcNow:yyyyMMddHHmmss}.csv";
|
||||
|
||||
@@ -2566,7 +2566,7 @@ public class ToolsController : Controller
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
var maintenance = await _unitOfWork.MaintenanceRecords.GetAllAsync(false, m => m.Equipment);
|
||||
var maintenance = await _unitOfWork.MaintenanceRecords.FindAsync(m => m.CompanyId == companyId, false, m => m.Equipment);
|
||||
var csv = GenerateMaintenanceCsv(maintenance);
|
||||
var fileName = $"maintenance_export_{DateTime.UtcNow:yyyyMMddHHmmss}.csv";
|
||||
|
||||
@@ -2654,7 +2654,7 @@ public class ToolsController : Controller
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
var invoices = await _unitOfWork.Invoices.GetAllAsync(false, i => i.Customer, i => i.Job);
|
||||
var invoices = await _unitOfWork.Invoices.FindAsync(i => i.CompanyId == companyId, false, i => i.Customer, i => i.Job);
|
||||
var csv = GenerateInvoicesCsv(invoices);
|
||||
var fileName = $"invoices_export_{DateTime.UtcNow:yyyyMMddHHmmss}.csv";
|
||||
|
||||
@@ -2687,7 +2687,7 @@ public class ToolsController : Controller
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
var payments = await _unitOfWork.Payments.GetAllAsync(false, p => p.Invoice, p => p.DepositAccount);
|
||||
var payments = await _unitOfWork.Payments.FindAsync(p => p.CompanyId == companyId, false, p => p.Invoice, p => p.DepositAccount);
|
||||
var csv = GeneratePaymentsCsv(payments);
|
||||
var fileName = $"payments_export_{DateTime.UtcNow:yyyyMMddHHmmss}.csv";
|
||||
|
||||
@@ -2877,7 +2877,7 @@ public class ToolsController : Controller
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
var purchaseOrders = await _unitOfWork.PurchaseOrders.GetAllAsync(false, po => po.Vendor);
|
||||
var purchaseOrders = await _unitOfWork.PurchaseOrders.FindAsync(po => po.CompanyId == companyId, false, po => po.Vendor);
|
||||
var csv = GeneratePurchaseOrdersCsv(purchaseOrders);
|
||||
var fileName = $"purchase_orders_export_{DateTime.UtcNow:yyyyMMddHHmmss}.csv";
|
||||
|
||||
@@ -4611,7 +4611,7 @@ public class ToolsController : Controller
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
var expenses = await _unitOfWork.Expenses.GetAllAsync(false,
|
||||
var expenses = await _unitOfWork.Expenses.FindAsync(e => e.CompanyId == companyId, false,
|
||||
e => e.ExpenseAccount,
|
||||
e => e.PaymentAccount,
|
||||
e => e.Vendor,
|
||||
|
||||
Reference in New Issue
Block a user