class TestTemplateProperty(unittest.TestCase): def test_single_parameter(self): self.add_parameter(create_parameter_model('param')) property = self.create_property('Hello, ${param}!') self.set_value('param', 'world') self.assertEqual('Hello, world!', property.get()) def test_single_parameter_when_no_value(self): self.add_parameter(create_parameter_model('param')) property = self.create_property('Hello, ${param}!') self.assertIsNone(property.get()) def test_single_parameter_with_value_on_init(self): self.add_parameter(create_parameter_model('param')) self.set_value('param', 'world') property = self.create_property('Hello, ${param}!') self.assertEqual('Hello, world!', property.get()) def test_no_dependencies(self): self.add_parameter(create_parameter_model('param')) property = self.create_property('Test value') self.assertEqual('Test value', property.get()) def test_multiple_parameters(self): self.add_parameter(create_parameter_model('p1')) self.add_parameter(create_parameter_model('p2')) self.add_parameter(create_parameter_model('p3')) property = self.create_property('Hello, ${p1}, ${p2} and ${p3}!') self.set_value('p1', 'John') self.set_value('p2', 'Mary') self.set_value('p3', 'world') self.assertEqual('Hello, John, Mary and world!', property.get()) def test_multiple_parameters_when_one_missing(self): self.add_parameter(create_parameter_model('p1')) self.add_parameter(create_parameter_model('p2')) self.add_parameter(create_parameter_model('p3')) property = self.create_property('Hello, ${p1}, ${p2} and ${p3}!') self.set_value('p1', 'John') self.set_value('p3', 'world') self.assertIsNone(property.get()) def test_multiple_parameters_when_one_becomes_missing(self): self.add_parameter(create_parameter_model('p1')) self.add_parameter(create_parameter_model('p2')) self.add_parameter(create_parameter_model('p3')) self.set_value('p1', 'John') self.set_value('p2', 'Mary') self.set_value('p3', 'world') property = self.create_property('Hello, ${p1}, ${p2} and ${p3}!') self.set_value('p3', None) self.assertIsNone(property.get()) def test_multiple_parameters_when_one_repeats(self): self.add_parameter(create_parameter_model('p1')) self.add_parameter(create_parameter_model('p2')) property = self.create_property('Hello ${p1}, ${p1}, ${p2} and ${p1}!') self.set_value('p1', 'John') self.set_value('p2', 'Mary') self.assertEqual('Hello John, John, Mary and John!', property.get()) def test_multiple_parameters_get_required(self): self.add_parameter(create_parameter_model('p1')) self.add_parameter(create_parameter_model('p2')) self.add_parameter(create_parameter_model('p3')) property = self.create_property('Hello ${p1}, ${p3} and ${p2}!') self.assertCountEqual(['p1', 'p2', 'p3'], property.required_parameters) def test_value_without_parameter(self): self.set_value('p1', 'John') property = self.create_property('Hello, ${p1}!') self.assertEqual('Hello, ${p1}!', property.get()) def test_late_add_single_parameter(self): self.set_value('p1', 'John') property = self.create_property('Hello, ${p1}!') self.add_parameter(create_parameter_model('p1')) self.assertEqual('Hello, John!', property.get()) def test_late_remove_single_parameter(self): parameter = create_parameter_model('p1') self.add_parameter(parameter) self.set_value('p1', 'John') property = self.create_property('Hello, ${p1}!') self.parameters.remove(parameter) self.assertEqual('Hello, ${p1}!', property.get()) def setUp(self): super().setUp() self.parameters = ObservableList() self.values = ObservableDict() def create_property(self, template): return _TemplateProperty(template, self.parameters, self.values) def add_parameter(self, config): self.parameters.append(config) def set_value(self, name, value): self.values[name] = value
class ConfigModel: def __init__(self, config_object, path, username, audit_name, pty_enabled_default=True, ansi_enabled_default=True, parameter_values=None): super().__init__() short_config = read_short(path, config_object) self.name = short_config.name self._ansi_enabled_default = ansi_enabled_default self._pty_enabled_default = pty_enabled_default self._config_folder = os.path.dirname(path) self._username = username self._audit_name = audit_name self.parameters = ObservableList() self.parameter_values = ObservableDict() self._original_config = config_object self._included_config_path = _TemplateProperty(config_object.get('include'), parameters=self.parameters, values=self.parameter_values) self._included_config_prop.bind(self._included_config_path, self._path_to_json) self._reload_config() self._init_parameters(username, audit_name) if parameter_values is not None: self.set_all_param_values(parameter_values) else: for parameter in self.parameters: self.parameter_values[parameter.name] = parameter.default self._reload_parameters({}) self._included_config_prop.subscribe(lambda old, new: self._reload(old)) def set_param_value(self, param_name, value): parameter = self.find_parameter(param_name) if parameter is None: LOGGER.warning('Parameter ' + param_name + ' does not exist in ' + self.name) return validation_error = parameter.validate_value(value, ignore_required=True) if validation_error is not None: self.parameter_values[param_name] = None raise InvalidValueException(param_name, validation_error) self.parameter_values[param_name] = value def set_all_param_values(self, param_values): original_values = dict(self.parameter_values) processed = {} anything_changed = True while (len(processed) < len(self.parameters)) and anything_changed: anything_changed = False for parameter in self.parameters: if parameter.name in processed: continue required_parameters = parameter.get_required_parameters() if required_parameters and any(r not in processed for r in required_parameters): continue value = parameter.normalize_user_value(param_values.get(parameter.name)) validation_error = parameter.validate_value(value) if validation_error: self.parameter_values.set(original_values) raise InvalidValueException(parameter.name, validation_error) self.parameter_values[parameter.name] = value processed[parameter.name] = parameter anything_changed = True if not anything_changed: remaining = [p.name for p in self.parameters if p.name not in processed] self.parameter_values.set(original_values) raise Exception('Could not resolve order for dependencies. Remaining: ' + str(remaining)) for key, value in param_values.items(): if self.find_parameter(key) is None: LOGGER.warning('Incoming value for unknown parameter ' + key) def list_files_for_param(self, parameter_name, path): parameter = self.find_parameter(parameter_name) if not parameter: raise ParameterNotFoundException(parameter_name) return parameter.list_files(path) def _init_parameters(self, username, audit_name): original_parameter_configs = self._original_config.get('parameters', []) for parameter_config in original_parameter_configs: parameter = ParameterModel(parameter_config, username, audit_name, lambda: self.parameters, self.parameter_values, self.working_directory) self.parameters.append(parameter) self._validate_parameter_configs() def _reload(self, old_included_config): self._reload_config() self._reload_parameters(old_included_config) def _reload_config(self): if self._included_config is None: config = self._original_config else: config = merge_dicts(self._original_config, self._included_config, ignored_keys=['parameters']) self.script_command = config.get('script_path') self.description = config.get('description') self.working_directory = config.get('working_directory') required_terminal = read_bool_from_config('requires_terminal', config, default=self._pty_enabled_default) self.requires_terminal = required_terminal ansi_enabled = read_bool_from_config('bash_formatting', config, default=self._ansi_enabled_default) self.ansi_enabled = ansi_enabled self.output_files = config.get('output_files', []) def _reload_parameters(self, old_included_config): original_parameters_names = {p.get('name') for p in self._original_config.get('parameters', [])} if old_included_config and old_included_config.get('parameters'): old_included_param_names = {p.get('name') for p in old_included_config.get('parameters', [])} for param_name in old_included_param_names: if param_name in original_parameters_names: continue parameter = self.find_parameter(param_name) self.parameters.remove(parameter) if self._included_config is not None: included_parameter_configs = self._included_config.get('parameters', []) for parameter_config in included_parameter_configs: parameter_name = parameter_config.get('name') parameter = self.find_parameter(parameter_name) if parameter is None: parameter = ParameterModel(parameter_config, self._username, self._audit_name, lambda: self.parameters, self.parameter_values, self.working_directory) self.parameters.append(parameter) if parameter.name not in self.parameter_values: self.parameter_values[parameter.name] = parameter.default continue else: LOGGER.warning('Parameter ' + parameter_name + ' exists in original and included file. ' + 'This is now allowed! Included parameter is ignored') continue def find_parameter(self, param_name): for parameter in self.parameters: if parameter.name == param_name: return parameter return None def _validate_parameter_configs(self): for parameter in self.parameters: parameter.validate_parameter_dependencies(self.parameters) def _path_to_json(self, path): if path is None: return None path = file_utils.normalize_path(path, self._config_folder) if os.path.exists(path): try: file_content = file_utils.read_file(path) return json.loads(file_content) except: LOGGER.exception('Failed to load included file ' + path) return None else: LOGGER.warning('Failed to load included file, path does not exist: ' + path) return None
class ConfigModel: def __init__(self, config_object, path, username, audit_name, pty_enabled_default=True, ansi_enabled_default=True, parameter_values=None): super().__init__() short_config = read_short(path, config_object) self.name = short_config.name self._ansi_enabled_default = ansi_enabled_default self._pty_enabled_default = pty_enabled_default self._config_folder = os.path.dirname(path) self._username = username self._audit_name = audit_name self.schedulable = False self.parameters = ObservableList() self.parameter_values = ObservableDict() self._original_config = config_object self._included_config_path = _TemplateProperty( config_object.get('include'), parameters=self.parameters, values=self.parameter_values) self._included_config_prop.bind(self._included_config_path, self._path_to_json) self._reload_config() self.parameters.subscribe(self) self._init_parameters(username, audit_name) if parameter_values is not None: self.set_all_param_values(parameter_values) else: for parameter in self.parameters: self.parameter_values[parameter.name] = parameter.default self._reload_parameters({}) self._included_config_prop.subscribe( lambda old, new: self._reload(old)) def set_param_value(self, param_name, value): parameter = self.find_parameter(param_name) if parameter is None: LOGGER.warning('Parameter ' + param_name + ' does not exist in ' + self.name) return validation_error = parameter.validate_value(value, ignore_required=True) if validation_error is not None: self.parameter_values[param_name] = None raise InvalidValueException(param_name, validation_error) self.parameter_values[param_name] = value def set_all_param_values(self, param_values): original_values = dict(self.parameter_values) processed = {} anything_changed = True while (len(processed) < len(self.parameters)) and anything_changed: anything_changed = False for parameter in self.parameters: if parameter.name in processed: continue required_parameters = parameter.get_required_parameters() if required_parameters and any(r not in processed for r in required_parameters): continue value = parameter.normalize_user_value( param_values.get(parameter.name)) validation_error = parameter.validate_value(value) if validation_error: self.parameter_values.set(original_values) raise InvalidValueException(parameter.name, validation_error) self.parameter_values[parameter.name] = value processed[parameter.name] = parameter anything_changed = True if not anything_changed: remaining = [ p.name for p in self.parameters if p.name not in processed ] self.parameter_values.set(original_values) raise Exception( 'Could not resolve order for dependencies. Remaining: ' + str(remaining)) for key, value in param_values.items(): if self.find_parameter(key) is None: LOGGER.warning('Incoming value for unknown parameter ' + key) def list_files_for_param(self, parameter_name, path): parameter = self.find_parameter(parameter_name) if not parameter: raise ParameterNotFoundException(parameter_name) return parameter.list_files(path) def _init_parameters(self, username, audit_name): original_parameter_configs = self._original_config.get( 'parameters', []) for parameter_config in original_parameter_configs: parameter = ParameterModel(parameter_config, username, audit_name, lambda: self.parameters, self.parameter_values, self.working_directory) self.parameters.append(parameter) self._validate_parameter_configs() def _reload(self, old_included_config): self._reload_config() self._reload_parameters(old_included_config) def _reload_config(self): if self._included_config is None: config = self._original_config else: config = merge_dicts(self._original_config, self._included_config, ignored_keys=['parameters']) self.script_command = config.get('script_path') self.description = replace_auth_vars(config.get('description'), self._username, self._audit_name) self.working_directory = config.get('working_directory') required_terminal = read_bool_from_config( 'requires_terminal', config, default=self._pty_enabled_default) self.requires_terminal = required_terminal ansi_enabled = read_bool_from_config( 'bash_formatting', config, default=self._ansi_enabled_default) self.ansi_enabled = ansi_enabled self.output_files = config.get('output_files', []) if config.get('scheduling'): self.schedulable = read_bool_from_config('enabled', config.get('scheduling'), default=False) if not self.script_command: raise Exception('No script_path is specified for ' + self.name) def _reload_parameters(self, old_included_config): original_parameters_names = { p.get('name') for p in self._original_config.get('parameters', []) } if old_included_config and old_included_config.get('parameters'): old_included_param_names = { p.get('name') for p in old_included_config.get('parameters', []) } for param_name in old_included_param_names: if param_name in original_parameters_names: continue parameter = self.find_parameter(param_name) self.parameters.remove(parameter) if self._included_config is not None: included_parameter_configs = self._included_config.get( 'parameters', []) for parameter_config in included_parameter_configs: parameter_name = parameter_config.get('name') parameter = self.find_parameter(parameter_name) if parameter is None: parameter = ParameterModel(parameter_config, self._username, self._audit_name, lambda: self.parameters, self.parameter_values, self.working_directory) self.parameters.append(parameter) if parameter.name not in self.parameter_values: self.parameter_values[ parameter.name] = parameter.default continue else: LOGGER.warning( 'Parameter ' + parameter_name + ' exists in original and included file. ' + 'This is now allowed! Included parameter is ignored') continue def find_parameter(self, param_name): for parameter in self.parameters: if parameter.name == param_name: return parameter return None def on_add(self, parameter, index): if self.schedulable and parameter.secure: LOGGER.warning( 'Disabling schedulable functionality, because parameter ' + parameter.str_name() + ' is secure') self.schedulable = False def on_remove(self, parameter): pass def _validate_parameter_configs(self): for parameter in self.parameters: parameter.validate_parameter_dependencies(self.parameters) def _path_to_json(self, path): if path is None: return None path = file_utils.normalize_path(path, self._config_folder) if os.path.exists(path): try: file_content = file_utils.read_file(path) return json.loads(file_content) except: LOGGER.exception('Failed to load included file ' + path) return None else: LOGGER.warning( 'Failed to load included file, path does not exist: ' + path) return None