Uh oh!
There was an error while loading. Please reload this page.
fix(permissions): scope tickets and bookings by attendee, not row creator - #358
Conversation
…ator My Tickets showed nothing to the people the tickets belong to. Two layers decided "mine" by `owner` — whoever created the row — so a ticket created by the booker, or a guest-checkout booking created as Administrator, was invisible to the attendee and to the buyer. `derived_query_conditions` and `derived_has_permission` now also match the column that names the person: `Event Ticket.attendee_email`, `Event Booking.user`, plus tickets belonging to a booking the user made. `if_owner` had to come off the Buzz User read permission on both doctypes — Frappe ANDs it onto every query, so no attendee clause could ever have passed. Removing `if_owner` moves the whole doc-level decision to the hook, where `has_team_access` waved unstamped rows through on the assumption that role permissions still gated them. That would have opened anyone's ticket on a team-less event, so these two doctypes no longer get that pass. `TicketService.details` asked its own attendee-only question and would have rejected the rows the list now shows; it asks the read permission instead. `booking_owned_by_user` compared `owner`, so a guest-checkout buyer never got their own booking back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Greptile SummaryThis PR scopes ticket and booking access by attendee and buyer identity rather than only by row creator, while preserving team-based access.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; the previously reported booking-deletion path is closed by removing the Buzz User role-level delete grant, with regression coverage confirming standard document deletion is denied.
|
| Filename | Overview |
|---|---|
| buzz/permissions.py | Adds attendee and buyer identity scoping, booking-derived ticket access, and protection for personal records attached to team-less events. |
| buzz/ticketing/doctype/event_booking/event_booking.json | Removes owner-only gating and Buzz User deletion rights while retaining buyer read/write capabilities enforced by permission hooks. |
| buzz/ticketing/doctype/event_ticket/event_ticket.json | Removes owner-only read gating so the document permission hooks can authorize attendees and bookers. |
| buzz/api/tickets/services.py | Uses document read permission for ticket details and recognizes the booking user as the buyer of record. |
| buzz/test_permissions.py | Covers attendee, booker, unrelated-user, team-less-event, write, and deletion permission behavior. |
| buzz/api/tickets/test_tickets.py | Verifies that a booker can open a ticket held by another attendee. |
| dashboard/src/pages/TicketsList.vue | Relies on server-side permission conditions rather than an attendee-only client filter. |
Reviews (2): Last reviewed commit: "fix(permissions): drop delete from the B..." | Re-trigger Greptile
| if doc.doctype in OWNER_VISIBLE_DOCTYPES and belongs_to_user(doc, user): | ||
| return True |
There was a problem hiding this comment.
Buyer identity grants deletion
When a Buzz User is the user of a draft booking owned by someone else, this permission hook approves delete as well as read and write. Because Event Booking grants Buzz User role-level deletion, a guest-checkout buyer can delete the booking and its attendee and other child records through Frappe's standard document API.
Knowledge Base Used:
Prompt To Fix With AI
This is a comment left during a code review.
Path: buzz/permissions.py
Line: 181-182
Comment:
**Buyer identity grants deletion**
When a Buzz User is the `user` of a draft booking owned by someone else, this permission hook approves `delete` as well as read and write. Because Event Booking grants Buzz User role-level deletion, a guest-checkout buyer can delete the booking and its attendee and other child records through Frappe's standard document API.
**Knowledge Base Used:**-[Ticketing domain](https://app.greptile.com/bwh-tech/-/custom-context/knowledge-base/bwhtech/buzz/-/docs/ticketing-domain.md)-[Frappe Buzz platform operations](https://app.greptile.com/bwh-tech/-/custom-context/knowledge-base/bwhtech/buzz/-/docs/platform-operations.md)---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
fixed in 7eb4eed
removed the "delete" perm for a buzz user on Event Booking
Bookings are cancelled, never deleted, and no code path outside tests deletes one. The role carried `delete` anyway, which `if_owner` used to narrow to the creator; with that gone a guest-checkout buyer could delete their own draft booking and its child rows through the standard document API. Removing the grant closes it for everyone on the portal, including the buyer's own self-made draft, which was deletable before this branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
Backport failed for Please cherry-pick the changes locally and resolve any conflicts. git fetch origin main
git worktree add -d .worktree/backport-358-to-main origin/main
cd .worktree/backport-358-to-main
git switch --create backport-358-to-main
git cherry-pick -x a246bba2d4be49d7b2d775ccbfcbd8f3776567e3 |
…ator (#359) Backport of #358. `develop` carries the fix inside the team permission hooks (`buzz/permissions.py`), which `main` does not have, so the cherry-pick could not apply. This reimplements the same rule against `main`'s permission model. My Tickets showed nothing to the people the tickets belong to. `if_owner` on the Buzz User read permission decided "mine" by `owner` — whoever created the row — so a ticket created by the booker, or a guest-checkout booking created as Administrator, was invisible to the attendee and to the buyer. `if_owner` comes off both doctypes and the decision moves to a pair of hooks in `buzz.permissions`, which match the column that names the person: `Event Ticket.attendee_email`, `Event Booking.user`, plus tickets belonging to a booking the user made. System Manager and Event Manager still read every row. `TicketService.details` asked its own attendee-only question and would have rejected the rows the list now shows; it asks the read permission instead. `booking_owned_by_user` compared `owner`, so a guest-checkout buyer never got their own booking back. Delete also comes off the Buzz User role on Event Booking. Bookings are cancelled, never deleted; with `if_owner` gone the grant would have let a buyer delete their own draft booking and its child rows through the document API. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
/b/account/ticketsshowed nothing to the people the tickets belong to. Reproduced with a plain Buzz User:Two layers decided "mine" by
owner— whoever created the row. A ticket created by the booker, or a guest-checkout booking created as Administrator, belonged to nobody who could see it. It looks fine if you test as a System Manager, which is why it survived this long.What changed
derived_query_conditions/derived_has_permissionnow also match the column that names the person:Event Ticket.attendee_email,Event Booking.user, plus tickets under a booking the user made.if_ownercomes off the Buzz User read permission on both doctypes — Frappe ANDs it onto every query, so no attendee clause could ever have passed while it was there.TicketService.detailsasked its own attendee-only question and would have rejected the rows the list now shows; it asks the read permission instead, which also drops the!= "Administrator"special case.booking_owned_by_usercomparedowner, so a guest-checkout buyer never got their own booking back.The gotcha
Dropping
if_ownermoves the entire doc-level decision to the hook —get_role_permissionszeroes every ptype for a non-owner whenif_owneris the only grant, so it was a second gate, not decoration. On the other side of the hook,has_team_accesswaves an unstamped row through on the comment "role permissions still gate it". True until now. Without a guard, any Buzz User could read anyone's ticket on a team-less event. Verified by deleting the guard and watchingtest_an_unstamped_event_does_not_open_its_tickets_to_a_strangerfail with "PermissionError not raised".Access, measured
Against 243 tickets, a
Buzz Userunrelated to any of them lists 0, reads none, and getsPermissionErrorfromfrappe.client.geton a known id. Attacker-suppliedor_filterscan only narrow. Attendee sees theirs, booker sees the ones they bought for others, the event's team sees all of its own — that last one is pre-existing and needed for check-in.Not changed
may_transferalready had the right rule.BookingsList.vuekeeps itsuserfilter.Sponsorship EnquiryandEvent Talkstay owner-only.Needs
bench migrate— the permission change is in the doctype JSONs.🤖 Generated with Claude Code