This is an automated email from the ASF dual-hosted git repository. nmalin pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git
The following commit(s) were added to refs/heads/trunk by this push: new 0fce1dd56c Improved: Improve ViewHandler interface (OFBIZ-13179) (#858) 0fce1dd56c is described below commit 0fce1dd56c76e3dde0d27085ddd90d692560a8e2 Author: Nicolas Malin <nicolas.ma...@nereide.fr> AuthorDate: Thu Nov 28 17:19:50 2024 +0100 Improved: Improve ViewHandler interface (OFBIZ-13179) (#858) We extend *AbstractViewHandler* with a new method to override *prepareViewContext*. For each view handler implementation this will allow to control context used for rendering, applying Scriptlet token detection for security purpose. A new class *SecuredFreemarker* has been created to manage freemarker specific controls, outside global *SecurityUtil* class. We also add a new parameter *secure-context* (set true by default) to view-map xml element to indicate that this view allow unsecure rendering, this implies the view-map to required authentication. Thanks to Gil Portenseigne for help --- .../org/apache/ofbiz/content/cms/CmsEvents.java | 2 +- .../content/view/SimpleContentViewHandler.java | 37 ++++-- .../ofbiz/product/category/SeoContextFilter.java | 4 +- .../apache/ofbiz/security/SecuredFreemarker.java | 131 +++++++++++++++++++++ .../org/apache/ofbiz/security/SecurityUtil.java | 72 ----------- framework/webapp/dtd/site-conf.xsd | 10 ++ .../ofbiz/webapp/control/ConfigXMLReader.java | 13 +- .../apache/ofbiz/webapp/control/ControlFilter.java | 7 +- .../ofbiz/webapp/control/RequestHandler.java | 3 +- .../ofbiz/webapp/ftl/FreeMarkerViewHandler.java | 59 +++++----- .../apache/ofbiz/webapp/view/HttpViewHandler.java | 9 +- .../apache/ofbiz/webapp/view/JspViewHandler.java | 19 ++- .../org/apache/ofbiz/webapp/view/ViewHandler.java | 14 ++- .../ofbiz/widget/renderer/ScreenRenderer.java | 13 +- .../widget/renderer/fo/ScreenFopViewHandler.java | 75 +++++++----- .../renderer/macro/MacroScreenViewHandler.java | 15 ++- 16 files changed, 314 insertions(+), 169 deletions(-) diff --git a/applications/content/src/main/java/org/apache/ofbiz/content/cms/CmsEvents.java b/applications/content/src/main/java/org/apache/ofbiz/content/cms/CmsEvents.java index 173ecaaa91..2012f2d5cc 100644 --- a/applications/content/src/main/java/org/apache/ofbiz/content/cms/CmsEvents.java +++ b/applications/content/src/main/java/org/apache/ofbiz/content/cms/CmsEvents.java @@ -294,7 +294,7 @@ public final class CmsEvents { if (statusCode == HttpServletResponse.SC_OK || hasErrorPage) { // create the template map MapStack<String> templateMap = MapStack.create(); - ScreenRenderer.populateContextForRequest(templateMap, null, request, response, servletContext); + ScreenRenderer.populateContextForRequest(templateMap, null, request, response, servletContext, true); templateMap.put("statusCode", statusCode); // make the link prefix diff --git a/applications/content/src/main/java/org/apache/ofbiz/content/view/SimpleContentViewHandler.java b/applications/content/src/main/java/org/apache/ofbiz/content/view/SimpleContentViewHandler.java index 8b697f0358..ae177cd102 100644 --- a/applications/content/src/main/java/org/apache/ofbiz/content/view/SimpleContentViewHandler.java +++ b/applications/content/src/main/java/org/apache/ofbiz/content/view/SimpleContentViewHandler.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.sql.Timestamp; import java.text.ParseException; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -44,9 +45,11 @@ import org.apache.ofbiz.entity.Delegator; import org.apache.ofbiz.entity.GenericValue; import org.apache.ofbiz.entity.util.EntityQuery; import org.apache.ofbiz.entity.util.EntityUtilProperties; +import org.apache.ofbiz.security.SecuredFreemarker; import org.apache.ofbiz.service.GenericServiceException; import org.apache.ofbiz.service.LocalDispatcher; import org.apache.ofbiz.service.ServiceUtil; +import org.apache.ofbiz.webapp.control.ConfigXMLReader; import org.apache.ofbiz.webapp.view.AbstractViewHandler; import org.apache.ofbiz.webapp.view.ViewHandlerException; import org.apache.ofbiz.webapp.website.WebSiteWorker; @@ -62,24 +65,36 @@ public class SimpleContentViewHandler extends AbstractViewHandler { rootDir = context.getRealPath("/"); https = (String) context.getAttribute("https"); } + + @Override + public Map<String, Object> prepareViewContext(HttpServletRequest request, HttpServletResponse response, ConfigXMLReader.ViewMap viewMap) { + List<String> fields = List.of("contentId", "rootContentId", "mapKey", + "contentAssocTypeId", "fromDate", "dataResourceId", + "contentRevisionSeqId", "mimeTypeId"); + Map<String, Object> context = new HashMap<>(); + fields.forEach(field -> context.put(field, request.getParameter(field))); + return viewMap.isSecureContext() + ? SecuredFreemarker.sanitizeParameterMap(context) + : context; + } + /** - * @see org.apache.ofbiz.webapp.view.ViewHandler#render(java.lang.String, java.lang.String, java.lang.String, java.lang.String, - * java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + * @see org.apache.ofbiz.webapp.view.ViewHandler#render(String, String, String, String, String, HttpServletRequest, HttpServletResponse, Map) */ @Override public void render(String name, String page, String info, String contentType, String encoding, HttpServletRequest request, - HttpServletResponse response) throws ViewHandlerException { + HttpServletResponse response, Map<String, Object> context) throws ViewHandlerException { LocalDispatcher dispatcher = (LocalDispatcher) request.getAttribute("dispatcher"); HttpSession session = request.getSession(); GenericValue userLogin = (GenericValue) session.getAttribute("userLogin"); - String contentId = request.getParameter("contentId"); - String rootContentId = request.getParameter("rootContentId"); - String mapKey = request.getParameter("mapKey"); - String contentAssocTypeId = request.getParameter("contentAssocTypeId"); - String fromDateStr = request.getParameter("fromDate"); - String dataResourceId = request.getParameter("dataResourceId"); - String contentRevisionSeqId = request.getParameter("contentRevisionSeqId"); - String mimeTypeId = request.getParameter("mimeTypeId"); + String contentId = (String) context.get("contentId"); + String rootContentId = (String) context.get("rootContentId"); + String mapKey = (String) context.get("mapKey"); + String contentAssocTypeId = (String) context.get("contentAssocTypeId"); + String fromDateStr = (String) context.get("fromDate"); + String dataResourceId = (String) context.get("dataResourceId"); + String contentRevisionSeqId = (String) context.get("contentRevisionSeqId"); + String mimeTypeId = (String) context.get("mimeTypeId"); Locale locale = UtilHttp.getLocale(request); String webSiteId = WebSiteWorker.getWebSiteId(request); diff --git a/applications/product/src/main/java/org/apache/ofbiz/product/category/SeoContextFilter.java b/applications/product/src/main/java/org/apache/ofbiz/product/category/SeoContextFilter.java index bea4d8a7ae..0b7e638782 100644 --- a/applications/product/src/main/java/org/apache/ofbiz/product/category/SeoContextFilter.java +++ b/applications/product/src/main/java/org/apache/ofbiz/product/category/SeoContextFilter.java @@ -48,7 +48,7 @@ import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.StringUtil; import org.apache.ofbiz.base.util.UtilHttp; import org.apache.ofbiz.base.util.UtilValidate; -import org.apache.ofbiz.security.SecurityUtil; +import org.apache.ofbiz.security.SecuredFreemarker; import org.apache.ofbiz.webapp.SeoConfigUtil; import org.apache.ofbiz.webapp.control.ConfigXMLReader; import org.apache.ofbiz.webapp.control.ConfigXMLReader.ControllerConfig; @@ -116,7 +116,7 @@ public final class SeoContextFilter implements Filter { uri = uri + "?" + queryString; } - if (SecurityUtil.containsFreemarkerInterpolation(httpRequest, httpResponse, uri)) { + if (SecuredFreemarker.containsFreemarkerInterpolation(httpRequest, httpResponse, uri)) { return; } diff --git a/framework/security/src/main/java/org/apache/ofbiz/security/SecuredFreemarker.java b/framework/security/src/main/java/org/apache/ofbiz/security/SecuredFreemarker.java new file mode 100644 index 0000000000..b0b788d0ec --- /dev/null +++ b/framework/security/src/main/java/org/apache/ofbiz/security/SecuredFreemarker.java @@ -0,0 +1,131 @@ +/******************************************************************************* + * 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.ofbiz.security; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.http.client.utils.URLEncodedUtils; +import org.apache.http.message.BasicNameValuePair; +import org.apache.ofbiz.base.util.Debug; +import org.apache.ofbiz.base.util.StringUtil; +import org.apache.ofbiz.base.util.UtilHttp; +import org.apache.ofbiz.base.util.UtilProperties; +import org.apache.ofbiz.base.util.UtilValidate; + +public class SecuredFreemarker { + private static final String MODULE = SecuredFreemarker.class.getName(); + private static final List<String> FTL_INTERPOLATION = List.of("%24%7B", "${", "%3C%23", "<#", "%23%7B", "#{", "%5B%3D", "[=", "%5B%23", "[#"); + + /* + * Prevents Freemarker exploits + * @param req + * @param resp + * @param uri + * @throws IOException + */ + public static boolean containsFreemarkerInterpolation(HttpServletRequest req, HttpServletResponse resp, String uri) + throws IOException { + String urisOkForFreemarker = UtilProperties.getPropertyValue("security", "allowedURIsForFreemarkerInterpolation"); + List<String> urisOK = UtilValidate.isNotEmpty(urisOkForFreemarker) ? StringUtil.split(urisOkForFreemarker, ",") + : new ArrayList<>(); + String uriEnd = uri.substring(uri.lastIndexOf("/") + 1, uri.length()); + + if (!urisOK.contains(uriEnd)) { + Map<String, String[]> parameterMap = req.getParameterMap(); + if (uri.contains("ecomseo")) { // SeoContextFilter call + if (containsFreemarkerInterpolation(resp, uri)) { + return true; + } + } else if (!parameterMap.isEmpty()) { // ControlFilter call + List<BasicNameValuePair> params = new ArrayList<>(); + parameterMap.forEach((name, values) -> { + for (String value : values) { + params.add(new BasicNameValuePair(name, value)); + } + }); + String queryString = URLEncodedUtils.format(params, Charset.forName("UTF-8")); + uri = uri + "?" + queryString; + if (containsFreemarkerInterpolation(resp, uri)) { + return true; + } + } else if (!UtilHttp.getAttributeMap(req).isEmpty()) { // Call with Content-Type modified by a MITM attack (rare case) + String attributeMap = UtilHttp.getAttributeMap(req).toString(); + if (containsFreemarkerInterpolation(resp, attributeMap)) { + return true; + } + } + } + return false; + } + + /** + * @param resp + * @param stringToCheck + * @throws IOException + */ + public static boolean containsFreemarkerInterpolation(HttpServletResponse resp, String stringToCheck) throws IOException { + if (containsFreemarkerInterpolation(stringToCheck)) { // not used OOTB in OFBiz, but possible + Debug.logError("===== Not saved for security reason, strings '${', '<#', '#{', '[=' or '[#' not accepted in fields! =====", MODULE); + resp.sendError(HttpServletResponse.SC_FORBIDDEN, + "Not saved for security reason, strings '${', '<#', '#{', '[=' or '[#' not accepted in fields!"); + return true; + } + return false; + } + + /** + * Analyze if stringToCheck contains a freemarker template + * @param stringToCheck + * @return true if freemarker template is detected + */ + public static boolean containsFreemarkerInterpolation(String stringToCheck) { + return UtilValidate.isNotEmpty(stringToCheck) + && FTL_INTERPOLATION.stream().anyMatch(stringToCheck::contains); + } + + /** + * Analyse each entry contains on params. If a freemarker template is detected, sanatize it to escape any exploit + * @param params + * @return Map with all values sanitized + */ + public static Map<String, Object> sanitizeParameterMap(Map<String, Object> params) { + List<Map.Entry<String, Object>> unsafeEntries = params.entrySet().stream() + .filter(entry -> entry.getValue() instanceof String + && containsFreemarkerInterpolation((String) entry.getValue())) + .toList(); + if (!unsafeEntries.isEmpty()) { + Map<String, Object> paramsSanitize = new HashMap<>(params); + unsafeEntries.forEach(entry -> { + String sanitazedValue = (String) entry.getValue(); + for (String interpolation : FTL_INTERPOLATION) { + sanitazedValue = sanitazedValue.replace(interpolation, "##"); + } + paramsSanitize.put(entry.getKey(), sanitazedValue); + }); + return paramsSanitize; + } + return params; + } +} diff --git a/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java b/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java index 446a1a475c..7648ab49c6 100644 --- a/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java +++ b/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java @@ -19,23 +19,14 @@ package org.apache.ofbiz.security; -import java.io.IOException; -import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.http.client.utils.URLEncodedUtils; -import org.apache.http.message.BasicNameValuePair; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.StringUtil; -import org.apache.ofbiz.base.util.UtilHttp; import org.apache.ofbiz.base.util.UtilMisc; -import org.apache.ofbiz.base.util.UtilProperties; import org.apache.ofbiz.base.util.UtilValidate; import org.apache.ofbiz.entity.Delegator; import org.apache.ofbiz.entity.GenericEntityException; @@ -172,67 +163,4 @@ public final class SecurityUtil { return false; } - /* - * Prevents Freemarker exploits - * @param req - * @param resp - * @param uri - * @throws IOException - */ - public static boolean containsFreemarkerInterpolation(HttpServletRequest req, HttpServletResponse resp, String uri) - throws IOException { - String urisOkForFreemarker = UtilProperties.getPropertyValue("security", "allowedURIsForFreemarkerInterpolation"); - List<String> urisOK = UtilValidate.isNotEmpty(urisOkForFreemarker) ? StringUtil.split(urisOkForFreemarker, ",") - : new ArrayList<>(); - String uriEnd = uri.substring(uri.lastIndexOf("/") + 1, uri.length()); - - if (!urisOK.contains(uriEnd)) { - Map<String, String[]> parameterMap = req.getParameterMap(); - if (uri.contains("ecomseo")) { // SeoContextFilter call - if (containsFreemarkerInterpolation(resp, uri)) { - return true; - } - } else if (!parameterMap.isEmpty()) { // ControlFilter call - List<BasicNameValuePair> params = new ArrayList<>(); - parameterMap.forEach((name, values) -> { - for (String value : values) { - params.add(new BasicNameValuePair(name, value)); - } - }); - String queryString = URLEncodedUtils.format(params, Charset.forName("UTF-8")); - uri = uri + "?" + queryString; - if (SecurityUtil.containsFreemarkerInterpolation(resp, uri)) { - return true; - } - } else if (!UtilHttp.getAttributeMap(req).isEmpty()) { // Call with Content-Type modified by a MITM attack (rare case) - String attributeMap = UtilHttp.getAttributeMap(req).toString(); - if (containsFreemarkerInterpolation(resp, attributeMap)) { - return true; - } - } - } - return false; - } - - /** - * @param resp - * @param stringToCheck - * @throws IOException - */ - public static boolean containsFreemarkerInterpolation(HttpServletResponse resp, String stringToCheck) throws IOException { - if (stringToCheck.contains("%24%7B") || stringToCheck.contains("${") - || stringToCheck.contains("%3C%23") || stringToCheck.contains("<#") - || stringToCheck.contains("%23%7B") || stringToCheck.contains("#{") - || stringToCheck.contains("%5B%3D") || stringToCheck.contains("[=") - || stringToCheck.contains("%5B%23") || stringToCheck.contains("[#")) { // not used OOTB in OFBiz, but possible - - Debug.logError("===== Not saved for security reason, strings '${', '<#', '#{', '[=' or '[#' not accepted in fields! =====", - MODULE); - resp.sendError(HttpServletResponse.SC_FORBIDDEN, - "Not saved for security reason, strings '${', '<#', '#{', '[=' or '[#' not accepted in fields!"); - return true; - } else { - return false; - } - } } diff --git a/framework/webapp/dtd/site-conf.xsd b/framework/webapp/dtd/site-conf.xsd index cbf2cfde81..79a02b7986 100644 --- a/framework/webapp/dtd/site-conf.xsd +++ b/framework/webapp/dtd/site-conf.xsd @@ -792,6 +792,16 @@ under the License. </xs:documentation> </xs:annotation> </xs:attribute> + <xs:attribute type="xs:boolean" name="secure-context" default="true"> + <xs:annotation> + <xs:documentation> + If secure-context=false, we authorize to forward to the renderer some parameters with interpretable code. + This raise a potential risk of code injection. This can be usefully for some administration page to + configure templating (like ftl for email or dynamic screen template). + For this reason if secure-context=false, auth is set as true. + </xs:documentation> + </xs:annotation> + </xs:attribute> <xs:attribute name="x-frame-options" default="sameorigin"> <xs:annotation> <xs:documentation> diff --git a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java index 59c3b91756..d6a16d3856 100644 --- a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java +++ b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java @@ -1044,6 +1044,7 @@ public final class ConfigXMLReader { private String strictTransportSecurity; private String description; private boolean noCache = false; + private boolean secureContext = true; private boolean securityAuth = false; /** @@ -1105,6 +1106,15 @@ public final class ConfigXMLReader { return noCache; } + /** + * Is secureContext boolean. + * + * @return the boolean + */ + public boolean isSecureContext() { + return secureContext; + } + /** * Gets type. * @return the type @@ -1144,7 +1154,8 @@ public final class ConfigXMLReader { this.info = viewMapElement.getAttribute("info"); this.contentType = viewMapElement.getAttribute("content-type"); this.noCache = "true".equals(viewMapElement.getAttribute("no-cache")); - this.securityAuth = "true".equals(viewMapElement.getAttribute("auth")); + this.secureContext = "true".equals(viewMapElement.getAttribute("secure-context")); + this.securityAuth = "true".equals(viewMapElement.getAttribute("auth")) || !this.secureContext; this.encoding = viewMapElement.getAttribute("encoding"); this.xFrameOption = viewMapElement.getAttribute("x-frame-options"); this.strictTransportSecurity = viewMapElement.getAttribute("strict-transport-security"); diff --git a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlFilter.java b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlFilter.java index f2c2a5a37d..ca8d2cca69 100644 --- a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlFilter.java +++ b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlFilter.java @@ -41,11 +41,8 @@ import org.apache.logging.log4j.ThreadContext; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.UtilValidate; import org.apache.ofbiz.entity.GenericValue; +import org.apache.ofbiz.security.SecuredFreemarker; import org.apache.ofbiz.security.SecuredUpload; -import org.apache.ofbiz.security.SecurityUtil; - - - /** * A Filter used to specify an allowlist of allowed paths to the OFBiz application. @@ -166,7 +163,7 @@ public class ControlFilter extends HttpFilter { GenericValue userLogin = (GenericValue) session.getAttribute("userLogin"); if (!LoginWorker.hasBasePermission(userLogin, req)) { // Allows UEL and FlexibleString (OFBIZ-12602) - if (isSolrTest() && SecurityUtil.containsFreemarkerInterpolation(req, resp, uri)) { + if (isSolrTest() && SecuredFreemarker.containsFreemarkerInterpolation(req, resp, uri)) { return; } } diff --git a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java index f02efae40f..9ec279ad67 100644 --- a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java +++ b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java @@ -1285,7 +1285,8 @@ public final class RequestHandler { Debug.logVerbose("Rendering view [" + nextPage + "] of type [" + viewMap.getType() + "]", MODULE); } ViewHandler vh = viewFactory.getViewHandler(viewMap.getType()); - vh.render(view, nextPage, viewMap.getInfo(), contentType, charset, req, resp); + Map<String, Object> context = vh.prepareViewContext(req, resp, viewMap); + vh.render(view, nextPage, viewMap.getInfo(), contentType, charset, req, resp, context); } catch (ViewHandlerException e) { Throwable throwable = e.getNested() != null ? e.getNested() : e; throw new RequestHandlerException(e.getNonNestedMessage(), throwable); diff --git a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/ftl/FreeMarkerViewHandler.java b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/ftl/FreeMarkerViewHandler.java index aa1974f5ad..05df6ea933 100644 --- a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/ftl/FreeMarkerViewHandler.java +++ b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/ftl/FreeMarkerViewHandler.java @@ -30,6 +30,8 @@ import org.apache.ofbiz.base.util.UtilHttp; import org.apache.ofbiz.base.util.UtilValidate; import org.apache.ofbiz.base.util.collections.MapStack; import org.apache.ofbiz.base.util.template.FreeMarkerWorker; +import org.apache.ofbiz.security.SecuredFreemarker; +import org.apache.ofbiz.webapp.control.ConfigXMLReader; import org.apache.ofbiz.webapp.view.AbstractViewHandler; import org.apache.ofbiz.webapp.view.ViewHandlerException; @@ -53,36 +55,10 @@ public class FreeMarkerViewHandler extends AbstractViewHandler { } @Override - public void render(String name, String page, String info, String contentType, String encoding, - HttpServletRequest request, HttpServletResponse response) throws ViewHandlerException { - if (UtilValidate.isEmpty(page)) { - throw new ViewHandlerException("Invalid template source"); - } - - // make the root context (data model) for freemarker - MapStack<String> context = MapStack.create(); - prepOfbizRoot(context, request, response); - - // process the template & flush the output - try { - if (page.startsWith("component://")) { - FreeMarkerWorker.renderTemplate(page, context, response.getWriter()); - } else { - // backwards compatibility - Template template = config.getTemplate(page); - FreeMarkerWorker.renderTemplate(template, context, response.getWriter()); - } - response.flushBuffer(); - } catch (TemplateException te) { - throw new ViewHandlerException("Problems processing Freemarker template", te); - } catch (IOException ie) { - throw new ViewHandlerException("Problems writing to output stream", ie); - } - } - - public static void prepOfbizRoot(Map<String, Object> root, HttpServletRequest request, HttpServletResponse response) { + public Map<String, Object> prepareViewContext(HttpServletRequest request, HttpServletResponse response, ConfigXMLReader.ViewMap viewMap) { ServletContext servletContext = request.getServletContext(); HttpSession session = request.getSession(); + MapStack<String> root = MapStack.create(); // add in the OFBiz objects root.put("delegator", request.getAttribute("delegator")); @@ -110,11 +86,38 @@ public class FreeMarkerViewHandler extends AbstractViewHandler { // add the request parameters -- this now uses a Map from UtilHttp Map<String, Object> requestParameters = UtilHttp.getParameterMap(request); + if (viewMap.isSecureContext()) { + requestParameters = SecuredFreemarker.sanitizeParameterMap(requestParameters); + } root.put("requestParameters", requestParameters); // add the TabLibFactory TaglibFactory jspTaglibs = new TaglibFactory(servletContext); root.put("JspTaglibs", jspTaglibs); + return root; + } + + @Override + public void render(String name, String page, String info, String contentType, String encoding, + HttpServletRequest request, HttpServletResponse response, Map<String, Object> context) throws ViewHandlerException { + if (UtilValidate.isEmpty(page)) { + throw new ViewHandlerException("Invalid template source"); + } + // process the template & flush the output + try { + if (page.startsWith("component://")) { + FreeMarkerWorker.renderTemplate(page, context, response.getWriter()); + } else { + // backwards compatibility + Template template = config.getTemplate(page); + FreeMarkerWorker.renderTemplate(template, context, response.getWriter()); + } + response.flushBuffer(); + } catch (TemplateException te) { + throw new ViewHandlerException("Problems processing Freemarker template", te); + } catch (IOException ie) { + throw new ViewHandlerException("Problems writing to output stream", ie); + } } } diff --git a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/view/HttpViewHandler.java b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/view/HttpViewHandler.java index fc8ad691be..f46c4d58df 100644 --- a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/view/HttpViewHandler.java +++ b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/view/HttpViewHandler.java @@ -20,6 +20,7 @@ package org.apache.ofbiz.webapp.view; import java.io.IOException; +import java.util.Map; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -28,6 +29,7 @@ import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.HttpClient; import org.apache.ofbiz.base.util.HttpClientException; import org.apache.ofbiz.base.util.UtilValidate; +import org.apache.ofbiz.webapp.control.ConfigXMLReader; /** * ViewHandlerException - View Handler Exception @@ -40,9 +42,14 @@ public class HttpViewHandler extends AbstractViewHandler { public void init(ServletContext context) throws ViewHandlerException { } + @Override + public Map<String, Object> prepareViewContext(HttpServletRequest request, HttpServletResponse response, ConfigXMLReader.ViewMap viewMap) { + return Map.of(); + } + @Override public void render(String name, String page, String info, String contentType, String encoding, HttpServletRequest request, HttpServletResponse - response) throws ViewHandlerException { + response, Map<String, Object> context) throws ViewHandlerException { // some containers call filters on EVERY request, even forwarded ones, // so let it know that it came from the control servlet diff --git a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/view/JspViewHandler.java b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/view/JspViewHandler.java index 23c04aa0e4..3112f34dbd 100644 --- a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/view/JspViewHandler.java +++ b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/view/JspViewHandler.java @@ -20,6 +20,7 @@ package org.apache.ofbiz.webapp.view; import java.io.IOException; +import java.util.Map; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; @@ -29,6 +30,7 @@ import javax.servlet.jsp.JspException; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.UtilValidate; +import org.apache.ofbiz.webapp.control.ConfigXMLReader; import org.apache.ofbiz.webapp.control.ControlFilter; /** @@ -37,16 +39,21 @@ import org.apache.ofbiz.webapp.control.ControlFilter; public class JspViewHandler extends AbstractViewHandler { private static final String MODULE = JspViewHandler.class.getName(); - private ServletContext context; + private ServletContext servletContext; @Override - public void init(ServletContext context) throws ViewHandlerException { - this.context = context; + public void init(ServletContext servletContext) throws ViewHandlerException { + this.servletContext = servletContext; + } + + @Override + public Map<String, Object> prepareViewContext(HttpServletRequest request, HttpServletResponse response, ConfigXMLReader.ViewMap viewMap) { + return Map.of(); } @Override public void render(String name, String page, String contentType, String encoding, String info, HttpServletRequest request, HttpServletResponse - response) throws ViewHandlerException { + response, Map<String, Object> context) throws ViewHandlerException { // some containers call filters on EVERY request, even forwarded ones, // so let it know that it came from the control servlet @@ -66,10 +73,10 @@ public class JspViewHandler extends AbstractViewHandler { if (rd == null) { Debug.logInfo("HttpServletRequest.getRequestDispatcher() failed; trying ServletContext", MODULE); - rd = context.getRequestDispatcher(page); + rd = this.servletContext.getRequestDispatcher(page); if (rd == null) { Debug.logInfo("ServletContext.getRequestDispatcher() failed; trying ServletContext.getNamedDispatcher(\"jsp\")", MODULE); - rd = context.getNamedDispatcher("jsp"); + rd = this.servletContext.getNamedDispatcher("jsp"); if (rd == null) { throw new ViewHandlerException("Source returned a null dispatcher (" + page + ")"); } diff --git a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/view/ViewHandler.java b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/view/ViewHandler.java index 6df8ec7b38..6c7591b096 100644 --- a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/view/ViewHandler.java +++ b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/view/ViewHandler.java @@ -18,9 +18,11 @@ *******************************************************************************/ package org.apache.ofbiz.webapp.view; +import java.util.Map; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.ofbiz.webapp.control.ConfigXMLReader; /** * ViewHandler - View Handler Interface @@ -55,8 +57,18 @@ public interface ViewHandler { * @param info An info string attached to this view * @param request The HttpServletRequest object used when requesting this page. * @param response The HttpServletResponse object to be used to present the page. + * @param context The context prepare by the handler to run * @throws ViewHandlerException */ void render(String name, String page, String info, String contentType, String encoding, HttpServletRequest request, - HttpServletResponse response) throws ViewHandlerException; + HttpServletResponse response, Map<String, Object> context) throws ViewHandlerException; + + /** + * Before call the render, this function have to purpose to analyse, secure and prepare the context + * @param request + * @param response + * @param viewMap + * @return + */ + Map<String, Object> prepareViewContext(HttpServletRequest request, HttpServletResponse response, ConfigXMLReader.ViewMap viewMap); } diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/ScreenRenderer.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/ScreenRenderer.java index 24174f7f24..26d3334545 100644 --- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/ScreenRenderer.java +++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/ScreenRenderer.java @@ -49,6 +49,7 @@ import org.apache.ofbiz.entity.Delegator; import org.apache.ofbiz.entity.GenericEntity; import org.apache.ofbiz.entity.GenericValue; import org.apache.ofbiz.entity.util.EntityUtilProperties; +import org.apache.ofbiz.security.SecuredFreemarker; import org.apache.ofbiz.security.Security; import org.apache.ofbiz.service.DispatchContext; import org.apache.ofbiz.service.GenericServiceException; @@ -219,12 +220,13 @@ public class ScreenRenderer { * @param response * @param servletContext */ - public void populateContextForRequest(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) { - populateContextForRequest(context, this, request, response, servletContext); + public void populateContextForRequest(HttpServletRequest request, HttpServletResponse response, + ServletContext servletContext, boolean secureParameters) { + populateContextForRequest(context, this, request, response, servletContext, secureParameters); } public static void populateContextForRequest(MapStack<String> context, ScreenRenderer screens, HttpServletRequest request, - HttpServletResponse response, ServletContext servletContext) { + HttpServletResponse response, ServletContext servletContext, boolean secureParameters) { HttpSession session = request.getSession(); // attribute names to skip for session and application attributes; these are all handled as special cases, @@ -232,6 +234,9 @@ public class ScreenRenderer { Set<String> attrNamesToSkip = UtilMisc.toSet("delegator", "dispatcher", "security", "webSiteId", "org.apache.catalina.jsp_classpath"); Map<String, Object> parameterMap = UtilHttp.getCombinedMap(request, attrNamesToSkip); + if (secureParameters) { + parameterMap = SecuredFreemarker.sanitizeParameterMap(parameterMap); + } GenericValue userLogin = (GenericValue) session.getAttribute("userLogin"); @@ -288,7 +293,7 @@ public class ScreenRenderer { context.put("requestAttributes", new HttpRequestHashModel(request, FreeMarkerWorker.getDefaultOfbizWrapper())); TaglibFactory jspTaglibs = new TaglibFactory(servletContext); context.put("JspTaglibs", jspTaglibs); - context.put("requestParameters", UtilHttp.getParameterMap(request)); + context.put("requestParameters", SecuredFreemarker.sanitizeParameterMap(UtilHttp.getParameterMap(request))); ServletContextHashModel ftlServletContext = (ServletContextHashModel) request.getAttribute("ftlServletContext"); context.put("Application", ftlServletContext); diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/fo/ScreenFopViewHandler.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/fo/ScreenFopViewHandler.java index 8185738853..e2aac75248 100644 --- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/fo/ScreenFopViewHandler.java +++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/fo/ScreenFopViewHandler.java @@ -25,6 +25,7 @@ import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; +import java.util.Map; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -39,8 +40,11 @@ import org.apache.fop.render.pdf.PDFEncryptionOption; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.GeneralException; import org.apache.ofbiz.base.util.UtilCodec; +import org.apache.ofbiz.base.util.UtilGenerics; import org.apache.ofbiz.base.util.UtilHttp; import org.apache.ofbiz.base.util.UtilValidate; +import org.apache.ofbiz.base.util.collections.MapStack; +import org.apache.ofbiz.webapp.control.ConfigXMLReader; import org.apache.ofbiz.webapp.view.AbstractViewHandler; import org.apache.ofbiz.webapp.view.ApacheFopWorker; import org.apache.ofbiz.webapp.view.ViewHandlerException; @@ -72,14 +76,21 @@ public class ScreenFopViewHandler extends AbstractViewHandler { this.servletContext = context; } + + @Override + public Map<String, Object> prepareViewContext(HttpServletRequest request, HttpServletResponse response, ConfigXMLReader.ViewMap viewMap) { + MapStack<String> context = MapStack.create(); + ScreenRenderer.populateContextForRequest(context, null, request, response, servletContext, viewMap.isSecureContext()); + return context; + } + /** - * @see org.apache.ofbiz.webapp.view.ViewHandler#render(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, - * javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + * @see org.apache.ofbiz.webapp.view.ViewHandler#render(String, String, String, String, String, HttpServletRequest, HttpServletResponse, Map) */ @SuppressWarnings("unchecked") @Override public void render(String name, String page, String info, String contentType, String encoding, HttpServletRequest request, - HttpServletResponse response) throws ViewHandlerException { + HttpServletResponse response, Map<String, Object> context) throws ViewHandlerException { VisualTheme visualTheme = UtilHttp.getVisualTheme(request); ModelTheme modelTheme = visualTheme.getModelTheme(); @@ -92,15 +103,14 @@ public class ScreenFopViewHandler extends AbstractViewHandler { // TODO: uncomment these lines when the renderers are implemented //TreeStringRenderer treeStringRenderer = new MacroTreeRenderer(modelTheme.getTreeRendererLocation(getName()), writer); //MenuStringRenderer menuStringRenderer = new MacroMenuRenderer(modelTheme.getMenuRendererLocation(getName()), writer); - ScreenRenderer screens = new ScreenRenderer(writer, null, screenStringRenderer); - screens.populateContextForRequest(request, response, servletContext); + ScreenRenderer screens = new ScreenRenderer(writer, UtilGenerics.cast(context), screenStringRenderer); // this is the object used to render forms from their definitions screens.getContext().put("formStringRenderer", formStringRenderer); screens.getContext().put("simpleEncoder", UtilCodec.getEncoder(modelTheme.getEncoder(getName()))); screens.render(page); } catch (IOException | GeneralException | SAXException | ParserConfigurationException | TemplateException e) { - renderError("Problems with the response writer/output stream", e, "[Not Yet Rendered]", request, response); + renderError("Problems with the response writer/output stream", e, "[Not Yet Rendered]", request, response, context); return; } @@ -118,21 +128,21 @@ public class ScreenFopViewHandler extends AbstractViewHandler { } // get encryption related parameters FOUserAgent foUserAgent = null; - String userPassword = request.getParameter("userPassword"); - String ownerPassword = request.getParameter("ownerPassword"); - boolean allowPrint = Boolean.parseBoolean(UtilValidate.isEmpty(request.getParameter("allowPrint")) - ? ApacheFopWorker.getAllowPrintDefault() : request.getParameter("allowPrint")); - boolean allowCopyContent = Boolean.parseBoolean(UtilValidate.isEmpty(request.getParameter("allowCopyContent")) - ? ApacheFopWorker.getAllowCopyContentDefault() : request.getParameter("allowCopyContent")); - boolean allowEditContent = Boolean.parseBoolean(UtilValidate.isEmpty(request.getParameter("allowEditContent")) - ? ApacheFopWorker.getAllowEditContentDefault() : request.getParameter("allowEditContent")); - boolean allowEditAnnotations = Boolean.parseBoolean(UtilValidate.isEmpty(request.getParameter("allowEditAnnotations")) - ? ApacheFopWorker.getAllowEditAnnotationsDefault() : request.getParameter("allowEditAnnotations")); + String userPassword = (String) context.get("userPassword"); + String ownerPassword = (String) context.get("ownerPassword"); + boolean allowPrint = Boolean.parseBoolean(UtilValidate.isEmpty(context.get("allowPrint")) + ? ApacheFopWorker.getAllowPrintDefault() : (String) context.get("allowPrint")); + boolean allowCopyContent = Boolean.parseBoolean(UtilValidate.isEmpty(context.get("allowCopyContent")) + ? ApacheFopWorker.getAllowCopyContentDefault() : (String) context.get("allowCopyContent")); + boolean allowEditContent = Boolean.parseBoolean(UtilValidate.isEmpty(context.get("allowEditContent")) + ? ApacheFopWorker.getAllowEditContentDefault() : (String) context.get("allowEditContent")); + boolean allowEditAnnotations = Boolean.parseBoolean(UtilValidate.isEmpty(context.get("allowEditAnnotations")) + ? ApacheFopWorker.getAllowEditAnnotationsDefault() : (String) context.get("allowEditAnnotations")); if (UtilValidate.isNotEmpty(userPassword) || UtilValidate.isNotEmpty(ownerPassword) || !allowPrint || !allowCopyContent || allowEditContent || !allowEditAnnotations) { int encryptionLength = 128; try { - encryptionLength = Integer.parseInt(request.getParameter("encryption-length")); + encryptionLength = Integer.parseInt((String) context.get("encryption-length")); } catch (NumberFormatException e) { try { encryptionLength = Integer.parseInt(ApacheFopWorker.getEncryptionLengthDefault()); @@ -141,16 +151,16 @@ public class ScreenFopViewHandler extends AbstractViewHandler { } } - boolean encryptMetadata = Boolean.parseBoolean(UtilValidate.isEmpty(request.getParameter("encrypt-metadata")) - ? ApacheFopWorker.getEncryptMetadataDefault() : request.getParameter("encrypt-metadata")); - boolean allowFillInForms = Boolean.parseBoolean(UtilValidate.isEmpty(request.getParameter("allowFillInForms")) - ? ApacheFopWorker.getAllowFillInFormsDefault() : request.getParameter("allowFillInForms")); - boolean allowAccessContent = Boolean.parseBoolean(UtilValidate.isEmpty(request.getParameter("allowAccessContent")) - ? ApacheFopWorker.getAllowAccessContentDefault() : request.getParameter("allowAccessContent")); - boolean allowAssembleDocument = Boolean.parseBoolean(UtilValidate.isEmpty(request.getParameter("allowAssembleDocument")) - ? ApacheFopWorker.getAllowAssembleDocumentDefault() : request.getParameter("allowAssembleDocument")); - boolean allowPrintHq = Boolean.parseBoolean(UtilValidate.isEmpty(request.getParameter("allowPrintHq")) - ? ApacheFopWorker.getAllowPrintHqDefault() : request.getParameter("allowPrintHq")); + boolean encryptMetadata = Boolean.parseBoolean(UtilValidate.isEmpty(context.get("encrypt-metadata")) + ? ApacheFopWorker.getEncryptMetadataDefault() : (String) context.get("encrypt-metadata")); + boolean allowFillInForms = Boolean.parseBoolean(UtilValidate.isEmpty(context.get("allowFillInForms")) + ? ApacheFopWorker.getAllowFillInFormsDefault() : (String) context.get("allowFillInForms")); + boolean allowAccessContent = Boolean.parseBoolean(UtilValidate.isEmpty(context.get("allowAccessContent")) + ? ApacheFopWorker.getAllowAccessContentDefault() : (String) context.get("allowAccessContent")); + boolean allowAssembleDocument = Boolean.parseBoolean(UtilValidate.isEmpty(context.get("allowAssembleDocument")) + ? ApacheFopWorker.getAllowAssembleDocumentDefault() : (String) context.get("allowAssembleDocument")); + boolean allowPrintHq = Boolean.parseBoolean(UtilValidate.isEmpty(context.get("allowPrintHq")) + ? ApacheFopWorker.getAllowPrintHqDefault() : (String) context.get("allowPrintHq")); FopFactory fopFactory = ApacheFopWorker.getFactoryInstance(); foUserAgent = fopFactory.newFOUserAgent(); PDFEncryptionParams pdfEncryptionParams = new PDFEncryptionParams(userPassword, ownerPassword, allowPrint, allowCopyContent, @@ -179,7 +189,7 @@ public class ScreenFopViewHandler extends AbstractViewHandler { Fop fop = ApacheFopWorker.createFopInstance(out, contentType, foUserAgent); ApacheFopWorker.transform(src, null, fop); } catch (Exception e) { - renderError("Unable to transform FO file", e, screenOutString, request, response); + renderError("Unable to transform FO file", e, screenOutString, request, response, context); return; } // set the content type and length @@ -191,7 +201,7 @@ public class ScreenFopViewHandler extends AbstractViewHandler { out.writeTo(response.getOutputStream()); response.getOutputStream().flush(); } catch (IOException e) { - renderError("Unable to write to OutputStream", e, screenOutString, request, response); + renderError("Unable to write to OutputStream", e, screenOutString, request, response, context); } } @@ -204,7 +214,9 @@ public class ScreenFopViewHandler extends AbstractViewHandler { * @param response the response * @throws ViewHandlerException the view handler exception */ - protected void renderError(String msg, Exception e, String screenOutString, HttpServletRequest request, HttpServletResponse response) + protected void renderError(String msg, Exception e, String screenOutString, + HttpServletRequest request, HttpServletResponse response, + Map<String, Object> context) throws ViewHandlerException { Debug.logError(msg + ": " + e + "; Screen XSL:FO text was:\n" + screenOutString, MODULE); try { @@ -214,8 +226,7 @@ public class ScreenFopViewHandler extends AbstractViewHandler { ScreenStringRenderer screenStringRenderer = new MacroScreenRenderer(modelTheme.getType("screen"), modelTheme.getScreenRendererLocation("screen")); - ScreenRenderer screens = new ScreenRenderer(writer, null, screenStringRenderer); - screens.populateContextForRequest(request, response, servletContext); + ScreenRenderer screens = new ScreenRenderer(writer, UtilGenerics.cast(context), screenStringRenderer); screens.getContext().put("errorMessage", msg + ": " + e); screens.render(DEFAULT_ERROR_TEMPLATE); response.setContentType("text/html"); diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroScreenViewHandler.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroScreenViewHandler.java index 4365a7bbaf..19350f6938 100644 --- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroScreenViewHandler.java +++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroScreenViewHandler.java @@ -33,6 +33,7 @@ import org.apache.ofbiz.base.util.UtilCodec; import org.apache.ofbiz.base.util.UtilHttp; import org.apache.ofbiz.base.util.UtilValidate; import org.apache.ofbiz.base.util.collections.MapStack; +import org.apache.ofbiz.webapp.control.ConfigXMLReader; import org.apache.ofbiz.webapp.view.AbstractViewHandler; import org.apache.ofbiz.webapp.view.ViewHandlerException; import org.apache.ofbiz.widget.model.ModelTheme; @@ -83,9 +84,17 @@ public class MacroScreenViewHandler extends AbstractViewHandler { return screenStringRenderer; } + + @Override + public Map<String, Object> prepareViewContext(HttpServletRequest request, HttpServletResponse response, ConfigXMLReader.ViewMap viewMap) { + MapStack<String> context = MapStack.create(); + ScreenRenderer.populateContextForRequest(context, null, request, response, servletContext, viewMap.isSecureContext()); + return context; + } + @Override public void render(String name, String page, String info, String contentType, String encoding, HttpServletRequest request, - HttpServletResponse response) throws ViewHandlerException { + HttpServletResponse response, Map<String, Object> context) throws ViewHandlerException { try { Writer writer = response.getWriter(); VisualTheme visualTheme = UtilHttp.getVisualTheme(request); @@ -106,10 +115,8 @@ public class MacroScreenViewHandler extends AbstractViewHandler { // to speed up output. writer = new StandardCompress().getWriter(writer, null); } - MapStack<String> context = MapStack.create(); - ScreenRenderer.populateContextForRequest(context, null, request, response, servletContext); ScreenStringRenderer screenStringRenderer = loadRenderers(request, response, context, writer); - ScreenRenderer screens = new ScreenRenderer(writer, context, screenStringRenderer); + ScreenRenderer screens = new ScreenRenderer(writer, MapStack.create(context), screenStringRenderer); context.put("screens", screens); context.put("simpleEncoder", UtilCodec.getEncoder(visualTheme.getModelTheme().getEncoder(getName()))); screenStringRenderer.renderBegin(writer, context);