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}