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:
parent
cdac7465b2
commit
7108bc48bd
@ -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):
|
||||||
|
@ -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()
|
||||||
|
@ -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."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user