001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.ugc;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsResource;
032import org.opencms.i18n.CmsEncoder;
033import org.opencms.json.JSONException;
034import org.opencms.main.CmsException;
035import org.opencms.main.CmsLog;
036import org.opencms.ugc.CmsUgcValueTransformerConfiguration.CmsUgcSingleValueTransformer;
037import org.opencms.util.CmsParameterEscaper;
038import org.opencms.xml.CmsXmlException;
039import org.opencms.xml.CmsXmlUtils;
040import org.opencms.xml.content.CmsXmlContent;
041import org.opencms.xml.content.CmsXmlContentFactory;
042
043import org.apache.commons.logging.Log;
044
045import org.owasp.validator.html.AntiSamy;
046import org.owasp.validator.html.CleanResults;
047import org.owasp.validator.html.PolicyException;
048import org.owasp.validator.html.ScanException;
049
050/**
051 * The class transforms the values that should be written to an XML content via UGC according to a
052 * content type specific value transformation configuration.
053 *
054 * By default all values are xml escaped to void XSS issues when content that is created via UGC is
055 * rendered on a webpage without any extra caution.
056 */
057public class CmsUgcValueTranformHandler {
058
059    /** The log instance for this class. */
060    private static final Log LOG = CmsLog.getLog(CmsUgcValueTranformHandler.class);
061
062    /** The default value transformer. */
063    public static final CmsUgcValueTranformHandler DEFAULT = new CmsUgcValueTranformHandler();
064
065    /** XSD parameter where the value transformer configuration is provided. */
066    private static String PARAM_UGC_VALUES_TRANSFORMER = "ugc.values.transformer";
067
068    /** Config parameter policy for the antisamy value transformer. */
069    private static String JSON_KEY_POLICY = "policy";
070
071    /** The context. */
072    private CmsObject m_cms;
073    /** The value transformer configuration. */
074    private CmsUgcValueTransformerConfiguration m_config;
075    /** The (lazily initialized) parameter escaper used for antisamy transformations. */
076    private CmsParameterEscaper m_escaper;
077
078    /**
079     * Creates the value transformer instance for the provided resource.
080     * @param cms the context
081     * @param resource the resource of the XML content to get the mapping security for
082     * @throws CmsXmlException thrown if the content can't be unmarshalled
083     * @throws CmsException thrown if the content can't be read
084     * @throws IllegalArgumentException thrown if the value transformer configuration contains invalid transformers.
085     * @throws JSONException thrown if the value transformer configuration is no valid JSON.
086     */
087    public CmsUgcValueTranformHandler(CmsObject cms, CmsResource resource)
088    throws CmsXmlException, CmsException, IllegalArgumentException, JSONException {
089
090        m_cms = cms;
091        CmsXmlContent content = CmsXmlContentFactory.unmarshal(m_cms, m_cms.readFile(resource));
092        String valuesTransformerConfig = content.getHandler().getParameter(PARAM_UGC_VALUES_TRANSFORMER);
093        m_config = new CmsUgcValueTransformerConfiguration(valuesTransformerConfig);
094    }
095
096    /**
097     * Constructor to get the default value transformer configuration.
098     */
099    private CmsUgcValueTranformHandler() {
100
101        m_config = CmsUgcValueTransformerConfiguration.DEFAULT;
102    }
103
104    /**
105     * The method applies the configured value transformer to the given value.
106     * @param path the XML path to store the value
107     * @param value the value to transform
108     * @return the transformed value
109     */
110    public String transformValue(String path, String value) {
111
112        CmsUgcSingleValueTransformer secVal = m_config.getTransformer(CmsXmlUtils.removeAllXpathIndices(path));
113        switch (secVal.getType()) {
114            case none:
115                return value;
116            case antisamy:
117                AntiSamy antiSamy = getParameterEscaper().createAntiSamy(
118                    m_cms,
119                    null == secVal.getConfig() ? null : secVal.getConfig().optString(JSON_KEY_POLICY, null));
120                try {
121                    CleanResults cres = antiSamy.scan(value);
122                    return cres.getCleanHTML();
123                } catch (ScanException | PolicyException e) {
124                    LOG.error(
125                        "Failed to clean HTML value \""
126                            + value
127                            + "\" in path \""
128                            + path
129                            + "\" via AntiSamy. Defaulting to 'escape'.",
130                        e);
131                }
132                break;
133            case escape:
134                // do the default;
135                break;
136            default:
137                LOG.error("Unsupported Security mapping type " + secVal.getType() + ". Defaulting to 'escape'.");
138        }
139        // Default: Escape
140        return CmsEncoder.escapeXml(value);
141    }
142
143    /**
144     * Returns the parameter escaper to use for antisamy.
145     * @return the parameter escaper to use for antisamy.
146     */
147    private CmsParameterEscaper getParameterEscaper() {
148
149        if (m_escaper == null) {
150            m_escaper = new CmsParameterEscaper();
151        }
152        return m_escaper;
153    }
154}