diff --git a/src/main/asciidoc/enums.adoc b/src/main/asciidoc/enums.adoc index f201c5f35..6794a434c 100644 --- a/src/main/asciidoc/enums.adoc +++ b/src/main/asciidoc/enums.adoc @@ -26,6 +26,32 @@ Require client to present authentication, if not presented then negotiations wil +++ |=== +[[CookieSameSite]] +== CookieSameSite + +++++ + Represents the Cookie SameSite policy to be used. For more info https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#SameSite_cookies. +++++ +''' + +[cols=">25%,75%"] +[frame="topbot"] +|=== +^|Name | Description +|[[NONE]]`NONE`|+++ +The browser will send cookies with both cross-site requests and same-site requests. ++++ +|[[STRICT]]`STRICT`|+++ +The browser will only send cookies for same-site requests (requests originating from the site that set the cookie). + If the request originated from a different URL than the URL of the current location, none of the cookies tagged + with the Strict attribute will be included. ++++ +|[[LAX]]`LAX`|+++ +Same-site cookies are withheld on cross-site subrequests, such as calls to load images or frames, but will be sent + when a user navigates to the URL from an external site; for example, by following a link. ++++ +|=== + [[DnsResponseCode]] == DnsResponseCode diff --git a/src/main/asciidoc/http.adoc b/src/main/asciidoc/http.adoc index 96cc36b3f..ca0204e65 100644 --- a/src/main/asciidoc/http.adoc +++ b/src/main/asciidoc/http.adoc @@ -349,6 +349,19 @@ browser can store them. Cookies are described by instances of {@link io.vertx.core.http.Cookie}. This allows you to retrieve the name, value, domain, path and other normal cookie properties. +Same Site Cookies let servers require that a cookie shouldn't be sent with cross-site (where Site is defined by the +registrable domain) requests, which provides some protection against cross-site request forgery attacks. This kind +of cookies are enabled using the setter: {@link io.vertx.core.http.Cookie#setSameSite(CookieSameSite)}. + +Same site cookies can have one of 3 values: + +* None - The browser will send cookies with both cross-site requests and same-site requests. +* Strict - he browser will only send cookies for same-site requests (requests originating from the site that set the + cookie). If the request originated from a different URL than the URL of the current location, none of the cookies + tagged with the Strict attribute will be included. +* Lax - Same-site cookies are withheld on cross-site subrequests, such as calls to load images or frames, but will be + sent when a user navigates to the URL from an external site; for example, by following a link. + Here's an example of querying and adding cookies: [source,$lang] diff --git a/src/main/java/io/vertx/core/http/Cookie.java b/src/main/java/io/vertx/core/http/Cookie.java index d290c098a..5603fbb3c 100644 --- a/src/main/java/io/vertx/core/http/Cookie.java +++ b/src/main/java/io/vertx/core/http/Cookie.java @@ -118,6 +118,15 @@ public interface Cookie { @Fluent Cookie setHttpOnly(boolean httpOnly); + /** + * Sets the same site of this cookie. + * + * @param policy The policy should be one of {@link CookieSameSite}. + * @return a reference to this, so the API can be used fluently + */ + @Fluent + Cookie setSameSite(CookieSameSite policy); + /** * Encode the cookie to a string. This is what is used in the Set-Cookie header * diff --git a/src/main/java/io/vertx/core/http/CookieSameSite.java b/src/main/java/io/vertx/core/http/CookieSameSite.java new file mode 100644 index 000000000..8aeb1dd04 --- /dev/null +++ b/src/main/java/io/vertx/core/http/CookieSameSite.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.http; + +import io.vertx.codegen.annotations.VertxGen; + +/** + * Represents the Cookie SameSite policy to be used. For more info https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#SameSite_cookies. + * + * @author Paulo Lopes + */ +@VertxGen +public enum CookieSameSite { + + /** + * The browser will send cookies with both cross-site requests and same-site requests. + */ + NONE("None"), + + /** + * The browser will only send cookies for same-site requests (requests originating from the site that set the cookie). + * If the request originated from a different URL than the URL of the current location, none of the cookies tagged + * with the Strict attribute will be included. + */ + STRICT("Strict"), + + /** + * Same-site cookies are withheld on cross-site subrequests, such as calls to load images or frames, but will be sent + * when a user navigates to the URL from an external site; for example, by following a link. + */ + LAX("Lax"); + + /** + * Just use a human friendly label instead of the capitalized name. + */ + private final String label; + + CookieSameSite(String label) { + this.label = label; + } + + @Override + public String toString() { + return label; + } +} diff --git a/src/main/java/io/vertx/core/http/impl/CookieImpl.java b/src/main/java/io/vertx/core/http/impl/CookieImpl.java index 4ba08d4df..649c4cb6d 100644 --- a/src/main/java/io/vertx/core/http/impl/CookieImpl.java +++ b/src/main/java/io/vertx/core/http/impl/CookieImpl.java @@ -15,6 +15,7 @@ import io.netty.handler.codec.http.cookie.DefaultCookie; import io.netty.handler.codec.http.cookie.ServerCookieDecoder; import io.netty.handler.codec.http.cookie.ServerCookieEncoder; import io.vertx.core.http.Cookie; +import io.vertx.core.http.CookieSameSite; import java.util.HashMap; import java.util.Map; @@ -63,6 +64,8 @@ public class CookieImpl implements ServerCookie { private final io.netty.handler.codec.http.cookie.Cookie nettyCookie; private boolean changed; private boolean fromUserAgent; + // extension features + private CookieSameSite sameSite; public CookieImpl(String name, String value) { this.nettyCookie = new DefaultCookie(name, value); @@ -136,9 +139,20 @@ public class CookieImpl implements ServerCookie { return this; } + @Override + public Cookie setSameSite(final CookieSameSite sameSite) { + this.sameSite = sameSite; + this.changed = true; + return this; + } + @Override public String encode() { - return ServerCookieEncoder.STRICT.encode(nettyCookie); + if (sameSite != null) { + return ServerCookieEncoder.STRICT.encode(nettyCookie) + "; SameSite=" + sameSite.toString(); + } else { + return ServerCookieEncoder.STRICT.encode(nettyCookie); + } } public boolean isChanged() { diff --git a/src/test/java/io/vertx/core/http/HttpTest.java b/src/test/java/io/vertx/core/http/HttpTest.java index 83503c789..afdc1da76 100644 --- a/src/test/java/io/vertx/core/http/HttpTest.java +++ b/src/test/java/io/vertx/core/http/HttpTest.java @@ -5656,6 +5656,37 @@ public abstract class HttpTest extends HttpTestBase { assertEquals("foo=bar; Path=/somepath; Domain=foo.com; Secure; HTTPOnly", cookie.encode()); } + @Test + public void testCookieSameSiteFieldEncoding() throws Exception { + Cookie cookie = Cookie.cookie("foo", "bar").setSameSite(CookieSameSite.LAX); + assertEquals("foo", cookie.getName()); + assertEquals("bar", cookie.getValue()); + assertEquals("foo=bar; SameSite=Lax", cookie.encode()); + + cookie.setSecure(true); + assertEquals("foo=bar; Secure; SameSite=Lax", cookie.encode()); + cookie.setHttpOnly(true); + assertEquals("foo=bar; Secure; HTTPOnly; SameSite=Lax", cookie.encode()); + } + + @Test + public void testCookieSameSiteFieldValidation() throws Exception { + Cookie cookie = Cookie.cookie("foo", "bar"); + + try { + cookie.setSameSite(CookieSameSite.LAX); + // OK + cookie.setSameSite(CookieSameSite.STRICT); + // OK + cookie.setSameSite(CookieSameSite.NONE); + // OK + cookie.setSameSite(null); + // OK + } catch (RuntimeException e) { + fail(); + } + } + @Test public void testRemoveCookies() throws Exception { testCookies("foo=bar", req -> {