From a90dfc52bb3ce358d68b995a5279503cf98594ef Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Thu, 11 Apr 2024 09:26:56 +0200 Subject: [PATCH 01/14] always add Cross-Origin-Embedder-Policy header, unless already set alternative implementation to #15063 - please review --- app/controllers/Plan.scala | 2 +- app/controllers/RelayRound.scala | 4 +++- app/http/HttpFilter.scala | 8 +++++++- app/http/ResponseHeaders.scala | 15 +++++++++++++++ modules/common/src/main/HTTPRequest.scala | 3 +++ 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/app/controllers/Plan.scala b/app/controllers/Plan.scala index 18d2c38612595..3beb8fccb2313 100644 --- a/app/controllers/Plan.scala +++ b/app/controllers/Plan.scala @@ -72,7 +72,7 @@ final class Plan(env: Env) extends LilaController(env): bestIds = bestIds, pricing = pricing ) - yield Ok(page) + yield Ok(page).withHeaders(embedderPolicy.default*) private def indexStripePatron(patron: lila.plan.Patron, customer: StripeCustomer)(using ctx: Context, diff --git a/app/controllers/RelayRound.scala b/app/controllers/RelayRound.scala index 26c9cf99863b4..e53f73debfe90 100644 --- a/app/controllers/RelayRound.scala +++ b/app/controllers/RelayRound.scala @@ -220,7 +220,9 @@ final class RelayRound( page <- renderPage: html.relay.show(rt.withStudy(sc.study), data, chat, sVersion, crossSiteIsolation) _ = if HTTPRequest.isHuman(req) then lila.mon.http.path(rt.tour.path).increment() - yield if crossSiteIsolation then Ok(page).enforceCrossSiteIsolation else Ok(page) + yield + if crossSiteIsolation then Ok(page).enforceCrossSiteIsolation + else Ok(page).withHeaders(embedderPolicy.default*) )( studyC.privateUnauthorizedFu(oldSc.study), studyC.privateForbiddenFu(oldSc.study) diff --git a/app/http/HttpFilter.scala b/app/http/HttpFilter.scala index c427c864ee211..8d42b489264cc 100644 --- a/app/http/HttpFilter.scala +++ b/app/http/HttpFilter.scala @@ -21,7 +21,8 @@ final class HttpFilter(env: Env)(using val mat: Materializer)(using Executor) handle(req).map: result => monitoring(req, startTime): addContextualResponseHeaders(req): - result + addEmbedderPolicyHeaders(req): + result } private def monitoring(req: RequestHeader, startTime: Long)(result: Result) = @@ -52,3 +53,8 @@ final class HttpFilter(env: Env)(using val mat: Materializer)(using Executor) if HTTPRequest.isApiOrApp(req) then result.withHeaders(headersForApiOrApp(using req)*) else result.withHeaders(permissionsPolicyHeader) + + private def addEmbedderPolicyHeaders(req: RequestHeader)(result: Result) = + if !embedderPolicy.isSet(result) && HTTPRequest.supportsCoepCredentialless(req) + then result.withHeaders(embedderPolicy.credentialless*) + else result diff --git a/app/http/ResponseHeaders.scala b/app/http/ResponseHeaders.scala index d523078c1a799..9a3c5ccad494d 100644 --- a/app/http/ResponseHeaders.scala +++ b/app/http/ResponseHeaders.scala @@ -59,3 +59,18 @@ trait ResponseHeaders extends HeaderNames: def asAttachmentStream(name: String)(res: Result) = noProxyBuffer(asAttachment(name)(res)) def lastModified(date: Instant) = LAST_MODIFIED -> date.atZone(utcZone) + + object embedderPolicy: + + def isSet(result: Result) = result.header.headers.contains(embedderPolicyHeader).pp("is set") + + def default = headers("unsafe-none") + def credentialless = headers("credentialless") + + private val openerPolicyHeader = "Cross-Origin-Opener-Policy" + private val embedderPolicyHeader = "Cross-Origin-Embedder-Policy" + + private def headers(policy: "credentialless" | "require-corp" | "unsafe-none") = List( + openerPolicyHeader -> "same-origin", + embedderPolicyHeader -> policy + ) diff --git a/modules/common/src/main/HTTPRequest.scala b/modules/common/src/main/HTTPRequest.scala index 0dc2bdee8bded..c20e25e5f24a2 100644 --- a/modules/common/src/main/HTTPRequest.scala +++ b/modules/common/src/main/HTTPRequest.scala @@ -52,6 +52,9 @@ object HTTPRequest: def isAndroid = UaMatcher("Android") def isLitools(req: RequestHeader) = userAgent(req).has(UserAgent("litools")) + def supportsCoepCredentialless(req: RequestHeader) = + isChrome96Plus(req) || (isFirefox119Plus(req) && !isMobileBrowser(req)) + def origin(req: RequestHeader): Option[String] = req.headers.get(HeaderNames.ORIGIN) def referer(req: RequestHeader): Option[String] = req.headers.get(HeaderNames.REFERER) From 2a2d154c0bd2a843c2d97d3f6d73189a6c15d2e9 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Thu, 11 Apr 2024 09:32:38 +0200 Subject: [PATCH 02/14] remove code duplication --- app/http/CtrlExtensions.scala | 12 ++---------- app/http/ResponseHeaders.scala | 10 +++++++++- modules/common/src/main/HTTPRequest.scala | 3 --- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/app/http/CtrlExtensions.scala b/app/http/CtrlExtensions.scala index c3d668f5282af..5678bf5a9d031 100644 --- a/app/http/CtrlExtensions.scala +++ b/app/http/CtrlExtensions.scala @@ -5,7 +5,7 @@ import play.api.mvc.* import lila.common.HTTPRequest -trait CtrlExtensions extends ControllerHelpers: +trait CtrlExtensions extends ControllerHelpers with ResponseHeaders: val env: Env @@ -26,15 +26,7 @@ trait CtrlExtensions extends ControllerHelpers: result.withHeaders(LINK -> s"<${env.net.baseUrl}${url}>; rel=\"canonical\"") def withCanonical(url: Call): Result = withCanonical(url.url) def enforceCrossSiteIsolation(using req: RequestHeader): Result = - val coep = - if HTTPRequest.isChrome96Plus(req) || - (HTTPRequest.isFirefox119Plus(req) && !HTTPRequest.isMobileBrowser(req)) - then "credentialless" - else "require-corp" - result.withHeaders( - "Cross-Origin-Embedder-Policy" -> coep, - "Cross-Origin-Opener-Policy" -> "same-origin" - ) + result.withHeaders(embedderPolicy.forReq(req)*) def noCache: Result = result.withHeaders( CACHE_CONTROL -> "no-cache, no-store, must-revalidate", EXPIRES -> "0" diff --git a/app/http/ResponseHeaders.scala b/app/http/ResponseHeaders.scala index 9a3c5ccad494d..79c98697c5f52 100644 --- a/app/http/ResponseHeaders.scala +++ b/app/http/ResponseHeaders.scala @@ -62,10 +62,18 @@ trait ResponseHeaders extends HeaderNames: object embedderPolicy: - def isSet(result: Result) = result.header.headers.contains(embedderPolicyHeader).pp("is set") + def isSet(result: Result) = result.header.headers.contains(embedderPolicyHeader) + + def forReq(req: RequestHeader) = + if supportsCoepCredentialless(req) then credentialless else requireCorp + + def supportsCoepCredentialless(req: RequestHeader) = + import HTTPRequest.* + isChrome96Plus(req) || (isFirefox119Plus(req) && !isMobileBrowser(req)) def default = headers("unsafe-none") def credentialless = headers("credentialless") + def requireCorp = headers("require-corp") private val openerPolicyHeader = "Cross-Origin-Opener-Policy" private val embedderPolicyHeader = "Cross-Origin-Embedder-Policy" diff --git a/modules/common/src/main/HTTPRequest.scala b/modules/common/src/main/HTTPRequest.scala index c20e25e5f24a2..0dc2bdee8bded 100644 --- a/modules/common/src/main/HTTPRequest.scala +++ b/modules/common/src/main/HTTPRequest.scala @@ -52,9 +52,6 @@ object HTTPRequest: def isAndroid = UaMatcher("Android") def isLitools(req: RequestHeader) = userAgent(req).has(UserAgent("litools")) - def supportsCoepCredentialless(req: RequestHeader) = - isChrome96Plus(req) || (isFirefox119Plus(req) && !isMobileBrowser(req)) - def origin(req: RequestHeader): Option[String] = req.headers.get(HeaderNames.ORIGIN) def referer(req: RequestHeader): Option[String] = req.headers.get(HeaderNames.REFERER) From 3de4dda424165f12eefa2a4cb9ae9d7d55d812c9 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Thu, 11 Apr 2024 09:38:53 +0200 Subject: [PATCH 03/14] fix call to embedderPolicy.supportsCoepCredentialless --- app/http/HttpFilter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/http/HttpFilter.scala b/app/http/HttpFilter.scala index 8d42b489264cc..c206f36da67fb 100644 --- a/app/http/HttpFilter.scala +++ b/app/http/HttpFilter.scala @@ -55,6 +55,6 @@ final class HttpFilter(env: Env)(using val mat: Materializer)(using Executor) else result.withHeaders(permissionsPolicyHeader) private def addEmbedderPolicyHeaders(req: RequestHeader)(result: Result) = - if !embedderPolicy.isSet(result) && HTTPRequest.supportsCoepCredentialless(req) + if !embedderPolicy.isSet(result) && embedderPolicy.supportsCoepCredentialless(req) then result.withHeaders(embedderPolicy.credentialless*) else result From 33ed7818376e69ac54295b993c622bf5ae5885c2 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Sat, 13 Apr 2024 19:25:22 +0200 Subject: [PATCH 04/14] Update app/controllers/RelayRound.scala Co-authored-by: Jonathan Gamble <101470903+schlawg@users.noreply.github.com> --- app/controllers/RelayRound.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/RelayRound.scala b/app/controllers/RelayRound.scala index e53f73debfe90..4465f7227dedd 100644 --- a/app/controllers/RelayRound.scala +++ b/app/controllers/RelayRound.scala @@ -222,7 +222,7 @@ final class RelayRound( _ = if HTTPRequest.isHuman(req) then lila.mon.http.path(rt.tour.path).increment() yield if crossSiteIsolation then Ok(page).enforceCrossSiteIsolation - else Ok(page).withHeaders(embedderPolicy.default*) + else Ok(page).withHeaders(embedderPolicy.unsafe*) )( studyC.privateUnauthorizedFu(oldSc.study), studyC.privateForbiddenFu(oldSc.study) From 249dfeacd085142ee13cc3c9eed7562bd51f9c54 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Sat, 13 Apr 2024 19:25:56 +0200 Subject: [PATCH 05/14] Update app/http/ResponseHeaders.scala Co-authored-by: Jonathan Gamble <101470903+schlawg@users.noreply.github.com> --- app/http/ResponseHeaders.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/http/ResponseHeaders.scala b/app/http/ResponseHeaders.scala index 79c98697c5f52..42452e5ae74f2 100644 --- a/app/http/ResponseHeaders.scala +++ b/app/http/ResponseHeaders.scala @@ -79,6 +79,6 @@ trait ResponseHeaders extends HeaderNames: private val embedderPolicyHeader = "Cross-Origin-Embedder-Policy" private def headers(policy: "credentialless" | "require-corp" | "unsafe-none") = List( - openerPolicyHeader -> "same-origin", + openerPolicyHeader -> if policy == "unsafe-none" then policy else "same-origin", embedderPolicyHeader -> policy ) From d159aa8cd527898c93e7bdfad90987b63e10df03 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Sat, 13 Apr 2024 19:30:02 +0200 Subject: [PATCH 06/14] fix scala syntax --- modules/web/src/main/ResponseHeaders.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/web/src/main/ResponseHeaders.scala b/modules/web/src/main/ResponseHeaders.scala index 1fa4bec936456..fa73d9e77e86c 100644 --- a/modules/web/src/main/ResponseHeaders.scala +++ b/modules/web/src/main/ResponseHeaders.scala @@ -78,6 +78,6 @@ trait ResponseHeaders extends HeaderNames: private val embedderPolicyHeader = "Cross-Origin-Embedder-Policy" private def headers(policy: "credentialless" | "require-corp" | "unsafe-none") = List( - openerPolicyHeader -> if policy == "unsafe-none" then policy else "same-origin", + openerPolicyHeader -> (if policy == "unsafe-none" then policy else "same-origin"), embedderPolicyHeader -> policy ) From 27598d0ee5e97c92ea91b9fbf5e1333616d6f42b Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Sat, 13 Apr 2024 19:49:57 +0200 Subject: [PATCH 07/14] fix master merge --- app/controllers/Plan.scala | 2 +- modules/web/src/main/CtrlExtensions.scala | 2 +- modules/web/src/main/ResponseHeaders.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/Plan.scala b/app/controllers/Plan.scala index 3beb8fccb2313..df58abcfb3656 100644 --- a/app/controllers/Plan.scala +++ b/app/controllers/Plan.scala @@ -72,7 +72,7 @@ final class Plan(env: Env) extends LilaController(env): bestIds = bestIds, pricing = pricing ) - yield Ok(page).withHeaders(embedderPolicy.default*) + yield Ok(page).withHeaders(embedderPolicy.unsafe*) private def indexStripePatron(patron: lila.plan.Patron, customer: StripeCustomer)(using ctx: Context, diff --git a/modules/web/src/main/CtrlExtensions.scala b/modules/web/src/main/CtrlExtensions.scala index d7c02d25fe86e..127044a1e9a45 100644 --- a/modules/web/src/main/CtrlExtensions.scala +++ b/modules/web/src/main/CtrlExtensions.scala @@ -9,7 +9,7 @@ import lila.core.i18n.Translate import lila.core.perf.UserWithPerfs import lila.core.config.BaseUrl -trait CtrlExtensions extends play.api.mvc.ControllerHelpers: +trait CtrlExtensions extends play.api.mvc.ControllerHelpers with ResponseHeaders: def baseUrl: BaseUrl diff --git a/modules/web/src/main/ResponseHeaders.scala b/modules/web/src/main/ResponseHeaders.scala index fa73d9e77e86c..dce9bc3b2e49e 100644 --- a/modules/web/src/main/ResponseHeaders.scala +++ b/modules/web/src/main/ResponseHeaders.scala @@ -70,7 +70,7 @@ trait ResponseHeaders extends HeaderNames: import HTTPRequest.* isChrome96Plus(req) || (isFirefox119Plus(req) && !isMobileBrowser(req)) - def default = headers("unsafe-none") + def unsafe = headers("unsafe-none") def credentialless = headers("credentialless") def requireCorp = headers("require-corp") From 16ac6e2a75bf5240820d0797beb9fd08b9dd1b7d Mon Sep 17 00:00:00 2001 From: Benedikt Werner <1benediktwerner@gmail.com> Date: Sat, 13 Apr 2024 20:42:58 +0200 Subject: [PATCH 08/14] Allow coep:credentialless on broadcasts with embeds --- app/controllers/RelayRound.scala | 3 +-- ui/analyse/src/study/relay/videoPlayerView.ts | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/RelayRound.scala b/app/controllers/RelayRound.scala index 4465f7227dedd..b9c9c45b1eaaf 100644 --- a/app/controllers/RelayRound.scala +++ b/app/controllers/RelayRound.scala @@ -221,8 +221,7 @@ final class RelayRound( html.relay.show(rt.withStudy(sc.study), data, chat, sVersion, crossSiteIsolation) _ = if HTTPRequest.isHuman(req) then lila.mon.http.path(rt.tour.path).increment() yield - if crossSiteIsolation then Ok(page).enforceCrossSiteIsolation - else Ok(page).withHeaders(embedderPolicy.unsafe*) + if crossSiteIsolation then Ok(page).enforceCrossSiteIsolation else Ok(page) )( studyC.privateUnauthorizedFu(oldSc.study), studyC.privateForbiddenFu(oldSc.study) diff --git a/ui/analyse/src/study/relay/videoPlayerView.ts b/ui/analyse/src/study/relay/videoPlayerView.ts index ab46e859b075d..2883d3005359c 100644 --- a/ui/analyse/src/study/relay/videoPlayerView.ts +++ b/ui/analyse/src/study/relay/videoPlayerView.ts @@ -1,5 +1,5 @@ -import RelayCtrl from './relayCtrl'; import { looseH as h, Redraw, VNode } from 'common/snabbdom'; +import RelayCtrl from './relayCtrl'; import { allowVideo } from './relayView'; let player: VideoPlayer; @@ -43,6 +43,7 @@ class VideoPlayer { this.iframe.id = 'video-player'; this.iframe.src = relay.data.videoUrls![0]; this.iframe.allow = 'autoplay'; + this.iframe.setAttribute('credentialless', 'credentialless'); this.close = document.createElement('img'); this.close.src = site.asset.flairSrc('symbols.cancel'); this.close.className = 'video-player-close'; From 5a7ed73233152904fdbb732dd0bf6cc853ea80ae Mon Sep 17 00:00:00 2001 From: Benedikt Werner <1benediktwerner@gmail.com> Date: Sat, 13 Apr 2024 20:45:48 +0200 Subject: [PATCH 09/14] Only enable site-wide coep:credentialless in Chrome 113+ --- modules/web/src/main/HttpFilter.scala | 2 +- modules/web/src/main/ResponseHeaders.scala | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/web/src/main/HttpFilter.scala b/modules/web/src/main/HttpFilter.scala index ff8b6c3608e28..1fa93553a13de 100644 --- a/modules/web/src/main/HttpFilter.scala +++ b/modules/web/src/main/HttpFilter.scala @@ -57,6 +57,6 @@ final class HttpFilter(net: NetConfig, parseMobileUa: RequestHeader => Option[Li else result.withHeaders(permissionsPolicyHeader) private def addEmbedderPolicyHeaders(req: RequestHeader)(result: Result) = - if !embedderPolicy.isSet(result) && embedderPolicy.supportsCoepCredentialless(req) + if !embedderPolicy.isSet(result) && embedderPolicy.supportsCredentiallessIFrames(req) then result.withHeaders(embedderPolicy.credentialless*) else result diff --git a/modules/web/src/main/ResponseHeaders.scala b/modules/web/src/main/ResponseHeaders.scala index dce9bc3b2e49e..790a19267d362 100644 --- a/modules/web/src/main/ResponseHeaders.scala +++ b/modules/web/src/main/ResponseHeaders.scala @@ -70,6 +70,10 @@ trait ResponseHeaders extends HeaderNames: import HTTPRequest.* isChrome96Plus(req) || (isFirefox119Plus(req) && !isMobileBrowser(req)) + def supportsCredentiallessIFrames(req: RequestHeader) = + import HTTPRequest.* + isChrome113Plus(req) + def unsafe = headers("unsafe-none") def credentialless = headers("credentialless") def requireCorp = headers("require-corp") From 7c87d9337c3adf92b3ffdf9efabd0ab9deb5e2b5 Mon Sep 17 00:00:00 2001 From: Benedikt Werner <1benediktwerner@gmail.com> Date: Sat, 13 Apr 2024 20:52:28 +0200 Subject: [PATCH 10/14] Cleanup --- app/controllers/Plan.scala | 2 +- modules/web/src/main/CtrlExtensions.scala | 2 +- modules/web/src/main/HttpFilter.scala | 4 ++-- modules/web/src/main/ResponseHeaders.scala | 9 ++------- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/app/controllers/Plan.scala b/app/controllers/Plan.scala index df58abcfb3656..a8b13142cf778 100644 --- a/app/controllers/Plan.scala +++ b/app/controllers/Plan.scala @@ -72,7 +72,7 @@ final class Plan(env: Env) extends LilaController(env): bestIds = bestIds, pricing = pricing ) - yield Ok(page).withHeaders(embedderPolicy.unsafe*) + yield Ok(page).withHeaders(crossOriginPolicy.unsafe*) private def indexStripePatron(patron: lila.plan.Patron, customer: StripeCustomer)(using ctx: Context, diff --git a/modules/web/src/main/CtrlExtensions.scala b/modules/web/src/main/CtrlExtensions.scala index 127044a1e9a45..6bece5240937d 100644 --- a/modules/web/src/main/CtrlExtensions.scala +++ b/modules/web/src/main/CtrlExtensions.scala @@ -34,7 +34,7 @@ trait CtrlExtensions extends play.api.mvc.ControllerHelpers with ResponseHeaders result.withHeaders(LINK -> s"<${baseUrl}${url}>; rel=\"canonical\"") def withCanonical(url: Call): Result = withCanonical(url.url) def enforceCrossSiteIsolation(using req: RequestHeader): Result = - result.withHeaders(embedderPolicy.forReq(req)*) + result.withHeaders(crossOriginPolicy.forReq(req)*) def noCache: Result = result.withHeaders( CACHE_CONTROL -> "no-cache, no-store, must-revalidate", EXPIRES -> "0" diff --git a/modules/web/src/main/HttpFilter.scala b/modules/web/src/main/HttpFilter.scala index 1fa93553a13de..76ca72be1e1f8 100644 --- a/modules/web/src/main/HttpFilter.scala +++ b/modules/web/src/main/HttpFilter.scala @@ -57,6 +57,6 @@ final class HttpFilter(net: NetConfig, parseMobileUa: RequestHeader => Option[Li else result.withHeaders(permissionsPolicyHeader) private def addEmbedderPolicyHeaders(req: RequestHeader)(result: Result) = - if !embedderPolicy.isSet(result) && embedderPolicy.supportsCredentiallessIFrames(req) - then result.withHeaders(embedderPolicy.credentialless*) + if !crossOriginPolicy.isSet(result) && crossOriginPolicy.supportsCredentiallessIFrames(req) + then result.withHeaders(crossOriginPolicy.credentialless*) else result diff --git a/modules/web/src/main/ResponseHeaders.scala b/modules/web/src/main/ResponseHeaders.scala index 790a19267d362..39953e0e8a058 100644 --- a/modules/web/src/main/ResponseHeaders.scala +++ b/modules/web/src/main/ResponseHeaders.scala @@ -38,11 +38,6 @@ trait ResponseHeaders extends HeaderNames: "Cross-Origin-Embedder-Policy" -> "require-corp" // for Stockfish worker ) - val credentiallessHeaders = List( - "Cross-Origin-Opener-Policy" -> "same-origin", - "Cross-Origin-Embedder-Policy" -> "credentialless" - ) - val permissionsPolicyHeader = "Permissions-Policy" -> List( "screen-wake-lock=(self \"https://lichess1.org\")", @@ -59,7 +54,7 @@ trait ResponseHeaders extends HeaderNames: def lastModified(date: Instant) = LAST_MODIFIED -> date.atZone(utcZone) - object embedderPolicy: + object crossOriginPolicy: def isSet(result: Result) = result.header.headers.contains(embedderPolicyHeader) @@ -82,6 +77,6 @@ trait ResponseHeaders extends HeaderNames: private val embedderPolicyHeader = "Cross-Origin-Embedder-Policy" private def headers(policy: "credentialless" | "require-corp" | "unsafe-none") = List( - openerPolicyHeader -> (if policy == "unsafe-none" then policy else "same-origin"), + openerPolicyHeader -> (if policy == "unsafe-none" then "unsafe-none" else "same-origin"), embedderPolicyHeader -> policy ) From ff77e473f3c33b9f62fa2acc489c18d36d950249 Mon Sep 17 00:00:00 2001 From: Benedikt Werner <1benediktwerner@gmail.com> Date: Sat, 13 Apr 2024 21:08:50 +0200 Subject: [PATCH 11/14] Add setting to disable site-wide coep:credentialless --- modules/web/src/main/Env.scala | 5 +++++ modules/web/src/main/HttpFilter.scala | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/web/src/main/Env.scala b/modules/web/src/main/Env.scala index 4036e954080b4..a97034c6f84c7 100644 --- a/modules/web/src/main/Env.scala +++ b/modules/web/src/main/Env.scala @@ -65,3 +65,8 @@ final class Env( default = false, text = "Use external piece images".some ) + val sitewideCoepCredentiallessHeader = settingStore[Boolean]( + "sitewideCoepCredentiallessHeader", + default = true, + text = "Enable COEP:credentialless header site-wide in supported browsers (Chromium)".some + ) diff --git a/modules/web/src/main/HttpFilter.scala b/modules/web/src/main/HttpFilter.scala index 76ca72be1e1f8..8dd87cfc1ae36 100644 --- a/modules/web/src/main/HttpFilter.scala +++ b/modules/web/src/main/HttpFilter.scala @@ -7,7 +7,7 @@ import lila.common.HTTPRequest import lila.core.config.NetConfig import lila.core.net.LichessMobileUa -final class HttpFilter(net: NetConfig, parseMobileUa: RequestHeader => Option[LichessMobileUa])(using +final class HttpFilter(env: Env, net: NetConfig, parseMobileUa: RequestHeader => Option[LichessMobileUa])(using val mat: Materializer )(using Executor) extends Filter @@ -57,6 +57,6 @@ final class HttpFilter(net: NetConfig, parseMobileUa: RequestHeader => Option[Li else result.withHeaders(permissionsPolicyHeader) private def addEmbedderPolicyHeaders(req: RequestHeader)(result: Result) = - if !crossOriginPolicy.isSet(result) && crossOriginPolicy.supportsCredentiallessIFrames(req) + if !crossOriginPolicy.isSet(result) && crossOriginPolicy.supportsCredentiallessIFrames(req) && env.web.settings.sitewideCoepCredentiallessHeader.get() then result.withHeaders(crossOriginPolicy.credentialless*) else result From 2e6bff6864e75f238fb1e2d31c01cc89d5805192 Mon Sep 17 00:00:00 2001 From: Benedikt Werner <1benediktwerner@gmail.com> Date: Sat, 13 Apr 2024 21:21:03 +0200 Subject: [PATCH 12/14] Disable coep:credentialless on PayPal patron page --- app/controllers/Plan.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/Plan.scala b/app/controllers/Plan.scala index a8b13142cf778..7b3c243976ac6 100644 --- a/app/controllers/Plan.scala +++ b/app/controllers/Plan.scala @@ -98,6 +98,8 @@ final class Plan(env: Env) extends LilaController(env): ) = Ok.pageAsync: env.plan.api.giftsFrom(me).map { html.plan.indexPayPal(me, patron, sub, _) } + .map: + _.withHeaders(crossOriginPolicy.unsafe*) private def myCurrency(using ctx: Context): Currency = get("currency") From baa94f2f8b9da0a94d7943a2f7e122397def8322 Mon Sep 17 00:00:00 2001 From: Benedikt Werner <1benediktwerner@gmail.com> Date: Sat, 13 Apr 2024 22:03:58 +0200 Subject: [PATCH 13/14] Fix HttpFilter env --- app/LilaComponents.scala | 2 +- modules/web/src/main/HttpFilter.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/LilaComponents.scala b/app/LilaComponents.scala index eeedf3de5c581..a5f73ba152114 100644 --- a/app/LilaComponents.scala +++ b/app/LilaComponents.scala @@ -69,7 +69,7 @@ final class LilaComponents( lila.log("boot").info(s"Loaded lila modules in ${c.showDuration}") c.result - val httpFilters = Seq(lila.web.HttpFilter(env.net, lila.security.Mobile.LichessMobileUa.parse)) + val httpFilters = Seq(lila.web.HttpFilter(env.web, env.net, lila.security.Mobile.LichessMobileUa.parse)) override lazy val httpErrorHandler = lila.app.http.ErrorHandler( diff --git a/modules/web/src/main/HttpFilter.scala b/modules/web/src/main/HttpFilter.scala index 8dd87cfc1ae36..f553378528837 100644 --- a/modules/web/src/main/HttpFilter.scala +++ b/modules/web/src/main/HttpFilter.scala @@ -7,7 +7,7 @@ import lila.common.HTTPRequest import lila.core.config.NetConfig import lila.core.net.LichessMobileUa -final class HttpFilter(env: Env, net: NetConfig, parseMobileUa: RequestHeader => Option[LichessMobileUa])(using +final class HttpFilter(webEnv: Env, net: NetConfig, parseMobileUa: RequestHeader => Option[LichessMobileUa])(using val mat: Materializer )(using Executor) extends Filter @@ -57,6 +57,6 @@ final class HttpFilter(env: Env, net: NetConfig, parseMobileUa: RequestHeader => else result.withHeaders(permissionsPolicyHeader) private def addEmbedderPolicyHeaders(req: RequestHeader)(result: Result) = - if !crossOriginPolicy.isSet(result) && crossOriginPolicy.supportsCredentiallessIFrames(req) && env.web.settings.sitewideCoepCredentiallessHeader.get() + if !crossOriginPolicy.isSet(result) && crossOriginPolicy.supportsCredentiallessIFrames(req) && webEnv.settings.sitewideCoepCredentiallessHeader.get() then result.withHeaders(crossOriginPolicy.credentialless*) else result From cf71e63efd965cf160408aaab960305580724302 Mon Sep 17 00:00:00 2001 From: Benedikt Werner <1benediktwerner@gmail.com> Date: Sat, 13 Apr 2024 22:19:57 +0200 Subject: [PATCH 14/14] scalafmt --- app/controllers/Plan.scala | 2 +- app/controllers/RelayRound.scala | 3 +-- modules/web/src/main/HttpFilter.scala | 8 +++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/controllers/Plan.scala b/app/controllers/Plan.scala index 7b3c243976ac6..9b3b28753ab4c 100644 --- a/app/controllers/Plan.scala +++ b/app/controllers/Plan.scala @@ -99,7 +99,7 @@ final class Plan(env: Env) extends LilaController(env): Ok.pageAsync: env.plan.api.giftsFrom(me).map { html.plan.indexPayPal(me, patron, sub, _) } .map: - _.withHeaders(crossOriginPolicy.unsafe*) + _.withHeaders(crossOriginPolicy.unsafe*) private def myCurrency(using ctx: Context): Currency = get("currency") diff --git a/app/controllers/RelayRound.scala b/app/controllers/RelayRound.scala index b9c9c45b1eaaf..26c9cf99863b4 100644 --- a/app/controllers/RelayRound.scala +++ b/app/controllers/RelayRound.scala @@ -220,8 +220,7 @@ final class RelayRound( page <- renderPage: html.relay.show(rt.withStudy(sc.study), data, chat, sVersion, crossSiteIsolation) _ = if HTTPRequest.isHuman(req) then lila.mon.http.path(rt.tour.path).increment() - yield - if crossSiteIsolation then Ok(page).enforceCrossSiteIsolation else Ok(page) + yield if crossSiteIsolation then Ok(page).enforceCrossSiteIsolation else Ok(page) )( studyC.privateUnauthorizedFu(oldSc.study), studyC.privateForbiddenFu(oldSc.study) diff --git a/modules/web/src/main/HttpFilter.scala b/modules/web/src/main/HttpFilter.scala index f553378528837..c12ce98fd682a 100644 --- a/modules/web/src/main/HttpFilter.scala +++ b/modules/web/src/main/HttpFilter.scala @@ -7,8 +7,8 @@ import lila.common.HTTPRequest import lila.core.config.NetConfig import lila.core.net.LichessMobileUa -final class HttpFilter(webEnv: Env, net: NetConfig, parseMobileUa: RequestHeader => Option[LichessMobileUa])(using - val mat: Materializer +final class HttpFilter(webEnv: Env, net: NetConfig, parseMobileUa: RequestHeader => Option[LichessMobileUa])( + using val mat: Materializer )(using Executor) extends Filter with ResponseHeaders: @@ -57,6 +57,8 @@ final class HttpFilter(webEnv: Env, net: NetConfig, parseMobileUa: RequestHeader else result.withHeaders(permissionsPolicyHeader) private def addEmbedderPolicyHeaders(req: RequestHeader)(result: Result) = - if !crossOriginPolicy.isSet(result) && crossOriginPolicy.supportsCredentiallessIFrames(req) && webEnv.settings.sitewideCoepCredentiallessHeader.get() + if !crossOriginPolicy.isSet(result) + && crossOriginPolicy.supportsCredentiallessIFrames(req) + && webEnv.settings.sitewideCoepCredentiallessHeader.get() then result.withHeaders(crossOriginPolicy.credentialless*) else result