PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
← All changes | app/Controllers/TripDownloadController.php +59 -1 3.0.3 → 3.0.16 View file →
@@ -328,9 +328,27 @@
328 328 // Check booking status
329 329 if (isset($booking->status) && !in_array($booking->status, ['confirmed', 'paid', 'completed'])) {
330 330 return new WP_Error('booking_invalid', __('Booking must be confirmed to access downloads.', 'yatra'), ['status' => 403]);
331 331 }
332 -
332 +
333 + // H-2: bind the signed URL to a requester who actually owns this booking.
334 + // Without this, any logged-in user could mint a download URL for another
335 + // customer's confirmed booking on the same trip. Monitor-first: in monitor
336 + // mode this only logs and proceeds (no broken downloads for anyone).
337 + if (!$this->requesterOwnsBooking($bookingId, $booking)) {
338 + if (\Yatra\Security\Guard::denied('download_url_ownership', [
339 + 'booking_id' => $bookingId,
340 + 'download_id' => $downloadId,
341 + 'user' => get_current_user_id(),
342 + ])) {
343 + return new WP_Error(
344 + 'forbidden',
345 + __('You do not have permission to download this file.', 'yatra'),
346 + ['status' => 403]
347 + );
348 + }
349 + }
350 +
333 351 $requiredBookingId = $bookingId;
334 352 }
335 353
336 354 // Generate secure download URL
@@ -347,8 +365,48 @@
347 365 'filename' => $filename,
348 366 'visibility' => $visibility,
349 367 'title' => $download->title ?? '',
350 368 ], 200);
369 + }
370 +
371 + /**
372 + * Does the current requester own this booking? (H-2)
373 + *
374 + * Admins pass; a registered-user booking requires the owning user; a guest
375 + * booking (user_id NULL/0) requires the booking-session token bound to it.
376 + * Same rule used across the booking-session/payment endpoints.
377 + *
378 + * @param object|null $booking Booking row, or null when not found.
379 + */
380 + private function requesterOwnsBooking(int $bookingId, $booking): bool
381 + {
382 + if (current_user_can('manage_options')) {
383 + return true;
384 + }
385 +
386 + if (!$booking) {
387 + return false;
388 + }
389 +
390 + $bookingUserId = (int) ($booking->user_id ?? 0);
391 + $currentUserId = (int) get_current_user_id();
392 +
393 + if ($bookingUserId > 0) {
394 + return $currentUserId === $bookingUserId;
395 + }
396 +
397 + // Guest booking: accept a matching short-lived booking-session token.
398 + $token = (isset($_GET['booking_token']) && is_string($_GET['booking_token']))
399 + ? sanitize_text_field((string) wp_unslash($_GET['booking_token']))
400 + : '';
401 + if ($token !== '') {
402 + $session = get_transient($token);
403 + if (is_array($session) && (int) ($session['booking_id'] ?? 0) === $bookingId) {
404 + return true;
405 + }
406 + }
407 +
408 + return false;
351 409 }
352 410
353 411 /**
354 412 * Check download permission based on visibility