Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support CSP nonce parameter for RUM header and footer #591

Merged
merged 11 commits into from
Feb 8, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,24 @@ public String getBrowserTimingHeader() {
return "";
}

@Override
public String getBrowserTimingHeader(String nonce) {

return "";
}

@Override
public String getBrowserTimingFooter() {

return "";
}

@Override
public String getBrowserTimingFooter(String nonce) {

return "";
}

@Override
public void setUserName(String name) {

Expand All @@ -126,11 +138,13 @@ public void setAppServerPort(int port) {

}

@Override public void setServerInfo(String dispatcherName, String version) {
@Override
public void setServerInfo(String dispatcherName, String version) {

}

@Override public void setInstanceName(String instanceName) {
@Override
public void setInstanceName(String instanceName) {

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,27 @@ public interface PublicApi {
*/
String getBrowserTimingHeader();

/**
* Get the RUM JavaScript header for the current web transaction.
* @param nonce a random per-request nonce for sites using Content Security Policy (CSP)
* @return RUM JavaScript header for the current web transaction.
*/
String getBrowserTimingHeader(String nonce);

/**
* Get the RUM JavaScript footer for the current web transaction.
*
* @return RUM JavaScript footer for the current web transaction.
*/
String getBrowserTimingFooter();

/**
* Get the RUM JavaScript footer for the current web transaction.
* @param nonce a random per-request nonce for sites using Content Security Policy (CSP)
* @return RUM JavaScript footer for the current web transaction.
*/
String getBrowserTimingFooter(String nonce);

/**
* Set the user name to associate with the RUM JavaScript footer for the current web transaction.
*
Expand Down
18 changes: 18 additions & 0 deletions agent-bridge/src/main/java/com/newrelic/api/agent/NewRelic.java
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,15 @@ public static String getBrowserTimingHeader() {
return AgentBridge.publicApi.getBrowserTimingHeader();
}

/**
* Get the RUM JavaScript header for the current web transaction.
* @param nonce a random per-request nonce for sites using Content Security Policy (CSP)
* @return RUM JavaScript header for the current web transaction.
*/
public static String getBrowserTimingHeader(String nonce) {
return AgentBridge.publicApi.getBrowserTimingHeader(nonce);
}

/**
* Get the RUM JavaScript footer for the current web transaction.
*
Expand All @@ -275,6 +284,15 @@ public static String getBrowserTimingFooter() {
return AgentBridge.publicApi.getBrowserTimingFooter();
}

/**
* Get the RUM JavaScript footer for the current web transaction.
* @param nonce a random per-request nonce for sites using Content Security Policy (CSP)
* @return RUM JavaScript footer for the current web transaction.
*/
public static String getBrowserTimingFooter(String nonce) {
return AgentBridge.publicApi.getBrowserTimingFooter(nonce);
}

/**
* Set the user name to associate with the RUM JavaScript footer for the current web transaction.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@
import java.util.logging.Level;

/* (non-javadoc)
* Note: the "beacon" was a predecessor technology for correlated transaction traces with the browser.
* Note: the "beacon" was a predecessor technology for correlated transaction traces with the browser.
* Some appearances of the term could be changed to "browser" now.
*/

/**
* A class that formats the JavaScript header and footer for Real User Monitoring.
*
* <p>
* This class is thread-safe
*/
public class BrowserConfig extends BaseConfig {
Expand All @@ -39,14 +39,16 @@ public class BrowserConfig extends BaseConfig {
private static final String HEADER_END = "</script>";

private final BrowserFooter footer;
private final String jsAgentLoader;
private final String header;

private BrowserConfig(String appName, Map<String, Object> props) throws Exception {
super(props);
// when rum is turned off on the server, none of the required properties come down
// meaning this will throw an exception
footer = initBrowserFooter(appName);
header = initBrowserHeader();
jsAgentLoader = getRequiredProperty(JS_AGENT_LOADER);
header = HEADER_BEGIN + jsAgentLoader + HEADER_END;
logVersion(appName);
}

Expand All @@ -58,11 +60,6 @@ private void logVersion(String appName) {
}
}

private String initBrowserHeader() throws Exception {
return HEADER_BEGIN + getRequiredProperty(JS_AGENT_LOADER) + HEADER_END;

}

private BrowserFooter initBrowserFooter(String appName) throws Exception {
String beacon = getRequiredProperty(BEACON);
String browserKey = getRequiredProperty(BROWSER_KEY);
Expand All @@ -85,10 +82,23 @@ public String getBrowserTimingHeader() {
return header;
}

public String getBrowserTimingHeader(String nonce) {
// nonce should change per request so we cannot pre-build this string
return "\n<script type=\"text/javascript\" nonce=\""
+ nonce
+ "\">"
+ jsAgentLoader
+ "</script>";
}

public String getBrowserTimingFooter(BrowserTransactionState state) {
return footer.getFooter(state);
}

public String getBrowserTimingFooter(BrowserTransactionState state, String nonce) {
return footer.getFooter(state, nonce);
}

public static BrowserConfig createBrowserConfig(String appName, Map<String, Object> settings) throws Exception {
if (settings == null) {
settings = Collections.emptyMap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ public class BrowserFooter {
private static final String AGENT_PAYLOAD_SCRIPT_KEY = "agent";

// Used by functional tests
public static final String FOOTER_START_SCRIPT = "\n<script type=\"text/javascript\">window.NREUM||(NREUM={});NREUM.info=";
static final String FOOTER_JS_START = "window.NREUM||(NREUM={});NREUM.info=";
public static final String FOOTER_START_SCRIPT = "\n<script type=\"text/javascript\">" + FOOTER_JS_START;
public static final String FOOTER_END = "</script>";

private final String beacon;
Expand Down Expand Up @@ -83,6 +84,20 @@ public String getFooter(BrowserTransactionState state) {
}
}

String getFooter(BrowserTransactionState state, String nonce) {
String jsonString = jsonToString(createMapWithData(state));
if (jsonString != null) {
return "\n<script type=\"text/javascript\" nonce=\""
+ nonce
+ "\">"
+ FOOTER_JS_START
+ jsonString
+ FOOTER_END;
} else {
return "";
}
}

private String jsonToString(Map<String, ?> map) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); Writer out = new OutputStreamWriter(baos, StandardCharsets.UTF_8)) {
JSONObject.writeJSONString(map, out);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@ public interface BrowserTransactionState {

String getBrowserTimingHeader();

String getBrowserTimingHeader(String nonce);

String getBrowserTimingHeaderForJsp();

String getBrowserTimingFooter();

String getBrowserTimingFooter(String nonce);

String getTransactionName();

Map<String, Object> getUserAttributes();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,41 @@ public String getBrowserTimingHeader() {
}
}

@Override
public String getBrowserTimingHeader(String nonce) {
synchronized (lock) {
if (!canRenderHeader()) {
return "";
}
return getBrowserTimingHeader2(nonce);
}
}

private String getBrowserTimingHeader2() {
BrowserConfig config = getBeaconConfig();
if (config == null) {
Agent.LOG.finer("Real user monitoring is disabled");
return "";
return onNoBrowserConfig();
}
onBrowserHeaderObtained();
return config.getBrowserTimingHeader();
}

private String getBrowserTimingHeader2(String nonce) {
BrowserConfig config = getBeaconConfig();
if (config == null) {
return onNoBrowserConfig();
}
String header = config.getBrowserTimingHeader();
onBrowserHeaderObtained();
return config.getBrowserTimingHeader(nonce);
}

private void onBrowserHeaderObtained() {
browserHeaderRendered = true;
return header;
}

private String onNoBrowserConfig() {
Agent.LOG.finer("Real user monitoring is disabled");
return "";
}

@Override
Expand All @@ -80,23 +106,54 @@ public String getBrowserTimingFooter() {
}
}

private String getBrowserTimingFooter2() {
BrowserConfig config = getBeaconConfig();
if (config == null) {
Agent.LOG.finer("Real user monitoring is disabled");
return "";
@Override
public String getBrowserTimingFooter(String nonce) {
synchronized (lock) {
if (!canRenderFooter()) {
return "";
}
return getBrowserTimingFooter2(nonce);
}
}

private String onBrowserFooterObtained(String value) {
if (!value.isEmpty()) {
browserFooterRendered = true;
}
return value;
}

private boolean prepareToObtainFooter() {
// this has the side-effect of possibly ignoring the transaction
tx.freezeTransactionName();
if (tx.isIgnore()) {
Agent.LOG.finer("Unable to get browser timing footer: transaction is ignore");
return false;
} else {
return true;
}
}

private String getBrowserTimingFooter2(String nonce) {
BrowserConfig config = getBeaconConfig();
if (config == null) {
return onNoBrowserConfig();
}
if(!prepareToObtainFooter()) {
return "";
}
String footer = config.getBrowserTimingFooter(this);
if (!footer.isEmpty()) {
browserFooterRendered = true;
return onBrowserFooterObtained(config.getBrowserTimingFooter(this, nonce));
}

private String getBrowserTimingFooter2() {
BrowserConfig config = getBeaconConfig();
if (config == null) {
return onNoBrowserConfig();
}
if(!prepareToObtainFooter()) {
return "";
}
return footer;
return onBrowserFooterObtained(config.getBrowserTimingFooter(this));
}

private boolean canRenderHeader() {
Expand Down
Loading