diff -Nru shiro-1.3.2/debian/changelog shiro-1.3.2/debian/changelog --- shiro-1.3.2/debian/changelog 2019-03-01 21:36:03.000000000 +0000 +++ shiro-1.3.2/debian/changelog 2021-08-27 17:10:19.000000000 +0000 @@ -1,3 +1,20 @@ +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 Fri, 27 Aug 2021 13:10:19 -0400 + shiro (1.3.2-4) unstable; urgency=medium * Team upload. diff -Nru shiro-1.3.2/debian/patches/03-spring-compatibility.patch shiro-1.3.2/debian/patches/03-spring-compatibility.patch --- shiro-1.3.2/debian/patches/03-spring-compatibility.patch 2019-03-01 21:36:03.000000000 +0000 +++ shiro-1.3.2/debian/patches/03-spring-compatibility.patch 2021-08-27 17:10:19.000000000 +0000 @@ -1,17 +1,33 @@ -Description: Fixes the compatibility with the version of Spring Framework in Debian -Author: Emmanuel Bourg -Forwarded: no +From aa2cd4fca10416623ecc29008cd851a4f1ed3d98 Mon Sep 17 00:00:00 2001 +From: Brian Demers +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 bean implements Destroyable. ++ * @param bean bean to check if requires destruction. ++ * @return true only if bean implements Destroyable. ++ * @since 1.4 ++ */ ++ @SuppressWarnings("unused") ++ public boolean requiresDestruction(Object bean) { ++ return (bean instanceof Destroyable); ++ } + } +-- +2.20.1 + diff -Nru shiro-1.3.2/debian/patches/05-guice-improvements.patch shiro-1.3.2/debian/patches/05-guice-improvements.patch --- shiro-1.3.2/debian/patches/05-guice-improvements.patch 1970-01-01 00:00:00.000000000 +0000 +++ shiro-1.3.2/debian/patches/05-guice-improvements.patch 2021-08-27 17:10:19.000000000 +0000 @@ -0,0 +1,751 @@ +commit f2dfa7ff39c9870e7b9856ceca8690c5398080fa +Author: Brian Demers +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 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 void hear(TypeLiteral typeLiteral, TypeEncounter typeEncounter) { ++ ++ final Provider eventBusProvider = typeEncounter.getProvider(EventBus.class); ++ ++ List methods = ClassUtils.getAnnotatedMethods(typeLiteral.getRawType(), Subscribe.class); ++ if (methods != null && !methods.isEmpty()) { ++ typeEncounter.register( new InjectionListener() { ++ @Override ++ public void afterInjection(Object o) { ++ eventBusProvider.get().register(o); ++ } ++ }); ++ } ++ } ++ } ++ ++ private class EventBusAwareTypeListener implements TypeListener { ++ @Override ++ public void hear(TypeLiteral typeLiteral, TypeEncounter typeEncounter) { ++ ++ final Provider eventBusProvider = typeEncounter.getProvider(EventBus.class); ++ ++ if (EventBusAware.class.isAssignableFrom(typeLiteral.getRawType())) { ++ typeEncounter.register( new InjectionListener() { ++ @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[]> filterChains = new LinkedHashMap[]>(); ++ private final Map[]> filterChains = new LinkedHashMap[]>(); + 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, Map> configs = new HashMap, Map>(); ++ private Map[]> setupFilterChainConfigs() { ++ ++ // loop through and build a map of Filter Key -> Map ++ Map, Map> filterToPathToConfig = new HashMap, Map>(); ++ ++ // At the same time build a map to return with Path -> Key[] ++ Map[]> resultConfigMap = new HashMap[]>(); ++ ++ for (Map.Entry[]> filterChain : filterChains.entrySet()) { ++ ++ String path = filterChain.getKey(); ++ ++ // collect the keys used for this path ++ List> keysForPath = new ArrayList>(); + +- for (Map.Entry[]> filterChain : filterChains.entrySet()) { + for (int i = 0; i < filterChain.getValue().length; i++) { +- Key key = filterChain.getValue()[i]; +- if (key instanceof FilterConfigKey) { +- FilterConfigKey configKey = (FilterConfigKey) 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()); +- 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()); +- configs.get(castToPathMatching(key)).put(filterChain.getKey(), ""); ++ FilterConfig filterConfig = filterChain.getValue()[i]; ++ ++ Key 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()); ++ } ++ // 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 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 key : filterToPathToConfig.keySet()) { ++ if (PathMatchingFilter.class.isAssignableFrom(key.getTypeLiteral().getRawType())) { ++ bindPathMatchingFilter(castToPathMatching(key), filterToPathToConfig.get(key)); ++ } ++ else { ++ bind(key); ++ } + } ++ ++ return resultConfigMap; + } + ++ + private void bindPathMatchingFilter(Key filterKey, Map configs) { + bind(filterKey).toProvider(new PathMatchingFilterProvider(filterKey, configs)).asEagerSingleton(); + } +@@ -218,6 +244,126 @@ + bind.to(WebGuiceEnvironment.class).asEagerSingleton(); + } + ++ protected final void addFilterChain(String pattern, Key key) { ++ // check for legacy API ++ if (key instanceof FilterConfigKey) { ++ addLegacyFilterChain(pattern, (FilterConfigKey) key); ++ } ++ else { ++ addFilterChain(pattern, new FilterConfig((Key) key, "")); ++ } ++ } ++ ++ /** ++ * Maps 'n' number of filterConfigs to a specific path pattern.
++ * 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 pattern. ++ * @since 1.4 ++ */ ++ protected final void addFilterChain(String pattern, FilterConfig... 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 A Servlet Filter class. ++ * @return A FilterConfig used to map a String path to this configuration. ++ * @since 1.4 ++ */ ++ protected static FilterConfig filterConfig(Key baseKey, String configValue) { ++ return new FilterConfig(baseKey, configValue); ++ } ++ ++ /** ++ * Builds a FilterConfig from a Filer and configuration String ++ * @param baseKey The Key of the Filter class to be used. ++ * @param A Servlet Filter class. ++ * @return A FilterConfig used to map a String path to this configuration. ++ * @since 1.4 ++ */ ++ protected static FilterConfig filterConfig(Key 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 A Servlet Filter class. ++ * @return A FilterConfig used to map a String path to this configuration. ++ * @since 1.4 ++ */ ++ @SuppressWarnings({"UnusedDeclaration"}) ++ protected static FilterConfig filterConfig(TypeLiteral 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 A Servlet Filter class. ++ * @return A FilterConfig used to map a String path to this configuration. ++ * @since 1.4 ++ */ ++ @SuppressWarnings({"UnusedDeclaration"}) ++ protected static FilterConfig filterConfig(Class type, String configValue) { ++ return filterConfig(Key.get(type), configValue); ++ } ++ ++ ++ /** ++ * Filter configuration which pairs a Filter class with its configuration used on a path. ++ * @param The Servlet Filter class. ++ * @since 1.4 ++ */ ++ public static class FilterConfig { ++ private Key key; ++ private String configValue; ++ ++ private FilterConfig(Key key, String configValue) { ++ super(); ++ this.key = key; ++ this.configValue = configValue; ++ } ++ ++ public Key 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 filterConfig = new FilterConfig(filterConfigKey.getKey(), filterConfigKey.getConfigValue()); ++ addFilterChain(pattern, filterConfig); ++ } ++ + /** + * Adds a filter chain to the shiro configuration. + *

+@@ -228,24 +374,52 @@ + * @param keys + */ + @SuppressWarnings({"UnusedDeclaration"}) ++ @Deprecated + protected final void addFilterChain(String pattern, Key... 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 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 Key config(Key baseKey, String configValue) { ++ ++ if( !isGuiceVersion3()) { ++ throw new ConfigurationException("Method ShiroWebModule.config(Key, String configValue), is not supported when using Guice 4+"); ++ } ++ + return new FilterConfigKey(baseKey, configValue); + } + + @SuppressWarnings({"UnusedDeclaration"}) ++ @Deprecated + protected static Key config(TypeLiteral typeLiteral, String configValue) { + return config(Key.get(typeLiteral), configValue); + } + + @SuppressWarnings({"UnusedDeclaration"}) ++ @Deprecated + protected static Key config(Class type, String configValue) { + return config(Key.get(type), configValue); + } + ++ @Deprecated + private static class FilterConfigKey extends Key { + private Key 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 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 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 filters = filterChain.getFilters(); ++ if (filters.hasNext()) { ++ return filters.next(); ++ } ++ ++ return null; ++ } ++ + public static class MyDefaultWebSecurityManager extends DefaultWebSecurityManager { + @Inject + public MyDefaultWebSecurityManager(Collection 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() {} ++ } + } diff -Nru shiro-1.3.2/debian/patches/CVE-2020-13933.patch shiro-1.3.2/debian/patches/CVE-2020-13933.patch --- shiro-1.3.2/debian/patches/CVE-2020-13933.patch 1970-01-01 00:00:00.000000000 +0000 +++ shiro-1.3.2/debian/patches/CVE-2020-13933.patch 2021-08-27 17:10:19.000000000 +0000 @@ -0,0 +1,1009 @@ +From dc194fc977ab6cfbf3c1ecb085e2bac5db14af6d Mon Sep 17 00:00:00 2001 +From: Brian Demers +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 SSL = Key.get(SslFilter.class); + @SuppressWarnings({"UnusedDeclaration"}) + public static final Key USER = Key.get(UserFilter.class); +- ++ @SuppressWarnings({"UnusedDeclaration"}) ++ public static final Key INVALID_REQUEST = Key.get(InvalidRequestFilter.class); + + static final String NAME = "SHIRO"; + +@@ -121,6 +123,12 @@ + }; + } + ++ public List> globalFilters() { ++ ArrayList> filters = new ArrayList>(); ++ 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> keysForPath = new ArrayList>(); + +- for (int i = 0; i < filterChain.getValue().length; i++) { +- FilterConfig filterConfig = filterChain.getValue()[i]; ++ List> globalFilters = this.globalFilters(); ++ FilterConfig[] pathFilters = filterChain.getValue(); ++ ++ // merge the global filters and the path specific filters ++ List> filterConfigs = new ArrayList>(globalFilters.size() + pathFilters.length); ++ filterConfigs.addAll(globalFilters); ++ filterConfigs.addAll(Arrays.asList(pathFilters)); ++ ++ for (FilterConfig filterConfig : filterConfigs) { + + Key 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> 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.* ++ ++@RunWith(SpringRunner.class) ++@SpringBootTest(classes = [ShiroWebAutoConfigurationTestApplication, Config]) ++ ++class ConfiguredGlobalFiltersTest { ++ ++ @Configuration ++ static class Config extends AbstractShiroWebFilterConfiguration { ++ ++ @Bean ++ List 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 ++ ++@RunWith(SpringRunner.class) ++@SpringBootTest(classes = [ShiroWebAutoConfigurationTestApplication, Config]) ++class DisabledGlobalFiltersTest { ++ ++ @Configuration ++ static class Config { ++ ++ @Bean ++ List 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 filters; + ++ private List globalFilters; ++ + private Map filterChainDefinitionMap; //urlPathExpression_to_comma-delimited-filter-chain-definition + + private String loginUrl; +@@ -131,6 +137,8 @@ + + public ShiroFilterFactoryBean() { + this.filters = new LinkedHashMap(); ++ this.globalFilters = new ArrayList(); ++ this.globalFilters.add(DefaultFilter.invalidRequest.name()); + this.filterChainDefinitionMap = new LinkedHashMap(); //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 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 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 defaultBeans; + ++ private List globalFilters = Collections.singletonList(DefaultFilter.invalidRequest.name()); ++ + public IniFilterChainResolverFactory() { + super(); + } +@@ -72,6 +77,14 @@ + this.filterConfig = filterConfig; + } + ++ public List getGlobalFilters() { ++ return globalFilters; ++ } ++ ++ public void setGlobalFilters(List 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 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. ++ *

++ * This filter checks and blocks the request if the following characters are found in the request URI: ++ *

    ++ *
  • Semicolon - can be disabled by setting {@code blockSemicolon = false}
  • ++ *
  • Backslash - can be disabled by setting {@code blockBackslash = false}
  • ++ *
  • Non-ASCII characters - can be disabled by setting {@code blockNonAscii = false}, the ability to disable this check will be removed in future version.
  • ++ *
++ * ++ * @see This class was inspired by Spring Security StrictHttpFirewall ++ * @since 1.6 ++ */ ++public class InvalidRequestFilter extends AccessControlFilter { ++ ++ private static final List SEMICOLON = Collections.unmodifiableList(Arrays.asList(";", "%3b", "%3B")); ++ ++ private static final List 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 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 filters; //pool of filters available for creating chains + ++ private List globalFilterNames; // list of filters to prepend to every chain ++ + private Map filterChains; //key: chain name, value: chain + + public DefaultFilterChainManager() { + this.filters = new LinkedHashMap(); + this.filterChains = new LinkedHashMap(); ++ this.globalFilterNames = new ArrayList(); + addDefaultFilters(false); + } + + public DefaultFilterChainManager(FilterConfig filterConfig) { + this.filters = new LinkedHashMap(); + this.filterChains = new LinkedHashMap(); ++ this.globalFilterNames = new ArrayList(); + 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 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. ++ *
++ * Filters configured in this list wll apply to ALL requests. ++ * ++ * @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 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() { ++ ++ } ++} diff -Nru shiro-1.3.2/debian/patches/CVE-2020-17510_1_of_2.patch shiro-1.3.2/debian/patches/CVE-2020-17510_1_of_2.patch --- shiro-1.3.2/debian/patches/CVE-2020-17510_1_of_2.patch 1970-01-01 00:00:00.000000000 +0000 +++ shiro-1.3.2/debian/patches/CVE-2020-17510_1_of_2.patch 2021-08-27 17:10:19.000000000 +0000 @@ -0,0 +1,236 @@ +From a28300448ae6c4bb78a8ba626b0cacb00f82d5f8 Mon Sep 17 00:00:00 2001 +From: Brian Demers +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 withProperties(Closure closure) { ++ return withProperties(Collections.emptyMap(), closure); ++ } ++ ++ public static T withProperties(Map properties, Closure closure) { ++ ++ RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties(); ++ try { ++ for (Map.Entry property : properties.entrySet()) { ++ System.setProperty(property.getKey(), property.getValue()); ++ } ++ ++ return closure.call(); ++ } finally { ++ restoreSystemProperties.close(); ++ } ++ } ++ ++ @Override ++ public void close() { ++ restore(); ++ } ++} diff -Nru shiro-1.3.2/debian/patches/CVE-2020-17510_2_of_2.patch shiro-1.3.2/debian/patches/CVE-2020-17510_2_of_2.patch --- shiro-1.3.2/debian/patches/CVE-2020-17510_2_of_2.patch 1970-01-01 00:00:00.000000000 +0000 +++ shiro-1.3.2/debian/patches/CVE-2020-17510_2_of_2.patch 2021-08-27 17:10:19.000000000 +0000 @@ -0,0 +1,67 @@ +From 74d4cb6aee9aa1af4b098edc526a1e5630743f9b Mon Sep 17 00:00:00 2001 +From: Brian Demers +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 + diff -Nru shiro-1.3.2/debian/patches/CVE-2020-1957.patch shiro-1.3.2/debian/patches/CVE-2020-1957.patch --- shiro-1.3.2/debian/patches/CVE-2020-1957.patch 1970-01-01 00:00:00.000000000 +0000 +++ shiro-1.3.2/debian/patches/CVE-2020-1957.patch 2021-08-27 17:10:19.000000000 +0000 @@ -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. WARNING - 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) { diff -Nru shiro-1.3.2/debian/patches/series shiro-1.3.2/debian/patches/series --- shiro-1.3.2/debian/patches/series 2019-03-01 21:36:03.000000000 +0000 +++ shiro-1.3.2/debian/patches/series 2021-08-27 17:10:19.000000000 +0000 @@ -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