From 34eb65ae28fb09ff17a4ca9fdc4b2cb89eb3968f Mon Sep 17 00:00:00 2001 From: Troy Date: Mon, 10 Nov 2025 17:08:15 -0700 Subject: [PATCH 1/4] Changed header dashboard settings file name string. --- obplayer/httpadmin/httpadmin.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/obplayer/httpadmin/httpadmin.py b/obplayer/httpadmin/httpadmin.py index 9562658..5b9888b 100644 --- a/obplayer/httpadmin/httpadmin.py +++ b/obplayer/httpadmin/httpadmin.py @@ -493,7 +493,12 @@ def req_export(self, request): ) res = httpserver.Response() - res.add_header("Content-Disposition", "attachment; filename=obsettings.txt") + res.add_header( + "Content-Disposition", + "attachment; filename=" + + re.sub(r"[^\w\d_-]", "_", self.title) + + " dashboard_settings.txt", + ) res.send_content("text/plain", settings) return res From 80dcee13adeffcbc18ec786e5130c75ed51b2a6b Mon Sep 17 00:00:00 2001 From: Troy Date: Mon, 10 Nov 2025 17:19:19 -0700 Subject: [PATCH 2/4] Changed to a more explict method of filtering password suffixes. --- obplayer/data.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/obplayer/data.py b/obplayer/data.py index 01f5d8e..c01344b 100644 --- a/obplayer/data.py +++ b/obplayer/data.py @@ -753,11 +753,15 @@ def save_settings(self, settings): def list_settings(self, hidepasswords=False): result = {} for name, value in self.settings_cache.items(): - if ( - not hidepasswords - or not name.endswith("_password") - and not name.endswith("_access_key") - and not name.endswith("_access_key_id") - ): + password_suffixes = [ + "_password", + "_access_key", + "_access_key_id", + ] + is_password_field = any( + name.endswith(suffix) for suffix in password_suffixes + ) + + if not hidepasswords or not is_password_field: result[name] = value return result From 2b07661f183514993fb20e6b2891ae44dcdfacf3 Mon Sep 17 00:00:00 2001 From: Troy Date: Tue, 18 Nov 2025 16:16:07 -0700 Subject: [PATCH 3/4] Added get_password_suffixes method --- obplayer/data.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/obplayer/data.py b/obplayer/data.py index c01344b..319ce1c 100644 --- a/obplayer/data.py +++ b/obplayer/data.py @@ -96,6 +96,14 @@ def __init__(self): def open_db(self, filename): return apsw.Connection(filename) + def get_password_suffixes(self): + # Update here to add more password suffixes + return [ + "_password", + "_access_key", + "_access_key_id", + ] + def table_exists(self, table): for row in self.execute( "SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name = ? UNION ALL SELECT name FROM sqlite_temp_master WHERE type IN ('table','view') AND name = ?", @@ -753,11 +761,7 @@ def save_settings(self, settings): def list_settings(self, hidepasswords=False): result = {} for name, value in self.settings_cache.items(): - password_suffixes = [ - "_password", - "_access_key", - "_access_key_id", - ] + password_suffixes = self.get_password_suffixes() is_password_field = any( name.endswith(suffix) for suffix in password_suffixes ) From 8fa87fe61ecb1c2e6476223d7c7e41054434b683 Mon Sep 17 00:00:00 2001 From: Troy Date: Tue, 18 Nov 2025 18:35:07 -0700 Subject: [PATCH 4/4] Added json secrets code --- obplayer/data.py | 100 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 4 deletions(-) diff --git a/obplayer/data.py b/obplayer/data.py index 319ce1c..6fcc59c 100644 --- a/obplayer/data.py +++ b/obplayer/data.py @@ -28,6 +28,7 @@ import re import traceback import subprocess +import json class ObData(object): @@ -166,6 +167,8 @@ def __init__(self): self.headless = False self.args = None self.version = open("VERSION").read().strip() + self.secrets_file = self.datadir + "/.secrets.json" + srcpath = os.path.dirname(os.path.dirname(obplayer.__file__)) branch = subprocess.Popen( 'cd "{0}" && git branch'.format(srcpath), stdout=subprocess.PIPE, shell=True @@ -191,10 +194,16 @@ def __init__(self): self.settings_cache = {} self.settings_type = {} + # Load secrets from JSON + secrets = self.load_secrets_file() + secrets_migrated = False + rows = self.query("SELECT name,value,type FROM 'settings'") for row in rows: + name = row["name"] value = row["value"] datatype = row["type"] + if datatype == "int": value = int(value) elif datatype == "float": @@ -203,8 +212,39 @@ def __init__(self): value = bool(int(value)) else: value = str(value) - self.settings_cache[row["name"]] = value - self.settings_type[row["name"]] = datatype + + # --- Migration Logic --- + # If this is a password field, check if it is real data in DB. + # If so, move to secrets and scrub DB. + password_suffixes = self.get_password_suffixes() + is_password_field = any( + name.endswith(suffix) for suffix in password_suffixes + ) + + if is_password_field: + # If value is not the placeholder, we need to migrate it + if value != "__SECRET__" and value != "": + secrets[name] = value + value = "__SECRET__" # Scrub local variable + # Update DB to placeholder immediately + self.execute( + 'UPDATE settings set value="__SECRET__" where name="' + + self.escape(name) + + '"' + ) + secrets_migrated = True + + # If the secret exists in the JSON file, override the DB value (which should be placeholder) + if name in secrets: + value = secrets[name] + # ----------------------- + + self.settings_cache[name] = value + self.settings_type[name] = datatype + + # If we migrated old DB passwords to JSON, save the JSON file now + if secrets_migrated: + self.write_secrets_file(secrets) # keep track of settings as they have been edited. # they don't take effect until restart, but we want to keep track of them for subsequent edits. @@ -213,6 +253,25 @@ def __init__(self): if not self.setting("video_out_enable"): self.headless = True + def load_secrets_file(self): + if os.path.exists(self.secrets_file): + try: + with open(self.secrets_file, "r") as f: + return json.load(f) + except (IOError, ValueError): + # File missing or corrupt JSON + return {} + return {} + + def write_secrets_file(self, secrets_data): + try: + # Set permissions so only owner can read/write (600) + with open(self.secrets_file, "w") as f: + json.dump(secrets_data, f, indent=4, sort_keys=True) + os.chmod(self.secrets_file, 0o600) + except IOError: + print("Error writing to secrets file: " + self.secrets_file) + def validate_settings(self, settings): for setting_name, setting_value in settings.items(): error = self.validate_setting(setting_name, setting_value, settings) @@ -721,9 +780,23 @@ def add_setting(self, name, value, datatype=None): if len(check_setting): return + # If this is a default setting being added, and it's a password, + # we should put the actual value in secrets.json and a placeholder in DB + password_suffixes = self.get_password_suffixes() + is_password_field = any(name.endswith(suffix) for suffix in password_suffixes) + + db_value = value + if is_password_field and value: + secrets = self.load_secrets_file() + # Only write to secrets if not already there (prevents overwriting existing user secrets with defaults) + if name not in secrets: + secrets[name] = value + self.write_secrets_file(secrets) + db_value = "__SECRET__" + data = {} data["name"] = name - data["value"] = value + data["value"] = db_value if datatype != None: data["type"] = datatype @@ -739,6 +812,11 @@ def setting(self, name, use_edit_cache=False): # save our settings into the database. update settings_edit_cache to handle subsequent edits. def save_settings(self, settings): + + secrets = self.load_secrets_file() + secrets_updated = False + password_suffixes = self.get_password_suffixes() + for name, value in settings.items(): dataType = self.settings_type[name] if dataType == "int": @@ -750,14 +828,28 @@ def save_settings(self, settings): else: self.settings_edit_cache[name] = str(value) + # Check if this is a secret + is_password_field = any( + name.endswith(suffix) for suffix in password_suffixes + ) + + db_value = value + if is_password_field: + secrets[name] = str(value) + secrets_updated = True + db_value = "__SECRET__" + self.query( 'UPDATE settings set value="' - + self.escape(str(value)) + + self.escape(str(db_value)) + '" where name="' + self.escape(name) + '"' ) + if secrets_updated: + self.write_secrets_file(secrets) + def list_settings(self, hidepasswords=False): result = {} for name, value in self.settings_cache.items():