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