azkaban-aplcache
Changes
build.gradle 5(+5 -0)
Details
diff --git a/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/index.vm b/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/index.vm
index d175a6b..ee261db 100644
--- a/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/index.vm
+++ b/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/index.vm
@@ -93,8 +93,11 @@
#foreach ($project in $projects)
<li>
<div class="project-info">
- <h4><a href="${context}/manager?project=${project.name}">$project.name</a></h4>
- <p class="project-description">$project.description</p>
+ ## The UI logic elsewhere enforces that $project.name must start with a letter, followed by any number
+ ## of letters, digits, '-' or '_'. Escape it just to play it safe.
+ ## todo: make escaping the default.
+ <h4><a href="${context}/manager?project=${project.name}">esc.html($project.name)</a></h4>
+ <p class="project-description">$esc.html($project.description)</p>
<p class="project-last-modified">Last modified on <strong>$utils.formatDate($project.lastModifiedTimestamp)</strong> by <strong>$project.lastModifiedUser</strong>.</p>
</div>
<div class="project-expander" id="${project.name}">
diff --git a/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/projectsidebar.vm b/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/projectsidebar.vm
index c730d8d..4ba7592 100644
--- a/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/projectsidebar.vm
+++ b/azkaban-webserver/src/main/resources/azkaban/webapp/servlet/velocity/projectsidebar.vm
@@ -16,7 +16,7 @@
<div class="well" id="project-sidebar">
<h3>$project.name</h3>
- <p class="editable" id="project-description">$project.description</p>
+ <p class="editable" id="project-description">$esc.html($project.description)</p>
<div id="project-description-form" class="editable-form">
<div class="input-group">
<input type="text" class="form-control input-sm" id="project-description-edit" placeholder="Project description">
diff --git a/azkaban-webserver/src/test/expected/project-side-bar.html b/azkaban-webserver/src/test/expected/project-side-bar.html
new file mode 100644
index 0000000..a41e5da
--- /dev/null
+++ b/azkaban-webserver/src/test/expected/project-side-bar.html
@@ -0,0 +1,25 @@
+<html>
+
+
+ <div class="well" id="project-sidebar">
+ <h3>test_project</h3>
+ <p class="editable" id="project-description"><script>window.echo("hacked")</script></p>
+ <div id="project-description-form" class="editable-form">
+ <div class="input-group">
+ <input type="text" class="form-control input-sm" id="project-description-edit" placeholder="Project description">
+ <span class="input-group-btn">
+ <button class="btn btn-primary btn-sm" type="button" id="project-description-btn">Save</button>
+ </span>
+ </div>
+ </div>
+ <hr>
+ <p><strong>Created on</strong> 1999-06-07 08:09:10</p>
+ <p><strong>Last modified by</strong> 2000-01-02 03:04:05</p>
+ <p><strong>Modified by</strong> last_modified_user_name</p>
+
+ <hr>
+
+ <p><strong>Project admins:</strong> admin_name</p>
+ <p><strong>Your Permissions:</strong> admin_permission</p>
+ </div>
+</html>
diff --git a/azkaban-webserver/src/test/java/azkaban/fixture/FileAssertion.java b/azkaban-webserver/src/test/java/azkaban/fixture/FileAssertion.java
new file mode 100644
index 0000000..f0e1164
--- /dev/null
+++ b/azkaban-webserver/src/test/java/azkaban/fixture/FileAssertion.java
@@ -0,0 +1,39 @@
+package azkaban.fixture;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import org.junit.Assert;
+
+
+/**
+ * The type File assertion.
+ */
+public class FileAssertion {
+ /**
+ * Assert the given string equals the given file's content.
+ *
+ * @param expectedFilePath the expected file path
+ * @param actual the actual string
+ * @throws IOException the io exception
+ */
+ public static void assertStringEqualFileContent(String expectedFilePath, String actual)
+ throws IOException {
+ String expected = new String(Files.readAllBytes(Paths.get(expectedFilePath)), StandardCharsets.UTF_8);
+ Assert.assertEquals(expected, actual);
+ }
+
+ /**
+ * Return the html based content surrounded with the HTML tag.
+ *
+ * This is useful to compare a fragment of HTML content with a proper expected HTML file
+ * so that the expected file can be viewed more easily with a browser.
+ *
+ * @param content the content
+ * @return string
+ */
+ public static String surroundWithHtmlTag(String content) {
+ return "<html>\n" + content + "</html>\n";
+ }
+}
diff --git a/azkaban-webserver/src/test/java/azkaban/fixture/MockProject.java b/azkaban-webserver/src/test/java/azkaban/fixture/MockProject.java
new file mode 100644
index 0000000..cbfb24c
--- /dev/null
+++ b/azkaban-webserver/src/test/java/azkaban/fixture/MockProject.java
@@ -0,0 +1,42 @@
+package azkaban.fixture;
+
+import azkaban.project.Project;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+
+/**
+ * Provide a mock project.
+ */
+public class MockProject {
+ /**
+ * Get mock project project.
+ *
+ * @return a mock project for testing
+ */
+ public static Project getMockProject(){
+ Project project = new Project(1, "test_project");
+ project.setDescription("My project description");
+ project.setLastModifiedUser("last_modified_user_name");
+
+ DateFormat dateFormat = new SimpleDateFormat("yyyy:MM:dd:HH:mm:ss");
+ try {
+ Date modifiedDate = dateFormat.parse("2000:01:02:3:4:5");
+
+ long modifiedTime = modifiedDate.getTime();
+ project.setLastModifiedTimestamp(modifiedTime);
+
+ Date createDate = dateFormat.parse("1999:06:07:8:9:10");
+
+ long createTime = createDate.getTime();
+
+ project.setCreateTimestamp(createTime);
+ }
+ catch (ParseException e) {
+ e.printStackTrace();
+ }
+ return project;
+ }
+}
diff --git a/azkaban-webserver/src/test/java/azkaban/fixture/VelocityContextTestUtil.java b/azkaban-webserver/src/test/java/azkaban/fixture/VelocityContextTestUtil.java
new file mode 100644
index 0000000..9a77d41
--- /dev/null
+++ b/azkaban-webserver/src/test/java/azkaban/fixture/VelocityContextTestUtil.java
@@ -0,0 +1,26 @@
+package azkaban.fixture;
+
+import azkaban.utils.WebUtils;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.tools.generic.EscapeTool;
+
+
+/**
+ * The type Velocity context test util.
+ */
+public class VelocityContextTestUtil {
+ /**
+ * Gets an instance of the velocity context
+ *
+ * Add the utility instances that are commonly used in many templates.
+ *
+ * @return the instance
+ */
+ public static VelocityContext getInstance() {
+ VelocityContext context = new VelocityContext();
+ context.put("esc", new EscapeTool());
+ WebUtils utils = new WebUtils();
+ context.put("utils", utils);
+ return context;
+ }
+}
\ No newline at end of file
diff --git a/azkaban-webserver/src/test/java/azkaban/fixture/VelocityTemplateTestUtil.java b/azkaban-webserver/src/test/java/azkaban/fixture/VelocityTemplateTestUtil.java
new file mode 100644
index 0000000..fa01234
--- /dev/null
+++ b/azkaban-webserver/src/test/java/azkaban/fixture/VelocityTemplateTestUtil.java
@@ -0,0 +1,29 @@
+package azkaban.fixture;
+
+import java.io.StringWriter;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+
+
+/**
+ * Test utility to render a template.
+ */
+public class VelocityTemplateTestUtil {
+
+ private static final String TEMPLATE_BASE_DIR = "src/main/resources/azkaban/webapp/servlet/velocity/";
+
+ /**
+ * Render a template and return the result
+ *
+ * @param templateName the template name only without the .vm extension
+ * @param context the context
+ * @return string
+ */
+ public static String renderTemplate(String templateName, VelocityContext context) {
+ StringWriter stringWriter = new StringWriter();
+ VelocityEngine engine = new VelocityEngine();
+
+ engine.mergeTemplate(TEMPLATE_BASE_DIR + templateName + ".vm", "UTF-8", context, stringWriter);
+ return stringWriter.getBuffer().toString();
+ }
+}
diff --git a/azkaban-webserver/src/test/java/azkaban/fixture/WebFileAssertion.java b/azkaban-webserver/src/test/java/azkaban/fixture/WebFileAssertion.java
new file mode 100644
index 0000000..52b5929
--- /dev/null
+++ b/azkaban-webserver/src/test/java/azkaban/fixture/WebFileAssertion.java
@@ -0,0 +1,24 @@
+package azkaban.fixture;
+
+import java.io.IOException;
+
+
+/**
+ * Utility to assert based on expected files in the webserver project.
+ */
+public class WebFileAssertion {
+ private static final String EXPECTED_FILE_DIR = "src/test/expected/";
+
+ /**
+ * Assert the string equals file content in this project's expected file directory.
+ *
+ * @param expectedFileName the expected file name
+ * @param actual the actual
+ * @throws IOException the io exception
+ */
+ public static void assertStringEqualFileContent(String expectedFileName, String actual)
+ throws IOException {
+ String expectedFilePath = EXPECTED_FILE_DIR + expectedFileName;
+ FileAssertion.assertStringEqualFileContent(expectedFilePath, actual);
+ }
+}
diff --git a/azkaban-webserver/src/test/java/azkaban/webapp/servlet/ProjectSideBarViewTest.java b/azkaban-webserver/src/test/java/azkaban/webapp/servlet/ProjectSideBarViewTest.java
new file mode 100644
index 0000000..1bbbf5e
--- /dev/null
+++ b/azkaban-webserver/src/test/java/azkaban/webapp/servlet/ProjectSideBarViewTest.java
@@ -0,0 +1,43 @@
+package azkaban.webapp.servlet;
+
+import azkaban.fixture.FileAssertion;
+import azkaban.fixture.MockProject;
+import azkaban.fixture.VelocityContextTestUtil;
+import azkaban.fixture.VelocityTemplateTestUtil;
+import azkaban.fixture.WebFileAssertion;
+import azkaban.project.Project;
+import org.apache.velocity.VelocityContext;
+import org.junit.Test;
+
+
+/**
+ * Test project side bar view
+ */
+public class ProjectSideBarViewTest {
+
+ /**
+ * Test project side bar view.
+ *
+ * The project description should be HTML encoded to avoid cross site scripting issues.
+ *
+ * @throws Exception the exception
+ */
+ @Test
+ public void testProjectSideBarView()
+ throws Exception {
+ VelocityContext context = VelocityContextTestUtil.getInstance();
+
+ Project project = MockProject.getMockProject();
+
+ // Intentionally tries to inject a Javascript.
+ project.setDescription("<script>window.echo(\"hacked\")</script>");
+
+ context.put("project", project);
+ context.put("admins", "admin_name");
+ context.put("userpermission", "admin_permission");
+
+ String result = VelocityTemplateTestUtil.renderTemplate("projectsidebar", context);
+ String actual = FileAssertion.surroundWithHtmlTag(result);
+ WebFileAssertion.assertStringEqualFileContent("project-side-bar.html", actual);
+ }
+}
\ No newline at end of file
diff --git a/azkaban-webserver/src/test/resources/log4j.properties b/azkaban-webserver/src/test/resources/log4j.properties
new file mode 100644
index 0000000..182c9a1
--- /dev/null
+++ b/azkaban-webserver/src/test/resources/log4j.properties
@@ -0,0 +1,7 @@
+log4j.rootLogger=INFO, Console
+
+log4j.appender.Console=org.apache.log4j.ConsoleAppender
+log4j.appender.Console.layout=org.apache.log4j.PatternLayout
+log4j.appender.Console.layout.ConversionPattern=%d{yyyy/MM/dd HH:mm:ss.SSS Z} %p [%c{1}] %m%n
+
+log4j.category.velocity=INFO
\ No newline at end of file
build.gradle 5(+5 -0)
diff --git a/build.gradle b/build.gradle
index 3495e10..f436fb1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -236,6 +236,11 @@ project(':azkaban-webserver') {
generateRestli('com.linkedin.pegasus:generator:' + pegasusVersion)
generateRestli('com.linkedin.pegasus:restli-tools:' + pegasusVersion)
+
+ // Needed by Velocity at runtime
+ testRuntime('commons-collections:commons-collections:3.2.2')
+ testCompile('junit:junit:4.11')
+ testCompile('org.hamcrest:hamcrest-all:1.3')
}
sourceSets {
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index bbc82a1..54e90fc 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Sat Sep 26 15:48:43 PDT 2015
+#Sat May 28 17:40:49 PDT 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.7-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.7-all.zip