Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions keepmenu/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
import shlex
from subprocess import run, DEVNULL
import sys
from os.path import exists, expanduser
import tempfile
from os.path import exists, expanduser, join

from keepmenu.menu import dmenu_err

Expand All @@ -22,7 +23,29 @@
# file_handler.setFormatter(formatter)
# logger.addHandler(file_handler)

AUTH_FILE = expanduser("~/.cache/.keepmenu-auth")

def get_runtime_dir():
"""Get the runtime directory for auth file storage.

Prefers $XDG_RUNTIME_DIR/keepmenu/ for security (tmpfs-backed, auto-cleanup
on logout, proper permissions enforced by systemd). Falls back to
$TMPDIR/keepmenu-<uid>/ which is also typically tmpfs and cleared on reboot.

Returns: str path to runtime directory

"""
xdg_runtime = os.environ.get('XDG_RUNTIME_DIR')
if xdg_runtime and exists(xdg_runtime):
runtime_dir = join(xdg_runtime, 'keepmenu')
else:
runtime_dir = join(tempfile.gettempdir(), f'keepmenu-{os.getuid()}')
# Ensure directory exists with secure permissions
if not exists(runtime_dir):
os.makedirs(runtime_dir, mode=0o700)
return runtime_dir


AUTH_FILE = join(get_runtime_dir(), ".keepmenu-auth")
CONF_FILE = expanduser("~/.config/keepmenu/config.ini")
SECRET_VALID_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"

Expand Down
5 changes: 4 additions & 1 deletion keepmenu/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ def port_in_use(port):
return s.connect_ex(('127.0.0.1', port)) == 0

def get_auth():
"""Generate and save port and authkey to ~/.cache/.keepmenu-auth
"""Generate and save port and authkey to runtime directory.

Uses $XDG_RUNTIME_DIR/keepmenu/ if available (tmpfs-backed, auto-cleanup),
otherwise falls back to $TMPDIR/keepmenu-<uid>/.

Returns: int port, bytestring authkey

Expand Down
72 changes: 72 additions & 0 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,78 @@
SECRET2 = 'PW4YAYYZVDE5RK2AOLKUATNZIKAFQLZO'


class TestRuntimeDir(unittest.TestCase):
"""Test get_runtime_dir() function for auth file location

"""
def setUp(self):
self.tmpdir = tempfile.mkdtemp()
# Save original environment and tempfile cache
self.orig_xdg_runtime = os.environ.get('XDG_RUNTIME_DIR')
self.orig_tmpdir = os.environ.get('TMPDIR')
self.orig_tempfile_tempdir = tempfile.tempdir

def tearDown(self):
rmtree(self.tmpdir)
# Restore original environment
if self.orig_xdg_runtime is not None:
os.environ['XDG_RUNTIME_DIR'] = self.orig_xdg_runtime
elif 'XDG_RUNTIME_DIR' in os.environ:
del os.environ['XDG_RUNTIME_DIR']
if self.orig_tmpdir is not None:
os.environ['TMPDIR'] = self.orig_tmpdir
elif 'TMPDIR' in os.environ:
del os.environ['TMPDIR']
# Restore tempfile cache
tempfile.tempdir = self.orig_tempfile_tempdir

def test_xdg_runtime_dir_used_when_set(self):
"""Test that $XDG_RUNTIME_DIR/keepmenu/ is used when available
"""
xdg_runtime = os.path.join(self.tmpdir, 'runtime')
os.makedirs(xdg_runtime, mode=0o700)
os.environ['XDG_RUNTIME_DIR'] = xdg_runtime

runtime_dir = KM.get_runtime_dir()

self.assertEqual(runtime_dir, os.path.join(xdg_runtime, 'keepmenu'))
self.assertTrue(os.path.exists(runtime_dir))
self.assertEqual(os.stat(runtime_dir).st_mode & 0o777, 0o700)

def test_tmpdir_fallback_when_xdg_runtime_unset(self):
"""Test fallback to $TMPDIR/keepmenu-<uid>/ when XDG_RUNTIME_DIR not set
"""
if 'XDG_RUNTIME_DIR' in os.environ:
del os.environ['XDG_RUNTIME_DIR']
custom_tmpdir = os.path.join(self.tmpdir, 'tmp')
os.makedirs(custom_tmpdir, mode=0o777)
os.environ['TMPDIR'] = custom_tmpdir
# Reset tempfile cache so it picks up new TMPDIR
tempfile.tempdir = None

runtime_dir = KM.get_runtime_dir()

expected = os.path.join(custom_tmpdir, f'keepmenu-{os.getuid()}')
self.assertEqual(runtime_dir, expected)
self.assertTrue(os.path.exists(runtime_dir))
self.assertEqual(os.stat(runtime_dir).st_mode & 0o777, 0o700)

def test_tmpdir_fallback_when_xdg_runtime_dir_not_exists(self):
"""Test fallback when XDG_RUNTIME_DIR is set but doesn't exist
"""
os.environ['XDG_RUNTIME_DIR'] = '/nonexistent/path'
custom_tmpdir = os.path.join(self.tmpdir, 'tmp')
os.makedirs(custom_tmpdir, mode=0o777)
os.environ['TMPDIR'] = custom_tmpdir
# Reset tempfile cache so it picks up new TMPDIR
tempfile.tempdir = None

runtime_dir = KM.get_runtime_dir()

expected = os.path.join(custom_tmpdir, f'keepmenu-{os.getuid()}')
self.assertEqual(runtime_dir, expected)


class TestServer(unittest.TestCase):
"""Test various BaseManager server functions

Expand Down
Loading