github-advanced-security[bot] commented on code in PR #57: URL: https://github.com/apache/iotdb-extras/pull/57#discussion_r2013602728
########## iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/plugin/PluginRuntime.java: ########## @@ -66,24 +88,139 @@ return sinkConstructor.reflectPlugin(sinkParameters); } - public boolean createPlugin() { - return true; + public PluginClassLoader getClassLoader(final String pluginName) throws IOException { + return classLoaderManager.getPluginClassLoader(pluginName); } - public boolean alterPlugin() { - return true; - } - - public boolean startPlugin() { - return true; + public synchronized Response createPlugin( + final String pluginName, + final String className, + final String jarName, + final String jarMD5FromDB, + final boolean isRestRequest) { + try { + // validate whether the plugin jar file exists + if (isRestRequest && !PluginFileUtils.isPluginJarFileExist(jarName)) { + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the plugin jar file %s is not found", + pluginName, jarName); + LOGGER.warn(errorMessage); Review Comment: ## Log Injection This log entry depends on a [user-provided value](1). [Show more details](https://github.com/apache/iotdb-extras/security/code-scanning/37) ########## iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/plugin/PluginRuntime.java: ########## @@ -66,24 +88,139 @@ return sinkConstructor.reflectPlugin(sinkParameters); } - public boolean createPlugin() { - return true; + public PluginClassLoader getClassLoader(final String pluginName) throws IOException { + return classLoaderManager.getPluginClassLoader(pluginName); } - public boolean alterPlugin() { - return true; - } - - public boolean startPlugin() { - return true; + public synchronized Response createPlugin( + final String pluginName, + final String className, + final String jarName, + final String jarMD5FromDB, + final boolean isRestRequest) { + try { + // validate whether the plugin jar file exists + if (isRestRequest && !PluginFileUtils.isPluginJarFileExist(jarName)) { + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the plugin jar file %s is not found", + pluginName, jarName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // validate whether the plugin has been loaded + final PluginMeta information = metaKeeper.getPipePluginMeta(pluginName); + if (Objects.nonNull(information)) { + // validate whether the plugin is builtin plugin + if (information.isBuiltin()) { + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the given Plugin name is the same as a built-in Plugin name.", + pluginName); + LOGGER.warn(errorMessage); Review Comment: ## Log Injection This log entry depends on a [user-provided value](1). [Show more details](https://github.com/apache/iotdb-extras/security/code-scanning/38) ########## iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/plugin/PluginRuntime.java: ########## @@ -66,24 +88,139 @@ return sinkConstructor.reflectPlugin(sinkParameters); } - public boolean createPlugin() { - return true; + public PluginClassLoader getClassLoader(final String pluginName) throws IOException { + return classLoaderManager.getPluginClassLoader(pluginName); } - public boolean alterPlugin() { - return true; - } - - public boolean startPlugin() { - return true; + public synchronized Response createPlugin( + final String pluginName, + final String className, + final String jarName, + final String jarMD5FromDB, + final boolean isRestRequest) { + try { + // validate whether the plugin jar file exists + if (isRestRequest && !PluginFileUtils.isPluginJarFileExist(jarName)) { + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the plugin jar file %s is not found", + pluginName, jarName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // validate whether the plugin has been loaded + final PluginMeta information = metaKeeper.getPipePluginMeta(pluginName); + if (Objects.nonNull(information)) { + // validate whether the plugin is builtin plugin + if (information.isBuiltin()) { + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the given Plugin name is the same as a built-in Plugin name.", + pluginName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // otherwise the plugin has been registered + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the Plugin has been registered.", + pluginName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // get the plugin jar md5 + final String jarMD5 = + jarMD5FromDB == null + ? DigestUtils.md5Hex( + Files.newInputStream(Paths.get(PluginFileUtils.getPluginJarFilePath(jarName)))) + : jarMD5FromDB; + + // If the {pluginName} directory already exists, delete the directory and the files under it, + // recreate the directory, and move the files to the new directory. If an exception occurs in + // the middle, delete the created directory. + final String pluginJarFileNameWithMD5 = + PluginFileUtils.getPluginJarFileNameWithMD5(jarName, jarMD5); + PluginFileUtils.savePluginToInstallDir(pluginName, jarName, pluginJarFileNameWithMD5); + + // create and save plugin class loader + final PluginClassLoader classLoader = + classLoaderManager.createPluginClassLoader( + PluginFileUtils.getPluginInstallDirPath(pluginName)); + + final Class<?> pluginClass = Class.forName(className, true, classLoader); + @SuppressWarnings("unused") // ensure that it is a PipePlugin class + final PipePlugin ignored = (PipePlugin) pluginClass.getDeclaredConstructor().newInstance(); + + classLoaderManager.addPluginClassLoader(pluginName, classLoader); + metaKeeper.addPipePluginMeta( + pluginName, new PluginMeta(pluginName, className, false, jarName, jarMD5)); + metaKeeper.addJarNameAndMd5(jarName, jarMD5); + + // storage registered plugin info + if (isRestRequest) { + PersistenceService.plugin() + .ifPresent( + pluginPersistence -> + pluginPersistence.tryPersistencePlugin(pluginName, className, jarName, jarMD5)); + } + + final String successMessage = String.format("Successfully register Plugin %s", pluginName); + LOGGER.info(successMessage); + return Response.ok().entity(successMessage).build(); + } catch (final Exception e) { + final String errorMessage = + String.format("Failed to register Plugin %s, because %s", pluginName, e); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } } - public boolean stopPlugin() { - return true; + public synchronized Response dropPlugin(final String pluginName) { + try { + final PluginMeta information = metaKeeper.getPipePluginMeta(pluginName); + if (Objects.nonNull(information) && information.isBuiltin()) { + final String errorMessage = + String.format("Failed to deregister builtin Plugin %s.", pluginName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // if it is needed to delete jar file of the plugin, delete both jar file and md5 + final String installedFileName = + FilenameUtils.getBaseName(information.getJarName()) + + "-" + + information.getJarMD5() + + "." + + FilenameUtils.getExtension(information.getJarName()); + PluginFileUtils.removePluginFileUnderLibRoot(information.getPluginName(), installedFileName); + + // remove anyway + metaKeeper.removeJarNameAndMd5IfPossible(pluginName); + metaKeeper.removePipePluginMeta(pluginName); + classLoaderManager.removePluginClassLoader(pluginName); + + // remove plugin info from sqlite + PersistenceService.plugin() + .ifPresent(pluginPersistence -> pluginPersistence.tryDeletePlugin(pluginName)); + + final String successMessage = String.format("Successfully deregister Plugin %s", pluginName); + LOGGER.info(successMessage); Review Comment: ## Log Injection This log entry depends on a [user-provided value](1). [Show more details](https://github.com/apache/iotdb-extras/security/code-scanning/43) ########## iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/plugin/PluginRuntime.java: ########## @@ -66,24 +88,139 @@ return sinkConstructor.reflectPlugin(sinkParameters); } - public boolean createPlugin() { - return true; + public PluginClassLoader getClassLoader(final String pluginName) throws IOException { + return classLoaderManager.getPluginClassLoader(pluginName); } - public boolean alterPlugin() { - return true; - } - - public boolean startPlugin() { - return true; + public synchronized Response createPlugin( + final String pluginName, + final String className, + final String jarName, + final String jarMD5FromDB, + final boolean isRestRequest) { + try { + // validate whether the plugin jar file exists + if (isRestRequest && !PluginFileUtils.isPluginJarFileExist(jarName)) { + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the plugin jar file %s is not found", + pluginName, jarName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // validate whether the plugin has been loaded + final PluginMeta information = metaKeeper.getPipePluginMeta(pluginName); + if (Objects.nonNull(information)) { + // validate whether the plugin is builtin plugin + if (information.isBuiltin()) { + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the given Plugin name is the same as a built-in Plugin name.", + pluginName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // otherwise the plugin has been registered + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the Plugin has been registered.", + pluginName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // get the plugin jar md5 + final String jarMD5 = + jarMD5FromDB == null + ? DigestUtils.md5Hex( + Files.newInputStream(Paths.get(PluginFileUtils.getPluginJarFilePath(jarName)))) + : jarMD5FromDB; + + // If the {pluginName} directory already exists, delete the directory and the files under it, + // recreate the directory, and move the files to the new directory. If an exception occurs in + // the middle, delete the created directory. + final String pluginJarFileNameWithMD5 = + PluginFileUtils.getPluginJarFileNameWithMD5(jarName, jarMD5); + PluginFileUtils.savePluginToInstallDir(pluginName, jarName, pluginJarFileNameWithMD5); + + // create and save plugin class loader + final PluginClassLoader classLoader = + classLoaderManager.createPluginClassLoader( + PluginFileUtils.getPluginInstallDirPath(pluginName)); + + final Class<?> pluginClass = Class.forName(className, true, classLoader); + @SuppressWarnings("unused") // ensure that it is a PipePlugin class + final PipePlugin ignored = (PipePlugin) pluginClass.getDeclaredConstructor().newInstance(); + + classLoaderManager.addPluginClassLoader(pluginName, classLoader); + metaKeeper.addPipePluginMeta( + pluginName, new PluginMeta(pluginName, className, false, jarName, jarMD5)); + metaKeeper.addJarNameAndMd5(jarName, jarMD5); + + // storage registered plugin info + if (isRestRequest) { + PersistenceService.plugin() + .ifPresent( + pluginPersistence -> + pluginPersistence.tryPersistencePlugin(pluginName, className, jarName, jarMD5)); + } + + final String successMessage = String.format("Successfully register Plugin %s", pluginName); + LOGGER.info(successMessage); + return Response.ok().entity(successMessage).build(); + } catch (final Exception e) { + final String errorMessage = + String.format("Failed to register Plugin %s, because %s", pluginName, e); + LOGGER.warn(errorMessage); Review Comment: ## Log Injection This log entry depends on a [user-provided value](1). [Show more details](https://github.com/apache/iotdb-extras/security/code-scanning/41) ########## iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/plugin/PluginRuntime.java: ########## @@ -66,24 +88,139 @@ return sinkConstructor.reflectPlugin(sinkParameters); } - public boolean createPlugin() { - return true; + public PluginClassLoader getClassLoader(final String pluginName) throws IOException { + return classLoaderManager.getPluginClassLoader(pluginName); } - public boolean alterPlugin() { - return true; - } - - public boolean startPlugin() { - return true; + public synchronized Response createPlugin( + final String pluginName, + final String className, + final String jarName, + final String jarMD5FromDB, + final boolean isRestRequest) { + try { + // validate whether the plugin jar file exists + if (isRestRequest && !PluginFileUtils.isPluginJarFileExist(jarName)) { + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the plugin jar file %s is not found", + pluginName, jarName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // validate whether the plugin has been loaded + final PluginMeta information = metaKeeper.getPipePluginMeta(pluginName); + if (Objects.nonNull(information)) { + // validate whether the plugin is builtin plugin + if (information.isBuiltin()) { + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the given Plugin name is the same as a built-in Plugin name.", + pluginName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // otherwise the plugin has been registered + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the Plugin has been registered.", + pluginName); + LOGGER.warn(errorMessage); Review Comment: ## Log Injection This log entry depends on a [user-provided value](1). [Show more details](https://github.com/apache/iotdb-extras/security/code-scanning/39) ########## iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/plugin/PluginRuntime.java: ########## @@ -66,24 +88,139 @@ return sinkConstructor.reflectPlugin(sinkParameters); } - public boolean createPlugin() { - return true; + public PluginClassLoader getClassLoader(final String pluginName) throws IOException { + return classLoaderManager.getPluginClassLoader(pluginName); } - public boolean alterPlugin() { - return true; - } - - public boolean startPlugin() { - return true; + public synchronized Response createPlugin( + final String pluginName, + final String className, + final String jarName, + final String jarMD5FromDB, + final boolean isRestRequest) { + try { + // validate whether the plugin jar file exists + if (isRestRequest && !PluginFileUtils.isPluginJarFileExist(jarName)) { + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the plugin jar file %s is not found", + pluginName, jarName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // validate whether the plugin has been loaded + final PluginMeta information = metaKeeper.getPipePluginMeta(pluginName); + if (Objects.nonNull(information)) { + // validate whether the plugin is builtin plugin + if (information.isBuiltin()) { + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the given Plugin name is the same as a built-in Plugin name.", + pluginName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // otherwise the plugin has been registered + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the Plugin has been registered.", + pluginName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // get the plugin jar md5 + final String jarMD5 = + jarMD5FromDB == null + ? DigestUtils.md5Hex( + Files.newInputStream(Paths.get(PluginFileUtils.getPluginJarFilePath(jarName)))) + : jarMD5FromDB; + + // If the {pluginName} directory already exists, delete the directory and the files under it, + // recreate the directory, and move the files to the new directory. If an exception occurs in + // the middle, delete the created directory. + final String pluginJarFileNameWithMD5 = + PluginFileUtils.getPluginJarFileNameWithMD5(jarName, jarMD5); + PluginFileUtils.savePluginToInstallDir(pluginName, jarName, pluginJarFileNameWithMD5); + + // create and save plugin class loader + final PluginClassLoader classLoader = + classLoaderManager.createPluginClassLoader( + PluginFileUtils.getPluginInstallDirPath(pluginName)); + + final Class<?> pluginClass = Class.forName(className, true, classLoader); + @SuppressWarnings("unused") // ensure that it is a PipePlugin class + final PipePlugin ignored = (PipePlugin) pluginClass.getDeclaredConstructor().newInstance(); + + classLoaderManager.addPluginClassLoader(pluginName, classLoader); + metaKeeper.addPipePluginMeta( + pluginName, new PluginMeta(pluginName, className, false, jarName, jarMD5)); + metaKeeper.addJarNameAndMd5(jarName, jarMD5); + + // storage registered plugin info + if (isRestRequest) { + PersistenceService.plugin() + .ifPresent( + pluginPersistence -> + pluginPersistence.tryPersistencePlugin(pluginName, className, jarName, jarMD5)); + } + + final String successMessage = String.format("Successfully register Plugin %s", pluginName); + LOGGER.info(successMessage); Review Comment: ## Log Injection This log entry depends on a [user-provided value](1). [Show more details](https://github.com/apache/iotdb-extras/security/code-scanning/40) ########## iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/plugin/utils/PluginFileUtils.java: ########## @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.runtime.plugin.utils; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +import static org.apache.iotdb.collector.config.PluginRuntimeOptions.PLUGIN_INSTALL_LIB_DIR; +import static org.apache.iotdb.collector.config.PluginRuntimeOptions.PLUGIN_LIB_DIR; + +public class PluginFileUtils { + + public static void savePluginToInstallDir( + final String pluginName, final String jarName, final String jarNameWithMD5) + throws IOException { + final Path pluginInstallPath = + Paths.get(PLUGIN_INSTALL_LIB_DIR.value() + File.separator + pluginName); + final Path pluginJarInstallPath = + Paths.get(getPluginJarFileWithMD5FilePath(pluginName, jarNameWithMD5)); + + if (!Files.exists(pluginInstallPath)) { + FileUtils.forceMkdir(pluginInstallPath.toFile()); + } + if (Files.exists(pluginJarInstallPath)) { Review Comment: ## Uncontrolled data used in path expression This path depends on a [user-provided value](1). [Show more details](https://github.com/apache/iotdb-extras/security/code-scanning/34) ########## iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/plugin/PluginRuntime.java: ########## @@ -66,24 +88,139 @@ return sinkConstructor.reflectPlugin(sinkParameters); } - public boolean createPlugin() { - return true; + public PluginClassLoader getClassLoader(final String pluginName) throws IOException { + return classLoaderManager.getPluginClassLoader(pluginName); } - public boolean alterPlugin() { - return true; - } - - public boolean startPlugin() { - return true; + public synchronized Response createPlugin( + final String pluginName, + final String className, + final String jarName, + final String jarMD5FromDB, + final boolean isRestRequest) { + try { + // validate whether the plugin jar file exists + if (isRestRequest && !PluginFileUtils.isPluginJarFileExist(jarName)) { + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the plugin jar file %s is not found", + pluginName, jarName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // validate whether the plugin has been loaded + final PluginMeta information = metaKeeper.getPipePluginMeta(pluginName); + if (Objects.nonNull(information)) { + // validate whether the plugin is builtin plugin + if (information.isBuiltin()) { + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the given Plugin name is the same as a built-in Plugin name.", + pluginName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // otherwise the plugin has been registered + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the Plugin has been registered.", + pluginName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // get the plugin jar md5 + final String jarMD5 = + jarMD5FromDB == null + ? DigestUtils.md5Hex( + Files.newInputStream(Paths.get(PluginFileUtils.getPluginJarFilePath(jarName)))) + : jarMD5FromDB; + + // If the {pluginName} directory already exists, delete the directory and the files under it, + // recreate the directory, and move the files to the new directory. If an exception occurs in + // the middle, delete the created directory. + final String pluginJarFileNameWithMD5 = + PluginFileUtils.getPluginJarFileNameWithMD5(jarName, jarMD5); + PluginFileUtils.savePluginToInstallDir(pluginName, jarName, pluginJarFileNameWithMD5); + + // create and save plugin class loader + final PluginClassLoader classLoader = + classLoaderManager.createPluginClassLoader( + PluginFileUtils.getPluginInstallDirPath(pluginName)); + + final Class<?> pluginClass = Class.forName(className, true, classLoader); + @SuppressWarnings("unused") // ensure that it is a PipePlugin class + final PipePlugin ignored = (PipePlugin) pluginClass.getDeclaredConstructor().newInstance(); + + classLoaderManager.addPluginClassLoader(pluginName, classLoader); + metaKeeper.addPipePluginMeta( + pluginName, new PluginMeta(pluginName, className, false, jarName, jarMD5)); + metaKeeper.addJarNameAndMd5(jarName, jarMD5); + + // storage registered plugin info + if (isRestRequest) { + PersistenceService.plugin() + .ifPresent( + pluginPersistence -> + pluginPersistence.tryPersistencePlugin(pluginName, className, jarName, jarMD5)); + } + + final String successMessage = String.format("Successfully register Plugin %s", pluginName); + LOGGER.info(successMessage); + return Response.ok().entity(successMessage).build(); + } catch (final Exception e) { + final String errorMessage = + String.format("Failed to register Plugin %s, because %s", pluginName, e); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } } - public boolean stopPlugin() { - return true; + public synchronized Response dropPlugin(final String pluginName) { + try { + final PluginMeta information = metaKeeper.getPipePluginMeta(pluginName); + if (Objects.nonNull(information) && information.isBuiltin()) { + final String errorMessage = + String.format("Failed to deregister builtin Plugin %s.", pluginName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // if it is needed to delete jar file of the plugin, delete both jar file and md5 + final String installedFileName = + FilenameUtils.getBaseName(information.getJarName()) + + "-" + + information.getJarMD5() + + "." + + FilenameUtils.getExtension(information.getJarName()); + PluginFileUtils.removePluginFileUnderLibRoot(information.getPluginName(), installedFileName); + + // remove anyway + metaKeeper.removeJarNameAndMd5IfPossible(pluginName); + metaKeeper.removePipePluginMeta(pluginName); + classLoaderManager.removePluginClassLoader(pluginName); + + // remove plugin info from sqlite + PersistenceService.plugin() + .ifPresent(pluginPersistence -> pluginPersistence.tryDeletePlugin(pluginName)); + + final String successMessage = String.format("Successfully deregister Plugin %s", pluginName); + LOGGER.info(successMessage); + return Response.ok().entity(successMessage).build(); + } catch (final IOException e) { + final String errorMessage = + String.format( + "Failed to deregister builtin Plugin %s, because %s", pluginName, e.getMessage()); + LOGGER.warn(errorMessage); Review Comment: ## Log Injection This log entry depends on a [user-provided value](1). [Show more details](https://github.com/apache/iotdb-extras/security/code-scanning/44) ########## iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/plugin/PluginRuntime.java: ########## @@ -66,24 +88,139 @@ return sinkConstructor.reflectPlugin(sinkParameters); } - public boolean createPlugin() { - return true; + public PluginClassLoader getClassLoader(final String pluginName) throws IOException { + return classLoaderManager.getPluginClassLoader(pluginName); } - public boolean alterPlugin() { - return true; - } - - public boolean startPlugin() { - return true; + public synchronized Response createPlugin( + final String pluginName, + final String className, + final String jarName, + final String jarMD5FromDB, + final boolean isRestRequest) { + try { + // validate whether the plugin jar file exists + if (isRestRequest && !PluginFileUtils.isPluginJarFileExist(jarName)) { + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the plugin jar file %s is not found", + pluginName, jarName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // validate whether the plugin has been loaded + final PluginMeta information = metaKeeper.getPipePluginMeta(pluginName); + if (Objects.nonNull(information)) { + // validate whether the plugin is builtin plugin + if (information.isBuiltin()) { + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the given Plugin name is the same as a built-in Plugin name.", + pluginName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // otherwise the plugin has been registered + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the Plugin has been registered.", + pluginName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // get the plugin jar md5 + final String jarMD5 = + jarMD5FromDB == null + ? DigestUtils.md5Hex( + Files.newInputStream(Paths.get(PluginFileUtils.getPluginJarFilePath(jarName)))) + : jarMD5FromDB; + + // If the {pluginName} directory already exists, delete the directory and the files under it, + // recreate the directory, and move the files to the new directory. If an exception occurs in + // the middle, delete the created directory. + final String pluginJarFileNameWithMD5 = + PluginFileUtils.getPluginJarFileNameWithMD5(jarName, jarMD5); + PluginFileUtils.savePluginToInstallDir(pluginName, jarName, pluginJarFileNameWithMD5); + + // create and save plugin class loader + final PluginClassLoader classLoader = + classLoaderManager.createPluginClassLoader( + PluginFileUtils.getPluginInstallDirPath(pluginName)); + + final Class<?> pluginClass = Class.forName(className, true, classLoader); + @SuppressWarnings("unused") // ensure that it is a PipePlugin class + final PipePlugin ignored = (PipePlugin) pluginClass.getDeclaredConstructor().newInstance(); + + classLoaderManager.addPluginClassLoader(pluginName, classLoader); + metaKeeper.addPipePluginMeta( + pluginName, new PluginMeta(pluginName, className, false, jarName, jarMD5)); + metaKeeper.addJarNameAndMd5(jarName, jarMD5); + + // storage registered plugin info + if (isRestRequest) { + PersistenceService.plugin() + .ifPresent( + pluginPersistence -> + pluginPersistence.tryPersistencePlugin(pluginName, className, jarName, jarMD5)); + } + + final String successMessage = String.format("Successfully register Plugin %s", pluginName); + LOGGER.info(successMessage); + return Response.ok().entity(successMessage).build(); + } catch (final Exception e) { + final String errorMessage = + String.format("Failed to register Plugin %s, because %s", pluginName, e); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } } - public boolean stopPlugin() { - return true; + public synchronized Response dropPlugin(final String pluginName) { + try { + final PluginMeta information = metaKeeper.getPipePluginMeta(pluginName); + if (Objects.nonNull(information) && information.isBuiltin()) { + final String errorMessage = + String.format("Failed to deregister builtin Plugin %s.", pluginName); + LOGGER.warn(errorMessage); Review Comment: ## Log Injection This log entry depends on a [user-provided value](1). [Show more details](https://github.com/apache/iotdb-extras/security/code-scanning/42) ########## iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/plugin/utils/PluginFileUtils.java: ########## @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.runtime.plugin.utils; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +import static org.apache.iotdb.collector.config.PluginRuntimeOptions.PLUGIN_INSTALL_LIB_DIR; +import static org.apache.iotdb.collector.config.PluginRuntimeOptions.PLUGIN_LIB_DIR; + +public class PluginFileUtils { + + public static void savePluginToInstallDir( + final String pluginName, final String jarName, final String jarNameWithMD5) + throws IOException { + final Path pluginInstallPath = + Paths.get(PLUGIN_INSTALL_LIB_DIR.value() + File.separator + pluginName); + final Path pluginJarInstallPath = + Paths.get(getPluginJarFileWithMD5FilePath(pluginName, jarNameWithMD5)); + + if (!Files.exists(pluginInstallPath)) { + FileUtils.forceMkdir(pluginInstallPath.toFile()); + } + if (Files.exists(pluginJarInstallPath)) { + return; + } + + FileUtils.moveFile( + new File(getPluginJarFilePath(jarName)), + pluginJarInstallPath.toFile(), + StandardCopyOption.REPLACE_EXISTING); + } + + public static boolean isPluginJarFileExist(final String jarName) { + return Files.exists(Paths.get(getPluginJarFilePath(jarName))); Review Comment: ## Uncontrolled data used in path expression This path depends on a [user-provided value](1). [Show more details](https://github.com/apache/iotdb-extras/security/code-scanning/36) ########## iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/plugin/PluginRuntime.java: ########## @@ -66,24 +88,139 @@ return sinkConstructor.reflectPlugin(sinkParameters); } - public boolean createPlugin() { - return true; + public PluginClassLoader getClassLoader(final String pluginName) throws IOException { + return classLoaderManager.getPluginClassLoader(pluginName); } - public boolean alterPlugin() { - return true; - } - - public boolean startPlugin() { - return true; + public synchronized Response createPlugin( + final String pluginName, + final String className, + final String jarName, + final String jarMD5FromDB, + final boolean isRestRequest) { + try { + // validate whether the plugin jar file exists + if (isRestRequest && !PluginFileUtils.isPluginJarFileExist(jarName)) { + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the plugin jar file %s is not found", + pluginName, jarName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // validate whether the plugin has been loaded + final PluginMeta information = metaKeeper.getPipePluginMeta(pluginName); + if (Objects.nonNull(information)) { + // validate whether the plugin is builtin plugin + if (information.isBuiltin()) { + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the given Plugin name is the same as a built-in Plugin name.", + pluginName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // otherwise the plugin has been registered + final String errorMessage = + String.format( + "Failed to register Plugin %s, because the Plugin has been registered.", + pluginName); + LOGGER.warn(errorMessage); + return Response.serverError().entity(errorMessage).build(); + } + + // get the plugin jar md5 + final String jarMD5 = + jarMD5FromDB == null + ? DigestUtils.md5Hex( + Files.newInputStream(Paths.get(PluginFileUtils.getPluginJarFilePath(jarName)))) Review Comment: ## Uncontrolled data used in path expression This path depends on a [user-provided value](1). [Show more details](https://github.com/apache/iotdb-extras/security/code-scanning/30) ########## iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/plugin/utils/PluginFileUtils.java: ########## @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.runtime.plugin.utils; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +import static org.apache.iotdb.collector.config.PluginRuntimeOptions.PLUGIN_INSTALL_LIB_DIR; +import static org.apache.iotdb.collector.config.PluginRuntimeOptions.PLUGIN_LIB_DIR; + +public class PluginFileUtils { + + public static void savePluginToInstallDir( + final String pluginName, final String jarName, final String jarNameWithMD5) + throws IOException { + final Path pluginInstallPath = + Paths.get(PLUGIN_INSTALL_LIB_DIR.value() + File.separator + pluginName); + final Path pluginJarInstallPath = + Paths.get(getPluginJarFileWithMD5FilePath(pluginName, jarNameWithMD5)); + + if (!Files.exists(pluginInstallPath)) { Review Comment: ## Uncontrolled data used in path expression This path depends on a [user-provided value](1). [Show more details](https://github.com/apache/iotdb-extras/security/code-scanning/32) ########## iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/plugin/utils/PluginFileUtils.java: ########## @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.runtime.plugin.utils; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +import static org.apache.iotdb.collector.config.PluginRuntimeOptions.PLUGIN_INSTALL_LIB_DIR; +import static org.apache.iotdb.collector.config.PluginRuntimeOptions.PLUGIN_LIB_DIR; + +public class PluginFileUtils { + + public static void savePluginToInstallDir( + final String pluginName, final String jarName, final String jarNameWithMD5) + throws IOException { + final Path pluginInstallPath = + Paths.get(PLUGIN_INSTALL_LIB_DIR.value() + File.separator + pluginName); + final Path pluginJarInstallPath = + Paths.get(getPluginJarFileWithMD5FilePath(pluginName, jarNameWithMD5)); + + if (!Files.exists(pluginInstallPath)) { + FileUtils.forceMkdir(pluginInstallPath.toFile()); Review Comment: ## Uncontrolled data used in path expression This path depends on a [user-provided value](1). [Show more details](https://github.com/apache/iotdb-extras/security/code-scanning/33) ########## iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/plugin/load/PluginClassLoader.java: ########## @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.runtime.plugin.load; + +import javax.annotation.concurrent.ThreadSafe; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@ThreadSafe +public class PluginClassLoader extends URLClassLoader { + + private final String libRoot; + private volatile boolean deprecated; + + public PluginClassLoader(final String libRoot) throws IOException { + super(new URL[0]); + this.libRoot = libRoot; + this.deprecated = false; + addUrls(); + } + + private void addUrls() throws IOException { + try (final Stream<Path> pathStream = Files.walk(new File(libRoot).toPath())) { + for (final Path path : + pathStream.filter(path -> !path.toFile().isDirectory()).collect(Collectors.toList())) { Review Comment: ## Uncontrolled data used in path expression This path depends on a [user-provided value](1). [Show more details](https://github.com/apache/iotdb-extras/security/code-scanning/31) ########## iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/plugin/utils/PluginFileUtils.java: ########## @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.runtime.plugin.utils; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +import static org.apache.iotdb.collector.config.PluginRuntimeOptions.PLUGIN_INSTALL_LIB_DIR; +import static org.apache.iotdb.collector.config.PluginRuntimeOptions.PLUGIN_LIB_DIR; + +public class PluginFileUtils { + + public static void savePluginToInstallDir( + final String pluginName, final String jarName, final String jarNameWithMD5) + throws IOException { + final Path pluginInstallPath = + Paths.get(PLUGIN_INSTALL_LIB_DIR.value() + File.separator + pluginName); + final Path pluginJarInstallPath = + Paths.get(getPluginJarFileWithMD5FilePath(pluginName, jarNameWithMD5)); + + if (!Files.exists(pluginInstallPath)) { + FileUtils.forceMkdir(pluginInstallPath.toFile()); + } + if (Files.exists(pluginJarInstallPath)) { + return; + } + + FileUtils.moveFile( + new File(getPluginJarFilePath(jarName)), + pluginJarInstallPath.toFile(), Review Comment: ## Uncontrolled data used in path expression This path depends on a [user-provided value](1). [Show more details](https://github.com/apache/iotdb-extras/security/code-scanning/35) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: dev-unsubscr...@iotdb.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org