diff --git a/src/config/constants.py b/src/config/constants.py index 09b8ab4d..c8456150 100644 --- a/src/config/constants.py +++ b/src/config/constants.py @@ -3,3 +3,4 @@ PARAM_TYPE_EDITABLE_LIST = 'editable_list' FILE_TYPE_FILE = 'file' FILE_TYPE_DIR = 'dir' +SHARED_ACCESS_TYPE_ALL = "ALL_USERS" diff --git a/src/execution/execution_service.py b/src/execution/execution_service.py index dc6c1b89..88e53a94 100644 --- a/src/execution/execution_service.py +++ b/src/execution/execution_service.py @@ -4,6 +4,7 @@ from auth.authorization import Authorizer, is_same_user from auth.user import User +from config.constants import SHARED_ACCESS_TYPE_ALL from execution.executor import ScriptExecutor from model import script_config from model.model_helper import is_empty, AccessProhibitedException @@ -145,7 +146,11 @@ def validate_execution_id(self, execution_id, user, only_active=True, allow_when @staticmethod def _can_access_execution(execution_info: _ExecutionInfo, user_id): - return (execution_info is not None) and (is_same_user(execution_info.owner_user.user_id, user_id)) + if execution_info is None: + return False + shared_access_type = execution_info.config.access.get('shared_access', {}).get('type') + return (shared_access_type == SHARED_ACCESS_TYPE_ALL or \ + is_same_user(execution_info.owner_user.user_id, user_id)) def get_user_parameter_values(self, execution_id): return self._get_for_executor(execution_id, diff --git a/src/model/script_config.py b/src/model/script_config.py index f0e0b0da..677d23d2 100644 --- a/src/model/script_config.py +++ b/src/model/script_config.py @@ -6,6 +6,7 @@ from auth.authorization import ANY_USER from config.exceptions import InvalidConfigException +from config.constants import SHARED_ACCESS_TYPE_ALL from model import parameter_config from model.model_helper import is_empty, fill_parameter_values, read_bool_from_config, InvalidValueException, \ read_str_from_config, replace_auth_vars @@ -180,6 +181,8 @@ def _reload_config(self): required_terminal = read_bool_from_config('requires_terminal', config, default=self._pty_enabled_default) self.requires_terminal = required_terminal + self.access = config.get('access', {}) + self.output_format = read_output_format(config) self.output_files = config.get('output_files', []) @@ -386,6 +389,7 @@ def get_sorted_config(config): 'group', 'allowed_users', 'admin_users', + 'access', 'schedulable', 'include', 'output_files', diff --git a/src/tests/execution_service_test.py b/src/tests/execution_service_test.py index 1a1a8e65..887d0da0 100644 --- a/src/tests/execution_service_test.py +++ b/src/tests/execution_service_test.py @@ -144,6 +144,13 @@ def test_can_access_different_user_reversed(self): self.assertFalse(execution_service.can_access(execution_id, DEFAULT_USER_ID)) + def test_can_access_different_user_shared_access(self): + execution_service = self.create_execution_service() + execution_id = self._start(execution_service) + execution_service._execution_infos[execution_id].config.access = {'shared_access': {'type': 'ALL_USERS'}} + + self.assertTrue(execution_service.can_access(execution_id, 'another_user')) + def test_get_audit_name(self): execution_service = self.create_execution_service() execution_id = self._start(execution_service) diff --git a/web-src/src/admin/components/scripts-config/ScriptConfigForm.vue b/web-src/src/admin/components/scripts-config/ScriptConfigForm.vue index 52cd5310..5f8ddd78 100644 --- a/web-src/src/admin/components/scripts-config/ScriptConfigForm.vue +++ b/web-src/src/admin/components/scripts-config/ScriptConfigForm.vue @@ -38,6 +38,11 @@ class="col s3 checkbox-field"/> + +
+ +
@@ -56,6 +61,7 @@ import { nameField, outputFormatField, requiresTerminalField, + globalInstancesField, scriptPathField, workDirField } from './script-fields'; @@ -86,6 +92,7 @@ export default { description: null, workingDirectory: null, requiresTerminal: null, + globalInstances: null, includeScript: null, outputFormat: null, allowedUsers: [], @@ -101,6 +108,7 @@ export default { allowAllAdminsField, outputFormatField, requiresTerminalField, + globalInstancesField, includeScriptField } }, @@ -125,6 +133,15 @@ export default { } }); }); + + this.$watch('globalInstances', (globalInstances) => { + if (globalInstances) { + this.$set(this.value, 'access', {'shared_access': {'type': 'ALL_USERS'}}); + } else { + this.$delete(this.value, 'access'); + } + }); + }, watch: { @@ -136,13 +153,16 @@ export default { this.description = config['description']; this.workingDirectory = config['working_directory']; this.requiresTerminal = get(config, 'requires_terminal', true); + this.globalInstances = false; + if (config?.access?.shared_access?.type == "ALL_USERS"){ + this.globalInstances = true; + } this.includeScript = config['include']; this.outputFormat = config['output_format']; this.updateAccessFieldInVm(config, 'allowedUsers', 'allowAllUsers', 'allowed_users') - this.updateAccessFieldInVm(config, 'adminUsers', 'allowAllAdmins', diff --git a/web-src/src/admin/components/scripts-config/script-fields.js b/web-src/src/admin/components/scripts-config/script-fields.js index 31ab9fda..2a5b5186 100644 --- a/web-src/src/admin/components/scripts-config/script-fields.js +++ b/web-src/src/admin/components/scripts-config/script-fields.js @@ -36,4 +36,8 @@ export const requiresTerminalField = { export const includeScriptField = { name: 'Include config', description: 'Allows to include another shared config' +}; +export const globalInstancesField = { + name: 'Shared Script Instances', + description: 'Allows script instances to be shared by all users' }; \ No newline at end of file diff --git a/web-src/tests/unit/admin/ScriptConfig_test.js b/web-src/tests/unit/admin/ScriptConfig_test.js index 3b8689a5..31a85bfd 100644 --- a/web-src/tests/unit/admin/ScriptConfig_test.js +++ b/web-src/tests/unit/admin/ScriptConfig_test.js @@ -136,5 +136,38 @@ describe('Test ScriptConfig', function () { }); }); + describe('Test show shared instances access', function () { + it('Test show shared instances access unchecked', async function () { + store.state.scriptConfig.scriptConfig = {}; + + await vueTicks(); + + expect(_findField('Shared Script Instances').value).toBe(false); + }); + + it('Test show shared instances access checked', async function () { + store.state.scriptConfig.scriptConfig = { + 'access': {'shared_access': {'type': 'ALL_USERS'}} + }; + + await vueTicks(); + + expect(_findField('Shared Script Instances').value).toBe(true); + }); + }); + + describe('Test edit global_instances', function () { + it('Test update global_instances manually unchecked', async function () { + await _setValueByUser('Shared Script Instances', false); + + expect(typeof store.state.scriptConfig.scriptConfig.access).toEqual('undefined'); + }); + + it('Test update global_instances manually checked', async function () { + await _setValueByUser('Shared Script Instances', true); + + expect(store.state.scriptConfig.scriptConfig.access).toEqual({'shared_access': {'type': 'ALL_USERS'}}); + }); + }); }); \ No newline at end of file