Skip to content

Commit

Permalink
SLING-8235 - Stop copying the AntiSamy configuration to the repository
Browse files Browse the repository at this point in the history
* stopped providing initial content
* implemented a Felix Webconsole Plugin to be able to check the active AntiSamy configuration
* added multithreading safeguards
  • Loading branch information
raducotescu committed Jan 25, 2019
1 parent f21410e commit f03f628
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 26 deletions.
3 changes: 0 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,6 @@
esapi;inline=true,
encoder;inline=true
</Embed-Dependency>
<Sling-Initial-Content>
SLING-INF/content;path:=/libs/sling/xss;overwrite:=true;ignoreImportProviders:=xml
</Sling-Initial-Content>
</instructions>
</configuration>
</plugin>
Expand Down
71 changes: 55 additions & 16 deletions src/main/java/org/apache/sling/xss/impl/XSSFilterImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -140,17 +140,18 @@ public class XSSFilterImpl implements XSSFilter {
);

static final String DEFAULT_POLICY_PATH = "sling/xss/config.xml";
private static final String EMBEDDED_POLICY_PATH = "SLING-INF/content/config.xml";
private PolicyHandler policyHandler;
static final String EMBEDDED_POLICY_PATH = "SLING-INF/content/config.xml";
private Attribute hrefAttribute;
private String policyPath;
private boolean policyLoadedFromFile;
private ServiceRegistration<ResourceChangeListener> serviceRegistration;

// available contexts
private final XSSFilterRule htmlHtmlContext = new HtmlToHtmlContentContext();
private final XSSFilterRule plainHtmlContext = new PlainTextToHtmlContentContext();

private volatile AntiSamyPolicy activePolicy;
private volatile PolicyHandler policyHandler;

@Reference
private ResourceResolverFactory resourceResolverFactory;

Expand Down Expand Up @@ -197,6 +198,10 @@ public boolean isValidHref(String url) {
return false;
}

AntiSamyPolicy getActivePolicy() {
return activePolicy;
}

private boolean runHrefValidation(@NotNull String url) {
// Same logic as in org.owasp.validator.html.scan.MagicSAXFilter.startElement()
boolean isValid = hrefAttribute.containsAllowedValue(url.toLowerCase());
Expand All @@ -210,7 +215,6 @@ private boolean runHrefValidation(@NotNull String url) {
@Modified
protected void activate(ComponentContext componentContext, Configuration configuration) {
// load default handler
policyLoadedFromFile = false;
policyPath = configuration.policyPath();
updatePolicy();
if (serviceRegistration != null) {
Expand All @@ -219,17 +223,15 @@ protected void activate(ComponentContext componentContext, Configuration configu
Dictionary<String, Object> rclProperties = new Hashtable<>();
rclProperties.put(ResourceChangeListener.CHANGES, new String[]{"ADDED", "CHANGED", "REMOVED"});
rclProperties.put(ResourceChangeListener.PATHS, policyPath);
if (policyLoadedFromFile) {
serviceRegistration = componentContext.getBundleContext()
.registerService(ResourceChangeListener.class, new PolicyChangeListener(), rclProperties);
logger.info("Registered a resource change listener for file {}.", policyPath);
}
serviceRegistration = componentContext.getBundleContext()
.registerService(ResourceChangeListener.class, new PolicyChangeListener(), rclProperties);
logger.info("Registered a resource change listener for file {}.", policyPath);
}

@Deactivate
protected void deactivate() {
if (serviceRegistration != null) {
serviceRegistration.unregister();;
serviceRegistration.unregister();
}
}

Expand All @@ -238,11 +240,12 @@ private synchronized void updatePolicy() {
try (final ResourceResolver xssResourceResolver = resourceResolverFactory.getServiceResourceResolver(null)) {
Resource policyResource = xssResourceResolver.getResource(policyPath);
if (policyResource != null) {
try (InputStream policyStream = policyResource.adaptTo(InputStream.class)) {
activePolicy = new AntiSamyPolicy(false, policyResource.getPath());
try (InputStream policyStream = activePolicy.read()) {
setPolicyHandler(new PolicyHandler(policyStream));
logger.info("Installed policy from {}.", policyResource.getPath());
policyLoadedFromFile = true;
logger.info("Installed policy from {}.", activePolicy.path);
} catch (Exception e) {
activePolicy = null;
Throwable[] suppressed = e.getSuppressed();
if (suppressed.length > 0) {
for (Throwable t : suppressed) {
Expand All @@ -259,10 +262,12 @@ private synchronized void updatePolicy() {
// the content was not installed but the service is active; let's use the embedded file for the default handler
logger.info("Could not find a policy file at the configured location {}. Attempting to use the default resource embedded in" +
" the bundle.", policyPath);
try (InputStream policyStream = this.getClass().getClassLoader().getResourceAsStream(EMBEDDED_POLICY_PATH)) {
activePolicy = new AntiSamyPolicy(true, EMBEDDED_POLICY_PATH);
try (InputStream policyStream = activePolicy.read()) {
setPolicyHandler(new PolicyHandler(policyStream));
logger.info("Installed policy from the embedded {} file from the bundle.", EMBEDDED_POLICY_PATH);
logger.info("Installed policy from the embedded {} file from the bundle.", activePolicy.path);
} catch (Exception e) {
activePolicy = null;
Throwable[] suppressed = e.getSuppressed();
if (suppressed.length > 0) {
for (Throwable t : suppressed) {
Expand All @@ -272,7 +277,7 @@ private synchronized void updatePolicy() {
logger.error("Unable to load policy from embedded policy file.", e);
}
}
if (policyHandler == null) {
if (policyHandler == null || activePolicy == null) {
throw new IllegalStateException("Cannot load a policy handler.");
}
}
Expand Down Expand Up @@ -314,4 +319,38 @@ public void onChange(@NotNull List<ResourceChange> resourceChanges) {
}
}
}

class AntiSamyPolicy {
private final boolean embedded;
private final String path;

AntiSamyPolicy(boolean embedded, String path) {
this.embedded = embedded;
this.path = path;
}

InputStream read() {
if (embedded) {
return this.getClass().getClassLoader().getResourceAsStream(EMBEDDED_POLICY_PATH);
}
try (final ResourceResolver xssResourceResolver = resourceResolverFactory.getServiceResourceResolver(null)) {
Resource policyResource = xssResourceResolver.getResource(path);
if (policyResource != null) {
return policyResource.adaptTo(InputStream.class);
} else {
throw new IllegalStateException("Cannot read policy from " + path + ".");
}
} catch (LoginException e) {
throw new IllegalStateException("Cannot read policy.", e);
}
}

boolean isEmbedded() {
return embedded;
}

String getPath() {
return path;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ 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.sling.xss.impl;

import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.nio.charset.StandardCharsets;

import javax.servlet.Servlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.sling.xss.XSSFilter;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(
service = Servlet.class,
property = {
XSSProtectionAPIWebConsolePlugin.REG_PROP_LABEL + "=" + XSSProtectionAPIWebConsolePlugin.LABEL,
XSSProtectionAPIWebConsolePlugin.REG_PROP_TITLE + "=" + XSSProtectionAPIWebConsolePlugin.TITLE,
XSSProtectionAPIWebConsolePlugin.REG_PROP_CATEGORY + "=Sling"
}
)
public class XSSProtectionAPIWebConsolePlugin extends HttpServlet {

/*
do not replace the following constants with the ones from org.apache.felix, since you'll create a wiring to those APIs; the
current way this plugin is written allows it to optionally be available, if the Felix Web Console is installed on the OSGi
platform where this bundle will be deployed
*/
static final String REG_PROP_LABEL = "felix.webconsole.label";
static final String REG_PROP_TITLE = "felix.webconsole.title";
static final String REG_PROP_CATEGORY = "felix.webconsole.category";
static final String LABEL = "xssprotection";
static final String TITLE= "XSS Protection";

private static final String RES_LOC = LABEL + "/res/ui";
private static final Logger LOGGER = LoggerFactory.getLogger(XSSProtectionAPIWebConsolePlugin.class);

@Reference(target = "(component.name=org.apache.sling.xss.impl.XSSFilterImpl)")
private XSSFilter xssFilter;

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
if (request.getRequestURI().endsWith(RES_LOC + "/prettify.css")) {
try(InputStream cssStream = getClass().getClassLoader().getResourceAsStream("/res/ui/prettify.css")) {
if (cssStream != null) {
response.setContentType("text/css");
IOUtils.copy(cssStream, response.getOutputStream());
}
}
} else if (request.getRequestURI().endsWith(RES_LOC + "/prettify.js")) {
try(InputStream jsStream = getClass().getClassLoader().getResourceAsStream("/res/ui/prettify.js")) {
if (jsStream != null) {
response.setContentType("application/javascript");
IOUtils.copy(jsStream, response.getOutputStream());
}
}
} else {
if (xssFilter != null) {
XSSFilterImpl xssFilterImpl = (XSSFilterImpl) xssFilter;
XSSFilterImpl.AntiSamyPolicy antiSamyPolicy = xssFilterImpl.getActivePolicy();
if (antiSamyPolicy != null) {
Writer w = response.getWriter();
w.write("<link rel=\"stylesheet\" type=\"text/css\" href=\"" + RES_LOC + "/prettify.css\"></link>");
w.write("<script type=\"text/javascript\" src=\"" + RES_LOC + "/prettify.js\"></script>");
w.write("<script type=\"text/javascript\" src=\"" + RES_LOC + "/fsclassloader.js\"></script>");
w.write("<script>$(document).ready(prettyPrint);</script>");
w.write("<style>.prettyprint ol.linenums > li { list-style-type: decimal; } pre.prettyprint { white-space: pre-wrap; " +
"}</style>");
w.write("<p class=\"statline ui-state-highlight\">The current AntiSamy configuration ");
if (antiSamyPolicy.isEmbedded()) {
w.write("is the default one embedded in the org.apache.sling.xss bundle.");
} else {
w.write("is loaded from ");
w.write(antiSamyPolicy.getPath());
w.write(".");
}
w.write("</p>");
String contents = "";
try(InputStream configurationStream = antiSamyPolicy.read()) {
contents = IOUtils.toString(configurationStream, StandardCharsets.UTF_8);
} catch (Throwable t) {
LOGGER.error("Unable to read policy file.", t);
}
w.write("<pre class=\"prettyprint linenums\">");
w.write(StringEscapeUtils.escapeHtml4(contents));
w.write("</pre>");
}
}

}
}
}
17 changes: 17 additions & 0 deletions src/main/resources/res/ui/prettify.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* @license
* Copyright (C) 2015 Google Inc.
*
* Licensed 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.
*/
.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.clo,.opn,.pun{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.kwd,.tag,.typ{font-weight:700}.str{color:#060}.kwd{color:#006}.com{color:#600;font-style:italic}.typ{color:#404}.lit{color:#044}.clo,.opn,.pun{color:#440}.tag{color:#006}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}

0 comments on commit f03f628

Please sign in to comment.