001/*
002 * #%L
003 * HAPI FHIR - Server Framework
004 * %%
005 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.rest.server.interceptor;
021
022import ca.uhn.fhir.i18n.Msg;
023import ca.uhn.fhir.rest.api.Constants;
024import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
025import jakarta.servlet.http.HttpServletRequest;
026import jakarta.servlet.http.HttpServletResponse;
027import org.apache.commons.lang3.Validate;
028import org.springframework.web.cors.CorsConfiguration;
029import org.springframework.web.cors.CorsProcessor;
030import org.springframework.web.cors.CorsUtils;
031import org.springframework.web.cors.DefaultCorsProcessor;
032
033import java.io.IOException;
034import java.util.ArrayList;
035
036public class CorsInterceptor extends InterceptorAdapter {
037
038        private CorsProcessor myCorsProcessor;
039        private CorsConfiguration myConfig;
040
041        /**
042         * Constructor which creates an interceptor with default CORS configuration for use in
043         * a FHIR server. This includes:
044         * <ul>
045         * <li>Allowed Origin: *</li>
046         * <li>Allowed Header: Accept</li>
047         * <li>Allowed Header: Access-Control-Request-Headers</li>
048         * <li>Allowed Header: Access-Control-Request-Method</li>
049         * <li>Allowed Header: Cache-Control</li>
050         * <li>Exposed Header: Content-Location</li>
051         * <li>Allowed Header: Content-Type</li>
052         * <li>Exposed Header: Location</li>
053         * <li>Allowed Header: Origin</li>
054         * <li>Allowed Header: Prefer</li>
055         * <li>Allowed Header: X-Requested-With</li>
056         * </ul>
057         * Note that this configuration is useful for quickly getting CORS working, but
058         * in a real production system you probably want to consider whether it is
059         * appropriate for your situation. In particular, using "Allowed Origin: *"
060         * isn't always the right thing to do.
061         */
062        public CorsInterceptor() {
063                this(createDefaultCorsConfig());
064        }
065
066        /**
067         * Constructor which accepts the given configuration
068         *
069         * @param theConfiguration
070         *           The CORS configuration
071         */
072        public CorsInterceptor(CorsConfiguration theConfiguration) {
073                Validate.notNull(theConfiguration, "theConfiguration must not be null");
074                myCorsProcessor = new DefaultCorsProcessor();
075                setConfig(theConfiguration);
076        }
077
078        /**
079         * Gets the CORS configuration
080         */
081        public CorsConfiguration getConfig() {
082                return myConfig;
083        }
084
085        /**
086         * Sets the CORS configuration
087         */
088        public void setConfig(CorsConfiguration theConfiguration) {
089                myConfig = theConfiguration;
090        }
091
092        @Override
093        public boolean incomingRequestPreProcessed(HttpServletRequest theRequest, HttpServletResponse theResponse) {
094                if (CorsUtils.isCorsRequest(theRequest)) {
095                        boolean isValid;
096                        try {
097                                isValid = myCorsProcessor.processRequest(myConfig, theRequest, theResponse);
098                        } catch (IOException e) {
099                                throw new InternalErrorException(Msg.code(326) + e);
100                        }
101                        if (!isValid || CorsUtils.isPreFlightRequest(theRequest)) {
102                                return false;
103                        }
104                }
105
106                return super.incomingRequestPreProcessed(theRequest, theResponse);
107        }
108
109        private static CorsConfiguration createDefaultCorsConfig() {
110                CorsConfiguration retVal = new CorsConfiguration();
111
112                retVal.setAllowedHeaders(new ArrayList<>(Constants.CORS_ALLOWED_HEADERS));
113                retVal.setAllowedMethods(new ArrayList<>(Constants.CORS_ALLWED_METHODS));
114
115                retVal.addExposedHeader("Content-Location");
116                retVal.addExposedHeader("Location");
117
118                retVal.addAllowedOrigin("*");
119
120                return retVal;
121        }
122}