Template permission fix (#7391)

* Update API permissions for report templates

- Allow reading for any authenticated user
- Write permissions for staff users

* Update unit tests
This commit is contained in:
Oliver 2024-06-03 12:05:09 +10:00 committed by GitHub
parent cdac7465b2
commit 7108bc48bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 87 additions and 11 deletions

View File

@ -142,7 +142,15 @@ class UserMixin:
def setUp(self):
"""Run setup for individual test methods."""
if self.auto_login:
self.client.login(username=self.username, password=self.password)
self.login()
def login(self):
"""Login with the current user credentials."""
self.client.login(username=self.username, password=self.password)
def logout(self):
"""Lougout current user."""
self.client.logout()
@classmethod
def assignRole(cls, role=None, assign_all: bool = False, group=None):

View File

@ -18,6 +18,7 @@ from rest_framework.response import Response
import common.models
import InvenTree.exceptions
import InvenTree.helpers
import InvenTree.permissions
import report.helpers
import report.models
import report.serializers
@ -34,6 +35,16 @@ from plugin.builtin.labels.inventree_label import InvenTreeLabelPlugin
from plugin.registry import registry
class TemplatePermissionMixin:
"""Permission mixin for report and label templates."""
# Read only for non-staff users
permission_classes = [
permissions.IsAuthenticated,
InvenTree.permissions.IsStaffOrReadOnly,
]
@method_decorator(cache_page(5), name='dispatch')
class TemplatePrintBase(RetrieveAPI):
"""Base class for printing against templates."""
@ -143,6 +154,7 @@ class LabelFilter(ReportFilterBase):
class LabelPrint(GenericAPIView):
"""API endpoint for printing labels."""
# Any authenticated user can print labels
permission_classes = [permissions.IsAuthenticated]
serializer_class = report.serializers.LabelPrintSerializer
@ -277,7 +289,7 @@ class LabelPrint(GenericAPIView):
)
class LabelTemplateList(ListCreateAPI):
class LabelTemplateList(TemplatePermissionMixin, ListCreateAPI):
"""API endpoint for viewing list of LabelTemplate objects."""
queryset = report.models.LabelTemplate.objects.all()
@ -288,7 +300,7 @@ class LabelTemplateList(ListCreateAPI):
ordering_fields = ['name', 'enabled']
class LabelTemplateDetail(RetrieveUpdateDestroyAPI):
class LabelTemplateDetail(TemplatePermissionMixin, RetrieveUpdateDestroyAPI):
"""Detail API endpoint for label template model."""
queryset = report.models.LabelTemplate.objects.all()
@ -298,6 +310,7 @@ class LabelTemplateDetail(RetrieveUpdateDestroyAPI):
class ReportPrint(GenericAPIView):
"""API endpoint for printing reports."""
# Any authenticated user can print reports
permission_classes = [permissions.IsAuthenticated]
serializer_class = report.serializers.ReportPrintSerializer
@ -434,7 +447,7 @@ class ReportPrint(GenericAPIView):
)
class ReportTemplateList(ListCreateAPI):
class ReportTemplateList(TemplatePermissionMixin, ListCreateAPI):
"""API endpoint for viewing list of ReportTemplate objects."""
queryset = report.models.ReportTemplate.objects.all()
@ -445,49 +458,49 @@ class ReportTemplateList(ListCreateAPI):
ordering_fields = ['name', 'enabled']
class ReportTemplateDetail(RetrieveUpdateDestroyAPI):
class ReportTemplateDetail(TemplatePermissionMixin, RetrieveUpdateDestroyAPI):
"""Detail API endpoint for report template model."""
queryset = report.models.ReportTemplate.objects.all()
serializer_class = report.serializers.ReportTemplateSerializer
class ReportSnippetList(ListCreateAPI):
class ReportSnippetList(TemplatePermissionMixin, ListCreateAPI):
"""API endpoint for listing ReportSnippet objects."""
queryset = report.models.ReportSnippet.objects.all()
serializer_class = report.serializers.ReportSnippetSerializer
class ReportSnippetDetail(RetrieveUpdateDestroyAPI):
class ReportSnippetDetail(TemplatePermissionMixin, RetrieveUpdateDestroyAPI):
"""API endpoint for a single ReportSnippet object."""
queryset = report.models.ReportSnippet.objects.all()
serializer_class = report.serializers.ReportSnippetSerializer
class ReportAssetList(ListCreateAPI):
class ReportAssetList(TemplatePermissionMixin, ListCreateAPI):
"""API endpoint for listing ReportAsset objects."""
queryset = report.models.ReportAsset.objects.all()
serializer_class = report.serializers.ReportAssetSerializer
class ReportAssetDetail(RetrieveUpdateDestroyAPI):
class ReportAssetDetail(TemplatePermissionMixin, RetrieveUpdateDestroyAPI):
"""API endpoint for a single ReportAsset object."""
queryset = report.models.ReportAsset.objects.all()
serializer_class = report.serializers.ReportAssetSerializer
class LabelOutputList(BulkDeleteMixin, ListAPI):
class LabelOutputList(TemplatePermissionMixin, BulkDeleteMixin, ListAPI):
"""List endpoint for LabelOutput objects."""
queryset = report.models.LabelOutput.objects.all()
serializer_class = report.serializers.LabelOutputSerializer
class ReportOutputList(BulkDeleteMixin, ListAPI):
class ReportOutputList(TemplatePermissionMixin, BulkDeleteMixin, ListAPI):
"""List endpoint for ReportOutput objects."""
queryset = report.models.ReportOutput.objects.all()

View File

@ -403,6 +403,61 @@ class ReportTest(InvenTreeAPITestCase):
self.assertEqual(len(p.metadata.keys()), 4)
def test_report_template_permissions(self):
"""Test that the user permissions are correctly applied.
- For all /api/report/ endpoints, any authenticated user should have full read access
- Write access is limited to staff users
- Non authenticated users should have no access at all
"""
# First test the "report list" endpoint
url = reverse('api-report-template-list')
template = ReportTemplate.objects.first()
detail_url = reverse('api-report-template-detail', kwargs={'pk': template.pk})
# Non-authenticated user should have no access
self.logout()
self.get(url, expected_code=401)
# Authenticated user should have read access
self.user.is_staff = False
self.user.save()
self.login()
# Check read access to template list URL
self.get(url, expected_code=200)
# Check read access to template detail URL
self.get(detail_url, expected_code=200)
# An update to the report template should fail
self.patch(
detail_url,
data={'description': 'Some new description here?'},
expected_code=403,
)
# Now, test with a staff user
self.logout()
self.user.is_staff = True
self.user.save()
self.login()
self.patch(
detail_url,
data={'description': 'An updated description'},
expected_code=200,
)
template.refresh_from_db()
self.assertEqual(template.description, 'An updated description')
class PrintTestMixins:
"""Mixin that enables e2e printing tests."""