Permissions fix for Calendar views (#12764) (#12765)

(cherry picked from commit 1238daa783)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
This commit is contained in:
github-actions[bot]
2026-09-02 16:00:42 +10:00
committed by GitHub
co-authored by Oliver
parent 08ae4aba07
commit 2ddc615ca2
2 changed files with 91 additions and 0 deletions
+31
View File
@@ -64,6 +64,7 @@ from order.status_codes import (
)
from part.models import Part
from users.models import Owner
from users.permissions import check_user_permission
class GeneralExtraLineListOutputOptions(OutputConfiguration):
@@ -2386,6 +2387,32 @@ class OrderCalendarExport(ICalFeed):
timezone = settings.TIME_ZONE
file_name = 'calendar.ics'
# Map the URL 'ordertype' kwarg to the corresponding order model,
# so that access can be checked against the matching RuleSet
ORDER_MODELS = {
'purchase-order': models.PurchaseOrder,
'sales-order': models.SalesOrder,
'return-order': models.ReturnOrder,
'transfer-order': models.TransferOrder,
}
def check_permission(self, request, **kwargs):
"""Check that the requesting user has 'view' permission for the requested order type.
Returns a 403 JsonResponse if the user lacks the required RuleSet permission,
or None if access is permitted.
"""
model = self.ORDER_MODELS.get(kwargs.get('ordertype'))
if model is not None and not check_user_permission(request.user, model, 'view'):
response = JsonResponse({
'detail': 'You do not have permission to view this resource.'
})
response.status_code = 403
return response
return None
def __call__(self, request, *args, **kwargs):
"""Overload call in order to check for authentication.
@@ -2402,6 +2429,8 @@ class OrderCalendarExport(ICalFeed):
if request.user.is_authenticated:
# Authenticated on first try - maybe normal browser call?
if forbidden := self.check_permission(request, **kwargs):
return forbidden
return super().__call__(request, *args, **kwargs)
# No login yet - check in headers
@@ -2420,6 +2449,8 @@ class OrderCalendarExport(ICalFeed):
# Check again
if request.user.is_authenticated:
# Authenticated after second try
if forbidden := self.check_permission(request, **kwargs):
return forbidden
return super().__call__(request, *args, **kwargs)
# Still nothing - return Unauth. header with info on how to authenticate
+60
View File
@@ -853,6 +853,21 @@ class PurchaseOrderTest(OrderTest):
)
self.assertEqual(response.status_code, 200)
def test_po_calendar_no_permission(self):
"""Test that an authenticated user without purchase_order view permission is denied."""
self.clearRoles()
response = self.get(
reverse('api-po-so-calendar', kwargs={'ordertype': 'purchase-order'}),
expected_code=403,
format=None,
)
resp_dict = response.json()
self.assertEqual(
resp_dict['detail'], 'You do not have permission to view this resource.'
)
def test_po_custom_status_query_count(self):
"""Test that listing PurchaseOrders with custom statuses does not cause N+1 queries.
@@ -2111,6 +2126,21 @@ class SalesOrderTest(OrderTest):
self.assertGreaterEqual(n_events, 1)
self.assertEqual(number_orders_incl_complete, n_events)
def test_so_calendar_no_permission(self):
"""Test that an authenticated user without sales_order view permission is denied."""
self.clearRoles()
response = self.get(
reverse('api-po-so-calendar', kwargs={'ordertype': 'sales-order'}),
expected_code=403,
format=None,
)
resp_dict = response.json()
self.assertEqual(
resp_dict['detail'], 'You do not have permission to view this resource.'
)
def test_export(self):
"""Test we can export the SalesOrder list."""
set_global_setting(models.SalesOrder.UNLOCK_SETTING, True)
@@ -3564,6 +3594,21 @@ class ReturnOrderTests(InvenTreeAPITestCase):
calendar = Calendar.from_ical(response.content)
self.assertIsInstance(calendar, Calendar)
def test_ro_calendar_no_permission(self):
"""Test that an authenticated user without return_order view permission is denied."""
self.clearRoles()
response = self.get(
reverse('api-po-so-calendar', kwargs={'ordertype': 'return-order'}),
expected_code=403,
format=None,
)
resp_dict = response.json()
self.assertEqual(
resp_dict['detail'], 'You do not have permission to view this resource.'
)
def test_export(self):
"""Test data export for the ReturnOrder API endpoints."""
# Export return orders
@@ -4114,6 +4159,21 @@ class TransferOrderTest(OrderTest):
self.assertGreaterEqual(n_events, 1)
self.assertEqual(number_orders_incl_complete, n_events)
def test_transfer_order_calendar_no_permission(self):
"""Test that an authenticated user without transfer_order view permission is denied."""
self.clearRoles()
response = self.get(
reverse('api-po-so-calendar', kwargs={'ordertype': 'transfer-order'}),
expected_code=403,
format=None,
)
resp_dict = response.json()
self.assertEqual(
resp_dict['detail'], 'You do not have permission to view this resource.'
)
def test_export(self):
"""Test we can export the TransferOrder list."""
n = models.TransferOrder.objects.count()