[Git][java-team/shiro][master] 6 commits: Import Debian changes 1.3.2-5
Emmanuel Bourg (@ebourg)
gitlab at salsa.debian.org
Tue Dec 17 14:40:11 GMT 2024
Emmanuel Bourg pushed to branch master at Debian Java Maintainers / shiro
Commits:
f83bf205 by Roberto C. Sánchez at 2021-08-27T13:10:19-04:00
Import Debian changes 1.3.2-5
shiro (1.3.2-5) unstable; urgency=medium
.
* Team upload.
* Update patch for Spring Framework 4.3.x build failure.
* Cherry-pick upstream patch with Guice improvements.
* CVE-2020-1957: Fix a path-traversal issue where a specially-crafted request
could cause an authentication bypass. (Closes: #955018)
* CVE-2020-11989: Fix an encoding issue introduced in the handling of the
previous CVE-2020-1957 path-traversal issue which could have also caused an
authentication bypass.
* CVE-2020-13933: Fix an authentication bypass resulting from a specially
crafted HTTP request. (Closes: #968753)
* CVE-2020-17510: Fix an authentication bypass resulting from a specially
crafted HTTP request.
- - - - -
217a26d8 by Emmanuel Bourg at 2024-12-17T15:28:28+01:00
Standards-Version updated to 4.7.0
- - - - -
e4e54ef5 by Emmanuel Bourg at 2024-12-17T15:28:32+01:00
Switch to debhelper level 13
- - - - -
c540df96 by Emmanuel Bourg at 2024-12-17T15:35:00+01:00
Depend on libservlet-api-java instead of libservlet3.1-java
- - - - -
f26685ee by Emmanuel Bourg at 2024-12-17T15:36:31+01:00
Ignore the deprecated guice-multibindings dependency
- - - - -
058ca477 by Emmanuel Bourg at 2024-12-17T15:36:56+01:00
Upload to unstable
- - - - -
12 changed files:
- debian/changelog
- − debian/compat
- debian/control
- debian/maven.ignoreRules
- debian/maven.rules
- debian/patches/03-spring-compatibility.patch
- + debian/patches/05-guice-improvements.patch
- + debian/patches/CVE-2020-13933.patch
- + debian/patches/CVE-2020-17510_1_of_2.patch
- + debian/patches/CVE-2020-17510_2_of_2.patch
- + debian/patches/CVE-2020-1957.patch
- debian/patches/series
Changes:
=====================================
debian/changelog
=====================================
@@ -1,3 +1,29 @@
+shiro (1.3.2-6) unstable; urgency=medium
+
+ * Depend on libservlet-api-java instead of libservlet3.1-java
+ * Ignore the deprecated guice-multibindings dependency
+ * Standards-Version updated to 4.7.0
+ * Switch to debhelper level 13
+
+ -- Emmanuel Bourg <ebourg at apache.org> Tue, 17 Dec 2024 15:36:48 +0100
+
+shiro (1.3.2-5) unstable; urgency=medium
+
+ * Team upload.
+ * Update patch for Spring Framework 4.3.x build failure.
+ * Cherry-pick upstream patch with Guice improvements.
+ * CVE-2020-1957: Fix a path-traversal issue where a specially-crafted request
+ could cause an authentication bypass. (Closes: #955018)
+ * CVE-2020-11989: Fix an encoding issue introduced in the handling of the
+ previous CVE-2020-1957 path-traversal issue which could have also caused an
+ authentication bypass.
+ * CVE-2020-13933: Fix an authentication bypass resulting from a specially
+ crafted HTTP request. (Closes: #968753)
+ * CVE-2020-17510: Fix an authentication bypass resulting from a specially
+ crafted HTTP request.
+
+ -- Roberto C. Sánchez <roberto at debian.org> Fri, 27 Aug 2021 13:10:19 -0400
+
shiro (1.3.2-4) unstable; urgency=medium
* Team upload.
=====================================
debian/compat deleted
=====================================
@@ -1 +0,0 @@
-11
=====================================
debian/control
=====================================
@@ -4,7 +4,7 @@ Priority: optional
Maintainer: Debian Java Maintainers <pkg-java-maintainers at lists.alioth.debian.org>
Uploaders: Emmanuel Bourg <ebourg at apache.org>
Build-Depends:
- debhelper (>= 11),
+ debhelper-compat (= 13),
default-jdk,
junit4,
libaspectj-java,
@@ -13,17 +13,18 @@ Build-Depends:
libcommons-cli-java,
libehcache-java,
libgeronimo-annotation-1.3-spec-java,
- libguice-java (>= 3.0-6~),
+ libguice-java (>= 4.0),
libhsqldb-java,
+ libjsp-api-java,
liblog4j1.2-java,
libmaven-bundle-plugin-java,
libquartz-java,
- libservlet3.1-java,
+ libservlet-api-java,
libslf4j-java,
libspring-context-java,
libtaglibs-standard-spec-java,
maven-debian-helper
-Standards-Version: 4.3.0
+Standards-Version: 4.7.0
Vcs-Git: https://salsa.debian.org/java-team/shiro.git
Vcs-Browser: https://salsa.debian.org/java-team/shiro
Homepage: http://shiro.apache.org
=====================================
debian/maven.ignoreRules
=====================================
@@ -1,4 +1,5 @@
+com.google.inject.extensions guice-multibindings * * * *
org.apache.shiro shiro-all jar * * *
org.apache.shiro shiro-cas bundle * * *
org.apache.shiro shiro-core test-jar * * *
=====================================
debian/maven.rules
=====================================
@@ -4,8 +4,8 @@ s/aspectj/org.aspectj/ aspectjtools jar s/.*/debian/ * *
s/aspectj/org.aspectj/ aspectjweaver jar s/.*/debian/ * *
commons-beanutils commons-beanutils jar s/.*/debian/ * *
s/javax.servlet/org.apache.taglibs/ s/jstl/taglibs-standard-spec/ jar s/.*/debian/ * *
-javax.servlet s/servlet-api/javax.servlet-api/ * s/.*/3.1/ * *
-javax.servlet.jsp s/jsp-api/javax.servlet.jsp-api/ * s/.*/2.3/ * *
+javax.servlet s/servlet-api/javax.servlet-api/ * s/.*/debian/ * *
+javax.servlet.jsp s/jsp-api/javax.servlet.jsp-api/ * s/.*/debian/ * *
junit junit jar s/4\..*/4.x/ * *
org.apache apache pom s/.*/debian/ * *
s/hsqldb/org.hsqldb/ hsqldb * s/.*/debian/ * *
=====================================
debian/patches/03-spring-compatibility.patch
=====================================
@@ -1,17 +1,33 @@
-Description: Fixes the compatibility with the version of Spring Framework in Debian
-Author: Emmanuel Bourg <ebourg at apache.org>
-Forwarded: no
+From aa2cd4fca10416623ecc29008cd851a4f1ed3d98 Mon Sep 17 00:00:00 2001
+From: Brian Demers <bdemers at apache.org>
+Date: Fri, 23 Sep 2016 16:43:48 -0400
+Subject: [PATCH] SHIRO-590 - Added Spring Boot starters and programatic Spring
+ support.
+
+---
+ .../shiro/spring/LifecycleBeanPostProcessor.java | 11 +++++++++++
+ 1 file changed, 11 insertions(+)
+
+diff --git a/support/spring/src/main/java/org/apache/shiro/spring/LifecycleBeanPostProcessor.java b/support/spring/src/main/java/org/apache/shiro/spring/LifecycleBeanPostProcessor.java
+index a526769e..a318d20b 100644
--- a/support/spring/src/main/java/org/apache/shiro/spring/LifecycleBeanPostProcessor.java
+++ b/support/spring/src/main/java/org/apache/shiro/spring/LifecycleBeanPostProcessor.java
-@@ -125,6 +125,11 @@
- }
+@@ -134,4 +134,15 @@ public int getOrder() {
+ // LifecycleBeanPostProcessor needs Order. See https://issues.apache.org/jira/browse/SHIRO-222
+ return order;
}
-
-+ @Override
-+ public boolean requiresDestruction(Object object) {
-+ return true;
-+ }
+
- /**
- * Order value of this BeanPostProcessor.
- *
++ /**
++ * Return true only if <code>bean</code> implements Destroyable.
++ * @param bean bean to check if requires destruction.
++ * @return true only if <code>bean</code> implements Destroyable.
++ * @since 1.4
++ */
++ @SuppressWarnings("unused")
++ public boolean requiresDestruction(Object bean) {
++ return (bean instanceof Destroyable);
++ }
+ }
+--
+2.20.1
+
=====================================
debian/patches/05-guice-improvements.patch
=====================================
@@ -0,0 +1,751 @@
+commit f2dfa7ff39c9870e7b9856ceca8690c5398080fa
+Author: Brian Demers <bdemers at apache.org>
+Date: Thu Jul 14 09:51:45 2016 -0400
+
+ SHIRO-493 - Adding new methods and deprecating old to ShiroWebModule to support Guice 4
+
+--- a/support/guice/src/main/java/org/apache/shiro/guice/ShiroModule.java
++++ b/support/guice/src/main/java/org/apache/shiro/guice/ShiroModule.java
+@@ -18,20 +18,33 @@
+ */
+ package org.apache.shiro.guice;
+
++import java.lang.reflect.Method;
+ import java.util.Collection;
+ import java.util.Collections;
++import java.util.List;
+ import java.util.Set;
+ import java.util.WeakHashMap;
+
+ import javax.annotation.PreDestroy;
+
++import com.google.inject.Provider;
++import com.google.inject.matcher.Matchers;
++import com.google.inject.name.Names;
++import com.google.inject.spi.InjectionListener;
++import com.google.inject.spi.TypeEncounter;
++import com.google.inject.spi.TypeListener;
+ import org.apache.shiro.config.ConfigurationException;
+ import org.apache.shiro.env.Environment;
++import org.apache.shiro.event.EventBus;
++import org.apache.shiro.event.EventBusAware;
++import org.apache.shiro.event.Subscribe;
++import org.apache.shiro.event.support.DefaultEventBus;
+ import org.apache.shiro.mgt.DefaultSecurityManager;
+ import org.apache.shiro.mgt.SecurityManager;
+ import org.apache.shiro.realm.Realm;
+ import org.apache.shiro.session.mgt.DefaultSessionManager;
+ import org.apache.shiro.session.mgt.SessionManager;
++import org.apache.shiro.util.ClassUtils;
+ import org.apache.shiro.util.Destroyable;
+
+ import com.google.inject.Key;
+@@ -57,6 +70,9 @@
+ bindSessionManager(bind(SessionManager.class));
+ bindEnvironment(bind(Environment.class));
+ bindListener(BeanTypeListener.MATCHER, new BeanTypeListener());
++ bindEventBus(bind(EventBus.class));
++ bindListener(Matchers.any(), new SubscribedEventTypeListener());
++ bindListener(Matchers.any(), new EventBusAwareTypeListener());
+ final DestroyableInjectionListener.DestroyableRegistry registry = new DestroyableInjectionListener.DestroyableRegistry() {
+ public void add(Destroyable destroyable) {
+ ShiroModule.this.add(destroyable);
+@@ -70,6 +86,7 @@
+ bindListener(LifecycleTypeListener.MATCHER, new LifecycleTypeListener(registry));
+
+ expose(SecurityManager.class);
++ expose(EventBus.class);
+
+ configureShiro();
+ bind(realmCollectionKey())
+@@ -153,6 +170,15 @@
+ }
+
+ /**
++ * Binds the EventBus. Override this method in order to provide your own {@link EventBus} binding.
++ * @param bind
++ * @since 1.4
++ */
++ protected void bindEventBus(AnnotatedBindingBuilder<EventBus> bind) {
++ bind.to(DefaultEventBus.class).asEagerSingleton();
++ }
++
++ /**
+ * Destroys all beans created within this module that implement {@link org.apache.shiro.util.Destroyable}. Should be called when this
+ * module will no longer be used.
+ *
+@@ -167,4 +193,39 @@
+ public void add(Destroyable destroyable) {
+ this.destroyables.add(destroyable);
+ }
++
++ private class SubscribedEventTypeListener implements TypeListener {
++ @Override
++ public <I> void hear(TypeLiteral<I> typeLiteral, TypeEncounter<I> typeEncounter) {
++
++ final Provider<EventBus> eventBusProvider = typeEncounter.getProvider(EventBus.class);
++
++ List<Method> methods = ClassUtils.getAnnotatedMethods(typeLiteral.getRawType(), Subscribe.class);
++ if (methods != null && !methods.isEmpty()) {
++ typeEncounter.register( new InjectionListener<I>() {
++ @Override
++ public void afterInjection(Object o) {
++ eventBusProvider.get().register(o);
++ }
++ });
++ }
++ }
++ }
++
++ private class EventBusAwareTypeListener implements TypeListener {
++ @Override
++ public <I> void hear(TypeLiteral<I> typeLiteral, TypeEncounter<I> typeEncounter) {
++
++ final Provider<EventBus> eventBusProvider = typeEncounter.getProvider(EventBus.class);
++
++ if (EventBusAware.class.isAssignableFrom(typeLiteral.getRawType())) {
++ typeEncounter.register( new InjectionListener<I>() {
++ @Override
++ public void afterInjection(Object o) {
++ ((EventBusAware)o).setEventBus(eventBusProvider.get());
++ }
++ });
++ }
++ }
++ }
+ }
+--- a/support/guice/src/main/java/org/apache/shiro/guice/web/ShiroWebModule.java
++++ b/support/guice/src/main/java/org/apache/shiro/guice/web/ShiroWebModule.java
+@@ -18,10 +18,7 @@
+ */
+ package org.apache.shiro.guice.web;
+
+-import java.util.Collection;
+-import java.util.HashMap;
+-import java.util.LinkedHashMap;
+-import java.util.Map;
++import java.util.*;
+
+ import javax.servlet.Filter;
+ import javax.servlet.ServletContext;
+@@ -31,6 +28,7 @@
+ import org.apache.shiro.guice.ShiroModule;
+ import org.apache.shiro.mgt.SecurityManager;
+ import org.apache.shiro.session.mgt.SessionManager;
++import org.apache.shiro.util.StringUtils;
+ import org.apache.shiro.web.env.WebEnvironment;
+ import org.apache.shiro.web.filter.PathMatchingFilter;
+ import org.apache.shiro.web.filter.authc.AnonymousFilter;
+@@ -94,7 +92,7 @@
+ * We use a LinkedHashMap here to ensure that iterator order is the same as add order. This is important, as the
+ * FilterChainResolver uses iterator order when searching for a matching chain.
+ */
+- private final Map<String, Key<? extends Filter>[]> filterChains = new LinkedHashMap<String, Key<? extends Filter>[]>();
++ private final Map<String, FilterConfig<? extends Filter>[]> filterChains = new LinkedHashMap<String, FilterConfig<? extends Filter>[]>();
+ private final ServletContext servletContext;
+
+ public ShiroWebModule(ServletContext servletContext) {
+@@ -134,37 +132,65 @@
+
+ this.configureShiroWeb();
+
+- setupFilterChainConfigs();
+-
+- bind(FilterChainResolver.class).toProvider(new FilterChainResolverProvider(filterChains));
++ bind(FilterChainResolver.class).toProvider(new FilterChainResolverProvider(setupFilterChainConfigs()));
+ }
+
+- private void setupFilterChainConfigs() {
+- Map<Key<? extends PathMatchingFilter>, Map<String, String>> configs = new HashMap<Key<? extends PathMatchingFilter>, Map<String, String>>();
++ private Map<String, Key<? extends Filter>[]> setupFilterChainConfigs() {
++
++ // loop through and build a map of Filter Key -> Map<Path, Config>
++ Map<Key<? extends Filter>, Map<String, String>> filterToPathToConfig = new HashMap<Key<? extends Filter>, Map<String, String>>();
++
++ // At the same time build a map to return with Path -> Key[]
++ Map<String, Key<? extends Filter>[]> resultConfigMap = new HashMap<String, Key<? extends Filter>[]>();
++
++ for (Map.Entry<String, FilterConfig<? extends Filter>[]> filterChain : filterChains.entrySet()) {
++
++ String path = filterChain.getKey();
++
++ // collect the keys used for this path
++ List<Key<? extends Filter>> keysForPath = new ArrayList<Key<? extends Filter>>();
+
+- for (Map.Entry<String, Key<? extends Filter>[]> filterChain : filterChains.entrySet()) {
+ for (int i = 0; i < filterChain.getValue().length; i++) {
+- Key<? extends Filter> key = filterChain.getValue()[i];
+- if (key instanceof FilterConfigKey) {
+- FilterConfigKey<? extends PathMatchingFilter> configKey = (FilterConfigKey<? extends PathMatchingFilter>) key;
+- key = configKey.getKey();
+- filterChain.getValue()[i] = key;
+- if (!PathMatchingFilter.class.isAssignableFrom(key.getTypeLiteral().getRawType())) {
+- throw new ConfigurationException("Config information requires a PathMatchingFilter - can't apply to " + key.getTypeLiteral().getRawType());
+- }
+- if (configs.get(castToPathMatching(key)) == null) configs.put(castToPathMatching(key), new HashMap<String, String>());
+- configs.get(castToPathMatching(key)).put(filterChain.getKey(), configKey.getConfigValue());
+- } else if (PathMatchingFilter.class.isAssignableFrom(key.getTypeLiteral().getRawType())) {
+- if (configs.get(castToPathMatching(key)) == null) configs.put(castToPathMatching(key), new HashMap<String, String>());
+- configs.get(castToPathMatching(key)).put(filterChain.getKey(), "");
++ FilterConfig<? extends Filter> filterConfig = filterChain.getValue()[i];
++
++ Key<? extends Filter> key = filterConfig.getKey();
++ String config = filterConfig.getConfigValue();
++
++ // initialize key in filterToPathToConfig, if it doesn't exist
++ if (filterToPathToConfig.get(key) == null) {
++ filterToPathToConfig.put((key), new HashMap<String, String>());
++ }
++ // now set the value
++ filterToPathToConfig.get(key).put(path, config);
++
++ // Config error if someone configured a non PathMatchingFilter with a config value
++ if (StringUtils.hasText(config) && !PathMatchingFilter.class.isAssignableFrom(key.getTypeLiteral().getRawType())) {
++ throw new ConfigurationException("Config information requires a PathMatchingFilter - can't apply to " + key.getTypeLiteral().getRawType());
+ }
++
++ // store the key in keysForPath
++ keysForPath.add(key);
+ }
++
++ // map the current path to all of its Keys
++ resultConfigMap.put(path, keysForPath.toArray(new Key[keysForPath.size()]));
+ }
+- for (Key<? extends PathMatchingFilter> filterKey : configs.keySet()) {
+- bindPathMatchingFilter(filterKey, configs.get(filterKey));
++
++ // now we find only the PathMatchingFilter and configure bindings
++ // non PathMatchingFilter, can be loaded with the default provider via the class name
++ for (Key<? extends Filter> key : filterToPathToConfig.keySet()) {
++ if (PathMatchingFilter.class.isAssignableFrom(key.getTypeLiteral().getRawType())) {
++ bindPathMatchingFilter(castToPathMatching(key), filterToPathToConfig.get(key));
++ }
++ else {
++ bind(key);
++ }
+ }
++
++ return resultConfigMap;
+ }
+
++
+ private <T extends PathMatchingFilter> void bindPathMatchingFilter(Key<T> filterKey, Map<String, String> configs) {
+ bind(filterKey).toProvider(new PathMatchingFilterProvider<T>(filterKey, configs)).asEagerSingleton();
+ }
+@@ -218,6 +244,126 @@
+ bind.to(WebGuiceEnvironment.class).asEagerSingleton();
+ }
+
++ protected final void addFilterChain(String pattern, Key<? extends Filter> key) {
++ // check for legacy API
++ if (key instanceof FilterConfigKey) {
++ addLegacyFilterChain(pattern, (FilterConfigKey) key);
++ }
++ else {
++ addFilterChain(pattern, new FilterConfig<Filter>((Key<Filter>) key, ""));
++ }
++ }
++
++ /**
++ * Maps 'n' number of <code>filterConfig</code>s to a specific path pattern.<BR/>
++ * For example, a path of '/my_private_resource/**' to 'filterConfig(AUTHC)' would require
++ * any resource under the path '/my_private_resource' would be processed through the {@link FormAuthenticationFilter}.
++ *
++ * @param pattern URL patter to be mapped to a FilterConfig, e.g. '/my_private-path/**'
++ * @param filterConfigs FilterConfiguration representing the Filter and config to be used when processing resources on <code>pattern</code>.
++ * @since 1.4
++ */
++ protected final void addFilterChain(String pattern, FilterConfig<? extends Filter>... filterConfigs) {
++ filterChains.put(pattern, filterConfigs);
++ }
++
++ /**
++ * Builds a FilterConfig from a Filer and configuration String
++ * @param baseKey The Key of the Filter class to be used.
++ * @param <T> A Servlet Filter class.
++ * @return A FilterConfig used to map a String path to this configuration.
++ * @since 1.4
++ */
++ protected static <T extends Filter> FilterConfig<T> filterConfig(Key<T> baseKey, String configValue) {
++ return new FilterConfig<T>(baseKey, configValue);
++ }
++
++ /**
++ * Builds a FilterConfig from a Filer and configuration String
++ * @param baseKey The Key of the Filter class to be used.
++ * @param <T> A Servlet Filter class.
++ * @return A FilterConfig used to map a String path to this configuration.
++ * @since 1.4
++ */
++ protected static <T extends Filter> FilterConfig<T> filterConfig(Key<T> baseKey) {
++ return filterConfig(baseKey, "");
++ }
++
++ /**
++ * Builds a FilterConfig from a Filer and configuration String
++ * @param typeLiteral The TyleLiteral of the filter key to be used.
++ * @param configValue the configuration used.
++ * @param <T> A Servlet Filter class.
++ * @return A FilterConfig used to map a String path to this configuration.
++ * @since 1.4
++ */
++ @SuppressWarnings({"UnusedDeclaration"})
++ protected static <T extends Filter> FilterConfig<T> filterConfig(TypeLiteral<T> typeLiteral, String configValue) {
++ return filterConfig(Key.get(typeLiteral), configValue);
++ }
++
++ /**
++ * Builds a FilterConfig from a Filer and configuration String
++ * @param type The filter to be used.
++ * @param configValue the configuration used.
++ * @param <T> A Servlet Filter class.
++ * @return A FilterConfig used to map a String path to this configuration.
++ * @since 1.4
++ */
++ @SuppressWarnings({"UnusedDeclaration"})
++ protected static <T extends Filter> FilterConfig<T> filterConfig(Class<T> type, String configValue) {
++ return filterConfig(Key.get(type), configValue);
++ }
++
++
++ /**
++ * Filter configuration which pairs a Filter class with its configuration used on a path.
++ * @param <T> The Servlet Filter class.
++ * @since 1.4
++ */
++ public static class FilterConfig<T extends Filter> {
++ private Key<T> key;
++ private String configValue;
++
++ private FilterConfig(Key<T> key, String configValue) {
++ super();
++ this.key = key;
++ this.configValue = configValue;
++ }
++
++ public Key<T> getKey() {
++ return key;
++ }
++
++ public String getConfigValue() {
++ return configValue;
++ }
++ }
++
++
++
++
++
++
++
++ // legacy methods
++
++
++ static boolean isGuiceVersion3() {
++ try {
++ Class.forName("com.google.inject.multibindings.MapKey");
++ return false;
++ } catch (ClassNotFoundException e) {
++ return true;
++ }
++ }
++
++ private void addLegacyFilterChain(String pattern, FilterConfigKey filterConfigKey) {
++
++ FilterConfig<Filter> filterConfig = new FilterConfig<Filter>(filterConfigKey.getKey(), filterConfigKey.getConfigValue());
++ addFilterChain(pattern, filterConfig);
++ }
++
+ /**
+ * Adds a filter chain to the shiro configuration.
+ * <p/>
+@@ -228,24 +374,52 @@
+ * @param keys
+ */
+ @SuppressWarnings({"UnusedDeclaration"})
++ @Deprecated
+ protected final void addFilterChain(String pattern, Key<? extends Filter>... keys) {
+- filterChains.put(pattern, keys);
++
++ // We need to extract the keys and FilterConfigKey and convert to the new format.
++
++ FilterConfig[] filterConfigs = new FilterConfig[keys.length];
++ for (int ii = 0; ii < keys.length; ii++) {
++ Key<? extends Filter> key = keys[ii];
++ // If this is a path matching filter, we need to remember the config
++ if (key instanceof FilterConfigKey) {
++ // legacy config
++ FilterConfigKey legacyKey = (FilterConfigKey) key;
++ filterConfigs[ii] = new FilterConfig(legacyKey.getKey(), legacyKey.getConfigValue());
++ }
++ else {
++ // Some other type of Filter key, no config
++ filterConfigs[ii] = new FilterConfig(key, "");
++ }
++ }
++
++ filterChains.put(pattern, filterConfigs);
+ }
+
++ @Deprecated
+ protected static <T extends PathMatchingFilter> Key<T> config(Key<T> baseKey, String configValue) {
++
++ if( !isGuiceVersion3()) {
++ throw new ConfigurationException("Method ShiroWebModule.config(Key<? extends PathMatchingFilter>, String configValue), is not supported when using Guice 4+");
++ }
++
+ return new FilterConfigKey<T>(baseKey, configValue);
+ }
+
+ @SuppressWarnings({"UnusedDeclaration"})
++ @Deprecated
+ protected static <T extends PathMatchingFilter> Key<T> config(TypeLiteral<T> typeLiteral, String configValue) {
+ return config(Key.get(typeLiteral), configValue);
+ }
+
+ @SuppressWarnings({"UnusedDeclaration"})
++ @Deprecated
+ protected static <T extends PathMatchingFilter> Key<T> config(Class<T> type, String configValue) {
+ return config(Key.get(type), configValue);
+ }
+
++ @Deprecated
+ private static class FilterConfigKey<T extends PathMatchingFilter> extends Key<T> {
+ private Key<T> key;
+ private String configValue;
+@@ -264,4 +438,5 @@
+ return configValue;
+ }
+ }
++
+ }
+--- a/support/guice/src/main/java/org/apache/shiro/guice/web/SimpleFilterChain.java
++++ b/support/guice/src/main/java/org/apache/shiro/guice/web/SimpleFilterChain.java
+@@ -44,4 +44,13 @@
+ originalChain.doFilter(request, response);
+ }
+ }
++
++ /**
++ * Exposed for testing, not part of public API.
++ * @return an Iterater of filters.
++ */
++ Iterator<? extends Filter> getFilters() {
++ return chain;
++ }
++
+ }
+--- a/support/guice/src/test/java/org/apache/shiro/guice/ShiroModuleTest.java
++++ b/support/guice/src/test/java/org/apache/shiro/guice/ShiroModuleTest.java
+@@ -28,6 +28,10 @@
+ import org.apache.shiro.authc.AuthenticationToken;
+ import org.apache.shiro.authc.SimpleAuthenticationInfo;
+ import org.apache.shiro.env.Environment;
++import org.apache.shiro.event.EventBus;
++import org.apache.shiro.event.EventBusAware;
++import org.apache.shiro.event.Subscribe;
++import org.apache.shiro.event.support.DefaultEventBus;
+ import org.apache.shiro.mgt.DefaultSecurityManager;
+ import org.apache.shiro.mgt.SecurityManager;
+ import org.apache.shiro.realm.Realm;
+@@ -37,11 +41,13 @@
+ import org.apache.shiro.util.Destroyable;
+ import org.junit.Test;
+
++import java.lang.reflect.Field;
+ import java.util.Collection;
++import java.util.Map;
+
+ import static org.easymock.EasyMock.*;
+-import static org.junit.Assert.assertNotNull;
+-import static org.junit.Assert.assertTrue;
++import static org.junit.Assert.*;
++import static org.hamcrest.CoreMatchers.*;
+
+ public class ShiroModuleTest {
+
+@@ -204,6 +210,82 @@
+ verify(myDestroyable);
+ }
+
++ /**
++ * @since 1.4
++ * @throws Exception
++ */
++ @Test
++ public void testEventListener() throws Exception {
++
++ final MockRealm mockRealm = createMock(MockRealm.class);
++ final EventBus eventBus = createMock(EventBus.class);
++
++ // expect both objects to be registered
++ eventBus.register(anyObject(MockEventListener1.class));
++ eventBus.register(anyObject(MockEventListener2.class));
++ replay(eventBus);
++
++ final ShiroModule shiroModule = new ShiroModule() {
++ @Override
++ protected void configureShiro() {
++ bindRealm().to(MockRealm.class);
++
++ // bind our event listeners
++ binder().bind(MockEventListener1.class).asEagerSingleton();
++ binder().bind(MockEventListener2.class).asEagerSingleton();
++ }
++
++ @Override
++ protected void bindEventBus(AnnotatedBindingBuilder<EventBus> bind) {
++ bind.toInstance(eventBus);
++ }
++
++ @Provides
++ public MockRealm createRealm() {
++ return mockRealm;
++ }
++
++ };
++ Guice.createInjector(shiroModule);
++
++ verify(eventBus);
++
++ }
++
++ /**
++ * @since 1.4
++ * @throws Exception
++ */
++ @Test
++ public void testEventBusAware() throws Exception {
++
++ final MockRealm mockRealm = createMock(MockRealm.class);
++
++ final ShiroModule shiroModule = new ShiroModule() {
++ @Override
++ protected void configureShiro() {
++ bindRealm().to(MockRealm.class);
++
++ binder().bind(MockEventBusAware.class).asEagerSingleton();
++ expose(MockEventBusAware.class);
++ }
++
++ @Provides
++ public MockRealm createRealm() {
++ return mockRealm;
++ }
++
++ };
++ Injector injector = Guice.createInjector(shiroModule);
++ EventBus eventBus = injector.getInstance(EventBus.class);
++ SecurityManager securityManager = injector.getInstance(SecurityManager.class);
++
++ MockEventBusAware eventBusAware = injector.getInstance(MockEventBusAware.class);
++
++ assertSame(eventBus, eventBusAware.eventBus);
++ assertSame(eventBus, ((DefaultSecurityManager)securityManager).getEventBus());
++ }
++
+ public static interface MockRealm extends Realm {
+
+ }
+@@ -227,4 +309,27 @@
+
+ public static interface MyDestroyable extends Destroyable {
+ }
++
++ public static class MockEventListener1 {
++ @Subscribe
++ public void listenToAllAndDoNothing(Object o) {}
++ }
++
++ public static class MockEventListener2 {
++ @Subscribe
++ public void listenToAllAndDoNothing(Object o) {}
++ }
++
++ public static class MockEventBusAware implements EventBusAware {
++ private EventBus eventBus;
++
++ public EventBus getEventBus() {
++ return eventBus;
++ }
++
++ @Override
++ public void setEventBus(EventBus eventBus) {
++ this.eventBus = eventBus;
++ }
++ }
+ }
+--- a/support/guice/src/test/java/org/apache/shiro/guice/web/FilterConfigTest.java
++++ b/support/guice/src/test/java/org/apache/shiro/guice/web/FilterConfigTest.java
+@@ -45,7 +45,8 @@
+ bindRealm().to(ShiroModuleTest.MockRealm.class);
+
+ addFilterChain("/index.html", AUTHC_BASIC);
+- addFilterChain("/index2.html", config(PERMS, "permission"));
++// addFilterChain("/index2.html", config(PERMS, "permission"));
++ addFilterChain("/index2.html", filterConfig(PERMS, "permission"));
+ }
+
+ @Provides
+--- a/support/guice/src/test/java/org/apache/shiro/guice/web/ShiroWebModuleTest.java
++++ b/support/guice/src/test/java/org/apache/shiro/guice/web/ShiroWebModuleTest.java
+@@ -21,6 +21,7 @@
+ import com.google.inject.Guice;
+ import com.google.inject.Inject;
+ import com.google.inject.Injector;
++import com.google.inject.Key;
+ import com.google.inject.Provides;
+ import com.google.inject.binder.AnnotatedBindingBuilder;
+ import org.apache.shiro.guice.ShiroModuleTest;
+@@ -28,21 +29,38 @@
+ import org.apache.shiro.mgt.SecurityManager;
+ import org.apache.shiro.realm.Realm;
+ import org.apache.shiro.session.mgt.SessionManager;
++import org.apache.shiro.web.env.EnvironmentLoader;
+ import org.apache.shiro.web.env.WebEnvironment;
++import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
++import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
++import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
++import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter;
+ import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+ import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
+ import org.apache.shiro.web.mgt.WebSecurityManager;
+ import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
+ import org.apache.shiro.web.session.mgt.ServletContainerSessionManager;
++import org.easymock.EasyMock;
++import org.junit.Assume;
+ import org.junit.Test;
+
+ import javax.inject.Named;
++import javax.servlet.Filter;
++import javax.servlet.FilterChain;
++import javax.servlet.FilterConfig;
+ import javax.servlet.ServletContext;
++import javax.servlet.ServletException;
++import javax.servlet.ServletRequest;
++import javax.servlet.ServletResponse;
++import javax.servlet.http.HttpServletRequest;
++import java.io.IOException;
+ import java.util.Collection;
++import java.util.Iterator;
++
++import static org.easymock.EasyMock.*;
++import static org.junit.Assert.*;
++import static org.hamcrest.CoreMatchers.*;
+
+-import static org.easymock.EasyMock.createMock;
+-import static org.junit.Assert.assertNotNull;
+-import static org.junit.Assert.assertTrue;
+
+ public class ShiroWebModuleTest {
+
+@@ -146,6 +164,92 @@
+ assertTrue( environment == webEnvironment );
+ }
+
++ /**
++ * @since 1.4
++ */
++ @Test
++ public void testAddFilterChainGuice3and4() {
++
++ final ShiroModuleTest.MockRealm mockRealm = createMock(ShiroModuleTest.MockRealm.class);
++ ServletContext servletContext = createMock(ServletContext.class);
++ HttpServletRequest request = createMock(HttpServletRequest.class);
++
++ servletContext.setAttribute(eq(EnvironmentLoader.ENVIRONMENT_ATTRIBUTE_KEY), EasyMock.anyObject());
++ expect(request.getAttribute("javax.servlet.include.context_path")).andReturn("").anyTimes();
++ expect(request.getCharacterEncoding()).andReturn("UTF-8").anyTimes();
++ expect(request.getAttribute("javax.servlet.include.request_uri")).andReturn("/test_authc");
++ expect(request.getAttribute("javax.servlet.include.request_uri")).andReturn("/test_custom_filter");
++ expect(request.getAttribute("javax.servlet.include.request_uri")).andReturn("/test_authc_basic");
++ expect(request.getAttribute("javax.servlet.include.request_uri")).andReturn("/test_perms");
++ expect(request.getAttribute("javax.servlet.include.request_uri")).andReturn("/multiple_configs");
++ replay(servletContext, request);
++
++ Injector injector = Guice.createInjector(new ShiroWebModule(servletContext) {
++ @Override
++ protected void configureShiroWeb() {
++ bindRealm().to(ShiroModuleTest.MockRealm.class);
++ expose(FilterChainResolver.class);
++ this.addFilterChain("/test_authc/**", filterConfig(AUTHC));
++ this.addFilterChain("/test_custom_filter/**", Key.get(CustomFilter.class));
++ this.addFilterChain("/test_authc_basic/**", AUTHC_BASIC);
++ this.addFilterChain("/test_perms/**", filterConfig(PERMS, "remote:invoke:lan,wan"));
++ this.addFilterChain("/multiple_configs/**", filterConfig(AUTHC), filterConfig(ROLES, "b2bClient"), filterConfig(PERMS, "remote:invoke:lan,wan"));
++ }
++
++ @Provides
++ public ShiroModuleTest.MockRealm createRealm() {
++ return mockRealm;
++ }
++ });
++
++ FilterChainResolver resolver = injector.getInstance(FilterChainResolver.class);
++ assertThat(resolver, instanceOf(SimpleFilterChainResolver.class));
++ SimpleFilterChainResolver simpleFilterChainResolver = (SimpleFilterChainResolver) resolver;
++
++ // test the /test_authc resource
++ FilterChain filterChain = simpleFilterChainResolver.getChain(request, null, null);
++ assertThat(filterChain, instanceOf(SimpleFilterChain.class));
++ Filter nextFilter = getNextFilter((SimpleFilterChain) filterChain);
++ assertThat(nextFilter, instanceOf(FormAuthenticationFilter.class));
++
++ // test the /test_custom_filter resource
++ filterChain = simpleFilterChainResolver.getChain(request, null, null);
++ assertThat(filterChain, instanceOf(SimpleFilterChain.class));
++ nextFilter = getNextFilter((SimpleFilterChain) filterChain);
++ assertThat(nextFilter, instanceOf(CustomFilter.class));
++
++ // test the /test_authc_basic resource
++ filterChain = simpleFilterChainResolver.getChain(request, null, null);
++ assertThat(filterChain, instanceOf(SimpleFilterChain.class));
++ nextFilter = getNextFilter((SimpleFilterChain) filterChain);
++ assertThat(nextFilter, instanceOf(BasicHttpAuthenticationFilter.class));
++
++ // test the /test_perms resource
++ filterChain = simpleFilterChainResolver.getChain(request, null, null);
++ assertThat(filterChain, instanceOf(SimpleFilterChain.class));
++ nextFilter = getNextFilter((SimpleFilterChain) filterChain);
++ assertThat(nextFilter, instanceOf(PermissionsAuthorizationFilter.class));
++
++ // test the /multiple_configs resource
++ filterChain = simpleFilterChainResolver.getChain(request, null, null);
++ assertThat(filterChain, instanceOf(SimpleFilterChain.class));
++ assertThat(getNextFilter((SimpleFilterChain) filterChain), instanceOf(FormAuthenticationFilter.class));
++ assertThat(getNextFilter((SimpleFilterChain) filterChain), instanceOf(RolesAuthorizationFilter.class));
++ assertThat(getNextFilter((SimpleFilterChain) filterChain), instanceOf(PermissionsAuthorizationFilter.class));
++
++ verify(servletContext, request);
++ }
++
++ private Filter getNextFilter(SimpleFilterChain filterChain) {
++
++ Iterator<? extends Filter> filters = filterChain.getFilters();
++ if (filters.hasNext()) {
++ return filters.next();
++ }
++
++ return null;
++ }
++
+ public static class MyDefaultWebSecurityManager extends DefaultWebSecurityManager {
+ @Inject
+ public MyDefaultWebSecurityManager(Collection<Realm> realms) {
+@@ -162,4 +266,16 @@
+ super(filterChainResolver, servletContext, securityManager);
+ }
+ }
++
++ public static class CustomFilter implements Filter {
++
++ @Override
++ public void init(FilterConfig filterConfig) throws ServletException {}
++
++ @Override
++ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {}
++
++ @Override
++ public void destroy() {}
++ }
+ }
=====================================
debian/patches/CVE-2020-13933.patch
=====================================
@@ -0,0 +1,1009 @@
+From dc194fc977ab6cfbf3c1ecb085e2bac5db14af6d Mon Sep 17 00:00:00 2001
+From: Brian Demers <bdemers at apache.org>
+Date: Tue, 7 Jul 2020 21:06:35 -0400
+Subject: [PATCH] Add a feature to allow for global filters
+
+Adds new filter to block invalid requests
+---
+ .../shiro/guice/web/ShiroWebModule.java | 25 ++-
+ .../shiro/guice/web/ShiroWebModuleTest.java | 153 ++++++++++++++++++
+ .../ShiroWebFilterConfiguration.java | 8 +
+ .../web/ConfiguredGlobalFiltersTest.groovy | 104 ++++++++++++
+ .../web/DisabledGlobalFiltersTest.groovy | 64 ++++++++
+ ...ShiroWebSpringAutoConfigurationTest.groovy | 30 +++-
+ ...roWebAutoConfigurationTestApplication.java | 4 +-
+ .../spring/web/ShiroFilterFactoryBean.java | 23 +++
+ .../config/AbstractShiroWebConfiguration.java | 3 -
+ .../AbstractShiroWebFilterConfiguration.java | 9 +-
+ .../config/ShiroWebFilterConfiguration.java | 6 +
+ .../ShiroWebFilterConfigurationTest.groovy | 3 +-
+ .../web/ShiroFilterFactoryBeanTest.java | 8 +-
+ .../config/IniFilterChainResolverFactory.java | 18 +++
+ .../web/filter/InvalidRequestFilter.java | 124 ++++++++++++++
+ .../shiro/web/filter/mgt/DefaultFilter.java | 4 +-
+ .../filter/mgt/DefaultFilterChainManager.java | 37 ++++-
+ .../web/filter/mgt/FilterChainManager.java | 22 +++
+ .../web/servlet/AbstractShiroFilter.java | 1 +
+ .../IniFilterChainResolverFactoryTest.groovy | 26 +++
+ .../web/env/IniWebEnvironmentTest.groovy | 69 ++++++++
+ .../filter/InvalidRequestFilterTest.groovy | 106 ++++++++++++
+ .../mgt/DefaultFilterChainManagerTest.groovy | 52 ++++++
+ .../org/apache/shiro/web/env/FilterStub.java | 45 ++++++
+ 24 files changed, 925 insertions(+), 19 deletions(-)
+ create mode 100644 support/spring-boot/spring-boot-web-starter/src/test/groovy/org/apache/shiro/spring/boot/autoconfigure/web/ConfiguredGlobalFiltersTest.groovy
+ create mode 100644 support/spring-boot/spring-boot-web-starter/src/test/groovy/org/apache/shiro/spring/boot/autoconfigure/web/DisabledGlobalFiltersTest.groovy
+ create mode 100644 web/src/main/java/org/apache/shiro/web/filter/InvalidRequestFilter.java
+ create mode 100644 web/src/test/groovy/org/apache/shiro/web/filter/InvalidRequestFilterTest.groovy
+ create mode 100644 web/src/test/java/org/apache/shiro/web/env/FilterStub.java
+
+--- a/support/guice/src/main/java/org/apache/shiro/guice/web/ShiroWebModule.java
++++ b/support/guice/src/main/java/org/apache/shiro/guice/web/ShiroWebModule.java
+@@ -30,6 +30,7 @@
+ import org.apache.shiro.session.mgt.SessionManager;
+ import org.apache.shiro.util.StringUtils;
+ import org.apache.shiro.web.env.WebEnvironment;
++import org.apache.shiro.web.filter.InvalidRequestFilter;
+ import org.apache.shiro.web.filter.PathMatchingFilter;
+ import org.apache.shiro.web.filter.authc.AnonymousFilter;
+ import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
+@@ -84,7 +85,8 @@
+ public static final Key<SslFilter> SSL = Key.get(SslFilter.class);
+ @SuppressWarnings({"UnusedDeclaration"})
+ public static final Key<UserFilter> USER = Key.get(UserFilter.class);
+-
++ @SuppressWarnings({"UnusedDeclaration"})
++ public static final Key<InvalidRequestFilter> INVALID_REQUEST = Key.get(InvalidRequestFilter.class);
+
+ static final String NAME = "SHIRO";
+
+@@ -121,6 +123,12 @@
+ };
+ }
+
++ public List<FilterConfig<? extends Filter>> globalFilters() {
++ ArrayList<FilterConfig<? extends Filter>> filters = new ArrayList<FilterConfig<? extends Filter>>();
++ filters.add(filterConfig(INVALID_REQUEST));
++ return Collections.unmodifiableList(filters);
++ }
++
+ @Override
+ protected final void configureShiro() {
+ bindBeanType(TypeLiteral.get(ServletContext.class), Key.get(ServletContext.class, Names.named(NAME)));
+@@ -132,6 +140,12 @@
+
+ this.configureShiroWeb();
+
++ // add default matching route if not already set
++ if (!filterChains.containsKey("/**")) {
++ // no config, this will add only the global filters
++ this.addFilterChain("/**", new FilterConfig[0]);
++ }
++
+ bind(FilterChainResolver.class).toProvider(new FilterChainResolverProvider(setupFilterChainConfigs()));
+ }
+
+@@ -150,8 +164,15 @@
+ // collect the keys used for this path
+ List<Key<? extends Filter>> keysForPath = new ArrayList<Key<? extends Filter>>();
+
+- for (int i = 0; i < filterChain.getValue().length; i++) {
+- FilterConfig<? extends Filter> filterConfig = filterChain.getValue()[i];
++ List<FilterConfig<? extends Filter>> globalFilters = this.globalFilters();
++ FilterConfig<? extends Filter>[] pathFilters = filterChain.getValue();
++
++ // merge the global filters and the path specific filters
++ List<FilterConfig<? extends Filter>> filterConfigs = new ArrayList<FilterConfig<? extends Filter>>(globalFilters.size() + pathFilters.length);
++ filterConfigs.addAll(globalFilters);
++ filterConfigs.addAll(Arrays.asList(pathFilters));
++
++ for (FilterConfig<? extends Filter> filterConfig : filterConfigs) {
+
+ Key<? extends Filter> key = filterConfig.getKey();
+ String config = filterConfig.getConfigValue();
+--- a/support/guice/src/test/java/org/apache/shiro/guice/web/ShiroWebModuleTest.java
++++ b/support/guice/src/test/java/org/apache/shiro/guice/web/ShiroWebModuleTest.java
+@@ -24,6 +24,7 @@
+ import com.google.inject.Key;
+ import com.google.inject.Provides;
+ import com.google.inject.binder.AnnotatedBindingBuilder;
++import com.google.inject.name.Names;
+ import org.apache.shiro.guice.ShiroModuleTest;
+ import org.apache.shiro.env.Environment;
+ import org.apache.shiro.mgt.SecurityManager;
+@@ -31,6 +32,7 @@
+ import org.apache.shiro.session.mgt.SessionManager;
+ import org.apache.shiro.web.env.EnvironmentLoader;
+ import org.apache.shiro.web.env.WebEnvironment;
++import org.apache.shiro.web.filter.InvalidRequestFilter;
+ import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
+ import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
+ import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
+@@ -55,7 +57,9 @@
+ import javax.servlet.http.HttpServletRequest;
+ import java.io.IOException;
+ import java.util.Collection;
++import java.util.Collections;
+ import java.util.Iterator;
++import java.util.List;
+
+ import static org.easymock.EasyMock.*;
+ import static org.junit.Assert.*;
+@@ -212,35 +216,184 @@
+ FilterChain filterChain = simpleFilterChainResolver.getChain(request, null, null);
+ assertThat(filterChain, instanceOf(SimpleFilterChain.class));
+ Filter nextFilter = getNextFilter((SimpleFilterChain) filterChain);
++ assertThat(nextFilter, instanceOf(InvalidRequestFilter.class));
++ nextFilter = getNextFilter((SimpleFilterChain) filterChain);
+ assertThat(nextFilter, instanceOf(FormAuthenticationFilter.class));
+
+ // test the /test_custom_filter resource
+ filterChain = simpleFilterChainResolver.getChain(request, null, null);
+ assertThat(filterChain, instanceOf(SimpleFilterChain.class));
+ nextFilter = getNextFilter((SimpleFilterChain) filterChain);
++ assertThat(nextFilter, instanceOf(InvalidRequestFilter.class));
++ nextFilter = getNextFilter((SimpleFilterChain) filterChain);
+ assertThat(nextFilter, instanceOf(CustomFilter.class));
+
+ // test the /test_authc_basic resource
+ filterChain = simpleFilterChainResolver.getChain(request, null, null);
+ assertThat(filterChain, instanceOf(SimpleFilterChain.class));
+ nextFilter = getNextFilter((SimpleFilterChain) filterChain);
++ assertThat(nextFilter, instanceOf(InvalidRequestFilter.class));
++ nextFilter = getNextFilter((SimpleFilterChain) filterChain);
+ assertThat(nextFilter, instanceOf(BasicHttpAuthenticationFilter.class));
+
+ // test the /test_perms resource
+ filterChain = simpleFilterChainResolver.getChain(request, null, null);
+ assertThat(filterChain, instanceOf(SimpleFilterChain.class));
+ nextFilter = getNextFilter((SimpleFilterChain) filterChain);
++ assertThat(nextFilter, instanceOf(InvalidRequestFilter.class));
++ nextFilter = getNextFilter((SimpleFilterChain) filterChain);
+ assertThat(nextFilter, instanceOf(PermissionsAuthorizationFilter.class));
+
+ // test the /multiple_configs resource
+ filterChain = simpleFilterChainResolver.getChain(request, null, null);
+ assertThat(filterChain, instanceOf(SimpleFilterChain.class));
++ assertThat(getNextFilter((SimpleFilterChain) filterChain), instanceOf(InvalidRequestFilter.class));
+ assertThat(getNextFilter((SimpleFilterChain) filterChain), instanceOf(FormAuthenticationFilter.class));
+ assertThat(getNextFilter((SimpleFilterChain) filterChain), instanceOf(RolesAuthorizationFilter.class));
+ assertThat(getNextFilter((SimpleFilterChain) filterChain), instanceOf(PermissionsAuthorizationFilter.class));
+
+ verify(servletContext, request);
+ }
++
++ @Test
++ public void testDefaultPath() {
++
++ final ShiroModuleTest.MockRealm mockRealm = createMock(ShiroModuleTest.MockRealm.class);
++ ServletContext servletContext = createMock(ServletContext.class);
++ HttpServletRequest request = createMock(HttpServletRequest.class);
++
++ servletContext.setAttribute(eq(EnvironmentLoader.ENVIRONMENT_ATTRIBUTE_KEY), EasyMock.anyObject());
++ expect(request.getAttribute("javax.servlet.include.context_path")).andReturn("").anyTimes();
++ expect(request.getCharacterEncoding()).andReturn("UTF-8").anyTimes();
++ expect(request.getAttribute("javax.servlet.include.path_info")).andReturn(null).anyTimes();
++ expect(request.getPathInfo()).andReturn(null).anyTimes();
++ expect(request.getAttribute("javax.servlet.include.servlet_path")).andReturn("/test/foobar");
++ replay(servletContext, request);
++
++ Injector injector = Guice.createInjector(new ShiroWebModule(servletContext) {
++ @Override
++ protected void configureShiroWeb() {
++ bindRealm().to(ShiroModuleTest.MockRealm.class);
++ expose(FilterChainResolver.class);
++ // no paths configured
++ }
++
++ @Provides
++ public ShiroModuleTest.MockRealm createRealm() {
++ return mockRealm;
++ }
++ });
++
++ FilterChainResolver resolver = injector.getInstance(FilterChainResolver.class);
++ assertThat(resolver, instanceOf(SimpleFilterChainResolver.class));
++ SimpleFilterChainResolver simpleFilterChainResolver = (SimpleFilterChainResolver) resolver;
++
++ // test the /test_authc resource
++ FilterChain filterChain = simpleFilterChainResolver.getChain(request, null, null);
++ assertThat(filterChain, instanceOf(SimpleFilterChain.class));
++
++ assertThat(getNextFilter((SimpleFilterChain) filterChain), instanceOf(InvalidRequestFilter.class));
++ assertThat(getNextFilter((SimpleFilterChain) filterChain), nullValue());
++
++ verify(servletContext, request);
++ }
++
++ @Test
++ public void testDisableGlobalFilters() {
++
++ final ShiroModuleTest.MockRealm mockRealm = createMock(ShiroModuleTest.MockRealm.class);
++ ServletContext servletContext = createMock(ServletContext.class);
++ HttpServletRequest request = createMock(HttpServletRequest.class);
++
++ servletContext.setAttribute(eq(EnvironmentLoader.ENVIRONMENT_ATTRIBUTE_KEY), EasyMock.anyObject());
++ expect(request.getAttribute("javax.servlet.include.context_path")).andReturn("").anyTimes();
++ expect(request.getCharacterEncoding()).andReturn("UTF-8").anyTimes();
++ expect(request.getAttribute("javax.servlet.include.path_info")).andReturn(null).anyTimes();
++ expect(request.getPathInfo()).andReturn(null).anyTimes();
++ expect(request.getAttribute("javax.servlet.include.servlet_path")).andReturn("/test/foobar");
++ replay(servletContext, request);
++
++ Injector injector = Guice.createInjector(new ShiroWebModule(servletContext) {
++ @Override
++ protected void configureShiroWeb() {
++ bindRealm().to(ShiroModuleTest.MockRealm.class);
++ expose(FilterChainResolver.class);
++ this.addFilterChain("/**", filterConfig(AUTHC));
++ }
++
++ @Override
++ public List<FilterConfig<? extends Filter>> globalFilters() {
++ return Collections.emptyList();
++ }
++
++ @Provides
++ public ShiroModuleTest.MockRealm createRealm() {
++ return mockRealm;
++ }
++ });
++
++ FilterChainResolver resolver = injector.getInstance(FilterChainResolver.class);
++ assertThat(resolver, instanceOf(SimpleFilterChainResolver.class));
++ SimpleFilterChainResolver simpleFilterChainResolver = (SimpleFilterChainResolver) resolver;
++
++ // test the /test_authc resource
++ FilterChain filterChain = simpleFilterChainResolver.getChain(request, null, null);
++ assertThat(filterChain, instanceOf(SimpleFilterChain.class));
++
++ assertThat(getNextFilter((SimpleFilterChain) filterChain), instanceOf(FormAuthenticationFilter.class));
++ assertThat(getNextFilter((SimpleFilterChain) filterChain), nullValue());
++
++ verify(servletContext, request);
++ }
++
++ @Test
++ public void testChangeInvalidFilterConfig() {
++
++ final ShiroModuleTest.MockRealm mockRealm = createMock(ShiroModuleTest.MockRealm.class);
++ ServletContext servletContext = createMock(ServletContext.class);
++ HttpServletRequest request = createMock(HttpServletRequest.class);
++
++ servletContext.setAttribute(eq(EnvironmentLoader.ENVIRONMENT_ATTRIBUTE_KEY), EasyMock.anyObject());
++ expect(request.getAttribute("javax.servlet.include.context_path")).andReturn("").anyTimes();
++ expect(request.getCharacterEncoding()).andReturn("UTF-8").anyTimes();
++ expect(request.getAttribute("javax.servlet.include.path_info")).andReturn(null).anyTimes();
++ expect(request.getPathInfo()).andReturn(null).anyTimes();
++ expect(request.getAttribute("javax.servlet.include.servlet_path")).andReturn("/test/foobar");
++ replay(servletContext, request);
++
++ Injector injector = Guice.createInjector(new ShiroWebModule(servletContext) {
++ @Override
++ protected void configureShiroWeb() {
++
++ bindConstant().annotatedWith(Names.named("shiro.blockBackslash")).to(false);
++
++ bindRealm().to(ShiroModuleTest.MockRealm.class);
++ expose(FilterChainResolver.class);
++ this.addFilterChain("/**", filterConfig(AUTHC));
++ }
++
++ @Provides
++ public ShiroModuleTest.MockRealm createRealm() {
++ return mockRealm;
++ }
++ });
++
++ FilterChainResolver resolver = injector.getInstance(FilterChainResolver.class);
++ assertThat(resolver, instanceOf(SimpleFilterChainResolver.class));
++ SimpleFilterChainResolver simpleFilterChainResolver = (SimpleFilterChainResolver) resolver;
++
++ // test the /test_authc resource
++ FilterChain filterChain = simpleFilterChainResolver.getChain(request, null, null);
++ assertThat(filterChain, instanceOf(SimpleFilterChain.class));
++
++ Filter invalidRequestFilter = getNextFilter((SimpleFilterChain) filterChain);
++ assertThat(invalidRequestFilter, instanceOf(InvalidRequestFilter.class));
++ assertFalse("Expected 'blockBackslash' to be false", ((InvalidRequestFilter) invalidRequestFilter).isBlockBackslash());
++ assertThat(getNextFilter((SimpleFilterChain) filterChain), instanceOf(FormAuthenticationFilter.class));
++ assertThat(getNextFilter((SimpleFilterChain) filterChain), nullValue());
++
++ verify(servletContext, request);
++ }
+
+ private Filter getNextFilter(SimpleFilterChain filterChain) {
+
+--- /dev/null
++++ b/support/spring-boot/spring-boot-web-starter/src/test/groovy/org/apache/shiro/spring/boot/autoconfigure/web/ConfiguredGlobalFiltersTest.groovy
+@@ -0,0 +1,104 @@
++/*
++ * 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.shiro.spring.boot.autoconfigure.web
++
++import org.apache.shiro.spring.boot.autoconfigure.web.application.ShiroWebAutoConfigurationTestApplication
++import org.apache.shiro.spring.web.ShiroFilterFactoryBean
++import org.apache.shiro.spring.web.config.AbstractShiroWebFilterConfiguration
++import org.apache.shiro.web.filter.InvalidRequestFilter
++import org.apache.shiro.web.filter.authz.PortFilter
++import org.apache.shiro.web.filter.mgt.DefaultFilter
++import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager
++import org.apache.shiro.web.filter.mgt.NamedFilterList
++import org.apache.shiro.web.servlet.AbstractShiroFilter
++import org.junit.Test
++import org.junit.runner.RunWith
++import org.springframework.beans.factory.annotation.Autowired
++import org.springframework.boot.test.context.SpringBootTest
++import org.springframework.context.annotation.Bean
++import org.springframework.context.annotation.Configuration
++import org.springframework.test.context.junit4.SpringRunner
++
++import static org.hamcrest.MatcherAssert.assertThat
++import static org.hamcrest.Matchers.*
++
++ at RunWith(SpringRunner.class)
++ at SpringBootTest(classes = [ShiroWebAutoConfigurationTestApplication, Config])
++
++class ConfiguredGlobalFiltersTest {
++
++ @Configuration
++ static class Config extends AbstractShiroWebFilterConfiguration {
++
++ @Bean
++ List<String> globalFilters() {
++ return [DefaultFilter.invalidRequest.name(), DefaultFilter.port.name()]
++ }
++
++ @Bean
++ @Override
++ ShiroFilterFactoryBean shiroFilterFactoryBean() {
++ ShiroFilterFactoryBean bean = super.shiroFilterFactoryBean()
++ InvalidRequestFilter invalidRequestFilter = new InvalidRequestFilter()
++ invalidRequestFilter.setBlockBackslash(false)
++ PortFilter portFilter = new PortFilter()
++ portFilter.setPort(9999)
++ bean.getFilters().put("invalidRequest", invalidRequestFilter)
++ bean.getFilters().put("port", portFilter)
++ return bean
++ }
++ }
++
++ @Autowired
++ private AbstractShiroFilter shiroFilter
++
++ @Test
++ void testGlobalFiltersConfigured() {
++ // make sure global chains are configured
++ assertThat shiroFilter.filterChainResolver.filterChainManager, instanceOf(DefaultFilterChainManager)
++ DefaultFilterChainManager filterChainManager = shiroFilter.filterChainResolver.filterChainManager
++
++ // default config set
++ assertThat filterChainManager.globalFilterNames, contains(DefaultFilter.invalidRequest.name(),
++ DefaultFilter.port.name())
++ // default route configured
++ NamedFilterList allChain = filterChainManager.getChain("/**")
++ assertThat allChain, contains(
++ instanceOf(DefaultFilter.invalidRequest.filterClass),
++ instanceOf(DefaultFilter.port.filterClass))
++
++ InvalidRequestFilter invalidRequest = allChain.get(0)
++ assertThat "Expected invalidRequest.blockBackslash to be false", !invalidRequest.isBlockBackslash()
++ PortFilter portFilter = allChain.get(1) // an ugly line, but we want to make sure that we can override the filters
++ // defined in Shiro's DefaultFilter
++ assertThat portFilter.port, equalTo(9999)
++
++ // configured routes also contain global filters
++ NamedFilterList loginChain = filterChainManager.getChain("/login.html")
++ assertThat loginChain, contains(
++ instanceOf(DefaultFilter.invalidRequest.filterClass),
++ instanceOf(DefaultFilter.port.filterClass),
++ instanceOf(DefaultFilter.authc.filterClass)) // configured in ShiroWebAutoConfigurationTestApplication
++
++ assertThat loginChain.get(0), sameInstance(invalidRequest)
++ assertThat loginChain.get(1), sameInstance(portFilter)
++
++
++ }
++}
+--- /dev/null
++++ b/support/spring-boot/spring-boot-web-starter/src/test/groovy/org/apache/shiro/spring/boot/autoconfigure/web/DisabledGlobalFiltersTest.groovy
+@@ -0,0 +1,64 @@
++/*
++ * 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.shiro.spring.boot.autoconfigure.web;
++
++import org.apache.shiro.spring.boot.autoconfigure.web.application.ShiroWebAutoConfigurationTestApplication
++import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager
++import org.apache.shiro.web.servlet.AbstractShiroFilter
++import org.junit.Test
++import org.junit.runner.RunWith
++import org.springframework.beans.factory.annotation.Autowired
++import org.springframework.boot.test.context.SpringBootTest
++import org.springframework.context.annotation.Bean
++import org.springframework.context.annotation.Configuration
++import org.springframework.test.context.junit4.SpringRunner
++
++import static org.hamcrest.MatcherAssert.assertThat
++import static org.hamcrest.Matchers.equalTo
++import static org.hamcrest.Matchers.instanceOf
++import static org.hamcrest.Matchers.nullValue
++
++ at RunWith(SpringRunner.class)
++ at SpringBootTest(classes = [ShiroWebAutoConfigurationTestApplication, Config])
++class DisabledGlobalFiltersTest {
++
++ @Configuration
++ static class Config {
++
++ @Bean
++ List<String> globalFilters() {
++ return []
++ }
++ }
++
++ @Autowired
++ private AbstractShiroFilter shiroFilter
++
++ @Test
++ void testGlobalFiltersDisabled() {
++ // make sure global chains are configured
++ assertThat shiroFilter.filterChainResolver.filterChainManager, instanceOf(DefaultFilterChainManager)
++ DefaultFilterChainManager filterChainManager = shiroFilter.filterChainResolver.filterChainManager
++
++ // default config set
++ assertThat filterChainManager.globalFilterNames, equalTo([])
++ // default route configured
++ assertThat filterChainManager.getChain("/**"), nullValue()
++ }
++}
+--- a/support/spring/src/main/java/org/apache/shiro/spring/web/ShiroFilterFactoryBean.java
++++ b/support/spring/src/main/java/org/apache/shiro/spring/web/ShiroFilterFactoryBean.java
+@@ -25,8 +25,10 @@
+ import org.apache.shiro.util.StringUtils;
+ import org.apache.shiro.web.config.IniFilterChainResolverFactory;
+ import org.apache.shiro.web.filter.AccessControlFilter;
++import org.apache.shiro.web.filter.InvalidRequestFilter;
+ import org.apache.shiro.web.filter.authc.AuthenticationFilter;
+ import org.apache.shiro.web.filter.authz.AuthorizationFilter;
++import org.apache.shiro.web.filter.mgt.DefaultFilter;
+ import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
+ import org.apache.shiro.web.filter.mgt.FilterChainManager;
+ import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+@@ -41,7 +43,9 @@
+ import org.springframework.beans.factory.config.BeanPostProcessor;
+
+ import javax.servlet.Filter;
++import java.util.ArrayList;
+ import java.util.LinkedHashMap;
++import java.util.List;
+ import java.util.Map;
+
+ /**
+@@ -121,6 +125,8 @@
+
+ private Map<String, Filter> filters;
+
++ private List<String> globalFilters;
++
+ private Map<String, String> filterChainDefinitionMap; //urlPathExpression_to_comma-delimited-filter-chain-definition
+
+ private String loginUrl;
+@@ -131,6 +137,8 @@
+
+ public ShiroFilterFactoryBean() {
+ this.filters = new LinkedHashMap<String, Filter>();
++ this.globalFilters = new ArrayList<String>();
++ this.globalFilters.add(DefaultFilter.invalidRequest.name());
+ this.filterChainDefinitionMap = new LinkedHashMap<String, String>(); //order matters!
+ }
+
+@@ -332,6 +340,14 @@
+ }
+
+ /**
++ * Sets the list of filters that will be executed against every request. Defaults to the {@link InvalidRequestFilter} which will block known invalid request attacks.
++ * @param globalFilters the list of filters to execute before specific path filters.
++ */
++ public void setGlobalFilters(List<String> globalFilters) {
++ this.globalFilters = globalFilters;
++ }
++
++ /**
+ * Lazily creates and returns a {@link AbstractShiroFilter} concrete instance via the
+ * {@link #createInstance} method.
+ *
+@@ -388,6 +404,9 @@
+ }
+ }
+
++ // set the global filters
++ manager.setGlobalFilters(this.globalFilters);
++
+ //build up the chains:
+ Map<String, String> chains = getFilterChainDefinitionMap();
+ if (!CollectionUtils.isEmpty(chains)) {
+@@ -398,6 +417,9 @@
+ }
+ }
+
++ // create the default chain, to match anything the path matching would have missed
++ manager.createDefaultChain("/**"); // TODO this assumes ANT path matching, which might be OK here
++
+ return manager;
+ }
+
+@@ -533,6 +555,7 @@
+ throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
+ }
+ setSecurityManager(webSecurityManager);
++
+ if (resolver != null) {
+ setFilterChainResolver(resolver);
+ }
+--- a/support/spring/src/test/java/org/apache/shiro/spring/web/ShiroFilterFactoryBeanTest.java
++++ b/support/spring/src/test/java/org/apache/shiro/spring/web/ShiroFilterFactoryBeanTest.java
+@@ -18,6 +18,7 @@
+ */
+ package org.apache.shiro.spring.web;
+
++import org.apache.shiro.web.filter.InvalidRequestFilter;
+ import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
+ import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
+ import org.apache.shiro.web.filter.mgt.NamedFilterList;
+@@ -55,11 +56,12 @@
+ DefaultFilterChainManager fcManager = (DefaultFilterChainManager) resolver.getFilterChainManager();
+ NamedFilterList chain = fcManager.getChain("/test");
+ assertNotNull(chain);
+- assertEquals(chain.size(), 2);
++ assertEquals(chain.size(), 3);
+ Filter[] filters = new Filter[chain.size()];
+ filters = chain.toArray(filters);
+- assertTrue(filters[0] instanceof DummyFilter);
+- assertTrue(filters[1] instanceof FormAuthenticationFilter);
++ assertTrue(filters[0] instanceof InvalidRequestFilter); // global filter
++ assertTrue(filters[1] instanceof DummyFilter);
++ assertTrue(filters[2] instanceof FormAuthenticationFilter);
+ }
+
+ /**
+--- a/web/src/main/java/org/apache/shiro/web/config/IniFilterChainResolverFactory.java
++++ b/web/src/main/java/org/apache/shiro/web/config/IniFilterChainResolverFactory.java
+@@ -24,6 +24,7 @@
+ import org.apache.shiro.config.ReflectionBuilder;
+ import org.apache.shiro.util.CollectionUtils;
+ import org.apache.shiro.util.Factory;
++import org.apache.shiro.web.filter.mgt.DefaultFilter;
+ import org.apache.shiro.web.filter.mgt.FilterChainManager;
+ import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+ import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
+@@ -32,7 +33,9 @@
+
+ import javax.servlet.Filter;
+ import javax.servlet.FilterConfig;
++import java.util.Collections;
+ import java.util.LinkedHashMap;
++import java.util.List;
+ import java.util.Map;
+
+ /**
+@@ -51,6 +54,8 @@
+
+ private Map<String, ?> defaultBeans;
+
++ private List<String> globalFilters = Collections.singletonList(DefaultFilter.invalidRequest.name());
++
+ public IniFilterChainResolverFactory() {
+ super();
+ }
+@@ -72,6 +77,14 @@
+ this.filterConfig = filterConfig;
+ }
+
++ public List<String> getGlobalFilters() {
++ return globalFilters;
++ }
++
++ public void setGlobalFilters(List<String> globalFilters) {
++ this.globalFilters = globalFilters;
++ }
++
+ protected FilterChainResolver createInstance(Ini ini) {
+ FilterChainResolver filterChainResolver = createDefaultInstance();
+ if (filterChainResolver instanceof PathMatchingFilterChainResolver) {
+@@ -122,9 +135,14 @@
+ //add the filters to the manager:
+ registerFilters(filters, manager);
+
++ manager.setGlobalFilters(getGlobalFilters());
++
+ //urls section:
+ section = ini.getSection(URLS);
+ createChains(section, manager);
++
++ // create the default chain, to match anything the path matching would have missed
++ manager.createDefaultChain("/**"); // TODO this assumes ANT path matching
+ }
+
+ protected void registerFilters(Map<String, Filter> filters, FilterChainManager manager) {
+--- /dev/null
++++ b/web/src/main/java/org/apache/shiro/web/filter/InvalidRequestFilter.java
+@@ -0,0 +1,136 @@
++/*
++ * 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.shiro.web.filter;
++
++import org.apache.shiro.web.util.WebUtils;
++
++import javax.servlet.ServletRequest;
++import javax.servlet.ServletResponse;
++import java.util.Arrays;
++import java.util.Collections;
++import java.util.List;
++
++/**
++ * A request filter that blocks malicious requests. Invalid request will respond with a 400 response code.
++ * <p>
++ * This filter checks and blocks the request if the following characters are found in the request URI:
++ * <ul>
++ * <li>Semicolon - can be disabled by setting {@code blockSemicolon = false}</li>
++ * <li>Backslash - can be disabled by setting {@code blockBackslash = false}</li>
++ * <li>Non-ASCII characters - can be disabled by setting {@code blockNonAscii = false}, the ability to disable this check will be removed in future version.</li>
++ * </ul>
++ *
++ * @see <a href="https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/firewall/StrictHttpFirewall.html">This class was inspired by Spring Security StrictHttpFirewall</a>
++ * @since 1.6
++ */
++public class InvalidRequestFilter extends AccessControlFilter {
++
++ private static final List<String> SEMICOLON = Collections.unmodifiableList(Arrays.asList(";", "%3b", "%3B"));
++
++ private static final List<String> BACKSLASH = Collections.unmodifiableList(Arrays.asList("\\", "%5c", "%5C"));
++
++ private boolean blockSemicolon = true;
++
++ private boolean blockBackslash = true;
++
++ private boolean blockNonAscii = true;
++
++ @Override
++ protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
++ String uri = WebUtils.toHttp(request).getRequestURI();
++ return !containsSemicolon(uri)
++ && !containsBackslash(uri)
++ && !containsNonAsciiCharacters(uri);
++ }
++
++ @Override
++ protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
++ WebUtils.toHttp(response).sendError(400, "Invalid request");
++ return false;
++ }
++
++ private boolean containsSemicolon(String uri) {
++ if (isBlockSemicolon()) {
++ int length = uri.length();
++ for (int i = 0; i < length; i++) {
++ char c = uri.charAt(i);
++ if (c == ';') {
++ return true;
++ }
++ }
++ }
++ return false;
++ }
++
++ private boolean containsBackslash(String uri) {
++ if (isBlockBackslash()) {
++ int length = uri.length();
++ for (int i = 0; i < length; i++) {
++ char c = uri.charAt(i);
++ if (c == '\\') {
++ return true;
++ }
++ }
++ }
++ return false;
++ }
++
++ private boolean containsNonAsciiCharacters(String uri) {
++ if (isBlockNonAscii()) {
++ return !containsOnlyPrintableAsciiCharacters(uri);
++ }
++ return false;
++ }
++
++ private static boolean containsOnlyPrintableAsciiCharacters(String uri) {
++ int length = uri.length();
++ for (int i = 0; i < length; i++) {
++ char c = uri.charAt(i);
++ if (c < '\u0020' || c > '\u007e') {
++ return false;
++ }
++ }
++ return true;
++ }
++
++ public boolean isBlockSemicolon() {
++ return blockSemicolon;
++ }
++
++ public void setBlockSemicolon(boolean blockSemicolon) {
++ this.blockSemicolon = blockSemicolon;
++ }
++
++ public boolean isBlockBackslash() {
++ return blockBackslash;
++ }
++
++ public void setBlockBackslash(boolean blockBackslash) {
++ this.blockBackslash = blockBackslash;
++ }
++
++ public boolean isBlockNonAscii() {
++ return blockNonAscii;
++ }
++
++ public void setBlockNonAscii(boolean blockNonAscii) {
++ this.blockNonAscii = blockNonAscii;
++ }
++}
+--- a/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilter.java
++++ b/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilter.java
+@@ -19,6 +19,7 @@
+ package org.apache.shiro.web.filter.mgt;
+
+ import org.apache.shiro.util.ClassUtils;
++import org.apache.shiro.web.filter.InvalidRequestFilter;
+ import org.apache.shiro.web.filter.authc.*;
+ import org.apache.shiro.web.filter.authz.*;
+ import org.apache.shiro.web.filter.session.NoSessionCreationFilter;
+@@ -47,7 +48,8 @@
+ rest(HttpMethodPermissionFilter.class),
+ roles(RolesAuthorizationFilter.class),
+ ssl(SslFilter.class),
+- user(UserFilter.class);
++ user(UserFilter.class),
++ invalidRequest(InvalidRequestFilter.class);
+
+ private final Class<? extends Filter> filterClass;
+
+--- a/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilterChainManager.java
++++ b/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilterChainManager.java
+@@ -30,8 +30,10 @@
+ import javax.servlet.FilterChain;
+ import javax.servlet.FilterConfig;
+ import javax.servlet.ServletException;
++import java.util.ArrayList;
+ import java.util.Collections;
+ import java.util.LinkedHashMap;
++import java.util.List;
+ import java.util.Map;
+ import java.util.Set;
+
+@@ -52,17 +54,21 @@
+
+ private Map<String, Filter> filters; //pool of filters available for creating chains
+
++ private List<String> globalFilterNames; // list of filters to prepend to every chain
++
+ private Map<String, NamedFilterList> filterChains; //key: chain name, value: chain
+
+ public DefaultFilterChainManager() {
+ this.filters = new LinkedHashMap<String, Filter>();
+ this.filterChains = new LinkedHashMap<String, NamedFilterList>();
++ this.globalFilterNames = new ArrayList<String>();
+ addDefaultFilters(false);
+ }
+
+ public DefaultFilterChainManager(FilterConfig filterConfig) {
+ this.filters = new LinkedHashMap<String, Filter>();
+ this.filterChains = new LinkedHashMap<String, NamedFilterList>();
++ this.globalFilterNames = new ArrayList<String>();
+ setFilterConfig(filterConfig);
+ addDefaultFilters(true);
+ }
+@@ -115,6 +121,17 @@
+ addFilter(name, filter, init, true);
+ }
+
++ public void createDefaultChain(String chainName) {
++ // only create the defaultChain if we don't have a chain with this name already
++ // (the global filters will already be in that chain)
++ if (!getChainNames().contains(chainName) && !CollectionUtils.isEmpty(globalFilterNames)) {
++ // add each of global filters
++ for (String filterName : globalFilterNames) {
++ addToChain(chainName, filterName);
++ }
++ }
++ }
++
+ public void createChain(String chainName, String chainDefinition) {
+ if (!StringUtils.hasText(chainName)) {
+ throw new NullPointerException("chainName cannot be null or empty.");
+@@ -124,7 +141,14 @@
+ }
+
+ if (log.isDebugEnabled()) {
+- log.debug("Creating chain [" + chainName + "] from String definition [" + chainDefinition + "]");
++ log.debug("Creating chain [" + chainName + "] with global filters " + globalFilterNames + " and from String definition [" + chainDefinition + "]");
++ }
++
++ // first add each of global filters
++ if (!CollectionUtils.isEmpty(globalFilterNames)) {
++ for (String filterName : globalFilterNames) {
++ addToChain(chainName, filterName);
++ }
+ }
+
+ //parse the value by tokenizing it to get the resulting filter-specific config entries
+@@ -273,6 +297,21 @@
+ chain.add(filter);
+ }
+
++ public void setGlobalFilters(List<String> globalFilterNames) throws ConfigurationException {
++ // validate each filter name
++ if (!CollectionUtils.isEmpty(globalFilterNames)) {
++ for (String filterName : globalFilterNames) {
++ Filter filter = filters.get(filterName);
++ if (filter == null) {
++ throw new ConfigurationException("There is no filter with name '" + filterName +
++ "' to apply to the global filters in the pool of available Filters. Ensure a " +
++ "filter with that name/path has first been registered with the addFilter method(s).");
++ }
++ this.globalFilterNames.add(filterName);
++ }
++ }
++ }
++
+ protected void applyChainConfig(String chainName, Filter filter, String chainSpecificFilterConfig) {
+ if (log.isDebugEnabled()) {
+ log.debug("Attempting to apply path [" + chainName + "] to filter [" + filter + "] " +
+--- a/web/src/main/java/org/apache/shiro/web/filter/mgt/FilterChainManager.java
++++ b/web/src/main/java/org/apache/shiro/web/filter/mgt/FilterChainManager.java
+@@ -22,6 +22,7 @@
+
+ import javax.servlet.Filter;
+ import javax.servlet.FilterChain;
++import java.util.List;
+ import java.util.Map;
+ import java.util.Set;
+
+@@ -165,6 +166,14 @@
+ void createChain(String chainName, String chainDefinition);
+
+ /**
++ * Creates a chain that should match any non-matched request paths, typically {@code /**} assuming an {@link AntPathMatcher} I used.
++ * @param chainName The name of the chain to create, likely {@code /**}.
++ * @since 1.6
++ * @see org.apache.shiro.lang.util.AntPathMatcher AntPathMatcher
++ */
++ void createDefaultChain(String chainName);
++
++ /**
+ * Adds (appends) a filter to the filter chain identified by the given {@code chainName}. If there is no chain
+ * with the given name, a new one is created and the filter will be the first in the chain.
+ *
+@@ -195,4 +204,17 @@
+ * interface).
+ */
+ void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) throws ConfigurationException;
++
++ /**
++ * Configures the set of named filters that will match all paths. These filters will match BEFORE explicitly
++ * configured filter chains i.e. by calling {@link #createChain(String, String)}, {@link #addToChain(String, String)}, etc.
++ * <br>
++ * <strong>Filters configured in this list wll apply to ALL requests.</strong>
++ *
++ * @param globalFilterNames the list of filter names to match ALL paths.
++ * @throws ConfigurationException if one of the filter names is invalid and cannot be loaded from the set of
++ * configured filters {@link #getFilters()}}.
++ * @since 1.6
++ */
++ void setGlobalFilters(List<String> globalFilterNames) throws ConfigurationException;
+ }
+--- a/web/src/main/java/org/apache/shiro/web/servlet/AbstractShiroFilter.java
++++ b/web/src/main/java/org/apache/shiro/web/servlet/AbstractShiroFilter.java
+@@ -404,6 +404,7 @@
+ * @since 1.0
+ */
+ protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
++
+ FilterChain chain = origChain;
+
+ FilterChainResolver resolver = getFilterChainResolver();
+--- /dev/null
++++ b/web/src/test/java/org/apache/shiro/web/env/FilterStub.java
+@@ -0,0 +1,45 @@
++/*
++ * 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.shiro.web.env;
++
++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 java.io.IOException;
++
++public class FilterStub implements Filter {
++
++ @Override
++ public void init(FilterConfig filterConfig) throws ServletException {
++
++ }
++
++ @Override
++ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
++
++ }
++
++ @Override
++ public void destroy() {
++
++ }
++}
=====================================
debian/patches/CVE-2020-17510_1_of_2.patch
=====================================
@@ -0,0 +1,236 @@
+From a28300448ae6c4bb78a8ba626b0cacb00f82d5f8 Mon Sep 17 00:00:00 2001
+From: Brian Demers <bdemers at apache.org>
+Date: Thu, 3 Sep 2020 14:58:45 -0400
+Subject: [PATCH] Adds configuration to toggle the normalization of backslashes
+
+This is normally handled by the container
+Update the InvalidRequestFilter to use WebUtils.ALLOW_BACKSLASH
+(new system property: org.apache.shiro.web.ALLOW_BACKSLASH)
+
+Fixes: SHIRO-794
+---
+ .../web/filter/InvalidRequestFilter.java | 22 ++++--
+ .../org/apache/shiro/web/util/WebUtils.java | 4 +-
+ .../filter/InvalidRequestFilterTest.groovy | 48 +++++++++++--
+ .../apache/shiro/web/util/WebUtilsTest.groovy | 52 ++++++++++++++
+ .../shiro/web/RestoreSystemProperties.java | 69 +++++++++++++++++++
+ 5 files changed, 182 insertions(+), 13 deletions(-)
+ create mode 100644 web/src/test/java/org/apache/shiro/web/RestoreSystemProperties.java
+
+--- a/web/src/main/java/org/apache/shiro/web/filter/InvalidRequestFilter.java
++++ b/web/src/main/java/org/apache/shiro/web/filter/InvalidRequestFilter.java
+@@ -19,10 +19,12 @@
+
+ package org.apache.shiro.web.filter;
+
++import org.apache.shiro.util.StringUtils;
+ import org.apache.shiro.web.util.WebUtils;
+
+ import javax.servlet.ServletRequest;
+ import javax.servlet.ServletResponse;
++import javax.servlet.http.HttpServletRequest;
+ import java.util.Arrays;
+ import java.util.Collections;
+ import java.util.List;
+@@ -48,16 +50,24 @@
+
+ private boolean blockSemicolon = true;
+
+- private boolean blockBackslash = true;
++ private boolean blockBackslash = !Boolean.getBoolean(WebUtils.ALLOW_BACKSLASH);
+
+ private boolean blockNonAscii = true;
+
+ @Override
+- protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
+- String uri = WebUtils.toHttp(request).getRequestURI();
+- return !containsSemicolon(uri)
+- && !containsBackslash(uri)
+- && !containsNonAsciiCharacters(uri);
++ protected boolean isAccessAllowed(ServletRequest req, ServletResponse response, Object mappedValue) throws Exception {
++ HttpServletRequest request = WebUtils.toHttp(req);
++ // check the original and decoded values
++ return isValid(request.getRequestURI()) // user request string (not decoded)
++ && isValid(request.getServletPath()) // decoded servlet part
++ && isValid(request.getPathInfo()); // decoded path info (may be null)
++ }
++
++ private boolean isValid(String uri) {
++ return !StringUtils.hasText(uri)
++ || ( !containsSemicolon(uri)
++ && !containsBackslash(uri)
++ && !containsNonAsciiCharacters(uri));
+ }
+
+ @Override
+--- a/web/src/main/java/org/apache/shiro/web/util/WebUtils.java
++++ b/web/src/main/java/org/apache/shiro/web/util/WebUtils.java
+@@ -56,6 +56,8 @@
+ public static final String SERVLET_REQUEST_KEY = ServletRequest.class.getName() + "_SHIRO_THREAD_CONTEXT_KEY";
+ public static final String SERVLET_RESPONSE_KEY = ServletResponse.class.getName() + "_SHIRO_THREAD_CONTEXT_KEY";
+
++ public static final String ALLOW_BACKSLASH = "org.apache.shiro.web.ALLOW_BACKSLASH";
++
+ /**
+ * {@link org.apache.shiro.session.Session Session} key used to save a request and later restore it, for example when redirecting to a
+ * requested page after login, equal to {@code shiroSavedRequest}.
+@@ -162,7 +164,7 @@
+ * @return normalized path
+ */
+ public static String normalize(String path) {
+- return normalize(path, true);
++ return normalize(path, Boolean.getBoolean(ALLOW_BACKSLASH));
+ }
+
+ /**
+--- a/web/src/test/groovy/org/apache/shiro/web/util/WebUtilsTest.groovy
++++ b/web/src/test/groovy/org/apache/shiro/web/util/WebUtilsTest.groovy
+@@ -18,12 +18,15 @@
+ */
+ package org.apache.shiro.web.util
+
++import org.apache.shiro.web.RestoreSystemProperties
++import org.hamcrest.CoreMatchers
+ import org.junit.Test
+
+ import javax.servlet.http.HttpServletRequest
+
+ import static org.easymock.EasyMock.*
+ import static org.junit.Assert.*
++import static org.hamcrest.CoreMatchers.*
+
+ /**
+ * Tests for {@link WebUtils}.
+@@ -193,6 +196,55 @@
+ doTestGetRequestURI("/context path/foobar", "/context path/foobar");
+ }
+
++ @Test
++ void testNormalize() {
++ doNormalizeTest"/foobar", "/foobar"
++ doNormalizeTest "/foobar/", "/foobar/"
++ doNormalizeTest"", "/"
++ doNormalizeTest"foobar", "/foobar"
++ doNormalizeTest"//foobar", "/foobar"
++ doNormalizeTest"//foobar///", "/foobar/"
++ doNormalizeTest"/context-path/foobar", "/context-path/foobar"
++ doNormalizeTest"/context-path/foobar/", "/context-path/foobar/"
++ doNormalizeTest"//context-path/foobar", "/context-path/foobar"
++ doNormalizeTest"//context-path//foobar" ,"/context-path/foobar"
++ doNormalizeTest"//context-path/remove-one/remove-two/../../././/foobar", "/context-path/foobar"
++ doNormalizeTest"//context-path//../../././/foobar", null
++ doNormalizeTest"/context path/foobar", "/context path/foobar"
++
++ doNormalizeTest"/context path/\\foobar", "/context path/\\foobar"
++ doNormalizeTest"//context-path\\..\\../.\\.\\foobar", "/context-path\\..\\../.\\.\\foobar"
++ doNormalizeTest"//context-path\\..\\..\\.\\.\\foobar", "/context-path\\..\\..\\.\\.\\foobar"
++ doNormalizeTest"\\context-path\\..\\foobar", "/\\context-path\\..\\foobar"
++ }
++
++ @Test
++ void testNormalize_allowBackslashes() {
++ RestoreSystemProperties.withProperties(["org.apache.shiro.web.ALLOW_BACKSLASH": "true"]) {
++ doNormalizeTest"/foobar", "/foobar"
++ doNormalizeTest "/foobar/", "/foobar/"
++ doNormalizeTest"", "/"
++ doNormalizeTest"foobar", "/foobar"
++ doNormalizeTest"//foobar", "/foobar"
++ doNormalizeTest"//foobar///", "/foobar/"
++ doNormalizeTest"/context-path/foobar", "/context-path/foobar"
++ doNormalizeTest"/context-path/foobar/", "/context-path/foobar/"
++ doNormalizeTest"//context-path/foobar", "/context-path/foobar"
++ doNormalizeTest"//context-path//foobar" ,"/context-path/foobar"
++ doNormalizeTest"//context-path/remove-one/remove-two/../../././/foobar", "/context-path/foobar"
++ doNormalizeTest"//context-path//../../././/foobar", null
++ doNormalizeTest"/context path/foobar", "/context path/foobar"
++ doNormalizeTest"/context path/\\foobar", "/context path/foobar"
++ doNormalizeTest"//context-path\\..\\..\\.\\.\\foobar", null
++ doNormalizeTest"\\context-path\\..\\foobar", "/foobar"
++
++ }
++ }
++
++ void doNormalizeTest(String path, String expected) {
++ assertThat WebUtils.normalize(path), equalTo(expected)
++ }
++
+ void doTestGetPathWithinApplication(String servletPath, String pathInfo, String expectedValue) {
+
+ def request = createMock(HttpServletRequest)
+--- /dev/null
++++ b/web/src/test/java/org/apache/shiro/web/RestoreSystemProperties.java
+@@ -0,0 +1,74 @@
++/*
++ * 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.shiro.web;
++
++import groovy.lang.Closure;
++
++import java.io.Closeable;
++import java.util.Collections;
++import java.util.Map;
++import java.util.Properties;
++
++/**
++ * Wrapper that will restore System properties after test methods.
++ *
++ * Based on: https://github.com/stefanbirkner/system-rules/blob/master/src/main/java/org/junit/contrib/java/lang/system/RestoreSystemProperties.java
++ */
++public class RestoreSystemProperties implements Closeable {
++
++ private final Properties originalProperties;
++
++ public RestoreSystemProperties() {
++ originalProperties = System.getProperties();
++ System.setProperties(copyOf(originalProperties));
++ }
++
++ public void restore() {
++ System.setProperties(originalProperties);
++ }
++
++ private Properties copyOf(Properties source) {
++ Properties copy = new Properties();
++ copy.putAll(source);
++ return copy;
++ }
++
++ public static <T> T withProperties(Closure<T> closure) {
++ return withProperties(Collections.<String, String>emptyMap(), closure);
++ }
++
++ public static <T> T withProperties(Map<String, String> properties, Closure<T> closure) {
++
++ RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
++ try {
++ for (Map.Entry<String, String> property : properties.entrySet()) {
++ System.setProperty(property.getKey(), property.getValue());
++ }
++
++ return closure.call();
++ } finally {
++ restoreSystemProperties.close();
++ }
++ }
++
++ @Override
++ public void close() {
++ restore();
++ }
++}
=====================================
debian/patches/CVE-2020-17510_2_of_2.patch
=====================================
@@ -0,0 +1,67 @@
+From 74d4cb6aee9aa1af4b098edc526a1e5630743f9b Mon Sep 17 00:00:00 2001
+From: Brian Demers <bdemers at apache.org>
+Date: Tue, 29 Sep 2020 17:59:29 -0400
+Subject: [PATCH] Disable jsessionid URL rewriting by default
+
+This matches the default of the InvalidRequestFilter
+
+Fixes: SHIRO-795
+---
+ .../spring/web/config/AbstractShiroWebConfiguration.java | 2 +-
+ .../shiro/web/session/mgt/DefaultWebSessionManager.java | 2 +-
+ .../web/session/mgt/DefaultWebSessionManagerTest.groovy | 5 ++++-
+ 3 files changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionManager.java b/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionManager.java
+index eb7eda1f..9aa275a9 100644
+--- a/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionManager.java
++++ b/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionManager.java
+@@ -58,7 +58,7 @@ public DefaultWebSessionManager() {
+ cookie.setHttpOnly(true); //more secure, protects against XSS attacks
+ this.sessionIdCookie = cookie;
+ this.sessionIdCookieEnabled = true;
+- this.sessionIdUrlRewritingEnabled = true;
++ this.sessionIdUrlRewritingEnabled = false;
+ }
+
+ public Cookie getSessionIdCookie() {
+diff --git a/web/src/test/groovy/org/apache/shiro/web/session/mgt/DefaultWebSessionManagerTest.groovy b/web/src/test/groovy/org/apache/shiro/web/session/mgt/DefaultWebSessionManagerTest.groovy
+index 841569fc..35b31204 100644
+--- a/web/src/test/groovy/org/apache/shiro/web/session/mgt/DefaultWebSessionManagerTest.groovy
++++ b/web/src/test/groovy/org/apache/shiro/web/session/mgt/DefaultWebSessionManagerTest.groovy
+@@ -127,7 +127,7 @@ public class DefaultWebSessionManagerTest {
+ ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
+ request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
+ request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
+- request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, Boolean.TRUE);
++ request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, Boolean.FALSE);
+
+ replay(cookie);
+ replay(request);
+@@ -147,6 +147,7 @@ public class DefaultWebSessionManagerTest {
+ Cookie cookie = createMock(Cookie.class);
+ mgr.setSessionIdCookie(cookie);
+ mgr.setSessionIdCookieEnabled(false);
++ mgr.setSessionIdUrlRewritingEnabled(true)
+
+ //we should not have any reads from the cookie fields - if we do, this test case will fail.
+
+@@ -182,6 +183,7 @@ public class DefaultWebSessionManagerTest {
+ Cookie cookie = createMock(Cookie.class);
+ mgr.setSessionIdCookie(cookie);
+ mgr.setSessionIdCookieEnabled(false);
++ mgr.setSessionIdUrlRewritingEnabled(true)
+
+ //we should not have any reads from the cookie fields - if we do, this test case will fail.
+
+@@ -218,6 +220,7 @@ public class DefaultWebSessionManagerTest {
+ public void testGetSessionIdFromRequestUriPathSegmentParam() {
+
+ mgr.setSessionIdCookieEnabled(false);
++ mgr.setSessionIdUrlRewritingEnabled(true)
+
+ HttpServletRequest request = createMock(HttpServletRequest.class);
+ HttpServletResponse response = createMock(HttpServletResponse.class);
+--
+2.20.1
+
=====================================
debian/patches/CVE-2020-1957.patch
=====================================
@@ -0,0 +1,297 @@
+--- a/web/src/main/java/org/apache/shiro/web/util/WebUtils.java
++++ b/web/src/main/java/org/apache/shiro/web/util/WebUtils.java
+@@ -108,16 +108,7 @@
+ * @return the path within the web application
+ */
+ public static String getPathWithinApplication(HttpServletRequest request) {
+- String contextPath = getContextPath(request);
+- String requestUri = getRequestUri(request);
+- if (StringUtils.startsWithIgnoreCase(requestUri, contextPath)) {
+- // Normal case: URI contains context path.
+- String path = requestUri.substring(contextPath.length());
+- return (StringUtils.hasText(path) ? path : "/");
+- } else {
+- // Special case: rather unusual.
+- return requestUri;
+- }
++ return normalize(removeSemicolon(getServletPath(request) + getPathInfo(request)));
+ }
+
+ /**
+@@ -131,7 +122,9 @@
+ *
+ * @param request current HTTP request
+ * @return the request URI
++ * @deprecated use getPathWithinApplication() to get the path minus the context path, or call HttpServletRequest.getRequestURI() directly from your code.
+ */
++ @Deprecated
+ public static String getRequestUri(HttpServletRequest request) {
+ String uri = (String) request.getAttribute(INCLUDE_REQUEST_URI_ATTRIBUTE);
+ if (uri == null) {
+@@ -140,6 +133,23 @@
+ return normalize(decodeAndCleanUriString(request, uri));
+ }
+
++ private static String getServletPath(HttpServletRequest request) {
++ String servletPath = (String) request.getAttribute(INCLUDE_SERVLET_PATH_ATTRIBUTE);
++ return servletPath != null ? servletPath : valueOrEmpty(request.getServletPath());
++ }
++
++ private static String getPathInfo(HttpServletRequest request) {
++ String pathInfo = (String) request.getAttribute(INCLUDE_PATH_INFO_ATTRIBUTE);
++ return pathInfo != null ? pathInfo : valueOrEmpty(request.getPathInfo());
++ }
++
++ private static String valueOrEmpty(String input) {
++ if (input == null) {
++ return "";
++ }
++ return input;
++ }
++
+ /**
+ * Normalize a relative URI path that may have relative values ("/./",
+ * "/../", and so on ) it it. <strong>WARNING</strong> - This method is
+@@ -230,6 +240,10 @@
+ */
+ private static String decodeAndCleanUriString(HttpServletRequest request, String uri) {
+ uri = decodeRequestString(request, uri);
++ return removeSemicolon(uri);
++ }
++
++ private static String removeSemicolon(String uri) {
+ int semicolonIndex = uri.indexOf(';');
+ return (semicolonIndex != -1 ? uri.substring(0, semicolonIndex) : uri);
+ }
+--- a/support/guice/src/test/java/org/apache/shiro/guice/web/FilterConfigTest.java
++++ b/support/guice/src/test/java/org/apache/shiro/guice/web/FilterConfigTest.java
+@@ -85,9 +85,8 @@
+ private HttpServletRequest createMockRequest(String path) {
+ HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+
+- expect(request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE)).andReturn(null).anyTimes();
+- expect(request.getContextPath()).andReturn("");
+- expect(request.getRequestURI()).andReturn(path);
++ expect(request.getServletPath()).andReturn("");
++ expect(request.getPathInfo()).andReturn(path);
+ replay(request);
+ return request;
+ }
+--- a/web/src/test/groovy/org/apache/shiro/web/util/WebUtilsTest.groovy
++++ b/web/src/test/groovy/org/apache/shiro/web/util/WebUtilsTest.groovy
+@@ -140,34 +140,90 @@
+
+ }
+
++ @Test
++ void testGetRequestUriWithServlet() {
++
++ dotTestGetPathWithinApplicationFromRequest("/servlet", "/foobar", "/servlet/foobar")
++ dotTestGetPathWithinApplicationFromRequest("/servlet", "/foobar", "/servlet/foobar")
++ dotTestGetPathWithinApplicationFromRequest("servlet", "/foobar", "/servlet/foobar")
++ dotTestGetPathWithinApplicationFromRequest("servlet", "/foobar", "/servlet/foobar")
++ dotTestGetPathWithinApplicationFromRequest("servlet", "/foobar", "/servlet/foobar")
++ dotTestGetPathWithinApplicationFromRequest("//servlet", "//foobar", "/servlet/foobar")
++ dotTestGetPathWithinApplicationFromRequest("/servlet", "/foobar", "/servlet/foobar")
++ dotTestGetPathWithinApplicationFromRequest("//servlet", "//foobar", "/servlet/foobar")
++ dotTestGetPathWithinApplicationFromRequest("/servlet", "/../servlet/other", "/servlet/other")
++ dotTestGetPathWithinApplicationFromRequest("/asdf", "/../servlet/other", "/servlet/other")
++ dotTestGetPathWithinApplicationFromRequest("/asdf/foo", ";/../servlet/other", "/asdf/foo")
++ dotTestGetPathWithinApplicationFromRequest("/servlet", "/foobar", "/servlet/foobar")
++ dotTestGetPathWithinApplicationFromRequest("/servlet", "/foobar", "/servlet/foobar")
++ dotTestGetPathWithinApplicationFromRequest("/servlet", "/foobar", "/servlet/foobar")
++ dotTestGetPathWithinApplicationFromRequest(null, null, "/")
++ dotTestGetPathWithinApplicationFromRequest("index.jsp", null, "/index.jsp")
++ }
+
+ @Test
+ void testGetPathWithinApplication() {
+
+- doTestGetPathWithinApplication("/", "/foobar", "/foobar");
+- doTestGetPathWithinApplication("", "/foobar", "/foobar");
+- doTestGetPathWithinApplication("", "foobar", "/foobar");
+- doTestGetPathWithinApplication("/", "foobar", "/foobar");
+- doTestGetPathWithinApplication("//", "foobar", "/foobar");
+- doTestGetPathWithinApplication("//", "//foobar", "/foobar");
+- doTestGetPathWithinApplication("/context-path", "/context-path/foobar", "/foobar");
+- doTestGetPathWithinApplication("/context-path", "/context-path/foobar/", "/foobar/");
+- doTestGetPathWithinApplication("//context-path", "//context-path/foobar", "/foobar");
+- doTestGetPathWithinApplication("//context-path", "//context-path//foobar", "/foobar");
+- doTestGetPathWithinApplication("//context-path", "//context-path/remove-one/remove-two/../../././/foobar", "/foobar");
+- doTestGetPathWithinApplication("//context-path", "//context-path//../../././/foobar", null);
+- doTestGetPathWithinApplication("/context%2525path", "/context%2525path/foobar", "/foobar");
+- doTestGetPathWithinApplication("/c%6Fntext%20path", "/c%6Fntext%20path/foobar", "/foobar");
+- doTestGetPathWithinApplication("/context path", "/context path/foobar", "/foobar");
++ doTestGetPathWithinApplication("/foobar", null, "/foobar");
++ doTestGetPathWithinApplication("/foobar", "", "/foobar");
++ doTestGetPathWithinApplication("", "/", "/");
++ doTestGetPathWithinApplication("", null, "/");
++ doTestGetPathWithinApplication("/foobar", "//", "/foobar/");
++ doTestGetPathWithinApplication("/foobar", "//extra", "/foobar/extra");
++ doTestGetPathWithinApplication("/foobar", "//extra///", "/foobar/extra/");
++ doTestGetPathWithinApplication("/foo bar", "/path info" ,"/foo bar/path info");
++ }
+
++ @Test
++ void testGetRequestURI() {
++ doTestGetRequestURI("/foobar", "/foobar")
++ doTestGetRequestURI( "/foobar/", "/foobar/")
++ doTestGetRequestURI("", "/");
++ doTestGetRequestURI("foobar", "/foobar");
++ doTestGetRequestURI("//foobar", "/foobar");
++ doTestGetRequestURI("//foobar///", "/foobar/");
++ doTestGetRequestURI("/context-path/foobar", "/context-path/foobar");
++ doTestGetRequestURI("/context-path/foobar/", "/context-path/foobar/");
++ doTestGetRequestURI("//context-path/foobar", "/context-path/foobar");
++ doTestGetRequestURI("//context-path//foobar", "/context-path/foobar");
++ doTestGetRequestURI("//context-path/remove-one/remove-two/../../././/foobar", "/context-path/foobar");
++ doTestGetRequestURI("//context-path//../../././/foobar", null);
++ doTestGetRequestURI("/context%2525path/foobar", "/context%25path/foobar");
++ doTestGetRequestURI("/c%6Fntext%20path/foobar", "/context path/foobar");
++ doTestGetRequestURI("/context path/foobar", "/context path/foobar");
+ }
+
+- void doTestGetPathWithinApplication(String contextPath, String requestUri, String expectedValue) {
++ void doTestGetPathWithinApplication(String servletPath, String pathInfo, String expectedValue) {
+
+ def request = createMock(HttpServletRequest)
+- expect(request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE)).andReturn(contextPath)
+- expect(request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE)).andReturn(requestUri)
+- expect(request.getCharacterEncoding()).andReturn("UTF-8").times(2)
++ expect(request.getAttribute(WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE)).andReturn(servletPath)
++ expect(request.getAttribute(WebUtils.INCLUDE_PATH_INFO_ATTRIBUTE)).andReturn(pathInfo)
++ if (pathInfo == null) {
++ expect(request.getPathInfo()).andReturn(null) // path info can be null
++ }
++ replay request
++ assertEquals expectedValue, WebUtils.getPathWithinApplication(request)
++ verify request
++ }
++
++ void doTestGetRequestURI(String rawRequestUri, String expectedValue) {
++
++ def request = createMock(HttpServletRequest)
++ expect(request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE)).andReturn(rawRequestUri)
++ expect(request.getCharacterEncoding()).andReturn("UTF-8").times(1)
++ replay request
++ assertEquals expectedValue, WebUtils.getRequestUri(request)
++ verify request
++ }
++
++ void dotTestGetPathWithinApplicationFromRequest(String servletPath, String pathInfo, String expectedValue) {
++
++ HttpServletRequest request = createMock(HttpServletRequest)
++ expect(request.getAttribute(WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE)).andReturn(null)
++ expect(request.getAttribute(WebUtils.INCLUDE_PATH_INFO_ATTRIBUTE)).andReturn(null)
++ expect(request.getServletPath()).andReturn(servletPath)
++ expect(request.getPathInfo()).andReturn(pathInfo)
++ expect(request.getCharacterEncoding()).andReturn("UTF-8").anyTimes()
+ replay request
+ assertEquals expectedValue, WebUtils.getPathWithinApplication(request)
+ verify request
+--- a/web/src/test/java/org/apache/shiro/web/filter/PathMatchingFilterTest.java
++++ b/web/src/test/java/org/apache/shiro/web/filter/PathMatchingFilterTest.java
+@@ -112,6 +112,8 @@
+
+ expect(request.getContextPath()).andReturn(CONTEXT_PATH).anyTimes();
+ expect(request.getRequestURI()).andReturn(ENABLED_PATH).anyTimes();
++ expect(request.getServletPath()).andReturn("").anyTimes();
++ expect(request.getPathInfo()).andReturn(ENABLED_PATH).anyTimes();
+ replay(request);
+
+ boolean continueFilterChain = filter.preHandle(request, response);
+--- a/web/src/test/java/org/apache/shiro/web/filter/mgt/PathMatchingFilterChainResolverTest.java
++++ b/web/src/test/java/org/apache/shiro/web/filter/mgt/PathMatchingFilterChainResolverTest.java
+@@ -97,9 +97,8 @@
+ //ensure at least one chain is defined:
+ resolver.getFilterChainManager().addToChain("/index.html", "authcBasic");
+
+- expect(request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE)).andReturn(null).anyTimes();
+- expect(request.getContextPath()).andReturn("");
+- expect(request.getRequestURI()).andReturn("/index.html");
++ expect(request.getServletPath()).andReturn("");
++ expect(request.getPathInfo()).andReturn("/index.html");
+ replay(request);
+
+ FilterChain resolved = resolver.getChain(request, response, chain);
+@@ -116,9 +115,8 @@
+ //ensure at least one chain is defined:
+ resolver.getFilterChainManager().addToChain("/index.html", "authcBasic");
+
+- expect(request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE)).andReturn(null).anyTimes();
+- expect(request.getContextPath()).andReturn("");
+- expect(request.getRequestURI()).andReturn("/./index.html");
++ expect(request.getServletPath()).andReturn("/");
++ expect(request.getPathInfo()).andReturn("./index.html");
+ replay(request);
+
+ FilterChain resolved = resolver.getChain(request, response, chain);
+@@ -135,9 +133,8 @@
+ //ensure at least one chain is defined:
+ resolver.getFilterChainManager().addToChain("/index.html", "authcBasic");
+
+- expect(request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE)).andReturn(null).anyTimes();
+- expect(request.getContextPath()).andReturn("");
+- expect(request.getRequestURI()).andReturn("/public/../index.html");
++ expect(request.getServletPath()).andReturn("/public/");
++ expect(request.getPathInfo()).andReturn("../index.html");
+ replay(request);
+
+ FilterChain resolved = resolver.getChain(request, response, chain);
+@@ -154,9 +151,8 @@
+ //ensure at least one chain is defined:
+ resolver.getFilterChainManager().addToChain("/index.html", "authcBasic");
+
+- expect(request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE)).andReturn(null).anyTimes();
+- expect(request.getContextPath()).andReturn("");
+- expect(request.getRequestURI()).andReturn("/");
++ expect(request.getServletPath()).andReturn("/");
++ expect(request.getPathInfo()).andReturn(null);
+ replay(request);
+
+ FilterChain resolved = resolver.getChain(request, response, chain);
+--- a/support/guice/src/test/java/org/apache/shiro/guice/web/SimpleFilterChainResolverTest.java
++++ b/support/guice/src/test/java/org/apache/shiro/guice/web/SimpleFilterChainResolverTest.java
+@@ -82,8 +82,8 @@
+ ServletResponse response = ctrl.createMock(HttpServletResponse.class);
+ FilterChain originalChain = ctrl.createMock(FilterChain.class);
+
+- expect(request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE)).andReturn("/context");
+- expect(request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE)).andReturn("/mychain");
++ expect(request.getAttribute(WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE)).andReturn("/mychain");
++ expect(request.getAttribute(WebUtils.INCLUDE_PATH_INFO_ATTRIBUTE)).andReturn("");
+
+ expect(request.getCharacterEncoding()).andStubReturn(null);
+
+@@ -113,8 +113,8 @@
+
+ ctrl.reset();
+
+- expect(request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE)).andReturn("/context");
+- expect(request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE)).andReturn("/nochain");
++ expect(request.getAttribute(WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE)).andReturn("/nochain");
++ expect(request.getAttribute(WebUtils.INCLUDE_PATH_INFO_ATTRIBUTE)).andReturn("");
+
+ expect(request.getCharacterEncoding()).andStubReturn(null);
+
+--- a/support/guice/src/test/java/org/apache/shiro/guice/web/ShiroWebModuleTest.java
++++ b/support/guice/src/test/java/org/apache/shiro/guice/web/ShiroWebModuleTest.java
+@@ -177,11 +177,13 @@
+ servletContext.setAttribute(eq(EnvironmentLoader.ENVIRONMENT_ATTRIBUTE_KEY), EasyMock.anyObject());
+ expect(request.getAttribute("javax.servlet.include.context_path")).andReturn("").anyTimes();
+ expect(request.getCharacterEncoding()).andReturn("UTF-8").anyTimes();
+- expect(request.getAttribute("javax.servlet.include.request_uri")).andReturn("/test_authc");
+- expect(request.getAttribute("javax.servlet.include.request_uri")).andReturn("/test_custom_filter");
+- expect(request.getAttribute("javax.servlet.include.request_uri")).andReturn("/test_authc_basic");
+- expect(request.getAttribute("javax.servlet.include.request_uri")).andReturn("/test_perms");
+- expect(request.getAttribute("javax.servlet.include.request_uri")).andReturn("/multiple_configs");
++ expect(request.getAttribute("javax.servlet.include.path_info")).andReturn(null).anyTimes();
++ expect(request.getPathInfo()).andReturn(null).anyTimes();
++ expect(request.getAttribute("javax.servlet.include.servlet_path")).andReturn("/test_authc");
++ expect(request.getAttribute("javax.servlet.include.servlet_path")).andReturn("/test_custom_filter");
++ expect(request.getAttribute("javax.servlet.include.servlet_path")).andReturn("/test_authc_basic");
++ expect(request.getAttribute("javax.servlet.include.servlet_path")).andReturn("/test_perms");
++ expect(request.getAttribute("javax.servlet.include.servlet_path")).andReturn("/multiple_configs");
+ replay(servletContext, request);
+
+ Injector injector = Guice.createInjector(new ShiroWebModule(servletContext) {
=====================================
debian/patches/series
=====================================
@@ -2,3 +2,8 @@
02-reproducible-build.patch
03-spring-compatibility.patch
04-java11-compatibility.patch
+05-guice-improvements.patch
+CVE-2020-1957.patch
+CVE-2020-13933.patch
+CVE-2020-17510_1_of_2.patch
+CVE-2020-17510_2_of_2.patch
View it on GitLab: https://salsa.debian.org/java-team/shiro/-/compare/eb6d684042c00a1b935bd8157e236427db5f70f4...058ca4777ea4a5223cfcfafbecf2278929c46d8f
--
View it on GitLab: https://salsa.debian.org/java-team/shiro/-/compare/eb6d684042c00a1b935bd8157e236427db5f70f4...058ca4777ea4a5223cfcfafbecf2278929c46d8f
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20241217/e5e6aef0/attachment.htm>
More information about the pkg-java-commits
mailing list