summaryrefslogtreecommitdiffstats
path: root/python/samba/gp/gp_gnome_settings_ext.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/samba/gp/gp_gnome_settings_ext.py')
-rw-r--r--python/samba/gp/gp_gnome_settings_ext.py418
1 files changed, 418 insertions, 0 deletions
diff --git a/python/samba/gp/gp_gnome_settings_ext.py b/python/samba/gp/gp_gnome_settings_ext.py
new file mode 100644
index 0000000..567ab94
--- /dev/null
+++ b/python/samba/gp/gp_gnome_settings_ext.py
@@ -0,0 +1,418 @@
+# gp_gnome_settings_ext samba gpo policy
+# Copyright (C) David Mulder <dmulder@suse.com> 2020
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os, re
+from samba.gp.gpclass import gp_pol_ext, gp_file_applier
+from tempfile import NamedTemporaryFile
+import shutil
+from configparser import ConfigParser
+from subprocess import Popen, PIPE
+from samba.common import get_string
+from glob import glob
+import xml.etree.ElementTree as etree
+from samba.gp.util.logging import log
+
+def dconf_update(test_dir):
+ if test_dir is not None:
+ return
+ dconf = shutil.which('dconf')
+ if dconf is None:
+ log.error('Failed to update dconf. Command not found')
+ return
+ p = Popen([dconf, 'update'], stdout=PIPE, stderr=PIPE)
+ out, err = p.communicate()
+ if p.returncode != 0:
+ log.error('Failed to update dconf', get_string(err))
+
+def create_locks_dir(test_dir):
+ locks_dir = '/etc/dconf/db/local.d/locks'
+ if test_dir is not None:
+ locks_dir = os.path.join(test_dir, locks_dir[1:])
+ os.makedirs(locks_dir, exist_ok=True)
+ return locks_dir
+
+def create_user_profile(test_dir):
+ user_profile = '/etc/dconf/profile/user'
+ if test_dir is not None:
+ user_profile = os.path.join(test_dir, user_profile[1:])
+ if os.path.exists(user_profile):
+ return
+ os.makedirs(os.path.dirname(user_profile), exist_ok=True)
+ with NamedTemporaryFile('w', dir=os.path.dirname(user_profile),
+ delete=False) as w:
+ w.write('user-db:user\nsystem-db:local')
+ os.chmod(w.name, 0o644)
+ fname = w.name
+ shutil.move(fname, user_profile)
+
+def create_local_db(test_dir):
+ local_db = '/etc/dconf/db/local.d'
+ if test_dir is not None:
+ local_db = os.path.join(test_dir, local_db[1:])
+ os.makedirs(local_db, exist_ok=True)
+ return local_db
+
+def select_next_conf(directory, fname=''):
+ configs = [re.match(r'(\d+)%s' % fname, f) for f in os.listdir(directory)]
+ return max([int(m.group(1)) for m in configs if m]+[0])+1
+
+class gp_gnome_settings_ext(gp_pol_ext, gp_file_applier):
+ def __init__(self, *args):
+ super().__init__(*args)
+ self.keys = ['Compose Key',
+ 'Dim Screen when User is Idle',
+ 'Lock Down Specific Settings',
+ 'Whitelisted Online Accounts',
+ 'Enabled Extensions']
+ self.lock_down_settings = {}
+ self.test_dir = None
+
+ def __str__(self):
+ return 'GNOME Settings/Lock Down Settings'
+
+ def __add_lockdown_data(self, k, e):
+ if k not in self.lock_down_settings:
+ self.lock_down_settings[k] = {}
+ self.lock_down_settings[k][e.valuename] = e.data
+
+ def __enable_lockdown_data(self, e):
+ if e.valuename not in self.lock_down_settings:
+ self.lock_down_settings[e.valuename] = {}
+ self.lock_down_settings[e.valuename]['Enabled'] = e.data == 1
+
+ def __apply_compose_key(self, data):
+ create_user_profile(self.test_dir)
+ local_db_dir = create_local_db(self.test_dir)
+
+ conf_id = select_next_conf(local_db_dir, '-input-sources')
+ local_db = os.path.join(local_db_dir,
+ '%010d-input-sources' % conf_id)
+ data_map = { 'Right Alt': 'compose:ralt',
+ 'Left Win': 'compose:lwin',
+ '3rd level of Left Win': 'compose:lwin-altgr',
+ 'Right Win': 'compose:rwin',
+ '3rd level of Right Win': 'compose:rwin-altgr',
+ 'Menu': 'compose:menu',
+ '3rd level of Menu': 'compose:menu-altgr',
+ 'Left Ctrl': 'compose:lctrl',
+ '3rd level of Left Ctrl': 'compose:lctrl-altgr',
+ 'Right Ctrl': 'compose:rctrl',
+ '3rd level of Right Ctrl': 'compose:rctrl-altgr',
+ 'Caps Lock': 'compose:caps',
+ '3rd level of Caps Lock': 'compose:caps-altgr',
+ 'The "< >" key': 'compose:102',
+ '3rd level of the "< >" key': 'compose:102-altgr',
+ 'Pause': 'compose:paus',
+ 'PrtSc': 'compose:prsc',
+ 'Scroll Lock': 'compose:sclk'
+ }
+ if data['Key Name'] not in data_map.keys():
+ log.error('Compose Key not recognized', data)
+ return
+ parser = ConfigParser()
+ section = 'org/gnome/desktop/input-sources'
+ parser.add_section(section)
+ parser.set(section, 'xkb-options',
+ "['%s']" % data_map[data['Key Name']])
+ with open(local_db, 'w') as w:
+ parser.write(w)
+
+ # Lock xkb-options
+ locks_dir = create_locks_dir(self.test_dir)
+ conf_id = select_next_conf(locks_dir)
+ lock = os.path.join(locks_dir, '%010d-input-sources' % conf_id)
+ with open(lock, 'w') as w:
+ w.write('/org/gnome/desktop/input-sources/xkb-options')
+
+ dconf_update(self.test_dir)
+ return [local_db, lock]
+
+ def __apply_dim_idle(self, data):
+ create_user_profile(self.test_dir)
+ local_db_dir = create_local_db(self.test_dir)
+ conf_id = select_next_conf(local_db_dir, '-power')
+ local_power_db = os.path.join(local_db_dir, '%010d-power' % conf_id)
+ parser = ConfigParser()
+ section = 'org/gnome/settings-daemon/plugins/power'
+ parser.add_section(section)
+ parser.set(section, 'idle-dim', 'true')
+ parser.set(section, 'idle-brightness', str(data['Dim Idle Brightness']))
+ with open(local_power_db, 'w') as w:
+ parser.write(w)
+ conf_id = select_next_conf(local_db_dir, '-session')
+ local_session_db = os.path.join(local_db_dir, '%010d-session' % conf_id)
+ parser = ConfigParser()
+ section = 'org/gnome/desktop/session'
+ parser.add_section(section)
+ parser.set(section, 'idle-delay', 'uint32 %d' % data['Delay'])
+ with open(local_session_db, 'w') as w:
+ parser.write(w)
+
+ # Lock power-saving
+ locks_dir = create_locks_dir(self.test_dir)
+ conf_id = select_next_conf(locks_dir)
+ lock = os.path.join(locks_dir, '%010d-power-saving' % conf_id)
+ with open(lock, 'w') as w:
+ w.write('/org/gnome/settings-daemon/plugins/power/idle-dim\n')
+ w.write('/org/gnome/settings-daemon/plugins/power/idle-brightness\n')
+ w.write('/org/gnome/desktop/session/idle-delay')
+
+ dconf_update(self.test_dir)
+ return [local_power_db, local_session_db, lock]
+
+ def __apply_specific_settings(self, data):
+ create_user_profile(self.test_dir)
+ locks_dir = create_locks_dir(self.test_dir)
+ conf_id = select_next_conf(locks_dir, '-group-policy')
+ policy_file = os.path.join(locks_dir, '%010d-group-policy' % conf_id)
+ with open(policy_file, 'w') as w:
+ for key in data.keys():
+ w.write('%s\n' % key)
+ dconf_update(self.test_dir)
+ return [policy_file]
+
+ def __apply_whitelisted_account(self, data):
+ create_user_profile(self.test_dir)
+ local_db_dir = create_local_db(self.test_dir)
+ locks_dir = create_locks_dir(self.test_dir)
+ val = "['%s']" % "', '".join(data.keys())
+ policy_files = self.__lockdown(local_db_dir, locks_dir, 'goa',
+ 'whitelisted-providers', val,
+ 'org/gnome/online-accounts')
+ dconf_update(self.test_dir)
+ return policy_files
+
+ def __apply_enabled_extensions(self, data):
+ create_user_profile(self.test_dir)
+ local_db_dir = create_local_db(self.test_dir)
+ conf_id = select_next_conf(local_db_dir)
+ policy_file = os.path.join(local_db_dir, '%010d-extensions' % conf_id)
+ parser = ConfigParser()
+ section = 'org/gnome/shell'
+ parser.add_section(section)
+ exts = data.keys()
+ parser.set(section, 'enabled-extensions', "['%s']" % "', '".join(exts))
+ parser.set(section, 'development-tools', 'false')
+ with open(policy_file, 'w') as w:
+ parser.write(w)
+ dconf_update(self.test_dir)
+ return [policy_file]
+
+ def __lockdown(self, local_db_dir, locks_dir, name, key, val,
+ section='org/gnome/desktop/lockdown'):
+ policy_files = []
+ conf_id = select_next_conf(local_db_dir)
+ policy_file = os.path.join(local_db_dir,
+ '%010d-%s' % (conf_id, name))
+ policy_files.append(policy_file)
+ conf_id = select_next_conf(locks_dir)
+ lock = os.path.join(locks_dir, '%010d-%s' % (conf_id, name))
+ policy_files.append(lock)
+ parser = ConfigParser()
+ parser.add_section(section)
+ parser.set(section, key, val)
+ with open(policy_file, 'w') as w:
+ parser.write(w)
+ with open(lock, 'w') as w:
+ w.write('/%s/%s' % (section, key))
+ return policy_files
+
+ def __apply_enabled(self, k):
+ policy_files = []
+
+ create_user_profile(self.test_dir)
+ local_db_dir = create_local_db(self.test_dir)
+ locks_dir = create_locks_dir(self.test_dir)
+
+ if k == 'Lock Down Enabled Extensions':
+ conf_id = select_next_conf(locks_dir)
+ policy_file = os.path.join(locks_dir, '%010d-extensions' % conf_id)
+ policy_files.append(policy_file)
+ with open(policy_file, 'w') as w:
+ w.write('/org/gnome/shell/enabled-extensions\n')
+ w.write('/org/gnome/shell/development-tools')
+ elif k == 'Disable Printing':
+ policy_files = self.__lockdown(local_db_dir, locks_dir, 'printing',
+ 'disable-printing', 'true')
+ elif k == 'Disable File Saving':
+ policy_files = self.__lockdown(local_db_dir, locks_dir,
+ 'filesaving',
+ 'disable-save-to-disk', 'true')
+ elif k == 'Disable Command-Line Access':
+ policy_files = self.__lockdown(local_db_dir, locks_dir, 'cmdline',
+ 'disable-command-line', 'true')
+ elif k == 'Disallow Login Using a Fingerprint':
+ policy_files = self.__lockdown(local_db_dir, locks_dir,
+ 'fingerprintreader',
+ 'enable-fingerprint-authentication',
+ 'false',
+ section='org/gnome/login-screen')
+ elif k == 'Disable User Logout':
+ policy_files = self.__lockdown(local_db_dir, locks_dir, 'logout',
+ 'disable-log-out', 'true')
+ elif k == 'Disable User Switching':
+ policy_files = self.__lockdown(local_db_dir, locks_dir, 'logout',
+ 'disable-user-switching', 'true')
+ elif k == 'Disable Repartitioning':
+ actions = '/usr/share/polkit-1/actions'
+ udisk2 = glob(os.path.join(actions,
+ 'org.freedesktop.[u|U][d|D]isks2.policy'))
+ if len(udisk2) == 1:
+ udisk2 = udisk2[0]
+ else:
+ udisk2 = os.path.join(actions,
+ 'org.freedesktop.UDisks2.policy')
+ udisk2_etc = os.path.join('/etc/share/polkit-1/actions',
+ os.path.basename(udisk2))
+ if self.test_dir is not None:
+ udisk2_etc = os.path.join(self.test_dir, udisk2_etc[1:])
+ os.makedirs(os.path.dirname(udisk2_etc), exist_ok=True)
+ xml_data = etree.ElementTree(etree.Element('policyconfig'))
+ if os.path.exists(udisk2):
+ with open(udisk2, 'rb') as f:
+ data = f.read()
+ existing_xml = etree.ElementTree(etree.fromstring(data))
+ root = xml_data.getroot()
+ root.append(existing_xml.find('vendor'))
+ root.append(existing_xml.find('vendor_url'))
+ root.append(existing_xml.find('icon_name'))
+ else:
+ vendor = etree.SubElement(xml_data.getroot(), 'vendor')
+ vendor.text = 'The Udisks Project'
+ vendor_url = etree.SubElement(xml_data.getroot(), 'vendor_url')
+ vendor_url.text = 'https://github.com/storaged-project/udisks'
+ icon_name = etree.SubElement(xml_data.getroot(), 'icon_name')
+ icon_name.text = 'drive-removable-media'
+ action = etree.SubElement(xml_data.getroot(), 'action')
+ action.attrib['id'] = 'org.freedesktop.udisks2.modify-device'
+ description = etree.SubElement(action, 'description')
+ description.text = 'Modify the drive settings'
+ message = etree.SubElement(action, 'message')
+ message.text = 'Authentication is required to modify drive settings'
+ defaults = etree.SubElement(action, 'defaults')
+ allow_any = etree.SubElement(defaults, 'allow_any')
+ allow_any.text = 'no'
+ allow_inactive = etree.SubElement(defaults, 'allow_inactive')
+ allow_inactive.text = 'no'
+ allow_active = etree.SubElement(defaults, 'allow_active')
+ allow_active.text = 'yes'
+ with open(udisk2_etc, 'wb') as w:
+ xml_data.write(w, encoding='UTF-8', xml_declaration=True)
+ policy_files.append(udisk2_etc)
+ else:
+ log.error('Unable to apply', k)
+ return
+ dconf_update(self.test_dir)
+ return policy_files
+
+ def __clean_data(self, k):
+ data = self.lock_down_settings[k]
+ return {i: data[i] for i in data.keys() if i != 'Enabled'}
+
+ def process_group_policy(self, deleted_gpo_list, changed_gpo_list,
+ test_dir=None):
+ if test_dir is not None:
+ self.test_dir = test_dir
+ for guid, settings in deleted_gpo_list:
+ if str(self) in settings:
+ for attribute, value in settings[str(self)].items():
+ self.unapply(guid, attribute, value, sep=';')
+ dconf_update(test_dir)
+
+ for gpo in changed_gpo_list:
+ if gpo.file_sys_path:
+ section_name = 'GNOME Settings\\Lock Down Settings'
+ pol_file = 'MACHINE/Registry.pol'
+ path = os.path.join(gpo.file_sys_path, pol_file)
+ pol_conf = self.parse(path)
+ if not pol_conf:
+ continue
+ for e in pol_conf.entries:
+ if e.keyname.startswith(section_name) and e.data and \
+ '**delvals.' not in e.valuename:
+ for k in self.keys:
+ if e.keyname.endswith(k):
+ self.__add_lockdown_data(k, e)
+ break
+ else:
+ self.__enable_lockdown_data(e)
+ for k in self.lock_down_settings.keys():
+ # Ignore disabled preferences
+ if not self.lock_down_settings[k]['Enabled']:
+ # Unapply the disabled preference if previously applied
+ self.clean(gpo.name, remove=k)
+ continue
+
+ # Apply using the appropriate applier
+ data = str(self.lock_down_settings[k])
+ value_hash = self.generate_value_hash(data)
+ if k == self.keys[0]:
+ self.apply(gpo.name, k, value_hash,
+ self.__apply_compose_key,
+ self.__clean_data(k), sep=';')
+ elif k == self.keys[1]:
+ self.apply(gpo.name, k, value_hash,
+ self.__apply_dim_idle,
+ self.__clean_data(k), sep=';')
+ elif k == self.keys[2]:
+ self.apply(gpo.name, k, value_hash,
+ self.__apply_specific_settings,
+ self.__clean_data(k), sep=';')
+ elif k == self.keys[3]:
+ self.apply(gpo.name, k, value_hash,
+ self.__apply_whitelisted_account,
+ self.__clean_data(k), sep=';')
+ elif k == self.keys[4]:
+ self.apply(gpo.name, k, value_hash,
+ self.__apply_enabled_extensions,
+ self.__clean_data(k), sep=';')
+ else:
+ self.apply(gpo.name, k, value_hash,
+ self.__apply_enabled,
+ k, sep=';')
+
+ # Unapply any policy that has been removed
+ self.clean(gpo.name, keep=self.lock_down_settings.keys())
+
+ def rsop(self, gpo):
+ output = {}
+ if gpo.file_sys_path:
+ section_name = 'GNOME Settings\\Lock Down Settings'
+ pol_file = 'MACHINE/Registry.pol'
+ path = os.path.join(gpo.file_sys_path, pol_file)
+ pol_conf = self.parse(path)
+ if not pol_conf:
+ return output
+ for e in pol_conf.entries:
+ if e.keyname.startswith(section_name) and e.data and \
+ '**delvals.' not in e.valuename:
+ for k in self.keys:
+ if e.keyname.endswith(k):
+ self.__add_lockdown_data(k, e)
+ break
+ else:
+ self.__enable_lockdown_data(e)
+ for k in self.lock_down_settings.keys():
+ if self.lock_down_settings[k]['Enabled']:
+ if len(self.lock_down_settings[k]) > 1:
+ data = self.__clean_data(k)
+ if all([i == data[i] for i in data.keys()]):
+ output[k] = list(data.keys())
+ else:
+ output[k] = data
+ else:
+ output[k] = self.lock_down_settings[k]
+ return output