plugins/python/regress: add tests for approval plugin
This commit is contained in:

committed by
Todd C. Miller

parent
80b3d86d6e
commit
43e256e34f
@@ -29,6 +29,7 @@ static const char *python_plugin_so_path = NULL;
|
||||
static void *python_plugin_handle = NULL;
|
||||
static struct io_plugin *python_io = NULL;
|
||||
static struct policy_plugin *python_policy = NULL;
|
||||
static struct approval_plugin *python_approval = NULL;
|
||||
static struct sudoers_group_plugin *group_plugin = NULL;
|
||||
static struct audit_plugin *python_audit = NULL;
|
||||
|
||||
@@ -1294,6 +1295,112 @@ check_audit_plugin_reports_error(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
static int
|
||||
check_example_approval_plugin(const char *date_str, const char *expected_error)
|
||||
{
|
||||
const char *errstr = NULL;
|
||||
|
||||
create_plugin_options("example_approval_plugin", "BusinessHoursApprovalPlugin", NULL);
|
||||
|
||||
VERIFY_INT(python_approval->open(SUDO_API_VERSION, fake_conversation, fake_printf,
|
||||
data.settings, data.user_info, 0, data.plugin_argv,
|
||||
data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
|
||||
|
||||
VERIFY_TRUE(mock_python_datetime_now("example_approval_plugin", date_str));
|
||||
|
||||
int expected_rc = (expected_error == NULL) ? SUDO_RC_ACCEPT : SUDO_RC_REJECT;
|
||||
|
||||
VERIFY_INT(python_approval->check(data.command_info, data.plugin_argv, data.user_env, &errstr),
|
||||
expected_rc);
|
||||
|
||||
if (expected_error == NULL) {
|
||||
VERIFY_PTR(errstr, NULL);
|
||||
VERIFY_STR(data.stdout_str, "");
|
||||
} else {
|
||||
VERIFY_STR(errstr, expected_error);
|
||||
VERIFY_STR_CONTAINS(data.stdout_str, expected_error); // (ends with \n)
|
||||
}
|
||||
VERIFY_STR(data.stderr_str, "");
|
||||
|
||||
python_approval->close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef struct approval_plugin * (approval_clone_func)(void);
|
||||
|
||||
static int
|
||||
check_multiple_approval_plugin_and_arguments(void)
|
||||
{
|
||||
// verify multiple python approval plugins are available
|
||||
approval_clone_func *python_approval_clone = (approval_clone_func *)sudo_dso_findsym(
|
||||
python_plugin_handle, "python_approval_clone");
|
||||
VERIFY_PTR_NE(python_approval_clone, NULL);
|
||||
|
||||
struct approval_plugin *python_approval2 = NULL;
|
||||
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
python_approval2 = (*python_approval_clone)();
|
||||
VERIFY_PTR_NE(python_approval2, NULL);
|
||||
VERIFY_PTR_NE(python_approval2, python_approval);
|
||||
}
|
||||
|
||||
const char *errstr = NULL;
|
||||
create_plugin_options("regress/plugin_approval_test", "ApprovalTestPlugin", "Id=1");
|
||||
|
||||
str_array_free(&data.plugin_argv);
|
||||
data.plugin_argv = create_str_array(6, "sudo", "-u", "user", "whoami", "--help", NULL);
|
||||
|
||||
str_array_free(&data.user_env);
|
||||
data.user_env = create_str_array(3, "USER_ENV1=VALUE1", "USER_ENV2=value2", NULL);
|
||||
|
||||
str_array_free(&data.user_info);
|
||||
data.user_info = create_str_array(3, "INFO1=VALUE1", "info2=value2", NULL);
|
||||
|
||||
str_array_free(&data.settings);
|
||||
data.settings = create_str_array(3, "SETTING1=VALUE1", "setting2=value2", NULL);
|
||||
|
||||
VERIFY_INT(python_approval->open(SUDO_API_VERSION, fake_conversation, fake_printf,
|
||||
data.settings, data.user_info, 3, data.plugin_argv,
|
||||
data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
|
||||
VERIFY_PTR(errstr, NULL);
|
||||
|
||||
// For verifying the error message of no more plugin. It should be displayed only once.
|
||||
VERIFY_PTR((*python_approval_clone)(), NULL);
|
||||
VERIFY_PTR((*python_approval_clone)(), NULL);
|
||||
|
||||
create_plugin_options("regress/plugin_approval_test", "ApprovalTestPlugin", "Id=2");
|
||||
VERIFY_INT(python_approval2->open(SUDO_API_VERSION, fake_conversation, fake_printf,
|
||||
data.settings, data.user_info, 3, data.plugin_argv,
|
||||
data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
|
||||
VERIFY_PTR(errstr, NULL);
|
||||
|
||||
VERIFY_INT(python_approval->show_version(false), SUDO_RC_OK);
|
||||
VERIFY_INT(python_approval2->show_version(true), SUDO_RC_OK);
|
||||
|
||||
str_array_free(&data.command_info);
|
||||
data.command_info = create_str_array(3, "CMDINFO1=value1", "CMDINFO2=VALUE2", NULL);
|
||||
|
||||
str_array_free(&data.plugin_argv);
|
||||
data.plugin_argv = create_str_array(3, "whoami", "--help", NULL);
|
||||
|
||||
VERIFY_INT(python_approval->check(data.command_info, data.plugin_argv, data.user_env, &errstr),
|
||||
SUDO_RC_OK);
|
||||
VERIFY_PTR(errstr, NULL);
|
||||
|
||||
VERIFY_INT(python_approval2->check(data.command_info, data.plugin_argv, data.user_env, &errstr),
|
||||
SUDO_RC_OK);
|
||||
VERIFY_PTR(errstr, NULL);
|
||||
|
||||
python_approval->close();
|
||||
python_approval2->close();
|
||||
|
||||
VERIFY_STDOUT(expected_path("check_multiple_approval_plugin_and_arguments.stdout"));
|
||||
VERIFY_STDERR(expected_path("check_multiple_approval_plugin_and_arguments.stderr"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
_load_symbols(void)
|
||||
@@ -1313,6 +1420,9 @@ _load_symbols(void)
|
||||
python_audit = sudo_dso_findsym(python_plugin_handle, "python_audit");
|
||||
VERIFY_PTR_NE(python_audit, NULL);
|
||||
|
||||
python_approval = sudo_dso_findsym(python_plugin_handle, "python_approval");
|
||||
VERIFY_PTR_NE(python_approval, NULL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1322,6 +1432,7 @@ _unload_symbols(void)
|
||||
python_io = NULL;
|
||||
group_plugin = NULL;
|
||||
python_policy = NULL;
|
||||
python_approval = NULL;
|
||||
python_audit = NULL;
|
||||
VERIFY_INT(sudo_dso_unload(python_plugin_handle), 0);
|
||||
return true;
|
||||
@@ -1377,6 +1488,21 @@ main(int argc, char *argv[])
|
||||
RUN_TEST(check_audit_plugin_callbacks_are_optional());
|
||||
RUN_TEST(check_audit_plugin_reports_error());
|
||||
|
||||
// Monday, too early
|
||||
RUN_TEST(check_example_approval_plugin(
|
||||
"2020-02-10T07:55:23", "That is not allowed outside the business hours!"));
|
||||
// Monday, good time
|
||||
RUN_TEST(check_example_approval_plugin("2020-02-10T08:05:23", NULL));
|
||||
// Friday, good time
|
||||
RUN_TEST(check_example_approval_plugin("2020-02-14T17:59:23", NULL));
|
||||
// Friday, too late
|
||||
RUN_TEST(check_example_approval_plugin(
|
||||
"2020-02-10T18:05:23", "That is not allowed outside the business hours!"));
|
||||
// Saturday
|
||||
RUN_TEST(check_example_approval_plugin(
|
||||
"2020-02-15T08:05:23", "That is not allowed on the weekend!"));
|
||||
RUN_TEST(check_multiple_approval_plugin_and_arguments());
|
||||
|
||||
RUN_TEST(check_python_plugins_do_not_affect_each_other());
|
||||
|
||||
RUN_TEST(check_example_debugging("plugin@err"));
|
||||
|
22
plugins/python/regress/plugin_approval_test.py
Normal file
22
plugins/python/regress/plugin_approval_test.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import sudo
|
||||
import json
|
||||
|
||||
|
||||
class ApprovalTestPlugin(sudo.Plugin):
|
||||
def __init__(self, plugin_options, **kwargs):
|
||||
id = sudo.options_as_dict(plugin_options).get("Id", "")
|
||||
super().__init__(plugin_options=plugin_options, **kwargs)
|
||||
self._id = "(APPROVAL {})".format(id)
|
||||
sudo.log_info("{} Constructed:".format(self._id))
|
||||
sudo.log_info(json.dumps(self.__dict__, indent=4))
|
||||
|
||||
def __del__(self):
|
||||
sudo.log_info("{} Destructed successfully".format(self._id))
|
||||
|
||||
def check(self, *args):
|
||||
sudo.log_info("{} Check was called with arguments: "
|
||||
"{}".format(self._id, args))
|
||||
|
||||
def show_version(self, *args):
|
||||
sudo.log_info("{} Show version was called with arguments: "
|
||||
"{}".format(self._id, args))
|
1
plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stderr
vendored
Normal file
1
plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stderr
vendored
Normal file
@@ -0,0 +1 @@
|
||||
sudo: loading more than 8 sudo python approval plugins is not supported
|
67
plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stdout
vendored
Normal file
67
plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stdout
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
(APPROVAL 1) Constructed:
|
||||
{
|
||||
"plugin_options": [
|
||||
"ModulePath=SRC_DIR/regress/plugin_approval_test.py",
|
||||
"ClassName=ApprovalTestPlugin",
|
||||
"Id=1"
|
||||
],
|
||||
"version": "1.15",
|
||||
"settings": [
|
||||
"SETTING1=VALUE1",
|
||||
"setting2=value2"
|
||||
],
|
||||
"user_env": [
|
||||
"USER_ENV1=VALUE1",
|
||||
"USER_ENV2=value2"
|
||||
],
|
||||
"user_info": [
|
||||
"INFO1=VALUE1",
|
||||
"info2=value2"
|
||||
],
|
||||
"submit_optind": 3,
|
||||
"submit_argv": [
|
||||
"sudo",
|
||||
"-u",
|
||||
"user",
|
||||
"whoami",
|
||||
"--help"
|
||||
],
|
||||
"_id": "(APPROVAL 1)"
|
||||
}
|
||||
(APPROVAL 2) Constructed:
|
||||
{
|
||||
"plugin_options": [
|
||||
"ModulePath=SRC_DIR/regress/plugin_approval_test.py",
|
||||
"ClassName=ApprovalTestPlugin",
|
||||
"Id=2"
|
||||
],
|
||||
"version": "1.15",
|
||||
"settings": [
|
||||
"SETTING1=VALUE1",
|
||||
"setting2=value2"
|
||||
],
|
||||
"user_env": [
|
||||
"USER_ENV1=VALUE1",
|
||||
"USER_ENV2=value2"
|
||||
],
|
||||
"user_info": [
|
||||
"INFO1=VALUE1",
|
||||
"info2=value2"
|
||||
],
|
||||
"submit_optind": 3,
|
||||
"submit_argv": [
|
||||
"sudo",
|
||||
"-u",
|
||||
"user",
|
||||
"whoami",
|
||||
"--help"
|
||||
],
|
||||
"_id": "(APPROVAL 2)"
|
||||
}
|
||||
(APPROVAL 1) Show version was called with arguments: (0,)
|
||||
Python approval plugin API version 1.0
|
||||
(APPROVAL 2) Show version was called with arguments: (1,)
|
||||
(APPROVAL 1) Check was called with arguments: (('CMDINFO1=value1', 'CMDINFO2=VALUE2'), ('whoami', '--help'), ('USER_ENV1=VALUE1', 'USER_ENV2=value2'))
|
||||
(APPROVAL 2) Check was called with arguments: (('CMDINFO1=value1', 'CMDINFO2=VALUE2'), ('whoami', '--help'), ('USER_ENV1=VALUE1', 'USER_ENV2=value2'))
|
||||
(APPROVAL 1) Destructed successfully
|
||||
(APPROVAL 2) Destructed successfully
|
@@ -252,3 +252,19 @@ verify_str_set(char **actual_set, char **expected_set, const char *actual_variab
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
mock_python_datetime_now(const char *plugin_name, const char *date_str)
|
||||
{
|
||||
char *cmd = NULL;
|
||||
asprintf(&cmd, "import %s\n" // the plugin has its own submodule
|
||||
"from datetime import datetime\n" // store the real datetime
|
||||
"from unittest.mock import Mock\n"
|
||||
"%s.datetime = Mock()\n" // replace plugin's datetime
|
||||
"%s.datetime.now = lambda: datetime.fromisoformat('%s')\n",
|
||||
plugin_name, plugin_name, plugin_name, date_str);
|
||||
VERIFY_PTR_NE(cmd, NULL);
|
||||
VERIFY_INT(PyRun_SimpleString(cmd), 0);
|
||||
free(cmd);
|
||||
return true;
|
||||
}
|
||||
|
@@ -157,6 +157,8 @@ int fake_printf(int msg_type, const char *fmt, ...);
|
||||
|
||||
int verify_log_lines(const char *reference_path);
|
||||
|
||||
int mock_python_datetime_now(const char *plugin_name, const char *date_str);
|
||||
|
||||
#define VERIFY_LOG_LINES(reference_path) \
|
||||
VERIFY_TRUE(verify_log_lines(reference_path))
|
||||
|
||||
|
Reference in New Issue
Block a user