Merged branch 'jetty-11.0.x' into 'jetty-12.0.x'.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2023-11-19 22:09:10 +01:00
commit e933116997
No known key found for this signature in database
GPG Key ID: 1677D141BCF3584D
12 changed files with 144 additions and 44 deletions

View File

@ -38,6 +38,7 @@ public interface HttpCookie
String PATH_ATTRIBUTE = "Path";
String SAME_SITE_ATTRIBUTE = "SameSite";
String SECURE_ATTRIBUTE = "Secure";
String PARTITIONED_ATTRIBUTE = "Partitioned";
/**
* @return the cookie name
@ -150,6 +151,15 @@ public interface HttpCookie
return Boolean.parseBoolean(getAttributes().get(HTTP_ONLY_ATTRIBUTE));
}
/**
* @return whether the {@code Partitioned} attribute is present
* @see #PARTITIONED_ATTRIBUTE
*/
default boolean isPartitioned()
{
return Boolean.parseBoolean(getAttributes().get(PARTITIONED_ATTRIBUTE));
}
/**
* @return the cookie hash code
* @see #hashCode(HttpCookie)
@ -260,6 +270,12 @@ public interface HttpCookie
return getWrapped().isHttpOnly();
}
@Override
public boolean isPartitioned()
{
return getWrapped().isPartitioned();
}
@Override
public int hashCode()
{
@ -560,6 +576,12 @@ public interface HttpCookie
throw new IllegalArgumentException("Invalid Secure attribute");
secure(true);
}
case "partitioned" ->
{
if (!isTruthy(value))
throw new IllegalArgumentException("Invalid Partitioned attribute");
partitioned(true);
}
default -> _attributes = lazyAttributePut(_attributes, name, value);
}
return this;
@ -630,6 +652,15 @@ public interface HttpCookie
return this;
}
public Builder partitioned(boolean partitioned)
{
if (partitioned)
_attributes = lazyAttributePut(_attributes, PARTITIONED_ATTRIBUTE, Boolean.TRUE.toString());
else
_attributes = lazyAttributeRemove(_attributes, PARTITIONED_ATTRIBUTE);
return this;
}
/**
* @return an immutable {@link HttpCookie} instance.
*/
@ -657,8 +688,8 @@ public interface HttpCookie
* @param value the value of the cookie
* @param attributes the map of attributes to use with this cookie (this map is used for field values
* such as {@link #getDomain()}, {@link #getPath()}, {@link #getMaxAge()}, {@link #isHttpOnly()},
* {@link #isSecure()}, {@link #getComment()}, plus any newly defined attributes unknown to this
* code base.
* {@link #isSecure()}, {@link #isPartitioned()}, {@link #getComment()}, plus any newly defined
* attributes unknown to this code base.
*/
static HttpCookie from(String name, String value, Map<String, String> attributes)
{
@ -673,8 +704,8 @@ public interface HttpCookie
* @param version the version of the cookie (only used in RFC2965 mode)
* @param attributes the map of attributes to use with this cookie (this map is used for field values
* such as {@link #getDomain()}, {@link #getPath()}, {@link #getMaxAge()}, {@link #isHttpOnly()},
* {@link #isSecure()}, {@link #getComment()}, plus any newly defined attributes unknown to this
* code base.
* {@link #isSecure()}, {@link #isPartitioned()}, {@link #getComment()}, plus any newly defined
* attributes unknown to this code base.
*/
static HttpCookie from(String name, String value, int version, Map<String, String> attributes)
{
@ -786,6 +817,8 @@ public interface HttpCookie
{
if (httpCookie.getSameSite() != null)
throw new IllegalArgumentException("SameSite attribute not supported by " + java.net.HttpCookie.class.getName());
if (httpCookie.isPartitioned())
throw new IllegalArgumentException("Partitioned attribute not supported by " + java.net.HttpCookie.class.getName());
java.net.HttpCookie cookie = new java.net.HttpCookie(httpCookie.getName(), httpCookie.getValue());
cookie.setVersion(httpCookie.getVersion());
cookie.setComment(httpCookie.getComment());

View File

@ -46,6 +46,7 @@ public final class HttpCookieUtils
.with(HttpCookie.PATH_ATTRIBUTE)
.with(HttpCookie.SAME_SITE_ATTRIBUTE)
.with(HttpCookie.SECURE_ATTRIBUTE)
.with(HttpCookie.PARTITIONED_ATTRIBUTE)
.build();
// RFC 1123 format of epoch for the Expires attribute.
private static final String EPOCH_EXPIRES = "Thu, 01 Jan 1970 00:00:00 GMT";
@ -187,6 +188,9 @@ public final class HttpCookieUtils
if (httpCookie.isHttpOnly())
builder.append(";HttpOnly");
if (httpCookie.isPartitioned())
builder.append(";Partitioned");
HttpCookie.SameSite sameSite = httpCookie.getSameSite();
if (sameSite != null)
builder.append(";SameSite=").append(sameSite.getAttributeValue());
@ -249,6 +253,8 @@ public final class HttpCookieUtils
builder.append("; Secure");
if (httpCookie.isHttpOnly())
builder.append("; HttpOnly");
if (httpCookie.isPartitioned())
builder.append("; Partitioned");
Map<String, String> attributes = httpCookie.getAttributes();

View File

@ -152,6 +152,9 @@ public class HttpCookieTest
httpCookie = HttpCookie.from("everything", "value", Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "domain", HttpCookie.PATH_ATTRIBUTE, "path", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true), HttpCookie.SAME_SITE_ATTRIBUTE, SameSite.STRICT.getAttributeValue()));
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Strict", HttpCookieUtils.getRFC6265SetCookie(httpCookie));
httpCookie = HttpCookie.from("everything", "value", Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "domain", HttpCookie.PATH_ATTRIBUTE, "path", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true), HttpCookie.SAME_SITE_ATTRIBUTE, SameSite.STRICT.getAttributeValue(), HttpCookie.PARTITIONED_ATTRIBUTE, Boolean.toString(true)));
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; Partitioned; SameSite=Strict", HttpCookieUtils.getRFC6265SetCookie(httpCookie));
}
public static Stream<String> rfc6265BadNameSource()

View File

@ -834,8 +834,8 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
}
/**
* @return true if session cookies should be HTTP-only (Microsoft extension)
* @see org.eclipse.jetty.http.HttpCookie#isHttpOnly()
* @return true if session cookies should be HTTP only
* @see HttpCookie#isHttpOnly()
*/
@Override
public boolean isHttpOnly()
@ -855,6 +855,28 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
_sessionCookieAttributes.put(HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(httpOnly));
}
/**
* @return true if session cookies should have the {@code Partitioned} attribute
* @see HttpCookie#isPartitioned()
*/
@Override
public boolean isPartitioned()
{
return Boolean.parseBoolean(_sessionCookieAttributes.get(HttpCookie.PARTITIONED_ATTRIBUTE));
}
/**
* Sets whether session cookies should have the {@code Partitioned} attribute
*
* @param partitioned whether session cookies should have the {@code Partitioned} attribute
* @see HttpCookie
*/
@Override
public void setPartitioned(boolean partitioned)
{
_sessionCookieAttributes.put(HttpCookie.PARTITIONED_ATTRIBUTE, Boolean.toString(partitioned));
}
/**
* Check if id is in use by this context
*

View File

@ -95,6 +95,9 @@ public interface SessionConfig
@ManagedAttribute("true if cookies use the http only flag")
boolean isHttpOnly();
@ManagedAttribute("true if cookies have the Partitioned attribute")
boolean isPartitioned();
@ManagedAttribute("if true, secure cookie flag is set on session cookies")
boolean isSecureCookies();
@ -115,6 +118,8 @@ public interface SessionConfig
void setHttpOnly(boolean value);
void setPartitioned(boolean value);
void setMaxCookieAge(int value);
void setMaxInactiveInterval(int value);

View File

@ -27,9 +27,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
*
*/
public class AbstractSessionManagerTest
{
@Test
@ -51,7 +48,8 @@ public class AbstractSessionManagerTest
assertEquals("/test", cookie.getPath());
assertFalse(cookie.isSecure());
assertFalse(cookie.isHttpOnly());
assertFalse(cookie.isPartitioned());
//check cookie with httpOnly and secure
sessionManager.setHttpOnly(true);
sessionManager.setSecureRequestOnly(true);

View File

@ -589,6 +589,12 @@ public class ServletApiResponse implements HttpServletResponse
return _cookie.isHttpOnly();
}
@Override
public boolean isPartitioned()
{
return Boolean.parseBoolean(getAttributes().get(HttpCookie.PARTITIONED_ATTRIBUTE));
}
@Override
public Map<String, String> getAttributes()
{

View File

@ -76,7 +76,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(WorkDirExtension.class)
public class SessionHandlerTest
{
public static class SessionConsumer implements Consumer<ManagedSession>
{
private ManagedSession _session;
@ -685,6 +684,7 @@ public class SessionHandlerTest
sessionCookieConfig.setSecure(false);
sessionCookieConfig.setPath("/foo");
sessionCookieConfig.setMaxAge(99);
sessionCookieConfig.setAttribute("Partitioned", "true");
sessionCookieConfig.setAttribute("SameSite", "Strict");
sessionCookieConfig.setAttribute("ham", "cheese");
@ -694,11 +694,12 @@ public class SessionHandlerTest
assertEquals("/foo", cookie.getPath());
assertFalse(cookie.isHttpOnly());
assertFalse(cookie.isSecure());
assertTrue(cookie.isPartitioned());
assertEquals(99, cookie.getMaxAge());
assertEquals(HttpCookie.SameSite.STRICT, cookie.getSameSite());
String cookieStr = HttpCookieUtils.getRFC6265SetCookie(cookie);
assertThat(cookieStr, containsString("; SameSite=Strict; ham=cheese"));
assertThat(cookieStr, containsString("; Partitioned; SameSite=Strict; ham=cheese"));
}
@Test

View File

@ -73,11 +73,18 @@ public class Response implements HttpServletResponse
public static final int USE_KNOWN_CONTENT_LENGTH = -2;
/**
* If this string is found within a cookie comment, then the cookie is HttpOnly
* String used in the {@code Comment} attribute of {@link Cookie}
* to support the {@code HttpOnly} attribute.
**/
private static final String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
/**
* These strings are used by to check for a SameSite specifier in a cookie comment
* String used in the {@code Comment} attribute of {@link Cookie}
* to support the {@code Partitioned} attribute.
**/
private static final String PARTITIONED_COMMENT = "__PARTITIONED__";
/**
* The strings used in the {@code Comment} attribute of {@link Cookie}
* to support the {@code SameSite} attribute.
**/
private static final String SAME_SITE_COMMENT = "__SAME_SITE_";
private static final String SAME_SITE_NONE_COMMENT = SAME_SITE_COMMENT + "NONE__";
@ -1480,16 +1487,16 @@ public class Response implements HttpServletResponse
private final String _comment;
private final boolean _httpOnly;
private final SameSite _sameSite;
private final boolean _partitioned;
public HttpCookieFacade(Cookie cookie)
{
_cookie = cookie;
String comment = cookie.getComment();
// HttpOnly was supported as a comment in cookie flags before the java.net.HttpCookie implementation so need to check that
// HttpOnly was supported as a comment in cookie flags before the Cookie implementation so need to check that.
_httpOnly = cookie.isHttpOnly() || isHttpOnlyInComment(comment);
_sameSite = getSameSiteFromComment(comment);
_partitioned = isPartitionedInComment(comment);
_comment = getCommentWithoutAttributes(comment);
}
@ -1553,6 +1560,12 @@ public class Response implements HttpServletResponse
return _httpOnly;
}
@Override
public boolean isPartitioned()
{
return _partitioned;
}
@Override
public Map<String, String> getAttributes()
{
@ -1568,6 +1581,8 @@ public class Response implements HttpServletResponse
attributes.put(SAME_SITE_ATTRIBUTE, _sameSite.getAttributeValue());
if (isSecure())
attributes.put(SECURE_ATTRIBUTE, Boolean.TRUE.toString());
if (isPartitioned())
attributes.put(PARTITIONED_ATTRIBUTE, Boolean.TRUE.toString());
return attributes;
}
@ -1589,47 +1604,43 @@ public class Response implements HttpServletResponse
return HttpCookie.toString(this);
}
static boolean isHttpOnlyInComment(String comment)
private static boolean isHttpOnlyInComment(String comment)
{
return comment != null && comment.contains(HTTP_ONLY_COMMENT);
}
static SameSite getSameSiteFromComment(String comment)
private static boolean isPartitionedInComment(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;
}
}
return comment != null && comment.contains(PARTITIONED_COMMENT);
}
private static SameSite getSameSiteFromComment(String comment)
{
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;
}
private 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;
}
}
}

View File

@ -316,6 +316,12 @@ public class SessionHandler extends ScopedHandler implements SessionConfig.Mutab
return _sessionManager.isHttpOnly();
}
@Override
public boolean isPartitioned()
{
return _sessionManager.isPartitioned();
}
@Override
public boolean isSecureCookies()
{
@ -352,6 +358,12 @@ public class SessionHandler extends ScopedHandler implements SessionConfig.Mutab
_sessionManager.setHttpOnly(value);
}
@Override
public void setPartitioned(boolean value)
{
_sessionManager.setPartitioned(value);
}
@Override
public void setMaxCookieAge(int value)
{

View File

@ -54,6 +54,7 @@ import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class SessionHandlerTest
{
@ -193,6 +194,7 @@ public class SessionHandlerTest
//for < ee10, SameSite cannot be set on the SessionCookieConfig, only on the SessionManager, or
//a default value on the context attribute org.eclipse.jetty.cookie.sameSiteDefault
mgr.setSameSite(HttpCookie.SameSite.STRICT);
mgr.setPartitioned(true);
HttpCookie cookie = mgr.getSessionManager().getSessionCookie(session, false);
assertEquals("SPECIAL", cookie.getName());
@ -200,11 +202,12 @@ public class SessionHandlerTest
assertEquals("/foo", cookie.getPath());
assertFalse(cookie.isHttpOnly());
assertFalse(cookie.isSecure());
assertTrue(cookie.isPartitioned());
assertEquals(99, cookie.getMaxAge());
assertEquals(HttpCookie.SameSite.STRICT, cookie.getSameSite());
String cookieStr = HttpCookieUtils.getRFC6265SetCookie(cookie);
assertThat(cookieStr, containsString("; SameSite=Strict"));
assertThat(cookieStr, containsString("; Partitioned; SameSite=Strict"));
}
@Test

12
pom.xml
View File

@ -126,14 +126,14 @@
</modules>
<scm>
<connection>scm:git:https://github.com/eclipse/jetty.project.git</connection>
<developerConnection>scm:git:git@github.com:eclipse/jetty.project.git</developerConnection>
<url>https://github.com/eclipse/jetty.project</url>
<connection>scm:git:https://github.com/jetty/jetty.project.git</connection>
<developerConnection>scm:git:git@github.com:jetty/jetty.project.git</developerConnection>
<url>https://github.com/jetty/jetty.project</url>
</scm>
<issueManagement>
<system>github</system>
<url>https://github.com/eclipse/jetty.project/issues</url>
<url>https://github.com/jetty/jetty.project/issues</url>
</issueManagement>
<distributionManagement>
@ -1588,8 +1588,8 @@
<attributes>
<JDURL>https://eclipse.dev/jetty/javadoc/jetty-12</JDURL>
<SRCDIR>${basedir}/..</SRCDIR>
<GITBROWSEURL>https://github.com/eclipse/jetty.project/tree/jetty-12.0.x</GITBROWSEURL>
<GITDOCURL>https://github.com/eclipse/jetty.project/tree/jetty-12.0.x/documentation/jetty-documentation/src/main/asciidoc</GITDOCURL>
<GITBROWSEURL>https://github.com/jetty/jetty.project/tree/jetty-12.0.x</GITBROWSEURL>
<GITDOCURL>https://github.com/jetty/jetty.project/tree/jetty-12.0.x/documentation/jetty-documentation/src/main/asciidoc</GITDOCURL>
<MVNCENTRAL>http://central.maven.org/maven2</MVNCENTRAL>
<VERSION>${project.version}</VERSION>
<TIMESTAMP>${maven.build.timestamp}</TIMESTAMP>