Files
api/services/nginx/app/routes/bookingsRoute.php
T
Jeppe B 08a1538ed6 Merge pull request #212 from copenhagentruckwash/propose-fix-for-sql-injection-vulnerability
Cast pickup_bool to int to prevent SQL injection in bookings sync
2026-06-01 23:02:38 +02:00

534 lines
24 KiB
PHP

<?php
namespace routes;
use classes\authentication;
use classes\pdf_store;
use classes\response;
use classes\wash_certificate_store;
use objects\bookings_o;
use objects\departments_o;
use objects\logs_o;
use objects\order_bookings_o;
use traits\route_t;
class bookingsRoute
{
use route_t;
public function run(): void
{
/** All bookings */
$this->get('/bookings', function () {
// Require the user to be logged in
global
/** @var response $response */
$EMAIL_WASH_CERTIFICATE_TOKEN,
$response;
$this->requirePermission('list_bookings');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if ($user) {
// Log the incident
(new logs_o())->add('bookings', 'global', 1, $user->id, 'LIST_BOOKINGS', 'Successfully listed bookings');
// If the user has the permission to issue wash certificates, add the wash certificate key to the response
if ($user->hasPermission('issue_wash_certificates')) {
$response->add_meta('wash_certificate_token', $EMAIL_WASH_CERTIFICATE_TOKEN);
}
$bookings_o = new bookings_o();
// Check if the user has specified a booking id
if (self::isParametersSet(['id'])) {
// Check if the booking id is a number
if (!is_numeric(self::getParameter('id'))) {
$response->error('Booking ID must be a number', 400);
}
// Check if the booking exists
if (!$bookings_o->select((int)self::getParameter('id'))->exists()) {
$response->error('Booking not found', 404);
}
// Check if the user has access to the booking
if (!$user->hasAccessToBooking((int)self::getParameter('id'))) {
$response->forbidden(['list_bookings']);
}
// Return the booking
$response->success(
$bookings_o->asArray()
);
}
// Return the list of bookings
$response->success(
$bookings_o->parseBookings($bookings_o->listObjectsWithPaginationIfSet(
function ($booking) {
return (new bookings_o())->select($booking['id'])->asArray(
[
'include_parsed_services' => true,
]
);
},
$bookings_o->forceRestrictFilters(
[
'department' => $user->getGroup()->getDepartments(),
]
)
))
);
} else {
// Log the incident
(new logs_o())->add('bookings', 'global', 1, 0, 'LIST_BOOKINGS', 'No user found, or invalid session');
// Return an error
$response->error('Invalid session', 400);
}
},
[
'list_bookings' => 'List all bookings',
'issue_wash_certificates' => 'When set, the response will include the wash certificate token for sending wash certificates'
]
);
/** Own bookings */
$this->get('/user/bookings', function () {
// Require the user to be logged in
global /** @var response $response */
$response;
$this->requirePermission('list_own_bookings');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if ($user) {
// Log the incident
(new logs_o())->add('bookings', 'global', 1, $user->id, 'LIST_OWN_BOOKINGS', 'Successfully listed own bookings');
// Return the list of departments
$bookings_o = new bookings_o();
$response->success(
$bookings_o->parseBookings($bookings_o->listObjectsWithPaginationIfSet(
function ($booking) {
return (new bookings_o())->select($booking['id'])->asArray(
[
'include_parsed_services' => true,
]
);
},
$bookings_o->forceRestrictFilters(
[
'customer_number' => [
$user->customer_number->value(),
],
]
)
))
);
} else {
// Log the incident
(new logs_o())->add('bookings', 'global', 1, 0, 'LIST_OWN_BOOKINGS', 'No user found, or invalid session');
// Return an error
$response->error('Invalid session', 400);
}
},
[
'list_own_bookings' => 'List all bookings for the logged in user'
]
);
$this->put('/bookings', function () {
// Require the user to be logged in
global /** @var response $response */
$response;
$this->requirePermission('list_own_bookings');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if ($user) {
// Log the incident
(new logs_o())->add('bookings', 'global', 1, $user->id, 'UPDATE_BOOKING', 'Successfully updated booking');
// Require the booking id parameter
self::requireParameters([
'id'
]);
// Check if the booking id is a number
self::requireType((int)$this->getParameter('id'), self::type_int());
self::requireMinValue((int)$this->getParameter('id'), 1);
self::requireSameLength(
(int)$this->getParameter('id'),
$this->getParameter('id'),
);
// Check if the booking exists
$booking = (new bookings_o())->select((int)$this->getParameter('id'));
if (!$booking->exists()) {
$response->error('Booking not found', 404);
}
// Check if the user has access to the booking
if (!$user->hasAccessToBooking((int)$this->getParameter('id'))) {
$response->forbidden(['list_bookings']);
}
// Check if the optional parameters are set
if (self::isParametersSet(['reference_number'])) {
// Check if the reference number is a string
self::requireType(
(string)$this->getParameter('reference_number'),
self::type_string()
);
self::requireMinLength(
'reference_number',
0
);
self::requireMaxLength(
'reference_number',
255
);
$booking->reference_number->set(
(string)$this->getParameter('reference_number')
);
}
// Return the booking
$response->success(
$booking->asArray()
);
} else {
// Log the incident
(new logs_o())->add('bookings', 'global', 1, 0, 'ADD_BOOKING', 'No user found, or invalid session');
// Return an error
$response->error('Invalid session', 400);
}
},
[
'add_booking' => 'Add a new booking'
]
);
// Synchronize booking from the external system
$this->post('/admin/bookings/sync', function () {
// Require the user to be logged in
global $response;
$this->requirePermission('sync_bookings');
// Check if the request was successful
$booking = [
'id' => $this->fromRequest('id'),
'customer_number' => $this->fromRequest('customer_number'),
'wash_type' => $this->fromRequest('wash_type'),
'contact_email' => $this->fromRequest('contact_email'),
'reference_number' => $this->fromRequest('reference_number'),
'regNrTraekker' => $this->fromRequest('regNrTraekker'),
'regNrTrailer' => $this->fromRequest('regNrTrailer'),
'washCertificateEmail' => $this->fromRequest('washCertificateEmail'),
'date' => $this->fromRequest('date'),
'department' => $this->fromRequest('department'),
'pickup_bool' => $this->fromRequest('pickup_bool'),
'notes' => $this->fromRequest('notes'),
'washCertificateStatus' => $this->fromRequest('washCertificateStatus'),
'washCertificateUrl' => $this->fromRequest('washCertificateUrl'),
'status' => $this->fromRequest('status'),
];
// Log the incident
(new logs_o())->add('bookings', 'global', 1, 0, 'SYNC_BOOKINGS', 'Successfully synced bookings');
// Add the booking, if it doesn't exist, update it if it does
(new bookings_o())->addOrUpdate(
(int)$booking['id'],
(int)$booking['customer_number'],
(string)$booking['wash_type'],
(string)$booking['contact_email'],
(string)$booking['reference_number'],
(string)$booking['regNrTraekker'],
(string)$booking['regNrTrailer'],
(string)$booking['washCertificateEmail'],
(string)$booking['date'],
(string)$booking['department'],
(int)$booking['pickup_bool'],
(string)$booking['notes'],
(string)$booking['washCertificateStatus'],
(string)$booking['washCertificateUrl'],
(string)$booking['status']
);
$response->success(
['message' => 'Successfully synced booking']
);
},
[
'sync_bookings' => 'Sync bookings from the external system'
]
);
// Get a departments unfulfilled bookings (count) for the day
$this->get('/admin/bookings/department/count', function () {
// Require the user to be logged in
global /** @var response $response */
$response;
$this->requirePermission('list_department_bookings_count');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if ($user) {
// Check if the department_id is set
if ($this->fromRequest('department_id') === null) {
$response->error('Department ID is required', 400);
}
// Check if the department id is a valid number
if (!is_numeric($this->fromRequest('department_id'))) {
$response->error('Department ID must be a number', 400);
}
// Check if the result is cached, if so, we don't need to query the database
// Check if the department exists
if (!(new departments_o())->selectId((int)$this->fromRequest('department_id'))->exists()) {
$response->error('Department not found', 404);
}
// Log the incident
(new logs_o())->add('bookings', 'global', 1, $user->id, 'LIST_DEPARTMENT_BOOKINGS_COUNT', 'Successfully listed department bookings');
// Return the list of departments
$response->success(
(int)(new order_bookings_o())->getDailyUnfulfilledBookingsCountForDepartment((int)$this->fromRequest('department_id'))
);
} else {
// Log the incident
(new logs_o())->add('bookings', 'global', 1, 0, 'LIST_DEPARTMENT_BOOKINGS_COUNT', 'No user found, or invalid session');
// Return an error
$response->error('Invalid session', 400);
}
},
[
'list_department_bookings_count' => 'List the unfulfilled bookings count for a department'
]
);
$this->post('/user/bookings/washcertificate/download', function () {
// Require the user to be logged in
global /** @var response $response */
$response;
// Check if the user has access to the department
$this->requirePermission('download_own_wash_certificate');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if (!$user->exists()) {
$response->error('User not found', 400);
}
// Check if the required fields are set
$id = $response->getRequestParameter('id');
// Make sure the id is a number
if (!is_numeric($id)) {
$response->error('id parameter must be a number got: ' . $id, 400);
}
// Make sure the user is allowed to download the wash certificate
if (!$user->hasAccessToBooking($id)) {
$response->error('You are not allowed to download this wash certificate', 400);
}
// Check if the booking is completed
if (!(new bookings_o())->select($id)->exists()) {
$response->error('Booking not found', 404);
}
$bookings_new = (new bookings_o())->select($id);
$wash_certificate_status = $bookings_new->washCertificateStatus->value();
switch ($wash_certificate_status) {
case 'pending':
$response->error('Wash certificate has not been issued yet', 400);
case 'completed':
// The wash certificate has been issued, so we can proceed
break;
case 'cancelled':
$response->error('Wash certificate is cancelled', 400);
default:
$response->error('Wash certificate status is unknown', 500);
}
// Get the wash certificate object
//$wash_certificate_object = $bookings_new->washCertificateUrl->value();
// Create the connection
$pdf_storage = new wash_certificate_store();
// Check if the wash certificate exists.
if (!$pdf_storage->washCertificateExists($bookings_new->id)) {
$response->error('Wash certificate not found, but it should exist', 500);
}
// Generate the download link
$response->success(
["link" => $pdf_storage->getWashCertificateDownload($bookings_new->id)]
);
},
[
'download_own_wash_certificate' => 'Download the wash certificate for a booking'
]
);
$this->get('/bookings/download_pdf', function () {
// Require the user to be logged in
global /** @var response $response */
$response;
// Check if the user has access to the department
$this->requirePermission('download_own_wash_certificate');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if (!$user->exists()) {
$response->error('User not found', 400);
}
self::requireParameters(['id']);
$id = (int)$this->getParameter('id');
self::requireType(
$id,
self::type_int()
);
self::requireMinValue($id, 1);
self::requireSameLength(
$id,
self::getParameter('id'),
);
// Make sure the user is allowed to download the wash certificate
if (!$user->hasAccessToBooking($id)) {
$response->error('You are not allowed to download this wash certificate', 400);
}
$bookings = (new bookings_o())->select((int)$id);
if (!$bookings->exists()) {
$response->error('Booking not found', 404);
}
// Check if the booking has a wash certificate
if (!empty($bookings->wash_certificate_pdf->value())) {
// The booking has a wash certificate, so we can proceed
$pdf_storage = new pdf_store();
// Check if the wash certificate exists.
if (!$pdf_storage->doesObjectExist($bookings->wash_certificate_pdf->value())) {
$response->error('Wash certificate not found, but it should exist', 500);
}
// Generate the download link
$response->success(
["link" => $pdf_storage->getPresignedUrl($bookings->wash_certificate_pdf->value())]
);
} else {
// Check if the certificate is stored in the other bucket
$wash_certificate_storage = new wash_certificate_store();
// Check if the wash certificate exists.
if (!$wash_certificate_storage->washCertificateExists($bookings->id)) {
$response->error('No wash certificate found for this booking', 404);
}
// Generate the download link
$response->success(
["link" => $wash_certificate_storage->getWashCertificateDownload($bookings->id)]
);
}
},
[
'download_own_wash_certificate' => 'Download the wash certificate for a booking'
]
);
$this->post('/admin/bookings/delete', function () {
// Require the user to be logged in
global /** @var response $response */
$response;
$this->requirePermission('delete_booking');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if (!$user->exists()) {
$response->error('User not found', 400);
}
// Check if the required fields are set
$id = $response->getRequestParameter('id');
// Make sure the id is a number
if (!is_numeric($id)) {
$response->error('id parameter must be a number got: ' . $id, 400);
}
// Make sure the user is allowed to delete the booking
if (!$user->hasAccessToBooking($id)) {
$response->error('You are not allowed to delete this booking', 400);
}
// Delete the booking
(new bookings_o())->delete($id);
// Return success
$response->success(
["message" => "Booking deleted"]
);
},
[
'delete_booking' => 'Delete a booking'
]
);
$this->post('/superuser/bookings/sync/all', function () {
// Require the user to be logged in
global /** @var response $response */
$response;
$this->requirePermission('sync_all_bookings');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if (!$user->exists()) {
$response->error('User not found', 400);
}
// Log the incident
(new logs_o())->add('bookings', 'global', 1, $user->id, 'SYNC_ALL_BOOKINGS', 'Successfully synced all bookings');
// Sync all bookings
(new bookings_o())->syncBookings();
// Return success
$response->success(
["message" => "All bookings synced"]
);
},
[
'sync_all_bookings' => 'Sync all bookings from the external system'
]
);
$this->post('/admin/bookings/completeWashWithoutWashCertificate', function () {
// Require the user to be logged in
global /** @var response $response */
$response;
$response->error('Booking completion must be completed through POS desktop or mobile steps.', 410);
$this->requirePermission('complete_wash_without_wash_certificate');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if (!$user->exists()) {
$response->error('User not found', 400);
}
// Check if the required fields are set
$id = $response->getRequestParameter('id');
// Make sure the id is a number
if (!is_numeric($id)) {
$response->error('id parameter must be a number got: ' . $id, 400);
}
// Make sure the user is allowed to complete the wash without a wash certificate
if (!$user->hasAccessToBooking($id)) {
$response->error('You are not allowed to complete this wash without a wash certificate', 400);
}
// Complete the wash without a wash certificate
(new bookings_o())->completeWashWithoutWashCertificate($id);
// Return success
$response->success(
["message" => "Wash completed without wash certificate"]
);
},
[
'complete_wash_without_wash_certificate' => 'Complete a wash without a wash certificate'
]
);
$this->post('/user/bookings/delete', function () {
// Require the user to be logged in
global /** @var response $response */
$response;
$this->requirePermission('delete_own_booking');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if (!$user->exists()) {
$response->error('User not found', 400);
}
// Check if the required fields are set
$id = $response->getRequestParameter('id');
// Make sure the id is a number
if (!is_numeric($id)) {
$response->error('id parameter must be a number got: ' . $id, 400);
}
// Make sure the user is allowed to delete the booking
if (!$user->hasAccessToBooking($id)) {
$response->error('You are not allowed to delete this booking', 400);
}
// Delete the booking
(new bookings_o())->delete($id);
// Return success
$response->success(
["message" => "Booking deleted"]
);
},
[
'delete_own_booking' => 'Delete the users own booking'
]
);
}
}