def __init__(self, template, parameters: ObservableList, values: ObservableDict, empty=None) -> None: self._value_property = Property(None) self._template = template self._values = values self._empty = empty self._parameters = parameters pattern = re.compile('\${([^}]+)\}') search_start = 0 script_template = '' required_parameters = set() if template: while search_start < len(template): match = pattern.search(template, search_start) if not match: script_template += template[search_start:] break param_start = match.start() if param_start > search_start: script_template += template[search_start:param_start] param_name = match.group(1) required_parameters.add(param_name) search_start = match.end() + 1 self.required_parameters = tuple(required_parameters) self._reload() if self.required_parameters: values.subscribe(self._value_changed) parameters.subscribe(self)
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