mirror of
https://github.com/sezanzeb/input-remapper
synced 2024-11-20 03:25:43 +00:00
more sophisticated permission checking
This commit is contained in:
parent
51a717e75e
commit
c40f82217b
@ -23,7 +23,9 @@
|
|||||||
|
|
||||||
|
|
||||||
import grp
|
import grp
|
||||||
|
import glob
|
||||||
import getpass
|
import getpass
|
||||||
|
import subprocess
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from keymapper.logger import logger
|
from keymapper.logger import logger
|
||||||
@ -31,48 +33,80 @@ from keymapper.paths import USER
|
|||||||
|
|
||||||
|
|
||||||
def check_group(group):
|
def check_group(group):
|
||||||
"""Check if the user can access files of that group.
|
"""Check if the required group is active and log if not."""
|
||||||
|
# TODO test
|
||||||
Returns True if this group doesn't even exist.
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
return USER in grp.getgrnam(group).gr_mem
|
in_group = USER in grp.getgrnam(group).gr_mem
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return True
|
# group doesn't exist. Ignore
|
||||||
|
return None
|
||||||
|
|
||||||
|
# check if files exist with that group in /dev. Even if plugdev
|
||||||
|
# exists, that doesn't mean that it is needed.
|
||||||
|
used_groups = [os.stat(path).st_gid for path in glob.glob('/dev/input/*')]
|
||||||
|
if grp.getgrnam(group).gr_gid not in used_groups:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not in_group:
|
||||||
|
msg = (
|
||||||
|
'Some devices may not be visible without being in the '
|
||||||
|
f'"{group}" user group. Try `sudo usermod -a -G {group} {USER}` '
|
||||||
|
'and log out and back in or restart.',
|
||||||
|
)
|
||||||
|
logger.warning(msg)
|
||||||
|
return msg
|
||||||
|
|
||||||
|
try:
|
||||||
|
groups = subprocess.check_output('groups').decode().split()
|
||||||
|
group_active = group in groups
|
||||||
|
except FileNotFoundError:
|
||||||
|
# groups command missing. Idk if any distro doesn't have it
|
||||||
|
# but if so, cover the case.
|
||||||
|
return None
|
||||||
|
|
||||||
|
if in_group and not group_active:
|
||||||
|
msg = (
|
||||||
|
f'You are in the "{group}" group, but your session is not yet '
|
||||||
|
'using it. Some device may not be visible. Please log out and '
|
||||||
|
'back in or restart',
|
||||||
|
group
|
||||||
|
)
|
||||||
|
logger.warning(msg)
|
||||||
|
return msg
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def check_injection_rights():
|
||||||
|
"""Check if the user may write into /dev/uinput."""
|
||||||
|
if not os.access('/dev/uinput', os.W_OK):
|
||||||
|
can_write = (
|
||||||
|
'Rights to write to /dev/uinput are missing, keycodes cannot '
|
||||||
|
f'be injected. Try `sudo setfacl -m u:{USER}:rw- /dev/uinput`'
|
||||||
|
)
|
||||||
|
logger.error(can_write)
|
||||||
|
return can_write
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def can_read_devices():
|
def can_read_devices():
|
||||||
"""If the people ever looks into the console, make sure to help them."""
|
"""Help the user to setup correct permissions."""
|
||||||
is_root = getpass.getuser() == 'root'
|
if getpass.getuser() == 'root':
|
||||||
is_input = check_group('input')
|
return None
|
||||||
is_plugdev = check_group('plugdev')
|
|
||||||
|
input_check = check_group('input')
|
||||||
|
plugdev_check = check_group('plugdev')
|
||||||
|
|
||||||
# ubuntu. funnily, individual devices in /dev/input/ have write permitted.
|
# ubuntu. funnily, individual devices in /dev/input/ have write permitted.
|
||||||
can_write = os.access('/dev/uinput', os.W_OK)
|
can_write = check_injection_rights()
|
||||||
|
|
||||||
def warn(group):
|
ret = []
|
||||||
logger.warning(
|
if can_write is not None:
|
||||||
'Some devices may not be visible without being in the '
|
ret.append(can_write)
|
||||||
'"%s" user group. Try `sudo usermod -a -G %s %s` '
|
if input_check is not None:
|
||||||
'and log out and back in.',
|
ret.append(input_check)
|
||||||
group,
|
if plugdev_check is not None:
|
||||||
group,
|
ret.append(plugdev_check)
|
||||||
USER
|
|
||||||
)
|
|
||||||
|
|
||||||
if not is_root:
|
return ret
|
||||||
if not is_plugdev:
|
|
||||||
warn('plugdev')
|
|
||||||
if not is_input:
|
|
||||||
warn('input')
|
|
||||||
if not can_write:
|
|
||||||
logger.error(
|
|
||||||
'Injecting keycodes into /dev/uinput is not permitted. '
|
|
||||||
'Either use sudo or run '
|
|
||||||
'`sudo setfacl -m u:%s:rw- /dev/uinput`',
|
|
||||||
{USER}
|
|
||||||
)
|
|
||||||
|
|
||||||
permitted = (is_root or (is_input and is_plugdev)) and can_write
|
|
||||||
|
|
||||||
return permitted, is_root, is_input, is_plugdev, can_write
|
|
||||||
|
@ -110,25 +110,14 @@ class Window:
|
|||||||
# already visible (without content) to make it look more responsive.
|
# already visible (without content) to make it look more responsive.
|
||||||
gtk_iteration()
|
gtk_iteration()
|
||||||
|
|
||||||
permitted, _, is_input, is_plugdev, can_write = can_read_devices()
|
permission_errors = can_read_devices()
|
||||||
if not permitted:
|
if len(permission_errors) > 0:
|
||||||
missing_groups = []
|
# TODO test
|
||||||
if not is_input:
|
self.show_status(
|
||||||
missing_groups.append('input')
|
CTX_ERROR,
|
||||||
if not is_plugdev:
|
'Permission error. Hover for info',
|
||||||
missing_groups.append('plugdev')
|
'\n\n'.join(permission_errors)
|
||||||
|
)
|
||||||
if len(missing_groups) > 0:
|
|
||||||
self.get('status_bar').push(
|
|
||||||
CTX_ERROR,
|
|
||||||
f'You are not in the {" and ".join(missing_groups)} '
|
|
||||||
f'group{"s" if len(missing_groups) > 0 else ""}'
|
|
||||||
)
|
|
||||||
elif not can_write:
|
|
||||||
self.get('status_bar').push(
|
|
||||||
CTX_ERROR,
|
|
||||||
'Insufficient permissions on /dev/uinput'
|
|
||||||
)
|
|
||||||
|
|
||||||
self.populate_devices()
|
self.populate_devices()
|
||||||
|
|
||||||
@ -235,10 +224,7 @@ class Window:
|
|||||||
# because the device is in grab mode by the daemon and
|
# because the device is in grab mode by the daemon and
|
||||||
# therefore the original keycode inaccessible
|
# therefore the original keycode inaccessible
|
||||||
logger.info('Cannot change keycodes while injecting')
|
logger.info('Cannot change keycodes while injecting')
|
||||||
self.get('status_bar').push(
|
self.show_status(CTX_ERROR, 'Use "Apply Defaults" before editing')
|
||||||
CTX_ERROR,
|
|
||||||
'Use "Apply Defaults" before editing'
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_focused_row(self):
|
def get_focused_row(self):
|
||||||
"""Get the Row that is currently in focus."""
|
"""Get the Row that is currently in focus."""
|
||||||
@ -288,10 +274,7 @@ class Window:
|
|||||||
def on_apply_system_layout_clicked(self, _):
|
def on_apply_system_layout_clicked(self, _):
|
||||||
"""Load the mapping."""
|
"""Load the mapping."""
|
||||||
self.dbus.stop_injecting(self.selected_device)
|
self.dbus.stop_injecting(self.selected_device)
|
||||||
self.get('status_bar').push(
|
self.show_status(CTX_APPLY, 'Applied the system default')
|
||||||
CTX_APPLY,
|
|
||||||
'Applied the system default'
|
|
||||||
)
|
|
||||||
GLib.timeout_add(10, self.show_device_mapping_status)
|
GLib.timeout_add(10, self.show_device_mapping_status)
|
||||||
|
|
||||||
def show_status(self, context_id, message, tooltip=None):
|
def show_status(self, context_id, message, tooltip=None):
|
||||||
|
@ -518,6 +518,7 @@ class TestPermissions(unittest.TestCase):
|
|||||||
shutil.rmtree('/tmp/key-mapper-test')
|
shutil.rmtree('/tmp/key-mapper-test')
|
||||||
|
|
||||||
def test_check_groups_missing(self):
|
def test_check_groups_missing(self):
|
||||||
|
# TODO modify test
|
||||||
class Grnam:
|
class Grnam:
|
||||||
def __init__(self, group):
|
def __init__(self, group):
|
||||||
self.gr_mem = []
|
self.gr_mem = []
|
||||||
@ -534,6 +535,7 @@ class TestPermissions(unittest.TestCase):
|
|||||||
self.assertIn('plugdev', labels)
|
self.assertIn('plugdev', labels)
|
||||||
|
|
||||||
def test_check_plugdev_missing(self):
|
def test_check_plugdev_missing(self):
|
||||||
|
# TODO modify test
|
||||||
class Grnam:
|
class Grnam:
|
||||||
def __init__(self, group):
|
def __init__(self, group):
|
||||||
if group == 'input':
|
if group == 'input':
|
||||||
@ -553,6 +555,7 @@ class TestPermissions(unittest.TestCase):
|
|||||||
self.assertIn('plugdev', labels)
|
self.assertIn('plugdev', labels)
|
||||||
|
|
||||||
def test_check_write_uinput(self):
|
def test_check_write_uinput(self):
|
||||||
|
# TODO modify test
|
||||||
class Grnam:
|
class Grnam:
|
||||||
def __init__(self, group):
|
def __init__(self, group):
|
||||||
self.gr_mem = [USER]
|
self.gr_mem = [USER]
|
||||||
|
@ -34,6 +34,7 @@ class TestPermissions(unittest.TestCase):
|
|||||||
grp.getgrnam = self.getgrnam
|
grp.getgrnam = self.getgrnam
|
||||||
|
|
||||||
def test_cannot_access(self):
|
def test_cannot_access(self):
|
||||||
|
# TODO modify test
|
||||||
class Grnam:
|
class Grnam:
|
||||||
def __init__(self, group):
|
def __init__(self, group):
|
||||||
self.gr_mem = []
|
self.gr_mem = []
|
||||||
@ -42,6 +43,7 @@ class TestPermissions(unittest.TestCase):
|
|||||||
self.assertFalse(can_read_devices()[0])
|
self.assertFalse(can_read_devices()[0])
|
||||||
|
|
||||||
def test_can_access(self):
|
def test_can_access(self):
|
||||||
|
# TODO modify test
|
||||||
class Grnam:
|
class Grnam:
|
||||||
def __init__(self, group):
|
def __init__(self, group):
|
||||||
self.gr_mem = [USER]
|
self.gr_mem = [USER]
|
||||||
|
Loading…
Reference in New Issue
Block a user