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.i18n.Msg; 025import ca.uhn.fhir.interceptor.api.Hook; 026import ca.uhn.fhir.interceptor.api.Interceptor; 027import ca.uhn.fhir.interceptor.api.Pointcut; 028import ca.uhn.fhir.model.valueset.BundleTypeEnum; 029import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 030import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails; 031import ca.uhn.fhir.rest.api.server.RequestDetails; 032import ca.uhn.fhir.rest.api.server.bulk.BulkExportJobParameters; 033import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException; 034import ca.uhn.fhir.util.BundleUtil; 035import com.google.common.collect.Lists; 036import jakarta.annotation.Nonnull; 037import jakarta.annotation.Nullable; 038import org.apache.commons.lang3.StringUtils; 039import org.apache.commons.lang3.Validate; 040import org.apache.commons.lang3.builder.ToStringBuilder; 041import org.apache.commons.lang3.builder.ToStringStyle; 042import org.hl7.fhir.instance.model.api.IBaseBundle; 043import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; 044import org.hl7.fhir.instance.model.api.IBaseParameters; 045import org.hl7.fhir.instance.model.api.IBaseResource; 046import org.hl7.fhir.instance.model.api.IIdType; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 049 050import java.util.ArrayList; 051import java.util.Collection; 052import java.util.Collections; 053import java.util.HashSet; 054import java.util.IdentityHashMap; 055import java.util.List; 056import java.util.Set; 057import java.util.concurrent.atomic.AtomicInteger; 058 059import static java.util.Objects.isNull; 060import static java.util.Objects.nonNull; 061import static org.apache.commons.lang3.StringUtils.EMPTY; 062import static org.apache.commons.lang3.StringUtils.defaultString; 063import static org.apache.commons.lang3.StringUtils.isNotBlank; 064 065/** 066 * This class is a base class for interceptors which can be used to 067 * inspect requests and responses to determine whether the calling user 068 * has permission to perform the given action. 069 * <p> 070 * See the HAPI FHIR 071 * <a href="https://hapifhir.io/hapi-fhir/docs/security/introduction.html">Documentation on Server Security</a> 072 * for information on how to use this interceptor. 073 * </p> 074 * 075 * @see SearchNarrowingInterceptor 076 */ 077@Interceptor(order = AuthorizationConstants.ORDER_AUTH_INTERCEPTOR) 078public class AuthorizationInterceptor implements IRuleApplier { 079 080 public static final String REQUEST_ATTRIBUTE_BULK_DATA_EXPORT_OPTIONS = 081 AuthorizationInterceptor.class.getName() + "_BulkDataExportOptions"; 082 private static final AtomicInteger ourInstanceCount = new AtomicInteger(0); 083 private static final Logger ourLog = LoggerFactory.getLogger(AuthorizationInterceptor.class); 084 private static final Set<BundleTypeEnum> STANDALONE_BUNDLE_RESOURCE_TYPES = 085 Set.of(BundleTypeEnum.DOCUMENT, BundleTypeEnum.MESSAGE); 086 087 private final int myInstanceIndex = ourInstanceCount.incrementAndGet(); 088 private final String myRequestSeenResourcesKey = 089 AuthorizationInterceptor.class.getName() + "_" + myInstanceIndex + "_SEENRESOURCES"; 090 private final String myRequestRuleListKey = 091 AuthorizationInterceptor.class.getName() + "_" + myInstanceIndex + "_RULELIST"; 092 private PolicyEnum myDefaultPolicy = PolicyEnum.DENY; 093 private Set<AuthorizationFlagsEnum> myFlags = Collections.emptySet(); 094 private IValidationSupport myValidationSupport; 095 096 private IAuthorizationSearchParamMatcher myAuthorizationSearchParamMatcher; 097 private Logger myTroubleshootingLog; 098 099 /** 100 * Constructor 101 */ 102 public AuthorizationInterceptor() { 103 super(); 104 setTroubleshootingLog(ourLog); 105 } 106 107 /** 108 * Constructor 109 * 110 * @param theDefaultPolicy The default policy if no rules apply (must not be null) 111 */ 112 public AuthorizationInterceptor(PolicyEnum theDefaultPolicy) { 113 this(); 114 setDefaultPolicy(theDefaultPolicy); 115 } 116 117 @Nonnull 118 @Override 119 public Logger getTroubleshootingLog() { 120 return myTroubleshootingLog; 121 } 122 123 public void setTroubleshootingLog(@Nonnull Logger theTroubleshootingLog) { 124 Validate.notNull(theTroubleshootingLog, "theTroubleshootingLog must not be null"); 125 myTroubleshootingLog = theTroubleshootingLog; 126 } 127 128 private void applyRulesAndFailIfDeny( 129 RestOperationTypeEnum theOperation, 130 RequestDetails theRequestDetails, 131 IBaseResource theInputResource, 132 IIdType theInputResourceId, 133 IBaseResource theOutputResource, 134 Pointcut thePointcut) { 135 Verdict decision = applyRulesAndReturnDecision( 136 theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, thePointcut); 137 138 if (decision.getDecision() == PolicyEnum.ALLOW) { 139 return; 140 } 141 142 handleDeny(theRequestDetails, decision); 143 } 144 145 @Override 146 public Verdict applyRulesAndReturnDecision( 147 RestOperationTypeEnum theOperation, 148 RequestDetails theRequestDetails, 149 IBaseResource theInputResource, 150 IIdType theInputResourceId, 151 IBaseResource theOutputResource, 152 Pointcut thePointcut) { 153 @SuppressWarnings("unchecked") 154 List<IAuthRule> rules = 155 (List<IAuthRule>) theRequestDetails.getUserData().get(myRequestRuleListKey); 156 if (rules == null) { 157 rules = buildRuleList(theRequestDetails); 158 theRequestDetails.getUserData().put(myRequestRuleListKey, rules); 159 } 160 Set<AuthorizationFlagsEnum> flags = getFlags(); 161 162 ourLog.trace( 163 "Applying {} rules to render an auth decision for operation {}, theInputResource type={}, theOutputResource type={}, thePointcut={} ", 164 rules.size(), 165 getPointcutNameOrEmpty(thePointcut), 166 getResourceTypeOrEmpty(theInputResource), 167 getResourceTypeOrEmpty(theOutputResource)); 168 169 Verdict verdict = null; 170 for (IAuthRule nextRule : rules) { 171 ourLog.trace("Rule being applied - {}", nextRule); 172 verdict = nextRule.applyRule( 173 theOperation, 174 theRequestDetails, 175 theInputResource, 176 theInputResourceId, 177 theOutputResource, 178 this, 179 flags, 180 thePointcut); 181 if (verdict != null) { 182 ourLog.trace("Rule {} returned decision {}", nextRule, verdict.getDecision()); 183 break; 184 } 185 } 186 187 if (verdict == null) { 188 ourLog.trace("No rules returned a decision, applying default {}", myDefaultPolicy); 189 return new Verdict(getDefaultPolicy(), null); 190 } 191 192 return verdict; 193 } 194 195 /** 196 * @since 6.0.0 197 */ 198 @Nullable 199 @Override 200 public IValidationSupport getValidationSupport() { 201 return myValidationSupport; 202 } 203 204 /** 205 * Sets a validation support module that will be used for terminology-based rules 206 * 207 * @param theValidationSupport The validation support. Null is also acceptable (this is the default), 208 * in which case the validation support module associated with the {@link FhirContext} 209 * will be used. 210 * @since 6.0.0 211 */ 212 public AuthorizationInterceptor setValidationSupport(IValidationSupport theValidationSupport) { 213 myValidationSupport = theValidationSupport; 214 return this; 215 } 216 217 /** 218 * Sets a search parameter matcher for use in handling SMART v2 filter scopes 219 * 220 * @param theAuthorizationSearchParamMatcher The search parameter matcher. Defaults to null. 221 */ 222 public void setAuthorizationSearchParamMatcher( 223 @Nullable IAuthorizationSearchParamMatcher theAuthorizationSearchParamMatcher) { 224 this.myAuthorizationSearchParamMatcher = theAuthorizationSearchParamMatcher; 225 } 226 227 @Override 228 @Nullable 229 public IAuthorizationSearchParamMatcher getSearchParamMatcher() { 230 return myAuthorizationSearchParamMatcher; 231 } 232 233 /** 234 * Subclasses should override this method to supply the set of rules to be applied to 235 * this individual request. 236 * <p> 237 * Typically this is done by examining <code>theRequestDetails</code> to find 238 * out who the current user is and then using a {@link RuleBuilder} to create 239 * an appropriate rule chain. 240 * </p> 241 * 242 * @param theRequestDetails The individual request currently being applied 243 */ 244 public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { 245 return new ArrayList<>(); 246 } 247 248 private OperationExamineDirection determineOperationDirection(RestOperationTypeEnum theOperation) { 249 250 switch (theOperation) { 251 case ADD_TAGS: 252 case DELETE_TAGS: 253 case GET_TAGS: 254 // These are DSTU1 operations and not relevant 255 return OperationExamineDirection.NONE; 256 257 case EXTENDED_OPERATION_INSTANCE: 258 case EXTENDED_OPERATION_SERVER: 259 case EXTENDED_OPERATION_TYPE: 260 return OperationExamineDirection.BOTH; 261 262 case METADATA: 263 // Security does not apply to these operations 264 return OperationExamineDirection.IN; 265 266 case DELETE: 267 // Delete is a special case 268 return OperationExamineDirection.IN; 269 270 case CREATE: 271 case UPDATE: 272 case PATCH: 273 return OperationExamineDirection.IN; 274 275 case META: 276 case META_ADD: 277 case META_DELETE: 278 // meta operations do not apply yet 279 return OperationExamineDirection.NONE; 280 281 case GET_PAGE: 282 case HISTORY_INSTANCE: 283 case HISTORY_SYSTEM: 284 case HISTORY_TYPE: 285 case READ: 286 case SEARCH_SYSTEM: 287 case SEARCH_TYPE: 288 case VREAD: 289 return OperationExamineDirection.OUT; 290 291 case TRANSACTION: 292 return OperationExamineDirection.BOTH; 293 294 case VALIDATE: 295 // Nothing yet 296 return OperationExamineDirection.NONE; 297 298 case GRAPHQL_REQUEST: 299 return OperationExamineDirection.BOTH; 300 301 default: 302 // Should not happen 303 throw new IllegalStateException( 304 Msg.code(332) + "Unable to apply security to event of type " + theOperation); 305 } 306 } 307 308 /** 309 * The default policy if no rules have been found to apply. Default value for this setting is {@link PolicyEnum#DENY} 310 */ 311 public PolicyEnum getDefaultPolicy() { 312 return myDefaultPolicy; 313 } 314 315 /** 316 * The default policy if no rules have been found to apply. Default value for this setting is {@link PolicyEnum#DENY} 317 * 318 * @param theDefaultPolicy The policy (must not be <code>null</code>) 319 */ 320 public AuthorizationInterceptor setDefaultPolicy(PolicyEnum theDefaultPolicy) { 321 Validate.notNull(theDefaultPolicy, "theDefaultPolicy must not be null"); 322 myDefaultPolicy = theDefaultPolicy; 323 return this; 324 } 325 326 /** 327 * This property configures any flags affecting how authorization is 328 * applied. By default no flags are applied. 329 * 330 * @see #setFlags(Collection) 331 */ 332 public Set<AuthorizationFlagsEnum> getFlags() { 333 return Collections.unmodifiableSet(myFlags); 334 } 335 336 /** 337 * This property configures any flags affecting how authorization is 338 * applied. By default no flags are applied. 339 * 340 * @param theFlags The flags (must not be null) 341 * @see #setFlags(AuthorizationFlagsEnum...) 342 */ 343 public AuthorizationInterceptor setFlags(Collection<AuthorizationFlagsEnum> theFlags) { 344 Validate.notNull(theFlags, "theFlags must not be null"); 345 myFlags = new HashSet<>(theFlags); 346 return this; 347 } 348 349 /** 350 * This property configures any flags affecting how authorization is 351 * applied. By default no flags are applied. 352 * 353 * @param theFlags The flags (must not be null) 354 * @see #setFlags(Collection) 355 */ 356 public AuthorizationInterceptor setFlags(AuthorizationFlagsEnum... theFlags) { 357 Validate.notNull(theFlags, "theFlags must not be null"); 358 return setFlags(Lists.newArrayList(theFlags)); 359 } 360 361 /** 362 * Handle an access control verdict of {@link PolicyEnum#DENY}. 363 * <p> 364 * Subclasses may override to implement specific behaviour, but default is to 365 * throw {@link ForbiddenOperationException} (HTTP 403) with error message citing the 366 * rule name which trigered failure 367 * </p> 368 * 369 * @since HAPI FHIR 3.6.0 370 */ 371 protected void handleDeny(RequestDetails theRequestDetails, Verdict decision) { 372 handleDeny(decision); 373 } 374 375 /** 376 * This method should not be overridden. As of HAPI FHIR 3.6.0, you 377 * should override {@link #handleDeny(RequestDetails, Verdict)} instead. This 378 * method will be removed in the future. 379 */ 380 protected void handleDeny(Verdict decision) { 381 if (decision.getDecidingRule() != null) { 382 String ruleName = defaultString(decision.getDecidingRule().getName(), "(unnamed rule)"); 383 throw new ForbiddenOperationException(Msg.code(333) + "Access denied by rule: " + ruleName); 384 } 385 throw new ForbiddenOperationException(Msg.code(334) + "Access denied by default policy (no applicable rules)"); 386 } 387 388 private void handleUserOperation( 389 RequestDetails theRequest, 390 IBaseResource theResource, 391 RestOperationTypeEnum theOperation, 392 Pointcut thePointcut) { 393 applyRulesAndFailIfDeny(theOperation, theRequest, theResource, theResource.getIdElement(), null, thePointcut); 394 } 395 396 @Hook(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED) 397 public void incomingRequestPreHandled(RequestDetails theRequest, Pointcut thePointcut) { 398 IBaseResource inputResource = null; 399 IIdType inputResourceId = null; 400 401 switch (determineOperationDirection(theRequest.getRestOperationType())) { 402 case IN: 403 case BOTH: 404 inputResource = theRequest.getResource(); 405 inputResourceId = theRequest.getId(); 406 if (inputResourceId == null && isNotBlank(theRequest.getResourceName())) { 407 inputResourceId = theRequest.getFhirContext().getVersion().newIdType(); 408 inputResourceId.setParts(null, theRequest.getResourceName(), null, null); 409 } 410 break; 411 case OUT: 412 // inputResource = null; 413 inputResourceId = theRequest.getId(); 414 break; 415 case NONE: 416 return; 417 } 418 419 applyRulesAndFailIfDeny( 420 theRequest.getRestOperationType(), theRequest, inputResource, inputResourceId, null, thePointcut); 421 } 422 423 @Hook(Pointcut.STORAGE_PRESHOW_RESOURCES) 424 public void hookPreShow( 425 RequestDetails theRequestDetails, IPreResourceShowDetails theDetails, Pointcut thePointcut) { 426 for (int i = 0; i < theDetails.size(); i++) { 427 IBaseResource next = theDetails.getResource(i); 428 checkOutgoingResourceAndFailIfDeny(theRequestDetails, next, thePointcut); 429 } 430 } 431 432 @Hook(Pointcut.SERVER_OUTGOING_RESPONSE) 433 public void hookOutgoingResponse( 434 RequestDetails theRequestDetails, IBaseResource theResponseObject, Pointcut thePointcut) { 435 checkOutgoingResourceAndFailIfDeny(theRequestDetails, theResponseObject, thePointcut); 436 } 437 438 @Hook(Pointcut.STORAGE_CASCADE_DELETE) 439 public void hookCascadeDeleteForConflict( 440 RequestDetails theRequestDetails, Pointcut thePointcut, IBaseResource theResourceToDelete) { 441 Validate.notNull(theResourceToDelete); // just in case 442 checkPointcutAndFailIfDeny(theRequestDetails, thePointcut, theResourceToDelete); 443 } 444 445 @Hook(Pointcut.STORAGE_PRE_DELETE_EXPUNGE) 446 public void hookDeleteExpunge(RequestDetails theRequestDetails, Pointcut thePointcut) { 447 applyRulesAndFailIfDeny( 448 theRequestDetails.getRestOperationType(), theRequestDetails, null, null, null, thePointcut); 449 } 450 451 @Hook(Pointcut.STORAGE_INITIATE_BULK_EXPORT) 452 public void initiateBulkExport( 453 RequestDetails theRequestDetails, BulkExportJobParameters theBulkExportOptions, Pointcut thePointcut) { 454 // RestOperationTypeEnum restOperationType = 455 // determineRestOperationTypeFromBulkExportOptions(theBulkExportOptions); 456 RestOperationTypeEnum restOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_SERVER; 457 458 if (theRequestDetails != null) { 459 theRequestDetails.setAttribute(REQUEST_ATTRIBUTE_BULK_DATA_EXPORT_OPTIONS, theBulkExportOptions); 460 } 461 applyRulesAndFailIfDeny(restOperationType, theRequestDetails, null, null, null, thePointcut); 462 } 463 464 /** 465 * TODO GGG This method should eventually be used when invoking the rules applier.....however we currently rely on the incorrect 466 * behaviour of passing down `EXTENDED_OPERATION_SERVER`. 467 */ 468 private RestOperationTypeEnum determineRestOperationTypeFromBulkExportOptions( 469 BulkExportJobParameters theBulkExportOptions) { 470 RestOperationTypeEnum restOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_SERVER; 471 BulkExportJobParameters.ExportStyle exportStyle = theBulkExportOptions.getExportStyle(); 472 if (exportStyle.equals(BulkExportJobParameters.ExportStyle.SYSTEM)) { 473 restOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_SERVER; 474 } else if (exportStyle.equals(BulkExportJobParameters.ExportStyle.PATIENT)) { 475 if (theBulkExportOptions.getPatientIds().size() == 1) { 476 restOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE; 477 } else { 478 restOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_TYPE; 479 } 480 } else if (exportStyle.equals(BulkExportJobParameters.ExportStyle.GROUP)) { 481 restOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE; 482 } 483 return restOperationType; 484 } 485 486 private void checkPointcutAndFailIfDeny( 487 RequestDetails theRequestDetails, Pointcut thePointcut, @Nonnull IBaseResource theInputResource) { 488 applyRulesAndFailIfDeny( 489 theRequestDetails.getRestOperationType(), 490 theRequestDetails, 491 theInputResource, 492 theInputResource.getIdElement(), 493 null, 494 thePointcut); 495 } 496 497 private void checkOutgoingResourceAndFailIfDeny( 498 RequestDetails theRequestDetails, IBaseResource theResponseObject, Pointcut thePointcut) { 499 500 switch (determineOperationDirection(theRequestDetails.getRestOperationType())) { 501 case IN: 502 case NONE: 503 return; 504 case BOTH: 505 case OUT: 506 break; 507 } 508 509 // Don't check the value twice 510 IdentityHashMap<IBaseResource, Boolean> alreadySeenMap = getAlreadySeenResourcesMap(theRequestDetails); 511 if (alreadySeenMap.putIfAbsent(theResponseObject, Boolean.TRUE) != null) { 512 return; 513 } 514 515 FhirContext fhirContext = theRequestDetails.getServer().getFhirContext(); 516 List<IBaseResource> resources = Collections.emptyList(); 517 518 //noinspection EnumSwitchStatementWhichMissesCases 519 switch (theRequestDetails.getRestOperationType()) { 520 case SEARCH_SYSTEM: 521 case SEARCH_TYPE: 522 case HISTORY_INSTANCE: 523 case HISTORY_SYSTEM: 524 case HISTORY_TYPE: 525 case TRANSACTION: 526 case GET_PAGE: 527 case EXTENDED_OPERATION_SERVER: 528 case EXTENDED_OPERATION_TYPE: 529 case EXTENDED_OPERATION_INSTANCE: { 530 if (theResponseObject != null) { 531 resources = toListOfResourcesAndExcludeContainer(theResponseObject, fhirContext); 532 } 533 break; 534 } 535 default: { 536 if (theResponseObject != null) { 537 resources = Collections.singletonList(theResponseObject); 538 } 539 break; 540 } 541 } 542 543 for (IBaseResource nextResponse : resources) { 544 applyRulesAndFailIfDeny( 545 theRequestDetails.getRestOperationType(), theRequestDetails, null, null, nextResponse, thePointcut); 546 } 547 } 548 549 @Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED) 550 public void hookResourcePreCreate(RequestDetails theRequest, IBaseResource theResource, Pointcut thePointcut) { 551 handleUserOperation(theRequest, theResource, RestOperationTypeEnum.CREATE, thePointcut); 552 } 553 554 @Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_DELETED) 555 public void hookResourcePreDelete(RequestDetails theRequest, IBaseResource theResource, Pointcut thePointcut) { 556 handleUserOperation(theRequest, theResource, RestOperationTypeEnum.DELETE, thePointcut); 557 } 558 559 @Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED) 560 public void hookResourcePreUpdate( 561 RequestDetails theRequest, 562 IBaseResource theOldResource, 563 IBaseResource theNewResource, 564 Pointcut thePointcut) { 565 if (theOldResource != null) { 566 handleUserOperation(theRequest, theOldResource, RestOperationTypeEnum.UPDATE, thePointcut); 567 } 568 handleUserOperation(theRequest, theNewResource, RestOperationTypeEnum.UPDATE, thePointcut); 569 } 570 571 private enum OperationExamineDirection { 572 BOTH, 573 IN, 574 NONE, 575 OUT, 576 } 577 578 protected static List<IBaseResource> toListOfResourcesAndExcludeContainer( 579 IBaseResource theResponseObject, FhirContext fhirContext) { 580 if (theResponseObject == null) { 581 return Collections.emptyList(); 582 } 583 584 List<IBaseResource> retVal; 585 586 boolean shouldExamineChildResources = shouldExamineChildResources(theResponseObject, fhirContext); 587 if (!shouldExamineChildResources) { 588 return Collections.singletonList(theResponseObject); 589 } 590 591 retVal = fhirContext.newTerser().getAllPopulatedChildElementsOfType(theResponseObject, IBaseResource.class); 592 593 // Exclude the container 594 if (!retVal.isEmpty() && retVal.get(0) == theResponseObject) { 595 retVal = retVal.subList(1, retVal.size()); 596 } 597 598 // Don't apply security to OperationOutcome 599 retVal.removeIf(t -> t instanceof IBaseOperationOutcome); 600 601 return retVal; 602 } 603 604 /** 605 * This method determines if the given Resource should have permissions applied to the resources inside or 606 * to the Resource itself. 607 * For Parameters resources, we include child resources when checking the permissions. 608 * For Bundle resources, we only look at resources inside if the Bundle is of type document, collection, or message. 609 */ 610 protected static boolean shouldExamineChildResources(IBaseResource theResource, FhirContext theFhirContext) { 611 if (theResource instanceof IBaseParameters) { 612 return true; 613 } 614 if (theResource instanceof IBaseBundle) { 615 BundleTypeEnum bundleType = BundleUtil.getBundleTypeEnum(theFhirContext, ((IBaseBundle) theResource)); 616 boolean isStandaloneBundleResource = 617 bundleType != null && STANDALONE_BUNDLE_RESOURCE_TYPES.contains(bundleType); 618 return !isStandaloneBundleResource; 619 } 620 return false; 621 } 622 623 public static class Verdict { 624 625 private final IAuthRule myDecidingRule; 626 private final PolicyEnum myDecision; 627 628 public Verdict(PolicyEnum theDecision, IAuthRule theDecidingRule) { 629 Validate.notNull(theDecision); 630 631 myDecision = theDecision; 632 myDecidingRule = theDecidingRule; 633 } 634 635 IAuthRule getDecidingRule() { 636 return myDecidingRule; 637 } 638 639 public PolicyEnum getDecision() { 640 return myDecision; 641 } 642 643 @Override 644 public String toString() { 645 ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); 646 String ruleName; 647 if (myDecidingRule != null) { 648 ruleName = myDecidingRule.getName(); 649 } else { 650 ruleName = "(none)"; 651 } 652 b.append("rule", ruleName); 653 b.append("decision", myDecision.name()); 654 return b.build(); 655 } 656 } 657 658 private Object getPointcutNameOrEmpty(Pointcut thePointcut) { 659 return nonNull(thePointcut) ? thePointcut.name() : EMPTY; 660 } 661 662 private String getResourceTypeOrEmpty(IBaseResource theResource) { 663 String retVal = StringUtils.EMPTY; 664 665 if (isNull(theResource)) { 666 return retVal; 667 } 668 669 if (isNull(theResource.getIdElement())) { 670 return retVal; 671 } 672 673 if (isNull(theResource.getIdElement().getResourceType())) { 674 return retVal; 675 } 676 677 return theResource.getIdElement().getResourceType(); 678 } 679 680 @SuppressWarnings("unchecked") 681 private IdentityHashMap<IBaseResource, Boolean> getAlreadySeenResourcesMap(RequestDetails theRequestDetails) { 682 IdentityHashMap<IBaseResource, Boolean> alreadySeenResources = (IdentityHashMap<IBaseResource, Boolean>) 683 theRequestDetails.getUserData().get(myRequestSeenResourcesKey); 684 if (alreadySeenResources == null) { 685 alreadySeenResources = new IdentityHashMap<>(); 686 theRequestDetails.getUserData().put(myRequestSeenResourcesKey, alreadySeenResources); 687 } 688 return alreadySeenResources; 689 } 690}