Fixes #10891 - Support the "Partitioned" cookie attribute.
Added support in oej.http.HttpCookie. Bridged support for Servlet cookies via the cookie Comment attribute. Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
parent
af13a72978
commit
f82844e2a2
|
@ -24,7 +24,6 @@ import org.eclipse.jetty.util.StringUtil;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
// TODO consider replacing this with java.net.HttpCookie (once it supports RFC6265)
|
||||
public class HttpCookie
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpCookie.class);
|
||||
|
@ -33,11 +32,18 @@ public class HttpCookie
|
|||
private static final String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim();
|
||||
|
||||
/**
|
||||
* If this string is found within the comment parsed with {@link #isHttpOnlyInComment(String)} the check will return true
|
||||
* String used in the {@code Comment} attribute of {@link java.net.HttpCookie},
|
||||
* parsed with {@link #isHttpOnlyInComment(String)}, to support the {@code HttpOnly} attribute.
|
||||
**/
|
||||
public static final String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
|
||||
/**
|
||||
* These strings are used by {@link #getSameSiteFromComment(String)} to check for a SameSite specifier in the comment
|
||||
* String used in the {@code Comment} attribute of {@link java.net.HttpCookie},
|
||||
* parsed with {@link #isPartitionedInComment(String)}, to support the {@code Partitioned} attribute.
|
||||
**/
|
||||
public static final String PARTITIONED_COMMENT = "__PARTITIONED__";
|
||||
/**
|
||||
* The strings used in the {@code Comment} attribute of {@link java.net.HttpCookie},
|
||||
* parsed with {@link #getSameSiteFromComment(String)}, to support the {@code SameSite} attribute.
|
||||
**/
|
||||
private static final String SAME_SITE_COMMENT = "__SAME_SITE_";
|
||||
public static final String SAME_SITE_NONE_COMMENT = SAME_SITE_COMMENT + "NONE__";
|
||||
|
@ -53,7 +59,7 @@ public class HttpCookie
|
|||
{
|
||||
NONE("None"), STRICT("Strict"), LAX("Lax");
|
||||
|
||||
private String attributeValue;
|
||||
private final String attributeValue;
|
||||
|
||||
SameSite(String attributeValue)
|
||||
{
|
||||
|
@ -77,6 +83,7 @@ public class HttpCookie
|
|||
private final boolean _httpOnly;
|
||||
private final long _expiration;
|
||||
private final SameSite _sameSite;
|
||||
private final boolean _partitioned;
|
||||
|
||||
public HttpCookie(String name, String value)
|
||||
{
|
||||
|
@ -104,6 +111,11 @@ public class HttpCookie
|
|||
}
|
||||
|
||||
public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure, String comment, int version, SameSite sameSite)
|
||||
{
|
||||
this(name, value, domain, path, maxAge, httpOnly, secure, comment, version, sameSite, false);
|
||||
}
|
||||
|
||||
public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure, String comment, int version, SameSite sameSite, boolean partitioned)
|
||||
{
|
||||
_name = name;
|
||||
_value = value;
|
||||
|
@ -116,6 +128,7 @@ public class HttpCookie
|
|||
_version = version;
|
||||
_expiration = maxAge < 0 ? -1 : NanoTime.now() + TimeUnit.SECONDS.toNanos(maxAge);
|
||||
_sameSite = sameSite;
|
||||
_partitioned = partitioned;
|
||||
}
|
||||
|
||||
public HttpCookie(String setCookie)
|
||||
|
@ -136,8 +149,10 @@ public class HttpCookie
|
|||
_comment = cookie.getComment();
|
||||
_version = cookie.getVersion();
|
||||
_expiration = _maxAge < 0 ? -1 : NanoTime.now() + TimeUnit.SECONDS.toNanos(_maxAge);
|
||||
// support for SameSite values has not yet been added to java.net.HttpCookie
|
||||
// Support for SameSite values has not yet been added to java.net.HttpCookie.
|
||||
_sameSite = getSameSiteFromComment(cookie.getComment());
|
||||
// Support for Partitioned has not yet been added to java.net.HttpCookie.
|
||||
_partitioned = isPartitionedInComment(cookie.getComment());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -229,6 +244,14 @@ public class HttpCookie
|
|||
return _expiration != -1 && NanoTime.isBefore(_expiration, timeNanos);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether this cookie is partitioned
|
||||
*/
|
||||
public boolean isPartitioned()
|
||||
{
|
||||
return _partitioned;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a string representation of this cookie
|
||||
*/
|
||||
|
@ -419,6 +442,8 @@ public class HttpCookie
|
|||
buf.append("; SameSite=");
|
||||
buf.append(_sameSite.getAttributeValue());
|
||||
}
|
||||
if (isPartitioned())
|
||||
buf.append("; Partitioned");
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
@ -428,23 +453,22 @@ public class HttpCookie
|
|||
return comment != null && comment.contains(HTTP_ONLY_COMMENT);
|
||||
}
|
||||
|
||||
public static boolean isPartitionedInComment(String comment)
|
||||
{
|
||||
return comment != null && comment.contains(PARTITIONED_COMMENT);
|
||||
}
|
||||
|
||||
public static SameSite getSameSiteFromComment(String comment)
|
||||
{
|
||||
if (comment != null)
|
||||
{
|
||||
if (comment.contains(SAME_SITE_STRICT_COMMENT))
|
||||
{
|
||||
return SameSite.STRICT;
|
||||
}
|
||||
if (comment.contains(SAME_SITE_LAX_COMMENT))
|
||||
{
|
||||
return SameSite.LAX;
|
||||
}
|
||||
if (comment.contains(SAME_SITE_NONE_COMMENT))
|
||||
{
|
||||
return SameSite.NONE;
|
||||
}
|
||||
}
|
||||
if (comment == null)
|
||||
return null;
|
||||
|
||||
if (comment.contains(SAME_SITE_STRICT_COMMENT))
|
||||
return SameSite.STRICT;
|
||||
if (comment.contains(SAME_SITE_LAX_COMMENT))
|
||||
return SameSite.LAX;
|
||||
if (comment.contains(SAME_SITE_NONE_COMMENT))
|
||||
return SameSite.NONE;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -488,21 +512,25 @@ public class HttpCookie
|
|||
public static String getCommentWithoutAttributes(String comment)
|
||||
{
|
||||
if (comment == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
String strippedComment = comment.trim();
|
||||
|
||||
strippedComment = StringUtil.strip(strippedComment, HTTP_ONLY_COMMENT);
|
||||
strippedComment = StringUtil.strip(strippedComment, PARTITIONED_COMMENT);
|
||||
strippedComment = StringUtil.strip(strippedComment, SAME_SITE_NONE_COMMENT);
|
||||
strippedComment = StringUtil.strip(strippedComment, SAME_SITE_LAX_COMMENT);
|
||||
strippedComment = StringUtil.strip(strippedComment, SAME_SITE_STRICT_COMMENT);
|
||||
|
||||
return strippedComment.length() == 0 ? null : strippedComment;
|
||||
return strippedComment.isEmpty() ? null : strippedComment;
|
||||
}
|
||||
|
||||
public static String getCommentWithAttributes(String comment, boolean httpOnly, SameSite sameSite)
|
||||
{
|
||||
return getCommentWithAttributes(comment, httpOnly, sameSite, false);
|
||||
}
|
||||
|
||||
public static String getCommentWithAttributes(String comment, boolean httpOnly, SameSite sameSite, boolean partitioned)
|
||||
{
|
||||
if (comment == null && sameSite == null)
|
||||
return null;
|
||||
|
@ -535,6 +563,9 @@ public class HttpCookie
|
|||
}
|
||||
}
|
||||
|
||||
if (partitioned)
|
||||
builder.append(PARTITIONED_COMMENT);
|
||||
|
||||
if (builder.length() == 0)
|
||||
return null;
|
||||
return builder.toString();
|
||||
|
|
|
@ -131,6 +131,9 @@ public class HttpCookieTest
|
|||
|
||||
httpCookie = new HttpCookie("everything", "value", "domain", "path", 0, true, true, null, -1, HttpCookie.SameSite.STRICT);
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Strict", httpCookie.getRFC6265SetCookie());
|
||||
|
||||
httpCookie = new HttpCookie("everything", "value", "domain", "path", 0, true, true, null, -1, HttpCookie.SameSite.STRICT, true);
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Strict; Partitioned", httpCookie.getRFC6265SetCookie());
|
||||
}
|
||||
|
||||
public static Stream<String> rfc6265BadNameSource()
|
||||
|
@ -336,7 +339,8 @@ public class HttpCookieTest
|
|||
Arguments.of("__HTTP_ONLY____SAME_SITE_NONE__comment", "comment"),
|
||||
// mixed - attributes at start and end
|
||||
Arguments.of("__SAME_SITE_NONE__comment__HTTP_ONLY__", "comment"),
|
||||
Arguments.of("__HTTP_ONLY__comment__SAME_SITE_NONE__", "comment")
|
||||
Arguments.of("__HTTP_ONLY__comment__SAME_SITE_NONE__", "comment"),
|
||||
Arguments.of("__PARTITIONED__comment__SAME_SITE_NONE__", "comment")
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -346,13 +350,9 @@ public class HttpCookieTest
|
|||
{
|
||||
String actualComment = HttpCookie.getCommentWithoutAttributes(rawComment);
|
||||
if (expectedComment == null)
|
||||
{
|
||||
assertNull(actualComment);
|
||||
}
|
||||
else
|
||||
{
|
||||
assertEquals(actualComment, expectedComment);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -368,6 +368,8 @@ public class HttpCookieTest
|
|||
is("__HTTP_ONLY____SAME_SITE_NONE__"));
|
||||
assertThat(HttpCookie.getCommentWithAttributes("hello", true, HttpCookie.SameSite.LAX),
|
||||
is("hello__HTTP_ONLY____SAME_SITE_LAX__"));
|
||||
assertThat(HttpCookie.getCommentWithAttributes("hello", true, HttpCookie.SameSite.LAX, true),
|
||||
is("hello__HTTP_ONLY____SAME_SITE_LAX____PARTITIONED__"));
|
||||
|
||||
assertThat(HttpCookie.getCommentWithAttributes("__HTTP_ONLY____SAME_SITE_LAX__", false, null), nullValue());
|
||||
assertThat(HttpCookie.getCommentWithAttributes("__HTTP_ONLY____SAME_SITE_LAX__", true, HttpCookie.SameSite.NONE),
|
||||
|
|
|
@ -267,6 +267,7 @@ public class Response implements HttpServletResponse
|
|||
String comment = cookie.getComment();
|
||||
// HttpOnly was supported as a comment in cookie flags before the java.net.HttpCookie implementation so need to check that
|
||||
boolean httpOnly = cookie.isHttpOnly() || HttpCookie.isHttpOnlyInComment(comment);
|
||||
boolean partitioned = HttpCookie.isPartitionedInComment(comment);
|
||||
SameSite sameSite = HttpCookie.getSameSiteFromComment(comment);
|
||||
comment = HttpCookie.getCommentWithoutAttributes(comment);
|
||||
|
||||
|
@ -280,7 +281,8 @@ public class Response implements HttpServletResponse
|
|||
cookie.getSecure(),
|
||||
comment,
|
||||
cookie.getVersion(),
|
||||
sameSite));
|
||||
sameSite,
|
||||
partitioned));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -632,29 +632,26 @@ public class SessionHandler extends ScopedHandler
|
|||
*/
|
||||
public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure)
|
||||
{
|
||||
if (isUsingCookies())
|
||||
{
|
||||
SessionCookieConfig cookieConfig = getSessionCookieConfig();
|
||||
String sessionPath = (cookieConfig.getPath() == null) ? contextPath : cookieConfig.getPath();
|
||||
sessionPath = (StringUtil.isEmpty(sessionPath)) ? "/" : sessionPath;
|
||||
String id = getExtendedId(session);
|
||||
HttpCookie cookie = null;
|
||||
|
||||
cookie = new HttpCookie(
|
||||
getSessionCookieName(_cookieConfig),
|
||||
id,
|
||||
cookieConfig.getDomain(),
|
||||
sessionPath,
|
||||
cookieConfig.getMaxAge(),
|
||||
cookieConfig.isHttpOnly(),
|
||||
cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure),
|
||||
HttpCookie.getCommentWithoutAttributes(cookieConfig.getComment()),
|
||||
0,
|
||||
HttpCookie.getSameSiteFromComment(cookieConfig.getComment()));
|
||||
|
||||
return cookie;
|
||||
}
|
||||
return null;
|
||||
if (!isUsingCookies())
|
||||
return null;
|
||||
SessionCookieConfig cookieConfig = getSessionCookieConfig();
|
||||
String sessionPath = (cookieConfig.getPath() == null) ? contextPath : cookieConfig.getPath();
|
||||
sessionPath = (StringUtil.isEmpty(sessionPath)) ? "/" : sessionPath;
|
||||
String id = getExtendedId(session);
|
||||
String comment = cookieConfig.getComment();
|
||||
return new HttpCookie(
|
||||
getSessionCookieName(_cookieConfig),
|
||||
id,
|
||||
cookieConfig.getDomain(),
|
||||
sessionPath,
|
||||
cookieConfig.getMaxAge(),
|
||||
cookieConfig.isHttpOnly(),
|
||||
cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure),
|
||||
HttpCookie.getCommentWithoutAttributes(comment),
|
||||
0,
|
||||
HttpCookie.getSameSiteFromComment(comment),
|
||||
HttpCookie.isPartitionedInComment(comment)
|
||||
);
|
||||
}
|
||||
|
||||
@ManagedAttribute("domain of the session cookie, or null for the default")
|
||||
|
@ -802,6 +799,19 @@ public class SessionHandler extends ScopedHandler
|
|||
_httpOnly = httpOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether session cookies should have the {@code Partitioned} attribute.
|
||||
*
|
||||
* @param partitioned whether session cookies should have the {@code Partitioned} attribute
|
||||
* @see HttpCookie
|
||||
*/
|
||||
public void setPartitioned(boolean partitioned)
|
||||
{
|
||||
// Encode in comment whilst not supported by SessionConfig,
|
||||
// so that it can be set/saved in web.xml and quickstart.
|
||||
_sessionComment = HttpCookie.getCommentWithAttributes(_sessionComment, false, null, partitioned);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Session cookie sameSite mode.
|
||||
* Currently this is encoded in the session comment until sameSite is supported by {@link SessionCookieConfig}
|
||||
|
|
Loading…
Reference in New Issue