
001/*- 002 * #%L 003 * HAPI FHIR - Server Framework 004 * %% 005 * Copyright (C) 2014 - 2023 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 org.apache.commons.lang3.Validate; 031import org.hl7.fhir.instance.model.api.IBaseResource; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034 035import javax.annotation.Nonnull; 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(IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry) { 061 myValidationSupport = theValidationSupport; 062 mySearchParamRegistry = theSearchParamRegistry; 063 } 064 065 /** 066 * Provides a log that will be apppended to for troubleshooting messages 067 * 068 * @param theTroubleshootingLog The logger (must not be <code>null</code>) 069 */ 070 public void setTroubleshootingLog(@Nonnull Logger theTroubleshootingLog) { 071 Validate.notNull(theTroubleshootingLog, "theTroubleshootingLog must not be null"); 072 myTroubleshootingLog = theTroubleshootingLog; 073 } 074 075 @Override 076 public boolean shouldProcessCanSeeResource(RequestDetails theRequestDetails, IConsentContextServices theContextServices) { 077 List<AllowedCodeInValueSet> postFilteringList = SearchNarrowingInterceptor.getPostFilteringListOrNull(theRequestDetails); 078 return postFilteringList != null && !postFilteringList.isEmpty(); 079 } 080 081 082 @Override 083 public ConsentOutcome canSeeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) { 084 return applyFilterForResource(theRequestDetails, theResource); 085 } 086 087 @Override 088 public ConsentOutcome willSeeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) { 089 return applyFilterForResource(theRequestDetails, theResource); 090 } 091 092 @Nonnull 093 private ConsentOutcome applyFilterForResource(RequestDetails theRequestDetails, IBaseResource theResource) { 094 List<AllowedCodeInValueSet> postFilteringList = SearchNarrowingInterceptor.getPostFilteringListOrNull(theRequestDetails); 095 if (postFilteringList == null) { 096 return ConsentOutcome.PROCEED; 097 } 098 099 String resourceType = myValidationSupport.getFhirContext().getResourceType(theResource); 100 101 boolean allPositiveRulesMatched = true; 102 for (AllowedCodeInValueSet next : postFilteringList) { 103 if (!next.getResourceName().equals(resourceType)) { 104 continue; 105 } 106 107 boolean returnOnFirstMatch = true; 108 String searchParamName = next.getSearchParameterName(); 109 String valueSetUrl = next.getValueSetUrl(); 110 111 SearchParameterAndValueSetRuleImpl.CodeMatchCount outcome = SearchParameterAndValueSetRuleImpl.countMatchingCodesInValueSetForSearchParameter(theResource, myValidationSupport, mySearchParamRegistry, returnOnFirstMatch, searchParamName, valueSetUrl, myTroubleshootingLog, "Search Narrowing"); 112 if (outcome.isAtLeastOneUnableToValidate()) { 113 myTroubleshootingLog.warn("Terminology Services failed to validate value from " + next.getResourceName() + ":" + next.getSearchParameterName() + " in ValueSet " + next.getValueSetUrl() + " - Assuming REJECT"); 114 return ConsentOutcome.REJECT; 115 } 116 117 if (next.isNegate()) { 118 if (outcome.getMatchingCodeCount() > 0) { 119 return ConsentOutcome.REJECT; 120 } 121 } else { 122 if (outcome.getMatchingCodeCount() == 0) { 123 allPositiveRulesMatched = false; 124 break; 125 } 126 } 127 128 } 129 130 if (!allPositiveRulesMatched) { 131 return ConsentOutcome.REJECT; 132 } 133 134 return ConsentOutcome.PROCEED; 135 } 136}