diff --git a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/Config.java b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/Config.java index d6825c7..5064850 100644 --- a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/Config.java +++ b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/Config.java @@ -96,4 +96,25 @@ public final class Config { return ConfigUtil.resolvePath(resolveCABasePath(), Config. GetValue(ConfigValues.TruststoreUrl)); } + /** + * Fetch the oVirtUiPluginsPath configuration value and, if it is not an absolute path, resolve it relative to + * the DataDir configuration value. + * + * @return an absolute path for oVirtUiPluginsPath + */ + public static String resolveOVirtUiPluginsPath() { + return ConfigUtil.resolvePath(Config. GetValue(ConfigValues.DataDir), + Config. GetValue(ConfigValues.UiPluginsPath)); + } + + /** + * Examine the given filename and if it is not an absolute path, resolve it relative to the oVirtUiPluginsPath + * + * @return an absolute path for the UiPluginConfigFile + */ + public static String resolveOVirtUiPluginConfigFile(String filename) { + String path = ConfigUtil.resolvePath(Config. GetValue(ConfigValues.ConfigDir), + Config. GetValue(ConfigValues.UiPluginsPath)); + return ConfigUtil.resolvePath(path, filename); + } } diff --git a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/ConfigValues.java b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/ConfigValues.java index 494ac71..5c685d8 100644 --- a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/ConfigValues.java +++ b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/config/ConfigValues.java @@ -1442,6 +1442,10 @@ public enum ConfigValues { @DefaultValueAttribute("ovirt-engine") SSHKeyAlias(377), + @TypeConverterAttribute(String.class) + @DefaultValueAttribute("ui-plugins") + UiPluginsPath(378), + Invalid(65535); private int intValue; diff --git a/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/PluginSourcePageServlet.java b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/PluginSourcePageServlet.java index 159fad8..e34fba4 100644 --- a/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/PluginSourcePageServlet.java +++ b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/PluginSourcePageServlet.java @@ -9,15 +9,15 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Writer; -import java.util.Arrays; -import java.util.List; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.codehaus.jackson.JsonNode; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +//import org.ovirt.engine.ui.frontend.server.gwt.WebadminDynamicHostingServlet; /** * Renders the HTML source page for the given UI plugin. @@ -25,34 +25,48 @@ import org.apache.commons.logging.LogFactory; public class PluginSourcePageServlet extends HttpServlet { private static final long serialVersionUID = 1L; + private static final long PATH_MAX = 512; private static Log logger = LogFactory.getLog(PluginSourcePageServlet.class); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { // Read plugin name as HTTP request parameter - String pluginName = request.getParameter("plugin"); //$NON-NLS-1$ - if (pluginName == null) { + String pathInfo = request.getPathInfo(); + String path[] = null; + logger.debug("Got plugin request "+pathInfo); //$NON-NLS-1$ + + try { + path = pathInfo.split("/", 3); //$NON-NLS-1$ + } catch(Exception ex) {} + + if (path == null || path.length < 2) { + logger.error("Missing plugin name request parameter"); //$NON-NLS-1$ + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + JsonNode pinfo = WebadminDynamicHostingServlet.getPluginDefinition(path[1]); + if (pinfo == null) { logger.error("Missing plugin name request parameter"); //$NON-NLS-1$ response.sendError(HttpServletResponse.SC_BAD_REQUEST); return; } // Locate plugin code in local file system - // TODO hard-coded plugin location - File pluginCodeLocation = new File("/home/vszocs/Downloads"); //$NON-NLS-1$ - File pluginCodeFile = new File(pluginCodeLocation, pluginName + ".js"); //$NON-NLS-1$ + String localfile = pinfo.path("path").getTextValue() + File.separator + path[2]; //$NON-NLS-1$ + if (!isSane(localfile)) { + response.sendError(HttpServletResponse.SC_NOT_FOUND); + return; + } + + File pluginCodeFile = new File(localfile); if (!pluginCodeFile.isFile() || !pluginCodeFile.canRead()) { logger.error("Cannot read plugin code: " + pluginCodeFile.getAbsolutePath()); //$NON-NLS-1$ response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } - // TODO simulate plugin dependencies - List pluginDependencyList = - Arrays.asList("https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"); //$NON-NLS-1$ - - // Render HTML source page to the output response.setContentType("text/html; charset=UTF-8"); //$NON-NLS-1$ response.setHeader("Cache-Control", "no-cache"); //$NON-NLS-1$ //$NON-NLS-2$ @@ -63,7 +77,7 @@ public class PluginSourcePageServlet extends HttpServlet { in = new BufferedReader(new InputStreamReader(new FileInputStream(pluginCodeFile), "UTF-8")); //$NON-NLS-1$ out = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), "UTF-8")); //$NON-NLS-1$ - renderPluginSourcePage(in, pluginDependencyList, out); + copyChars(in, out); out.flush(); } finally { if (in != null) { @@ -72,28 +86,6 @@ public class PluginSourcePageServlet extends HttpServlet { } } - void renderPluginSourcePage(Reader pluginCodeInput, List pluginDependencyList, Writer output) - throws IOException { - output.write(""); //$NON-NLS-1$ - output.write(""); //$NON-NLS-1$ - - for (String dependency : pluginDependencyList) { - output.write(""); //$NON-NLS-1$ - } - - output.write(""); //$NON-NLS-1$ - output.write(""); //$NON-NLS-1$ - output.write(""); //$NON-NLS-1$ - } - void copyChars(Reader in, Writer out) throws IOException { char[] buffer = new char[4 * 1024]; // Use 4 kB buffer int numRead = 0; @@ -103,4 +95,23 @@ public class PluginSourcePageServlet extends HttpServlet { } } + private static boolean isSane(String path) { + // Check that the path is not too long: + final int length = path.length(); + if (length > PATH_MAX) { + logger.error("The path is " + length + " characters long, which is longer than the maximum allowed " + PATH_MAX + "."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + return false; + } + + // Check that there aren't potentially dangerous directory navigation sequences: + if (path.contains("..") || path.contains("//") || path.contains("./")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + logger.error("The path contains potentially dangerous directory navigation sequences."); //$NON-NLS-1$ + return false; + } + + // All checks passed, the path is sane: + return true; + } } + +// vim: ts=4 sts=4 sw=4 expandtab: diff --git a/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/WebadminDynamicHostingServlet.java b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/WebadminDynamicHostingServlet.java index 683cb68..1d49297 100644 --- a/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/WebadminDynamicHostingServlet.java +++ b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/WebadminDynamicHostingServlet.java @@ -1,10 +1,21 @@ package org.ovirt.engine.ui.frontend.server.gwt; import java.io.PrintWriter; +import java.io.File; +import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; +import java.util.Iterator; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import javax.servlet.http.HttpServletRequest; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.node.ObjectNode; +import org.codehaus.jackson.map.ObjectMapper; import org.ovirt.engine.core.common.config.Config; import org.ovirt.engine.core.common.queries.ConfigurationValues; @@ -19,6 +30,8 @@ import org.ovirt.engine.core.common.queries.VdcQueryType; public class WebadminDynamicHostingServlet extends GwtDynamicHostPageServlet { private static final long serialVersionUID = 1L; + private static Map pluginDefinitions; + private static Log logger = LogFactory.getLog(WebadminDynamicHostingServlet.class); @Override protected String getSelectorScriptName() { @@ -36,10 +49,14 @@ public class WebadminDynamicHostingServlet extends GwtDynamicHostPageServlet { writeJsObject(writer, "applicationMode", appModeData); //$NON-NLS-1$ } - StringBuilder pluginDefinitions = new StringBuilder(" var pluginDefinitions = [ "); //$NON-NLS-1$ - pluginDefinitions.append("{ name: \"myPlugin\", url: \"/webadmin/webadmin/PluginSourcePage?plugin=myPlugin\", config: { \"foo\": 1 } }"); //$NON-NLS-1$ - pluginDefinitions.append(" ]; "); //$NON-NLS-1$ - writer.append(pluginDefinitions.toString()); + // FIXME: do we load this everytime, or just once? + if (true) { /**pluginDefinitions == null) {**/ + logger.debug("getPluginDefinitions: Loading plugin definitions"); //$NON-NLS-1$ + pluginDefinitions = getPluginDefinitions(); + } + + writeJsonObject(writer, "pluginDefinitions", pluginDefinitions.values()); //$NON-NLS-1$ + } private Integer getApplicationMode(HttpServletRequest request) { @@ -48,4 +65,100 @@ public class WebadminDynamicHostingServlet extends GwtDynamicHostPageServlet { Config.DefaultConfigurationVersion), request); } + private boolean checkPluginDefinition(String filename, JsonNode def) { + String url, cfg; + + if (!def.path("name").isTextual()) { //$NON-NLS-1$ + logger.error(filename+": The 'name' field must be present and must be a string."); //$NON-NLS-1$ + return false; + } + if (!def.path("url").isTextual()) { //$NON-NLS-1$ + logger.error(filename+": The 'url' field must be present and must be a string."); //$NON-NLS-1$ + return false; + } + url = def.path("url").getValueAsText(); //$NON-NLS-1$ + if (url.startsWith("/webadmin")) { //$NON-NLS-1$ + if (!def.path("path").isTextual()) { //$NON-NLS-1$ + logger.error(filename+": The 'path' field must be present when the 'url' is local and must be a string."); //$NON-NLS-1$ + return false; + } + } + return true; + } + + private Map getPluginDefinitions() { + Map defs = new HashMap(); + File directory; + File[] files; + + logger.debug("getPluginDefinitions: Checking path "+Config.resolveOVirtUiPluginsPath()); //$NON-NLS-1$ + directory = new File(Config.resolveOVirtUiPluginsPath()); + files = directory.listFiles(); + + JsonNode root; + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true); + // FIXME: If JACKSON-618 is resolved (ALLOW_TRAILING_COMMA), use it. + + for(int i=0; i> it=config.getFields(); it.hasNext();) { + Entry e = it.next(); + on.put(e.getKey(), e.getValue()); + } + } catch(Exception ex) { + logger.error(filename+": Error reading the plugin configuration from "+cfg+". Exception was: "+ex); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + defs.put(name, root); + } + } + return defs; + } + + /** + * Return a single plugin definition given a plugin name + */ + public static JsonNode getPluginDefinition(String name) { + JsonNode ret = null; + if (pluginDefinitions != null) { + ret = pluginDefinitions.get(name); + } + return ret; + } + + /** + * Writes a string representing JavaScript object literal containing given attributes. + */ + protected void writeJsonObject(PrintWriter writer, String objectName, Object obj) { + StringBuilder sb = new StringBuilder(); + ObjectMapper mapper = new ObjectMapper(); + sb.append(" var ").append(objectName).append(" = "); //$NON-NLS-1$ //$NON-NLS-2$ + try { + sb.append(mapper.writeValueAsString(obj)); + } catch(Exception ex) { } + sb.append(";"); //$NON-NLS-1$ + writer.append(sb.toString()); + } + + } +// vim: ts=4 sts=4 sw=4 expandtab: diff --git a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/plugin/PluginEventHandler.java b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/plugin/PluginEventHandler.java index a25fc75..687e801 100644 --- a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/plugin/PluginEventHandler.java +++ b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/plugin/PluginEventHandler.java @@ -15,5 +15,4 @@ public class PluginEventHandler { // TODO call EventBus.addHandler for each extension point (event), // with the handler implementation using PluginManager to call plugins } - } diff --git a/frontend/webadmin/modules/webadmin/src/main/webapp/WEB-INF/web.xml b/frontend/webadmin/modules/webadmin/src/main/webapp/WEB-INF/web.xml index dbf93a2..0ed4ce8 100644 --- a/frontend/webadmin/modules/webadmin/src/main/webapp/WEB-INF/web.xml +++ b/frontend/webadmin/modules/webadmin/src/main/webapp/WEB-INF/web.xml @@ -32,6 +32,10 @@ PluginSourcePage /webadmin/PluginSourcePage + + PluginSourcePage + /webadmin/plugin/* +