Commit a8cbf4ca authored by segfault's avatar segfault

Greeter: Refactor: Redesign how settings are saved and loaded

Settings are now saved to file immediately when the user changes them,
instead of before login. Beside making the code simpler, this makes it
easier to handle saving the admin password file (which we want to delete
if the user disabled the feature after loading a persistent password
file, but want to overwrite or leave untouched in other cases).
parent a047e3be
...@@ -71,7 +71,6 @@ class GreeterApplication(object): ...@@ -71,7 +71,6 @@ class GreeterApplication(object):
persistence = PersistenceSettings() persistence = PersistenceSettings()
self.localisationsettings = LocalisationSettings( self.localisationsettings = LocalisationSettings(
usermanager_loaded_cb=self.usermanager_loaded, usermanager_loaded_cb=self.usermanager_loaded,
locale_selected_cb=self.on_language_changed
) )
self.admin_setting = AdminSetting() self.admin_setting = AdminSetting()
self.network_setting = NetworkSetting() self.network_setting = NetworkSetting()
...@@ -79,7 +78,7 @@ class GreeterApplication(object): ...@@ -79,7 +78,7 @@ class GreeterApplication(object):
# Initialize the settings # Initialize the settings
self.settings = GreeterSettingsCollection( self.settings = GreeterSettingsCollection(
LanguageSettingUI(self.localisationsettings.language), LanguageSettingUI(self.localisationsettings.language, self.on_language_changed),
KeyboardSettingUI(self.localisationsettings.keyboard), KeyboardSettingUI(self.localisationsettings.keyboard),
FormatsSettingUI(self.localisationsettings.formats), FormatsSettingUI(self.localisationsettings.formats),
AdminSettingUI(self.admin_setting), AdminSettingUI(self.admin_setting),
...@@ -90,6 +89,10 @@ class GreeterApplication(object): ...@@ -90,6 +89,10 @@ class GreeterApplication(object):
# Initialize main window # Initialize main window
self.mainwindow = GreeterMainWindow(self, persistence, self.settings) self.mainwindow = GreeterMainWindow(self, persistence, self.settings)
# Apply the default settings
for setting in self.settings:
setting.apply()
# Inhibit the session being marked as idle # Inhibit the session being marked as idle
self.inhibit_idle() self.inhibit_idle()
...@@ -97,38 +100,15 @@ class GreeterApplication(object): ...@@ -97,38 +100,15 @@ class GreeterApplication(object):
"""Translate all windows to target language""" """Translate all windows to target language"""
TranslatableWindow.translate_all(lang) TranslatableWindow.translate_all(lang)
def load_settings(self):
if self.localisationsettings.language.load():
self.settings["language"].selected_code = self.localisationsettings.language.value
self.settings["language"].apply()
if self.localisationsettings.formats.load():
self.settings["formats"].selected_code = self.localisationsettings.formats.value
self.settings["formats"].apply()
if self.localisationsettings.keyboard.load():
self.settings["keyboard"].selected_code = self.localisationsettings.keyboard.value
self.settings["keyboard"].apply()
if self.admin_setting.load():
self.settings["admin"].password = self.admin_setting.password
self.mainwindow.add_setting("admin")
if self.network_setting.load():
if self.network_setting.value != self.settings["network"].value:
self.settings["network"].value = self.network_setting.value
self.mainwindow.add_setting("network")
if self.macspoof_setting.load():
if self.settings["macspoof"].spoofing_enabled != self.macspoof_setting.value:
self.settings["macspoof"].spoofing_enabled = self.macspoof_setting.value
self.mainwindow.add_setting("macspoof")
def login(self): def login(self):
"""Login GDM to the server""" """Login GDM to the server"""
logging.debug("login called") logging.debug("login called")
# Apply settings # Apply settings
# We now apply all settings immediately when they are
# changed. The only thing that still happens here is
# concatenating the locale settings files.
self.localisationsettings.apply_to_upcoming_session() self.localisationsettings.apply_to_upcoming_session()
self.admin_setting.apply_to_upcoming_session()
self.macspoof_setting.apply_to_upcoming_session()
self.network_setting.apply_to_upcoming_session()
self.mainwindow.hide() self.mainwindow.hide()
self.gdmclient.do_login() self.gdmclient.do_login()
...@@ -142,10 +122,9 @@ class GreeterApplication(object): ...@@ -142,10 +122,9 @@ class GreeterApplication(object):
def on_language_changed(self, locale_code: str): def on_language_changed(self, locale_code: str):
"""Translate to the given locale""" """Translate to the given locale"""
self.localisationsettings.formats.on_language_changed(locale_code) # XXX: notify for setting in self.settings.region_settings:
self.settings["formats"].update_value_label() setting.on_language_changed(locale_code)
self.localisationsettings.keyboard.on_language_changed(locale_code) # XXX: notify
self.settings["keyboard"].update_value_label()
self.translate_to(locale_code) self.translate_to(locale_code)
self.mainwindow.current_language = localization.language_from_locale(locale_code) self.mainwindow.current_language = localization.language_from_locale(locale_code)
......
...@@ -11,45 +11,38 @@ from tailsgreeter.settings.utils import read_settings, write_settings ...@@ -11,45 +11,38 @@ from tailsgreeter.settings.utils import read_settings, write_settings
class AdminSetting(object): class AdminSetting(object):
"""Setting controlling the sudo password""" """Setting controlling the sudo password"""
def __init__(self): settings_file = tailsgreeter.config.admin_password_path
self.password = None
self.settings_file = tailsgreeter.config.admin_password_path def save(self, password: str):
proc = subprocess.run(
def apply_to_upcoming_session(self): ["mkpasswd", "-s", "--method=sha512crypt"],
if self.password: input=pipes.quote(password).encode(),
proc = subprocess.run( capture_output=True,
["mkpasswd", "-s", "--method=sha512crypt"], check=True,
input=pipes.quote(self.password).encode(), )
capture_output=True, hashed_and_salted_pw = proc.stdout.decode().strip()
check=True,
) write_settings(self.settings_file, {
hashed_and_salted_pw = proc.stdout.decode().strip() 'TAILS_USER_PASSWORD': pipes.quote(hashed_and_salted_pw),
})
write_settings(self.settings_file, { logging.debug('password written to %s', self.settings_file)
'TAILS_USER_PASSWORD': pipes.quote(hashed_and_salted_pw),
}) def delete(self):
logging.debug('password written to %s', self.settings_file)
return
# Try to remove the password file # Try to remove the password file
try: try:
os.unlink(self.settings_file) os.unlink(self.settings_file)
logging.debug('removed %s', self.settings_file) logging.debug('removed %s', self.settings_file)
except OSError: except OSError:
# It's bad if the file exists and couldn't be removed, so we # It's bad if the file exists and couldn't be removed, so we
# we raise the exception in that case (which prevents the login) # we raise the exception in that case
if os.path.exists(self.settings_file): if os.path.exists(self.settings_file):
raise raise
def load(self) -> bool: def load(self) -> {str, None}:
try: try:
settings = read_settings(self.settings_file) settings = read_settings(self.settings_file)
except FileNotFoundError: except FileNotFoundError:
logging.debug("No persistent admin settings file found (path: %s)", self.settings_file) logging.debug("No persistent admin settings file found (path: %s)", self.settings_file)
return False return None
password = settings.get('TAILS_USER_PASSWORD') return settings.get('TAILS_USER_PASSWORD')
if password:
self.password = password
logging.debug("Loaded admin password setting")
return True
import gi import gi
import logging import logging
import os
import tailsgreeter.config import tailsgreeter.config
from tailsgreeter.settings.localization import LocalizationSetting, language_from_locale, country_from_locale from tailsgreeter.settings.localization import LocalizationSetting, language_from_locale, country_from_locale
...@@ -15,30 +14,30 @@ from gi.repository import GObject, GnomeDesktop, Gtk ...@@ -15,30 +14,30 @@ from gi.repository import GObject, GnomeDesktop, Gtk
class FormatsSetting(LocalizationSetting): class FormatsSetting(LocalizationSetting):
def __init__(self, language_codes: [str]): def __init__(self, language_codes: [str]):
super().__init__() super().__init__()
self.value = 'en_US'
self.locales_per_country = self._make_locales_per_country_dict(language_codes) self.locales_per_country = self._make_locales_per_country_dict(language_codes)
self.settings_file = tailsgreeter.config.formats_setting_path self.settings_file = tailsgreeter.config.formats_setting_path
def apply_to_upcoming_session(self): def save(self, locale: str, is_default: bool):
write_settings(self.settings_file, { write_settings(self.settings_file, {
'TAILS_FORMATS': self.get_value(), 'TAILS_FORMATS': locale,
'IS_DEFAULT': str(not self.value_changed_by_user).lower(), 'IS_DEFAULT': str(is_default).lower(),
}) })
def load(self) -> bool: def load(self) -> ({str, None}, bool):
try: try:
settings = read_settings(self.settings_file) settings = read_settings(self.settings_file)
except FileNotFoundError: except FileNotFoundError:
logging.debug("No persistent formats settings file found (path: %s)", self.settings_file) logging.debug("No persistent formats settings file found (path: %s)", self.settings_file)
return False return None, False
formats = settings.get('TAILS_FORMATS') formats = settings.get('TAILS_FORMATS')
if not formats: if formats is None:
return False logging.debug("No formats setting found in settings file (path: %s)", self.settings_file)
return None, False
is_default = settings.get('IS_DEFAULT') == 'true' is_default = settings.get('IS_DEFAULT') == 'true'
self.set_value(formats, chosen_by_user=not is_default)
logging.debug("Loaded formats setting '%s' (is default: %s)", formats, is_default) logging.debug("Loaded formats setting '%s' (is default: %s)", formats, is_default)
return True return formats, is_default
def get_tree(self) -> Gtk.TreeStore: def get_tree(self) -> Gtk.TreeStore:
treestore = Gtk.TreeStore(GObject.TYPE_STRING, # id treestore = Gtk.TreeStore(GObject.TYPE_STRING, # id
...@@ -65,8 +64,8 @@ class FormatsSetting(LocalizationSetting): ...@@ -65,8 +64,8 @@ class FormatsSetting(LocalizationSetting):
treestore.set(treeiter_locale, 1, self._locale_name(locale)) treestore.set(treeiter_locale, 1, self._locale_name(locale))
return treestore return treestore
def get_name(self) -> str: def get_name(self, locale: str) -> str:
return self._locale_name(self.get_value()) return self._locale_name(locale)
def get_default_locale(self, country_code=None) -> str: def get_default_locale(self, country_code=None) -> str:
"""Return default locale for given country """Return default locale for given country
...@@ -141,12 +140,3 @@ class FormatsSetting(LocalizationSetting): ...@@ -141,12 +140,3 @@ class FormatsSetting(LocalizationSetting):
if language_code not in res[country_code]: if language_code not in res[country_code]:
res[country_code].append(language_code) res[country_code].append(language_code)
return res return res
def on_language_changed(self, language_code: str):
"""Set the formats according to the new language"""
# Don't overwrite user chosen values
if self.value_changed_by_user:
return
logging.debug("setting formats to %s", language_code)
self.set_value(language_code)
...@@ -19,14 +19,13 @@ class KeyboardSetting(LocalizationSetting): ...@@ -19,14 +19,13 @@ class KeyboardSetting(LocalizationSetting):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.xkbinfo = GnomeDesktop.XkbInfo() self.xkbinfo = GnomeDesktop.XkbInfo()
self.value = 'us'
self.settings_file = tailsgreeter.config.keyboard_setting_path self.settings_file = tailsgreeter.config.keyboard_setting_path
def apply_to_upcoming_session(self): def save(self, value: str, is_default: bool):
try: try:
layout, variant = self.get_value().split('+') layout, variant = value.split('+')
except ValueError: except ValueError:
layout = self.get_value() layout = value
variant = '' variant = ''
write_settings(self.settings_file, { write_settings(self.settings_file, {
...@@ -34,29 +33,28 @@ class KeyboardSetting(LocalizationSetting): ...@@ -34,29 +33,28 @@ class KeyboardSetting(LocalizationSetting):
'TAILS_XKBMODEL': 'pc105', 'TAILS_XKBMODEL': 'pc105',
'TAILS_XKBLAYOUT': layout, 'TAILS_XKBLAYOUT': layout,
'TAILS_XKBVARIANT': variant, 'TAILS_XKBVARIANT': variant,
'IS_DEFAULT': str(not self.value_changed_by_user).lower(), 'IS_DEFAULT': str(is_default).lower(),
}) })
def load(self) -> bool: def load(self) -> ({str, None}, bool):
try: try:
settings = read_settings(self.settings_file) settings = read_settings(self.settings_file)
except FileNotFoundError: except FileNotFoundError:
logging.debug("No persistent keyboard settings file found (path: %s)", self.settings_file) logging.debug("No persistent keyboard settings file found (path: %s)", self.settings_file)
return False return None, False
keyboard_layout = settings.get('TAILS_XKBLAYOUT') keyboard_layout = settings.get('TAILS_XKBLAYOUT')
if not keyboard_layout: if keyboard_layout is None:
return False logging.debug("No keyboard setting found in settings file (path: %s)", self.settings_file)
return None, False
keyboard_variant = settings.get('TAILS_XKBVARIANT') keyboard_variant = settings.get('TAILS_XKBVARIANT')
if keyboard_variant: if keyboard_variant:
keyboard_layout += "+" + keyboard_variant keyboard_layout += "+" + keyboard_variant
is_default = settings.get('IS_DEFAULT') == 'true' is_default = settings.get('IS_DEFAULT') == 'true'
self.set_value(keyboard_layout, chosen_by_user=not is_default)
logging.debug("Loaded keyboard setting '%s' (is default: %s)", keyboard_layout, is_default) logging.debug("Loaded keyboard setting '%s' (is default: %s)", keyboard_layout, is_default)
return True return keyboard_layout, is_default
def get_tree(self, layout_codes=None) -> Gtk.TreeStore: def get_tree(self, layout_codes=None) -> Gtk.TreeStore:
if not layout_codes: if not layout_codes:
...@@ -79,8 +77,8 @@ class KeyboardSetting(LocalizationSetting): ...@@ -79,8 +77,8 @@ class KeyboardSetting(LocalizationSetting):
treestore.set(treeiter_layout, 1, self._layout_name(layout_code)) treestore.set(treeiter_layout, 1, self._layout_name(layout_code))
return treestore return treestore
def get_name(self) -> str: def get_name(self, value: str) -> str:
return self._layout_name(self.get_value()) return self._layout_name(value)
def get_all(self) -> [str]: def get_all(self) -> [str]:
"""Return a list of all keyboard layout codes """Return a list of all keyboard layout codes
...@@ -88,11 +86,6 @@ class KeyboardSetting(LocalizationSetting): ...@@ -88,11 +86,6 @@ class KeyboardSetting(LocalizationSetting):
""" """
return self.xkbinfo.get_all_layouts() return self.xkbinfo.get_all_layouts()
def set_value(self, layout, chosen_by_user=False):
super().set_value(layout)
self.value_changed_by_user = chosen_by_user
self._apply_layout_to_current_screen()
def _layout_name(self, layout_code) -> str: def _layout_name(self, layout_code) -> str:
layout_exists, display_name, short_name, xkb_layout, xkb_variant = \ layout_exists, display_name, short_name, xkb_layout, xkb_variant = \
self.xkbinfo.get_layout_info(layout_code) self.xkbinfo.get_layout_info(layout_code)
...@@ -192,13 +185,7 @@ class KeyboardSetting(LocalizationSetting): ...@@ -192,13 +185,7 @@ class KeyboardSetting(LocalizationSetting):
layouts = filtered_layouts layouts = filtered_layouts
return layouts return layouts
def on_language_changed(self, locale: str): def get_layout_for_locale(self, locale: str):
"""Set the keyboard layout according to the new language"""
# Don't overwrite a user chosen value
if self.value_changed_by_user:
return
language = language_from_locale(locale) language = language_from_locale(locale)
country = country_from_locale(locale) country = country_from_locale(locale)
...@@ -243,11 +230,10 @@ class KeyboardSetting(LocalizationSetting): ...@@ -243,11 +230,10 @@ class KeyboardSetting(LocalizationSetting):
else: else:
default_layout = 'us' default_layout = 'us'
logging.debug("Using us as fallback default layout") logging.debug("Using us as fallback default layout")
self.set_value(default_layout) return default_layout
def _apply_layout_to_current_screen(self): def apply_layout_to_current_screen(self, layout: str):
layout = self.get_value() logging.debug("applying keyboard layout '%s'", layout)
logging.debug("layout=%s", layout)
settings = Gio.Settings('org.gnome.desktop.input-sources') settings = Gio.Settings('org.gnome.desktop.input-sources')
settings.set_value('sources', GLib.Variant('a(ss)', [('xkb', layout)])) settings.set_value('sources', GLib.Variant('a(ss)', [('xkb', layout)]))
...@@ -21,7 +21,7 @@ from collections import OrderedDict ...@@ -21,7 +21,7 @@ from collections import OrderedDict
import gi import gi
import logging import logging
import locale import locale
from typing import Callable import pipes
import tailsgreeter.config import tailsgreeter.config
from tailsgreeter.settings.localization import LocalizationSetting, \ from tailsgreeter.settings.localization import LocalizationSetting, \
...@@ -37,11 +37,9 @@ from gi.repository import GLib, GObject, GnomeDesktop, Gtk ...@@ -37,11 +37,9 @@ from gi.repository import GLib, GObject, GnomeDesktop, Gtk
class LanguageSetting(LocalizationSetting): class LanguageSetting(LocalizationSetting):
def __init__(self, locales: [str], language_changed_cb: Callable): def __init__(self, locales: [str]):
super().__init__() super().__init__()
self.value = 'en_US'
self.locales = locales self.locales = locales
self.language_changed_cb = language_changed_cb
self._user_account = None self._user_account = None
self.settings_file = tailsgreeter.config.language_setting_path self.settings_file = tailsgreeter.config.language_setting_path
...@@ -49,26 +47,27 @@ class LanguageSetting(LocalizationSetting): ...@@ -49,26 +47,27 @@ class LanguageSetting(LocalizationSetting):
self.locales_per_language = self._make_language_to_locale_dict(locales) self.locales_per_language = self._make_language_to_locale_dict(locales)
self.language_names_per_language = self._make_language_to_language_name_dict(self.lang_codes) self.language_names_per_language = self._make_language_to_language_name_dict(self.lang_codes)
def apply_to_upcoming_session(self): def save(self, language: str, is_default: bool):
write_settings(self.settings_file, { write_settings(self.settings_file, {
'TAILS_LOCALE_NAME': self.get_value(), 'TAILS_LOCALE_NAME': pipes.quote(language),
'IS_DEFAULT': str(not self.value_changed_by_user).lower(), 'IS_DEFAULT': str(is_default).lower(),
}) })
def load(self) -> bool: def load(self) -> ({str, None}, bool):
try: try:
settings = read_settings(self.settings_file) settings = read_settings(self.settings_file)
except FileNotFoundError: except FileNotFoundError:
logging.debug("No persistent language settings file found (path: %s)", self.settings_file) logging.debug("No persistent language settings file found (path: %s)", self.settings_file)
return False return None, False
language = settings.get('TAILS_LOCALE_NAME') language = settings.get('TAILS_LOCALE_NAME')
if not language: if language is None:
return False logging.debug("No language setting found in settings file (path: %s)", self.settings_file)
return None, False
is_default = settings.get('IS_DEFAULT') == 'true' is_default = settings.get('IS_DEFAULT') == 'true'
self.set_value(language, chosen_by_user=not is_default)
logging.debug("Loaded language setting '%s' (is default: %s)", language, is_default) logging.debug("Loaded language setting '%s' (is default: %s)", language, is_default)
return True return language, is_default
def get_tree(self) -> Gtk.TreeStore: def get_tree(self) -> Gtk.TreeStore:
treestore = Gtk.TreeStore(GObject.TYPE_STRING, # id treestore = Gtk.TreeStore(GObject.TYPE_STRING, # id
...@@ -92,8 +91,8 @@ class LanguageSetting(LocalizationSetting): ...@@ -92,8 +91,8 @@ class LanguageSetting(LocalizationSetting):
treestore.set(treeiter_locale, 1, self._locale_name(locale_code)) treestore.set(treeiter_locale, 1, self._locale_name(locale_code))
return treestore return treestore
def get_name(self) -> str: def get_name(self, value: str) -> str:
return self._locale_name(self.get_value()) return self._locale_name(value)
def get_default_locale(self, lang_code: str) -> str: def get_default_locale(self, lang_code: str) -> str:
"""Try to find a default locale for the given language """Try to find a default locale for the given language
...@@ -114,11 +113,6 @@ class LanguageSetting(LocalizationSetting): ...@@ -114,11 +113,6 @@ class LanguageSetting(LocalizationSetting):
return locales[0] return locales[0]
def set_value(self, locale_code: str, chosen_by_user=False):
super().set_value(locale_code, chosen_by_user)
self._apply_language(locale_code)
self.language_changed_cb(locale_code)
def _language_name(self, lang_code: str) -> str: def _language_name(self, lang_code: str) -> str:
default_locale = 'C' default_locale = 'C'
...@@ -189,7 +183,7 @@ class LanguageSetting(LocalizationSetting): ...@@ -189,7 +183,7 @@ class LanguageSetting(LocalizationSetting):
except AttributeError: except AttributeError:
return locale_code return locale_code
def _apply_language(self, language_code: str): def apply_language(self, language_code: str):
normalized_code = locale.normalize(language_code + '.' + locale.getpreferredencoding())