SECURITY WARNING: Please take note of the warning at the end of the tutorial !
UPDATE 2011-07-08: A good article on the security concern I recommend http://www.jroller.com/melix/entry/customizing_groovy_compilation_process
We use the Groovy Script Engine to run small Groovy scripts to prepare incoming interface messages for further processing. Here I share with you how to embed the engine into a ZK page. Very basic, though it shows you the concept and it is wasy to expand to your needs.
Pre-Requirements:
- Netbeans with the ZK plugin (link)
- A Groovy installation or at least the lib folder from the package (link)
Tutorial:
- Create a new Web Project ZKGroovyConsolewith the ZK Framework
- Add the embeddable Groovy Library (from the downloaded Groovy package)
- Update the index.zul file
<?xml version="1.0" encoding="UTF-8"?> <zk xmlns="http://www.zkoss.org/2005/zul"> <window id="list" apply="controller.indexController" title="ZKGroovy 0.1"> <grid> <columns width="100%"> <column label="" width="10%"/> <column label=""/> <column label="" /> </columns> <rows> <row > <label value="Script" /> <vbox> <textbox value="" id="txtScript" cols="80" rows="10"/> <hbox> <label value="Parameter p1" /> <textbox id="txtp1" value="1" cols="10"/> </hbox> <hbox> <label value="Parameter p2" /> <textbox id="txtp2" value="2" cols="10"/> </hbox> <radiogroup Id="optTrace" onCheck=""> <radio label="Full Stacktrace" checked="true"/> <radio label="Simplified"/> </radiogroup> </vbox> <vbox> <button label="Execute" id="btnExec" width="100px" /> <button label="Load Sample Script 1" id="btnSample" width="100px" /> <button label="Load Sample Script 2" id="btnSample2" width="100px" /> <button label="Load Sample Script 3 (exception)" id="btnSample3" width="100px" /> <button label="Clear Script" id="btnClearScript" width="100px" /> </vbox> </row> <row align="left"> <label value="Result" /> <textbox value="" id="txtConsole" cols="140" rows="10"/> <button label="Clear" id="btnClearConsole" width="100px" /> </row> <row> <label value="Errors"/> <textbox value="" id="txtError" cols="140" rows="10"/> <button label="Clear" id="btnClearError" width="100px" onClick=""/> </row> </rows> </grid> </window> </zk>
- Add a controller
package controller; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.util.GenericForwardComposer; import groovy.lang.Binding; import groovy.lang.GroovyShell; import java.io.PrintWriter; import java.io.StringWriter; import org.zkoss.zul.Radiogroup; import org.zkoss.zul.Textbox; public class indexController extends GenericForwardComposer { private Textbox txtConsole; private Textbox txtError; private Textbox txtScript; private Textbox txtp1; private Textbox txtp2; private Radiogroup optTrace; @Override public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); } public void onClick$btnExec(Event evt) throws InterruptedException { execScript(); } public void onClick$btnSample(Event evt) throws InterruptedException { txtScript.setValue("println 'Sample Script';\rresult = par1 + par2;\rsomestring='dummyString'; "); } public void onClick$btnSample2(Event evt) throws InterruptedException { txtScript.setValue("println 'Sample Script';\rresult = par1 + par2;\rsomestring=par1+'.'+new Date().format('MMMM'); "); } public void onClick$btnSample3(Event evt) throws InterruptedException { txtScript.setValue("println 'Sample Script';\rresult = par1 + par2;\rsomestring='dummyString';\rx=5/0; "); } public void onClick$btnClearConsole(Event evt) throws InterruptedException { txtConsole.setValue(""); } public void onClick$btnClearScript(Event evt) throws InterruptedException { txtScript.setValue(""); } public void onClick$btnClearError(Event evt) throws InterruptedException { txtError.setValue(""); } void execScript() { try { Binding binding = new Binding(); Integer val1 = Integer.valueOf(txtp1.getValue()); Integer val2 = Integer.valueOf(txtp2.getValue()); binding.setVariable("par1", val1); binding.setVariable("par2", val2); GroovyShell shell = new GroovyShell(binding); Object value = shell.evaluate(txtScript.getValue()); String stringResult = (String) binding.getVariable("somestring"); Integer result = (Integer) binding.getVariable("result"); txtConsole.setValue("Integer Result: \t" + result.toString() + "\rString Result: \t" + stringResult); } catch (Exception ex) { if (optTrace.getSelectedIndex()==0) txtError.setValue(stackTraceToString(ex)); else txtError.setValue(shortStackTraceToString(ex)); } } private String stackTraceToString(Exception ex) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); ex.printStackTrace(pw); return sw.toString(); } private String shortStackTraceToString(Exception ex) { String returnTrace = ex.toString(); String el; for (StackTraceElement element : ex.getStackTrace()) { el = element.toString(); if (el.trim().startsWith("Script")) { int index = el.indexOf(':'); returnTrace = returnTrace + " (line" + el.substring(index); } } return returnTrace; } }
The actual Groovy magic is hidden in these few lines.
Binding binding = new Binding(); Integer val1 = Integer.valueOf(txtp1.getValue()); Integer val2 = Integer.valueOf(txtp2.getValue()); binding.setVariable("par1", val1); binding.setVariable("par2", val2); GroovyShell shell = new GroovyShell(binding); Object value = shell.evaluate(txtScript.getValue()); String stringResult = (String) binding.getVariable("somestring"); Integer result = (Integer) binding.getVariable("result");
You can binding for inbound and outbound variables to “talk” with your script.
Note:
- The exception that happen in the script is caught by our container.
- println statements are directed to our server logfile.
!!! WARNING !!!
Please be very careful to deploy this as part of a real life production web application. You open big back doors for hacker, and you dont even need to be experienced to create trouble with this. Look at this sample:
- Executing System.exit(0) makes the application server shutdown (Glassfish)
Remarks:
- I would make use of this in a restrictive fashion only, maybe allow certain commands or filter out dangerous stuff (no recipe for that yet)
- I am not sure if you can consider the above problem as a Glassfish security problem, but rather a insecure implementation