Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions conf/defaults.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ ui.http.creds.plugin: org.apache.storm.security.auth.DefaultHttpCredentialsPlugi
ui.pagination: 20
ui.disable.http.binding: true
ui.disable.spout.lag.monitoring: true
ui.enable.jsonp: false

logviewer.port: 8000
logviewer.childopts: "-Xmx128m"
Expand Down
1 change: 1 addition & 0 deletions docs/STORM-UI-REST-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ metrics data and configuration information as well as management operations such

The REST API returns JSON responses and supports JSONP.
Clients can pass a callback query parameter to wrap JSON in the callback function.
JSONP is disabled by default; the callback parameter is ignored unless `ui.enable.jsonp` is set to true.


# Using the UI REST API
Expand Down
8 changes: 8 additions & 0 deletions storm-server/src/main/java/org/apache/storm/DaemonConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,14 @@ public class DaemonConfig implements Validated {
@IsBoolean
public static final String UI_DISABLE_SPOUT_LAG_MONITORING = "ui.disable.spout.lag.monitoring";

/**
* This controls whether the Storm UI and Logviewer REST APIs wrap their response in the
* JSONP callback named by the "callback" query parameter. It is disabled by default, since
* a JSONP response can be read by any page that is able to include it with a script tag.
*/
@IsBoolean
public static final String UI_ENABLE_JSONP = "ui.enable.jsonp";

/**
* This controls wheather Storm Logviewer should bind to http port even if logviewer.port is > 0.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package org.apache.storm.daemon.ui;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
Expand Down Expand Up @@ -89,6 +90,7 @@
import org.apache.storm.scheduler.resource.normalization.NormalizedResourceRequest;
import org.apache.storm.stats.StatsUtil;
import org.apache.storm.thrift.TException;
import org.apache.storm.utils.ConfigUtils;
import org.apache.storm.utils.IVersionInfo;
import org.apache.storm.utils.ObjectReader;
import org.apache.storm.utils.Time;
Expand Down Expand Up @@ -202,7 +204,7 @@
for (int i = 0; i < args.length; i++) {
argsEncoded[i] = Utils.urlEncodeUtf8(String.valueOf(args[i]));
}
return String.format(fmt, argsEncoded);

Check warning on line 207 in storm-webapp/src/main/java/org/apache/storm/daemon/ui/UIHelpers.java

View workflow job for this annotation

GitHub Actions / test (25, Server, false)

non-varargs call of varargs method with inexact argument type for last parameter;
}

/**
Expand Down Expand Up @@ -314,8 +316,8 @@
* @return corsFilterHandle
*/
public static FilterHolder corsFilterHandle() {
FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter());

Check warning on line 319 in storm-webapp/src/main/java/org/apache/storm/daemon/ui/UIHelpers.java

View workflow job for this annotation

GitHub Actions / test (25, Server, false)

org.eclipse.jetty.ee10.servlets.CrossOriginFilter in org.eclipse.jetty.ee10.servlets has been deprecated and marked for removal
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*");

Check warning on line 320 in storm-webapp/src/main/java/org/apache/storm/daemon/ui/UIHelpers.java

View workflow job for this annotation

GitHub Actions / test (25, Server, false)

org.eclipse.jetty.ee10.servlets.CrossOriginFilter in org.eclipse.jetty.ee10.servlets has been deprecated and marked for removal
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET, POST, PUT");
filterHolder.setInitParameter(
CrossOriginFilter.ALLOWED_HEADERS_PARAM,
Expand Down Expand Up @@ -447,10 +449,27 @@
private static final Pattern JSONP_CALLBACK_PATTERN =
Pattern.compile("^[A-Za-z_$][A-Za-z0-9_$]*(?:\\.[A-Za-z_$][A-Za-z0-9_$]*)*$");

/**
* Whether the "callback" query parameter is honored, see {@link DaemonConfig#UI_ENABLE_JSONP}.
* It is read once, like the rest of the daemon configuration, so a change needs a restart.
*/
private static boolean jsonpEnabled =
ObjectReader.getBoolean(ConfigUtils.readStormConfig().get(DaemonConfig.UI_ENABLE_JSONP), false);

@VisibleForTesting
static void setJsonpEnabled(boolean enabled) {
jsonpEnabled = enabled;
}

private static String sanitizeJsonpCallback(String callback) {
if (callback == null) {
return null;
}
if (!jsonpEnabled) {
LOG.warn("Ignoring JSONP callback parameter, set {} to true to enable JSONP responses",
DaemonConfig.UI_ENABLE_JSONP);
return null;
}
if (callback.length() > 128 || !JSONP_CALLBACK_PATTERN.matcher(callback).matches()) {
LOG.warn("Ignoring invalid JSONP callback parameter");
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ void setup() {
void cleanup() {
// Stop simulating time
mockTime.close();
// Restore the default of ui.enable.jsonp
UIHelpers.setJsonpEnabled(false);
}

/**
Expand Down Expand Up @@ -603,6 +605,7 @@ public void testGetJsonResponseBodyNoCallbackReturnsJson() {

@Test
public void testGetJsonResponseBodyValidCallbackIsWrapped() {
UIHelpers.setJsonpEnabled(true);
Map<String, Object> data = new HashMap<>();
data.put("a", 1);
String body = UIHelpers.getJsonResponseBody(data, "myCb", true);
Expand All @@ -611,12 +614,14 @@ public void testGetJsonResponseBodyValidCallbackIsWrapped() {

@Test
public void testGetJsonResponseBodyValidDottedCallbackIsWrapped() {
UIHelpers.setJsonpEnabled(true);
String body = UIHelpers.getJsonResponseBody("{\"x\":1}", "foo.bar.$baz_0", false);
assertEquals("foo.bar.$baz_0({\"x\":1});", body);
}

@Test
public void testGetJsonResponseBodyInvalidCallbackFallsBackToJson() {
UIHelpers.setJsonpEnabled(true);
Map<String, Object> data = new HashMap<>();
data.put("a", 1);
String body = UIHelpers.getJsonResponseBody(data, "alert(document.cookie)//", true);
Expand All @@ -625,12 +630,14 @@ public void testGetJsonResponseBodyInvalidCallbackFallsBackToJson() {

@Test
public void testGetJsonResponseBodyEmptyCallbackFallsBackToJson() {
UIHelpers.setJsonpEnabled(true);
String body = UIHelpers.getJsonResponseBody("{\"x\":1}", "", false);
assertEquals("{\"x\":1}", body);
}

@Test
public void testGetJsonResponseBodyTooLongCallbackFallsBackToJson() {
UIHelpers.setJsonpEnabled(true);
StringBuilder sb = new StringBuilder("cb");
for (int i = 0; i < 200; i++) {
sb.append('x');
Expand All @@ -641,10 +648,20 @@ public void testGetJsonResponseBodyTooLongCallbackFallsBackToJson() {

@Test
public void testGetJsonResponseBodyRejectsCallbacksStartingWithDigit() {
UIHelpers.setJsonpEnabled(true);
String body = UIHelpers.getJsonResponseBody("{\"x\":1}", "1cb", false);
assertEquals("{\"x\":1}", body);
}

@Test
public void testGetJsonResponseBodyCallbackIgnoredWhenJsonpDisabled() {
UIHelpers.setJsonpEnabled(false);
Map<String, Object> data = new HashMap<>();
data.put("a", 1);
String body = UIHelpers.getJsonResponseBody(data, "myCb", true);
assertEquals("{\"a\":1}", body);
}

@Test
public void testGetJsonResponseHeadersNoCallbackUsesJsonContentType() {
Map headers = UIHelpers.getJsonResponseHeaders(null, null);
Expand All @@ -654,15 +671,25 @@ public void testGetJsonResponseHeadersNoCallbackUsesJsonContentType() {

@Test
public void testGetJsonResponseHeadersValidCallbackUsesJavaScriptContentType() {
UIHelpers.setJsonpEnabled(true);
Map headers = UIHelpers.getJsonResponseHeaders("myCb", null);
assertEquals("application/javascript;charset=utf-8", headers.get("Content-Type"));
assertEquals("nosniff", headers.get("X-Content-Type-Options"));
}

@Test
public void testGetJsonResponseHeadersInvalidCallbackFallsBackToJsonContentType() {
UIHelpers.setJsonpEnabled(true);
Map headers = UIHelpers.getJsonResponseHeaders("alert(1)//", null);
assertEquals("application/json;charset=utf-8", headers.get("Content-Type"));
assertEquals("nosniff", headers.get("X-Content-Type-Options"));
}

@Test
public void testGetJsonResponseHeadersCallbackIgnoredWhenJsonpDisabled() {
UIHelpers.setJsonpEnabled(false);
Map headers = UIHelpers.getJsonResponseHeaders("myCb", null);
assertEquals("application/json;charset=utf-8", headers.get("Content-Type"));
assertEquals("nosniff", headers.get("X-Content-Type-Options"));
}
}
Loading