Merge pull request #173 from copenhagentruckwash/fix-cross-tenant-certificate-attachment-vulnerability
Validate booking order context before certificates
This commit is contained in:
@@ -369,6 +369,7 @@ class order_bookings_o extends db
|
||||
return;
|
||||
}
|
||||
|
||||
$this->requireLinkedOrderMatchesBooking($order);
|
||||
$normalizedSafetySeal = orders_o::normalizeSafetySealValue($safety_seal);
|
||||
if ($normalizedSafetySeal !== null) {
|
||||
$order->setSafetySealValue($normalizedSafetySeal);
|
||||
@@ -467,14 +468,34 @@ class order_bookings_o extends db
|
||||
return $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function requireLinkedOrderMatchesBooking(orders_o $order): void
|
||||
{
|
||||
self::requireSelected();
|
||||
|
||||
$bookingCustomerNumber = (int)$this->customer_number->value();
|
||||
$bookingDepartmentId = (int)$this->department->value();
|
||||
$orderCustomerId = (int)$order->customer_id->value();
|
||||
$orderDepartmentId = (int)$order->department_id->value();
|
||||
|
||||
if ($orderCustomerId !== $bookingCustomerNumber || $orderDepartmentId !== $bookingDepartmentId) {
|
||||
throw new Exception('Linked order does not match booking customer or department');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function attachWashCertificate(int $user_id, ?string $safety_seal = null): void
|
||||
{
|
||||
self::requireSelected();
|
||||
$order = $this->getOrder();
|
||||
$this->requireLinkedOrderMatchesBooking($order);
|
||||
|
||||
// Check if the order already has a wash certificate attached
|
||||
if ($this->getOrder()->hasWashCertificateAttached()) {
|
||||
if ($order->hasWashCertificateAttached()) {
|
||||
return;
|
||||
}
|
||||
// Get the operator name
|
||||
|
||||
@@ -15,8 +15,12 @@ if (!class_exists('OrderBookingsCompletionOrderDouble')) {
|
||||
{
|
||||
$this->id = -1;
|
||||
$this->booking_id = new object_property('orders', -1, 'booking_id', 'int', false);
|
||||
$this->customer_id = new object_property('orders', -1, 'customer_id', 'int', true);
|
||||
$this->department_id = new object_property('orders', -1, 'department_id', 'int', true);
|
||||
$this->safety_seal = new object_property('orders', -1, 'safety_seal', 'string', false);
|
||||
$this->completed_at = new object_property('orders', -1, 'completed_at', 'timestamp', false);
|
||||
$this->customer_id->set(111111);
|
||||
$this->department_id->set(10);
|
||||
}
|
||||
|
||||
public bool $washCertificateAttached = false;
|
||||
@@ -52,8 +56,12 @@ if (!class_exists('OrderBookingsCompletionDouble')) {
|
||||
{
|
||||
$this->id = -1;
|
||||
$this->linkedOrder = $linkedOrder;
|
||||
$this->customer_number = new object_property('order_bookings', -1, 'customer_number', 'int', true);
|
||||
$this->department = new object_property('order_bookings', -1, 'department', 'int', true);
|
||||
$this->order_id = new object_property('order_bookings', -1, 'order_id', 'int', false);
|
||||
$this->items = new object_property('order_bookings', -1, 'items', 'json', false);
|
||||
$this->customer_number->set(111111);
|
||||
$this->department->set(10);
|
||||
$this->items->set([]);
|
||||
}
|
||||
|
||||
@@ -91,7 +99,7 @@ if (!class_exists('OrderBookingsCompletionDouble')) {
|
||||
}
|
||||
}
|
||||
|
||||
it('attaches and emails a wash certificate when a booking is already linked to a pos order without one', function (): void {
|
||||
it('attaches and emails a wash certificate when a booking is already linked to a matching pos order without one', function (): void {
|
||||
$order = new OrderBookingsCompletionOrderDouble();
|
||||
$booking = new OrderBookingsCompletionDouble($order);
|
||||
$booking->order_id->set(321);
|
||||
@@ -104,6 +112,22 @@ it('attaches and emails a wash certificate when a booking is already linked to a
|
||||
expect($booking->sendCalls)->toBe(1);
|
||||
});
|
||||
|
||||
it('rejects wash certificate completion when the linked pos order belongs to another booking context', function (): void {
|
||||
$order = new OrderBookingsCompletionOrderDouble();
|
||||
$order->customer_id->set(222222);
|
||||
$order->department_id->set(99);
|
||||
|
||||
$booking = new OrderBookingsCompletionDouble($order);
|
||||
$booking->order_id->set(321);
|
||||
$booking->containsWashCertificate = true;
|
||||
|
||||
expect(fn() => $booking->completeBooking(77, 'LINKED-SEAL'))
|
||||
->toThrow(Exception::class, 'Linked order does not match booking customer or department');
|
||||
expect($order->getSafetySealValue())->toBeNull();
|
||||
expect($booking->attachCalls)->toBe(0);
|
||||
expect($booking->sendCalls)->toBe(0);
|
||||
});
|
||||
|
||||
it('uses the linked pos order wash certificate item added during mobile completion', function (): void {
|
||||
$order = new OrderBookingsCompletionOrderDouble();
|
||||
$order->containsWashCertificate = true;
|
||||
|
||||
Reference in New Issue
Block a user