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}