All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.wicket.markup.html.SecurePackageResourceGuard Maven / Gradle / Ivy

Go to download

Pax Wicket Service is an OSGi extension of the Wicket framework, allowing for dynamic loading and unloading of Wicket components and pageSources.

There is a newer version: 5.0.0
Show newest version
/*
 * 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.wicket.markup.html;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Pattern;

import org.apache.wicket.settings.IResourceSettings;
import org.apache.wicket.util.collections.ReverseListIterator;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.string.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * This is a resource guard which by default denies access to all resources and thus is more secure.
 * 

* All pattern are executed in the order they were provided. All pattern are executed to determine * if access can be granted or not. *

* Note that access to the config data such as get/setPattern() and acceptXXX() is not synchronized. * It is assumed that configuration has finished before the first request gets executed. *

* The rules are fairly simple. Each pattern must start with either "+" (include) or "-" (exclude). * "*" is a placeholder for zero, one or more characters within a file or directory name. "**" is a * placeholder for zero, one or more sub-directories. *

* Examples: *

* * * * * * * * * * * * * * * * *
+*.gifAll gif files in all directories
+test*.*All files in all directories starting with "test"
+mydir/*/*.gifAll gif files two levels below the mydir directory. E.g. mydir/dir2/test.gif
+mydir/**/*.gifAll gif files in all directories below mydir. E.g. mydir/test.gif or * mydir/dir2/dir3/test.gif
* * @see IPackageResourceGuard * @see IResourceSettings#getPackageResourceGuard * @see PackageResourceGuard * * @author Juergen Donnerstag */ public class SecurePackageResourceGuard extends PackageResourceGuard { /** Log. */ private static final Logger log = LoggerFactory.getLogger(SecurePackageResourceGuard.class); /** The path separator used */ private static final char PATH_SEPARATOR = '/'; /** The list of pattern. Note that the order is important, hence a list */ private List pattern = new ArrayList(); /** A cache to speed up the checks */ private final ConcurrentMap cache; /** * Constructor. */ public SecurePackageResourceGuard() { this(new SimpleCache(100)); } /** * Constructor. * * @param cache * the internal cache that will hold the results for all already checked resources. * Use {@code null} to disable caching. */ public SecurePackageResourceGuard(final ConcurrentMap cache) { this.cache = cache; // the order is important for better performance // first add the most commonly used addPattern("+*.js"); addPattern("+*.css"); addPattern("+*.png"); addPattern("+*.jpg"); addPattern("+*.jpeg"); addPattern("+*.gif"); addPattern("+*.ico"); // WICKET-208 non page templates may be served addPattern("+*.html"); addPattern("+*.txt"); addPattern("+*.swf"); addPattern("+*.bmp"); } /** * Get a new cache implementation. Subclasses may return null to disable caching. More advanced * caches (e.h. ehcache) should be used in production environments to limit the size and remove * "old" entries. * * @return the cache implementation * @deprecated Pass the cache as a parameter to the constructor */ @Deprecated public ConcurrentHashMap newCache() { return new SimpleCache(100); } /** * */ public void clearCache() { if (cache != null) { cache.clear(); } } /** * Whether the provided absolute path is accepted. * * @param path * The absolute path, starting from the class root (packages are separated with * forward slashes instead of dots). * @return True if accepted, false otherwise. */ @Override protected boolean acceptAbsolutePath(String path) { // First check the cache if (cache != null) { Boolean rtn = cache.get(path); if (rtn != null) { return rtn; } } // Check typical files such as log4j.xml etc. if (super.acceptAbsolutePath(path) == false) { return false; } // Check against the pattern boolean hit = false; for (SearchPattern pattern : new ReverseListIterator(this.pattern)) { if ((pattern != null) && pattern.isActive()) { if (pattern.matches(path)) { hit = pattern.isInclude(); break; } } } if (cache != null) { // Do not use putIfAbsent(). See newCache() cache.put(path, (hit ? Boolean.TRUE : Boolean.FALSE)); } if (hit == false) { log.warn("Access denied to shared (static) resource: " + path); } return hit; } /** * Gets the current list of pattern. Please invoke clearCache() or setPattern(List) when * finished in order to clear the cache of previous checks. * * @return pattern */ public List getPattern() { clearCache(); return pattern; } /** * Sets pattern. * * @param pattern * pattern */ public void setPattern(List pattern) { this.pattern = pattern; clearCache(); } /** * @param pattern */ public void addPattern(String pattern) { this.pattern.add(new SearchPattern(pattern)); clearCache(); } /** * */ public static class SearchPattern { private String pattern; private Pattern regex; private boolean include; private boolean active = true; private boolean fileOnly; /** * Construct. * * @param pattern */ public SearchPattern(final String pattern) { setPattern(pattern); } /** * * @param pattern * @return Regex pattern */ private Pattern convertToRegex(final String pattern) { String regex = Strings.replaceAll(pattern, ".", "#dot#").toString(); // If path starts with "*/" or "**/" regex = regex.replaceAll("^\\*" + PATH_SEPARATOR, "[^" + PATH_SEPARATOR + "]+" + PATH_SEPARATOR); regex = regex.replaceAll("^[\\*]{2,}" + PATH_SEPARATOR, "([^" + PATH_SEPARATOR + "].#star#" + PATH_SEPARATOR + ")?"); // Handle "/*/" and "/**/" regex = regex.replaceAll(PATH_SEPARATOR + "\\*" + PATH_SEPARATOR, PATH_SEPARATOR + "[^" + PATH_SEPARATOR + "]+" + PATH_SEPARATOR); regex = regex.replaceAll(PATH_SEPARATOR + "[\\*]{2,}" + PATH_SEPARATOR, "(" + PATH_SEPARATOR + "|" + PATH_SEPARATOR + ".+" + PATH_SEPARATOR + ")"); // Handle "*" within dir or file names regex = regex.replaceAll("\\*+", "[^" + PATH_SEPARATOR + "]*"); // replace placeholder regex = Strings.replaceAll(regex, "#dot#", "\\.").toString(); regex = Strings.replaceAll(regex, "#star#", "*").toString(); return Pattern.compile(regex); } /** * Gets pattern. * * @return pattern */ public String getPattern() { return pattern; } /** * Gets regex. * * @return regex */ public Pattern getRegex() { return regex; } /** * Sets pattern. * * @param pattern * pattern */ public void setPattern(String pattern) { if (Strings.isEmpty(pattern)) { throw new IllegalArgumentException( "Parameter 'pattern' can not be null or an empty string"); } if (pattern.charAt(0) == '+') { include = true; } else if (pattern.charAt(0) == '-') { include = false; } else { throw new IllegalArgumentException( "Parameter 'pattern' must start with either '+' or '-'. pattern='" + pattern + "'"); } this.pattern = pattern; regex = convertToRegex(pattern.substring(1)); fileOnly = (pattern.indexOf(PATH_SEPARATOR) == -1); } /** * * @param path * @return True if 'path' matches the pattern */ public boolean matches(String path) { if (fileOnly) { path = Strings.lastPathComponent(path, PATH_SEPARATOR); } return regex.matcher(path).matches(); } /** * Gets include. * * @return include */ public boolean isInclude() { return include; } /** * Sets include. * * @param include * include */ public void setInclude(boolean include) { this.include = include; } /** * Gets active. * * @return active */ public boolean isActive() { return active; } /** * Sets active. * * @param active * active */ public void setActive(boolean active) { this.active = active; } @Override public String toString() { return "Pattern: " + pattern + ", Regex: " + regex + ", include:" + include + ", fileOnly:" + fileOnly + ", active:" + active; } } /** * A very simple cache */ public static class SimpleCache extends ConcurrentHashMap { private static final long serialVersionUID = 1L; private final ConcurrentLinkedQueue fifo = new ConcurrentLinkedQueue(); private final int maxSize; /** * Construct. * * @param maxSize */ public SimpleCache(int maxSize) { this.maxSize = maxSize; } /** * @see java.util.concurrent.ConcurrentHashMap#put(java.lang.Object, java.lang.Object) */ @Override public Boolean put(String key, Boolean value) { // add the key to the hash map. Do not replace existing once Boolean rtn = super.putIfAbsent(key, value); // If found, than remove it from the fifo list and ... if (rtn != null) { fifo.remove(key); } // append it at the end of the list fifo.add(key); // remove all "outdated" cache entries while (fifo.size() > maxSize) { remove(fifo.poll()); } return rtn; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy