diff --git a/InvenTree/InvenTree/api.py b/InvenTree/InvenTree/api.py index 38a3d559a0..ff2a489c5f 100644 --- a/InvenTree/InvenTree/api.py +++ b/InvenTree/InvenTree/api.py @@ -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, diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index acf585848d..142f0e97ac 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -841,12 +841,13 @@ for app in SOCIAL_BACKENDS: SOCIALACCOUNT_PROVIDERS = CONFIG.get('social_providers', []) +SOCIALACCOUNT_STORE_TOKENS = True + # settings for allauth ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = get_setting('INVENTREE_LOGIN_CONFIRM_DAYS', CONFIG.get('login_confirm_days', 3)) - ACCOUNT_LOGIN_ATTEMPTS_LIMIT = get_setting('INVENTREE_LOGIN_ATTEMPTS', CONFIG.get('login_attempts', 5)) - ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True +ACCOUNT_PREVENT_ENUMERATION = True # override forms / adapters ACCOUNT_FORMS = { diff --git a/InvenTree/build/templates/build/detail.html b/InvenTree/build/templates/build/detail.html index 248fcec580..f9bac157a8 100644 --- a/InvenTree/build/templates/build/detail.html +++ b/InvenTree/build/templates/build/detail.html @@ -172,7 +172,7 @@

{% trans "Allocate Stock to Build" %}

{% include "spacer.html" %}
- {% if build.active and build.has_untracked_bom_items %} + {% if roles.build.add and build.active and build.has_untracked_bom_items %} @@ -229,7 +229,7 @@

{% trans "Incomplete Build Outputs" %}

{% include "spacer.html" %}
- {% if build.active %} + {% if roles.build.add and build.active %} @@ -249,12 +249,16 @@
diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index 3705ad929c..10ec44489a 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -20,9 +20,11 @@

{% trans "Part Stock" %}

{% include "spacer.html" %}
+ {% if roles.stock.add %} + {% endif %}
diff --git a/InvenTree/stock/templates/stock/item.html b/InvenTree/stock/templates/stock/item.html index d5ccc21d35..52fbab79b4 100644 --- a/InvenTree/stock/templates/stock/item.html +++ b/InvenTree/stock/templates/stock/item.html @@ -147,9 +147,11 @@

{% trans "Installed Stock Items" %}

{% include "spacer.html" %}
+ {% if roles.stock.add %} + {% endif %}
diff --git a/InvenTree/templates/attachment_table.html b/InvenTree/templates/attachment_table.html index f47e1f5173..fbabb7e510 100644 --- a/InvenTree/templates/attachment_table.html +++ b/InvenTree/templates/attachment_table.html @@ -2,7 +2,7 @@
-
+
diff --git a/InvenTree/templates/js/translated/attachment.js b/InvenTree/templates/js/translated/attachment.js index 991d3efeba..e070cc65b1 100644 --- a/InvenTree/templates/js/translated/attachment.js +++ b/InvenTree/templates/js/translated/attachment.js @@ -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 = `
`; - 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 += `
`; diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index ca9ee7c240..b3bafb2657 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -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 diff --git a/requirements.txt b/requirements.txt index 765c774284..eb908235ee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ coverage==5.3 # Unit test coverage coveralls==2.1.2 # Coveralls linking (for Travis) cryptography==3.4.8 # Cryptography support django-admin-shell==0.1.2 # Python shell for the admin interface -django-allauth==0.45.0 # SSO for external providers via OpenID +django-allauth==0.48.0 # SSO for external providers via OpenID django-allauth-2fa==0.9 # MFA / 2FA django-cleanup==5.1.0 # Manage deletion of old / unused uploaded files django-cors-headers==3.2.0 # CORS headers extension for DRF