001/*
002 * #%L
003 * HAPI FHIR - Server Framework
004 * %%
005 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 * http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.rest.server.interceptor.auth;
021
022import ca.uhn.fhir.interceptor.model.RequestPartitionId;
023import ca.uhn.fhir.model.api.annotation.ResourceDef;
024import ca.uhn.fhir.model.primitive.IdDt;
025import ca.uhn.fhir.rest.api.Constants;
026import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
027import ca.uhn.fhir.rest.api.server.RequestDetails;
028import ca.uhn.fhir.rest.server.provider.ProviderConstants;
029import com.google.common.collect.Lists;
030import jakarta.annotation.Nonnull;
031import org.apache.commons.lang3.Validate;
032import org.hl7.fhir.instance.model.api.IBaseResource;
033import org.hl7.fhir.instance.model.api.IIdType;
034
035import java.util.ArrayList;
036import java.util.Arrays;
037import java.util.Collection;
038import java.util.Collections;
039import java.util.HashSet;
040import java.util.List;
041import java.util.Optional;
042import java.util.Set;
043import java.util.concurrent.ConcurrentHashMap;
044
045import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
046
047public class RuleBuilder implements IAuthRuleBuilder {
048
049        private static final ConcurrentHashMap<Class<? extends IBaseResource>, String> ourTypeToName =
050                        new ConcurrentHashMap<>();
051        private final ArrayList<IAuthRule> myRules;
052        private IAuthRuleBuilderRule myAllow;
053        private IAuthRuleBuilderRule myDeny;
054
055        public RuleBuilder() {
056                myRules = new ArrayList<>();
057        }
058
059        @Override
060        public IAuthRuleBuilderRule allow() {
061                if (myAllow == null) {
062                        myAllow = allow(null);
063                }
064                return myAllow;
065        }
066
067        @Override
068        public IAuthRuleBuilderRule allow(String theRuleName) {
069                return new RuleBuilderRule(PolicyEnum.ALLOW, theRuleName);
070        }
071
072        @Override
073        public IAuthRuleBuilderRuleOpClassifierFinished allowAll() {
074                return allowAll(null);
075        }
076
077        @Override
078        public IAuthRuleBuilderRuleOpClassifierFinished allowAll(String theRuleName) {
079                RuleImplOp rule = new RuleImplOp(theRuleName);
080                rule.setOp(RuleOpEnum.ALL);
081                rule.setMode(PolicyEnum.ALLOW);
082                myRules.add(rule);
083                return new RuleBuilderFinished(rule);
084        }
085
086        @Override
087        public List<IAuthRule> build() {
088                return myRules;
089        }
090
091        @Override
092        public IAuthRuleBuilderRule deny() {
093                if (myDeny == null) {
094                        myDeny = deny(null);
095                }
096                return myDeny;
097        }
098
099        @Override
100        public IAuthRuleBuilderRule deny(String theRuleName) {
101                return new RuleBuilderRule(PolicyEnum.DENY, theRuleName);
102        }
103
104        @Override
105        public IAuthRuleBuilderRuleOpClassifierFinished denyAll() {
106                return denyAll(null);
107        }
108
109        @Override
110        public IAuthRuleBuilderRuleOpClassifierFinished denyAll(String theRuleName) {
111                RuleImplOp rule = new RuleImplOp(theRuleName);
112                rule.setOp(RuleOpEnum.ALL);
113                rule.setMode(PolicyEnum.DENY);
114                myRules.add(rule);
115                return new RuleBuilderFinished(rule);
116        }
117
118        private class RuleBuilderFinished
119                        implements IAuthRuleFinished,
120                                        IAuthRuleBuilderRuleOpClassifierFinished,
121                                        IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId {
122
123                protected final BaseRule myOpRule;
124                private List<IAuthRuleTester> myTesters;
125
126                RuleBuilderFinished(BaseRule theRule) {
127                        assert theRule != null;
128                        myOpRule = theRule;
129                }
130
131                @Override
132                public IAuthRuleBuilder andThen() {
133                        doBuildRule();
134                        return RuleBuilder.this;
135                }
136
137                @Override
138                public List<IAuthRule> build() {
139                        doBuildRule();
140                        return myRules;
141                }
142
143                /**
144                 * Subclasses may override
145                 */
146                protected void doBuildRule() {
147                        // nothing
148                }
149
150                @Override
151                public IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId forTenantIds(String... theTenantIds) {
152                        return forTenantIds(Arrays.asList(defaultIfNull(theTenantIds, Constants.EMPTY_STRING_ARRAY)));
153                }
154
155                @Override
156                public IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId forTenantIds(
157                                final Collection<String> theTenantIds) {
158                        withTester(new TenantCheckingTester(theTenantIds, true));
159                        return this;
160                }
161
162                List<IAuthRuleTester> getTesters() {
163                        if (myTesters == null) {
164                                return Collections.emptyList();
165                        }
166                        return myTesters;
167                }
168
169                @Override
170                public IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId notForTenantIds(String... theTenantIds) {
171                        return notForTenantIds(Arrays.asList(defaultIfNull(theTenantIds, Constants.EMPTY_STRING_ARRAY)));
172                }
173
174                @Override
175                public IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId notForTenantIds(
176                                final Collection<String> theTenantIds) {
177                        withTester(new TenantCheckingTester(theTenantIds, false));
178                        return this;
179                }
180
181                @Override
182                public IAuthRuleFinished withTester(IAuthRuleTester theTester) {
183                        if (theTester != null) {
184                                if (myTesters == null) {
185                                        myTesters = new ArrayList<>();
186                                }
187                                myTesters.add(theTester);
188                                myOpRule.addTester(theTester);
189                        }
190
191                        return this;
192                }
193
194                @Override
195                public IAuthRuleFinished withFilterTester(String theQueryParameters) {
196                        return withTester(new FhirQueryRuleTester(theQueryParameters));
197                }
198
199                private class TenantCheckingTester implements IAuthRuleTester {
200                        private final Collection<String> myTenantIds;
201                        private final boolean myOutcome;
202
203                        public TenantCheckingTester(Collection<String> theTenantIds, boolean theOutcome) {
204                                myTenantIds = theTenantIds;
205                                myOutcome = theOutcome;
206                        }
207
208                        @Override
209                        public boolean matches(
210                                        RestOperationTypeEnum theOperation,
211                                        RequestDetails theRequestDetails,
212                                        IIdType theInputResourceId,
213                                        IBaseResource theInputResource) {
214                                if (!myTenantIds.contains(theRequestDetails.getTenantId())) {
215                                        return !myOutcome;
216                                }
217
218                                return matchesResource(theInputResource);
219                        }
220
221                        @Override
222                        public boolean matchesOutput(
223                                        RestOperationTypeEnum theOperation,
224                                        RequestDetails theRequestDetails,
225                                        IBaseResource theOutputResource) {
226                                if (!myTenantIds.contains(theRequestDetails.getTenantId())) {
227                                        return !myOutcome;
228                                }
229
230                                return matchesResource(theOutputResource);
231                        }
232
233                        private boolean matchesResource(IBaseResource theResource) {
234                                if (theResource != null) {
235                                        RequestPartitionId partitionId =
236                                                        (RequestPartitionId) theResource.getUserData(Constants.RESOURCE_PARTITION_ID);
237                                        if (partitionId != null) {
238                                                if (partitionId.hasDefaultPartitionId()
239                                                                && myTenantIds.contains(ProviderConstants.DEFAULT_PARTITION_NAME)) {
240                                                        return myOutcome;
241                                                }
242
243                                                String partitionNameOrNull = partitionId.getFirstPartitionNameOrNull();
244                                                if (partitionNameOrNull == null || !myTenantIds.contains(partitionNameOrNull)) {
245                                                        return !myOutcome;
246                                                }
247                                        }
248                                }
249
250                                return myOutcome;
251                        }
252                }
253        }
254
255        private class RuleBuilderRule implements IAuthRuleBuilderRule {
256
257                private final PolicyEnum myRuleMode;
258                private final String myRuleName;
259                private RuleBuilderRuleOp myReadRuleBuilder;
260                private RuleBuilderRuleOp myWriteRuleBuilder;
261                private RuleBuilderBulkExport ruleBuilderBulkExport;
262
263                RuleBuilderRule(PolicyEnum theRuleMode, String theRuleName) {
264                        myRuleMode = theRuleMode;
265                        myRuleName = theRuleName;
266                }
267
268                @Override
269                public IAuthRuleBuilderRuleConditional createConditional() {
270                        return new RuleBuilderRuleConditional(RestOperationTypeEnum.CREATE);
271                }
272
273                @Override
274                public IAuthRuleBuilderRuleOpDelete delete() {
275                        return new RuleBuilderRuleOp(RuleOpEnum.DELETE);
276                }
277
278                @Override
279                public IAuthRuleBuilderRuleConditional deleteConditional() {
280                        return new RuleBuilderRuleConditional(RestOperationTypeEnum.DELETE);
281                }
282
283                @Override
284                public RuleBuilderFinished metadata() {
285                        RuleImplOp rule = new RuleImplOp(myRuleName);
286                        rule.setOp(RuleOpEnum.METADATA);
287                        rule.setMode(myRuleMode);
288                        myRules.add(rule);
289                        return new RuleBuilderFinished(rule);
290                }
291
292                @Override
293                public IAuthRuleBuilderOperation operation() {
294                        return new RuleBuilderRuleOperation();
295                }
296
297                @Override
298                public IAuthRuleBuilderPatch patch() {
299                        return new PatchBuilder();
300                }
301
302                @Override
303                public IAuthRuleBuilderRuleOp read() {
304                        if (myReadRuleBuilder == null) {
305                                myReadRuleBuilder = new RuleBuilderRuleOp(RuleOpEnum.READ);
306                        }
307                        return myReadRuleBuilder;
308                }
309
310                @Override
311                public IAuthRuleBuilderRuleTransaction transaction() {
312                        return new RuleBuilderRuleTransaction();
313                }
314
315                @Override
316                public IAuthRuleBuilderRuleConditional updateConditional() {
317                        return new RuleBuilderRuleConditional(RestOperationTypeEnum.UPDATE);
318                }
319
320                @Override
321                public IAuthRuleBuilderRuleOp write() {
322                        if (myWriteRuleBuilder == null) {
323                                myWriteRuleBuilder = new RuleBuilderRuleOp(RuleOpEnum.WRITE);
324                        }
325                        return myWriteRuleBuilder;
326                }
327
328                @Override
329                public IAuthRuleBuilderRuleOp create() {
330                        if (myWriteRuleBuilder == null) {
331                                myWriteRuleBuilder = new RuleBuilderRuleOp(RuleOpEnum.CREATE);
332                        }
333                        return myWriteRuleBuilder;
334                }
335
336                @Override
337                public IAuthRuleBuilderGraphQL graphQL() {
338                        return new RuleBuilderGraphQL();
339                }
340
341                @Override
342                public IAuthRuleBuilderRuleBulkExport bulkExport() {
343                        if (ruleBuilderBulkExport == null) {
344                                ruleBuilderBulkExport = new RuleBuilderBulkExport();
345                        }
346                        return ruleBuilderBulkExport;
347                }
348
349                @Override
350                public IAuthRuleBuilderUpdateHistoryRewrite updateHistoryRewrite() {
351                        return new UpdateHistoryRewriteBuilder();
352                }
353
354                private class RuleBuilderRuleConditional implements IAuthRuleBuilderRuleConditional {
355
356                        private AppliesTypeEnum myAppliesTo;
357                        private Set<String> myAppliesToTypes;
358                        private final RestOperationTypeEnum myOperationType;
359
360                        RuleBuilderRuleConditional(RestOperationTypeEnum theOperationType) {
361                                myOperationType = theOperationType;
362                        }
363
364                        @Override
365                        public IAuthRuleBuilderRuleConditionalClassifier allResources() {
366                                myAppliesTo = AppliesTypeEnum.ALL_RESOURCES;
367                                return new RuleBuilderRuleConditionalClassifier();
368                        }
369
370                        @Override
371                        public IAuthRuleBuilderRuleConditionalClassifier resourcesOfType(Class<? extends IBaseResource> theType) {
372                                Validate.notNull(theType, "theType must not be null");
373
374                                String typeName = toTypeName(theType);
375                                return resourcesOfType(typeName);
376                        }
377
378                        @Override
379                        public IAuthRuleBuilderRuleConditionalClassifier resourcesOfType(String theType) {
380                                myAppliesTo = AppliesTypeEnum.TYPES;
381                                myAppliesToTypes = Collections.singleton(theType);
382                                return new RuleBuilderRuleConditionalClassifier();
383                        }
384
385                        public class RuleBuilderRuleConditionalClassifier extends RuleBuilderFinished
386                                        implements IAuthRuleBuilderRuleConditionalClassifier {
387
388                                RuleBuilderRuleConditionalClassifier() {
389                                        super(new RuleImplConditional(myRuleName));
390                                }
391
392                                @Override
393                                protected void doBuildRule() {
394                                        RuleImplConditional rule = (RuleImplConditional) myOpRule;
395                                        rule.setMode(myRuleMode);
396                                        rule.setOperationType(myOperationType);
397                                        rule.setAppliesTo(myAppliesTo);
398                                        rule.setAppliesToTypes(myAppliesToTypes);
399                                        rule.addTesters(getTesters());
400                                        myRules.add(rule);
401                                }
402                        }
403                }
404
405                private class RuleBuilderRuleOp implements IAuthRuleBuilderRuleOp, IAuthRuleBuilderRuleOpDelete {
406
407                        private final RuleOpEnum myRuleOp;
408                        private RuleBuilderRuleOpClassifier myInstancesBuilder;
409                        private boolean myOnCascade;
410                        private boolean myOnExpunge;
411
412                        RuleBuilderRuleOp(RuleOpEnum theRuleOp) {
413                                myRuleOp = theRuleOp;
414                        }
415
416                        @Override
417                        public IAuthRuleBuilderRuleOpClassifier allResources() {
418                                return new RuleBuilderRuleOpClassifier(AppliesTypeEnum.ALL_RESOURCES, null);
419                        }
420
421                        @Override
422                        public IAuthRuleFinished instance(String theId) {
423                                Validate.notBlank(theId, "theId must not be null or empty");
424                                return instance(new IdDt(theId));
425                        }
426
427                        @Override
428                        public IAuthRuleFinished instance(IIdType theId) {
429                                Validate.notNull(theId, "theId must not be null");
430                                Validate.notBlank(theId.getValue(), "theId.getValue() must not be null or empty");
431                                Validate.notBlank(theId.getIdPart(), "theId must contain an ID part");
432
433                                List<IIdType> instances = Lists.newArrayList(theId);
434                                return instances(instances);
435                        }
436
437                        @Override
438                        public RuleBuilderFinished instances(Collection<IIdType> theInstances) {
439                                Validate.notNull(theInstances, "theInstances must not be null");
440                                Validate.notEmpty(theInstances, "theInstances must not be empty");
441
442                                if (myInstancesBuilder == null) {
443                                        RuleBuilderRuleOpClassifier instancesBuilder = new RuleBuilderRuleOpClassifier(theInstances);
444                                        myInstancesBuilder = instancesBuilder;
445                                        return instancesBuilder.finished();
446                                } else {
447                                        return myInstancesBuilder.addInstances(theInstances);
448                                }
449                        }
450
451                        @Override
452                        public IAuthRuleBuilderRuleOpClassifier resourcesOfType(Class<? extends IBaseResource> theType) {
453                                Validate.notNull(theType, "theType must not be null");
454                                String resourceName = toTypeName(theType);
455                                return resourcesOfType(resourceName);
456                        }
457
458                        @Override
459                        public IAuthRuleBuilderRuleOpClassifier resourcesOfType(String theType) {
460                                Validate.notNull(theType, "theType must not be null");
461                                return new RuleBuilderRuleOpClassifier(AppliesTypeEnum.TYPES, Collections.singleton(theType));
462                        }
463
464                        @Override
465                        public IAuthRuleBuilderRuleOp onCascade() {
466                                myOnCascade = true;
467                                return this;
468                        }
469
470                        @Override
471                        public IAuthRuleBuilderRuleOp onExpunge() {
472                                myOnExpunge = true;
473                                return this;
474                        }
475
476                        private class RuleBuilderRuleOpClassifier implements IAuthRuleBuilderRuleOpClassifier {
477
478                                private final AppliesTypeEnum myAppliesTo;
479                                private final Set<String> myAppliesToTypes;
480                                private ClassifierTypeEnum myClassifierType;
481                                private String myInCompartmentName;
482                                private Collection<? extends IIdType> myInCompartmentOwners;
483                                private Collection<IIdType> myAppliesToInstances;
484                                private RuleImplOp myRule;
485                                private AdditionalCompartmentSearchParameters myAdditionalSearchParamsForCompartmentTypes =
486                                                new AdditionalCompartmentSearchParameters();
487
488                                /**
489                                 * Constructor
490                                 */
491                                RuleBuilderRuleOpClassifier(AppliesTypeEnum theAppliesTo, Set<String> theAppliesToTypes) {
492                                        super();
493                                        myAppliesTo = theAppliesTo;
494                                        myAppliesToTypes = theAppliesToTypes;
495                                }
496
497                                /**
498                                 * Constructor
499                                 */
500                                RuleBuilderRuleOpClassifier(Collection<IIdType> theAppliesToInstances) {
501                                        myAppliesToInstances = theAppliesToInstances;
502                                        myAppliesTo = AppliesTypeEnum.INSTANCES;
503                                        myAppliesToTypes = null;
504                                }
505
506                                private RuleBuilderFinished finished() {
507                                        return finished(new RuleImplOp(myRuleName));
508                                }
509
510                                private RuleBuilderFinished finished(RuleImplOp theRule) {
511                                        Validate.isTrue(myRule == null, "Can not call finished() twice");
512                                        myRule = theRule;
513                                        theRule.setMode(myRuleMode);
514                                        theRule.setOp(myRuleOp);
515                                        theRule.setAppliesTo(myAppliesTo);
516                                        theRule.setAppliesToTypes(myAppliesToTypes);
517                                        theRule.setAppliesToInstances(myAppliesToInstances);
518                                        theRule.setClassifierType(myClassifierType);
519                                        theRule.setClassifierCompartmentName(myInCompartmentName);
520                                        theRule.setClassifierCompartmentOwners(myInCompartmentOwners);
521                                        theRule.setAppliesToDeleteCascade(myOnCascade);
522                                        theRule.setAppliesToDeleteExpunge(myOnExpunge);
523                                        theRule.setAdditionalSearchParamsForCompartmentTypes(myAdditionalSearchParamsForCompartmentTypes);
524                                        myRules.add(theRule);
525
526                                        return new RuleBuilderFinished(theRule);
527                                }
528
529                                @Override
530                                public IAuthRuleBuilderRuleOpClassifierFinished inCompartment(
531                                                String theCompartmentName, Collection<? extends IIdType> theOwners) {
532                                        return inCompartmentWithAdditionalSearchParams(
533                                                        theCompartmentName, theOwners, new AdditionalCompartmentSearchParameters());
534                                }
535
536                                @Override
537                                public IAuthRuleBuilderRuleOpClassifierFinished inCompartmentWithAdditionalSearchParams(
538                                                String theCompartmentName,
539                                                Collection<? extends IIdType> theOwners,
540                                                AdditionalCompartmentSearchParameters theAdditionalTypeSearchParams) {
541                                        Validate.notBlank(theCompartmentName, "theCompartmentName must not be null");
542                                        Validate.notNull(theOwners, "theOwners must not be null");
543                                        Validate.noNullElements(theOwners, "theOwners must not contain any null elements");
544                                        for (IIdType next : theOwners) {
545                                                validateOwner(next);
546                                        }
547                                        myInCompartmentName = theCompartmentName;
548                                        myInCompartmentOwners = theOwners;
549                                        myAdditionalSearchParamsForCompartmentTypes = theAdditionalTypeSearchParams;
550                                        myClassifierType = ClassifierTypeEnum.IN_COMPARTMENT;
551                                        return finished();
552                                }
553
554                                @Override
555                                public IAuthRuleBuilderRuleOpClassifierFinished inCompartment(
556                                                String theCompartmentName, IIdType theOwner) {
557                                        return inCompartmentWithAdditionalSearchParams(
558                                                        theCompartmentName, theOwner, new AdditionalCompartmentSearchParameters());
559                                }
560
561                                @Override
562                                public IAuthRuleBuilderRuleOpClassifierFinished inCompartmentWithAdditionalSearchParams(
563                                                String theCompartmentName,
564                                                IIdType theOwner,
565                                                AdditionalCompartmentSearchParameters theAdditionalTypeSearchParamNames) {
566                                        Validate.notBlank(theCompartmentName, "theCompartmentName must not be null");
567                                        Validate.notNull(theOwner, "theOwner must not be null");
568                                        validateOwner(theOwner);
569                                        myClassifierType = ClassifierTypeEnum.IN_COMPARTMENT;
570                                        myInCompartmentName = theCompartmentName;
571                                        myAdditionalSearchParamsForCompartmentTypes = theAdditionalTypeSearchParamNames;
572                                        Optional<RuleImplOp> oRule = findMatchingRule();
573                                        if (oRule.isPresent()) {
574                                                RuleImplOp rule = oRule.get();
575                                                rule.setAdditionalSearchParamsForCompartmentTypes(myAdditionalSearchParamsForCompartmentTypes);
576                                                rule.addClassifierCompartmentOwner(theOwner);
577                                                return new RuleBuilderFinished(rule);
578                                        }
579                                        myInCompartmentOwners = Collections.singletonList(theOwner);
580                                        return finished();
581                                }
582
583                                private Optional<RuleImplOp> findMatchingRule() {
584                                        return myRules.stream()
585                                                        .filter(RuleImplOp.class::isInstance)
586                                                        .map(RuleImplOp.class::cast)
587                                                        .filter(rule -> rule.matches(
588                                                                        myRuleOp,
589                                                                        myAppliesTo,
590                                                                        myAppliesToInstances,
591                                                                        myAppliesToTypes,
592                                                                        myClassifierType,
593                                                                        myInCompartmentName))
594                                                        .findFirst();
595                                }
596
597                                private void validateOwner(IIdType theOwner) {
598                                        Validate.notBlank(theOwner.getIdPart(), "owner.getIdPart() must not be null or empty");
599                                        Validate.notBlank(theOwner.getIdPart(), "owner.getResourceType() must not be null or empty");
600                                }
601
602                                @Override
603                                public IAuthRuleBuilderRuleOpClassifierFinished withAnyId() {
604                                        myClassifierType = ClassifierTypeEnum.ANY_ID;
605                                        return finished();
606                                }
607
608                                @Override
609                                public IAuthRuleBuilderRuleOpClassifierFinished withCodeInValueSet(
610                                                @Nonnull String theSearchParameterName, @Nonnull String theValueSetUrl) {
611                                        SearchParameterAndValueSetRuleImpl rule = new SearchParameterAndValueSetRuleImpl(myRuleName);
612                                        rule.setSearchParameterName(theSearchParameterName);
613                                        rule.setValueSetUrl(theValueSetUrl);
614                                        rule.setWantCode(true);
615                                        return finished(rule);
616                                }
617
618                                @Override
619                                public IAuthRuleFinished withCodeNotInValueSet(
620                                                @Nonnull String theSearchParameterName, @Nonnull String theValueSetUrl) {
621                                        SearchParameterAndValueSetRuleImpl rule = new SearchParameterAndValueSetRuleImpl(myRuleName);
622                                        rule.setSearchParameterName(theSearchParameterName);
623                                        rule.setValueSetUrl(theValueSetUrl);
624                                        rule.setWantCode(false);
625                                        return finished(rule);
626                                }
627
628                                @Override
629                                public IAuthRuleFinished inCompartmentWithFilter(
630                                                String theCompartmentName, IIdType theIdElement, String theFilter) {
631                                        Validate.notBlank(theCompartmentName, "theCompartmentName must not be null");
632                                        Validate.notNull(theIdElement, "theOwner must not be null");
633                                        validateOwner(theIdElement);
634
635                                        // inlined from inCompartmentWithAdditionalSearchParams()
636                                        myClassifierType = ClassifierTypeEnum.IN_COMPARTMENT;
637                                        myInCompartmentName = theCompartmentName;
638                                        myAdditionalSearchParamsForCompartmentTypes = new AdditionalCompartmentSearchParameters();
639                                        // todo JR/MB this is a quick and dirty fix at the last minute before the release.
640                                        //  We should revisit approach so that findMatchingRule() takes the filters into account
641                                        //  and only merges the rules if the filters are compatible
642                                        //                                      Optional<RuleImplOp> oRule = findMatchingRule();
643                                        //                                      if (oRule.isPresent()) {
644                                        //                                              RuleImplOp rule = oRule.get();
645                                        //
646                                        //      rule.setAdditionalSearchParamsForCompartmentTypes(myAdditionalSearchParamsForCompartmentTypes);
647                                        //                                              rule.addClassifierCompartmentOwner(theIdElement);
648                                        //                                              return new RuleBuilderFinished(rule);
649                                        //                                      }
650                                        myInCompartmentOwners = Collections.singletonList(theIdElement);
651
652                                        RuleBuilderFinished result = finished();
653                                        result.withTester(new FhirQueryRuleTester(theFilter));
654                                        return result;
655                                }
656
657                                @Override
658                                public IAuthRuleFinished withFilter(String theFilter) {
659                                        myClassifierType = ClassifierTypeEnum.ANY_ID;
660
661                                        RuleBuilderFinished result = finished();
662                                        result.withTester(new FhirQueryRuleTester(theFilter));
663                                        return result;
664                                }
665
666                                RuleBuilderFinished addInstances(Collection<IIdType> theInstances) {
667                                        myAppliesToInstances.addAll(theInstances);
668                                        return new RuleBuilderFinished(myRule);
669                                }
670                        }
671                }
672
673                private class RuleBuilderRuleOperation implements IAuthRuleBuilderOperation {
674
675                        @Override
676                        public IAuthRuleBuilderOperationNamed named(String theOperationName) {
677                                Validate.notBlank(theOperationName, "theOperationName must not be null or empty");
678                                return new RuleBuilderRuleOperationNamed(theOperationName);
679                        }
680
681                        @Override
682                        public IAuthRuleBuilderOperationNamed withAnyName() {
683                                return new RuleBuilderRuleOperationNamed(null);
684                        }
685
686                        private class RuleBuilderRuleOperationNamed implements IAuthRuleBuilderOperationNamed {
687
688                                private final String myOperationName;
689
690                                RuleBuilderRuleOperationNamed(String theOperationName) {
691                                        if (theOperationName != null && !theOperationName.startsWith("$")) {
692                                                myOperationName = '$' + theOperationName;
693                                        } else {
694                                                myOperationName = theOperationName;
695                                        }
696                                }
697
698                                private OperationRule createRule() {
699                                        OperationRule rule = new OperationRule(myRuleName);
700                                        rule.setOperationName(myOperationName);
701                                        rule.setMode(myRuleMode);
702                                        return rule;
703                                }
704
705                                @Override
706                                public IAuthRuleBuilderOperationNamedAndScoped onAnyInstance() {
707                                        OperationRule rule = createRule();
708                                        rule.appliesToAnyInstance();
709                                        return new RuleBuilderOperationNamedAndScoped(rule);
710                                }
711
712                                @Override
713                                public IAuthRuleBuilderOperationNamedAndScoped atAnyLevel() {
714                                        OperationRule rule = createRule();
715                                        rule.appliesAtAnyLevel(true);
716                                        return new RuleBuilderOperationNamedAndScoped(rule);
717                                }
718
719                                @Override
720                                public IAuthRuleBuilderOperationNamedAndScoped onAnyType() {
721                                        OperationRule rule = createRule();
722                                        rule.appliesToAnyType();
723                                        return new RuleBuilderOperationNamedAndScoped(rule);
724                                }
725
726                                @Override
727                                public IAuthRuleBuilderOperationNamedAndScoped onInstance(IIdType theInstanceId) {
728                                        Validate.notNull(theInstanceId, "theInstanceId must not be null");
729                                        Validate.notBlank(theInstanceId.getResourceType(), "theInstanceId does not have a resource type");
730                                        Validate.notBlank(theInstanceId.getIdPart(), "theInstanceId does not have an ID part");
731
732                                        OperationRule rule = createRule();
733                                        ArrayList<IIdType> ids = new ArrayList<>();
734                                        ids.add(theInstanceId);
735                                        rule.appliesToInstances(ids);
736                                        return new RuleBuilderOperationNamedAndScoped(rule);
737                                }
738
739                                @Override
740                                public IAuthRuleBuilderOperationNamedAndScoped onInstances(Collection<IIdType> theInstanceIds) {
741                                        Validate.notNull(theInstanceIds, "theInstanceIds must not be null");
742                                        theInstanceIds.forEach(instanceId -> Validate.notBlank(
743                                                        instanceId.getResourceType(),
744                                                        "at least one of theInstanceIds does not have a resource type"));
745                                        theInstanceIds.forEach(instanceId -> Validate.notBlank(
746                                                        instanceId.getIdPart(), "at least one of theInstanceIds does not have an ID part"));
747
748                                        final OperationRule rule = createRule();
749                                        rule.appliesToInstances(new ArrayList<>(theInstanceIds));
750                                        return new RuleBuilderOperationNamedAndScoped(rule);
751                                }
752
753                                @Override
754                                public IAuthRuleBuilderOperationNamedAndScoped onInstancesOfType(
755                                                Class<? extends IBaseResource> theType) {
756                                        validateType(theType);
757
758                                        OperationRule rule = createRule();
759                                        rule.appliesToInstancesOfType(toTypeSet(theType));
760                                        return new RuleBuilderOperationNamedAndScoped(rule);
761                                }
762
763                                @Override
764                                public IAuthRuleBuilderOperationNamedAndScoped onServer() {
765                                        OperationRule rule = createRule();
766                                        rule.appliesToServer();
767                                        return new RuleBuilderOperationNamedAndScoped(rule);
768                                }
769
770                                @Override
771                                public IAuthRuleBuilderOperationNamedAndScoped onType(Class<? extends IBaseResource> theType) {
772                                        validateType(theType);
773
774                                        OperationRule rule = createRule();
775                                        rule.appliesToTypes(toTypeSet(theType));
776                                        return new RuleBuilderOperationNamedAndScoped(rule);
777                                }
778
779                                private HashSet<Class<? extends IBaseResource>> toTypeSet(Class<? extends IBaseResource> theType) {
780                                        HashSet<Class<? extends IBaseResource>> appliesToTypes = new HashSet<>();
781                                        appliesToTypes.add(theType);
782                                        return appliesToTypes;
783                                }
784
785                                private void validateType(Class<? extends IBaseResource> theType) {
786                                        Validate.notNull(theType, "theType must not be null");
787                                }
788
789                                private class RuleBuilderOperationNamedAndScoped implements IAuthRuleBuilderOperationNamedAndScoped {
790
791                                        private final OperationRule myRule;
792
793                                        RuleBuilderOperationNamedAndScoped(OperationRule theRule) {
794                                                myRule = theRule;
795                                        }
796
797                                        @Override
798                                        public IAuthRuleBuilderRuleOpClassifierFinished andAllowAllResponses() {
799                                                myRule.allowAllResponses();
800                                                myRules.add(myRule);
801                                                return new RuleBuilderFinished(myRule);
802                                        }
803
804                                        @Override
805                                        public IAuthRuleBuilderRuleOpClassifierFinished andAllowAllResponsesWithAllResourcesAccess() {
806                                                myRule.allowAllResponses();
807                                                myRule.allowAllResourcesAccess();
808                                                myRules.add(myRule);
809                                                return new RuleBuilderFinished(myRule);
810                                        }
811
812                                        @Override
813                                        public IAuthRuleBuilderRuleOpClassifierFinished andRequireExplicitResponseAuthorization() {
814                                                myRules.add(myRule);
815                                                return new RuleBuilderFinished(myRule);
816                                        }
817                                }
818                        }
819                }
820
821                private class RuleBuilderRuleTransaction implements IAuthRuleBuilderRuleTransaction {
822
823                        @Override
824                        public IAuthRuleBuilderRuleTransactionOp withAnyOperation() {
825                                return new RuleBuilderRuleTransactionOp();
826                        }
827
828                        private class RuleBuilderRuleTransactionOp implements IAuthRuleBuilderRuleTransactionOp {
829
830                                @Override
831                                public IAuthRuleBuilderRuleOpClassifierFinished andApplyNormalRules() {
832                                        // Allow transaction
833                                        RuleImplOp rule = new RuleImplOp(myRuleName);
834                                        rule.setMode(myRuleMode);
835                                        rule.setOp(RuleOpEnum.TRANSACTION);
836                                        rule.setTransactionAppliesToOp(TransactionAppliesToEnum.ANY_OPERATION);
837                                        myRules.add(rule);
838                                        return new RuleBuilderFinished(rule);
839                                }
840                        }
841                }
842
843                private class PatchBuilder implements IAuthRuleBuilderPatch {
844
845                        PatchBuilder() {
846                                super();
847                        }
848
849                        @Override
850                        public IAuthRuleFinished allRequests() {
851                                BaseRule rule =
852                                                new RuleImplPatch(myRuleName).setAllRequests(true).setMode(myRuleMode);
853                                myRules.add(rule);
854                                return new RuleBuilderFinished(rule);
855                        }
856                }
857
858                private class UpdateHistoryRewriteBuilder implements IAuthRuleBuilderUpdateHistoryRewrite {
859
860                        UpdateHistoryRewriteBuilder() {
861                                super();
862                        }
863
864                        @Override
865                        public IAuthRuleFinished allRequests() {
866                                BaseRule rule = new RuleImplUpdateHistoryRewrite(myRuleName)
867                                                .setAllRequests(true)
868                                                .setMode(myRuleMode);
869                                myRules.add(rule);
870                                return new RuleBuilderFinished(rule);
871                        }
872                }
873
874                private class RuleBuilderGraphQL implements IAuthRuleBuilderGraphQL {
875                        @Override
876                        public IAuthRuleFinished any() {
877                                RuleImplOp rule = new RuleImplOp(myRuleName);
878                                rule.setOp(RuleOpEnum.GRAPHQL);
879                                rule.setMode(myRuleMode);
880                                myRules.add(rule);
881                                return new RuleBuilderFinished(rule);
882                        }
883                }
884
885                private class RuleBuilderBulkExport implements IAuthRuleBuilderRuleBulkExport {
886                        private RuleBulkExportImpl myRuleBulkExport;
887
888                        @Override
889                        public IAuthRuleBuilderRuleBulkExportWithTarget groupExportOnGroup(@Nonnull String theFocusResourceId) {
890                                RuleBulkExportImpl rule = new RuleBulkExportImpl(myRuleName);
891                                rule.setAppliesToGroupExportOnGroup(theFocusResourceId);
892                                rule.setMode(myRuleMode);
893                                myRules.add(rule);
894
895                                return new RuleBuilderBulkExportWithTarget(rule);
896                        }
897
898                        @Override
899                        public IAuthRuleBuilderRuleBulkExportWithTarget patientExportOnAllPatients() {
900                                if (myRuleBulkExport == null) {
901                                        RuleBulkExportImpl rule = new RuleBulkExportImpl(myRuleName);
902                                        rule.setMode(myRuleMode);
903                                        myRuleBulkExport = rule;
904                                }
905                                myRuleBulkExport.setAppliesToPatientExportAllPatients();
906
907                                // prevent duplicate rules being added
908                                if (!myRules.contains(myRuleBulkExport)) {
909                                        myRules.add(myRuleBulkExport);
910                                }
911
912                                return new RuleBuilderBulkExportWithTarget(myRuleBulkExport);
913                        }
914
915                        @Override
916                        public IAuthRuleBuilderRuleBulkExportWithTarget patientExportOnPatient(@Nonnull String theFocusResourceId) {
917                                if (myRuleBulkExport == null) {
918                                        RuleBulkExportImpl rule = new RuleBulkExportImpl(myRuleName);
919                                        rule.setAppliesToPatientExport(theFocusResourceId);
920                                        rule.setMode(myRuleMode);
921                                        myRuleBulkExport = rule;
922                                } else {
923                                        myRuleBulkExport.setAppliesToPatientExport(theFocusResourceId);
924                                }
925
926                                // prevent duplicate rules being added
927                                if (!myRules.contains(myRuleBulkExport)) {
928                                        myRules.add(myRuleBulkExport);
929                                }
930
931                                return new RuleBuilderBulkExportWithTarget(myRuleBulkExport);
932                        }
933
934                        @Override
935                        public IAuthRuleBuilderRuleBulkExportWithTarget patientExportOnPatientStrings(
936                                        @Nonnull Collection<String> theFocusResourceIds) {
937                                if (myRuleBulkExport == null) {
938                                        RuleBulkExportImpl rule = new RuleBulkExportImpl(myRuleName);
939                                        rule.setAppliesToPatientExport(theFocusResourceIds);
940                                        rule.setMode(myRuleMode);
941                                        myRuleBulkExport = rule;
942                                } else {
943                                        myRuleBulkExport.setAppliesToPatientExport(theFocusResourceIds);
944                                }
945
946                                // prevent duplicate rules being added
947                                if (!myRules.contains(myRuleBulkExport)) {
948                                        myRules.add(myRuleBulkExport);
949                                }
950
951                                return new RuleBuilderBulkExportWithTarget(myRuleBulkExport);
952                        }
953
954                        @Override
955                        public IAuthRuleBuilderRuleBulkExportWithTarget patientExportOnGroup(@Nonnull String theFocusResourceId) {
956                                RuleBulkExportImpl rule = new RuleBulkExportImpl(myRuleName);
957                                rule.setAppliesToPatientExportOnGroup(theFocusResourceId);
958                                rule.setMode(myRuleMode);
959                                myRules.add(rule);
960
961                                return new RuleBuilderBulkExportWithTarget(rule);
962                        }
963
964                        @Override
965                        public IAuthRuleBuilderRuleBulkExportWithTarget systemExport() {
966                                RuleBulkExportImpl rule = new RuleBulkExportImpl(myRuleName);
967                                rule.setAppliesToSystem();
968                                rule.setMode(myRuleMode);
969                                myRules.add(rule);
970
971                                return new RuleBuilderBulkExportWithTarget(rule);
972                        }
973
974                        @Override
975                        public IAuthRuleBuilderRuleBulkExportWithTarget any() {
976                                RuleBulkExportImpl rule = new RuleBulkExportImpl(myRuleName);
977                                rule.setAppliesToAny();
978                                rule.setMode(myRuleMode);
979                                myRules.add(rule);
980
981                                return new RuleBuilderBulkExportWithTarget(rule);
982                        }
983
984                        private class RuleBuilderBulkExportWithTarget extends RuleBuilderFinished
985                                        implements IAuthRuleBuilderRuleBulkExportWithTarget {
986                                private final RuleBulkExportImpl myRule;
987
988                                private RuleBuilderBulkExportWithTarget(RuleBulkExportImpl theRule) {
989                                        super(theRule);
990                                        myRule = theRule;
991                                }
992
993                                @Override
994                                public IAuthRuleBuilderRuleBulkExportWithTarget withResourceTypes(Collection<String> theResourceTypes) {
995                                        myRule.setResourceTypes(theResourceTypes);
996                                        return this;
997                                }
998                        }
999                }
1000        }
1001
1002        private static String toTypeName(Class<? extends IBaseResource> theType) {
1003                String retVal = ourTypeToName.get(theType);
1004                if (retVal == null) {
1005                        ResourceDef resourceDef = theType.getAnnotation(ResourceDef.class);
1006                        retVal = resourceDef.name();
1007                        Validate.notBlank(retVal, "Could not determine resource type of class %s", theType);
1008                        ourTypeToName.put(theType, retVal);
1009                }
1010                return retVal;
1011        }
1012}