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.auth; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.context.support.IValidationSupport; 024import ca.uhn.fhir.rest.api.server.RequestDetails; 025import ca.uhn.fhir.rest.server.interceptor.consent.ConsentOutcome; 026import ca.uhn.fhir.rest.server.interceptor.consent.IConsentContextServices; 027import ca.uhn.fhir.rest.server.interceptor.consent.IConsentService; 028import ca.uhn.fhir.rest.server.util.FhirContextSearchParamRegistry; 029import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; 030import jakarta.annotation.Nonnull; 031import org.apache.commons.lang3.Validate; 032import org.hl7.fhir.instance.model.api.IBaseResource; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036import java.util.List; 037 038public class SearchNarrowingConsentService implements IConsentService { 039 private static final Logger ourLog = LoggerFactory.getLogger(SearchNarrowingConsentService.class); 040 041 private final IValidationSupport myValidationSupport; 042 private final ISearchParamRegistry mySearchParamRegistry; 043 private Logger myTroubleshootingLog = ourLog; 044 045 /** 046 * Constructor (use this only if no {@link ISearchParamRegistry} is available 047 * 048 * @param theValidationSupport The validation support module 049 */ 050 public SearchNarrowingConsentService(IValidationSupport theValidationSupport, FhirContext theFhirContext) { 051 this(theValidationSupport, new FhirContextSearchParamRegistry(theFhirContext)); 052 } 053 054 /** 055 * Constructor 056 * 057 * @param theValidationSupport The validation support module 058 * @param theSearchParamRegistry The search param registry 059 */ 060 public SearchNarrowingConsentService( 061 IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry) { 062 myValidationSupport = theValidationSupport; 063 mySearchParamRegistry = theSearchParamRegistry; 064 } 065 066 /** 067 * Provides a log that will be apppended to for troubleshooting messages 068 * 069 * @param theTroubleshootingLog The logger (must not be <code>null</code>) 070 */ 071 public void setTroubleshootingLog(@Nonnull Logger theTroubleshootingLog) { 072 Validate.notNull(theTroubleshootingLog, "theTroubleshootingLog must not be null"); 073 myTroubleshootingLog = theTroubleshootingLog; 074 } 075 076 @Override 077 public boolean shouldProcessCanSeeResource( 078 RequestDetails theRequestDetails, IConsentContextServices theContextServices) { 079 List<AllowedCodeInValueSet> postFilteringList = 080 SearchNarrowingInterceptor.getPostFilteringListOrNull(theRequestDetails); 081 return postFilteringList != null && !postFilteringList.isEmpty(); 082 } 083 084 @Override 085 public ConsentOutcome canSeeResource( 086 RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) { 087 return applyFilterForResource(theRequestDetails, theResource); 088 } 089 090 @Override 091 public ConsentOutcome willSeeResource( 092 RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) { 093 return applyFilterForResource(theRequestDetails, theResource); 094 } 095 096 @Nonnull 097 private ConsentOutcome applyFilterForResource(RequestDetails theRequestDetails, IBaseResource theResource) { 098 List<AllowedCodeInValueSet> postFilteringList = 099 SearchNarrowingInterceptor.getPostFilteringListOrNull(theRequestDetails); 100 if (postFilteringList == null) { 101 return ConsentOutcome.PROCEED; 102 } 103 104 String resourceType = myValidationSupport.getFhirContext().getResourceType(theResource); 105 106 boolean allPositiveRulesMatched = true; 107 for (AllowedCodeInValueSet next : postFilteringList) { 108 if (!next.getResourceName().equals(resourceType)) { 109 continue; 110 } 111 112 boolean returnOnFirstMatch = true; 113 String searchParamName = next.getSearchParameterName(); 114 String valueSetUrl = next.getValueSetUrl(); 115 116 SearchParameterAndValueSetRuleImpl.CodeMatchCount outcome = 117 SearchParameterAndValueSetRuleImpl.countMatchingCodesInValueSetForSearchParameter( 118 theResource, 119 myValidationSupport, 120 mySearchParamRegistry, 121 returnOnFirstMatch, 122 searchParamName, 123 valueSetUrl, 124 myTroubleshootingLog, 125 "Search Narrowing"); 126 if (outcome.isAtLeastOneUnableToValidate()) { 127 myTroubleshootingLog.warn("Terminology Services failed to validate value from " + next.getResourceName() 128 + ":" + next.getSearchParameterName() + " in ValueSet " + next.getValueSetUrl() 129 + " - Assuming REJECT"); 130 return ConsentOutcome.REJECT; 131 } 132 133 if (next.isNegate()) { 134 if (outcome.getMatchingCodeCount() > 0) { 135 return ConsentOutcome.REJECT; 136 } 137 } else { 138 if (outcome.getMatchingCodeCount() == 0) { 139 allPositiveRulesMatched = false; 140 break; 141 } 142 } 143 } 144 145 if (!allPositiveRulesMatched) { 146 return ConsentOutcome.REJECT; 147 } 148 149 return ConsentOutcome.PROCEED; 150 } 151}