Skip to content

Commit 5accf80

Browse files
committed
#342 added admin_users to script configuration
1 parent 4cec0c4 commit 5accf80

7 files changed

Lines changed: 172 additions & 20 deletions

File tree

src/config/config_service.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ def load_config(self, name, user):
4949
if config_object.get('name') is None:
5050
config_object['name'] = short_config.name
5151

52+
if not self._can_edit_script(user, short_config):
53+
raise ConfigNotAllowedException(str(user) + ' has no admin access to ' + short_config.name)
54+
5255
return {'config': config_object, 'filename': os.path.basename(path)}
5356

5457
def create_config(self, user, config):
@@ -84,6 +87,9 @@ def update_config(self, user, config, filename):
8487
if (found_config_path is not None) and (os.path.basename(found_config_path) != filename):
8588
raise InvalidConfigException('Another script found with the same name: ' + name)
8689

90+
if (short_config is not None) and not self._can_edit_script(user, short_config):
91+
raise ConfigNotAllowedException(str(user) + ' is not allowed to modify ' + short_config.name)
92+
8793
LOGGER.info('Updating script config "' + name + '" in ' + original_file_path)
8894
self._save_config(config, original_file_path)
8995

@@ -107,6 +113,9 @@ def load_script(path, content):
107113
if short_config is None:
108114
return None
109115

116+
if edit_mode and (not conf_service._can_edit_script(user, short_config)):
117+
return None
118+
110119
if (not edit_mode) and (not conf_service._can_access_script(user, short_config)):
111120
return None
112121

@@ -196,14 +205,17 @@ def _load_script_config(self, path, content_or_json_dict, user, parameter_values
196205
def _can_access_script(self, user, short_config):
197206
return self._authorizer.is_allowed(user.user_id, short_config.allowed_users)
198207

208+
def _can_edit_script(self, user, short_config):
209+
return self._authorizer.is_allowed(user.user_id, short_config.admin_users)
210+
199211
def _check_admin_access(self, user):
200212
if not self._authorizer.is_admin(user.user_id):
201213
raise AdminAccessRequiredException('Admin access to scripts is prohibited for ' + str(user))
202214

203215

204216
class ConfigNotAllowedException(Exception):
205-
def __init__(self):
206-
pass
217+
def __init__(self, message=None):
218+
super().__init__(message)
207219

208220

209221
class AdminAccessRequiredException(Exception):

src/model/script_config.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class ShortConfig(object):
2020
def __init__(self):
2121
self.name = None
2222
self.allowed_users = []
23+
self.admin_users = []
2324
self.group = None
2425

2526

@@ -256,6 +257,7 @@ def read_short(file_path, json_object):
256257

257258
config.name = _read_name(file_path, json_object)
258259
config.allowed_users = json_object.get('allowed_users')
260+
config.admin_users = json_object.get('admin_users')
259261
config.group = read_str_from_config(json_object, 'group', blank_to_none=True)
260262

261263
hidden = read_bool_from_config('hidden', json_object, default=False)
@@ -267,6 +269,11 @@ def read_short(file_path, json_object):
267269
elif (config.allowed_users == '*') or ('*' in config.allowed_users):
268270
config.allowed_users = ANY_USER
269271

272+
if config.admin_users is None:
273+
config.admin_users = ANY_USER
274+
elif (config.admin_users == '*') or ('*' in config.admin_users):
275+
config.admin_users = ANY_USER
276+
270277
return config
271278

272279

@@ -352,7 +359,15 @@ def get(self):
352359

353360

354361
def get_sorted_config(config):
355-
key_order = ['name', 'script_path', 'working_directory', 'hidden', 'description', 'allowed_users', 'include',
362+
key_order = ['name', 'script_path',
363+
'working_directory',
364+
'hidden',
365+
'description',
366+
'group',
367+
'allowed_users',
368+
'admin_users',
369+
'schedulable',
370+
'include',
356371
'output_files', 'requires_terminal', 'bash_formatting', 'parameters']
357372

358373
def get_order(key):

src/tests/config_service_test.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ def test_list_configs_when_edit_mode_and_admin_without_allowance(self):
138138

139139
self.assert_list_config_names(self.admin_user, ['a1', 'c2'], mode='edit')
140140

141+
def test_list_configs_when_edit_mode_and_admin_not_in_admin_users(self):
142+
_create_script_config_file('a1', admin_users=['user1'])
143+
_create_script_config_file('c2', admin_users=['adm_user'])
144+
145+
self.assert_list_config_names(self.admin_user, ['c2'], mode='edit')
146+
141147
def test_list_configs_when_edit_mode_and_non_admin(self):
142148
_create_script_config_file('a1', allowed_users=['user1'])
143149
_create_script_config_file('c2', allowed_users=['user1'])
@@ -266,6 +272,14 @@ def test_insert_sorted_values(self):
266272
('requires_terminal', False),
267273
('parameters', [{'name': 'param1'}])]))
268274

275+
def test_create_config_with_admin_users(self):
276+
config = _prepare_script_config_object('conf1',
277+
description='My wonderful test config',
278+
admin_users=['another_user'])
279+
self.config_service.create_config(self.admin_user, config)
280+
281+
_validate_config(self, 'conf1.json', config)
282+
269283

270284
class ConfigServiceUpdateConfigTest(unittest.TestCase):
271285

@@ -375,6 +389,32 @@ def test_update_sorted_values(self):
375389
('parameters', [{'name': 'param1'}])])
376390
_validate_config(self, 'confX.json', body)
377391

392+
def test_update_config_allowed_admin_user(self):
393+
config = _prepare_script_config_object('Conf X',
394+
description='My wonderful test config',
395+
admin_users=['admin_user'])
396+
self.config_service.update_config(self.admin_user, config, 'confX.json')
397+
398+
new_config = _prepare_script_config_object('Conf X',
399+
description='New desc')
400+
self.config_service.update_config(self.admin_user, new_config, 'confX.json')
401+
402+
_validate_config(self, 'confX.json', new_config)
403+
404+
def test_update_config_different_admin_user(self):
405+
config = _prepare_script_config_object('Conf X',
406+
description='My wonderful test config',
407+
admin_users=['another_user'])
408+
self.config_service.update_config(self.admin_user, config, 'confX.json')
409+
410+
new_config = _prepare_script_config_object('Conf X',
411+
description='New desc',
412+
admin_users=['admin_user'])
413+
self.assertRaisesRegex(ConfigNotAllowedException, 'is not allowed to modify',
414+
self.config_service.update_config, self.admin_user, new_config, 'confX.json')
415+
416+
_validate_config(self, 'confX.json', config)
417+
378418

379419
class ConfigServiceLoadConfigForAdminTest(unittest.TestCase):
380420
def setUp(self):
@@ -413,6 +453,15 @@ def test_load_config_when_not_exists(self):
413453
config = self.config_service.load_config('ConfX', self.admin_user)
414454
self.assertIsNone(config)
415455

456+
def test_load_config_when_script_has_admin_users(self):
457+
_create_script_config_file('ConfX', admin_users=['admin_user'])
458+
config = self.config_service.load_config('ConfX', self.admin_user)
459+
self.assertEqual(config['filename'], 'ConfX.json')
460+
461+
def test_load_config_when_script_has_different_admin_users(self):
462+
_create_script_config_file('ConfX', admin_users=['admin_user2'])
463+
self.assertRaises(ConfigNotAllowedException, self.config_service.load_config, 'ConfX', self.admin_user)
464+
416465

417466
def _create_script_config_file(filename, *, name=None, **kwargs):
418467
conf_folder = os.path.join(test_utils.temp_folder, 'runners')

src/web/server.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,13 +241,22 @@ def put(self, user):
241241
self.application.config_service.update_config(user, config, filename)
242242
except (InvalidConfigException, InvalidFileException) as e:
243243
raise tornado.web.HTTPError(422, str(e))
244+
except ConfigNotAllowedException:
245+
LOGGER.warning('Admin access to the script "' + config['name'] + '" is denied for ' + user.get_audit_name())
246+
respond_error(self, 403, 'Access to the script is denied')
247+
return
244248

245249

246250
class AdminGetScriptEndpoint(BaseRequestHandler):
247251
@requires_admin_rights
248252
@inject_user
249253
def get(self, user, script_name):
250-
config = self.application.config_service.load_config(script_name, user)
254+
try:
255+
config = self.application.config_service.load_config(script_name, user)
256+
except ConfigNotAllowedException:
257+
LOGGER.warning('Admin access to the script "' + script_name + '" is denied for ' + user.get_audit_name())
258+
respond_error(self, 403, 'Access to the script is denied')
259+
return
251260

252261
if config is None:
253262
raise tornado.web.HTTPError(404, str('Failed to find config for name: ' + script_name))

web-src/src/admin/components/scripts-config/ScriptConfigForm.vue

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@
2121
v-model="allowAllUsers"/>
2222
</div>
2323

24+
<div class="row">
25+
<div v-if="allowAllAdmins" class="input-field col s9">
26+
<input id="admin_users_disabled" disabled type="text" value="Any admin">
27+
<label class="active" for="admin_users_disabled">Admin users</label>
28+
</div>
29+
<ChipsList v-else v-model="adminUsers" class="col s9" title="Admin users"/>
30+
<CheckBox v-model="allowAllAdmins" :config="allowAllAdminsField"
31+
class="col s2 offset-s1 checkbox-field"/>
32+
</div>
33+
2434
<div class="row">
2535
<CheckBox :config="bashFormattingField" class="col s3 checkbox-field" v-model="bashFormatting"/>
2636
<CheckBox :config="requiresTerminalField" class="col s3 checkbox-field"
@@ -48,6 +58,7 @@
4858
scriptPathField,
4959
workDirField
5060
} from './script-fields';
61+
import {allowAllAdminsField} from "@/admin/components/scripts-config/script-fields";
5162
5263
export default {
5364
name: 'ScriptConfigForm',
@@ -73,12 +84,15 @@
7384
bashFormatting: null,
7485
allowedUsers: [],
7586
allowAllUsers: true,
87+
adminUsers: [],
88+
allowAllAdmins: true,
7689
nameField,
7790
groupField,
7891
scriptPathField,
7992
workDirField,
8093
descriptionField,
8194
allowAllField,
95+
allowAllAdminsField,
8296
bashFormattingField,
8397
requiresTerminalField,
8498
includeScriptField
@@ -120,38 +134,64 @@
120134
this.requiresTerminal = get(config, 'requires_terminal', true);
121135
this.includeScript = config['include'];
122136
this.bashFormatting = get(config, 'bash_formatting', true);
123-
let allowedUsers = get(config, 'allowed_users');
124-
if (isNull(allowedUsers)) {
125-
allowedUsers = [];
126-
}
127-
this.allowedUsers = allowedUsers.filter(u => u !== '*');
128-
this.allowAllUsers = isNull(config['allowed_users']) || allowedUsers.includes('*');
137+
this.updateAccessFieldInVm(config,
138+
'allowedUsers',
139+
'allowAllUsers',
140+
'allowed_users')
141+
142+
this.updateAccessFieldInVm(config,
143+
'adminUsers',
144+
'allowAllAdmins',
145+
'admin_users')
129146
}
130147
},
131148
allowAllUsers() {
132149
this.updateAllowedUsers();
133150
},
134151
allowedUsers() {
135152
this.updateAllowedUsers();
153+
},
154+
allowAllAdmins() {
155+
this.updateAdminUsers();
156+
},
157+
adminUsers() {
158+
this.updateAdminUsers();
136159
}
137160
},
138161
139162
methods: {
140163
updateAllowedUsers() {
141-
if (this.allowAllUsers) {
142-
if (isEmptyArray(this.allowedUsers)) {
143-
this.$delete(this.value, 'allowed_users');
144-
} else {
145-
if (this.allowedUsers.includes('*')) {
146-
this.value['allowed_users'] = this.allowedUsers;
164+
this.updateAccessFieldInValue(this.allowAllUsers, 'allowedUsers', 'allowed_users');
165+
},
166+
updateAdminUsers() {
167+
this.updateAccessFieldInValue(this.allowAllAdmins, 'adminUsers', 'admin_users');
168+
},
169+
updateAccessFieldInValue(allowAll, vmPropertyName, valuePropertyName) {
170+
const newValue = this[vmPropertyName];
171+
172+
if (isEmptyArray(newValue)) {
173+
this.$delete(this.value, valuePropertyName);
174+
} else {
175+
if (allowAll) {
176+
if (newValue.includes('*')) {
177+
this.value[valuePropertyName] = newValue;
147178
} else {
148-
this.value['allowed_users'] = [...this.allowedUsers, '*'];
179+
this.value[valuePropertyName] = [...newValue, '*'];
149180
}
181+
} else {
182+
this.value[valuePropertyName] = newValue;
150183
}
151-
} else {
152-
this.value['allowed_users'] = this.allowedUsers;
153184
}
154-
}
185+
},
186+
updateAccessFieldInVm(config, vmPropertyName, vmAllowAllPropertyName, valuePropertyName) {
187+
let users = get(config, valuePropertyName);
188+
if (isNull(users)) {
189+
users = [];
190+
}
191+
this[vmPropertyName] = users.filter(u => u !== '*');
192+
this[vmAllowAllPropertyName] = isNull(config[valuePropertyName]) || users.includes('*');
193+
},
194+
155195
}
156196
}
157197
</script>

web-src/src/admin/components/scripts-config/script-fields.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ export const workDirField = {
1919
export const allowAllField = {
2020
name: 'Allow all'
2121
};
22+
export const allowAllAdminsField = {
23+
name: 'Any admin'
24+
};
2225
export const bashFormattingField = {
2326
name: 'Bash formatting',
2427
description: 'Enable ANSI escape sequences for text formatting and cursor moves'

web-src/tests/unit/admin/ScriptConfig_test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,28 @@ describe('Test ScriptConfig', function () {
100100
});
101101
});
102102

103+
describe('Test edit allowed_users', function () {
104+
it('Test edit allowed_users manually', async function () {
105+
await _setValueByUser('Allow all', false);
106+
await _setValueByUser('Allowed users', ['user A', 'user B']);
107+
108+
expect(store.state.scriptConfig.scriptConfig.allowed_users).toEqual(['user A', 'user B'])
109+
});
110+
});
111+
112+
describe('Test edit admin_users', function () {
113+
it('Test edit admin_users manually', async function () {
114+
await _setValueByUser('Any admin', false);
115+
await _setValueByUser('Admin users', ['user A', 'user B']);
116+
117+
expect(store.state.scriptConfig.scriptConfig.admin_users).toEqual(['user A', 'user B'])
118+
});
119+
120+
it('Test set any admin = false without any user, manually', async function () {
121+
await _setValueByUser('Any admin', false);
122+
123+
expect(store.state.scriptConfig.scriptConfig.admin_users).toBeNil()
124+
});
125+
});
126+
103127
});

0 commit comments

Comments
 (0)