Skip to content

Add support for SamSite cookie attribute #1376

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

Open
wants to merge 2 commits into
base: 2.4
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
2 changes: 1 addition & 1 deletion modules/org.restlet.test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<name>Restlet Unit Tests</name>
<description>All Restlet unit tests.</description>
<build>
<testSourceDirectory>${basedir}/src</testSourceDirectory>
<testSourceDirectory>${basedir}/src/main/java</testSourceDirectory>
</build>

<dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.restlet.test.engine.header;

import org.restlet.data.CookieSetting;
import org.restlet.data.CookieSetting.SameSite;
import org.restlet.engine.header.CookieSettingReader;
import org.restlet.test.RestletTestCase;

public class CookieSettingReaderTestCase extends RestletTestCase {

public void testReadingWithoutSameSite() {
CookieSetting readSetting = CookieSettingReader.read("cookie=value");
assertNull(readSetting.getSameSite());
}

public void testReadingOfSameSite() {
for(SameSite sameSite : SameSite.values()) {
CookieSetting readSetting = CookieSettingReader.read("cookie=value; SameSite=" + sameSite);
assertEquals(sameSite, readSetting.getSameSite());
}
}

public void testReadingOfInvalidSameSite() {
CookieSetting readSetting = CookieSettingReader.read("cookie=value; SameSite=InvalidSameSiteValue");
assertNull( readSetting.getSameSite());
}

public void testReadingOfEmptySameSite() {
CookieSetting readSetting = CookieSettingReader.read("cookie=value; SameSite=");
assertNull( readSetting.getSameSite());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.restlet.test.engine.header;

import org.restlet.data.CookieSetting;
import org.restlet.data.CookieSetting.SameSite;
import org.restlet.engine.header.CookieSettingWriter;
import org.restlet.test.RestletTestCase;

public class CookieSettingWriterTestCase extends RestletTestCase {

public void testWritingOfUnsetSameSite() {
CookieSetting testSetting = new CookieSetting("cookie", "value");
assertEquals("cookie=value", CookieSettingWriter.write(testSetting));

assertEquals("cookie=value", CookieSettingWriter.write(testSetting));
}

public void testWritingOfSameSite() {
for(SameSite sameSite : SameSite.values()) {
CookieSetting testSetting = new CookieSetting("cookie", "value");
testSetting.setSameSite(sameSite);
assertEquals("cookie=value; SameSite=" + sameSite, CookieSettingWriter.write(testSetting));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,24 @@ public final class CookieSetting extends Cookie {
/** Indicates if cookie should only be transmitted by secure means. */
private volatile boolean secure;

/** Explicitly specifies a same site policy for browsers. */
private volatile SameSite sameSite;

public enum SameSite {
LAX("Lax"),
STRICT("Strict"),
NONE("None");

final String value;
SameSite(String value) {
this.value = value;
}

public String toString() {
return value;
}
}

/**
* Default constructor.
*/
Expand Down Expand Up @@ -164,6 +182,47 @@ public CookieSetting(int version, String name, String value, String path,
this.secure = secure;
this.accessRestricted = accessRestricted;
}

/**
* Constructor.
*
* @param version
* The cookie's version.
* @param name
* The cookie's name.
* @param value
* The cookie's value.
* @param path
* The cookie's path.
* @param domain
* The cookie's domain name.
* @param comment
* The cookie's comment.
* @param maxAge
* Sets the maximum age in seconds.<br>
* Use 0 to immediately discard an existing cookie.<br>
* Use -1 to discard the cookie at the end of the session
* (default).
* @param secure
* Indicates if cookie should only be transmitted by secure
* means.
* @param accessRestricted
* Indicates whether to restrict cookie access to untrusted
* parties. Currently this toggles the non-standard but widely
* supported HttpOnly cookie parameter.
* @param sameSite
* The cookie's same site policy.
*/
public CookieSetting(int version, String name, String value, String path,
String domain, String comment, int maxAge, boolean secure,
boolean accessRestricted, SameSite sameSite) {
super(version, name, value, path, domain);
this.comment = comment;
this.maxAge = maxAge;
this.secure = secure;
this.accessRestricted = accessRestricted;
this.sameSite = sameSite;
}

/**
* Preferred constructor.
Expand Down Expand Up @@ -192,7 +251,8 @@ public boolean equals(Object obj) {
return super.equals(obj)
&& this.maxAge == that.maxAge
&& this.secure == that.secure
&& Objects.equals(this.comment, that.comment);
&& Objects.equals(this.comment, that.comment)
&& Objects.equals(this.sameSite, that.sameSite);
}

/**
Expand Down Expand Up @@ -228,7 +288,7 @@ public int getMaxAge() {
@Override
public int hashCode() {
return SystemUtils.hashCode(super.hashCode(), getComment(),
getMaxAge(), isSecure());
getMaxAge(), isSecure(), getSameSite());
}

/**
Expand All @@ -251,6 +311,17 @@ public boolean isSecure() {
return this.secure;
}


/**
* Returns the currently set same site policy.
*
* @return sameSite
* The currently set same site attribute setting.
*/
public SameSite getSameSite() {
return this.sameSite;
}

/**
* Indicates whether to restrict cookie access to untrusted parties.
* Currently this toggles the non-standard but widely supported HttpOnly
Expand Down Expand Up @@ -293,14 +364,25 @@ public void setMaxAge(int maxAge) {
public void setSecure(boolean secure) {
this.secure = secure;
}

/**
* Sets the same site policy for the browser to apply to this cookie.
*
* @param sameSite
* The new same site policy to set.
*/
public void setSameSite(SameSite sameSite) {
this.sameSite = sameSite;
}

@Override
public String toString() {
return "CookieSetting [accessRestricted=" + accessRestricted
+ ", comment=" + comment + ", maxAge=" + maxAge + ", secure="
+ secure + ", domain=" + getDomain() + ", name=" + getName()
+ ", path=" + getPath() + ", value=" + getValue()
+ ", version=" + getVersion() + "]";
+ ", version=" + getVersion()
+", sameSite=" + "]";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.restlet.Context;
import org.restlet.data.CookieSetting;
import org.restlet.data.Parameter;
import org.restlet.data.CookieSetting.SameSite;
import org.restlet.engine.util.DateUtils;
import org.restlet.engine.util.StringUtils;

Expand Down Expand Up @@ -65,6 +66,8 @@ public class CookieSettingReader extends HeaderReader<CookieSetting> {
private static final String NAME_SET_SECURE = "secure";

private static final String NAME_SET_VERSION = "version";

private static final String NAME_SET_SAME_SITE ="samesite";

/**
* Parses the given String to a CookieSetting
Expand Down Expand Up @@ -243,6 +246,16 @@ public CookieSetting readValue() throws IOException {
}
} else if (pair.getName().equalsIgnoreCase(NAME_SET_VERSION)) {
result.setVersion(Integer.valueOf(pair.getValue()));
} else if(pair.getName().equalsIgnoreCase(NAME_SET_SAME_SITE) && !"".equals(pair.getValue())) {
SameSite sameSite = null;
try {
sameSite = SameSite.valueOf(pair.getValue().toUpperCase());
} catch(IllegalArgumentException illigalArgumentException) {
Context.getCurrentLogger()
.warning("Unable to parse cookie setting same-site value \"" + pair.getValue()
+ "\". Not setting same-site attribute.");
}
result.setSameSite(sameSite);
} else {
// Unexpected special attribute
// Silently ignore it as it may have been introduced by new specifications
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.util.List;

import org.restlet.data.CookieSetting;
import org.restlet.data.CookieSetting.SameSite;
import org.restlet.engine.util.DateUtils;

/**
Expand Down Expand Up @@ -142,6 +143,13 @@ public CookieSettingWriter append(CookieSetting cookieSetting)
if (cookieSetting.isAccessRestricted()) {
append("; HttpOnly");
}

// Append the same site attribute if it is set.
SameSite sameSite = cookieSetting.getSameSite();
if(sameSite != null) {
append("; SameSite=");
appendValue(sameSite.toString(), version);
}

// Append the comment
if (version > 0) {
Expand Down