plugins/python: autodetect ClassName field
If "ClassName" is not specified, load the one and only sudo.Plugin from the module (if so), otherwise display which plugins are available from which the system admin can choose.
This commit is contained in:

committed by
Todd C. Miller

parent
5c96b4407d
commit
34b4bb72d6
@@ -186,7 +186,11 @@ It must be either an absolute path or a path relative to the sudo Python plugin
|
||||
directory: "@plugindir@/python".
|
||||
.TP 6n
|
||||
ClassName
|
||||
The name of the class implementing the sudo Python plugin.
|
||||
(Optional.) The name of the class implementing the sudo Python plugin.
|
||||
If not supplied, the one and only sudo.Plugin that is present in the module
|
||||
will be used.
|
||||
If there are multiple such plugins in the module (or none), it
|
||||
will result in an error.
|
||||
.SS "Policy plugin API"
|
||||
Policy plugins must be registered in
|
||||
sudo.conf(@mansectform@).
|
||||
|
@@ -156,7 +156,11 @@ The path of a python file which contains the class of the sudo Python plugin.
|
||||
It must be either an absolute path or a path relative to the sudo Python plugin
|
||||
directory: "@plugindir@/python".
|
||||
.It ClassName
|
||||
The name of the class implementing the sudo Python plugin.
|
||||
(Optional.) The name of the class implementing the sudo Python plugin.
|
||||
If not supplied, the one and only sudo.Plugin that is present in the module
|
||||
will be used.
|
||||
If there are multiple such plugins in the module (or none), it
|
||||
will result in an error.
|
||||
.El
|
||||
.Ss Policy plugin API
|
||||
Policy plugins must be registered in
|
||||
|
@@ -399,6 +399,82 @@ _python_plugin_set_path(struct PluginContext *plugin_ctx, const char *path)
|
||||
return SUDO_RC_OK;
|
||||
}
|
||||
|
||||
/* Returns the list of sudo.Plugins in a module */
|
||||
static PyObject *
|
||||
_python_plugin_class_list(PyObject *py_module) {
|
||||
PyObject *py_module_dict = PyModule_GetDict(py_module); // Note: borrowed
|
||||
PyObject *key, *value; // Note: borrowed
|
||||
Py_ssize_t pos = 0;
|
||||
PyObject *py_plugin_list = PyList_New(0);
|
||||
|
||||
while (PyDict_Next(py_module_dict, &pos, &key, &value)) {
|
||||
if (PyObject_IsSubclass(value, (PyObject *)sudo_type_Plugin) == 1) {
|
||||
if (PyList_Append(py_plugin_list, key) != 0)
|
||||
goto cleanup;
|
||||
} else {
|
||||
PyErr_Clear();
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (PyErr_Occurred()) {
|
||||
Py_CLEAR(py_plugin_list);
|
||||
}
|
||||
return py_plugin_list;
|
||||
}
|
||||
|
||||
/* Gets a sudo.Plugin class from the specified module. The argument "plugin_class"
|
||||
* can be NULL in which case it loads the one and only "sudo.Plugin" present
|
||||
* in the module (if so), or displays helpful error message. */
|
||||
static PyObject *
|
||||
_python_plugin_get_class(const char *plugin_path, PyObject *py_module, const char *plugin_class)
|
||||
{
|
||||
debug_decl(python_plugin_init, PYTHON_DEBUG_PLUGIN_LOAD);
|
||||
PyObject *py_plugin_list = NULL, *py_class = NULL;
|
||||
|
||||
if (plugin_class == NULL) {
|
||||
py_plugin_list = _python_plugin_class_list(py_module);
|
||||
if (py_plugin_list == NULL) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (PyList_Size(py_plugin_list) == 1) {
|
||||
PyObject *py_plugin_name = PyList_GetItem(py_plugin_list, 0); // Note: borrowed
|
||||
plugin_class = PyUnicode_AsUTF8(py_plugin_name);
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin_class == NULL) {
|
||||
py_sudo_log(SUDO_CONV_ERROR_MSG, "No plugin class is specified for python module '%s'. "
|
||||
"Use 'ClassName' configuration option in 'sudo.conf'\n", plugin_path);
|
||||
if (py_plugin_list != NULL) {
|
||||
char *possible_plugins = py_join_str_list(py_plugin_list, ", ");
|
||||
if (possible_plugins != NULL) {
|
||||
py_sudo_log(SUDO_CONV_ERROR_MSG, "Possible plugins: %s\n", possible_plugins);
|
||||
free(possible_plugins);
|
||||
}
|
||||
}
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
sudo_debug_printf(SUDO_DEBUG_DEBUG, "Using plugin class '%s'", plugin_class);
|
||||
py_class = PyObject_GetAttrString(py_module, plugin_class);
|
||||
if (py_class == NULL) {
|
||||
py_sudo_log(SUDO_CONV_ERROR_MSG, "Failed to find plugin class '%s'\n", plugin_class);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!PyObject_IsSubclass(py_class, (PyObject *)sudo_type_Plugin)) {
|
||||
py_sudo_log(SUDO_CONV_ERROR_MSG, "Plugin class '%s' does not inherit from 'sudo.Plugin'\n", plugin_class);
|
||||
Py_CLEAR(py_class);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
Py_CLEAR(py_plugin_list);
|
||||
debug_return_ptr(py_class);
|
||||
}
|
||||
|
||||
int
|
||||
python_plugin_init(struct PluginContext *plugin_ctx, char * const plugin_options[],
|
||||
unsigned int version)
|
||||
@@ -419,8 +495,7 @@ python_plugin_init(struct PluginContext *plugin_ctx, char * const plugin_options
|
||||
PyThreadState_Swap(plugin_ctx->py_interpreter);
|
||||
|
||||
if (!sudo_conf_developer_mode() && sudo_module_register_importblocker() < 0) {
|
||||
py_log_last_error(NULL);
|
||||
debug_return_int(SUDO_RC_ERROR);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (_python_plugin_set_path(plugin_ctx, _lookup_value(plugin_options, "ModulePath")) != SUDO_RC_OK) {
|
||||
@@ -433,23 +508,9 @@ python_plugin_init(struct PluginContext *plugin_ctx, char * const plugin_options
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
const char *plugin_class = _lookup_value(plugin_options, "ClassName");
|
||||
if (plugin_class == NULL) {
|
||||
py_sudo_log(SUDO_CONV_ERROR_MSG, "No plugin class is specified for python module '%s'. "
|
||||
"Use 'ClassName' configuration option in 'sudo.conf'\n", plugin_ctx->plugin_path);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
sudo_debug_printf(SUDO_DEBUG_DEBUG, "Using plugin class '%s'", plugin_class);
|
||||
plugin_ctx->py_class = PyObject_GetAttrString(plugin_ctx->py_module, plugin_class);
|
||||
plugin_ctx->py_class = _python_plugin_get_class(plugin_ctx->plugin_path, plugin_ctx->py_module,
|
||||
_lookup_value(plugin_options, "ClassName"));
|
||||
if (plugin_ctx->py_class == NULL) {
|
||||
py_sudo_log(SUDO_CONV_ERROR_MSG, "Failed to find plugin class '%s'\n", plugin_class);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!PyObject_IsSubclass(plugin_ctx->py_class, (PyObject *)sudo_type_Plugin)) {
|
||||
py_sudo_log(SUDO_CONV_ERROR_MSG, "Plugin class '%s' does not inherit from 'sudo.Plugin'\n", plugin_class);
|
||||
Py_CLEAR(plugin_ctx->py_class);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
|
@@ -584,10 +584,31 @@ check_loading_fails_with_missing_path(void)
|
||||
}
|
||||
|
||||
int
|
||||
check_loading_fails_with_missing_classname(void)
|
||||
check_loading_succeeds_with_missing_classname(void)
|
||||
{
|
||||
str_array_free(&data.plugin_options);
|
||||
data.plugin_options = create_str_array(2, "ModulePath=" SRC_DIR "/example_debugging.py", NULL);
|
||||
|
||||
const char *errstr = NULL;
|
||||
|
||||
VERIFY_INT(python_io->open(SUDO_API_VERSION, fake_conversation, fake_printf, data.settings,
|
||||
data.user_info, data.command_info, data.plugin_argc, data.plugin_argv,
|
||||
data.user_env, data.plugin_options, &errstr), SUDO_RC_OK);
|
||||
VERIFY_PTR(errstr, NULL);
|
||||
VERIFY_INT(python_io->show_version(1), SUDO_RC_OK);
|
||||
python_io->close(0, 0);
|
||||
|
||||
VERIFY_STDOUT(expected_path("check_loading_succeeds_with_missing_classname.stdout"));
|
||||
VERIFY_STR(data.stderr_str, "");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
check_loading_fails_with_missing_classname(void)
|
||||
{
|
||||
str_array_free(&data.plugin_options);
|
||||
data.plugin_options = create_str_array(2, "ModulePath=" SRC_DIR "/regress/plugin_errorstr.py", NULL);
|
||||
return check_loading_fails("missing_classname");
|
||||
}
|
||||
|
||||
@@ -1511,6 +1532,7 @@ main(int argc, char *argv[])
|
||||
RUN_TEST(check_plugin_unload());
|
||||
|
||||
RUN_TEST(check_loading_fails_with_missing_path());
|
||||
RUN_TEST(check_loading_succeeds_with_missing_classname());
|
||||
RUN_TEST(check_loading_fails_with_missing_classname());
|
||||
RUN_TEST(check_loading_fails_with_wrong_classname());
|
||||
RUN_TEST(check_loading_fails_with_wrong_path());
|
||||
|
@@ -1,2 +1,3 @@
|
||||
No plugin class is specified for python module 'SRC_DIR/example_debugging.py'. Use 'ClassName' configuration option in 'sudo.conf'
|
||||
No plugin class is specified for python module 'SRC_DIR/regress/plugin_errorstr.py'. Use 'ClassName' configuration option in 'sudo.conf'
|
||||
Possible plugins: ErrorMsgPlugin, ConstructErrorPlugin
|
||||
Failed during loading plugin class
|
||||
|
1
plugins/python/regress/testdata/check_loading_succeeds_with_missing_classname.stdout
vendored
Normal file
1
plugins/python/regress/testdata/check_loading_succeeds_with_missing_classname.stdout
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Python io plugin (API 1.0): DebugDemoPlugin (loaded from 'SRC_DIR/example_debugging.py')
|
Reference in New Issue
Block a user