You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by mg...@apache.org on 2020/08/27 11:59:34 UTC

[wicket] branch master updated: [WICKET-6805] Add Cross-Origin Opener Policy and Cross-Origin Embedder Policy support (#442)

This is an automated email from the ASF dual-hosted git repository.

mgrigorov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/wicket.git


The following commit(s) were added to refs/heads/master by this push:
     new 0f42d33  [WICKET-6805] Add Cross-Origin Opener Policy and Cross-Origin Embedder Policy support (#442)
0f42d33 is described below

commit 0f42d3314f1200a3b88f76e435910ef389af6b58
Author: Ecenaz Ozmen <eo...@columbia.edu>
AuthorDate: Thu Aug 27 14:59:26 2020 +0300

    [WICKET-6805] Add Cross-Origin Opener Policy and Cross-Origin Embedder Policy support (#442)
    
    * COOP and COEP Implementation  (#5)
    
    * Initial coop implementation
    
    * Fixed typo +reformatting code
    
    * Update wicket-core/src/main/java/org/apache/wicket/coop/CoopConfiguration.java
    
    Co-authored-by: Sal <sa...@gmail.com>
    
    * Update wicket-core/src/main/java/org/apache/wicket/coop/CoopConfiguration.java
    
    Co-authored-by: Sal <sa...@gmail.com>
    
    * Updates based on comments on the PR
    
    * Initial COEP implementation that doesn't handle report-to and setting up a reporting endpoint
    
    * Added javadocs and reformatted code
    
    * Fixed typo in javadoc
    
    * Updated valid values for COOP, same-origin-allow-popups instead of same-site
    
    * Made builder methods public so they can be called from init() in a sample app, added default values for builder fields to avoid null pointer exceptions
    
    * making exempted paths a HashSet for faster lookup
    
    * Using Set instead of HashSet in the declaration of exemptedPaths + reformatting code
    
    * Reformatting code to match Wicket's style
    
    * Indentation fix for CoepMode enum
    
    * Added tests for each COOP value, inlined url argument for checkHeaders in tests, formatted log statement to include path variable for exempted paths
    
    Co-authored-by: Sal <sa...@gmail.com>
    
    * Fixed typo in COEP debug statement
    
    * Refactoring configs into SecuritySettings for COOP and COEP
    
    * Renamed the request cycle listeners and tests with longer names of coop and coep, removed CoopConfiguration and CoepConfiguration files
    
    * Renamed securityInit() method to coopCoepInit()
    
    * Moved adding coop/coep listeners to validetInit in WebApplication
    
    * WICKET-6805 Formatting, cleanup and minor improvements
    
    Co-authored-by: Sal <sa...@gmail.com>
    Co-authored-by: Martin Tzvetanov Grigorov <mg...@apache.org>
---
 .../CrossOriginEmbedderPolicyConfiguration.java    | 109 ++++++++++++++++++
 ...ssOriginEmbedderPolicyRequestCycleListener.java |  94 ++++++++++++++++
 .../coop/CrossOriginOpenerPolicyConfiguration.java | 113 +++++++++++++++++++
 ...rossOriginOpenerPolicyRequestCycleListener.java |  90 +++++++++++++++
 .../wicket/protocol/http/WebApplication.java       |  27 +++++
 .../apache/wicket/settings/SecuritySettings.java   |  66 ++++++++++-
 ...iginEmbedderPolicyRequestCycleListenerTest.java | 113 +++++++++++++++++++
 ...OriginOpenerPolicyRequestCycleListenerTest.java | 122 +++++++++++++++++++++
 8 files changed, 733 insertions(+), 1 deletion(-)

diff --git a/wicket-core/src/main/java/org/apache/wicket/coep/CrossOriginEmbedderPolicyConfiguration.java b/wicket-core/src/main/java/org/apache/wicket/coep/CrossOriginEmbedderPolicyConfiguration.java
new file mode 100644
index 0000000..4094b74
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/coep/CrossOriginEmbedderPolicyConfiguration.java
@@ -0,0 +1,109 @@
+/*
+ * 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.wicket.coep;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.protocol.http.WebApplication;
+import org.apache.wicket.util.lang.Args;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Specifies the configuration for Cross-Origin Embedder Policy to be used for
+ * {@link CrossOriginEmbedderPolicyRequestCycleListener}. Users can specify the paths that should be exempt from COEP and
+ * one of 3 modes (<code>REPORTING, ENFORCING, DISABLED</code>) for the policy. The config object
+ * lives in {@link org.apache.wicket.settings.SecuritySettings}, users can specify their COOP
+ * preferences with the following lines in their application's {@link WebApplication#init()} method:
+ *
+ * <pre>
+ * &#064;Override
+ * protected void init()
+ * {
+ * 	// ...
+ * 	getSecuritySettings().setCrossOriginEmbedderPolicyConfiguration(CoepMode.REPORTING,
+ * 		"EXEMPTED PATHS");
+ * 	// ...
+ * }
+ * </pre>
+ *
+ * The config value will be read once at startup in {@link Application#initApplication()}, changing
+ * the configuration at runtime will have no effect of the COOP headers set.
+ * 
+ * @author Santiago Diaz - saldiaz@google.com
+ * @author Ecenaz Jen Ozmen - ecenazo@google.com
+ *
+ * @see CrossOriginEmbedderPolicyRequestCycleListener
+ * @see org.apache.wicket.settings.SecuritySettings
+ */
+public class CrossOriginEmbedderPolicyConfiguration
+{
+	public enum CoepMode
+	{
+		ENFORCING("Cross-Origin-Embedder-Policy"),
+		REPORTING("Cross-Origin-Embedder-Policy-Report-Only"),
+		DISABLED("");
+
+		final String header;
+
+		CoepMode(String header)
+		{
+			this.header = header;
+		}
+	}
+
+	private final Set<String> exemptions = new HashSet<>();
+	private final CoepMode mode;
+
+	public CrossOriginEmbedderPolicyConfiguration(CoepMode mode, String... exemptions)
+	{
+		this.exemptions.addAll(Arrays.asList(exemptions));
+		this.mode = Args.notNull(mode, "mode");
+	}
+
+	public CrossOriginEmbedderPolicyConfiguration(CoepMode mode)
+	{
+		this.mode = Args.notNull(mode, "mode");
+	}
+
+	public Set<String> getExemptions()
+	{
+		return exemptions;
+	}
+
+	public CoepMode getMode()
+	{
+		return mode;
+	}
+
+	public String getCoepHeader()
+	{
+		return mode.header;
+	}
+
+	public CrossOriginEmbedderPolicyConfiguration addExemptedPath(String path)
+	{
+		exemptions.add(path);
+		return this;
+	}
+
+	public boolean isEnabled()
+	{
+		return mode != CoepMode.DISABLED;
+	}
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/coep/CrossOriginEmbedderPolicyRequestCycleListener.java b/wicket-core/src/main/java/org/apache/wicket/coep/CrossOriginEmbedderPolicyRequestCycleListener.java
new file mode 100644
index 0000000..d2ecfa0
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/coep/CrossOriginEmbedderPolicyRequestCycleListener.java
@@ -0,0 +1,94 @@
+/*
+ * 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.wicket.coep;
+
+import org.apache.wicket.coop.CrossOriginOpenerPolicyRequestCycleListener;
+import org.apache.wicket.request.IRequestHandler;
+import org.apache.wicket.request.cycle.IRequestCycleListener;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.http.WebResponse;
+import org.apache.wicket.util.lang.Args;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Sets <a href="https://wicg.github.io/cross-origin-embedder-policy/">Cross-Origin Embedder
+ * Policy</a> (COEP) headers on the responses based on the mode specified by
+ * {@link CrossOriginEmbedderPolicyConfiguration}. COEP can be enabled in <code>REPORTING</code>
+ * mode which will set the headers as <code>Cross-Origin-Embedder-Policy-Report-Only</code> or
+ * <code>ENFORCING</code> mode which will set the header as
+ * <code>Cross-Origin-Embedder-Policy</code>. The header is not set for the paths that are exempted
+ * from COEP. The only valid value of COEP is <code>require-corp</code>, so if the listener is
+ * enabled the policy value will be specified as so.
+ *
+ * COEP prevents a document from loading any non-same-origin resources which don't explicitly grant
+ * the document permission to be loaded. Using COEP and COOP together allows developers to safely
+ * use powerful features such as <code>SharedArrayBuffer</code>,
+ * <code>performance.measureMemory()</code>, and the JS Self-Profiling API.See
+ * {@link CrossOriginOpenerPolicyRequestCycleListener} for instructions on how to enable COOP.
+ * Read more about cross-origin isolation on
+ * <a href="https://web.dev/why-coop-coep/">https://web.dev/why-coop-coep/</a>
+ *
+ * 
+ * @author Santiago Diaz - saldiaz@google.com
+ * @author Ecenaz Jen Ozmen - ecenazo@google.com
+ *
+ * @see CrossOriginEmbedderPolicyConfiguration
+ * @see org.apache.wicket.settings.SecuritySettings
+ */
+public class CrossOriginEmbedderPolicyRequestCycleListener implements IRequestCycleListener
+{
+	private static final Logger log = LoggerFactory.getLogger(CrossOriginEmbedderPolicyRequestCycleListener.class);
+
+	static final String REQUIRE_CORP = "require-corp";
+
+	private CrossOriginEmbedderPolicyConfiguration coepConfig;
+
+	public CrossOriginEmbedderPolicyRequestCycleListener(CrossOriginEmbedderPolicyConfiguration coepConfig)
+	{
+		this.coepConfig = Args.notNull(coepConfig, "coepConfig");
+	}
+
+	@Override
+	public void onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler)
+	{
+		final Object containerRequest = cycle.getRequest().getContainerRequest();
+		if (containerRequest instanceof HttpServletRequest)
+		{
+			HttpServletRequest request = (HttpServletRequest) containerRequest;
+			String path = request.getContextPath();
+			final String coepHeaderName = coepConfig.getCoepHeader();
+
+			if (coepConfig.getExemptions().contains(path))
+			{
+				log.debug("Request path {} is exempted from COEP, no '{}' header added", path, coepHeaderName);
+				return;
+			}
+
+			if (cycle.getResponse() instanceof WebResponse)
+			{
+				WebResponse webResponse = (WebResponse) cycle.getResponse();
+				if (webResponse.isHeaderSupported())
+				{
+					webResponse.setHeader(coepHeaderName, REQUIRE_CORP);
+				}
+			}
+		}
+	}
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/coop/CrossOriginOpenerPolicyConfiguration.java b/wicket-core/src/main/java/org/apache/wicket/coop/CrossOriginOpenerPolicyConfiguration.java
new file mode 100644
index 0000000..ff363bd
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/coop/CrossOriginOpenerPolicyConfiguration.java
@@ -0,0 +1,113 @@
+/*
+ * 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.wicket.coop;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.protocol.http.WebApplication;
+import org.apache.wicket.util.lang.Args;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Specifies the configuration for Cross-Origin Opener Policy to be used by
+ * {@link CrossOriginOpenerPolicyRequestCycleListener} when adding COOP headers. Users can specify the paths that
+ * should be exempt from COOP and one of 4 modes
+ * (<code>UNSAFE_NONE, SAME_ORIGIN, SAME_ORIGIN_ALLOW_POPUPS, DISABLED</code>) for the policy. The
+ * config object lives in {@link org.apache.wicket.settings.SecuritySettings}, users can specify
+ * their COOP preferences with the following lines in their application's
+ * {@link WebApplication#init()} method:
+ * 
+ * <pre>
+ * &#064;Override
+ * protected void init()
+ * {
+ * 	// ...
+ * 	getSecuritySettings().setCrossOriginOpenerPolicyConfiguration(CoopMode.SAME_ORIGIN,
+ * 		"EXEMPTED PATHS");
+ * 	// ...
+ * }
+ * </pre>
+ * 
+ * The config value will be read once at startup in {@link Application#initApplication()}, changing
+ * the configuration at runtime will have no effect of the COOP headers set.
+ *
+ * @author Santiago Diaz - saldiaz@google.com
+ * @author Ecenaz Jen Ozmen - ecenazo@google.com
+ *
+ * @see CrossOriginOpenerPolicyRequestCycleListener
+ * @see org.apache.wicket.settings.SecuritySettings
+ */
+public class CrossOriginOpenerPolicyConfiguration
+{
+	public enum CoopMode
+	{
+		UNSAFE_NONE("unsafe-none"),
+		SAME_ORIGIN("same-origin"),
+		SAME_ORIGIN_ALLOW_POPUPS("same-origin-allow-popups"),
+		DISABLED("");
+
+		final String keyword;
+
+		CoopMode(String keyword)
+		{
+			this.keyword = keyword;
+		}
+	}
+
+
+	private final Set<String> exemptions = new HashSet<>();
+	private final CoopMode mode;
+
+	public CrossOriginOpenerPolicyConfiguration(CoopMode mode, String... exemptions)
+	{
+		this.exemptions.addAll(Arrays.asList(exemptions));
+		this.mode = Args.notNull(mode, "mode");
+	}
+
+	public CrossOriginOpenerPolicyConfiguration(CoopMode mode)
+	{
+		this.mode = Args.notNull(mode, "mode");
+	}
+
+	public CrossOriginOpenerPolicyConfiguration addExemptedPath(String path)
+	{
+		exemptions.add(path);
+		return this;
+	}
+
+	public Set<String> getExemptions()
+	{
+		return exemptions;
+	}
+
+	public CoopMode getMode()
+	{
+		return mode;
+	}
+
+	public String getHeaderValue()
+	{
+		return mode.keyword;
+	}
+
+	public boolean isEnabled()
+	{
+		return mode != CoopMode.DISABLED;
+	}
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/coop/CrossOriginOpenerPolicyRequestCycleListener.java b/wicket-core/src/main/java/org/apache/wicket/coop/CrossOriginOpenerPolicyRequestCycleListener.java
new file mode 100644
index 0000000..7474796
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/coop/CrossOriginOpenerPolicyRequestCycleListener.java
@@ -0,0 +1,90 @@
+/*
+ * 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.wicket.coop;
+
+import org.apache.wicket.coep.CrossOriginEmbedderPolicyRequestCycleListener;
+import org.apache.wicket.request.IRequestHandler;
+import org.apache.wicket.request.cycle.IRequestCycleListener;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.http.WebResponse;
+import org.apache.wicket.util.lang.Args;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Sets <a href="https://github.com/whatwg/html/pull/5334/files">Cross-Origin Opener Policy</a>
+ * headers on the responses based on the policy specified by {@link CrossOriginOpenerPolicyConfiguration}. The header
+ * is not set for the paths that are exempted from COOP.
+ *
+ * COOP is a mitigation against cross-origin information leaks and is used to make websites
+ * cross-origin isolated. Setting the COOP header allows you to ensure that a top-level window is
+ * isolated from other documents by putting them in a different browsing context group, so they
+ * cannot directly interact with the top-level window. Using COEP and COOP together allows
+ * developers to safely use * powerful features such as <code>SharedArrayBuffer</code>,
+ * <code>performance.measureMemory()</code>, * and the JS Self-Profiling API.See
+ * {@link CrossOriginEmbedderPolicyRequestCycleListener} for instructions * on how to enable COOP.
+ * Read more about cross-origin isolation on
+ * <a href="https://web.dev/why-coop-coep/">https://web.dev/why-coop-coep/</a>
+ *
+ *
+ * @author Santiago Diaz - saldiaz@google.com
+ * @author Ecenaz Jen Ozmen - ecenazo@google.com
+ *
+ * @see CrossOriginOpenerPolicyConfiguration
+ * @see org.apache.wicket.settings.SecuritySettings
+ */
+public class CrossOriginOpenerPolicyRequestCycleListener implements IRequestCycleListener
+{
+	private static final Logger log = LoggerFactory.getLogger(CrossOriginOpenerPolicyRequestCycleListener.class);
+
+	static final String COOP_HEADER = "Cross-Origin-Opener-Policy";
+
+	private CrossOriginOpenerPolicyConfiguration coopConfig;
+
+	public CrossOriginOpenerPolicyRequestCycleListener(CrossOriginOpenerPolicyConfiguration coopConfig)
+	{
+		this.coopConfig = Args.notNull(coopConfig, "coopConfig");
+	}
+
+	@Override
+	public void onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler)
+	{
+		final Object containerRequest = cycle.getRequest().getContainerRequest();
+		if (containerRequest instanceof HttpServletRequest)
+		{
+			HttpServletRequest request = (HttpServletRequest) containerRequest;
+			String path = request.getContextPath();
+
+			if (coopConfig.getExemptions().contains(path))
+			{
+				log.debug("Request path {} is exempted from COOP, no {} header added", path, COOP_HEADER);
+				return;
+			}
+
+			if (cycle.getResponse() instanceof WebResponse)
+			{
+				WebResponse webResponse = (WebResponse) cycle.getResponse();
+				if (webResponse.isHeaderSupported())
+				{
+					webResponse.setHeader(COOP_HEADER, coopConfig.getHeaderValue());
+				}
+			}
+		}
+	}
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/protocol/http/WebApplication.java b/wicket-core/src/main/java/org/apache/wicket/protocol/http/WebApplication.java
index c682e715..22dcc71 100644
--- a/wicket-core/src/main/java/org/apache/wicket/protocol/http/WebApplication.java
+++ b/wicket-core/src/main/java/org/apache/wicket/protocol/http/WebApplication.java
@@ -37,6 +37,10 @@ import org.apache.wicket.WicketRuntimeException;
 import org.apache.wicket.ajax.AjaxRequestHandler;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.AjaxRequestTargetListenerCollection;
+import org.apache.wicket.coep.CrossOriginEmbedderPolicyConfiguration;
+import org.apache.wicket.coep.CrossOriginEmbedderPolicyRequestCycleListener;
+import org.apache.wicket.coop.CrossOriginOpenerPolicyConfiguration;
+import org.apache.wicket.coop.CrossOriginOpenerPolicyRequestCycleListener;
 import org.apache.wicket.core.request.mapper.IMapperContext;
 import org.apache.wicket.core.request.mapper.MountedMapper;
 import org.apache.wicket.core.request.mapper.PackageMapper;
@@ -773,6 +777,29 @@ public abstract class WebApplication extends Application
 		getCspSettings().blocking().strict();
 	}
 
+	@Override
+	protected void validateInit()
+	{
+		super.validateInit();
+
+		// enable coop and coep listeners if specified in security settings
+		CrossOriginOpenerPolicyConfiguration coopConfig = getSecuritySettings()
+			.getCrossOriginOpenerPolicyConfiguration();
+		if (coopConfig.isEnabled())
+		{
+			getRequestCycleListeners()
+				.add(new CrossOriginOpenerPolicyRequestCycleListener(coopConfig));
+		}
+
+		CrossOriginEmbedderPolicyConfiguration coepConfig = getSecuritySettings()
+			.getCrossOriginEmbedderPolicyConfiguration();
+		if (coepConfig.isEnabled())
+		{
+			getRequestCycleListeners()
+				.add(new CrossOriginEmbedderPolicyRequestCycleListener(coepConfig));
+		}
+	}
+
 	/**
 	 * set runtime configuration type
 	 * <p/>
diff --git a/wicket-core/src/main/java/org/apache/wicket/settings/SecuritySettings.java b/wicket-core/src/main/java/org/apache/wicket/settings/SecuritySettings.java
index 9556f98..a618ee2 100644
--- a/wicket-core/src/main/java/org/apache/wicket/settings/SecuritySettings.java
+++ b/wicket-core/src/main/java/org/apache/wicket/settings/SecuritySettings.java
@@ -24,6 +24,10 @@ import org.apache.wicket.authorization.IAuthorizationStrategy;
 import org.apache.wicket.authorization.IUnauthorizedComponentInstantiationListener;
 import org.apache.wicket.authorization.IUnauthorizedResourceRequestListener;
 import org.apache.wicket.authorization.UnauthorizedInstantiationException;
+import org.apache.wicket.coep.CrossOriginEmbedderPolicyConfiguration;
+import org.apache.wicket.coep.CrossOriginEmbedderPolicyConfiguration.CoepMode;
+import org.apache.wicket.coop.CrossOriginOpenerPolicyConfiguration;
+import org.apache.wicket.coop.CrossOriginOpenerPolicyConfiguration.CoopMode;
 import org.apache.wicket.core.random.DefaultSecureRandomSupplier;
 import org.apache.wicket.core.random.ISecureRandomSupplier;
 import org.apache.wicket.core.util.crypt.KeyInSessionSunJceCryptFactory;
@@ -57,7 +61,7 @@ public class SecuritySettings
 
 	/** factory for creating crypt objects */
 	private ICryptFactory cryptFactory;
-	
+
 	/** supplier of random data and SecureRandom */
 	private ISecureRandomSupplier randomSupplier = new DefaultSecureRandomSupplier();
 
@@ -69,6 +73,18 @@ public class SecuritySettings
 	 */
 	private boolean enforceMounts = false;
 
+	/**
+	 * Represents the configuration for Cross-Origin-Opener-Policy headers
+	 */
+	private CrossOriginOpenerPolicyConfiguration crossOriginOpenerPolicyConfiguration = new CrossOriginOpenerPolicyConfiguration(
+		CoopMode.SAME_ORIGIN);
+
+	/**
+	 * Represents the configuration for Cross-Origin-Embedder-Policy headers
+	 */
+	private CrossOriginEmbedderPolicyConfiguration crossOriginEmbedderPolicyConfiguration = new CrossOriginEmbedderPolicyConfiguration(
+		CoepMode.REPORTING);
+
 	/** Authorizer for component instantiations */
 	private static final IUnauthorizedComponentInstantiationListener DEFAULT_UNAUTHORIZED_COMPONENT_INSTANTIATION_LISTENER = new IUnauthorizedComponentInstantiationListener()
 	{
@@ -275,4 +291,52 @@ public class SecuritySettings
 		authenticationStrategy = strategy;
 		return this;
 	}
+
+	public CrossOriginOpenerPolicyConfiguration getCrossOriginOpenerPolicyConfiguration()
+	{
+		return crossOriginOpenerPolicyConfiguration;
+	}
+
+	/**
+	 * Sets the Cross-Origin Opener Policy's mode and exempted paths. The config values are only
+	 * read once at startup in Application#initApplication(), changing the config at runtime will have no effect
+	 *
+	 * @param mode
+	 *            CoopMode, one of the 4 values: UNSAFE_NONE, SAME_ORIGIN, SAME_ORIGIN_ALLOW_POPUPS, DISABLED
+	 * @param exemptions
+	 *            exempted paths for which COOP will be disabled
+	 * @return
+	 */
+	public SecuritySettings setCrossOriginOpenerPolicyConfiguration(
+		CoopMode mode, String... exemptions)
+	{
+		crossOriginOpenerPolicyConfiguration = new CrossOriginOpenerPolicyConfiguration(mode, exemptions);
+		return this;
+	}
+
+
+	public CrossOriginEmbedderPolicyConfiguration getCrossOriginEmbedderPolicyConfiguration()
+	{
+		return crossOriginEmbedderPolicyConfiguration;
+	}
+
+	/**
+	 * Sets the Cross-Origin Embedder Policy's mode and exempted paths. The config values are only
+	 * read once at startup in Application#initApplication(), changing the config at runtime will
+	 * have no effect
+	 * 
+	 * @param mode
+	 *            CoepMode, one of the 3 values: ENFORCING, REPORTING, DISABLED
+	 * @param exemptions
+	 *            exempted paths for which COEP will be disabled
+	 * @return
+	 */
+	public SecuritySettings setCrossOriginEmbedderPolicyConfiguration(CoepMode mode,
+		String... exemptions)
+	{
+		crossOriginEmbedderPolicyConfiguration = new CrossOriginEmbedderPolicyConfiguration(mode,
+			exemptions);
+		return this;
+	}
+
 }
diff --git a/wicket-core/src/test/java/org/apache/wicket/coep/CrossOriginEmbedderPolicyRequestCycleListenerTest.java b/wicket-core/src/test/java/org/apache/wicket/coep/CrossOriginEmbedderPolicyRequestCycleListenerTest.java
new file mode 100644
index 0000000..4e0ea79
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/coep/CrossOriginEmbedderPolicyRequestCycleListenerTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.wicket.coep;
+
+import org.apache.wicket.ThreadContext;
+import org.apache.wicket.mock.MockApplication;
+import org.apache.wicket.protocol.http.WebApplication;
+import org.apache.wicket.util.tester.WicketTestCase;
+import org.junit.jupiter.api.Test;
+
+import org.apache.wicket.coep.CrossOriginEmbedderPolicyConfiguration.CoepMode;
+
+import static org.apache.wicket.coep.CrossOriginEmbedderPolicyRequestCycleListener.REQUIRE_CORP;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+class CrossOriginEmbedderPolicyRequestCycleListenerTest extends WicketTestCase
+{
+	private CoepMode mode;
+	private String exemptions;
+
+	@Test
+	void testEnforcingCoepHeadersSetCorrectly()
+	{
+		mode = CoepMode.ENFORCING;
+		buildApp();
+		checkHeaders(CoepMode.ENFORCING);
+	}
+
+	@Test
+	void testReportingCoepHeadersSetCorrectly()
+	{
+		mode = CoepMode.REPORTING;
+		buildApp();
+		checkHeaders(CoepMode.REPORTING);
+	}
+
+	@Test
+	void testCoepDisabled()
+	{
+		mode = CoepMode.DISABLED;
+		buildApp();
+		tester.executeUrl("exempt");
+		String coepHeaderValue = tester.getLastResponse().getHeader(CoepMode.REPORTING.header);
+		assertNull(coepHeaderValue, "COOP header should be null on DISABLED");
+	}
+
+	@Test
+	void testCoepHeadersNotSetExemptedPath()
+	{
+		mode = CoepMode.DISABLED;
+		exemptions = "exempt";
+		buildApp();
+		tester.executeUrl("exempt");
+		String coepHeaderValue = tester.getLastResponse().getHeader(CoepMode.REPORTING.header);
+
+		assertNull(coepHeaderValue, "COOP header should be null on exempted path");
+	}
+
+	private void checkHeaders(CoepMode mode)
+	{
+		tester.executeUrl("/");
+		String coepHeaderValue = tester.getLastResponse().getHeader(mode.header);
+
+		assertNotNull(coepHeaderValue, "COEP " + mode + " header should not be null");
+
+		assertEquals(REQUIRE_CORP, coepHeaderValue, "Unexpected COEP header: " + coepHeaderValue);
+	}
+
+	@Override
+	protected WebApplication newApplication()
+	{
+		return new MockApplication()
+		{
+			@Override
+			protected void init()
+			{
+				super.init();
+				getSecuritySettings().setCrossOriginEmbedderPolicyConfiguration(mode, exemptions);
+			}
+		};
+	}
+
+	// overriding the commonBefore because we want to modify init behavior
+	// contents of commonBefore moved to buildApp, called after the coepMode / exemption set in every test
+	@Override
+	public void commonBefore()
+	{
+	}
+
+	private void buildApp()
+	{
+		ThreadContext.detach();
+
+		WebApplication application = newApplication();
+		tester = newWicketTester(application);
+	}
+}
diff --git a/wicket-core/src/test/java/org/apache/wicket/coop/CrossOriginOpenerPolicyRequestCycleListenerTest.java b/wicket-core/src/test/java/org/apache/wicket/coop/CrossOriginOpenerPolicyRequestCycleListenerTest.java
new file mode 100644
index 0000000..0cf544e
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/coop/CrossOriginOpenerPolicyRequestCycleListenerTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.wicket.coop;
+
+import org.apache.wicket.ThreadContext;
+import org.apache.wicket.mock.MockApplication;
+import org.apache.wicket.protocol.http.WebApplication;
+import org.apache.wicket.util.tester.WicketTestCase;
+import org.apache.wicket.coop.CrossOriginOpenerPolicyConfiguration.CoopMode;
+import org.junit.jupiter.api.Test;
+
+
+import static org.apache.wicket.coop.CrossOriginOpenerPolicyRequestCycleListener.COOP_HEADER;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+class CrossOriginOpenerPolicyRequestCycleListenerTest extends WicketTestCase
+{
+	private CoopMode mode;
+	private String exemptions;
+
+	@Test
+	void testCoopHeaderSameOrigin()
+	{
+		mode = CoopMode.SAME_ORIGIN;
+		buildApp();
+		checkHeaders(CoopMode.SAME_ORIGIN);
+	}
+
+	@Test
+	void testCoopHeaderSameOriginAllowPopups()
+	{
+		mode = CoopMode.SAME_ORIGIN_ALLOW_POPUPS;
+		buildApp();
+		checkHeaders(CoopMode.SAME_ORIGIN_ALLOW_POPUPS);
+	}
+
+	@Test
+	void testCoopHeaderUnsafeNone()
+	{
+		mode = CoopMode.UNSAFE_NONE;
+		buildApp();
+		checkHeaders(CoopMode.UNSAFE_NONE);
+	}
+
+	@Test
+	void testCoopDisabled()
+	{
+		mode = CoopMode.DISABLED;
+		buildApp();
+		tester.executeUrl("/");
+		String coopHeaderValue = tester.getLastResponse().getHeader(COOP_HEADER);
+
+		assertNull(coopHeaderValue, "COOP header should be null on DISABLED");
+	}
+
+	@Test
+	void testCoopHeadersNotSetExemptedPath()
+	{
+		mode = CoopMode.DISABLED;
+		exemptions = "exempt";
+		buildApp();
+		tester.executeUrl("exempt");
+		String coopHeaderValue = tester.getLastResponse().getHeader(COOP_HEADER);
+
+		assertNull(coopHeaderValue, "COOP header should be null on exempted path");
+	}
+
+	private void checkHeaders(CoopMode mode)
+	{
+		tester.executeUrl("/");
+		String coopHeaderValue = tester.getLastResponse().getHeader(COOP_HEADER);
+
+		assertNotNull(coopHeaderValue, "COOP header should not be null");
+
+		assertEquals(mode.keyword, coopHeaderValue, "Unexpected COOP header: " + coopHeaderValue);
+	}
+  
+	@Override
+	protected WebApplication newApplication()
+	{
+		return new MockApplication()
+		{
+			@Override
+			protected void init()
+			{
+				super.init();
+				getSecuritySettings().setCrossOriginOpenerPolicyConfiguration(mode, exemptions);
+			}
+		};
+	}
+
+	// overriding the commonBefore because we want to modify init behavior
+	// contents of commonBefore moved to buildApp, called after the coopMode/exemption set in every test
+	@Override
+	public void commonBefore()
+	{
+	}
+
+	private void buildApp()
+	{
+		ThreadContext.detach();
+
+		WebApplication application = newApplication();
+		tester = newWicketTester(application);
+	}
+}