001package ca.uhn.fhir.rest.server.interceptor.validation.fields;
002
003/*-
004 * #%L
005 * HAPI FHIR - Server Framework
006 * %%
007 * Copyright (C) 2014 - 2022 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.context.FhirContext;
025import ca.uhn.fhir.fhirpath.IFhirPath;
026import ca.uhn.fhir.interceptor.api.Hook;
027import ca.uhn.fhir.interceptor.api.Interceptor;
028import ca.uhn.fhir.interceptor.api.Pointcut;
029import ca.uhn.fhir.rest.api.server.RequestDetails;
030import ca.uhn.fhir.rest.server.interceptor.ConfigLoader;
031import ca.uhn.fhir.util.ExtensionUtil;
032import org.hl7.fhir.instance.model.api.IBase;
033import org.hl7.fhir.instance.model.api.IBaseExtension;
034import org.hl7.fhir.instance.model.api.IBaseResource;
035import org.hl7.fhir.instance.model.api.IPrimitiveType;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039import java.util.List;
040import java.util.Map;
041
042import static ca.uhn.fhir.rest.server.interceptor.validation.fields.IValidator.VALIDATION_EXTENSION_URL;
043
044@Interceptor
045public class FieldValidatingInterceptor {
046
047        public static final String FHIR_PATH_VALUE = "value";
048
049        public enum ValidatorType {
050                EMAIL;
051        }
052
053        private static final Logger ourLog = LoggerFactory.getLogger(FieldValidatingInterceptor.class);
054
055        public static final String VALIDATION_DISABLED_HEADER = "HAPI-Field-Validation-Disabled";
056        public static final String PROPERTY_EXTENSION_URL = "validation.extension.url";
057
058        private Map<String, String> myConfig;
059
060        public FieldValidatingInterceptor() {
061                super();
062
063                ourLog.info("Starting FieldValidatingInterceptor {}", this);
064                myConfig = ConfigLoader.loadJson("classpath:field-validation-rules.json", Map.class);
065        }
066
067        @Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED)
068        public void resourcePreCreate(RequestDetails theRequest, IBaseResource theResource) {
069                ourLog.debug("Validating address on create for resource {} / request {}", theResource, theRequest);
070                handleRequest(theRequest, theResource);
071        }
072
073        @Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED)
074        public void resourcePreUpdate(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) {
075                ourLog.debug("Validating address on update for resource {} / old resource {} / request {}", theOldResource, theNewResource, theRequest);
076                handleRequest(theRequest, theNewResource);
077        }
078
079        protected void handleRequest(RequestDetails theRequest, IBaseResource theResource) {
080                if (theRequest == null) {
081                        ourLog.debug("RequestDetails is null - unable to validate {}", theResource);
082                        return;
083                }
084
085                if (!theRequest.getHeaders(VALIDATION_DISABLED_HEADER).isEmpty()) {
086                        ourLog.debug("Address validation is disabled for this request via header");
087                        return;
088                }
089
090                FhirContext ctx = theRequest.getFhirContext();
091                IFhirPath fhirPath = ctx.newFhirPath();
092
093                for (Map.Entry<String, String> e : myConfig.entrySet()) {
094                        IValidator validator = getValidator(e.getValue());
095                        if (validator == null) {
096                                continue;
097                        }
098
099                        List<IBase> fields = fhirPath.evaluate(theResource, e.getKey(), IBase.class);
100                        for (IBase field : fields) {
101
102                                List<IPrimitiveType> values = fhirPath.evaluate(field, FHIR_PATH_VALUE, IPrimitiveType.class);
103                                boolean isValid = true;
104                                for (IPrimitiveType value : values) {
105                                        String valueAsString = value.getValueAsString();
106                                        isValid = validator.isValid(valueAsString);
107                                        ourLog.debug("Field {} at path {} validated {}", value, e.getKey(), isValid);
108                                        if (!isValid) {
109                                                break;
110                                        }
111                                }
112                                setValidationStatus(ctx, field, isValid);
113                        }
114                }
115        }
116
117        private void setValidationStatus(FhirContext ctx, IBase theBase, boolean isValid) {
118                ExtensionUtil.clearExtensionsByUrl(theBase, getValidationExtensionUrl());
119                ExtensionUtil.setExtension(ctx, theBase, getValidationExtensionUrl(), "boolean", !isValid);
120        }
121
122        private String getValidationExtensionUrl() {
123                if (myConfig.containsKey(PROPERTY_EXTENSION_URL)) {
124                        return myConfig.get(PROPERTY_EXTENSION_URL);
125                }
126                return VALIDATION_EXTENSION_URL;
127        }
128
129        private IValidator getValidator(String theValue) {
130                if (PROPERTY_EXTENSION_URL.equals(theValue)) {
131                        return null;
132                }
133
134                if (ValidatorType.EMAIL.name().equals(theValue)) {
135                        return new EmailValidator();
136                }
137
138                try {
139                        return (IValidator) Class.forName(theValue).getDeclaredConstructor().newInstance();
140                } catch (Exception e) {
141                        throw new IllegalStateException(Msg.code(348) + String.format("Unable to create validator for %s", theValue), e);
142                }
143        }
144
145        public Map<String, String> getConfig() {
146                return myConfig;
147        }
148
149        public void setConfig(Map<String, String> theConfig) {
150                myConfig = theConfig;
151        }
152}