diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java index 9a2299d9d..e84249351 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java @@ -52,6 +52,8 @@ public class FineractProperties { private FineractJobProperties job; + private FineractTemplateProperties template; + @Getter @Setter public static class FineractTenantProperties { @@ -234,4 +236,12 @@ public class FineractProperties { private int stuckRetryThreshold; } + + @Getter + @Setter + public static class FineractTemplateProperties { + + private boolean regexWhitelistEnabled; + private List regexWhitelist; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/exception/TemplateForbiddenException.java b/fineract-provider/src/main/java/org/apache/fineract/template/exception/TemplateForbiddenException.java new file mode 100644 index 000000000..c089b07f3 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/template/exception/TemplateForbiddenException.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.template.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + +public class TemplateForbiddenException extends AbstractPlatformResourceNotFoundException { + + public TemplateForbiddenException(final String url) { + super("error.msg.template.url.forbidden", "Template with url " + url + " not allowed"); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/service/TemplateMergeService.java b/fineract-provider/src/main/java/org/apache/fineract/template/service/TemplateMergeService.java index 0a87073ed..cd4d808ec 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/template/service/TemplateMergeService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/template/service/TemplateMergeService.java @@ -38,20 +38,24 @@ import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.template.domain.Template; import org.apache.fineract.template.domain.TemplateFunctions; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.fineract.template.exception.TemplateForbiddenException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; +@Slf4j +@RequiredArgsConstructor @Service public class TemplateMergeService { - private static final Logger LOG = LoggerFactory.getLogger(TemplateMergeService.class); - - // private final FromJsonHelper fromApiJsonHelper; + private final FineractProperties fineractProperties; // TODO Replace this with appropriate alternative available in Guava private static String getStringFromInputStream(final InputStream is) { @@ -64,7 +68,7 @@ public class TemplateMergeService { sb.append(line); } } catch (final IOException e) { - LOG.error("getStringFromInputStream() failed", e); + log.error("getStringFromInputStream() failed", e); } return sb.toString(); @@ -102,7 +106,7 @@ public class TemplateMergeService { try { scopes.put(entry.getKey(), getMapFromUrl(url)); } catch (final IOException e) { - LOG.error("getCompiledMapFromMappers() failed", e); + log.error("getCompiledMapFromMappers() failed", e); } } } @@ -124,6 +128,26 @@ public class TemplateMergeService { } private HttpURLConnection getConnection(final String url) { + if (fineractProperties.getTemplate() != null && fineractProperties.getTemplate().isRegexWhitelistEnabled()) { + boolean whitelisted = false; + + if (fineractProperties.getTemplate().getRegexWhitelist() != null + && !fineractProperties.getTemplate().getRegexWhitelist().isEmpty()) { + for (String urlPattern : fineractProperties.getTemplate().getRegexWhitelist()) { + Pattern pattern = Pattern.compile(urlPattern); + Matcher matcher = pattern.matcher(url); + if (matcher.matches()) { + whitelisted = true; + break; + } + } + } + + if (!whitelisted) { + throw new TemplateForbiddenException(url); + } + } + String authToken = ThreadLocalContextUtil.getAuthToken(); if (authToken == null) { final String name = SecurityContextHolder.getContext().getAuthentication().getName(); @@ -149,7 +173,7 @@ public class TemplateMergeService { connection.setDoInput(true); } catch (IOException | KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) { - LOG.error("getConnection() failed, return null", e); + log.error("getConnection() failed, return null", e); } return connection; diff --git a/fineract-provider/src/main/resources/application.properties b/fineract-provider/src/main/resources/application.properties index d13c4c355..7c37d32b5 100644 --- a/fineract-provider/src/main/resources/application.properties +++ b/fineract-provider/src/main/resources/application.properties @@ -88,6 +88,8 @@ fineract.content.s3.bucketName=${FINERACT_CONTENT_S3_BUCKET_NAME:} fineract.content.s3.accessKey=${FINERACT_CONTENT_S3_ACCESS_KEY:} fineract.content.s3.secretKey=${FINERACT_CONTENT_S3_SECRET_KEY:} +fineract.template.regex-whitelist-enabled=${FINERACT_TEMPLATE_REGEX_WHITELIST_ENABLED:true} +fineract.template.regex-whitelist=${FINERACT_TEMPLATE_REGEX_WHITELIST:} fineract.report.export.s3.bucket=${FINERACT_REPORT_EXPORT_S3_BUCKET_NAME:} fineract.report.export.s3.enabled=${FINERACT_REPORT_EXPORT_S3_ENABLED:false} diff --git a/fineract-provider/src/test/java/org/apache/fineract/template/service/TemplateServiceStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/template/service/TemplateServiceStepDefinitions.java index 0cf4fd147..69f9ee6c4 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/template/service/TemplateServiceStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/template/service/TemplateServiceStepDefinitions.java @@ -34,10 +34,12 @@ import java.util.Map; import org.apache.commons.io.IOUtils; import org.apache.fineract.template.domain.Template; import org.apache.fineract.template.domain.TemplateMapper; +import org.springframework.beans.factory.annotation.Autowired; public class TemplateServiceStepDefinitions implements En { - private TemplateMergeService tms = new TemplateMergeService(); + @Autowired + private TemplateMergeService tms; private String template;