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