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 -2 3.0.2.8 → 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
@@ -350,8 +368,48 @@
350 368 ], 200);
351 369 }
352 370
353 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;
409 + }
410 +
411 + /**
354 412 * Check download permission based on visibility
355 413 */
356 414 public function check_download_permission(WP_REST_Request $request): bool
357 415 {
@@ -508,9 +566,8 @@
508 566 $attachmentId = (int) $download->attachment_id;
509 567 }
510 568
511 569 if ($attachmentId <= 0) {
512 - error_log("ensureProtectedFile: No attachment_id found for download " . ($download->id ?? 'unknown'));
513 570 return '';
514 571 }
515 572
516 573 $source = get_attached_file($attachmentId);