001/*- 002 * #%L 003 * HAPI FHIR Storage api 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.jpa.interceptor; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.interceptor.api.Hook; 025import ca.uhn.fhir.interceptor.api.Interceptor; 026import ca.uhn.fhir.interceptor.api.Pointcut; 027import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; 028import ca.uhn.fhir.jpa.util.ResourceCompartmentUtil; 029import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 030import ca.uhn.fhir.util.StopWatch; 031import org.apache.commons.lang3.StringUtils; 032import org.hl7.fhir.instance.model.api.IBaseResource; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036import static org.apache.commons.lang3.StringUtils.EMPTY; 037 038/** 039 * This interceptor can be used to block resource updates which would make resource patient compartment change. 040 * <p/> 041 * This could be used when the JPA server has partitioning enabled, and Tenant Identification Strategy is PATIENT_ID. 042 */ 043@Interceptor 044public class PatientCompartmentEnforcingInterceptor { 045 private static final Logger ourLog = LoggerFactory.getLogger(PatientCompartmentEnforcingInterceptor.class); 046 047 private final FhirContext myFhirContext; 048 private final ISearchParamExtractor mySearchParamExtractor; 049 050 public PatientCompartmentEnforcingInterceptor( 051 FhirContext theFhirContext, ISearchParamExtractor theSearchParamExtractor) { 052 myFhirContext = theFhirContext; 053 mySearchParamExtractor = theSearchParamExtractor; 054 } 055 056 /** 057 * Blocks resource updates which would make the resource change Patient Compartment. 058 * @param theOldResource the original resource state 059 * @param theResource the updated resource state 060 */ 061 @Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED) 062 public void storagePreStorageResourceUpdated(IBaseResource theOldResource, IBaseResource theResource) { 063 064 ourLog.info("Interceptor STORAGE_PRESTORAGE_RESOURCE_UPDATED - started"); 065 StopWatch stopWatch = new StopWatch(); 066 try { 067 String patientCompartmentOld = ResourceCompartmentUtil.getPatientCompartmentIdentity( 068 theOldResource, myFhirContext, mySearchParamExtractor) 069 .orElse(EMPTY); 070 String patientCompartmentCurrent = ResourceCompartmentUtil.getPatientCompartmentIdentity( 071 theResource, myFhirContext, mySearchParamExtractor) 072 .orElse(EMPTY); 073 074 if (!StringUtils.equals(patientCompartmentOld, patientCompartmentCurrent)) { 075 // Avoid disclosing compartments in message, which could have security implications 076 throw new InternalErrorException( 077 Msg.code(2476) + "Resource compartment changed. Was a referenced Patient changed?"); 078 } 079 080 } finally { 081 ourLog.info("Interceptor STORAGE_PRESTORAGE_RESOURCE_UPDATED - ended, execution took {}", stopWatch); 082 } 083 } 084}