You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2019/05/20 08:15:38 UTC

[tomcat] branch 8.5.x updated: Implement same-site cookie header. Patch provided by John Kelly.

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-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org