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.protocol.http;
018
019import javax.servlet.http.HttpServletRequest;
020import javax.servlet.http.HttpServletResponse;
021
022import org.apache.wicket.request.component.IRequestablePage;
023import org.apache.wicket.util.string.Strings;
024
025/**
026 * Default resource isolation policy used in {@link ResourceIsolationRequestCycleListener},
027 * based on <a href="https://web.dev/fetch-metadata/">https://web.dev/fetch-metadata/</a>.
028 *
029 * @see <a href="https://web.dev/fetch-metadata/">https://web.dev/fetch-metadata/</a>
030 *
031 * @author Santiago Diaz - saldiaz@google.com
032 * @author Ecenaz Jen Ozmen - ecenazo@google.com
033 */
034public class FetchMetadataResourceIsolationPolicy implements IResourceIsolationPolicy
035{
036
037        public static final String SEC_FETCH_SITE_HEADER = "sec-fetch-site";
038        public static final String SEC_FETCH_MODE_HEADER = "sec-fetch-mode";
039        public static final String SEC_FETCH_DEST_HEADER = "sec-fetch-dest";
040
041        public static final String SAME_ORIGIN = "same-origin";
042        public static final String SAME_SITE = "same-site";
043        public static final String NONE = "none";
044        public static final String MODE_NAVIGATE = "navigate";
045        public static final String MODE_NO_CORS = "no-cors";
046        public static final String DEST_OBJECT = "object";
047        public static final String DEST_EMBED = "embed";
048        public static final String CROSS_SITE = "cross-site";
049        public static final String CORS = "cors";
050        public static final String DEST_DOCUMENT = "document";
051        public static final String DEST_SCRIPT = "script";
052        public static final String DEST_IMAGE = "image";
053        
054        public static final String VARY_HEADER = "Vary";
055        
056        private static final String VARY_HEADER_VALUE = SEC_FETCH_DEST_HEADER + ", "
057                + SEC_FETCH_SITE_HEADER + ", " + SEC_FETCH_MODE_HEADER;
058        
059        @Override
060        public ResourceIsolationOutcome isRequestAllowed(HttpServletRequest request,
061                IRequestablePage targetPage)
062        {
063                // request made by a legacy browser with no support for Fetch Metadata
064                String site = request.getHeader(SEC_FETCH_SITE_HEADER);
065                if (Strings.isEmpty(site))
066                {
067                        return ResourceIsolationOutcome.UNKNOWN;
068                }
069                
070                // Allow same-site and browser-initiated requests
071                if (SAME_ORIGIN.equals(site) || SAME_SITE.equals(site) || NONE.equals(site))
072                {
073                        return ResourceIsolationOutcome.ALLOWED;
074                }
075
076                // Allow simple top-level navigations except <object> and <embed>
077                return isAllowedTopLevelNavigation(request)
078                        ? ResourceIsolationOutcome.ALLOWED
079                        : ResourceIsolationOutcome.DISALLOWED;
080        }
081
082        private boolean isAllowedTopLevelNavigation(HttpServletRequest request)
083        {
084                String mode = request.getHeader(SEC_FETCH_MODE_HEADER);
085                String dest = request.getHeader(SEC_FETCH_DEST_HEADER);
086
087                boolean isSimpleTopLevelNavigation = MODE_NAVIGATE.equals(mode)
088                        && "GET".equalsIgnoreCase(request.getMethod());
089                boolean isNotObjectOrEmbedRequest = !DEST_EMBED.equals(dest) && !DEST_OBJECT.equals(dest);
090
091                return isSimpleTopLevelNavigation && isNotObjectOrEmbedRequest;
092        }
093
094        /**
095         * Set vary headers to avoid caching responses processed by Fetch Metadata.
096         * <p>
097         * Caching these responses may return 403 responses to legitimate requests
098         * defeat the protection.
099         */
100        @Override
101        public void setHeaders(HttpServletResponse response)
102        {
103                response.addHeader(VARY_HEADER, VARY_HEADER_VALUE);
104        }
105}