Skip to content

fix(permissions): scope tickets and bookings by attendee, not row creator - #358

Merged
harshtandiya merged 2 commits into
developfrom
fix/my-tickets
Aug 17, 2026
Merged

fix(permissions): scope tickets and bookings by attendee, not row creator#358
harshtandiya merged 2 commits into
developfrom
fix/my-tickets

Conversation

@harshtandiya

Copy link
Copy Markdown
Collaborator

/b/account/tickets showed nothing to the people the tickets belong to. Reproduced with a plain Buzz User:

paid_booking@as.free roles: ['Buzz User']
Event Tickets with that attendee_email: ubo2ev2ujd, fnr41nnkvd (owner = someone else)
frappe.get_list("Event Ticket", {"attendee_email": ...}) as that 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_permission now also match the column that names the person: Event Ticket.attendee_email, Event Booking.user, plus tickets under a booking the user made. if_owner comes 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.details asked 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_user compared owner, so a guest-checkout buyer never got their own booking back.

The gotcha

Dropping if_owner moves the entire doc-level decision to the hook — get_role_permissions zeroes every ptype for a non-owner when if_owner is the only grant, so it was a second gate, not decoration. On the other side of the hook, has_team_access waves 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 watching test_an_unstamped_event_does_not_open_its_tickets_to_a_stranger fail with "PermissionError not raised".

Access, measured

Against 243 tickets, a Buzz User unrelated to any of them lists 0, reads none, and gets PermissionError from frappe.client.get on a known id. Attacker-supplied or_filters can 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_transfer already had the right rule. BookingsList.vue keeps its user filter. Sponsorship Enquiry and Event Talk stay owner-only.

Needs bench migrate — the permission change is in the doctype JSONs.

🤖 Generated with Claude Code

…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>
@harshtandiyaharshtandiya added the backport main backport to main branch label Aug 17, 2026
@greptile-apps

greptile-appsBot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR scopes ticket and booking access by attendee and buyer identity rather than only by row creator, while preserving team-based access.

  • Extends query and document permission hooks for attendee- and buyer-owned records.
  • Aligns ticket details and ticket-list behavior with the expanded read rules.
  • Removes portal deletion rights from Event Booking and adds permission regression coverage.

Confidence Score: 5/5

The 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.

Important Files Changed

FilenameOverview
buzz/permissions.pyAdds 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.jsonRemoves 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.jsonRemoves owner-only read gating so the document permission hooks can authorize attendees and bookers.
buzz/api/tickets/services.pyUses document read permission for ticket details and recognizes the booking user as the buyer of record.
buzz/test_permissions.pyCovers attendee, booker, unrelated-user, team-less-event, write, and deletion permission behavior.
buzz/api/tickets/test_tickets.pyVerifies that a booker can open a ticket held by another attendee.
dashboard/src/pages/TicketsList.vueRelies 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

Comment threadbuzz/permissions.py
Comment on lines +181 to 182
if doc.doctype in OWNER_VISIBLE_DOCTYPES and belongs_to_user(doc, user):
return True

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1Buyer 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.

Fix in Claude CodeFix in Codex

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@harshtandiya
harshtandiya merged commit a246bba into developAug 17, 2026
8 checks passed
@harshtandiya
harshtandiya deleted the fix/my-tickets branch August 17, 2026 11:30
@github-actions

Copy link
Copy Markdown
Contributor

Backport failed for main, because it was unable to cherry-pick the commit(s).

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

harshtandiya added a commit that referenced this pull request Aug 17, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport mainbackport to main branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@harshtandiya