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}