2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 11:36:44 +00:00

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): def setUp(self):
"""Run setup for individual test methods.""" """Run setup for individual test methods."""
if self.auto_login: 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 @classmethod
def assignRole(cls, role=None, assign_all: bool = False, group=None): 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 common.models
import InvenTree.exceptions import InvenTree.exceptions
import InvenTree.helpers import InvenTree.helpers
import InvenTree.permissions
import report.helpers import report.helpers
import report.models import report.models
import report.serializers import report.serializers
@ -34,6 +35,16 @@ from plugin.builtin.labels.inventree_label import InvenTreeLabelPlugin
from plugin.registry import registry 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') @method_decorator(cache_page(5), name='dispatch')
class TemplatePrintBase(RetrieveAPI): class TemplatePrintBase(RetrieveAPI):
"""Base class for printing against templates.""" """Base class for printing against templates."""
@ -143,6 +154,7 @@ class LabelFilter(ReportFilterBase):
class LabelPrint(GenericAPIView): class LabelPrint(GenericAPIView):
"""API endpoint for printing labels.""" """API endpoint for printing labels."""
# Any authenticated user can print labels
permission_classes = [permissions.IsAuthenticated] permission_classes = [permissions.IsAuthenticated]
serializer_class = report.serializers.LabelPrintSerializer 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.""" """API endpoint for viewing list of LabelTemplate objects."""
queryset = report.models.LabelTemplate.objects.all() queryset = report.models.LabelTemplate.objects.all()
@ -288,7 +300,7 @@ class LabelTemplateList(ListCreateAPI):
ordering_fields = ['name', 'enabled'] ordering_fields = ['name', 'enabled']
class LabelTemplateDetail(RetrieveUpdateDestroyAPI): class LabelTemplateDetail(TemplatePermissionMixin, RetrieveUpdateDestroyAPI):
"""Detail API endpoint for label template model.""" """Detail API endpoint for label template model."""
queryset = report.models.LabelTemplate.objects.all() queryset = report.models.LabelTemplate.objects.all()
@ -298,6 +310,7 @@ class LabelTemplateDetail(RetrieveUpdateDestroyAPI):
class ReportPrint(GenericAPIView): class ReportPrint(GenericAPIView):
"""API endpoint for printing reports.""" """API endpoint for printing reports."""
# Any authenticated user can print reports
permission_classes = [permissions.IsAuthenticated] permission_classes = [permissions.IsAuthenticated]
serializer_class = report.serializers.ReportPrintSerializer 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.""" """API endpoint for viewing list of ReportTemplate objects."""
queryset = report.models.ReportTemplate.objects.all() queryset = report.models.ReportTemplate.objects.all()
@ -445,49 +458,49 @@ class ReportTemplateList(ListCreateAPI):
ordering_fields = ['name', 'enabled'] ordering_fields = ['name', 'enabled']
class ReportTemplateDetail(RetrieveUpdateDestroyAPI): class ReportTemplateDetail(TemplatePermissionMixin, RetrieveUpdateDestroyAPI):
"""Detail API endpoint for report template model.""" """Detail API endpoint for report template model."""
queryset = report.models.ReportTemplate.objects.all() queryset = report.models.ReportTemplate.objects.all()
serializer_class = report.serializers.ReportTemplateSerializer serializer_class = report.serializers.ReportTemplateSerializer
class ReportSnippetList(ListCreateAPI): class ReportSnippetList(TemplatePermissionMixin, ListCreateAPI):
"""API endpoint for listing ReportSnippet objects.""" """API endpoint for listing ReportSnippet objects."""
queryset = report.models.ReportSnippet.objects.all() queryset = report.models.ReportSnippet.objects.all()
serializer_class = report.serializers.ReportSnippetSerializer serializer_class = report.serializers.ReportSnippetSerializer
class ReportSnippetDetail(RetrieveUpdateDestroyAPI): class ReportSnippetDetail(TemplatePermissionMixin, RetrieveUpdateDestroyAPI):
"""API endpoint for a single ReportSnippet object.""" """API endpoint for a single ReportSnippet object."""
queryset = report.models.ReportSnippet.objects.all() queryset = report.models.ReportSnippet.objects.all()
serializer_class = report.serializers.ReportSnippetSerializer serializer_class = report.serializers.ReportSnippetSerializer
class ReportAssetList(ListCreateAPI): class ReportAssetList(TemplatePermissionMixin, ListCreateAPI):
"""API endpoint for listing ReportAsset objects.""" """API endpoint for listing ReportAsset objects."""
queryset = report.models.ReportAsset.objects.all() queryset = report.models.ReportAsset.objects.all()
serializer_class = report.serializers.ReportAssetSerializer serializer_class = report.serializers.ReportAssetSerializer
class ReportAssetDetail(RetrieveUpdateDestroyAPI): class ReportAssetDetail(TemplatePermissionMixin, RetrieveUpdateDestroyAPI):
"""API endpoint for a single ReportAsset object.""" """API endpoint for a single ReportAsset object."""
queryset = report.models.ReportAsset.objects.all() queryset = report.models.ReportAsset.objects.all()
serializer_class = report.serializers.ReportAssetSerializer serializer_class = report.serializers.ReportAssetSerializer
class LabelOutputList(BulkDeleteMixin, ListAPI): class LabelOutputList(TemplatePermissionMixin, BulkDeleteMixin, ListAPI):
"""List endpoint for LabelOutput objects.""" """List endpoint for LabelOutput objects."""
queryset = report.models.LabelOutput.objects.all() queryset = report.models.LabelOutput.objects.all()
serializer_class = report.serializers.LabelOutputSerializer serializer_class = report.serializers.LabelOutputSerializer
class ReportOutputList(BulkDeleteMixin, ListAPI): class ReportOutputList(TemplatePermissionMixin, BulkDeleteMixin, ListAPI):
"""List endpoint for ReportOutput objects.""" """List endpoint for ReportOutput objects."""
queryset = report.models.ReportOutput.objects.all() queryset = report.models.ReportOutput.objects.all()

View File

@ -403,6 +403,61 @@ class ReportTest(InvenTreeAPITestCase):
self.assertEqual(len(p.metadata.keys()), 4) 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: class PrintTestMixins:
"""Mixin that enables e2e printing tests.""" """Mixin that enables e2e printing tests."""