-
Notifications
You must be signed in to change notification settings - Fork 135
Fix: prevent unauthorized manual check-ins in dashboard #1239
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: enext
Are you sure you want to change the base?
Conversation
Reviewer's GuideAdds strict authorization checks to the manual check-in and revert workflows by introducing a position validation helper that enforces event and product constraints, and bolsters test coverage to ensure unauthorized positions are correctly ignored. File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes - here's some feedback:
- Ensure that the manual check-in endpoint in production code actually calls
_validate_position_for_checkin_list(the PR currently only defines and uses it in tests). - Rather than silently skipping unauthorized positions, consider returning a clear error or logging the attempt so clients know when invalid IDs were provided.
- Optimize the product check by using a queryset filter/exists instead of loading
limit_products.all()into memory for membership testing.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Ensure that the manual check-in endpoint in production code actually calls `_validate_position_for_checkin_list` (the PR currently only defines and uses it in tests).
- Rather than silently skipping unauthorized positions, consider returning a clear error or logging the attempt so clients know when invalid IDs were provided.
- Optimize the product check by using a queryset filter/exists instead of loading `limit_products.all()` into memory for membership testing.
## Individual Comments
### Comment 1
<location> `src/tests/control/test_checkins.py:384-387` </location>
<code_context>
@pytest.mark.django_db
def test_manual_checkins_unauthorized_position(client, checkin_list_env):
client.login(email='dummy@dummy.dummy', password='dummy')
# Create a position from a different event that shouldn't be checkable
other_orga = Organizer.objects.create(name='Other', slug='other')
other_event = Event.objects.create(
organizer=other_orga,
name='Other Event',
slug='other',
date_from=now(),
)
other_item = Item.objects.create(event=other_event, name='Other Ticket', default_price=23, admission=True)
other_order = Order.objects.create(
code='OTHER',
event=other_event,
email='other@dummy.test',
status=Order.STATUS_PAID,
datetime=now(),
expires=now() + timedelta(days=10),
total=23,
locale='en',
)
other_position = OrderPosition.objects.create(
order=other_order,
item=other_item,
variation=None,
price=Decimal('23'),
attendee_name_parts={'full_name': 'Other'},
)
# Try to check in the unauthorized position
with scopes_disabled():
assert not _validate_position_for_checkin_list(other_position, checkin_list_env[6])
initial_checkin_count = Checkin.objects.count()
initial_log_count = LogEntry.objects.count()
client.post(
'/control/event/dummy/dummy/checkinlists/{}/'.format(checkin_list_env[6].pk),
{'checkin': [other_position.pk]},
)
# Verify the unauthorized position was not checked in
with scopes_disabled():
assert Checkin.objects.count() == initial_checkin_count
assert not other_position.checkins.exists()
# Verify no log entries were created for the unauthorized operation
assert LogEntry.objects.count() == initial_log_count
assert not LogEntry.objects.filter(
action_type='pretix.event.checkin',
object_id=other_position.order.pk
).exists()
</code_context>
<issue_to_address>
**issue (code-quality):** Replace call to format with f-string ([`use-fstring-for-formatting`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-formatting/))
</issue_to_address>
### Comment 2
<location> `src/tests/control/test_checkins.py:463-466` </location>
<code_context>
@pytest.mark.django_db
def test_manual_checkins_revert_unauthorized_position(client, checkin_list_env):
client.login(email='dummy@dummy.dummy', password='dummy')
# Create a position from a different event
other_orga = Organizer.objects.create(name='Other', slug='other')
other_event = Event.objects.create(
organizer=other_orga,
name='Other Event',
slug='other',
date_from=now(),
)
other_item = Item.objects.create(event=other_event, name='Other Ticket', default_price=23, admission=True)
other_order = Order.objects.create(
code='OTHER',
event=other_event,
email='other@dummy.test',
status=Order.STATUS_PAID,
datetime=now(),
expires=now() + timedelta(days=10),
total=23,
locale='en',
)
other_position = OrderPosition.objects.create(
order=other_order,
item=other_item,
variation=None,
price=Decimal('23'),
attendee_name_parts={'full_name': 'Other'},
)
# Create a checkin for the unauthorized position (simulating it was somehow created)
with scopes_disabled():
Checkin.objects.create(position=other_position, list=checkin_list_env[6])
initial_checkin_count = Checkin.objects.count()
initial_log_count = LogEntry.objects.count()
# Try to revert the unauthorized position
client.post(
'/control/event/dummy/dummy/checkinlists/{}/'.format(checkin_list_env[6].pk),
{'checkin': [other_position.pk], 'revert': 'true'},
)
# Verify the unauthorized position checkin was not reverted
with scopes_disabled():
assert Checkin.objects.count() == initial_checkin_count
assert other_position.checkins.exists()
# Verify no log entries were created for the unauthorized operation
assert LogEntry.objects.count() == initial_log_count
assert not LogEntry.objects.filter(
action_type='pretix.event.checkin.reverted',
object_id=other_position.order.pk
).exists()
</code_context>
<issue_to_address>
**issue (code-quality):** Replace call to format with f-string ([`use-fstring-for-formatting`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-formatting/))
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
33fb0ac to
81aac77
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR addresses a security vulnerability that allows users to check in OrderPositions for events they shouldn't have access to by adding validation to the manual check-in endpoint. The changes add comprehensive test coverage for the new authorization validation logic.
Key Changes:
- Added two new test cases to verify unauthorized check-ins are properly rejected (
test_manual_checkins_unauthorized_positionandtest_manual_checkins_revert_unauthorized_position) - Imported
validate_position_for_checkin_listfunction for testing authorization logic - Enhanced existing tests with docstrings and improved code style (f-strings, better comments)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
mariobehling
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please check AI comments in regards to pretix.base.services.checkin.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
validate_position_for_checkin_list is not present in the current codebase. This will cause CI failure. Either add the function or import _validate_position_for_checkin_list instead. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| is_valid, error_msg = validate_position_for_checkin_list(other_position, checkin_list_env[6]) | ||
| assert not is_valid |
Copilot
AI
Nov 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test calls validate_position_for_checkin_list() but this function is not included in the PR. Without the actual implementation of this validation function, these tests will fail with an import error. The implementation needs to be added to the PR.
| # Create a position from a different event | ||
| other_orga = Organizer.objects.create(name='Other', slug='other') | ||
| other_event = Event.objects.create( | ||
| organizer=other_orga, | ||
| name='Other Event', | ||
| slug='other', | ||
| date_from=now(), | ||
| ) | ||
| other_item = Item.objects.create(event=other_event, name='Other Ticket', default_price=23, admission=True) | ||
| other_order = Order.objects.create( | ||
| code='OTHER', | ||
| event=other_event, | ||
| email='other@dummy.test', | ||
| status=Order.STATUS_PAID, | ||
| datetime=now(), | ||
| expires=now() + timedelta(days=10), | ||
| total=23, | ||
| locale='en', | ||
| ) | ||
| other_position = OrderPosition.objects.create( | ||
| order=other_order, | ||
| item=other_item, | ||
| variation=None, | ||
| price=Decimal('23'), | ||
| attendee_name_parts={'full_name': 'Other'}, | ||
| ) |
Copilot
AI
Nov 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] There is significant code duplication between this test and test_manual_checkins_unauthorized_position (lines 357-381). The setup of the "other" event, organizer, item, order, and position is identical. Consider extracting this setup into a pytest fixture to improve maintainability and reduce duplication.
closes #1232
Description
This patch adds validation for the manual check-in endpoint to ensure that only authorized OrderPositions can be checked in.
Currently, users can POST arbitrary OrderPosition IDs, which allows checking in attendees for events they shouldn't have access to. This introduces a security risk.
Changes
_validate_position_for_checkin_listto verify:checkin_list.all_products=False, the item must be incheckin_list.limit_products.Impact
Related Tests
test_manual_checkinstest_manual_checkins_unauthorized_positiontest_manual_checkins_reverttest_manual_checkins_revert_unauthorized_positionSummary by Sourcery
Enforce proper authorization on manual check-in and revert actions by validating positions against the check-in list’s event and product constraints, and cover these behaviors with new tests.
Bug Fixes:
Enhancements:
Tests: