Skip to content
This repository has been archived by the owner on Apr 22, 2022. It is now read-only.

#461 - added support to divolte.js - cookie - samesite #492

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
15 changes: 15 additions & 0 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,21 @@ Browser source property: ``cookie_domain``
cookie_domain = ".example.com"
}

Browser source property: ``cookie_same_site``
""""""""""""""""""""""""""""""""""""""""""
:Description:
The cookie SameSite that is assigned to the cookies. When left empty, the cookies will be set with browser default. Available options to set - Strict, Lax & None; Secure
:Default:
*Empty*
:Example:

.. code-block:: none

divolte.sources.a_source {
type = browser
cookie_same_site = "Lax"
}

Browser source property: ``javascript.name``
""""""""""""""""""""""""""""""""""""""""""""
:Description:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,21 @@

package io.divolte.server.config;

import java.time.Duration;
import java.util.Objects;
import java.util.Optional;

import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.ParametersAreNullableByDefault;
import javax.validation.Valid;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.MoreObjects;
import io.divolte.server.BrowserSource;
import io.divolte.server.HttpSource;
import io.divolte.server.IncomingRequestProcessingPool;

import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.ParametersAreNullableByDefault;
import javax.validation.Valid;
import java.time.Duration;
import java.util.Objects;
import java.util.Optional;

@ParametersAreNonnullByDefault
public class BrowserSourceConfiguration extends SourceConfiguration {
private static final String DEFAULT_PARTY_COOKIE = "_dvp";
Expand All @@ -43,19 +42,21 @@ public class BrowserSourceConfiguration extends SourceConfiguration {
private static final String DEFAULT_HTTP_RESPONSE_DELAY = "0 seconds";

public static final BrowserSourceConfiguration DEFAULT_BROWSER_SOURCE_CONFIGURATION = new BrowserSourceConfiguration(
DEFAULT_PREFIX,
DEFAULT_EVENT_SUFFIX,
Optional.empty(),
DEFAULT_PARTY_COOKIE,
DurationDeserializer.parseDuration(DEFAULT_PARTY_TIMEOUT),
DEFAULT_SESSION_COOKIE,
DurationDeserializer.parseDuration(DEFAULT_SESSION_TIMEOUT),
DurationDeserializer.parseDuration(DEFAULT_HTTP_RESPONSE_DELAY),
JavascriptConfiguration.DEFAULT_JAVASCRIPT_CONFIGURATION);
DEFAULT_PREFIX,
DEFAULT_EVENT_SUFFIX,
Optional.empty(),
DEFAULT_PARTY_COOKIE,
DurationDeserializer.parseDuration(DEFAULT_PARTY_TIMEOUT),
DEFAULT_SESSION_COOKIE,
DurationDeserializer.parseDuration(DEFAULT_SESSION_TIMEOUT),
DurationDeserializer.parseDuration(DEFAULT_HTTP_RESPONSE_DELAY),
JavascriptConfiguration.DEFAULT_JAVASCRIPT_CONFIGURATION,
Optional.empty());

public final String prefix;
public final String eventSuffix;
public final Optional<String> cookieDomain;
public final Optional<String> cookieSameSite;
public final String partyCookie;
public final Duration partyTimeout;
public final String sessionCookie;
Expand All @@ -67,15 +68,16 @@ public class BrowserSourceConfiguration extends SourceConfiguration {

@JsonCreator
@ParametersAreNullableByDefault
BrowserSourceConfiguration(@JsonProperty(defaultValue=DEFAULT_PREFIX) final String prefix,
@JsonProperty(defaultValue=DEFAULT_EVENT_SUFFIX) final String eventSuffix,
BrowserSourceConfiguration(@JsonProperty(defaultValue = DEFAULT_PREFIX) final String prefix,
@JsonProperty(defaultValue = DEFAULT_EVENT_SUFFIX) final String eventSuffix,
@Nonnull final Optional<String> cookieDomain,
@JsonProperty(defaultValue=DEFAULT_PARTY_COOKIE) final String partyCookie,
@JsonProperty(defaultValue=DEFAULT_PARTY_TIMEOUT) final Duration partyTimeout,
@JsonProperty(defaultValue=DEFAULT_SESSION_COOKIE) final String sessionCookie,
@JsonProperty(defaultValue=DEFAULT_SESSION_TIMEOUT) final Duration sessionTimeout,
@JsonProperty(defaultValue=DEFAULT_HTTP_RESPONSE_DELAY) final Duration httpResponseDelay,
final JavascriptConfiguration javascript) {
@JsonProperty(defaultValue = DEFAULT_PARTY_COOKIE) final String partyCookie,
@JsonProperty(defaultValue = DEFAULT_PARTY_TIMEOUT) final Duration partyTimeout,
@JsonProperty(defaultValue = DEFAULT_SESSION_COOKIE) final String sessionCookie,
@JsonProperty(defaultValue = DEFAULT_SESSION_TIMEOUT) final Duration sessionTimeout,
@JsonProperty(defaultValue = DEFAULT_HTTP_RESPONSE_DELAY) final Duration httpResponseDelay,
final JavascriptConfiguration javascript,
@Nonnull final Optional<String> cookieSameSite) {
// TODO: register a custom deserializer with Jackson that uses the defaultValue property from the annotation to fix this
this.prefix = Optional.ofNullable(prefix).map(BrowserSourceConfiguration::ensureTrailingSlash).orElse(DEFAULT_PREFIX);
this.eventSuffix = Optional.ofNullable(eventSuffix).orElse(DEFAULT_EVENT_SUFFIX);
Expand All @@ -86,6 +88,7 @@ public class BrowserSourceConfiguration extends SourceConfiguration {
this.sessionTimeout = Optional.ofNullable(sessionTimeout).orElseGet(() -> DurationDeserializer.parseDuration(DEFAULT_SESSION_TIMEOUT));
this.httpResponseDelay = Optional.ofNullable(httpResponseDelay).orElseGet(() -> DurationDeserializer.parseDuration(DEFAULT_HTTP_RESPONSE_DELAY));
this.javascript = Optional.ofNullable(javascript).orElse(JavascriptConfiguration.DEFAULT_JAVASCRIPT_CONFIGURATION);
this.cookieSameSite = Objects.requireNonNull(cookieSameSite);
}

private static String ensureTrailingSlash(final String s) {
Expand All @@ -95,22 +98,23 @@ private static String ensureTrailingSlash(final String s) {
@Override
protected MoreObjects.ToStringHelper toStringHelper() {
return super.toStringHelper()
.add("prefix", prefix)
.add("eventSuffix", eventSuffix)
.add("cookieDomain", cookieDomain)
.add("partyCookie", partyCookie)
.add("partyTimeout", partyTimeout)
.add("sessionCookie", sessionCookie)
.add("sessionTimeout", sessionTimeout)
.add("httpResponseDelay", httpResponseDelay)
.add("javascript", javascript);
.add("prefix", prefix)
.add("eventSuffix", eventSuffix)
.add("cookieDomain", cookieDomain)
.add("partyCookie", partyCookie)
.add("partyTimeout", partyTimeout)
.add("sessionCookie", sessionCookie)
.add("sessionTimeout", sessionTimeout)
.add("httpResponseDelay", httpResponseDelay)
.add("javascript", javascript)
.add("SameSite", cookieSameSite);
}

@Override
public HttpSource createSource(
final ValidatedConfiguration vc,
final String sourceName,
final IncomingRequestProcessingPool processingPool) {
final ValidatedConfiguration vc,
final String sourceName,
final IncomingRequestProcessingPool processingPool) {
return new BrowserSource(vc, sourceName, processingPool);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,14 @@ private static ImmutableMap<String, Object> createScriptConstants(final BrowserS
builder.put("PARTY_ID_TIMEOUT_SECONDS", trimLongToMaxInt(browserSourceConfiguration.partyTimeout.get(ChronoUnit.SECONDS)));
builder.put("SESSION_COOKIE_NAME", browserSourceConfiguration.sessionCookie);
builder.put("SESSION_ID_TIMEOUT_SECONDS", trimLongToMaxInt(browserSourceConfiguration.sessionTimeout.get(ChronoUnit.SECONDS)));
browserSourceConfiguration.cookieDomain.ifPresent((v) -> builder.put("COOKIE_DOMAIN", v));
browserSourceConfiguration.cookieDomain.ifPresent(v -> builder.put("COOKIE_DOMAIN", v));
browserSourceConfiguration.cookieSameSite.ifPresent(v -> builder.put("COOKIE_SAME_SITE", v));
builder.put("LOGGING", browserSourceConfiguration.javascript.logging);
builder.put(SCRIPT_CONSTANT_NAME, browserSourceConfiguration.javascript.name);
builder.put("EVENT_SUFFIX", browserSourceConfiguration.eventSuffix);
builder.put("AUTO_PAGE_VIEW_EVENT", browserSourceConfiguration.javascript.autoPageViewEvent);
builder.put("EVENT_TIMEOUT_SECONDS", browserSourceConfiguration.javascript.eventTimeout.getSeconds() +
browserSourceConfiguration.javascript.eventTimeout.getNano() / (double)NANOS_PER_SECOND);
browserSourceConfiguration.javascript.eventTimeout.getNano() / (double) NANOS_PER_SECOND);
return builder.build();
}

Expand All @@ -68,17 +69,17 @@ private static int trimLongToMaxInt(long duration) {
} else {
result = Integer.MAX_VALUE;
logger.warn("Configured duration ({}) is too higher; capping at {}.",
duration, result);
duration, result);
}
return result;
}

public static TrackingJavaScriptResource create(final ValidatedConfiguration vc,
final String sourceName) throws IOException {
final BrowserSourceConfiguration browserSourceConfiguration =
vc.configuration().getSourceConfiguration(sourceName, BrowserSourceConfiguration.class);
vc.configuration().getSourceConfiguration(sourceName, BrowserSourceConfiguration.class);
return new TrackingJavaScriptResource("divolte.js",
createScriptConstants(browserSourceConfiguration),
browserSourceConfiguration.javascript.debug);
createScriptConstants(browserSourceConfiguration),
browserSourceConfiguration.javascript.debug);
}
}
14 changes: 11 additions & 3 deletions src/main/resources/divolte.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ var SCRIPT_NAME = 'divolte.js';
var EVENT_SUFFIX = 'csc-event';
/** @define {boolean} */
var AUTO_PAGE_VIEW_EVENT = true;
/** @define {string} */
var COOKIE_SAME_SITE = '';

(function (global, factory) {
factory(global);
Expand Down Expand Up @@ -179,14 +181,20 @@ var AUTO_PAGE_VIEW_EVENT = true;
* @param {number} nowMs The current time, in milliseconds since the Unix epoch.
* @param {string} domain The domain to set the cookies for, if non-zero in length.
*/
var setCookie = function(name, value, maxAgeSeconds, nowMs, domain) {
var setCookie = function(name, value, maxAgeSeconds, nowMs, domain, sameSite) {
var expiry = new Date(nowMs + 1000 * maxAgeSeconds);
// Assumes cookie name and value are sensible. (For our use they are.)
// Note: No domain means these are always first-party cookies.
var cookieString = name + '=' + value + "; path=/; expires=" + expiry.toUTCString() + "; max-age=" + maxAgeSeconds;
if (domain) {
cookieString += "; domain=" + domain;
}

// SameSite supports None; Secure, Strict & Lax
if (sameSite) {
cookieString += "; SameSite=" + sameSite;
}

document.cookie = cookieString;
};

Expand Down Expand Up @@ -1309,8 +1317,8 @@ var AUTO_PAGE_VIEW_EVENT = true;
isFirstInSession = false;

// Update the party and session cookies.
setCookie(SESSION_COOKIE_NAME, sessionId, SESSION_ID_TIMEOUT_SECONDS, eventTime, COOKIE_DOMAIN);
setCookie(PARTY_COOKIE_NAME, partyId, PARTY_ID_TIMEOUT_SECONDS, eventTime, COOKIE_DOMAIN);
setCookie(SESSION_COOKIE_NAME, sessionId, SESSION_ID_TIMEOUT_SECONDS, eventTime, COOKIE_DOMAIN, COOKIE_SAME_SITE);
setCookie(PARTY_COOKIE_NAME, partyId, PARTY_ID_TIMEOUT_SECONDS, eventTime, COOKIE_DOMAIN, COOKIE_SAME_SITE);

// Last thing we do: add a checksum to the queryString.
addParam('x', calculateChecksum(params).toString(36));
Expand Down