mirror of
https://github.com/pretix/pretix.git
synced 2026-05-05 15:14:04 +00:00
Add option to restrict anonymous access to order URLs (#4735)
* Add option to restrict anonymous access to order URLs By default, users who place orders while logged in can still access their order URLs without authentication. This raises potential security risks, particularly if order confirmation emails are forwarded. This commit introduces an organiser-level setting to disable anonymous access for such orders. When enabled, unauthenticated attempts to access URLs starting with `/order/`, which are intended for the customer, are redirected to the login page. Upon successful authentication, the user is redirected back to the original order URL. It is important to note that this change does not impact routes intended for attendees (e.g., `/ticket/*`), which remain accessible without authentication. * Change name of setting for future clarity Co-authored-by: Raphael Michel <mail@raphaelmichel.de> * Update message wording Co-authored-by: Raphael Michel <mail@raphaelmichel.de> * Eliminate database query Co-authored-by: Raphael Michel <mail@raphaelmichel.de> * Rename feature flag to fix breaking tests * Refactor order access verification code into `OrderDetailsMixin` * Add test for logged-in customer accessing another customer's order * Refactor order access conditions to remove nesting * Handle case where customer is not yet verified * Add additional information to help message * Fix multidomain issue Co-authored-by: Raphael Michel <mail@raphaelmichel.de> * Merge order/position variants into single tests * Add docstring explaining return type of `order` property * Apply suggestion from @raphaelm * Fix indentation --------- Co-authored-by: Raphael Michel <mail@raphaelmichel.de> Co-authored-by: Raphael Michel <michel@rami.io>
This commit is contained in:
@@ -2611,3 +2611,11 @@ def test_approve_cancellation_request(client, env):
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
assert doc.select('input[name=refund-new-giftcard]')[0]['value'] == '10.00'
|
||||
assert not env[2].cancellation_requests.exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_view_as_user(client, env):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
response = client.get('/%s/%s/order/%s/%s/' % (env[0].organizer.slug, env[0].slug, env[2].code, env[2].secret))
|
||||
assert response.status_code == 200
|
||||
assert env[2].code in response.content.decode()
|
||||
|
||||
@@ -1738,3 +1738,222 @@ class OrdersTest(BaseOrdersTest):
|
||||
self.order.secret, a.pk, match.group(1))
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_require_login_for_order_access_disabled_unauth(self):
|
||||
self.orga.settings.customer_accounts = True
|
||||
|
||||
with scopes_disabled():
|
||||
customer = self.orga.customers.create(email='john@example.org', is_verified=True)
|
||||
customer.set_password("foo")
|
||||
customer.save()
|
||||
|
||||
self.order.customer = customer
|
||||
self.order.save()
|
||||
|
||||
self.orga.settings.customer_accounts_require_login_for_order_access = False
|
||||
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret)
|
||||
)
|
||||
assert response.status_code == 200
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
assert len(doc.select(".cart-row")) > 0
|
||||
assert "pending" in doc.select(".order-details")[0].text.lower()
|
||||
assert "Peter" in response.content.decode()
|
||||
assert "Lukas" not in response.content.decode()
|
||||
|
||||
response = self.client.get(
|
||||
'/%s/%s/ticket/%s/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
|
||||
self.ticket_pos.positionid, self.ticket_pos.web_secret)
|
||||
)
|
||||
assert response.status_code == 200
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
assert len(doc.select(".cart-row")) > 0
|
||||
assert "pending" in doc.select(".order-details")[0].text.lower()
|
||||
assert "Peter" in response.content.decode()
|
||||
assert "Lukas" not in response.content.decode()
|
||||
|
||||
def test_require_login_for_order_access_enabled_unauth(self):
|
||||
self.orga.settings.customer_accounts = True
|
||||
|
||||
with scopes_disabled():
|
||||
customer = self.orga.customers.create(email='john@example.org', is_verified=True)
|
||||
customer.set_password("foo")
|
||||
customer.save()
|
||||
|
||||
self.order.customer = customer
|
||||
self.order.save()
|
||||
|
||||
self.orga.settings.customer_accounts_require_login_for_order_access = True
|
||||
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret)
|
||||
)
|
||||
assert response.status_code == 302
|
||||
|
||||
response = self.client.get(
|
||||
'/%s/%s/ticket/%s/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
|
||||
self.ticket_pos.positionid, self.ticket_pos.web_secret)
|
||||
)
|
||||
assert response.status_code == 200
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
assert len(doc.select(".cart-row")) > 0
|
||||
assert "pending" in doc.select(".order-details")[0].text.lower()
|
||||
assert "Peter" in response.content.decode()
|
||||
assert "Lukas" not in response.content.decode()
|
||||
|
||||
def test_require_login_for_order_access_disabled_auth(self):
|
||||
self.orga.settings.customer_accounts = True
|
||||
|
||||
with scopes_disabled():
|
||||
customer = self.orga.customers.create(email='john@example.org', is_verified=True)
|
||||
customer.set_password("foo")
|
||||
customer.save()
|
||||
|
||||
self.order.customer = customer
|
||||
self.order.save()
|
||||
|
||||
self.orga.settings.customer_accounts_require_login_for_order_access = False
|
||||
|
||||
response = self.client.post('/%s/account/login' % (self.orga.slug), {
|
||||
'email': 'john@example.org',
|
||||
'password': 'foo',
|
||||
})
|
||||
assert response.status_code == 302
|
||||
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret)
|
||||
)
|
||||
assert response.status_code == 200
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
assert len(doc.select(".cart-row")) > 0
|
||||
assert "pending" in doc.select(".order-details")[0].text.lower()
|
||||
assert "Peter" in response.content.decode()
|
||||
assert "Lukas" not in response.content.decode()
|
||||
|
||||
response = self.client.get(
|
||||
'/%s/%s/ticket/%s/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
|
||||
self.ticket_pos.positionid, self.ticket_pos.web_secret)
|
||||
)
|
||||
assert response.status_code == 200
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
assert len(doc.select(".cart-row")) > 0
|
||||
assert "pending" in doc.select(".order-details")[0].text.lower()
|
||||
assert "Peter" in response.content.decode()
|
||||
assert "Lukas" not in response.content.decode()
|
||||
|
||||
def test_require_login_for_order_access_enabled_auth(self):
|
||||
self.orga.settings.customer_accounts = True
|
||||
|
||||
with scopes_disabled():
|
||||
customer = self.orga.customers.create(email='john@example.org', is_verified=True)
|
||||
customer.set_password("foo")
|
||||
customer.save()
|
||||
|
||||
self.order.customer = customer
|
||||
self.order.save()
|
||||
|
||||
self.orga.settings.customer_accounts_require_login_for_order_access = True
|
||||
|
||||
response = self.client.post('/%s/account/login' % (self.orga.slug), {
|
||||
'email': 'john@example.org',
|
||||
'password': 'foo',
|
||||
})
|
||||
assert response.status_code == 302
|
||||
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret)
|
||||
)
|
||||
assert response.status_code == 200
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
assert len(doc.select(".cart-row")) > 0
|
||||
assert "pending" in doc.select(".order-details")[0].text.lower()
|
||||
assert "Peter" in response.content.decode()
|
||||
assert "Lukas" not in response.content.decode()
|
||||
|
||||
response = self.client.get(
|
||||
'/%s/%s/ticket/%s/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
|
||||
self.ticket_pos.positionid, self.ticket_pos.web_secret)
|
||||
)
|
||||
assert response.status_code == 200
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
assert len(doc.select(".cart-row")) > 0
|
||||
assert "pending" in doc.select(".order-details")[0].text.lower()
|
||||
assert "Peter" in response.content.decode()
|
||||
assert "Lukas" not in response.content.decode()
|
||||
|
||||
def test_require_login_for_order_access_accounts_disabled(self):
|
||||
self.orga.settings.customer_accounts = True
|
||||
|
||||
with scopes_disabled():
|
||||
customer = self.orga.customers.create(email='john@example.org', is_verified=True)
|
||||
customer.set_password("foo")
|
||||
customer.save()
|
||||
|
||||
self.order.customer = customer
|
||||
self.order.save()
|
||||
|
||||
self.orga.settings.customer_accounts = False
|
||||
self.orga.settings.customer_accounts_require_login_for_order_access = True
|
||||
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret)
|
||||
)
|
||||
assert response.status_code == 200
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
assert len(doc.select(".cart-row")) > 0
|
||||
assert "pending" in doc.select(".order-details")[0].text.lower()
|
||||
assert "Peter" in response.content.decode()
|
||||
assert "Lukas" not in response.content.decode()
|
||||
|
||||
def test_require_login_for_order_access_enabled_wrong_customer(self):
|
||||
self.orga.settings.customer_accounts = True
|
||||
|
||||
with scopes_disabled():
|
||||
customer = self.orga.customers.create(email='john@example.org', is_verified=True)
|
||||
customer.set_password("foo")
|
||||
customer.save()
|
||||
|
||||
self.order.customer = customer
|
||||
self.order.save()
|
||||
|
||||
with scopes_disabled():
|
||||
customer = self.orga.customers.create(email='jill@example.org', is_verified=True)
|
||||
customer.set_password("bar")
|
||||
customer.save()
|
||||
|
||||
self.orga.settings.customer_accounts_require_login_for_order_access = True
|
||||
|
||||
response = self.client.post('/%s/account/login' % (self.orga.slug), {
|
||||
'email': 'jill@example.org',
|
||||
'password': 'bar',
|
||||
})
|
||||
assert response.status_code == 302
|
||||
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret)
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_require_login_for_order_access_enabled_unverified_account(self):
|
||||
self.orga.settings.customer_accounts = True
|
||||
|
||||
with scopes_disabled():
|
||||
customer = self.orga.customers.create(email='john@example.org', is_verified=False)
|
||||
customer.set_password("foo")
|
||||
customer.save()
|
||||
|
||||
self.order.customer = customer
|
||||
self.order.save()
|
||||
|
||||
self.orga.settings.customer_accounts_require_login_for_order_access = True
|
||||
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret)
|
||||
)
|
||||
assert response.status_code == 200
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
assert len(doc.select(".cart-row")) > 0
|
||||
assert "pending" in doc.select(".order-details")[0].text.lower()
|
||||
assert "Peter" in response.content.decode()
|
||||
assert "Lukas" not in response.content.decode()
|
||||
|
||||
Reference in New Issue
Block a user