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.interceptor.api.Pointcut; 023import ca.uhn.fhir.model.primitive.IdDt; 024import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 025import ca.uhn.fhir.rest.api.server.RequestDetails; 026import ca.uhn.fhir.rest.api.server.bulk.BulkExportJobParameters; 027import org.hl7.fhir.instance.model.api.IBaseResource; 028import org.hl7.fhir.instance.model.api.IIdType; 029 030import java.util.ArrayList; 031import java.util.Collection; 032import java.util.List; 033import java.util.Objects; 034import java.util.Set; 035import java.util.stream.Collectors; 036 037import static org.apache.commons.collections4.CollectionUtils.isEmpty; 038import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; 039import static org.apache.commons.lang3.StringUtils.isNotBlank; 040 041public class RuleBulkExportImpl extends BaseRule { 042 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RuleBulkExportImpl.class); 043 private String myGroupId; 044 private final Collection<String> myPatientIds; 045 private BulkExportJobParameters.ExportStyle myWantExportStyle; 046 private Collection<String> myResourceTypes; 047 private boolean myWantAnyStyle; 048 049 RuleBulkExportImpl(String theRuleName) { 050 super(theRuleName); 051 myPatientIds = new ArrayList<>(); 052 } 053 054 @Override 055 public AuthorizationInterceptor.Verdict applyRule( 056 RestOperationTypeEnum theOperation, 057 RequestDetails theRequestDetails, 058 IBaseResource theInputResource, 059 IIdType theInputResourceId, 060 IBaseResource theOutputResource, 061 IRuleApplier theRuleApplier, 062 Set<AuthorizationFlagsEnum> theFlags, 063 Pointcut thePointcut) { 064 if (thePointcut != Pointcut.STORAGE_INITIATE_BULK_EXPORT) { 065 return null; 066 } 067 068 if (theRequestDetails == null) { 069 return null; 070 } 071 072 BulkExportJobParameters options = (BulkExportJobParameters) 073 theRequestDetails.getAttribute(AuthorizationInterceptor.REQUEST_ATTRIBUTE_BULK_DATA_EXPORT_OPTIONS); 074 075 if (!myWantAnyStyle && options.getExportStyle() != myWantExportStyle) { 076 return null; 077 } 078 079 if (isNotEmpty(myResourceTypes)) { 080 if (isEmpty(options.getResourceTypes())) { 081 return null; 082 } 083 for (String next : options.getResourceTypes()) { 084 if (!myResourceTypes.contains(next)) { 085 return new AuthorizationInterceptor.Verdict(PolicyEnum.DENY, this); 086 } 087 } 088 } 089 090 if (myWantAnyStyle || myWantExportStyle == BulkExportJobParameters.ExportStyle.SYSTEM) { 091 return newVerdict( 092 theOperation, 093 theRequestDetails, 094 theInputResource, 095 theInputResourceId, 096 theOutputResource, 097 theRuleApplier); 098 } 099 100 if (isNotBlank(myGroupId) && options.getGroupId() != null) { 101 String expectedGroupId = 102 new IdDt(myGroupId).toUnqualifiedVersionless().getValue(); 103 String actualGroupId = 104 new IdDt(options.getGroupId()).toUnqualifiedVersionless().getValue(); 105 if (Objects.equals(expectedGroupId, actualGroupId)) { 106 return newVerdict( 107 theOperation, 108 theRequestDetails, 109 theInputResource, 110 theInputResourceId, 111 theOutputResource, 112 theRuleApplier); 113 } 114 } 115 116 // 1. If each of the requested resource IDs in the parameters are present in the users permissions, Approve 117 // 2. If any requested ID is not present in the users permissions, Deny. 118 if (myWantExportStyle == BulkExportJobParameters.ExportStyle.PATIENT && isNotEmpty(myPatientIds)) { 119 List<String> permittedPatientIds = myPatientIds.stream() 120 .map(id -> new IdDt(id).toUnqualifiedVersionless().getValue()) 121 .collect(Collectors.toList()); 122 if (!options.getPatientIds().isEmpty()) { 123 ourLog.debug("options.getPatientIds() != null"); 124 List<String> requestedPatientIds = options.getPatientIds().stream() 125 .map(t -> new IdDt(t).toUnqualifiedVersionless().getValue()) 126 .collect(Collectors.toList()); 127 boolean requestedPatientsPermitted = true; 128 for (String requestedPatientId : requestedPatientIds) { 129 if (!permittedPatientIds.contains(requestedPatientId)) { 130 requestedPatientsPermitted = false; 131 break; 132 } 133 } 134 if (requestedPatientsPermitted) { 135 return newVerdict( 136 theOperation, 137 theRequestDetails, 138 theInputResource, 139 theInputResourceId, 140 theOutputResource, 141 theRuleApplier); 142 } 143 144 return new AuthorizationInterceptor.Verdict(PolicyEnum.DENY, this); 145 } 146 147 final List<String> filters = options.getFilters(); 148 149 if (!filters.isEmpty()) { 150 ourLog.debug("filters not empty"); 151 final Set<String> patientIdsInFilters = filters.stream() 152 .filter(filter -> filter.startsWith("Patient?_id=")) 153 .map(filter -> filter.replace("?_id=", "/")) 154 .collect(Collectors.toUnmodifiableSet()); 155 156 boolean filteredPatientIdsPermitted = true; 157 for (String patientIdInFilters : patientIdsInFilters) { 158 if (!permittedPatientIds.contains(patientIdInFilters)) { 159 filteredPatientIdsPermitted = false; 160 break; 161 } 162 } 163 164 if (filteredPatientIdsPermitted) { 165 return newVerdict( 166 theOperation, 167 theRequestDetails, 168 theInputResource, 169 theInputResourceId, 170 theOutputResource, 171 theRuleApplier); 172 } 173 174 return new AuthorizationInterceptor.Verdict(PolicyEnum.DENY, this); 175 } 176 ourLog.debug("patientIds and filters both empty"); 177 } 178 return null; 179 } 180 181 public void setAppliesToGroupExportOnGroup(String theGroupId) { 182 myWantExportStyle = BulkExportJobParameters.ExportStyle.GROUP; 183 myGroupId = theGroupId; 184 } 185 186 public void setAppliesToPatientExportOnGroup(String theGroupId) { 187 myWantExportStyle = BulkExportJobParameters.ExportStyle.PATIENT; 188 myGroupId = theGroupId; 189 } 190 191 public void setAppliesToPatientExport(String thePatientId) { 192 myWantExportStyle = BulkExportJobParameters.ExportStyle.PATIENT; 193 myPatientIds.add(thePatientId); 194 } 195 196 public void setAppliesToSystem() { 197 myWantExportStyle = BulkExportJobParameters.ExportStyle.SYSTEM; 198 } 199 200 public void setResourceTypes(Collection<String> theResourceTypes) { 201 myResourceTypes = theResourceTypes; 202 } 203 204 public void setAppliesToAny() { 205 myWantAnyStyle = true; 206 } 207 208 String getGroupId() { 209 return myGroupId; 210 } 211 212 BulkExportJobParameters.ExportStyle getWantExportStyle() { 213 return myWantExportStyle; 214 } 215}