/* * 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.samples.urlfilter.impl; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.regex.Pattern; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.apache.commons.lang.ArrayUtils; import org.apache.felix.scr.annotations.sling.SlingFilter; import org.apache.felix.scr.annotations.sling.SlingFilterScope; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.request.RequestPathInfo; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.resource.ValueMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Illustration of a technique for url filtering on suffixes, selectors, and/or * extensions */ @SlingFilter(scope = SlingFilterScope.REQUEST, order = Integer.MIN_VALUE) public class UrlFilter implements Filter { static final String PN_ALLOWED_EXTENSION_PATTERN = "allowedExtensionPattern"; static final String PN_ALLOWED_EXTENSIONS = "allowedExtensions"; static final String PN_ALLOWED_SELECTOR_PATTERN = "allowedSelectorPattern"; static final String PN_ALLOWED_SELECTORS = "allowedSelectors"; static final String PN_ALLOWED_SUFFIX_PATTERN = "allowedSuffixPattern"; static final String PN_ALLOWED_SUFFIXES = "allowedSuffixes"; static final Collection<String> PROPERTY_NAMES = Arrays.asList(PN_ALLOWED_SUFFIXES, PN_ALLOWED_EXTENSIONS, PN_ALLOWED_SELECTORS, PN_ALLOWED_SUFFIX_PATTERN, PN_ALLOWED_SELECTOR_PATTERN, PN_ALLOWED_EXTENSION_PATTERN); private Logger logger = LoggerFactory.getLogger(this.getClass()); public void destroy() { // nothing to do } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (request instanceof SlingHttpServletRequest && response instanceof SlingHttpServletResponse) { SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request; SlingHttpServletResponse slingResponse = (SlingHttpServletResponse) response; RequestPathInfo pathInfo = slingRequest.getRequestPathInfo(); Resource definitionResource = findUrlFilterDefinitionResource(slingRequest.getResource(), slingRequest.getResourceResolver()); if (definitionResource != null) { logger.debug("found url filter definition resource at {}", definitionResource.getPath()); ValueMap properties = definitionResource.adaptTo(ValueMap.class); if (properties != null) { if (checkSelector(pathInfo, properties) && checkSuffix(pathInfo, properties) && checkExtension(pathInfo, properties)) { logger.debug("url filter definition resource at {} passed for request {}.", definitionResource.getPath(), slingRequest.getRequestPathInfo()); } else { logger.info("url filter definition resource at {} FAILED for request {}.", definitionResource.getPath(), slingRequest.getRequestPathInfo()); slingResponse.sendError(403); return; } } } } chain.doFilter(request, response); } public void init(FilterConfig filterConfig) throws ServletException { // nothing to do } boolean checkExtension(RequestPathInfo pathInfo, ValueMap properties) { return check(pathInfo.getExtension(), PN_ALLOWED_EXTENSIONS, PN_ALLOWED_EXTENSION_PATTERN, properties); } boolean checkSelector(RequestPathInfo pathInfo, ValueMap properties) { return check(pathInfo.getSelectorString(), PN_ALLOWED_SELECTORS, PN_ALLOWED_SELECTOR_PATTERN, properties); } boolean check(String value, String allowedArrayPropertyName, String allowedPatternPropertyName, ValueMap properties) { if (value == null) { // no value is always allowed return true; } String[] allowedValues = properties.get(allowedArrayPropertyName, String[].class); if (allowedValues != null) { if (allowedValues.length == 0) { logger.debug("{} was empty, therefore not allowing any value.", allowedArrayPropertyName); return false; } else if (!ArrayUtils.contains(allowedValues, value)) { logger.debug("{} did not contain our string {}. checking the pattern.", allowedArrayPropertyName, value); String allowedPattern = properties.get(allowedPatternPropertyName, String.class); if (allowedPattern == null || !Pattern.matches(allowedPattern, value)) { logger.debug("allowedPattern ({}) did not match our string {}", allowedPattern, value); return false; } else { logger.debug("allowedPattern ({}) did match our string {}", allowedPattern, value); return true; } } else { return true; } } else { String allowedPattern = properties.get(allowedPatternPropertyName, String.class); if (allowedPattern != null && !Pattern.matches(allowedPattern, value)) { logger.debug("allowedPattern ({}) did not match our string {}", allowedPattern, value); return false; } else { return true; } } } boolean checkSuffix(RequestPathInfo pathInfo, ValueMap properties) { return check(pathInfo.getSuffix(), PN_ALLOWED_SUFFIXES, PN_ALLOWED_SUFFIX_PATTERN, properties); } Resource findUrlFilterDefinitionResource(Resource resource, ResourceResolver resolver) { if (resource == null) { return null; } Resource contentResource = resource.getChild("jcr:content"); if (contentResource != null) { resource = contentResource; } String resourceType = resource.getResourceType(); Resource definitionResource = findUrlFilterDefinitionResource(resourceType, resolver); if (definitionResource == null) { return findUrlFilterDefinitionResource(resource.getResourceSuperType(), resolver); } else { return definitionResource; } } private Resource findUrlFilterDefinitionResource(String resourceType, ResourceResolver resolver) { if (resourceType == null) { return null; } Resource typeResource = resolver.getResource(resourceType); if (typeResource == null) { return null; } ValueMap properties = typeResource.adaptTo(ValueMap.class); if (properties == null) { return null; } // Collections.disjoint returns true if the collections // have nothing in common, so when it is false, use the current resource if (!Collections.disjoint(properties.keySet(), PROPERTY_NAMES)) { return typeResource; } else { // otherwise, look at the resource type resource's super type return findUrlFilterDefinitionResource(typeResource.getResourceSuperType(), resolver); } } }