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