001package ca.uhn.fhir.rest.server.interceptor.auth; 002 003/* 004 * #%L 005 * HAPI FHIR - Server Framework 006 * %% 007 * Copyright (C) 2014 - 2019 University Health Network 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.model.api.annotation.ResourceDef; 024import ca.uhn.fhir.model.primitive.IdDt; 025import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 026import ca.uhn.fhir.rest.api.server.RequestDetails; 027import com.google.common.collect.Lists; 028import org.apache.commons.lang3.Validate; 029import org.hl7.fhir.instance.model.api.IBaseResource; 030import org.hl7.fhir.instance.model.api.IIdType; 031 032import java.util.*; 033import java.util.concurrent.ConcurrentHashMap; 034 035import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; 036 037public class RuleBuilder implements IAuthRuleBuilder { 038 039 private static final String[] EMPTY_STRING_ARRAY = new String[0]; 040 private static final ConcurrentHashMap<Class<? extends IBaseResource>, String> ourTypeToName = new ConcurrentHashMap<>(); 041 private ArrayList<IAuthRule> myRules; 042 private IAuthRuleBuilderRule myAllow; 043 private IAuthRuleBuilderRule myDeny; 044 045 public RuleBuilder() { 046 myRules = new ArrayList<>(); 047 } 048 049 @Override 050 public IAuthRuleBuilderRule allow() { 051 if (myAllow == null) { 052 myAllow = allow(null); 053 } 054 return myAllow; 055 } 056 057 @Override 058 public IAuthRuleBuilderRule allow(String theRuleName) { 059 return new RuleBuilderRule(PolicyEnum.ALLOW, theRuleName); 060 } 061 062 @Override 063 public IAuthRuleBuilderRuleOpClassifierFinished allowAll() { 064 return allowAll(null); 065 } 066 067 @Override 068 public IAuthRuleBuilderRuleOpClassifierFinished allowAll(String theRuleName) { 069 RuleImplOp rule = new RuleImplOp(theRuleName); 070 myRules.add(rule.setOp(RuleOpEnum.ALLOW_ALL)); 071 return new RuleBuilderFinished(rule); 072 } 073 074 @Override 075 public List<IAuthRule> build() { 076 return myRules; 077 } 078 079 @Override 080 public IAuthRuleBuilderRule deny() { 081 if (myDeny == null) { 082 myDeny = deny(null); 083 } 084 return myDeny; 085 } 086 087 @Override 088 public IAuthRuleBuilderRule deny(String theRuleName) { 089 return new RuleBuilderRule(PolicyEnum.DENY, theRuleName); 090 } 091 092 @Override 093 public IAuthRuleBuilderRuleOpClassifierFinished denyAll() { 094 return denyAll(null); 095 } 096 097 @Override 098 public IAuthRuleBuilderRuleOpClassifierFinished denyAll(String theRuleName) { 099 RuleImplOp rule = new RuleImplOp(theRuleName); 100 myRules.add(rule.setOp(RuleOpEnum.DENY_ALL)); 101 return new RuleBuilderFinished(rule); 102 } 103 104 public interface ITenantApplicabilityChecker { 105 boolean applies(RequestDetails theRequest); 106 } 107 108 private class RuleBuilderFinished implements IAuthRuleFinished, IAuthRuleBuilderRuleOpClassifierFinished, IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId { 109 110 protected final BaseRule myOpRule; 111 ITenantApplicabilityChecker myTenantApplicabilityChecker; 112 private List<IAuthRuleTester> myTesters; 113 114 RuleBuilderFinished(BaseRule theRule) { 115 assert theRule != null; 116 myOpRule = theRule; 117 } 118 119 @Override 120 public IAuthRuleBuilder andThen() { 121 doBuildRule(); 122 return RuleBuilder.this; 123 } 124 125 @Override 126 public List<IAuthRule> build() { 127 doBuildRule(); 128 return myRules; 129 } 130 131 /** 132 * Subclasses may override 133 */ 134 protected void doBuildRule() { 135 // nothing 136 } 137 138 @Override 139 public IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId forTenantIds(String... theTenantIds) { 140 return forTenantIds(Arrays.asList(defaultIfNull(theTenantIds, EMPTY_STRING_ARRAY))); 141 } 142 143 @Override 144 public IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId forTenantIds(final Collection<String> theTenantIds) { 145 setTenantApplicabilityChecker(theRequest -> theTenantIds.contains(theRequest.getTenantId())); 146 return this; 147 } 148 149 List<IAuthRuleTester> getTesters() { 150 if (myTesters == null) { 151 return Collections.emptyList(); 152 } 153 return myTesters; 154 } 155 156 @Override 157 public IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId notForTenantIds(String... theTenantIds) { 158 return notForTenantIds(Arrays.asList(defaultIfNull(theTenantIds, EMPTY_STRING_ARRAY))); 159 } 160 161 @Override 162 public IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId notForTenantIds(final Collection<String> theTenantIds) { 163 setTenantApplicabilityChecker(theRequest -> !theTenantIds.contains(theRequest.getTenantId())); 164 return this; 165 } 166 167 private void setTenantApplicabilityChecker(ITenantApplicabilityChecker theTenantApplicabilityChecker) { 168 myTenantApplicabilityChecker = theTenantApplicabilityChecker; 169 myOpRule.setTenantApplicabilityChecker(myTenantApplicabilityChecker); 170 } 171 172 @Override 173 public IAuthRuleFinished withTester(IAuthRuleTester theTester) { 174 if (myTesters == null) { 175 myTesters = new ArrayList<>(); 176 } 177 myTesters.add(theTester); 178 myOpRule.addTester(theTester); 179 180 return this; 181 } 182 } 183 184 private class RuleBuilderRule implements IAuthRuleBuilderRule { 185 186 private PolicyEnum myRuleMode; 187 private String myRuleName; 188 private RuleBuilderRuleOp myReadRuleBuilder; 189 private RuleBuilderRuleOp myWriteRuleBuilder; 190 191 RuleBuilderRule(PolicyEnum theRuleMode, String theRuleName) { 192 myRuleMode = theRuleMode; 193 myRuleName = theRuleName; 194 } 195 196 @Override 197 public IAuthRuleBuilderRuleConditional createConditional() { 198 return new RuleBuilderRuleConditional(RestOperationTypeEnum.CREATE); 199 } 200 201 @Override 202 public IAuthRuleBuilderRuleOpDelete delete() { 203 return new RuleBuilderRuleOp(RuleOpEnum.DELETE); 204 } 205 206 @Override 207 public IAuthRuleBuilderRuleConditional deleteConditional() { 208 return new RuleBuilderRuleConditional(RestOperationTypeEnum.DELETE); 209 } 210 211 @Override 212 public RuleBuilderFinished metadata() { 213 RuleImplOp rule = new RuleImplOp(myRuleName); 214 rule.setOp(RuleOpEnum.METADATA); 215 rule.setMode(myRuleMode); 216 myRules.add(rule); 217 return new RuleBuilderFinished(rule); 218 } 219 220 @Override 221 public IAuthRuleBuilderOperation operation() { 222 return new RuleBuilderRuleOperation(); 223 } 224 225 @Override 226 public IAuthRuleBuilderPatch patch() { 227 return new PatchBuilder(); 228 } 229 230 @Override 231 public IAuthRuleBuilderRuleOp read() { 232 if (myReadRuleBuilder == null) { 233 myReadRuleBuilder = new RuleBuilderRuleOp(RuleOpEnum.READ); 234 } 235 return myReadRuleBuilder; 236 } 237 238 @Override 239 public IAuthRuleBuilderRuleTransaction transaction() { 240 return new RuleBuilderRuleTransaction(); 241 } 242 243 @Override 244 public IAuthRuleBuilderRuleConditional updateConditional() { 245 return new RuleBuilderRuleConditional(RestOperationTypeEnum.UPDATE); 246 } 247 248 @Override 249 public IAuthRuleBuilderRuleOp write() { 250 if (myWriteRuleBuilder == null) { 251 myWriteRuleBuilder = new RuleBuilderRuleOp(RuleOpEnum.WRITE); 252 } 253 return myWriteRuleBuilder; 254 } 255 256 @Override 257 public IAuthRuleBuilderRuleOp create() { 258 if (myWriteRuleBuilder == null) { 259 myWriteRuleBuilder = new RuleBuilderRuleOp(RuleOpEnum.CREATE); 260 } 261 return myWriteRuleBuilder; 262 } 263 264 @Override 265 public IAuthRuleBuilderGraphQL graphQL() { 266 return new RuleBuilderGraphQL(); 267 } 268 269 private class RuleBuilderRuleConditional implements IAuthRuleBuilderRuleConditional { 270 271 private AppliesTypeEnum myAppliesTo; 272 private Set<String> myAppliesToTypes; 273 private RestOperationTypeEnum myOperationType; 274 275 RuleBuilderRuleConditional(RestOperationTypeEnum theOperationType) { 276 myOperationType = theOperationType; 277 } 278 279 @Override 280 public IAuthRuleBuilderRuleConditionalClassifier allResources() { 281 myAppliesTo = AppliesTypeEnum.ALL_RESOURCES; 282 return new RuleBuilderRuleConditionalClassifier(); 283 } 284 285 @Override 286 public IAuthRuleBuilderRuleConditionalClassifier resourcesOfType(Class<? extends IBaseResource> theType) { 287 Validate.notNull(theType, "theType must not be null"); 288 289 String typeName = toTypeName(theType); 290 return resourcesOfType(typeName); 291 } 292 293 @Override 294 public IAuthRuleBuilderRuleConditionalClassifier resourcesOfType(String theType) { 295 myAppliesTo = AppliesTypeEnum.TYPES; 296 myAppliesToTypes = Collections.singleton(theType); 297 return new RuleBuilderRuleConditionalClassifier(); 298 } 299 300 public class RuleBuilderRuleConditionalClassifier extends RuleBuilderFinished implements IAuthRuleBuilderRuleConditionalClassifier { 301 302 RuleBuilderRuleConditionalClassifier() { 303 super(new RuleImplConditional(myRuleName)); 304 } 305 306 @Override 307 protected void doBuildRule() { 308 RuleImplConditional rule = (RuleImplConditional) myOpRule; 309 rule.setMode(myRuleMode); 310 rule.setOperationType(myOperationType); 311 rule.setAppliesTo(myAppliesTo); 312 rule.setAppliesToTypes(myAppliesToTypes); 313 rule.setTenantApplicabilityChecker(myTenantApplicabilityChecker); 314 rule.addTesters(getTesters()); 315 myRules.add(rule); 316 317 } 318 } 319 320 } 321 322 private class RuleBuilderRuleOp implements IAuthRuleBuilderRuleOp, IAuthRuleBuilderRuleOpDelete { 323 324 private final RuleOpEnum myRuleOp; 325 private RuleBuilderRuleOpClassifier myInstancesBuilder; 326 private boolean myOnCascade; 327 328 RuleBuilderRuleOp(RuleOpEnum theRuleOp) { 329 myRuleOp = theRuleOp; 330 } 331 332 @Override 333 public IAuthRuleBuilderRuleOpClassifier allResources() { 334 return new RuleBuilderRuleOpClassifier(AppliesTypeEnum.ALL_RESOURCES, null); 335 } 336 337 @Override 338 public IAuthRuleFinished instance(String theId) { 339 Validate.notBlank(theId, "theId must not be null or empty"); 340 return instance(new IdDt(theId)); 341 } 342 343 @Override 344 public IAuthRuleFinished instance(IIdType theId) { 345 Validate.notNull(theId, "theId must not be null"); 346 Validate.notBlank(theId.getValue(), "theId.getValue() must not be null or empty"); 347 Validate.notBlank(theId.getIdPart(), "theId must contain an ID part"); 348 349 List<IIdType> instances = Lists.newArrayList(theId); 350 return instances(instances); 351 } 352 353 @Override 354 public RuleBuilderFinished instances(Collection<IIdType> theInstances) { 355 Validate.notNull(theInstances, "theInstances must not be null"); 356 Validate.notEmpty(theInstances, "theInstances must not be empty"); 357 358 if (myInstancesBuilder == null) { 359 RuleBuilderRuleOpClassifier instancesBuilder = new RuleBuilderRuleOpClassifier(theInstances); 360 myInstancesBuilder = instancesBuilder; 361 return instancesBuilder.finished(); 362 } else { 363 return myInstancesBuilder.addInstances(theInstances); 364 } 365 } 366 367 368 @Override 369 public IAuthRuleBuilderRuleOpClassifier resourcesOfType(Class<? extends IBaseResource> theType) { 370 Validate.notNull(theType, "theType must not be null"); 371 String resourceName = toTypeName(theType); 372 return resourcesOfType(resourceName); 373 } 374 375 @Override 376 public IAuthRuleBuilderRuleOpClassifier resourcesOfType(String theType) { 377 Validate.notNull(theType, "theType must not be null"); 378 return new RuleBuilderRuleOpClassifier(AppliesTypeEnum.TYPES, Collections.singleton(theType)); 379 } 380 381 @Override 382 public IAuthRuleBuilderRuleOp onCascade() { 383 myOnCascade = true; 384 return this; 385 } 386 387 private class RuleBuilderRuleOpClassifier implements IAuthRuleBuilderRuleOpClassifier { 388 389 private final AppliesTypeEnum myAppliesTo; 390 private final Set<String> myAppliesToTypes; 391 private ClassifierTypeEnum myClassifierType; 392 private String myInCompartmentName; 393 private Collection<? extends IIdType> myInCompartmentOwners; 394 private Collection<IIdType> myAppliesToInstances; 395 private RuleImplOp myRule; 396 397 /** 398 * Constructor 399 */ 400 RuleBuilderRuleOpClassifier(AppliesTypeEnum theAppliesTo, Set<String> theAppliesToTypes) { 401 super(); 402 myAppliesTo = theAppliesTo; 403 myAppliesToTypes = theAppliesToTypes; 404 } 405 406 /** 407 * Constructor 408 */ 409 RuleBuilderRuleOpClassifier(Collection<IIdType> theAppliesToInstances) { 410 myAppliesToInstances = theAppliesToInstances; 411 myAppliesTo = AppliesTypeEnum.INSTANCES; 412 myAppliesToTypes = null; 413 } 414 415 private RuleBuilderFinished finished() { 416 Validate.isTrue(myRule == null, "Can not call finished() twice"); 417 myRule = new RuleImplOp(myRuleName); 418 myRule.setMode(myRuleMode); 419 myRule.setOp(myRuleOp); 420 myRule.setAppliesTo(myAppliesTo); 421 myRule.setAppliesToTypes(myAppliesToTypes); 422 myRule.setAppliesToInstances(myAppliesToInstances); 423 myRule.setClassifierType(myClassifierType); 424 myRule.setClassifierCompartmentName(myInCompartmentName); 425 myRule.setClassifierCompartmentOwners(myInCompartmentOwners); 426 myRule.setAppliesToDeleteCascade(myOnCascade); 427 myRules.add(myRule); 428 429 return new RuleBuilderFinished(myRule); 430 } 431 432 @Override 433 public IAuthRuleBuilderRuleOpClassifierFinished inCompartment(String theCompartmentName, Collection<? extends IIdType> theOwners) { 434 Validate.notBlank(theCompartmentName, "theCompartmentName must not be null"); 435 Validate.notNull(theOwners, "theOwners must not be null"); 436 Validate.noNullElements(theOwners, "theOwners must not contain any null elements"); 437 for (IIdType next : theOwners) { 438 validateOwner(next); 439 } 440 myInCompartmentName = theCompartmentName; 441 myInCompartmentOwners = theOwners; 442 myClassifierType = ClassifierTypeEnum.IN_COMPARTMENT; 443 return finished(); 444 } 445 446 @Override 447 public IAuthRuleBuilderRuleOpClassifierFinished inCompartment(String theCompartmentName, IIdType theOwner) { 448 Validate.notBlank(theCompartmentName, "theCompartmentName must not be null"); 449 Validate.notNull(theOwner, "theOwner must not be null"); 450 validateOwner(theOwner); 451 myInCompartmentName = theCompartmentName; 452 myInCompartmentOwners = Collections.singletonList(theOwner); 453 myClassifierType = ClassifierTypeEnum.IN_COMPARTMENT; 454 return finished(); 455 } 456 457 private void validateOwner(IIdType theOwner) { 458 Validate.notBlank(theOwner.getIdPart(), "owner.getIdPart() must not be null or empty"); 459 Validate.notBlank(theOwner.getIdPart(), "owner.getResourceType() must not be null or empty"); 460 } 461 462 @Override 463 public IAuthRuleBuilderRuleOpClassifierFinished withAnyId() { 464 myClassifierType = ClassifierTypeEnum.ANY_ID; 465 return finished(); 466 } 467 468 RuleBuilderFinished addInstances(Collection<IIdType> theInstances) { 469 myAppliesToInstances.addAll(theInstances); 470 return new RuleBuilderFinished(myRule); 471 } 472 } 473 474 } 475 476 private class RuleBuilderRuleOperation implements IAuthRuleBuilderOperation { 477 478 @Override 479 public IAuthRuleBuilderOperationNamed named(String theOperationName) { 480 Validate.notBlank(theOperationName, "theOperationName must not be null or empty"); 481 return new RuleBuilderRuleOperationNamed(theOperationName); 482 } 483 484 @Override 485 public IAuthRuleBuilderOperationNamed withAnyName() { 486 return new RuleBuilderRuleOperationNamed(null); 487 } 488 489 private class RuleBuilderRuleOperationNamed implements IAuthRuleBuilderOperationNamed { 490 491 private String myOperationName; 492 493 RuleBuilderRuleOperationNamed(String theOperationName) { 494 if (theOperationName != null && !theOperationName.startsWith("$")) { 495 myOperationName = '$' + theOperationName; 496 } else { 497 myOperationName = theOperationName; 498 } 499 } 500 501 private OperationRule createRule() { 502 OperationRule rule = new OperationRule(myRuleName); 503 rule.setOperationName(myOperationName); 504 rule.setMode(myRuleMode); 505 return rule; 506 } 507 508 @Override 509 public IAuthRuleBuilderOperationNamedAndScoped onAnyInstance() { 510 OperationRule rule = createRule(); 511 rule.appliesToAnyInstance(); 512 return new RuleBuilderOperationNamedAndScoped(rule); 513 } 514 515 @Override 516 public IAuthRuleBuilderOperationNamedAndScoped atAnyLevel() { 517 OperationRule rule = createRule(); 518 rule.appliesAtAnyLevel(true); 519 return new RuleBuilderOperationNamedAndScoped(rule); 520 } 521 522 @Override 523 public IAuthRuleBuilderOperationNamedAndScoped onAnyType() { 524 OperationRule rule = createRule(); 525 rule.appliesToAnyType(); 526 return new RuleBuilderOperationNamedAndScoped(rule); 527 } 528 529 @Override 530 public IAuthRuleBuilderOperationNamedAndScoped onInstance(IIdType theInstanceId) { 531 Validate.notNull(theInstanceId, "theInstanceId must not be null"); 532 Validate.notBlank(theInstanceId.getResourceType(), "theInstanceId does not have a resource type"); 533 Validate.notBlank(theInstanceId.getIdPart(), "theInstanceId does not have an ID part"); 534 535 OperationRule rule = createRule(); 536 ArrayList<IIdType> ids = new ArrayList<>(); 537 ids.add(theInstanceId); 538 rule.appliesToInstances(ids); 539 return new RuleBuilderOperationNamedAndScoped(rule); 540 } 541 542 @Override 543 public IAuthRuleBuilderOperationNamedAndScoped onInstancesOfType(Class<? extends IBaseResource> theType) { 544 validateType(theType); 545 546 OperationRule rule = createRule(); 547 rule.appliesToInstancesOfType(toTypeSet(theType)); 548 return new RuleBuilderOperationNamedAndScoped(rule); 549 } 550 551 @Override 552 public IAuthRuleBuilderOperationNamedAndScoped onServer() { 553 OperationRule rule = createRule(); 554 rule.appliesToServer(); 555 return new RuleBuilderOperationNamedAndScoped(rule); 556 } 557 558 @Override 559 public IAuthRuleBuilderOperationNamedAndScoped onType(Class<? extends IBaseResource> theType) { 560 validateType(theType); 561 562 OperationRule rule = createRule(); 563 rule.appliesToTypes(toTypeSet(theType)); 564 return new RuleBuilderOperationNamedAndScoped(rule); 565 } 566 567 private HashSet<Class<? extends IBaseResource>> toTypeSet(Class<? extends IBaseResource> theType) { 568 HashSet<Class<? extends IBaseResource>> appliesToTypes = new HashSet<>(); 569 appliesToTypes.add(theType); 570 return appliesToTypes; 571 } 572 573 private void validateType(Class<? extends IBaseResource> theType) { 574 Validate.notNull(theType, "theType must not be null"); 575 } 576 577 private class RuleBuilderOperationNamedAndScoped implements IAuthRuleBuilderOperationNamedAndScoped { 578 579 private final OperationRule myRule; 580 581 RuleBuilderOperationNamedAndScoped(OperationRule theRule) { 582 myRule = theRule; 583 } 584 585 @Override 586 public IAuthRuleBuilderRuleOpClassifierFinished andAllowAllResponses() { 587 myRule.allowAllResponses(); 588 myRules.add(myRule); 589 return new RuleBuilderFinished(myRule); 590 } 591 592 @Override 593 public IAuthRuleBuilderRuleOpClassifierFinished andRequireExplicitResponseAuthorization() { 594 myRules.add(myRule); 595 return new RuleBuilderFinished(myRule); 596 } 597 } 598 599 } 600 601 } 602 603 private class RuleBuilderRuleTransaction implements IAuthRuleBuilderRuleTransaction { 604 605 @Override 606 public IAuthRuleBuilderRuleTransactionOp withAnyOperation() { 607 return new RuleBuilderRuleTransactionOp(); 608 } 609 610 private class RuleBuilderRuleTransactionOp implements IAuthRuleBuilderRuleTransactionOp { 611 612 @Override 613 public IAuthRuleBuilderRuleOpClassifierFinished andApplyNormalRules() { 614 // Allow transaction 615 RuleImplOp rule = new RuleImplOp(myRuleName); 616 rule.setMode(myRuleMode); 617 rule.setOp(RuleOpEnum.TRANSACTION); 618 rule.setTransactionAppliesToOp(TransactionAppliesToEnum.ANY_OPERATION); 619 myRules.add(rule); 620 return new RuleBuilderFinished(rule); 621 } 622 623 } 624 625 } 626 627 private class PatchBuilder implements IAuthRuleBuilderPatch { 628 629 PatchBuilder() { 630 super(); 631 } 632 633 @Override 634 public IAuthRuleFinished allRequests() { 635 BaseRule rule = new RuleImplPatch(myRuleName) 636 .setAllRequests(true) 637 .setMode(myRuleMode); 638 myRules.add(rule); 639 return new RuleBuilderFinished(rule); 640 } 641 } 642 643 private class RuleBuilderGraphQL implements IAuthRuleBuilderGraphQL { 644 @Override 645 public IAuthRuleFinished any() { 646 RuleImplOp rule = new RuleImplOp(myRuleName); 647 rule.setOp(RuleOpEnum.GRAPHQL); 648 rule.setMode(myRuleMode); 649 myRules.add(rule); 650 return new RuleBuilderFinished(rule); 651 } 652 } 653 } 654 655 private static String toTypeName(Class<? extends IBaseResource> theType) { 656 String retVal = ourTypeToName.get(theType); 657 if (retVal == null) { 658 ResourceDef resourceDef = theType.getAnnotation(ResourceDef.class); 659 retVal = resourceDef.name(); 660 Validate.notBlank(retVal, "Could not determine resource type of class %s", theType); 661 ourTypeToName.put(theType, retVal); 662 } 663 return retVal; 664 } 665 666}