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

Add config option to fully disable installing plugins (#6535)

* [FR] Add config option to fully disable installing plugins
Fixes #6531

* also restrict uninstalling

* Added test

* diff cleanup

* extend api to show if install was disabled

* PUI disable install buttons

* CUI disable install button if not available

* add config option

* Rephrase
This commit is contained in:
Matthias Mair 2024-02-26 12:44:31 +01:00 committed by GitHub
parent 85225538e6
commit 75c24fb8f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 46 additions and 5 deletions

View File

@ -152,6 +152,7 @@ class InfoView(AjaxView):
'worker_running': is_worker_running(), 'worker_running': is_worker_running(),
'worker_pending_tasks': self.worker_pending_tasks(), 'worker_pending_tasks': self.worker_pending_tasks(),
'plugins_enabled': settings.PLUGINS_ENABLED, 'plugins_enabled': settings.PLUGINS_ENABLED,
'plugins_install_disabled': settings.PLUGINS_INSTALL_DISABLED,
'active_plugins': plugins_info(), 'active_plugins': plugins_info(),
'email_configured': is_email_configured(), 'email_configured': is_email_configured(),
'debug_mode': settings.DEBUG, 'debug_mode': settings.DEBUG,

View File

@ -1,11 +1,14 @@
"""InvenTree API version information.""" """InvenTree API version information."""
# InvenTree API version # InvenTree API version
INVENTREE_API_VERSION = 175 INVENTREE_API_VERSION = 176
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" """Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """ INVENTREE_API_TEXT = """
v176 - 2024-02-26 : https://github.com/inventree/InvenTree/pull/6535
- Adds the field "plugins_install_disabled" to the Server info API endpoint
v175 - 2024-02-21 : https://github.com/inventree/InvenTree/pull/6538 v175 - 2024-02-21 : https://github.com/inventree/InvenTree/pull/6538
- Adds "parts" count to PartParameterTemplate serializer - Adds "parts" count to PartParameterTemplate serializer

View File

@ -1136,6 +1136,9 @@ MAINTENANCE_MODE_STATE_BACKEND = 'InvenTree.backends.InvenTreeMaintenanceModeBac
PLUGINS_ENABLED = get_boolean_setting( PLUGINS_ENABLED = get_boolean_setting(
'INVENTREE_PLUGINS_ENABLED', 'plugins_enabled', False 'INVENTREE_PLUGINS_ENABLED', 'plugins_enabled', False
) )
PLUGINS_INSTALL_DISABLED = get_boolean_setting(
'INVENTREE_PLUGIN_NOINSTALL', 'plugin_noinstall', False
)
PLUGIN_FILE = config.get_plugin_file() PLUGIN_FILE = config.get_plugin_file()

View File

@ -155,6 +155,12 @@ def plugins_enabled(*args, **kwargs):
return djangosettings.PLUGINS_ENABLED return djangosettings.PLUGINS_ENABLED
@register.simple_tag()
def plugins_install_disabled(*args, **kwargs):
"""Return True if plugin install is disabled for the server instance."""
return djangosettings.PLUGINS_INSTALL_DISABLED
@register.simple_tag() @register.simple_tag()
def plugins_info(*args, **kwargs): def plugins_info(*args, **kwargs):
"""Return information about activated plugins.""" """Return information about activated plugins."""

View File

@ -152,6 +152,7 @@ sentry_enabled: False
# Set this variable to True to enable InvenTree Plugins # Set this variable to True to enable InvenTree Plugins
# Alternatively, use the environment variable INVENTREE_PLUGINS_ENABLED # Alternatively, use the environment variable INVENTREE_PLUGINS_ENABLED
plugins_enabled: False plugins_enabled: False
#plugin_noinstall: True
#plugin_file: '/path/to/plugins.txt' #plugin_file: '/path/to/plugins.txt'
#plugin_dir: '/path/to/plugins/' #plugin_dir: '/path/to/plugins/'

View File

@ -55,6 +55,10 @@ class TemplateTagTest(InvenTreeTestCase):
"""Test the plugins_enabled tag.""" """Test the plugins_enabled tag."""
self.assertEqual(inventree_extras.plugins_enabled(), True) self.assertEqual(inventree_extras.plugins_enabled(), True)
def test_plugins_install_disabled(self):
"""Test the plugins_install_disabled tag."""
self.assertEqual(inventree_extras.plugins_install_disabled(), False)
def test_inventree_instance_name(self): def test_inventree_instance_name(self):
"""Test the 'instance name' setting.""" """Test the 'instance name' setting."""
self.assertEqual(inventree_extras.inventree_instance_name(), 'InvenTree') self.assertEqual(inventree_extras.inventree_instance_name(), 'InvenTree')

View File

@ -193,6 +193,9 @@ def install_plugin(url=None, packagename=None, user=None, version=None):
if user and not user.is_staff: if user and not user.is_staff:
raise ValidationError(_('Only staff users can administer plugins')) raise ValidationError(_('Only staff users can administer plugins'))
if settings.PLUGINS_INSTALL_DISABLED:
raise ValidationError(_('Plugin installation is disabled'))
logger.info('install_plugin: %s, %s', url, packagename) logger.info('install_plugin: %s, %s', url, packagename)
# Check if we are running in a virtual environment # Check if we are running in a virtual environment
@ -292,6 +295,9 @@ def uninstall_plugin(cfg: plugin.models.PluginConfig, user=None, delete_config=T
""" """
from plugin.registry import registry from plugin.registry import registry
if settings.PLUGINS_INSTALL_DISABLED:
raise ValidationError(_('Plugin uninstalling is disabled'))
if cfg.active: if cfg.active:
raise ValidationError( raise ValidationError(
_('Plugin cannot be uninstalled as it is currently active') _('Plugin cannot be uninstalled as it is currently active')

View File

@ -1,5 +1,6 @@
"""Tests for general API tests for the plugin app.""" """Tests for general API tests for the plugin app."""
from django.conf import settings
from django.urls import reverse from django.urls import reverse
from rest_framework.exceptions import NotFound from rest_framework.exceptions import NotFound
@ -81,6 +82,11 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
data['confirm'][0].title().upper(), 'Installation not confirmed'.upper() data['confirm'][0].title().upper(), 'Installation not confirmed'.upper()
) )
# install disabled
settings.PLUGINS_INSTALL_DISABLED = True
self.post(url, {}, expected_code=400)
settings.PLUGINS_INSTALL_DISABLED = False
def test_plugin_activate(self): def test_plugin_activate(self):
"""Test the plugin activate.""" """Test the plugin activate."""
test_plg = self.plugin_confs.first() test_plg = self.plugin_confs.first()

View File

@ -29,6 +29,7 @@
</div> </div>
{% plugins_enabled as plug %} {% plugins_enabled as plug %}
{% plugins_install_disabled as plug_disabled %}
<div class='panel-heading'> <div class='panel-heading'>
<div class='d-flex flex-wrap'> <div class='d-flex flex-wrap'>
@ -38,7 +39,7 @@
{% admin_url user "plugin.pluginconfig" None as url %} {% admin_url user "plugin.pluginconfig" None as url %}
{% include "admin_button.html" with url=url %} {% include "admin_button.html" with url=url %}
{% if plug %} {% if plug %}
<button class="btn btn-success" id="install-plugin" title="{% trans 'Install Plugin' %}"> <button class="btn btn-success" id="install-plugin" title="{% trans 'Install Plugin' %}"{% if plug_disabled %}disabled{% endif %}>
<span class='fas fa-plus-circle'></span> {% trans "Install Plugin" %} <span class='fas fa-plus-circle'></span> {% trans "Install Plugin" %}
</button> </button>
<button class='btn btn-success' id='reload-plugins' title='{% trans "Reload Plugins" %}'> <button class='btn btn-success' id='reload-plugins' title='{% trans "Reload Plugins" %}'>

View File

@ -324,6 +324,7 @@ The following [plugin](../extend/plugins.md) configuration options are available
| Environment Variable | Configuration File | Description | Default | | Environment Variable | Configuration File | Description | Default |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| INVENTREE_PLUGINS_ENABLED | plugins_enabled | Enable plugin support | False | | INVENTREE_PLUGINS_ENABLED | plugins_enabled | Enable plugin support | False |
| INVENTREE_PLUGIN_NOINSTALL | plugin_noinstall | Disable Plugin installation via API - only use plugins.txt file | False |
| INVENTREE_PLUGIN_FILE | plugins_plugin_file | Location of plugin installation file | *Not specified* | | INVENTREE_PLUGIN_FILE | plugins_plugin_file | Location of plugin installation file | *Not specified* |
| INVENTREE_PLUGIN_DIR | plugins_plugin_dir | Location of external plugin directory | *Not specified* | | INVENTREE_PLUGIN_DIR | plugins_plugin_dir | Location of external plugin directory | *Not specified* |

View File

@ -8,6 +8,7 @@ export const emptyServerAPI = {
worker_running: null, worker_running: null,
worker_pending_tasks: null, worker_pending_tasks: null,
plugins_enabled: null, plugins_enabled: null,
plugins_install_disabled: null,
active_plugins: [], active_plugins: [],
email_configured: null, email_configured: null,
debug_mode: null, debug_mode: null,

View File

@ -34,6 +34,7 @@ export interface ServerAPIProps {
worker_running: null | boolean; worker_running: null | boolean;
worker_pending_tasks: null | number; worker_pending_tasks: null | number;
plugins_enabled: null | boolean; plugins_enabled: null | boolean;
plugins_install_disabled: null | boolean;
active_plugins: PluginProps[]; active_plugins: PluginProps[];
email_configured: null | boolean; email_configured: null | boolean;
debug_mode: null | boolean; debug_mode: null | boolean;

View File

@ -14,6 +14,7 @@ export type RowAction = {
icon: ReactNode; icon: ReactNode;
onClick?: () => void; onClick?: () => void;
hidden?: boolean; hidden?: boolean;
disabled?: boolean;
}; };
// Component for duplicating a row in a table // Component for duplicating a row in a table
@ -129,6 +130,7 @@ export function RowActions({
setOpened(false); setOpened(false);
}} }}
disabled={action.disabled || false}
> >
{action.title} {action.title}
</Menu.Item> </Menu.Item>

View File

@ -271,8 +271,11 @@ export default function PluginListTable() {
const navigate = useNavigate(); const navigate = useNavigate();
const user = useUserState(); const user = useUserState();
const pluginsEnabled = useServerApiState( const [pluginsEnabled, plugins_install_disabled] = useServerApiState(
(state) => state.server.plugins_enabled (state) => [
state.server.plugins_enabled,
state.server.plugins_install_disabled
]
); );
const pluginTableColumns: TableColumn[] = useMemo( const pluginTableColumns: TableColumn[] = useMemo(
@ -457,7 +460,8 @@ export default function PluginListTable() {
onClick: () => { onClick: () => {
setSelectedPlugin(record.pk); setSelectedPlugin(record.pk);
uninstallPluginModal.open(); uninstallPluginModal.open();
} },
disabled: plugins_install_disabled || false
}); });
} }
@ -592,6 +596,7 @@ export default function PluginListTable() {
setPluginPackage(''); setPluginPackage('');
installPluginModal.open(); installPluginModal.open();
}} }}
disabled={plugins_install_disabled || false}
/> />
); );
} }