2828import re
2929import traceback
3030import subprocess
31+ import json
3132
3233
3334class ObData (object ):
@@ -96,6 +97,14 @@ def __init__(self):
9697 def open_db (self , filename ):
9798 return apsw .Connection (filename )
9899
100+ def get_password_suffixes (self ):
101+ # Update here to add more password suffixes
102+ return [
103+ "_password" ,
104+ "_access_key" ,
105+ "_access_key_id" ,
106+ ]
107+
99108 def table_exists (self , table ):
100109 for row in self .execute (
101110 "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 = ?" ,
@@ -158,6 +167,8 @@ def __init__(self):
158167 self .headless = False
159168 self .args = None
160169 self .version = open ("VERSION" ).read ().strip ()
170+ self .secrets_file = self .datadir + "/.secrets.json"
171+
161172 srcpath = os .path .dirname (os .path .dirname (obplayer .__file__ ))
162173 branch = subprocess .Popen (
163174 'cd "{0}" && git branch' .format (srcpath ), stdout = subprocess .PIPE , shell = True
@@ -183,10 +194,16 @@ def __init__(self):
183194 self .settings_cache = {}
184195 self .settings_type = {}
185196
197+ # Load secrets from JSON
198+ secrets = self .load_secrets_file ()
199+ secrets_migrated = False
200+
186201 rows = self .query ("SELECT name,value,type FROM 'settings'" )
187202 for row in rows :
203+ name = row ["name" ]
188204 value = row ["value" ]
189205 datatype = row ["type" ]
206+
190207 if datatype == "int" :
191208 value = int (value )
192209 elif datatype == "float" :
@@ -195,8 +212,39 @@ def __init__(self):
195212 value = bool (int (value ))
196213 else :
197214 value = str (value )
198- self .settings_cache [row ["name" ]] = value
199- self .settings_type [row ["name" ]] = datatype
215+
216+ # --- Migration Logic ---
217+ # If this is a password field, check if it is real data in DB.
218+ # If so, move to secrets and scrub DB.
219+ password_suffixes = self .get_password_suffixes ()
220+ is_password_field = any (
221+ name .endswith (suffix ) for suffix in password_suffixes
222+ )
223+
224+ if is_password_field :
225+ # If value is not the placeholder, we need to migrate it
226+ if value != "__SECRET__" and value != "" :
227+ secrets [name ] = value
228+ value = "__SECRET__" # Scrub local variable
229+ # Update DB to placeholder immediately
230+ self .execute (
231+ 'UPDATE settings set value="__SECRET__" where name="'
232+ + self .escape (name )
233+ + '"'
234+ )
235+ secrets_migrated = True
236+
237+ # If the secret exists in the JSON file, override the DB value (which should be placeholder)
238+ if name in secrets :
239+ value = secrets [name ]
240+ # -----------------------
241+
242+ self .settings_cache [name ] = value
243+ self .settings_type [name ] = datatype
244+
245+ # If we migrated old DB passwords to JSON, save the JSON file now
246+ if secrets_migrated :
247+ self .write_secrets_file (secrets )
200248
201249 # keep track of settings as they have been edited.
202250 # they don't take effect until restart, but we want to keep track of them for subsequent edits.
@@ -205,6 +253,25 @@ def __init__(self):
205253 if not self .setting ("video_out_enable" ):
206254 self .headless = True
207255
256+ def load_secrets_file (self ):
257+ if os .path .exists (self .secrets_file ):
258+ try :
259+ with open (self .secrets_file , "r" ) as f :
260+ return json .load (f )
261+ except (IOError , ValueError ):
262+ # File missing or corrupt JSON
263+ return {}
264+ return {}
265+
266+ def write_secrets_file (self , secrets_data ):
267+ try :
268+ # Set permissions so only owner can read/write (600)
269+ with open (self .secrets_file , "w" ) as f :
270+ json .dump (secrets_data , f , indent = 4 , sort_keys = True )
271+ os .chmod (self .secrets_file , 0o600 )
272+ except IOError :
273+ print ("Error writing to secrets file: " + self .secrets_file )
274+
208275 def validate_settings (self , settings ):
209276 for setting_name , setting_value in settings .items ():
210277 error = self .validate_setting (setting_name , setting_value , settings )
@@ -713,9 +780,23 @@ def add_setting(self, name, value, datatype=None):
713780 if len (check_setting ):
714781 return
715782
783+ # If this is a default setting being added, and it's a password,
784+ # we should put the actual value in secrets.json and a placeholder in DB
785+ password_suffixes = self .get_password_suffixes ()
786+ is_password_field = any (name .endswith (suffix ) for suffix in password_suffixes )
787+
788+ db_value = value
789+ if is_password_field and value :
790+ secrets = self .load_secrets_file ()
791+ # Only write to secrets if not already there (prevents overwriting existing user secrets with defaults)
792+ if name not in secrets :
793+ secrets [name ] = value
794+ self .write_secrets_file (secrets )
795+ db_value = "__SECRET__"
796+
716797 data = {}
717798 data ["name" ] = name
718- data ["value" ] = value
799+ data ["value" ] = db_value
719800
720801 if datatype != None :
721802 data ["type" ] = datatype
@@ -731,6 +812,11 @@ def setting(self, name, use_edit_cache=False):
731812
732813 # save our settings into the database. update settings_edit_cache to handle subsequent edits.
733814 def save_settings (self , settings ):
815+
816+ secrets = self .load_secrets_file ()
817+ secrets_updated = False
818+ password_suffixes = self .get_password_suffixes ()
819+
734820 for name , value in settings .items ():
735821 dataType = self .settings_type [name ]
736822 if dataType == "int" :
@@ -742,22 +828,36 @@ def save_settings(self, settings):
742828 else :
743829 self .settings_edit_cache [name ] = str (value )
744830
831+ # Check if this is a secret
832+ is_password_field = any (
833+ name .endswith (suffix ) for suffix in password_suffixes
834+ )
835+
836+ db_value = value
837+ if is_password_field :
838+ secrets [name ] = str (value )
839+ secrets_updated = True
840+ db_value = "__SECRET__"
841+
745842 self .query (
746843 'UPDATE settings set value="'
747- + self .escape (str (value ))
844+ + self .escape (str (db_value ))
748845 + '" where name="'
749846 + self .escape (name )
750847 + '"'
751848 )
752849
850+ if secrets_updated :
851+ self .write_secrets_file (secrets )
852+
753853 def list_settings (self , hidepasswords = False ):
754854 result = {}
755855 for name , value in self .settings_cache .items ():
756- if (
757- not hidepasswords
758- or not name .endswith ("_password" )
759- and not name . endswith ( "_access_key" )
760- and not name . endswith ( "_access_key_id" )
761- ) :
856+ password_suffixes = self . get_password_suffixes ()
857+ is_password_field = any (
858+ name .endswith (suffix ) for suffix in password_suffixes
859+ )
860+
861+ if not hidepasswords or not is_password_field :
762862 result [name ] = value
763863 return result
0 commit comments