From de1070df35b1fac829a101dfaf863d351208c108 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Mon, 11 Aug 2025 14:02:52 -0700 Subject: [PATCH] action_utils: Implement a utility to run a command as different user - To be used to run specific command as another user. Tests: - Unit tests. Signed-off-by: Sunil Mohan Adapa Reviewed-by: Joseph Nuthalapati --- plinth/action_utils.py | 10 ++++++++++ plinth/tests/test_action_utils.py | 17 ++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/plinth/action_utils.py b/plinth/action_utils.py index 7cd46fa75..997168ff1 100644 --- a/plinth/action_utils.py +++ b/plinth/action_utils.py @@ -788,3 +788,13 @@ def move_uploaded_file(source: str | pathlib.Path, shutil.move(source, destination) shutil.chown(destination, user, group) destination.chmod(permissions) + + +def run_as_user(command, username, **kwargs): + """Run a command as another user. + + Uses 'runuser' which is similar to 'su'. Creates PAM session unlike + setpriv. Sets real/effective uid/gid and resets the environment. + """ + command = ['runuser', '--user', username, '--'] + command + return subprocess.run(command, **kwargs) diff --git a/plinth/tests/test_action_utils.py b/plinth/tests/test_action_utils.py index 980b03024..d26ba1b45 100644 --- a/plinth/tests/test_action_utils.py +++ b/plinth/tests/test_action_utils.py @@ -12,7 +12,7 @@ import pytest from plinth.action_utils import (get_addresses, get_hostname, is_systemd_running, move_uploaded_file, - service_action, service_disable, + run_as_user, service_action, service_disable, service_enable, service_is_enabled, service_is_running, service_reload, service_restart, service_start, service_stop, @@ -229,3 +229,18 @@ def test_move_uploaded_file(tmp_path, upload_dir): assert destination_file.stat().st_mode & 0o777 == 0o600 assert destination_file.read_text() == 'x-contents-2' assert not source.exists() + + +@patch('subprocess.run') +def test_run_as_user(run): + """Test running a command as another user works.""" + run.return_value = 'test-return-value' + return_value = run_as_user(['command', 'arg1', '--foo'], + username='foouser', stdout=subprocess.PIPE, + check=True) + assert return_value == 'test-return-value' + assert run.mock_calls == [ + call( + ['runuser', '--user', 'foouser', '--', 'command', 'arg1', '--foo'], + stdout=subprocess.PIPE, check=True) + ]