001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.coep;
018
019import org.apache.wicket.coop.CrossOriginOpenerPolicyRequestCycleListener;
020import org.apache.wicket.request.IRequestHandler;
021import org.apache.wicket.request.cycle.IRequestCycleListener;
022import org.apache.wicket.request.cycle.RequestCycle;
023import org.apache.wicket.request.http.WebResponse;
024import org.apache.wicket.util.lang.Args;
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028import javax.servlet.http.HttpServletRequest;
029
030/**
031 * Sets <a href="https://wicg.github.io/cross-origin-embedder-policy/">Cross-Origin Embedder
032 * Policy</a> (COEP) headers on the responses based on the mode specified by
033 * {@link CrossOriginEmbedderPolicyConfiguration}. COEP can be enabled in <code>REPORTING</code>
034 * mode which will set the headers as <code>Cross-Origin-Embedder-Policy-Report-Only</code> or
035 * <code>ENFORCING</code> mode which will set the header as
036 * <code>Cross-Origin-Embedder-Policy</code>. The header is not set for the paths that are exempted
037 * from COEP. The only valid value of COEP is <code>require-corp</code>, so if the listener is
038 * enabled the policy value will be specified as so.
039 * <p>
040 * COEP prevents a document from loading any non-same-origin resources which don't explicitly grant
041 * the document permission to be loaded. Using COEP and COOP together allows developers to safely
042 * use powerful features such as <code>SharedArrayBuffer</code>,
043 * <code>performance.measureMemory()</code>, and the JS Self-Profiling API.See
044 * {@link CrossOriginOpenerPolicyRequestCycleListener} for instructions on how to enable COOP.
045 * Read more about cross-origin isolation on
046 * <a href="https://web.dev/why-coop-coep/">https://web.dev/why-coop-coep/</a>
047 * <p>
048 * 
049 * @author Santiago Diaz - saldiaz@google.com
050 * @author Ecenaz Jen Ozmen - ecenazo@google.com
051 *
052 * @see CrossOriginEmbedderPolicyConfiguration
053 * @see org.apache.wicket.settings.SecuritySettings
054 */
055public class CrossOriginEmbedderPolicyRequestCycleListener implements IRequestCycleListener
056{
057        private static final Logger log = LoggerFactory.getLogger(CrossOriginEmbedderPolicyRequestCycleListener.class);
058
059        static final String REQUIRE_CORP = "require-corp";
060
061        private final CrossOriginEmbedderPolicyConfiguration coepConfig;
062
063        public CrossOriginEmbedderPolicyRequestCycleListener(CrossOriginEmbedderPolicyConfiguration coepConfig)
064        {
065                this.coepConfig = Args.notNull(coepConfig, "coepConfig");
066        }
067
068        @Override
069        public void onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler)
070        {
071                // WICKET-7028- this is needed for redirect to buffer use case.
072                protect(cycle, handler);
073        }
074
075        @Override
076        public void onRequestHandlerExecuted(RequestCycle cycle, IRequestHandler handler)
077        {
078                protect(cycle, handler);
079        }
080
081        protected void protect(RequestCycle cycle, IRequestHandler handler)
082        {
083                final Object containerRequest = cycle.getRequest().getContainerRequest();
084                if (containerRequest instanceof HttpServletRequest)
085                {
086                        HttpServletRequest request = (HttpServletRequest) containerRequest;
087                        String path = request.getContextPath();
088                        final String coepHeaderName = coepConfig.getCoepHeader();
089
090                        if (coepConfig.getExemptions().contains(path))
091                        {
092                                log.debug("Request path {} is exempted from COEP, no '{}' header added", path, coepHeaderName);
093                                return;
094                        }
095
096                        if (cycle.getResponse() instanceof WebResponse)
097                        {
098                                WebResponse webResponse = (WebResponse) cycle.getResponse();
099                                if (webResponse.isHeaderSupported())
100                                {
101                                        webResponse.setHeader(coepHeaderName, REQUIRE_CORP);
102                                }
103                        }
104                }
105        }
106
107}