Skip to content

Commit

Permalink
Merge pull request #1893 from bunkerity/dev
Browse files Browse the repository at this point in the history
Fix multiples issues with web UI
  • Loading branch information
TheophileDiot authored Jan 10, 2025
2 parents 9f1da55 + c97fd79 commit c75c24f
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 25 deletions.
14 changes: 13 additions & 1 deletion src/common/core/templates/templates/high.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
"LETS_ENCRYPT_DNS_PROVIDER": "",
"LETS_ENCRYPT_DNS_PROPAGATION": "default",
"LETS_ENCRYPT_DNS_CREDENTIAL_ITEM": "",
"USE_CUSTOM_SSL": "no",
"CUSTOM_SSL_CERT_PRIORITY": "file",
"CUSTOM_SSL_CERT": "",
"CUSTOM_SSL_KEY": "",
"CUSTOM_SSL_CERT_DATA": "",
"CUSTOM_SSL_KEY_DATA": "",
"ALLOWED_METHODS": "GET|POST|HEAD",
"MAX_CLIENT_SIZE": "10m",
"HTTP2": "yes",
Expand Down Expand Up @@ -81,7 +87,13 @@
"LETS_ENCRYPT_CHALLENGE",
"LETS_ENCRYPT_DNS_PROVIDER",
"LETS_ENCRYPT_DNS_PROPAGATION",
"LETS_ENCRYPT_DNS_CREDENTIAL_ITEM"
"LETS_ENCRYPT_DNS_CREDENTIAL_ITEM",
"USE_CUSTOM_SSL",
"CUSTOM_SSL_CERT_PRIORITY",
"CUSTOM_SSL_CERT",
"CUSTOM_SSL_KEY",
"CUSTOM_SSL_CERT_DATA",
"CUSTOM_SSL_KEY_DATA"
]
},
{
Expand Down
14 changes: 13 additions & 1 deletion src/common/core/templates/templates/low.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
"LETS_ENCRYPT_DNS_PROVIDER": "",
"LETS_ENCRYPT_DNS_PROPAGATION": "default",
"LETS_ENCRYPT_DNS_CREDENTIAL_ITEM": "",
"USE_CUSTOM_SSL": "no",
"CUSTOM_SSL_CERT_PRIORITY": "file",
"CUSTOM_SSL_CERT": "",
"CUSTOM_SSL_KEY": "",
"CUSTOM_SSL_CERT_DATA": "",
"CUSTOM_SSL_KEY_DATA": "",
"ALLOWED_METHODS": "GET|POST|HEAD|OPTIONS|PUT|DELETE|PATCH",
"MAX_CLIENT_SIZE": "100m",
"HTTP2": "yes",
Expand Down Expand Up @@ -79,7 +85,13 @@
"LETS_ENCRYPT_CHALLENGE",
"LETS_ENCRYPT_DNS_PROVIDER",
"LETS_ENCRYPT_DNS_PROPAGATION",
"LETS_ENCRYPT_DNS_CREDENTIAL_ITEM"
"LETS_ENCRYPT_DNS_CREDENTIAL_ITEM",
"USE_CUSTOM_SSL",
"CUSTOM_SSL_CERT_PRIORITY",
"CUSTOM_SSL_CERT",
"CUSTOM_SSL_KEY",
"CUSTOM_SSL_CERT_DATA",
"CUSTOM_SSL_KEY_DATA"
]
},
{
Expand Down
14 changes: 13 additions & 1 deletion src/common/core/templates/templates/medium.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
"LETS_ENCRYPT_DNS_PROVIDER": "",
"LETS_ENCRYPT_DNS_PROPAGATION": "default",
"LETS_ENCRYPT_DNS_CREDENTIAL_ITEM": "",
"USE_CUSTOM_SSL": "no",
"CUSTOM_SSL_CERT_PRIORITY": "file",
"CUSTOM_SSL_CERT": "",
"CUSTOM_SSL_KEY": "",
"CUSTOM_SSL_CERT_DATA": "",
"CUSTOM_SSL_KEY_DATA": "",
"ALLOWED_METHODS": "GET|POST|HEAD|OPTIONS|PUT|DELETE|PATCH",
"MAX_CLIENT_SIZE": "50m",
"HTTP2": "yes",
Expand Down Expand Up @@ -81,7 +87,13 @@
"LETS_ENCRYPT_CHALLENGE",
"LETS_ENCRYPT_DNS_PROVIDER",
"LETS_ENCRYPT_DNS_PROPAGATION",
"LETS_ENCRYPT_DNS_CREDENTIAL_ITEM"
"LETS_ENCRYPT_DNS_CREDENTIAL_ITEM",
"USE_CUSTOM_SSL",
"CUSTOM_SSL_CERT_PRIORITY",
"CUSTOM_SSL_CERT",
"CUSTOM_SSL_KEY",
"CUSTOM_SSL_CERT_DATA",
"CUSTOM_SSL_KEY_DATA"
]
},
{
Expand Down
4 changes: 2 additions & 2 deletions src/common/core/ui/confs/modsec-crs/ui.conf
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{%- if USE_UI == "yes" -%}
SecRule REQUEST_FILENAME "@rx /(global-config|services/.+)$" "id:1007771,ctl:ruleRemoveById=932235,nolog"
SecRule REQUEST_FILENAME "@rx /(global-config|services/.+)$" "id:1007771,ctl:ruleRemoveById=932235,ctl:ruleRemoveByTag=attack-rfi,nolog"
SecRule REQUEST_FILENAME "@rx /(services|cache)/.+$" "id:1007772,ctl:ruleRemoveById=920440,nolog"
SecRule REQUEST_FILENAME "@rx /(configs)/.+$" "id:1007773,ctl:ruleRemoveByTag=attack-rce,nolog"
SecRule REQUEST_FILENAME "@rx /(configs)/.+$" "id:1007773,ctl:ruleRemoveByTag=attack-rce,ctl:ruleRemoveByTag=attack-rfi,nolog"
SecRule REQUEST_FILENAME "@endsWith /logs" "id:1007774,ctl:ruleRemoveById=953100,nolog"
{%- endif %}
4 changes: 1 addition & 3 deletions src/ui/app/models/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ def check_variables(
config: dict,
*,
global_config: bool = False,
ignored_multiples: Optional[Set[str]] = None,
new: bool = False,
threaded: bool = False,
) -> dict:
Expand Down Expand Up @@ -197,9 +196,8 @@ def check_variables(
flash(message, "error")
variables.pop(k)

ignored_multiples = ignored_multiples or set()
for k in config:
if k in plugins_settings or k in ignored_multiples:
if k in plugins_settings:
continue
setting = k[0 : k.rfind("_")] # noqa: E203

Expand Down
7 changes: 3 additions & 4 deletions src/ui/app/routes/global_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,18 @@ def update_global_config(variables: Dict[str, str], threaded: bool = False):
wait_applying()

# Edit check fields and remove already existing ones
config = DB.get_config(methods=True, with_drafts=True, filtered_settings=list(variables.keys()))
config = DB.get_config(methods=True, with_drafts=True)
services = config["SERVER_NAME"]["value"].split(" ")
ignored_multiples = set()

for variable, value in variables.copy().items():
setting = config.get(variable, {"value": None, "global": True})
if setting["global"] and value == setting["value"]:
if match(r"^.+_\d+$", variable):
ignored_multiples.add(variable)
continue
del variables[variable]
continue

variables = BW_CONFIG.check_variables(variables, config, global_config=True, ignored_multiples=ignored_multiples, threaded=threaded)
variables = BW_CONFIG.check_variables(variables, config, global_config=True, threaded=threaded)

if not variables:
content = "The global configuration was not edited because no values were changed."
Expand Down
3 changes: 3 additions & 0 deletions src/ui/app/routes/instances.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ def instances_new():
@instances.route("/instances/<string:action>", methods=["POST"])
@login_required
def instances_action(action: Literal["ping", "reload", "stop", "delete"]): # TODO: see if we can support start and restart
if DB.readonly:
return handle_error("Database is in read-only mode", "instances")

verify_data_in_form(
data={"instances": None},
err_message=f"Missing instances parameter on /instances/{action}.",
Expand Down
7 changes: 3 additions & 4 deletions src/ui/app/routes/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,11 @@ def update_service(service: str, variables: Dict[str, str], is_draft: bool, mode
if service != "new":
db_config = DB.get_config(methods=True, with_drafts=True, service=service)
else:
db_config = DB.get_config(global_only=True, methods=True, filtered_settings=list(variables.keys()))
db_config = DB.get_config(global_only=True, methods=True)

was_draft = db_config.get("IS_DRAFT", {"value": "no"})["value"] == "yes"

old_server_name = variables.pop("OLD_SERVER_NAME", "")
ignored_multiples = set()
db_custom_configs = {}
new_configs = set()
configs_changed = False
Expand Down Expand Up @@ -279,10 +278,10 @@ def update_service(service: str, variables: Dict[str, str], is_draft: bool, mode
for variable, value in variables.copy().items():
if (mode == "advanced" or variable != "SERVER_NAME") and value == db_config.get(variable, {"value": None})["value"]:
if match(r"^.+_\d+$", variable):
ignored_multiples.add(variable)
continue
del variables[variable]

variables = BW_CONFIG.check_variables(variables, db_config, ignored_multiples=ignored_multiples, new=service == "new", threaded=True)
variables = BW_CONFIG.check_variables(variables, db_config, new=service == "new", threaded=True)

if service != "new" and was_draft == is_draft and not variables and not configs_changed:
DATA["TO_FLASH"].append(
Expand Down
11 changes: 9 additions & 2 deletions src/ui/app/static/js/plugins-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,10 +303,17 @@ $(document).ready(() => {
settingValue = $this.is(":checked") ? "yes" : "no";
}

// Check if it's a multiple setting with numeric suffix
const isMultipleSetting =
settingName &&
$this.attr("id").startsWith("multiple-") &&
/_\d+$/.test(settingName);

if (
!isEasy &&
settingName !== "SERVER_NAME" &&
settingValue == originalValue
settingValue == originalValue &&
!isMultipleSetting
)
return;

Expand Down Expand Up @@ -1117,7 +1124,7 @@ $(document).ready(() => {
}, 30);
});

$(".plugin-setting").on("keydown", function (e) {
$(document).on("keydown", ".plugin-setting", function () {
if (e.key === "Enter") {
e.preventDefault();
$(".save-settings").trigger("click");
Expand Down
18 changes: 17 additions & 1 deletion src/ui/app/templates/models/plugins_settings_raw.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
{% for plugin_data in plugins.values() %}
{% set filtered_settings = get_filtered_settings(plugin_data["settings"], current_endpoint == "global-config") %}
{% if filtered_settings %}
{% for setting, setting_data in filtered_settings.items() if setting not in blacklisted_settings %}
{% for setting, setting_data in filtered_settings.items() if not setting_data.get('multiple', false) and setting not in blacklisted_settings %}
{% set setting_config = config.get(setting, {}) %}
{% set setting_default = setting_data.get("default", "") %}
{% set setting_value = setting_config.get("value", setting_default) %}
Expand All @@ -49,6 +49,22 @@
{% endif %}
{% endfor %}
{% endif %}
{% set plugin_multiples = get_multiples(filtered_settings, config) %}
{% if plugin_multiples %}
{% for multiple, multiples in plugin_multiples.items() %}
{% for setting_suffix, settings in multiples.items() %}
{% for setting, setting_data in settings.items() if setting not in blacklisted_settings %}
{% set setting_config = config.get(setting, {}) %}
{% set setting_default = setting_data.get("default", "") %}
{% set setting_value = setting_config.get("value", setting_default) %}
{% if setting_value != setting_default %}
{% if config_lines.append(setting + "=" + setting_value) %}{% endif %}
{% if default_settings.append(setting + "=" + setting_default) %}{% endif %}
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
{% endif %}
{% endfor %}
<!-- Join the configuration lines with newlines -->
{% set raw_config = config_lines | join('\r\n') %}
Expand Down
33 changes: 27 additions & 6 deletions src/ui/app/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,32 +102,53 @@ def handle_stop(signum, frame):

def get_multiples(settings: dict, config: dict) -> Dict[str, Dict[str, Dict[str, dict]]]:
plugin_multiples = {}

for setting, data in settings.items():
multiple = data.get("multiple")
if multiple:
# Add the setting without suffix for reference
data = data | {"setting_no_suffix": setting}

if multiple not in plugin_multiples:
plugin_multiples[multiple] = {}
if "0" not in plugin_multiples[multiple]:
plugin_multiples[multiple]["0"] = {}

# Add the base (suffix "0") setting
plugin_multiples[multiple]["0"].update({setting: data})

for config_setting in config:
# Process config settings with suffixes
for config_setting, value in config.items():
setting_match = match(setting + r"_(?P<suffix>\d+)$", config_setting)
if setting_match:
suffix = setting_match.group("suffix")
if suffix == "0":
continue

if suffix not in plugin_multiples[multiple]:
plugin_multiples[multiple][suffix] = {}
plugin_multiples[multiple][suffix].update({config_setting: data})
plugin_multiples[multiple][suffix][config_setting] = {
**data,
"value": value, # Include the value from the config
}

# Ensure every suffix group has all settings in the same order as "0"
base_settings = plugin_multiples[multiple]["0"]
for suffix, settings_dict in plugin_multiples[multiple].items():
if suffix == "0":
continue
for default_setting, default_data in base_settings.items():
if f"{default_setting}_{suffix}" not in settings_dict:
settings_dict[f"{default_setting}_{suffix}"] = {
**default_data,
"value": default_data.get("value"), # Default value if not in config
}

# Preserve the order of settings based on suffix "0"
plugin_multiples[multiple][suffix] = {
f"{default_setting}_{suffix}": settings_dict[f"{default_setting}_{suffix}"] for default_setting in base_settings
}

# Sort the multiples and their settings
for multiple, multiples in plugin_multiples.items():
plugin_multiples[multiple] = dict(sorted(multiples.items()))
plugin_multiples[multiple] = dict(sorted(multiples.items(), key=lambda x: int(x[0])))

return plugin_multiples

Expand Down

0 comments on commit c75c24f

Please sign in to comment.