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.rest.api.server.RequestDetails;
023import jakarta.annotation.Nonnull;
024import jakarta.annotation.Nullable;
025import org.apache.commons.lang3.Validate;
026
027import java.util.ArrayList;
028import java.util.List;
029
030/**
031 * Return type for {@link SearchNarrowingInterceptor#buildAuthorizedList(RequestDetails)}
032 */
033public class AuthorizedList {
034
035        private List<String> myAllowedCompartments;
036        private List<String> myAllowedInstances;
037        private List<AllowedCodeInValueSet> myAllowedCodeInValueSets;
038
039        @Nullable
040        List<String> getAllowedCompartments() {
041                return myAllowedCompartments;
042        }
043
044        @Nullable
045        List<AllowedCodeInValueSet> getAllowedCodeInValueSets() {
046                return myAllowedCodeInValueSets;
047        }
048
049        @Nullable
050        List<String> getAllowedInstances() {
051                return myAllowedInstances;
052        }
053
054        /**
055         * Adds a compartment that the user should be allowed to access
056         *
057         * @param theCompartment The compartment name, e.g. "Patient/123" (in this example the user would be allowed to access Patient/123 as well as Observations where Observation.subject="Patient/123"m, etc.
058         * @return Returns <code>this</code> for easy method chaining
059         */
060        public AuthorizedList addCompartment(String theCompartment) {
061                Validate.notNull(theCompartment, "theCompartment must not be null");
062                if (myAllowedCompartments == null) {
063                        myAllowedCompartments = new ArrayList<>();
064                }
065                myAllowedCompartments.add(theCompartment);
066
067                return this;
068        }
069
070        /**
071         * Adds a compartment that the user should be allowed to access
072         *
073         * @param theCompartments The compartment names, e.g. "Patient/123" (in this example the user would be allowed to access Patient/123 as well as Observations where Observation.subject="Patient/123"m, etc.
074         * @return Returns <code>this</code> for easy method chaining
075         */
076        public AuthorizedList addCompartments(String... theCompartments) {
077                Validate.notNull(theCompartments, "theCompartments must not be null");
078                for (String next : theCompartments) {
079                        addCompartment(next);
080                }
081                return this;
082        }
083
084        /**
085         * Adds a resource that the user should be allowed to access
086         *
087         * @param theResource The resource name, e.g. "Patient/123" (in this example the user would be allowed to access Patient/123 but not Observations where Observation.subject="Patient/123"m, etc.
088         * @return Returns <code>this</code> for easy method chaining
089         */
090        public AuthorizedList addResource(String theResource) {
091                Validate.notNull(theResource, "theResource must not be null");
092                if (myAllowedInstances == null) {
093                        myAllowedInstances = new ArrayList<>();
094                }
095                myAllowedInstances.add(theResource);
096
097                return this;
098        }
099
100        /**
101         * Adds a resource that the user should be allowed to access
102         *
103         * @param theResources The resource names, e.g. "Patient/123" (in this example the user would be allowed to access Patient/123 but not Observations where Observation.subject="Patient/123"m, etc.
104         * @return Returns <code>this</code> for easy method chaining
105         */
106        public AuthorizedList addResources(String... theResources) {
107                Validate.notNull(theResources, "theResources must not be null");
108                for (String next : theResources) {
109                        addResource(next);
110                }
111                return this;
112        }
113
114        /**
115         * If specified, any search for <code>theResourceName</code> will automatically include a parameter indicating that
116         * the token search parameter <code>theSearchParameterName</code> must have a value in the ValueSet with URL <code>theValueSetUrl</code>.
117         *
118         * @param theResourceName        The resource name, e.g. <code>Observation</code>
119         * @param theSearchParameterName The search parameter name, e.g. <code>code</code>
120         * @param theValueSetUrl         The valueset URL, e.g. <code>http://my-value-set</code>
121         * @return Returns a reference to <code>this</code> for easy chaining
122         * @see AuthorizationInterceptor If search narrowing by code is being used for security reasons, consider also using AuthorizationInterceptor as a failsafe to ensure that no inapproproiate resources are returned
123         * @since 6.0.0
124         */
125        public AuthorizedList addCodeInValueSet(
126                        @Nonnull String theResourceName, @Nonnull String theSearchParameterName, @Nonnull String theValueSetUrl) {
127                Validate.notBlank(theResourceName, "theResourceName must not be missing or null");
128                Validate.notBlank(theSearchParameterName, "theSearchParameterName must not be missing or null");
129                Validate.notBlank(theValueSetUrl, "theResourceUrl must not be missing or null");
130
131                return doAddCodeInValueSet(theResourceName, theSearchParameterName, theValueSetUrl, false);
132        }
133
134        /**
135         * If specified, any search for <code>theResourceName</code> will automatically include a parameter indicating that
136         * the token search parameter <code>theSearchParameterName</code> must have a value not in the ValueSet with URL <code>theValueSetUrl</code>.
137         *
138         * @param theResourceName        The resource name, e.g. <code>Observation</code>
139         * @param theSearchParameterName The search parameter name, e.g. <code>code</code>
140         * @param theValueSetUrl         The valueset URL, e.g. <code>http://my-value-set</code>
141         * @return Returns a reference to <code>this</code> for easy chaining
142         * @see AuthorizationInterceptor If search narrowing by code is being used for security reasons, consider also using AuthorizationInterceptor as a failsafe to ensure that no inapproproiate resources are returned
143         * @since 6.0.0
144         */
145        public AuthorizedList addCodeNotInValueSet(
146                        @Nonnull String theResourceName, @Nonnull String theSearchParameterName, @Nonnull String theValueSetUrl) {
147                Validate.notBlank(theResourceName, "theResourceName must not be missing or null");
148                Validate.notBlank(theSearchParameterName, "theSearchParameterName must not be missing or null");
149                Validate.notBlank(theValueSetUrl, "theResourceUrl must not be missing or null");
150
151                return doAddCodeInValueSet(theResourceName, theSearchParameterName, theValueSetUrl, true);
152        }
153
154        private AuthorizedList doAddCodeInValueSet(
155                        String theResourceName, String theSearchParameterName, String theValueSetUrl, boolean negate) {
156                if (myAllowedCodeInValueSets == null) {
157                        myAllowedCodeInValueSets = new ArrayList<>();
158                }
159                myAllowedCodeInValueSets.add(
160                                new AllowedCodeInValueSet(theResourceName, theSearchParameterName, theValueSetUrl, negate));
161
162                return this;
163        }
164}