mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-30 20:55:42 +00:00 
			
		
		
		
	Fix API endpoint permission for the "AttachmentMixin" class (#3218)
* Fix API endpoint permission for the "AttachmentMixin" class - Any authenticated user could perform CREATE and UPDATE operations on attachments - Could be performed via the browsable DRF API - Could also be performed via the front-end (with some advaned jiggering of OPTIONS code) * Show or hide buttons depending on the permissions of the user * Add shortcut for table permission check
This commit is contained in:
		| @@ -11,6 +11,7 @@ from rest_framework.response import Response | ||||
| from rest_framework.serializers import ValidationError | ||||
|  | ||||
| from InvenTree.mixins import ListCreateAPI | ||||
| from InvenTree.permissions import RolePermission | ||||
|  | ||||
| from .status import is_worker_running | ||||
| from .version import (inventreeApiVersion, inventreeInstanceName, | ||||
| @@ -182,7 +183,10 @@ class APIDownloadMixin: | ||||
| class AttachmentMixin: | ||||
|     """Mixin for creating attachment objects, and ensuring the user information is saved correctly.""" | ||||
|  | ||||
|     permission_classes = [permissions.IsAuthenticated] | ||||
|     permission_classes = [ | ||||
|         permissions.IsAuthenticated, | ||||
|         RolePermission, | ||||
|     ] | ||||
|  | ||||
|     filter_backends = [ | ||||
|         DjangoFilterBackend, | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|  | ||||
| <div id='attachment-buttons'> | ||||
|     <div class='btn-group' role='group'> | ||||
|         <div class='btn-group'> | ||||
|         <div class='btn-group' id='multi-attachment-actions'> | ||||
|             <button class='btn btn-primary dropdown-toggle' type='button' data-bs-toggle='dropdown' title='{% trans "Actions" %}'> | ||||
|                 <span class='fas fa-tools'></span> <span class='caret'></span> | ||||
|             </button> | ||||
|   | ||||
| @@ -136,19 +136,56 @@ function loadAttachmentTable(url, options) { | ||||
|  | ||||
|     var table = options.table || '#attachment-table'; | ||||
|  | ||||
|     setupFilterList('attachments', $(table), '#filter-list-attachments'); | ||||
|     var permissions = {}; | ||||
|  | ||||
|     addAttachmentButtonCallbacks(url, options.fields || {}); | ||||
|     // First we determine which permissions the user has for this attachment table | ||||
|     $.ajax({ | ||||
|         url: url, | ||||
|         async: false, | ||||
|         type: 'OPTIONS', | ||||
|         contentType: 'application/json', | ||||
|         dataType: 'json', | ||||
|         accepts: { | ||||
|             json: 'application/json', | ||||
|         }, | ||||
|         success: function(response) { | ||||
|             if (response.actions.DELETE) { | ||||
|                 permissions.delete = true; | ||||
|             } | ||||
|  | ||||
|     // Add callback for the 'multi delete' button | ||||
|     $('#multi-attachment-delete').click(function() { | ||||
|         var attachments = getTableData(table); | ||||
|  | ||||
|         if (attachments.length > 0) { | ||||
|             deleteAttachments(attachments, url, options); | ||||
|             if (response.actions.POST) { | ||||
|                 permissions.change = true; | ||||
|                 permissions.add = true; | ||||
|             } | ||||
|         }, | ||||
|         error: function(xhr) { | ||||
|             showApiError(xhr, url); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     setupFilterList('attachments', $(table), '#filter-list-attachments'); | ||||
|  | ||||
|     if (permissions.add) { | ||||
|         addAttachmentButtonCallbacks(url, options.fields || {}); | ||||
|     } else { | ||||
|         // Hide the buttons | ||||
|         $('#new-attachment').hide(); | ||||
|         $('#new-attachment-link').hide(); | ||||
|     } | ||||
|  | ||||
|     if (permissions.delete) { | ||||
|         // Add callback for the 'multi delete' button | ||||
|         $('#multi-attachment-delete').click(function() { | ||||
|             var attachments = getTableData(table); | ||||
|  | ||||
|             if (attachments.length > 0) { | ||||
|                 deleteAttachments(attachments, url, options); | ||||
|             } | ||||
|         }); | ||||
|     } else { | ||||
|         $('#multi-attachment-actions').hide(); | ||||
|     } | ||||
|  | ||||
|     $(table).inventreeTable({ | ||||
|         url: url, | ||||
|         name: options.name || 'attachments', | ||||
| @@ -162,32 +199,36 @@ function loadAttachmentTable(url, options) { | ||||
|         onPostBody: function() { | ||||
|  | ||||
|             // Add callback for 'edit' button | ||||
|             $(table).find('.button-attachment-edit').click(function() { | ||||
|                 var pk = $(this).attr('pk'); | ||||
|             if (permissions.change) { | ||||
|                 $(table).find('.button-attachment-edit').click(function() { | ||||
|                     var pk = $(this).attr('pk'); | ||||
|  | ||||
|                 constructForm(`${url}${pk}/`, { | ||||
|                     fields: { | ||||
|                         link: {}, | ||||
|                         comment: {}, | ||||
|                     }, | ||||
|                     processResults: function(data, fields, opts) { | ||||
|                         // Remove the "link" field if the attachment is a file! | ||||
|                         if (data.attachment) { | ||||
|                             delete opts.fields.link; | ||||
|                         } | ||||
|                     }, | ||||
|                     onSuccess: reloadAttachmentTable, | ||||
|                     title: '{% trans "Edit Attachment" %}', | ||||
|                     constructForm(`${url}${pk}/`, { | ||||
|                         fields: { | ||||
|                             link: {}, | ||||
|                             comment: {}, | ||||
|                         }, | ||||
|                         processResults: function(data, fields, opts) { | ||||
|                             // Remove the "link" field if the attachment is a file! | ||||
|                             if (data.attachment) { | ||||
|                                 delete opts.fields.link; | ||||
|                             } | ||||
|                         }, | ||||
|                         onSuccess: reloadAttachmentTable, | ||||
|                         title: '{% trans "Edit Attachment" %}', | ||||
|                     }); | ||||
|                 }); | ||||
|             }); | ||||
|             } | ||||
|  | ||||
|             // Add callback for 'delete' button | ||||
|             $(table).find('.button-attachment-delete').click(function() { | ||||
|                 var pk = $(this).attr('pk'); | ||||
|             if (permissions.delete) { | ||||
|                 // Add callback for 'delete' button | ||||
|                 $(table).find('.button-attachment-delete').click(function() { | ||||
|                     var pk = $(this).attr('pk'); | ||||
|  | ||||
|                 var attachment = $(table).bootstrapTable('getRowByUniqueId', pk); | ||||
|                 deleteAttachments([attachment], url, options); | ||||
|             }); | ||||
|                     var attachment = $(table).bootstrapTable('getRowByUniqueId', pk); | ||||
|                     deleteAttachments([attachment], url, options); | ||||
|                 }); | ||||
|             } | ||||
|         }, | ||||
|         columns: [ | ||||
|             { | ||||
| @@ -261,19 +302,23 @@ function loadAttachmentTable(url, options) { | ||||
|  | ||||
|                     html = `<div class='btn-group float-right' role='group'>`; | ||||
|  | ||||
|                     html += makeIconButton( | ||||
|                         'fa-edit icon-blue', | ||||
|                         'button-attachment-edit', | ||||
|                         row.pk, | ||||
|                         '{% trans "Edit attachment" %}', | ||||
|                     ); | ||||
|                     if (permissions.change) { | ||||
|                         html += makeIconButton( | ||||
|                             'fa-edit icon-blue', | ||||
|                             'button-attachment-edit', | ||||
|                             row.pk, | ||||
|                             '{% trans "Edit attachment" %}', | ||||
|                         ); | ||||
|                     } | ||||
|  | ||||
|                     html += makeIconButton( | ||||
|                         'fa-trash-alt icon-red', | ||||
|                         'button-attachment-delete', | ||||
|                         row.pk, | ||||
|                         '{% trans "Delete attachment" %}', | ||||
|                     ); | ||||
|                     if (permissions.delete) { | ||||
|                         html += makeIconButton( | ||||
|                             'fa-trash-alt icon-red', | ||||
|                             'button-attachment-delete', | ||||
|                             row.pk, | ||||
|                             '{% trans "Delete attachment" %}', | ||||
|                         ); | ||||
|                     } | ||||
|  | ||||
|                     html += `</div>`; | ||||
|  | ||||
|   | ||||
| @@ -222,6 +222,11 @@ class RuleSet(models.Model): | ||||
|     @classmethod | ||||
|     def check_table_permission(cls, user, table, permission): | ||||
|         """Check if the provided user has the specified permission against the table.""" | ||||
|  | ||||
|         # Superuser knows no bounds | ||||
|         if user.is_superuser: | ||||
|             return True | ||||
|  | ||||
|         # If the table does *not* require permissions | ||||
|         if table in cls.RULESET_IGNORE: | ||||
|             return True | ||||
|   | ||||
		Reference in New Issue
	
	Block a user