This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch 8.5.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/8.5.x by this push: new f02fe40 Implement same-site cookie header. Patch provided by John Kelly. f02fe40 is described below commit f02fe40ae481d73553d0d64d3e54f78d8cf8ad2c Author: Mark Thomas <ma...@apache.org> AuthorDate: Mon May 20 09:15:08 2019 +0100 Implement same-site cookie header. Patch provided by John Kelly. --- .../tomcat/util/http/CookieProcessorBase.java | 10 +++ .../tomcat/util/http/LegacyCookieProcessor.java | 8 ++ .../tomcat/util/http/LocalStrings.properties | 1 + .../tomcat/util/http/Rfc6265CookieProcessor.java | 7 ++ .../apache/tomcat/util/http/SameSiteCookies.java | 59 +++++++++++++ .../util/http/TestCookieProcessorGeneration.java | 49 +++++++++++ .../tomcat/util/http/TestSameSiteCookies.java | 97 ++++++++++++++++++++++ webapps/docs/changelog.xml | 4 + webapps/docs/config/cookie-processor.xml | 34 +++++++- 9 files changed, 267 insertions(+), 2 deletions(-) diff --git a/java/org/apache/tomcat/util/http/CookieProcessorBase.java b/java/org/apache/tomcat/util/http/CookieProcessorBase.java index dceb573..3cb5430 100644 --- a/java/org/apache/tomcat/util/http/CookieProcessorBase.java +++ b/java/org/apache/tomcat/util/http/CookieProcessorBase.java @@ -42,4 +42,14 @@ public abstract class CookieProcessorBase implements CookieProcessor { static { ANCIENT_DATE = COOKIE_DATE_FORMAT.get().format(new Date(10000)); } + + private SameSiteCookies sameSiteCookies = SameSiteCookies.NONE; + + public SameSiteCookies getSameSiteCookies() { + return sameSiteCookies; + } + + public void setSameSiteCookies(String sameSiteCookies) { + this.sameSiteCookies = SameSiteCookies.fromString(sameSiteCookies); + } } diff --git a/java/org/apache/tomcat/util/http/LegacyCookieProcessor.java b/java/org/apache/tomcat/util/http/LegacyCookieProcessor.java index 16cee6c..e792875 100644 --- a/java/org/apache/tomcat/util/http/LegacyCookieProcessor.java +++ b/java/org/apache/tomcat/util/http/LegacyCookieProcessor.java @@ -324,6 +324,14 @@ public final class LegacyCookieProcessor extends CookieProcessorBase { if (cookie.isHttpOnly()) { buf.append("; HttpOnly"); } + + SameSiteCookies sameSiteCookiesValue = getSameSiteCookies(); + + if (!sameSiteCookiesValue.equals(SameSiteCookies.NONE)) { + buf.append("; SameSite="); + buf.append(sameSiteCookiesValue.getValue()); + } + return buf.toString(); } diff --git a/java/org/apache/tomcat/util/http/LocalStrings.properties b/java/org/apache/tomcat/util/http/LocalStrings.properties index 4b91257..5eab413 100644 --- a/java/org/apache/tomcat/util/http/LocalStrings.properties +++ b/java/org/apache/tomcat/util/http/LocalStrings.properties @@ -26,6 +26,7 @@ parameters.noequal=Parameter starting at position [{0}] and ending at position [ parameters.fallToDebug=\n Note: further occurrences of Parameter errors will be logged at DEBUG level. cookies.invalidCookieToken=Cookies: Invalid cookie. Value not a token or quoted value +cookies.invalidSameSiteCookies=Unknown setting [{0}], must be one of: none, lax, strict. Default value is none. cookies.invalidSpecial=Cookies: Unknown Special Cookie cookies.fallToDebug=\n Note: further occurrences of Cookie errors will be logged at DEBUG level. cookies.maxCountFail=More than the maximum allowed number of cookies, [{0}], were detected. diff --git a/java/org/apache/tomcat/util/http/Rfc6265CookieProcessor.java b/java/org/apache/tomcat/util/http/Rfc6265CookieProcessor.java index a0e54f3..0d81a9b 100644 --- a/java/org/apache/tomcat/util/http/Rfc6265CookieProcessor.java +++ b/java/org/apache/tomcat/util/http/Rfc6265CookieProcessor.java @@ -162,6 +162,13 @@ public class Rfc6265CookieProcessor extends CookieProcessorBase { header.append("; HttpOnly"); } + SameSiteCookies sameSiteCookiesValue = getSameSiteCookies(); + + if (!sameSiteCookiesValue.equals(SameSiteCookies.NONE)) { + header.append("; SameSite="); + header.append(sameSiteCookiesValue.getValue()); + } + return header.toString(); } diff --git a/java/org/apache/tomcat/util/http/SameSiteCookies.java b/java/org/apache/tomcat/util/http/SameSiteCookies.java new file mode 100644 index 0000000..c79fbc1 --- /dev/null +++ b/java/org/apache/tomcat/util/http/SameSiteCookies.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tomcat.util.http; + +import org.apache.tomcat.util.res.StringManager; + +public enum SameSiteCookies { + + /** + * Don't set the SameSite cookie attribute. Cookie is always sent + */ + NONE("None"), + + /** + * Cookie is only sent on same-site requests and cross-site top level navigation GET requests + */ + LAX("Lax"), + + /** + * Prevents the cookie from being sent by the browser in all cross-site requests + */ + STRICT("Strict"); + + private static final StringManager sm = StringManager.getManager(SameSiteCookies.class); + + private final String value; + + SameSiteCookies(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public static SameSiteCookies fromString(String value) { + for (SameSiteCookies sameSiteCookies : SameSiteCookies.values()) { + if (sameSiteCookies.getValue().equalsIgnoreCase(value)) { + return sameSiteCookies; + } + } + + throw new IllegalStateException(sm.getString("cookies.invalidSameSiteCookies", value)); + } +} diff --git a/test/org/apache/tomcat/util/http/TestCookieProcessorGeneration.java b/test/org/apache/tomcat/util/http/TestCookieProcessorGeneration.java index f8a1d58..96bc238 100644 --- a/test/org/apache/tomcat/util/http/TestCookieProcessorGeneration.java +++ b/test/org/apache/tomcat/util/http/TestCookieProcessorGeneration.java @@ -254,6 +254,55 @@ public class TestCookieProcessorGeneration { doV1TestPath("exa\tmple", "foo=bar; Version=1; Path=\"exa\tmple\"", null); } + @Test + public void testSameSiteCookies() { + LegacyCookieProcessor legacy = new LegacyCookieProcessor(); + Rfc6265CookieProcessor rfc6265 = new Rfc6265CookieProcessor(); + + Cookie cookie = new Cookie("foo", "bar"); + + Assert.assertEquals("foo=bar", legacy.generateHeader(cookie)); + Assert.assertEquals("foo=bar", rfc6265.generateHeader(cookie)); + + legacy.setSameSiteCookies("none"); + rfc6265.setSameSiteCookies("none"); + + Assert.assertEquals("foo=bar", legacy.generateHeader(cookie)); + Assert.assertEquals("foo=bar", rfc6265.generateHeader(cookie)); + + legacy.setSameSiteCookies("lax"); + rfc6265.setSameSiteCookies("lax"); + + Assert.assertEquals("foo=bar; SameSite=Lax", legacy.generateHeader(cookie)); + Assert.assertEquals("foo=bar; SameSite=Lax", rfc6265.generateHeader(cookie)); + + legacy.setSameSiteCookies("strict"); + rfc6265.setSameSiteCookies("strict"); + + Assert.assertEquals("foo=bar; SameSite=Strict", legacy.generateHeader(cookie)); + Assert.assertEquals("foo=bar; SameSite=Strict", rfc6265.generateHeader(cookie)); + + cookie.setSecure(true); + cookie.setHttpOnly(true); + + legacy.setSameSiteCookies("none"); + rfc6265.setSameSiteCookies("none"); + + Assert.assertEquals("foo=bar; Secure; HttpOnly", legacy.generateHeader(cookie)); + Assert.assertEquals("foo=bar; Secure; HttpOnly", rfc6265.generateHeader(cookie)); + + legacy.setSameSiteCookies("lax"); + rfc6265.setSameSiteCookies("lax"); + + Assert.assertEquals("foo=bar; Secure; HttpOnly; SameSite=Lax", legacy.generateHeader(cookie)); + Assert.assertEquals("foo=bar; Secure; HttpOnly; SameSite=Lax", rfc6265.generateHeader(cookie)); + + legacy.setSameSiteCookies("strict"); + rfc6265.setSameSiteCookies("strict"); + + Assert.assertEquals("foo=bar; Secure; HttpOnly; SameSite=Strict", legacy.generateHeader(cookie)); + Assert.assertEquals("foo=bar; Secure; HttpOnly; SameSite=Strict", rfc6265.generateHeader(cookie)); + } private void doTest(Cookie cookie, String expected) { doTest(cookie, expected, expected); diff --git a/test/org/apache/tomcat/util/http/TestSameSiteCookies.java b/test/org/apache/tomcat/util/http/TestSameSiteCookies.java new file mode 100644 index 0000000..60cc3a8 --- /dev/null +++ b/test/org/apache/tomcat/util/http/TestSameSiteCookies.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tomcat.util.http; + +import org.junit.Assert; +import org.junit.Test; + + +public class TestSameSiteCookies { + + @Test + public void testNone() { + SameSiteCookies attribute = SameSiteCookies.NONE; + + Assert.assertEquals("None", attribute.getValue()); + Assert.assertEquals(SameSiteCookies.NONE, attribute); + + Assert.assertNotEquals(SameSiteCookies.LAX, attribute); + Assert.assertNotEquals(SameSiteCookies.STRICT, attribute); + } + + @Test + public void testLax() { + SameSiteCookies attribute = SameSiteCookies.LAX; + + Assert.assertEquals("Lax", attribute.getValue()); + Assert.assertEquals(SameSiteCookies.LAX, attribute); + + Assert.assertNotEquals(SameSiteCookies.NONE, attribute); + Assert.assertNotEquals(SameSiteCookies.STRICT, attribute); + } + + @Test + public void testStrict() { + SameSiteCookies attribute = SameSiteCookies.STRICT; + + Assert.assertEquals("Strict", attribute.getValue()); + Assert.assertEquals(SameSiteCookies.STRICT, attribute); + + Assert.assertNotEquals(SameSiteCookies.NONE, attribute); + Assert.assertNotEquals(SameSiteCookies.LAX, attribute); + } + + @Test + public void testToValidAttribute() { + Assert.assertEquals(SameSiteCookies.fromString("none"), SameSiteCookies.NONE); + Assert.assertEquals(SameSiteCookies.fromString("None"), SameSiteCookies.NONE); + Assert.assertEquals(SameSiteCookies.fromString("NONE"), SameSiteCookies.NONE); + + Assert.assertEquals(SameSiteCookies.fromString("lax"), SameSiteCookies.LAX); + Assert.assertEquals(SameSiteCookies.fromString("Lax"), SameSiteCookies.LAX); + Assert.assertEquals(SameSiteCookies.fromString("LAX"), SameSiteCookies.LAX); + + Assert.assertEquals(SameSiteCookies.fromString("strict"), SameSiteCookies.STRICT); + Assert.assertEquals(SameSiteCookies.fromString("Strict"), SameSiteCookies.STRICT); + Assert.assertEquals(SameSiteCookies.fromString("STRICT"), SameSiteCookies.STRICT); + } + + @Test(expected = IllegalStateException.class) + public void testToInvalidAttribute01() { + SameSiteCookies.fromString(""); + } + + @Test(expected = IllegalStateException.class) + public void testToInvalidAttribute02() { + SameSiteCookies.fromString(" "); + } + + @Test(expected = IllegalStateException.class) + public void testToInvalidAttribute03() { + SameSiteCookies.fromString("Strict1"); + } + + @Test(expected = IllegalStateException.class) + public void testToInvalidAttribute04() { + SameSiteCookies.fromString("foo"); + } + + @Test(expected = IllegalStateException.class) + public void testToInvalidAttribute05() { + SameSiteCookies.fromString("Lax "); + } +} \ No newline at end of file diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index de11eb5..f016b00 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -87,6 +87,10 @@ Reduce the default HTTP/2 header list size from 4GB to 32kB to align with typical HTTP/2 implementations. (markt) </update> + <add> + Add support for same-site cookie attribute. Patch provided by John + Kelly. (markt) + </add> </changelog> </subsection> <subsection name="Cluster"> diff --git a/webapps/docs/config/cookie-processor.xml b/webapps/docs/config/cookie-processor.xml index f671283..f2d2e9c 100644 --- a/webapps/docs/config/cookie-processor.xml +++ b/webapps/docs/config/cookie-processor.xml @@ -94,8 +94,25 @@ <li>The cookie header is always preserved.</li> </ul> - <p>No additional attributes are supported by the <strong>RFC 6265 Cookie - Processor</strong>.</p> + <p>The <strong>RFC 6265 Cookie Processor</strong> supports the following + additional attributes.</p> + + <attributes> + + <attribute name="sameSiteCookies" required="false"> + <p>Enables setting same-site cookie attribute.</p> + + <p>If value is <code>none</code> then the same-site cookie attribute + won't be set. This is the default value.</p> + + <p>If value is <code>lax</code> then the browser only sends the cookie + in same-site requests and cross-site top level GET requests.</p> + + <p>If value is <code>strict</code> then the browser prevents sending the + cookie in any cross-site request.</p> + </attribute> + + </attributes> </subsection> @@ -154,6 +171,19 @@ </p> </attribute> + <attribute name="sameSiteCookies" required="false"> + <p>Enables setting same-site cookie attribute.</p> + + <p>If value is <code>none</code> then the same-site cookie attribute + won't be set. This is the default value.</p> + + <p>If value is <code>lax</code> then the browser only sends the cookie + in same-site requests and cross-site top level GET requests.</p> + + <p>If value is <code>strict</code> then the browser prevents sending the + cookie in any cross-site request.</p> + </attribute> + </attributes> </subsection> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org