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