Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -1353,6 +1353,79 @@ public void ShareUsageElement_RepeatWithNewSequence_NotShared() throws Exception
assertEquals(1, devices.getLength(), "1 device expected");
}

/**
* Within one session, a request that differs from one already shared
* only in its sequence number is not shared again, and a later request
* with a 51D_ value changed is, with the same SessionId and its own
* Sequence. The session id and the sequence never enter the repeat
* comparison and are always written to the record.
*/
@Test
public void ShareUsageElement_OneSession_ChangedValueSharedWithItsSequence()
throws Exception {
// Arrange
tracker = null;
createShareUsage(
1,
3,
1,
new ArrayList<>(),
new ArrayList<>(),
new ArrayList<>());
String[] screenWidths = { "1920", "1920", "1024" };

// Act
for (int sequence = 1; sequence <= screenWidths.length; sequence++) {
Map<String, Object> evidenceData = new HashMap<>();
evidenceData.put(EVIDENCE_CLIENTIP_KEY, "1.2.3.4");
evidenceData.put("header.user-agent", "one session user agent");
evidenceData.put(
"cookie.51D_ScreenPixelsWidth",
screenWidths[sequence - 1]);
evidenceData.put(Constants.EVIDENCE_SESSIONID, "one session");
evidenceData.put(Constants.EVIDENCE_SEQUENCE, sequence);
try (FlowData data = pipeline.createFlowData()) {
data.addEvidence(evidenceData);
shareUsageElement.process(data);
}
}
shareUsageElement.close();

// Assert
Document doc = docBuilderFactory.newDocumentBuilder().parse(
new ByteArrayInputStream(connector.getBaos().toByteArray()));
NodeList devices = (NodeList) xpath.compile("//Device")
.evaluate(doc, XPathConstants.NODESET);
assertEquals(2, devices.getLength(),
"Only the first request and the one with a changed value " +
"should be shared");
assertEquals("one session", xpath.evaluate("SessionId", devices.item(0)));
assertEquals("1", xpath.evaluate("Sequence", devices.item(0)));
assertEquals("1920", cookieValue(devices.item(0), "51D_ScreenPixelsWidth"));
assertEquals("one session", xpath.evaluate("SessionId", devices.item(1)));
assertEquals("3", xpath.evaluate("Sequence", devices.item(1)));
assertEquals("1024", cookieValue(devices.item(1), "51D_ScreenPixelsWidth"));
}

/**
* The value a record gives for the named cookie, or null when it gives
* none. Names are compared without regard to case.
*/
private static String cookieValue(Node device, String name) {
NodeList children = device.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
Node nameAttribute = child.getAttributes() == null ?
null : child.getAttributes().getNamedItem("Name");
if ("cookie".equals(child.getNodeName()) &&
nameAttribute != null &&
name.equalsIgnoreCase(nameAttribute.getNodeValue())) {
return child.getTextContent();
}
}
return null;
}

/**
* The session cookie the element names is read by the tracker, whatever
* the case of its name, and never sent. With sessions tracked, the same
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,31 +317,8 @@ public void Revalidation_SameETag_Gets304() throws Exception {
@Test
public void UnreadHeader_ReachesTheUsageSharingMessage() throws Exception {
BlockingQueue<byte[]> received = new LinkedBlockingQueue<>();
HttpServer server = HttpServer.create(
new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
server.createContext("/", exchange -> {
try (InputStream body = exchange.getRequestBody()) {
received.add(readAll(body));
}
exchange.sendResponseHeaders(200, -1);
exchange.close();
});
server.start();
try (Pipeline sharing = new PipelineBuilder(
LoggerFactory.getILoggerFactory())
.addFlowElement(new SequenceElementBuilder(
LoggerFactory.getILoggerFactory()).build())
.addFlowElement(new ShareUsageBuilder(
LoggerFactory.getILoggerFactory())
.setSharePercentage(1)
.setMinimumEntriesPerMessage(1)
.setRepeatEvidenceIntervalMinutes(0)
.setShareUsageUrl("http://" +
server.getAddress().getHostString() + ":" +
server.getAddress().getPort() + "/new.ashx")
.build())
.setAutoCloseElements(true)
.build()) {
HttpServer server = messageServer(received);
try (Pipeline sharing = sharingPipeline(server)) {
assertFalse("A header no element uses is accepted.",
sharing.getEvidenceKeyFilter().include("header.x-test"));
try (FlowData flowData = sharing.createFlowData()) {
Expand All @@ -350,12 +327,7 @@ public void UnreadHeader_ReachesTheUsageSharingMessage() throws Exception {
flowData.process();
}

byte[] sent = received.poll(
MESSAGE_WAIT_SECONDS, TimeUnit.SECONDS);
assertNotNull("No usage sharing message was sent.", sent);
Document message = DocumentBuilderFactory.newInstance()
.newDocumentBuilder()
.parse(new GZIPInputStream(new ByteArrayInputStream(sent)));
Document message = receivedMessage(received);
assertEquals("The header no element reads was not shared.",
"shared", sharedValue(message, "header", "X-Test"));
assertEquals("The User-Agent was not shared.",
Expand All @@ -367,6 +339,101 @@ public void UnreadHeader_ReachesTheUsageSharingMessage() throws Exception {
}
}

/**
* Values an application adds to the Evidence beside the web request
* reach the usage sharing message with the request's own values. They
* are the JA4 fingerprint, the HTTP/2 fingerprint and the two billing
* keys, which the specification says are shared whatever the
* configuration.
*/
@Test
public void AddedEvidence_ReachesTheUsageSharingMessage() throws Exception {
Map<String, String> added = new LinkedHashMap<>();
added.put("server.ja4", "t13d1516h2_8daaf6152771_02713d6af862");
added.put("server.h2",
"1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p");
added.put("fiftyone.resource-key", "test-resource-key");
added.put("fiftyone.license-key", "test-license-key");
BlockingQueue<byte[]> received = new LinkedBlockingQueue<>();
HttpServer server = messageServer(received);
try (Pipeline sharing = sharingPipeline(server)) {
try (FlowData flowData = sharing.createFlowData()) {
evidenceService.addEvidenceFromRequest(
flowData, request(headers()));
for (Map.Entry<String, String> value : added.entrySet()) {
flowData.addEvidence(value.getKey(), value.getValue());
}
flowData.process();
}

Document message = receivedMessage(received);
for (Map.Entry<String, String> value : added.entrySet()) {
String[] parts = value.getKey().split("\\.", 2);
assertEquals(value.getKey() + " was not shared.",
value.getValue(),
sharedValue(message, parts[0], parts[1]));
}
assertEquals("The request's own header was not shared.",
"shared", sharedValue(message, "header", "X-Test"));
} finally {
server.stop(0);
}
}

/**
* Starts a local HTTP endpoint that adds the body of each request it
* receives to the queue, as 51Degrees receives a usage sharing message.
*/
private static HttpServer messageServer(BlockingQueue<byte[]> received)
throws IOException {
HttpServer server = HttpServer.create(
new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
server.createContext("/", exchange -> {
try (InputStream body = exchange.getRequestBody()) {
received.add(readAll(body));
}
exchange.sendResponseHeaders(200, -1);
exchange.close();
});
server.start();
return server;
}

/**
* A Pipeline whose Usage Sharing Element sends each request to the local
* endpoint at once, with no repeat interval.
*/
private static Pipeline sharingPipeline(HttpServer server)
throws Exception {
return new PipelineBuilder(LoggerFactory.getILoggerFactory())
.addFlowElement(new SequenceElementBuilder(
LoggerFactory.getILoggerFactory()).build())
.addFlowElement(new ShareUsageBuilder(
LoggerFactory.getILoggerFactory())
.setSharePercentage(1)
.setMinimumEntriesPerMessage(1)
.setRepeatEvidenceIntervalMinutes(0)
.setShareUsageUrl("http://" +
server.getAddress().getHostString() + ":" +
server.getAddress().getPort() + "/new.ashx")
.build())
.setAutoCloseElements(true)
.build();
}

/**
* The first usage sharing message the local endpoint receives, unzipped
* and parsed.
*/
private static Document receivedMessage(BlockingQueue<byte[]> received)
throws Exception {
byte[] sent = received.poll(MESSAGE_WAIT_SECONDS, TimeUnit.SECONDS);
assertNotNull("No usage sharing message was sent.", sent);
return DocumentBuilderFactory.newInstance()
.newDocumentBuilder()
.parse(new GZIPInputStream(new ByteArrayInputStream(sent)));
}

/**
* The value the message gives for the named value of a category, such
* as header, or null when it gives none. Names are compared without
Expand Down
Loading