getRouteBookingId($request); if ($request->method() == 'GET') { if (PermissionManager::userCan(['manage_own_calendar','read_all_bookings'])) { return true; } if ($bookingId) { $booking = Booking::find($bookingId); return $this->hasBookingAccess($booking); } } if ($bookingId) { $booking = Booking::find($bookingId); return $this->hasBookingAccess($booking); } return false; } public function getGroupAttendees(Request $request) { if (current_user_can('manage_options')) { return true; } if (PermissionManager::userCanSeeAllBookings()) { return true; } $groupId = $this->getRouteParam($request, 'group_id'); if (!$groupId) { return false; } $booking = Booking::where('group_id', $groupId)->first(); return $this->hasBookingAccess($booking); } public function getBookingActivities(Request $request) { return $this->authorizeBookingAccess($request); } public function getBookingMetaInfo(Request $request) { return $this->authorizeBookingAccess($request); } public function getCrmContact(Request $request) { return $this->authorizeBookingAccess($request); } public function getCrmOptions(Request $request) { return $this->authorizeBookingAccess($request); } // State-changing: use verifyRequest so read-only roles (read_all_bookings) // cannot mutate CRM data; only host access or manage_all_* passes for POST. public function updateCrmTags(Request $request) { return $this->verifyRequest($request); } public function updateCrmLists(Request $request) { return $this->verifyRequest($request); } private function authorizeBookingAccess(Request $request) { if (PermissionManager::userCan(['manage_all_bookings', 'manage_all_data', 'read_all_bookings'])) { return true; } $bookingId = $this->getRouteBookingId($request); if (!$bookingId) { return false; } $booking = Booking::find($bookingId); return $this->hasBookingAccess($booking); } /** * Resolve the booking ID from the URL route parameter only. * * Why: merged request inputs let JSON body values shadow URL params, * which previously allowed authorizing against an attacker-owned ID * while the controller acted on the URL-targeted victim ID. */ private function getRouteBookingId(Request $request) { return $this->getRouteParam($request, 'id'); } /** * Read a URL-only route parameter safely. Routes such as /schedules/ * and /schedules/export have no path placeholders, so a direct * access would emit an undefined-array-key warning under PHP 8. */ private function getRouteParam(Request $request, $key) { $params = (array) $request->get_url_params(); return isset($params[$key]) ? $params[$key] : null; } private function hasBookingAccess($booking) { if (!$booking) { return false; } $userId = get_current_user_id(); if (in_array($userId, $booking->getHostIds())) { return true; } if (!PermissionManager::userCan('manage_own_calendar')) { return false; } $calendarEvent = CalendarSlot::find($booking->event_id); if (!$calendarEvent) { return false; } return in_array($userId, $calendarEvent->getHostIds()); } }