From 74da396fd8693a0d114d7dee577ce064d6023529 Mon Sep 17 00:00:00 2001 From: sezanzeb Date: Sun, 6 Dec 2020 12:40:58 +0100 Subject: [PATCH] unittests for the new permission checks --- keymapper/dev/permissions.py | 16 ++-- tests/testcases/test_permissions.py | 135 ++++++++++++++++++++++++---- 2 files changed, 124 insertions(+), 27 deletions(-) diff --git a/keymapper/dev/permissions.py b/keymapper/dev/permissions.py index be279a73..b7b4c014 100644 --- a/keymapper/dev/permissions.py +++ b/keymapper/dev/permissions.py @@ -90,9 +90,9 @@ def check_injection_rights(): def can_read_devices(): - """Help the user to setup correct permissions.""" + """Get a list of problems before key-mapper can be used properly.""" if getpass.getuser() == 'root': - return None + return [] input_check = check_group('input') plugdev_check = check_group('plugdev') @@ -100,13 +100,11 @@ def can_read_devices(): # ubuntu. funnily, individual devices in /dev/input/ have write permitted. can_write = check_injection_rights() - ret = [] - if can_write is not None: - ret.append(can_write) - if input_check is not None: - ret.append(input_check) - if plugdev_check is not None: - ret.append(plugdev_check) + ret = [ + check for check + in [can_write, input_check, plugdev_check] + if check is not None + ] if len(ret) > 0: logger.info( diff --git a/tests/testcases/test_permissions.py b/tests/testcases/test_permissions.py index 8528dbc7..a8db790f 100644 --- a/tests/testcases/test_permissions.py +++ b/tests/testcases/test_permissions.py @@ -19,37 +19,136 @@ # along with key-mapper. If not, see . +import os import grp +import getpass +import subprocess import unittest -from keymapper.dev.permissions import can_read_devices +from keymapper.dev.permissions import check_injection_rights, check_group, \ + can_read_devices from keymapper.paths import USER -class TestPermissions(unittest.TestCase): - def setUp(self): - self.getgrnam = grp.getgrnam +original_access = os.access +original_getgrnam = grp.getgrnam +original_check_output = subprocess.check_output +original_stat = os.stat +oringal_getuser = getpass.getuser + +class TestCheckGroup(unittest.TestCase): def tearDown(self): - grp.getgrnam = self.getgrnam + # reset all fakes + os.access = original_access + grp.getgrnam = original_getgrnam + subprocess.check_output = original_check_output + os.stat = original_stat + getpass.getuser = oringal_getuser - def test_cannot_access(self): - # TODO modify test - class Grnam: - def __init__(self, group): - self.gr_mem = [] + def test_check_injection_rights(self): + can_access = False + os.access = lambda *args: can_access + + self.assertIsNotNone(check_injection_rights()) + can_access = True + self.assertIsNone(check_injection_rights()) + + def fake_setup(self): + """Patch some functions to have the following fake environment: + + Groups + ------ + input: id: 0, members: $USER, used in /dev, set up + plugdev: id: 1, members: $USER, used in /dev, not in `groups` + foobar: id: 2, no members, used in /dev + a_unused: id: 0, members: $USER, not used in /dev, set up + b_unused: id: 1, members: $USER, not used in /dev, not in `groups` + c_unused: id: 2, no members, not used in /dev + """ + gr_mems = { + 'input': (0, [USER]), + 'plugdev': (1, [USER]), + 'foobar': (2, []), + 'a_unused': (3, [USER]), + 'b_unused': (4, [USER]), + 'c_unused': (5, []) + } + + stat_counter = 0 - grp.getgrnam = Grnam - self.assertFalse(can_read_devices()[0]) + class stat: + def __init__(self, path): + nonlocal stat_counter + stat_counter += 1 + # make sure stat returns all of those groups at some point. + # only works if there are more than three files in /dev, which + # should be the case + self.st_gid = [0, 1, 2][stat_counter % 3] - def test_can_access(self): - # TODO modify test - class Grnam: + os.stat = stat + + class getgrnam: def __init__(self, group): - self.gr_mem = [USER] + if group not in gr_mems: + raise KeyError() + + self.gr_gid = gr_mems[group][0] + self.gr_mem = gr_mems[group][1] + + grp.getgrnam = getgrnam + # fake the `groups` output to act like the current session only + # has input and a_unused active + subprocess.check_output = lambda cmd: b'foo input a_unused bar' + + def test_can_read_devices(self): + self.fake_setup() + + # root user doesn't need this stuff + getpass.getuser = lambda: 'root' + self.assertEqual(len(can_read_devices()), 0) + + getpass.getuser = lambda: USER + os.access = lambda *args: False + # plugdev not yet setup correctly and cannot write + self.assertEqual(len(can_read_devices()), 2) + + os.access = lambda *args: True + self.assertEqual(len(can_read_devices()), 1) + + subprocess.check_output = lambda cmd: b'plugdev input' + self.assertEqual(len(can_read_devices()), 0) + + def test_check_group(self): + self.fake_setup() + + # correctly setup + self.assertIsNone(check_group('input')) + + # session restart required, usermod already done + self.assertIsNotNone(check_group('plugdev')) + self.assertIn('plugdev', check_group('plugdev')) + self.assertNotIn('usermod', check_group('plugdev')) + + # usermod required + self.assertIsNotNone(check_group('foobar')) + self.assertIn('foobar', check_group('foobar')) + self.assertIn('usermod', check_group('foobar')) + + # don't exist in /dev + self.assertIsNone(check_group('a_unused')) + self.assertIsNone(check_group('b_unused')) + self.assertIsNone(check_group('c_unused')) + + # group doesn't exist + self.assertIsNone(check_group('qux')) + + def file_not_found_error(cmd): + raise FileNotFoundError() + subprocess.check_output = file_not_found_error - grp.getgrnam = Grnam - self.assertTrue(can_read_devices()[0]) + # groups command doesn't exist, so cannot check this suff + self.assertIsNone(check_group('plugdev')) if __name__ == "__main__":