From 43e256e34f85aa6032c741278a676a80fbe8cff9 Mon Sep 17 00:00:00 2001 From: Robert Manner Date: Tue, 11 Feb 2020 14:39:59 +0100 Subject: [PATCH] plugins/python/regress: add tests for approval plugin --- .../python/regress/check_python_examples.c | 126 ++++++++++++++++++ .../python/regress/plugin_approval_test.py | 22 +++ ...tiple_approval_plugin_and_arguments.stderr | 1 + ...tiple_approval_plugin_and_arguments.stdout | 67 ++++++++++ plugins/python/regress/testhelpers.c | 16 +++ plugins/python/regress/testhelpers.h | 2 + 6 files changed, 234 insertions(+) create mode 100644 plugins/python/regress/plugin_approval_test.py create mode 100644 plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stderr create mode 100644 plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stdout diff --git a/plugins/python/regress/check_python_examples.c b/plugins/python/regress/check_python_examples.c index 430d475ff..ba38ce297 100644 --- a/plugins/python/regress/check_python_examples.c +++ b/plugins/python/regress/check_python_examples.c @@ -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")); diff --git a/plugins/python/regress/plugin_approval_test.py b/plugins/python/regress/plugin_approval_test.py new file mode 100644 index 000000000..ec602e388 --- /dev/null +++ b/plugins/python/regress/plugin_approval_test.py @@ -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)) diff --git a/plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stderr b/plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stderr new file mode 100644 index 000000000..6dfb14166 --- /dev/null +++ b/plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stderr @@ -0,0 +1 @@ +sudo: loading more than 8 sudo python approval plugins is not supported diff --git a/plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stdout b/plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stdout new file mode 100644 index 000000000..4eca0f47a --- /dev/null +++ b/plugins/python/regress/testdata/check_multiple_approval_plugin_and_arguments.stdout @@ -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 diff --git a/plugins/python/regress/testhelpers.c b/plugins/python/regress/testhelpers.c index c56c8030c..cca3d140d 100644 --- a/plugins/python/regress/testhelpers.c +++ b/plugins/python/regress/testhelpers.c @@ -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; +} diff --git a/plugins/python/regress/testhelpers.h b/plugins/python/regress/testhelpers.h index 55c477b03..70cccb625 100644 --- a/plugins/python/regress/testhelpers.h +++ b/plugins/python/regress/testhelpers.h @@ -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))