From 6ac0d9986be1594fc6a47cfa9e1e46b37adaa01a Mon Sep 17 00:00:00 2001
From: Scott Murphy Heiberg <scott@alwaysvip.com>
Date: Thu, 10 Oct 2024 23:54:22 -0700
Subject: [PATCH 1/2] Filter order is completely different in Spring Security 5

---
 gradle/libs.versions.toml                     |   1 +
 plugin/build.gradle                           |   1 +
 ...ion.groovy => SecurityFilterPosition.java} | 106 ++++++++++++------
 .../springsecurity/SpringSecurityUtils.groovy |   2 +-
 .../SpringSecurityUtilsSpec.groovy            |   6 +
 .../config/http/SecurityFiltersMapper.groovy  |   5 +
 6 files changed, 86 insertions(+), 35 deletions(-)
 rename plugin/src/main/groovy/grails/plugin/springsecurity/{SecurityFilterPosition.groovy => SecurityFilterPosition.java} (57%)
 create mode 100644 plugin/src/test/groovy/org/springframework/security/config/http/SecurityFiltersMapper.groovy

diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 0dbdae2e1..97670006c 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -77,6 +77,7 @@ spring-context-core = { module = 'org.springframework:spring-context', version.r
 spring-context-support = { module = 'org.springframework:spring-context-support', version.ref = 'spring' }
 spring-expression = { module = 'org.springframework:spring-expression', version.ref = 'spring' }
 spring-security-core = { module = 'org.springframework.security:spring-security-core', version.ref = 'spring-security' }
+spring-security-config = { module = 'org.springframework.security:spring-security-config', version.ref = 'spring-security' }
 spring-security-crypto = { module = 'org.springframework.security:spring-security-crypto', version.ref = 'spring-security' }
 spring-security-web = { module = 'org.springframework.security:spring-security-web', version.ref = 'spring-security' }
 spring-test = { module = 'org.springframework:spring-test', version.ref = 'spring' }
diff --git a/plugin/build.gradle b/plugin/build.gradle
index f03a62481..ceaecf0fa 100644
--- a/plugin/build.gradle
+++ b/plugin/build.gradle
@@ -44,6 +44,7 @@ dependencies {
 	testImplementation libs.bundles.grails.testing.support
 	testImplementation libs.spock.core
 	testImplementation libs.spring.test
+	testImplementation libs.spring.security.config
 
 	testRuntimeOnly libs.slf4j.nop // Prevents warnings about missing slf4j implementation during tests
 }
diff --git a/plugin/src/main/groovy/grails/plugin/springsecurity/SecurityFilterPosition.groovy b/plugin/src/main/groovy/grails/plugin/springsecurity/SecurityFilterPosition.java
similarity index 57%
rename from plugin/src/main/groovy/grails/plugin/springsecurity/SecurityFilterPosition.groovy
rename to plugin/src/main/groovy/grails/plugin/springsecurity/SecurityFilterPosition.java
index 543770d25..06f1a1879 100644
--- a/plugin/src/main/groovy/grails/plugin/springsecurity/SecurityFilterPosition.groovy
+++ b/plugin/src/main/groovy/grails/plugin/springsecurity/SecurityFilterPosition.java
@@ -12,7 +12,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package grails.plugin.springsecurity
+package grails.plugin.springsecurity;
 
 /**
  * Stores the default order numbers of all Spring Security filters for use in configuration.
@@ -23,63 +23,101 @@
  * @author Burt Beckwith
  */
 enum SecurityFilterPosition {
-	/** First */
+
 	FIRST(Integer.MIN_VALUE),
-	/** HTTP/HTTPS channel filter */
+
+	DISABLE_ENCODE_URL_FILTER,
+
+	FORCE_EAGER_SESSION_FILTER,
+
 	CHANNEL_FILTER,
-	/** Concurrent Sessions */
-	CONCURRENT_SESSION_FILTER,
-	/** Populates the SecurityContextHolder */
+
 	SECURITY_CONTEXT_FILTER,
-	/** Logout */
+
+	CONCURRENT_SESSION_FILTER,
+
+	WEB_ASYNC_MANAGER_FILTER,
+
+	HEADERS_FILTER,
+
+	CORS_FILTER,
+
+	SAML2_LOGOUT_REQUEST_FILTER,
+
+	SAML2_LOGOUT_RESPONSE_FILTER,
+
+	CSRF_FILTER,
+
+	SAML2_LOGOUT_FILTER,
+
 	LOGOUT_FILTER,
-	/** x509 certs */
+
+	OAUTH2_AUTHORIZATION_REQUEST_FILTER,
+
+	SAML2_AUTHENTICATION_REQUEST_FILTER,
+
 	X509_FILTER,
-	/** Pre-auth */
+
 	PRE_AUTH_FILTER,
-	/** CAS */
+
 	CAS_FILTER,
-	/** UsernamePasswordAuthenticationFilter */
+
+	OAUTH2_LOGIN_FILTER,
+
+	SAML2_AUTHENTICATION_FILTER,
+
 	FORM_LOGIN_FILTER,
-	/** OpenID */
+
 	OPENID_FILTER,
-	/** Not used, generates a dynamic login form */
+
 	LOGIN_PAGE_FILTER,
-	/** Digest auth */
+
+	LOGOUT_PAGE_FILTER,
+
 	DIGEST_AUTH_FILTER,
-	/** Basic Auth */
+
+	BEARER_TOKEN_AUTH_FILTER,
+
 	BASIC_AUTH_FILTER,
-	/** saved request filter */
+
 	REQUEST_CACHE_FILTER,
-	/** SecurityContextHolderAwareRequestFilter */
+
 	SERVLET_API_SUPPORT_FILTER,
-	/** Remember-me cookie */
+
+	JAAS_API_SUPPORT_FILTER,
+
 	REMEMBER_ME_FILTER,
-	/** Anonymous auth */
+
 	ANONYMOUS_FILTER,
-	/** SessionManagementFilter */
+
+	OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER,
+
+	WELL_KNOWN_CHANGE_PASSWORD_REDIRECT_FILTER,
+
 	SESSION_MANAGEMENT_FILTER,
-	/** Spring FormContentFilter allows www-url-form-encoded content-types to provide params in PUT requests */
-	FORM_CONTENT_FILTER,
-	/** ExceptionTranslationFilter */
+
 	EXCEPTION_TRANSLATION_FILTER,
-	/** FilterSecurityInterceptor */
+
 	FILTER_SECURITY_INTERCEPTOR,
-	/** Switch user */
+
 	SWITCH_USER_FILTER,
-	/** Last */
-	LAST(Integer.MAX_VALUE)
 
-	private static final int INTERVAL = 100
+	LAST(Integer.MAX_VALUE);
+
+	private static final int INTERVAL = 100;
 
-	/** The position in the chain. */
-	final int order
+	private final int order;
 
-	private SecurityFilterPosition() {
-		order = ordinal() * INTERVAL
+	SecurityFilterPosition() {
+		this.order = ordinal() * INTERVAL;
 	}
 
-	private SecurityFilterPosition(int filterOrder) {
-		order = filterOrder
+	SecurityFilterPosition(int order) {
+		this.order = order;
 	}
+
+	public int getOrder() {
+		return this.order;
+	}
+
 }
diff --git a/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy b/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy
index 2a79fc9db..9f1424898 100644
--- a/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy
+++ b/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy
@@ -762,7 +762,7 @@ final class SpringSecurityUtils {
 				orderedNames[SecurityFilterPosition.SWITCH_USER_FILTER.order] = 'switchUserProcessingFilter'
 			}
 
-			orderedNames[SecurityFilterPosition.FORM_CONTENT_FILTER.order] = 'formContentFilter'
+			orderedNames[SecurityFilterPosition.SWITCH_USER_FILTER.order+1] = 'formContentFilter'
 
 			// add in filters contributed by secondary plugins
 			orderedNames << SpringSecurityUtils.orderedFilters
diff --git a/plugin/src/test/groovy/grails/plugin/springsecurity/SpringSecurityUtilsSpec.groovy b/plugin/src/test/groovy/grails/plugin/springsecurity/SpringSecurityUtilsSpec.groovy
index e4f9f8ca7..92d10d631 100644
--- a/plugin/src/test/groovy/grails/plugin/springsecurity/SpringSecurityUtilsSpec.groovy
+++ b/plugin/src/test/groovy/grails/plugin/springsecurity/SpringSecurityUtilsSpec.groovy
@@ -17,6 +17,7 @@ package grails.plugin.springsecurity
 import grails.plugin.springsecurity.web.GrailsSecurityFilterChain
 import grails.plugin.springsecurity.web.SecurityRequestHolder
 import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl
+import org.springframework.security.config.http.SecurityFiltersMapper
 import org.springframework.security.core.GrantedAuthority
 import org.springframework.security.core.authority.SimpleGrantedAuthority
 import org.springframework.security.web.FilterChainProxy
@@ -340,6 +341,11 @@ class SpringSecurityUtilsSpec extends AbstractUnitSpec {
 		!SpringSecurityUtils.ifAnyGranted('ROLE_4')
 	}
 
+	void 'SecurityFilterPosition order should match SecurityFilters'() {
+		expect:
+			SecurityFilterPosition.SWITCH_USER_FILTER.order == SecurityFiltersMapper.SWITCH_USER_FILTER.order
+	}
+
 	void 'private constructor'() {
 		expect:
 		SecurityTestUtils.testPrivateConstructor SpringSecurityUtils
diff --git a/plugin/src/test/groovy/org/springframework/security/config/http/SecurityFiltersMapper.groovy b/plugin/src/test/groovy/org/springframework/security/config/http/SecurityFiltersMapper.groovy
new file mode 100644
index 000000000..0c939e751
--- /dev/null
+++ b/plugin/src/test/groovy/org/springframework/security/config/http/SecurityFiltersMapper.groovy
@@ -0,0 +1,5 @@
+package org.springframework.security.config.http
+
+class SecurityFiltersMapper {
+    static final SWITCH_USER_FILTER = SecurityFilters.SWITCH_USER_FILTER
+}
\ No newline at end of file

From 99e2d2ed8a9a8de05b96cbb3763146a64850ed82 Mon Sep 17 00:00:00 2001
From: Scott Murphy Heiberg <scott@alwaysvip.com>
Date: Fri, 11 Oct 2024 00:16:23 -0700
Subject: [PATCH 2/2] Fix integration test

---
 .../SpringSecurityUtilsIntegrationSpec.groovy | 20 +++++++++----------
 .../springsecurity/SpringSecurityUtils.groovy |  2 +-
 2 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/examples/integration-test-app/src/integration-test/groovy/grails/plugin/springsecurity/SpringSecurityUtilsIntegrationSpec.groovy b/examples/integration-test-app/src/integration-test/groovy/grails/plugin/springsecurity/SpringSecurityUtilsIntegrationSpec.groovy
index 81fa0d968..03d550dcb 100644
--- a/examples/integration-test-app/src/integration-test/groovy/grails/plugin/springsecurity/SpringSecurityUtilsIntegrationSpec.groovy
+++ b/examples/integration-test-app/src/integration-test/groovy/grails/plugin/springsecurity/SpringSecurityUtilsIntegrationSpec.groovy
@@ -87,15 +87,15 @@ class SpringSecurityUtilsIntegrationSpec extends AbstractIntegrationSpec {
         expect:
         10 == map.size()
         map[Integer.MIN_VALUE + 10] instanceof SecurityRequestHolderFilter
-        map[300] instanceof SecurityContextPersistenceFilter
-        map[400] instanceof MutableLogoutFilter
-        map[800] instanceof GrailsUsernamePasswordAuthenticationFilter
-        map[1400] instanceof SecurityContextHolderAwareRequestFilter
-        map[1500] instanceof GrailsRememberMeAuthenticationFilter
-        map[1600] instanceof GrailsAnonymousAuthenticationFilter
-        map[1800] instanceof FormContentFilter
-        map[1900] instanceof ExceptionTranslationFilter
-        map[2000] instanceof FilterSecurityInterceptor
+        map[SecurityFilterPosition.SECURITY_CONTEXT_FILTER.order] instanceof SecurityContextPersistenceFilter
+        map[SecurityFilterPosition.LOGOUT_FILTER.order] instanceof MutableLogoutFilter
+        map[SecurityFilterPosition.FORM_LOGIN_FILTER.order] instanceof GrailsUsernamePasswordAuthenticationFilter
+        map[SecurityFilterPosition.SERVLET_API_SUPPORT_FILTER.order] instanceof SecurityContextHolderAwareRequestFilter
+        map[SecurityFilterPosition.REMEMBER_ME_FILTER.order] instanceof GrailsRememberMeAuthenticationFilter
+        map[SecurityFilterPosition.ANONYMOUS_FILTER.order] instanceof GrailsAnonymousAuthenticationFilter
+        map[SecurityFilterPosition.EXCEPTION_TRANSLATION_FILTER.order-10] instanceof FormContentFilter
+        map[SecurityFilterPosition.EXCEPTION_TRANSLATION_FILTER.order] instanceof ExceptionTranslationFilter
+        map[SecurityFilterPosition.FILTER_SECURITY_INTERCEPTOR.order] instanceof FilterSecurityInterceptor
 
         when:
         SpringSecurityUtils.clientRegisterFilter 'foo', SecurityFilterPosition.LOGOUT_FILTER
@@ -123,7 +123,7 @@ class SpringSecurityUtilsIntegrationSpec extends AbstractIntegrationSpec {
 
         then:
         11 == map.size()
-        map[410] instanceof DummyFilter
+        map[SecurityFilterPosition.LOGOUT_FILTER.order + 10] instanceof DummyFilter
 
         when:
         def filters = securityFilterChains[0].filters
diff --git a/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy b/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy
index 9f1424898..a8d7e521b 100644
--- a/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy
+++ b/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy
@@ -762,7 +762,7 @@ final class SpringSecurityUtils {
 				orderedNames[SecurityFilterPosition.SWITCH_USER_FILTER.order] = 'switchUserProcessingFilter'
 			}
 
-			orderedNames[SecurityFilterPosition.SWITCH_USER_FILTER.order+1] = 'formContentFilter'
+			orderedNames[SecurityFilterPosition.EXCEPTION_TRANSLATION_FILTER.order-10] = 'formContentFilter'
 
 			// add in filters contributed by secondary plugins
 			orderedNames << SpringSecurityUtils.orderedFilters