You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@grails.apache.org by "sbglasius (via GitHub)" <gi...@apache.org> on 2026/08/19 15:41:58 UTC

[PR] fix(cas): repair proxy receptor and single sign-out, add integration tests [grails-core]

sbglasius opened a new pull request, #16174:
URL: https://github.com/apache/grails-core/pull/16174

   ## Summary
   
   `grails-spring-security-cas` had no tests, and its only exerciser was a `bootRun`-only demo pointed at a hand-run CAS server. Two defects were living in that gap. This fixes both, makes a security-relevant default explicit, and puts the plugin under integration tests that run against a real Apereo CAS server.
   
   ## Fixes
   
   **`proxyReceptorUrl` was set unconditionally.** Spring Security 7.1 rejects a null pattern, so an application that did not configure a proxy receptor — the default — failed to start with `IllegalArgumentException: pattern cannot be null`. It is now only set when configured, which is how `CasAuthenticationFilter` expresses that proxy support is off. Before Spring Security 7.1 the unguarded assignment produced a matcher for the literal path `/**null`, quietly giving unconfigured applications a live proxy receptor.
   
   **Single sign-out never worked.** The plugin set `useSessionFixationPrevention = false` from `doWithSpring`, but it declares `loadAfter = ['springSecurityCore']`, so the core plugin had already defined `sessionAuthenticationStrategy` from the original value. The session was still replaced on login, so CAS logout requests matched no session and were silently dropped. The bean is now redefined from the CAS plugin as well, through the same `BeanTypeResolver` the core plugin uses, so an application's `sessionAuthenticationStrategyBeanClass` override still wins.
   
   Overriding a core bean from a sibling security plugin is the established pattern in this module family — CAS already overrides `authenticationEntryPoint`, LDAP overrides `userDetailsService`, ACL overrides `passwordEncoder`/`permissionEvaluator`/`runAsManager`.
   
   ## Behaviour change
   
   `cas.useSingleSignout` now defaults to `false`. Enabling it disables session fixation prevention, which an application should choose deliberately rather than inherit. The plugin logs and prints a warning at startup when it is enabled.
   
   Because single sign-out did not actually work before, applications upgrading are unlikely to lose working behaviour. Documented in the CAS configuration reference and in a new section of the Grails 8 upgrade guide.
   
   ## Tests
   
   `grails-test-examples-spring-security-cas-test1` now runs against a real `apereo/cas` server started with Testcontainers, gated on Docker availability, and no longer needs an externally managed CAS server for `bootRun` either.
   
   An `EnvironmentPostProcessor` starts the container and supplies the CAS URLs before the context is built. The service URL and proxy callback URL depend on the port the embedded server binds, so they are set once the server is up but before it serves a request.
   
   Three configurations run via the existing `TESTCONFIG` idiom, all added to CI:
   
   | `TESTCONFIG` | Configuration |
   |---|---|
   | `cas` | proxy settings unset; login, validation, roles, single sign-out |
   | `casProxy` | full proxy-granting-ticket round trip |
   | `casNoSingleSignout` | asserts the new default |
   
   Proxy coverage obtains a real `PT-` proxy ticket through `AttributePrincipal.getProxyTicketFor(...)`, so it exercises the whole callback round trip rather than inspecting internals. Logout requests are posted from a cookie-less client, as CAS does on its own connection — posting from the authenticated client would clear the session through failed authentication anyway, which is indistinguishable from single sign-out working.
   
   Verified that reverting either fix turns the relevant specs red.
   
   ## Notes for reviewers
   
   Two findings worth knowing, both documented in the code:
   
   - Do **not** add `attributeReleasePolicy.authorizedToReleaseProxyGrantingTicket` to the CAS service definition. Despite the name it is a different feature, it needs a service public key, and without one CAS drops `<cas:proxyGrantingTicket>` from the validation response entirely, silently breaking proxy authentication.
   - The stock `apereo/cas` image only authorises `https` services, so a service definition permitting `http` is copied into the container.
   
   This branches from and targets `8.0.x` because the default change is documented in the Grails 8 upgrade guide. The two bug fixes are independently backportable to `7.0.x` if wanted.
   
   Also included: `Grails BOM Hibernate7 Micronaut.adoc` is generated and gitignored but was missing from the rat exclusions, so `./gradlew rat` failed after any docs build. One line, unrelated to CAS — happy to split it out.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] fix(cas): repair proxy receptor and single sign-out, add integration tests [grails-core]

Posted by "bkoehm (via GitHub)" <gi...@apache.org>.
bkoehm commented on code in PR #16174:
URL: https://github.com/apache/grails-core/pull/16174#discussion_r3816375062


##########
grails-test-examples/spring-security/cas/test1/src/integration-test/groovy/specs/AbstractCasSpec.groovy:
##########
@@ -0,0 +1,182 @@
+/*
+ *  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
+ *
+ *    https://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 specs

Review Comment:
   Is this a package typically used for tests?



##########
grails-spring-security/cas/plugin/src/main/groovy/grails/plugin/springsecurity/cas/SpringSecurityCasGrailsPlugin.groovy:
##########
@@ -87,10 +91,30 @@ class SpringSecurityCasGrailsPlugin extends Plugin {
 
             if (conf.cas.useSingleSignout) {
 
-                // session fixation prevention breaks single signout because
-                // the service ticket is mapped to the session id which changes
+                // Session fixation prevention breaks single signout because the service ticket is
+                // mapped to the session id, which changes when the session is replaced on login.
+                // Disabling it is a security trade-off the application has opted into, so say so.
+                String message = '''
+    WARNING: cas.useSingleSignout is enabled, so session fixation prevention has been disabled.
+    CAS maps the service ticket to the HTTP session id, and a logout request cannot be matched to a
+    session that was replaced when the user authenticated. Set cas.useSingleSignout to false to keep
+    session fixation prevention and handle logout in the application instead.
+    '''
+                println message

Review Comment:
   Minor: Is the `println` necessary on top of the `log.warn`?



##########
grails-test-examples/spring-security/cas/test1/src/main/groovy/spring/security/cas/test/CasTestConfig.groovy:
##########
@@ -0,0 +1,72 @@
+/*
+ *  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
+ *
+ *    https://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 spring.security.cas.test

Review Comment:
   Again, not sure if this is following typical Grails test package naming conventions or not.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] fix(cas): repair proxy receptor and single sign-out, add integration tests [grails-core]

Posted by "testlens-app[bot] (via GitHub)" <gi...@apache.org>.
testlens-app[bot] commented on PR #16174:
URL: https://github.com/apache/grails-core/pull/16174#issuecomment-5353051489

   ## 🚨 TestLens detected 6 failed tests 🚨
   
   Here is what you can do:
   
   1) Inspect the test failures carefully.
   2) If you are convinced that some of the tests are flaky, you can mute them below.
   3) Finally, trigger a rerun by checking the rerun checkbox.
   
   ### Test Summary
   
   #### [CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot \(shard 1\)](https://github.com/apache/grails-core/actions/runs/32344453542/job/96350842997?pr=16174) > :grails-data-mongodb-core:test
   
   | Test | Runs | Flakiness |
   |---|---|--:|
   | MongoTransactionSpec > test a REQUIRES\_NEW inner transaction commits independently of a rolled back outer transaction | ❌ | [1%&nbsp;🟡](https://github.com/apache/grails-core/issues/16030) |
   | MongoTransactionSpec > test a committed transaction persists all writes atomically | ❌ | [1%&nbsp;🟡](https://github.com/apache/grails-core/issues/16030) |
   | MongoTransactionSpec > test a findOneAndDelete via the MongoEntity API participates in the transaction | ❌ | [1%&nbsp;🟡](https://github.com/apache/grails-core/issues/16030) |
   | MongoTransactionSpec > test a rolled back transaction discards all writes on the server | ❌ | [1%&nbsp;🟡](https://github.com/apache/grails-core/issues/16030) |
   | MongoTransactionSpec > test read-your-writes within an active transaction | ❌ | [1%&nbsp;🟡](https://github.com/apache/grails-core/issues/16030) |
   | MongoTransactionSpec > test writes across multiple collections roll back together | ❌ | [1%&nbsp;🟡](https://github.com/apache/grails-core/issues/16030) |
   
   🏷️ Commit: e8a04fc9aabbcebfbfd48df4657af12d859d1457
   ▶️ Tests:  33111 executed
   🟡 Checks: 30/77 completed
   
   ### Test Failures
   
   <details>
   
   <summary><strong>MongoTransactionSpec > test a REQUIRES_NEW inner transaction commits independently of a rolled back outer transaction</strong> (:grails-data-mongodb-core:test in <a href="https://github.com/apache/grails-core/actions/runs/32344453542/job/96350842997?pr=16174">CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot (shard 1)</a>)</summary>
   
   ```
   com.mongodb.MongoTimeoutException: Timed out while waiting for a server that matches WritableServerSelector. Client view of cluster state is {type=UNKNOWN, servers=[{address=localhost:32828, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused}}]
   	at com.mongodb.internal.connection.BaseCluster.logAndThrowTimeoutException(BaseCluster.java:431)
   	at com.mongodb.internal.connection.BaseCluster.lambda$selectServer$0(BaseCluster.java:166)
   	at com.mongodb.internal.time.Timeout.lambda$onExistsAndExpired$16(Timeout.java:238)
   	at com.mongodb.internal.time.Timeout.lambda$run$10(Timeout.java:203)
   	at com.mongodb.internal.time.TimePoint.checkedCall(TimePoint.java:100)
   	at com.mongodb.internal.time.Timeout.call(Timeout.java:176)
   	at com.mongodb.internal.time.Timeout.run(Timeout.java:196)
   	at com.mongodb.internal.time.Timeout.onExistsAndExpired(Timeout.java:235)
   	at com.mongodb.internal.time.Timeout.onExpired(Timeout.java:228)
   	at com.mongodb.internal.connection.BaseCluster.selectServer(BaseCluster.java:165)
   	at com.mongodb.internal.connection.SingleServerCluster.selectServer(SingleServerCluster.java:47)
   	at com.mongodb.internal.binding.ClusterBinding.getWriteConnectionSource(ClusterBinding.java:91)
   	at com.mongodb.client.internal.ClientSessionBinding.getConnectionSource(ClientSessionBinding.java:109)
   	at com.mongodb.client.internal.ClientSessionBinding.getWriteConnectionSource(ClientSessionBinding.java:102)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:169)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSourceAndConnection(SyncOperationHelper.java:138)
   	at com.mongodb.internal.operation.SyncOperationHelper.withConnection(SyncOperationHelper.java:117)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:73)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:48)
   	at com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl.execute(MongoClusterImpl.java:472)
   	at com.mongodb.client.internal.MongoDatabaseImpl.executeDrop(MongoDatabaseImpl.java:230)
   	at com.mongodb.client.internal.MongoDatabaseImpl.drop(MongoDatabaseImpl.java:220)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup_closure1(MongoTransactionSpec.groovy:58)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession_closure23(GormStaticApi.groovy:767)
   	at org.grails.datastore.mapping.core.DatastoreUtils.executeWithNewSession(DatastoreUtils.java:371)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession(GormStaticApi.groovy:766)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.withNewSession(GormEntity.groovy:1157)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup(MongoTransactionSpec.groovy:57)
   ```
   
   </details>
   <details>
   
   <summary><strong>MongoTransactionSpec > test a committed transaction persists all writes atomically</strong> (:grails-data-mongodb-core:test in <a href="https://github.com/apache/grails-core/actions/runs/32344453542/job/96350842997?pr=16174">CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot (shard 1)</a>)</summary>
   
   ```
   com.mongodb.MongoSocketReadException: Prematurely reached end of stream
   	at com.mongodb.internal.connection.SocketStream.read(SocketStream.java:184)
   	at com.mongodb.internal.connection.InternalStreamConnection.receiveResponseBuffers(InternalStreamConnection.java:942)
   	at com.mongodb.internal.connection.InternalStreamConnection.receiveCommandMessageResponse(InternalStreamConnection.java:567)
   	at com.mongodb.internal.connection.InternalStreamConnection.sendAndReceiveInternal(InternalStreamConnection.java:494)
   	at com.mongodb.internal.connection.InternalStreamConnection.lambda$sendAndReceive$1(InternalStreamConnection.java:383)
   	at com.mongodb.internal.connection.InternalStreamConnection.sendAndReceive(InternalStreamConnection.java:386)
   	at com.mongodb.internal.connection.UsageTrackingInternalConnection.sendAndReceive(UsageTrackingInternalConnection.java:111)
   	at com.mongodb.internal.connection.DefaultConnectionPool$PooledConnection.sendAndReceive(DefaultConnectionPool.java:757)
   	at com.mongodb.internal.connection.CommandProtocolImpl.execute(CommandProtocolImpl.java:60)
   	at com.mongodb.internal.connection.DefaultServer$DefaultServerProtocolExecutor.execute(DefaultServer.java:207)
   	at com.mongodb.internal.connection.DefaultServerConnection.executeProtocol(DefaultServerConnection.java:112)
   	at com.mongodb.internal.connection.DefaultServerConnection.command(DefaultServerConnection.java:82)
   	at com.mongodb.internal.connection.DefaultServerConnection.command(DefaultServerConnection.java:74)
   	at com.mongodb.internal.connection.DefaultServer$OperationCountTrackingConnection.command(DefaultServer.java:297)
   	at com.mongodb.internal.operation.SyncOperationHelper.lambda$executeRetryableWrite$11(SyncOperationHelper.java:285)
   	at com.mongodb.internal.operation.SyncOperationHelper.lambda$withSourceAndConnection$1(SyncOperationHelper.java:146)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:177)
   	at com.mongodb.internal.operation.SyncOperationHelper.lambda$withSourceAndConnection$2(SyncOperationHelper.java:142)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:177)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSourceAndConnection(SyncOperationHelper.java:138)
   	at com.mongodb.internal.operation.SyncOperationHelper.lambda$executeRetryableWrite$12(SyncOperationHelper.java:270)
   	at com.mongodb.internal.operation.SyncOperationHelper.lambda$decorateWriteWithRetries$13(SyncOperationHelper.java:329)
   	at com.mongodb.internal.async.function.RetryingSyncSupplier.get(RetryingSyncSupplier.java:67)
   	at com.mongodb.internal.operation.SyncOperationHelper.executeRetryableWrite(SyncOperationHelper.java:297)
   	at com.mongodb.internal.operation.BaseFindAndModifyOperation.execute(BaseFindAndModifyOperation.java:81)
   	at com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl.execute(MongoClusterImpl.java:472)
   	at com.mongodb.client.internal.MongoCollectionImpl.executeFindOneAndUpdate(MongoCollectionImpl.java:787)
   	at com.mongodb.client.internal.MongoCollectionImpl.findOneAndUpdate(MongoCollectionImpl.java:766)
   	at org.grails.datastore.mapping.mongo.engine.MongoCodecEntityPersister.generateIdentifier(MongoCodecEntityPersister.groovy:443)
   	at org.grails.datastore.mapping.mongo.engine.MongoCodecEntityPersister.persistEntity(MongoCodecEntityPersister.groovy:226)
   	at org.grails.datastore.mapping.mongo.engine.MongoCodecEntityPersister.persistEntity(MongoCodecEntityPersister.groovy:567)
   	at org.grails.datastore.mapping.engine.EntityPersister.persist(EntityPersister.java:188)
   	at org.grails.datastore.mapping.core.AbstractSession.persist(AbstractSession.java:609)
   	at org.grails.datastore.gorm.GormInstanceApi.save_closure5(GormInstanceApi.groovy:213)
   	at org.grails.datastore.mapping.core.DatastoreUtils.execute(DatastoreUtils.java:334)
   	at org.grails.datastore.gorm.AbstractGormApi.execute(AbstractGormApi.groovy:164)
   	at org.grails.datastore.gorm.GormInstanceApi.save(GormInstanceApi.groovy:206)
   	at org.grails.datastore.gorm.GormInstanceApi.save(GormInstanceApi.groovy:173)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.save(GormEntity.groovy:110)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.test a committed transaction persists all writes atomically_closure2(MongoTransactionSpec.groovy:73)
   	at grails.gorm.transactions.GrailsTransactionTemplate$2.doInTransaction(GrailsTransactionTemplate.groovy:103)
   	at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:137)
   	at grails.gorm.transactions.GrailsTransactionTemplate.execute(GrailsTransactionTemplate.groovy:100)
   	at org.grails.datastore.gorm.GormStaticApi.withTransaction(GormStaticApi.groovy:703)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.withTransaction(GormEntity.groovy:1066)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.test a committed transaction persists all writes atomically(MongoTransactionSpec.groovy:71)
   ```
   
   </details>
   <details>
   
   <summary><strong>MongoTransactionSpec > test a findOneAndDelete via the MongoEntity API participates in the transaction</strong> (:grails-data-mongodb-core:test in <a href="https://github.com/apache/grails-core/actions/runs/32344453542/job/96350842997?pr=16174">CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot (shard 1)</a>)</summary>
   
   ```
   com.mongodb.MongoTimeoutException: Timed out while waiting for a server that matches WritableServerSelector. Client view of cluster state is {type=UNKNOWN, servers=[{address=localhost:32828, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused}}]
   	at com.mongodb.internal.connection.BaseCluster.logAndThrowTimeoutException(BaseCluster.java:431)
   	at com.mongodb.internal.connection.BaseCluster.lambda$selectServer$0(BaseCluster.java:166)
   	at com.mongodb.internal.time.Timeout.lambda$onExistsAndExpired$16(Timeout.java:238)
   	at com.mongodb.internal.time.Timeout.lambda$run$10(Timeout.java:203)
   	at com.mongodb.internal.time.TimePoint.checkedCall(TimePoint.java:100)
   	at com.mongodb.internal.time.Timeout.call(Timeout.java:176)
   	at com.mongodb.internal.time.Timeout.run(Timeout.java:196)
   	at com.mongodb.internal.time.Timeout.onExistsAndExpired(Timeout.java:235)
   	at com.mongodb.internal.time.Timeout.onExpired(Timeout.java:228)
   	at com.mongodb.internal.connection.BaseCluster.selectServer(BaseCluster.java:165)
   	at com.mongodb.internal.connection.SingleServerCluster.selectServer(SingleServerCluster.java:47)
   	at com.mongodb.internal.binding.ClusterBinding.getWriteConnectionSource(ClusterBinding.java:91)
   	at com.mongodb.client.internal.ClientSessionBinding.getConnectionSource(ClientSessionBinding.java:109)
   	at com.mongodb.client.internal.ClientSessionBinding.getWriteConnectionSource(ClientSessionBinding.java:102)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:169)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSourceAndConnection(SyncOperationHelper.java:138)
   	at com.mongodb.internal.operation.SyncOperationHelper.withConnection(SyncOperationHelper.java:117)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:73)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:48)
   	at com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl.execute(MongoClusterImpl.java:472)
   	at com.mongodb.client.internal.MongoDatabaseImpl.executeDrop(MongoDatabaseImpl.java:230)
   	at com.mongodb.client.internal.MongoDatabaseImpl.drop(MongoDatabaseImpl.java:220)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup_closure1(MongoTransactionSpec.groovy:58)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession_closure23(GormStaticApi.groovy:767)
   	at org.grails.datastore.mapping.core.DatastoreUtils.executeWithNewSession(DatastoreUtils.java:371)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession(GormStaticApi.groovy:766)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.withNewSession(GormEntity.groovy:1157)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup(MongoTransactionSpec.groovy:57)
   ```
   
   </details>
   <details>
   
   <summary><strong>MongoTransactionSpec > test a rolled back transaction discards all writes on the server</strong> (:grails-data-mongodb-core:test in <a href="https://github.com/apache/grails-core/actions/runs/32344453542/job/96350842997?pr=16174">CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot (shard 1)</a>)</summary>
   
   ```
   com.mongodb.MongoTimeoutException: Timed out while waiting for a server that matches WritableServerSelector. Client view of cluster state is {type=UNKNOWN, servers=[{address=localhost:32828, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused}}]
   	at com.mongodb.internal.connection.BaseCluster.logAndThrowTimeoutException(BaseCluster.java:431)
   	at com.mongodb.internal.connection.BaseCluster.lambda$selectServer$0(BaseCluster.java:166)
   	at com.mongodb.internal.time.Timeout.lambda$onExistsAndExpired$16(Timeout.java:238)
   	at com.mongodb.internal.time.Timeout.lambda$run$10(Timeout.java:203)
   	at com.mongodb.internal.time.TimePoint.checkedCall(TimePoint.java:100)
   	at com.mongodb.internal.time.Timeout.call(Timeout.java:176)
   	at com.mongodb.internal.time.Timeout.run(Timeout.java:196)
   	at com.mongodb.internal.time.Timeout.onExistsAndExpired(Timeout.java:235)
   	at com.mongodb.internal.time.Timeout.onExpired(Timeout.java:228)
   	at com.mongodb.internal.connection.BaseCluster.selectServer(BaseCluster.java:165)
   	at com.mongodb.internal.connection.SingleServerCluster.selectServer(SingleServerCluster.java:47)
   	at com.mongodb.internal.binding.ClusterBinding.getWriteConnectionSource(ClusterBinding.java:91)
   	at com.mongodb.client.internal.ClientSessionBinding.getConnectionSource(ClientSessionBinding.java:109)
   	at com.mongodb.client.internal.ClientSessionBinding.getWriteConnectionSource(ClientSessionBinding.java:102)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:169)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSourceAndConnection(SyncOperationHelper.java:138)
   	at com.mongodb.internal.operation.SyncOperationHelper.withConnection(SyncOperationHelper.java:117)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:73)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:48)
   	at com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl.execute(MongoClusterImpl.java:472)
   	at com.mongodb.client.internal.MongoDatabaseImpl.executeDrop(MongoDatabaseImpl.java:230)
   	at com.mongodb.client.internal.MongoDatabaseImpl.drop(MongoDatabaseImpl.java:220)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup_closure1(MongoTransactionSpec.groovy:58)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession_closure23(GormStaticApi.groovy:767)
   	at org.grails.datastore.mapping.core.DatastoreUtils.executeWithNewSession(DatastoreUtils.java:371)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession(GormStaticApi.groovy:766)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.withNewSession(GormEntity.groovy:1157)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup(MongoTransactionSpec.groovy:57)
   ```
   
   </details>
   <details>
   
   <summary><strong>MongoTransactionSpec > test read-your-writes within an active transaction</strong> (:grails-data-mongodb-core:test in <a href="https://github.com/apache/grails-core/actions/runs/32344453542/job/96350842997?pr=16174">CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot (shard 1)</a>)</summary>
   
   ```
   com.mongodb.MongoTimeoutException: Timed out while waiting for a server that matches WritableServerSelector. Client view of cluster state is {type=UNKNOWN, servers=[{address=localhost:32828, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused}}]
   	at com.mongodb.internal.connection.BaseCluster.logAndThrowTimeoutException(BaseCluster.java:431)
   	at com.mongodb.internal.connection.BaseCluster.lambda$selectServer$0(BaseCluster.java:166)
   	at com.mongodb.internal.time.Timeout.lambda$onExistsAndExpired$16(Timeout.java:238)
   	at com.mongodb.internal.time.Timeout.lambda$run$10(Timeout.java:203)
   	at com.mongodb.internal.time.TimePoint.checkedCall(TimePoint.java:100)
   	at com.mongodb.internal.time.Timeout.call(Timeout.java:176)
   	at com.mongodb.internal.time.Timeout.run(Timeout.java:196)
   	at com.mongodb.internal.time.Timeout.onExistsAndExpired(Timeout.java:235)
   	at com.mongodb.internal.time.Timeout.onExpired(Timeout.java:228)
   	at com.mongodb.internal.connection.BaseCluster.selectServer(BaseCluster.java:165)
   	at com.mongodb.internal.connection.SingleServerCluster.selectServer(SingleServerCluster.java:47)
   	at com.mongodb.internal.binding.ClusterBinding.getWriteConnectionSource(ClusterBinding.java:91)
   	at com.mongodb.client.internal.ClientSessionBinding.getConnectionSource(ClientSessionBinding.java:109)
   	at com.mongodb.client.internal.ClientSessionBinding.getWriteConnectionSource(ClientSessionBinding.java:102)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:169)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSourceAndConnection(SyncOperationHelper.java:138)
   	at com.mongodb.internal.operation.SyncOperationHelper.withConnection(SyncOperationHelper.java:117)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:73)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:48)
   	at com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl.execute(MongoClusterImpl.java:472)
   	at com.mongodb.client.internal.MongoDatabaseImpl.executeDrop(MongoDatabaseImpl.java:230)
   	at com.mongodb.client.internal.MongoDatabaseImpl.drop(MongoDatabaseImpl.java:220)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup_closure1(MongoTransactionSpec.groovy:58)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession_closure23(GormStaticApi.groovy:767)
   	at org.grails.datastore.mapping.core.DatastoreUtils.executeWithNewSession(DatastoreUtils.java:371)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession(GormStaticApi.groovy:766)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.withNewSession(GormEntity.groovy:1157)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup(MongoTransactionSpec.groovy:57)
   ```
   
   </details>
   <details>
   
   <summary><strong>MongoTransactionSpec > test writes across multiple collections roll back together</strong> (:grails-data-mongodb-core:test in <a href="https://github.com/apache/grails-core/actions/runs/32344453542/job/96350842997?pr=16174">CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot (shard 1)</a>)</summary>
   
   ```
   com.mongodb.MongoTimeoutException: Timed out while waiting for a server that matches WritableServerSelector. Client view of cluster state is {type=UNKNOWN, servers=[{address=localhost:32828, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused}}]
   	at com.mongodb.internal.connection.BaseCluster.logAndThrowTimeoutException(BaseCluster.java:431)
   	at com.mongodb.internal.connection.BaseCluster.lambda$selectServer$0(BaseCluster.java:166)
   	at com.mongodb.internal.time.Timeout.lambda$onExistsAndExpired$16(Timeout.java:238)
   	at com.mongodb.internal.time.Timeout.lambda$run$10(Timeout.java:203)
   	at com.mongodb.internal.time.TimePoint.checkedCall(TimePoint.java:100)
   	at com.mongodb.internal.time.Timeout.call(Timeout.java:176)
   	at com.mongodb.internal.time.Timeout.run(Timeout.java:196)
   	at com.mongodb.internal.time.Timeout.onExistsAndExpired(Timeout.java:235)
   	at com.mongodb.internal.time.Timeout.onExpired(Timeout.java:228)
   	at com.mongodb.internal.connection.BaseCluster.selectServer(BaseCluster.java:165)
   	at com.mongodb.internal.connection.SingleServerCluster.selectServer(SingleServerCluster.java:47)
   	at com.mongodb.internal.binding.ClusterBinding.getWriteConnectionSource(ClusterBinding.java:91)
   	at com.mongodb.client.internal.ClientSessionBinding.getConnectionSource(ClientSessionBinding.java:109)
   	at com.mongodb.client.internal.ClientSessionBinding.getWriteConnectionSource(ClientSessionBinding.java:102)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:169)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSourceAndConnection(SyncOperationHelper.java:138)
   	at com.mongodb.internal.operation.SyncOperationHelper.withConnection(SyncOperationHelper.java:117)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:73)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:48)
   	at com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl.execute(MongoClusterImpl.java:472)
   	at com.mongodb.client.internal.MongoDatabaseImpl.executeDrop(MongoDatabaseImpl.java:230)
   	at com.mongodb.client.internal.MongoDatabaseImpl.drop(MongoDatabaseImpl.java:220)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup_closure1(MongoTransactionSpec.groovy:58)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession_closure23(GormStaticApi.groovy:767)
   	at org.grails.datastore.mapping.core.DatastoreUtils.executeWithNewSession(DatastoreUtils.java:371)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession(GormStaticApi.groovy:766)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.withNewSession(GormEntity.groovy:1157)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup(MongoTransactionSpec.groovy:57)
   ```
   
   </details>
   
   ### Rerun Controls
   > [!NOTE]
   > Checks are currently running using the configuration below.
   
   Select tests to mute in this pull request:
   
   🔲 MongoTransactionSpec > test a REQUIRES\_NEW inner transaction commits independently of a rolled back outer transaction <!-- uniqueId=[engine:spock]/[spec:org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec]/[feature:$spock_feature_1_6] -->
   🔲 MongoTransactionSpec > test a committed transaction persists all writes atomically <!-- uniqueId=[engine:spock]/[spec:org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec]/[feature:$spock_feature_1_1] -->
   🔲 MongoTransactionSpec > test a findOneAndDelete via the MongoEntity API participates in the transaction <!-- uniqueId=[engine:spock]/[spec:org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec]/[feature:$spock_feature_1_5] -->
   🔲 MongoTransactionSpec > test a rolled back transaction discards all writes on the server <!-- uniqueId=[engine:spock]/[spec:org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec]/[feature:$spock_feature_1_2] -->
   🔲 MongoTransactionSpec > test read-your-writes within an active transaction <!-- uniqueId=[engine:spock]/[spec:org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec]/[feature:$spock_feature_1_4] -->
   🔲 MongoTransactionSpec > test writes across multiple collections roll back together <!-- uniqueId=[engine:spock]/[spec:org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec]/[feature:$spock_feature_1_3] -->
   
   Reuse successful test results:
   
   🔲 ♻️ Only rerun the tests that failed or were muted before
   
   Click the checkbox to trigger a rerun:
   
   🔲 **Rerun jobs**
   
   ---
   _Learn more about TestLens at [testlens.app/docs](https://testlens.app/docs/features/pr-comment/)._
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] fix(cas): repair proxy receptor and single sign-out, add integration tests [grails-core]

Posted by "testlens-app[bot] (via GitHub)" <gi...@apache.org>.
testlens-app[bot] commented on PR #16174:
URL: https://github.com/apache/grails-core/pull/16174#issuecomment-5346903275

   ## ✅ All tests passed ✅
   
   🏷️ Commit: 06d8801b2c225103c27cff25c9ea3b65b9eacec2
   ▶️ Tests:  67180 executed
   ⚪️ Checks: 83/83 completed
   
   ---
   _Learn more about TestLens at [testlens.app/docs](https://testlens.app/docs/features/pr-comment/)._
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] fix(cas): repair proxy receptor and single sign-out, add integration tests [grails-core]

Posted by "sbglasius (via GitHub)" <gi...@apache.org>.
sbglasius commented on code in PR #16174:
URL: https://github.com/apache/grails-core/pull/16174#discussion_r3824024972


##########
.github/workflows/gradle.yml:
##########
@@ -567,6 +567,46 @@ jobs:
           --stacktrace
           -DTESTCONFIG=${{ matrix.test-config }}
           -PskipCodeStyle
+  casSecurityConfig:
+    name: "Spring Security CAS Functional Tests"
+    if: ${{ !contains(github.event.head_commit.message, '[skip tests]') }}
+    strategy:
+      fail-fast: false
+      matrix:
+        # The CAS test app authenticates against a real Apereo CAS server started via Testcontainers.
+        # 'cas' leaves the proxy settings unset and is the default, so it is also covered by the main
+        # security job; 'casProxy' exercises the proxy-granting-ticket callback; 'casNoSingleSignout'
+        # asserts the shipped default, where single signout is not enabled.
+        # TESTCONFIG is applied at app startup, so every config must be its own run.
+        # Carried over from the standalone grails-spring-security CI convention - add, don't remove.
+        test-config: [ 'cas', 'casProxy', 'casNoSingleSignout' ]

Review Comment:
   Yes, and actually three times rather than twice. The specs defaulted to the cas configuration when `TESTCONFIG` was unset, so the security job picked it up in its Java 21 entry and one of its Java 25 shards, plus the run here.
   
   Fixed in e4a3e8e by requiring a configuration to be selected, which is what the core functional-test-app specs already do — every one of them is gated on `TESTCONFIG`, so a run without it exercises none of them. The security job now skips all CAS specs (and no longer pulls the 882 MB CAS image at all), and each configuration runs exactly once here. `bootRun` still needs no `TESTCONFIG`.
   
   One trade-off worth flagging: `:grails-test-examples-spring-security-cas-test1:check` without `-DTESTCONFIG` now reports no CAS tests. That matches the core app's behaviour and is called out in the `README`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] fix(cas): repair proxy receptor and single sign-out, add integration tests [grails-core]

Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on code in PR #16174:
URL: https://github.com/apache/grails-core/pull/16174#discussion_r3824707223


##########
grails-spring-security/cas/plugin/src/main/groovy/grails/plugin/springsecurity/cas/SpringSecurityCasGrailsPlugin.groovy:
##########
@@ -35,25 +36,28 @@ import org.springframework.security.cas.authentication.CasAuthenticationProvider
 import org.springframework.security.cas.authentication.NullStatelessTicketCache
 import org.springframework.security.cas.web.CasAuthenticationEntryPoint
 import org.springframework.security.cas.web.CasAuthenticationFilter
+import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy
 
+import grails.plugin.springsecurity.BeanTypeResolver
 import grails.plugin.springsecurity.SecurityFilterPosition
 import grails.plugin.springsecurity.SpringSecurityUtils
 import grails.plugins.Plugin
 
+@Slf4j
 @CompileStatic
 class SpringSecurityCasGrailsPlugin extends Plugin {
 
-    String grailsVersion = '7.0.0 > *'
+    String grailsVersion = '8.0.0-SNAPSHOT > *'
     String author = 'Burt Beckwith'

Review Comment:
   We should go ahead and remove the author here since we're updating the code and ASF policy is to no longer have that.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] fix(cas): repair proxy receptor and single sign-out, add integration tests [grails-core]

Posted by "sbglasius (via GitHub)" <gi...@apache.org>.
sbglasius commented on code in PR #16174:
URL: https://github.com/apache/grails-core/pull/16174#discussion_r3819486894


##########
grails-spring-security/cas/plugin/src/main/groovy/grails/plugin/springsecurity/cas/SpringSecurityCasGrailsPlugin.groovy:
##########
@@ -87,10 +91,30 @@ class SpringSecurityCasGrailsPlugin extends Plugin {
 
             if (conf.cas.useSingleSignout) {
 
-                // session fixation prevention breaks single signout because
-                // the service ticket is mapped to the session id which changes
+                // Session fixation prevention breaks single signout because the service ticket is
+                // mapped to the session id, which changes when the session is replaced on login.
+                // Disabling it is a security trade-off the application has opted into, so say so.
+                String message = '''
+    WARNING: cas.useSingleSignout is enabled, so session fixation prevention has been disabled.
+    CAS maps the service ticket to the HTTP session id, and a logout request cannot be matched to a
+    session that was replaced when the user authenticated. Set cas.useSingleSignout to false to keep
+    session fixation prevention and handle logout in the application instead.
+    '''
+                println message

Review Comment:
   Since Grails Spring Security uses `println` I have removed `log.warn`



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] fix(cas): repair proxy receptor and single sign-out, add integration tests [grails-core]

Posted by "sbglasius (via GitHub)" <gi...@apache.org>.
sbglasius merged PR #16174:
URL: https://github.com/apache/grails-core/pull/16174


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] fix(cas): repair proxy receptor and single sign-out, add integration tests [grails-core]

Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on code in PR #16174:
URL: https://github.com/apache/grails-core/pull/16174#discussion_r3824861585


##########
grails-doc/src/en/guide/security/securityPlugins/springSecurity/cas/usage.adoc:
##########
@@ -25,7 +25,7 @@ under the License.
 Configuring your CAS server is beyond the scope of this document. There are many different approaches and this will most likely be done by IT staff. It's assumed here that you already have a running CAS server.
 ====
 
-https://www.jasig.org/cas[CAS] is a popular single sign-on implementation. It's open source and has an Apache-like license, and is easy to get started with but is also highly configurable. In addition it has clients written in Java, .Net, PHP, Perl, and other languages.
+https://apereo.github.io/cas[CAS] is a popular single sign-on implementation. It's open source and has an Apache-like license, and is easy to get started with but is also highly configurable. In addition it has clients written in Java, .Net, PHP, Perl, and other languages.

Review Comment:
   Not this line, but this file: the `=== Single Signout` section below (line 62) still says "Single signout is enabled by default" and only documents how to disable it. Since this PR flips `cas.useSingleSignout` to opt-in, that section needs updating to match the new default (off, enable explicitly).



##########
grails-test-examples/spring-security/cas/README.md:
##########
@@ -14,21 +14,77 @@ See the License for the specific language governing permissions and
 limitations under the License.
 -->
 
-This is a CAS-enabled test application.  To run it successfully, a CAS
-server is required.  The URL for the CAS server is configured in the
-[application.groovy](test1/grails-app/conf/application.groovy)
-file.  Setting up a CAS server is out of the scope of this document, but
-good places to start are [Apereo CAS GitHub](https://github.com/apereo/cas)
-and the [CAS Initializr](https://getcas.apereo.org/ui) service.
+This is a CAS-enabled test application. It no longer needs a hand-run CAS server: an
+[Apereo CAS](https://github.com/apereo/cas) server is started in a container by
+[CasContainerHolder](test1/src/main/groovy/grails/plugin/springsecurity/cas/test/CasContainerHolder.groovy),
+and [CasTestEnvironmentPostProcessor](test1/src/main/groovy/grails/plugin/springsecurity/cas/test/CasTestEnvironmentPostProcessor.groovy)
+points the CAS plugin at it before the application context is built. Docker (or a compatible
+container runtime) is therefore required to run or test this application.
 
-The test application can be run with:
+## Running the tests
 
-`./gradlew :testapp-spring-security-cas-test1:bootRun`
+The application is exercised under three configurations, selected with the `TESTCONFIG` system
+property. Each has to be its own run, because the configuration is applied at application startup.
+
+The specs only run when one of these is selected, so a build that covers every Spring Security
+example does not repeat a configuration that the dedicated per-configuration CI job already runs.
+Running `check` without `-DTESTCONFIG` therefore reports no CAS tests.
+
+| `TESTCONFIG` | Configuration | Covered by |
+|---|---|---|
+| `cas` (default) | `proxyCallbackUrl` and `proxyReceptorUrl` unset, single signout enabled | `CasLoginSpec`, `CasNoProxyReceptorSpec`, `CasSingleSignOutSpec` |
+| `casProxy` | both proxy settings configured, single signout enabled | `CasLoginSpec`, `CasProxyTicketSpec`, `CasSingleSignOutSpec` |
+| `casNoSingleSignout` | `cas.useSingleSignout` left at its default | `CasLoginSpec`, `CasNoProxyReceptorSpec`, `CasNoSingleSignOutSpec` |
+
+```
+./gradlew :grails-test-examples-spring-security-cas-test1:check -DTESTCONFIG=cas
+./gradlew :grails-test-examples-spring-security-cas-test1:check -DTESTCONFIG=casProxy
+./gradlew :grails-test-examples-spring-security-cas-test1:check -DTESTCONFIG=casNoSingleSignout
+```
+
+`cas.useSingleSignout` is opt-in as of Grails 8. The app enables it for the first two configurations
+so the single signout filter is exercised; enabling it disables session fixation prevention, and the
+plugin warns about that at startup.
+
+The CAS image is pinned to a known-good version and can be overridden:
+
+```
+./gradlew :grails-test-examples-spring-security-cas-test1:check -PcasContainerVersion=7.3.6
+```
+
+The specs skip themselves when no Docker daemon is available.
+
+## Running the application
+
+```
+./gradlew :grails-test-examples-spring-security-cas-test1:bootRun
+```
 
 The test application URLs are:
 * [http://localhost:8081/secure/admins](http://localhost:8081/secure/admins)
 * [http://localhost:8081/secure/users](http://localhost:8081/secure/users)
+* [http://localhost:8081/secure/proxyStatus](http://localhost:8081/secure/proxyStatus) — asks CAS for a proxy ticket

Review Comment:
   Under `bootRun`, `TESTCONFIG` is unset, so `CasTestEnvironmentPostProcessor` never configures `proxyReceptorUrl`/`proxyCallbackUrl` and this endpoint can only ever return `NO_PROXY_TICKET`. A developer following this section will conclude the proxy support is broken. Worth either noting that limitation here or documenting how to start `bootRun` with the proxy configuration so the endpoint can actually be exercised.



##########
grails-test-examples/spring-security/cas/test1/src/integration-test/groovy/grails/plugin/springsecurity/cas/test/CasSingleSignOutSpec.groovy:
##########
@@ -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
+ *
+ *    https://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 grails.plugin.springsecurity.cas.test
+
+import spock.lang.IgnoreIf
+
+import java.net.http.HttpResponse
+
+/**
+ * Covers single sign-out, which the plugin enables by default via {@code cas.useSingleSignout} by

Review Comment:
   "which the plugin enables by default" is stale — this PR makes `cas.useSingleSignout` opt-in (default `false`), which is exactly what `CasNoSingleSignOutSpec` asserts. Suggest rewording to something like "which the app opts into via `cas.useSingleSignout`".



##########
grails-test-examples/spring-security/cas/test1/src/integration-test/groovy/grails/plugin/springsecurity/cas/test/CasLoginSpec.groovy:
##########
@@ -0,0 +1,92 @@
+/*
+ *  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
+ *
+ *    https://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 grails.plugin.springsecurity.cas.test
+
+
+import spock.lang.IgnoreIf
+
+import java.net.http.HttpResponse
+
+/**
+ * Covers the CAS handshake the plugin exists to perform: redirect to CAS, service ticket back,
+ * ticket validated against the CAS server, and the resulting authentication carrying the roles
+ * looked up in GORM.
+ */
+@IgnoreIf({ !CasTestConfig.configured })
+class CasLoginSpec extends AbstractCasSpec {
+
+    void 'an unauthenticated request is redirected to the CAS login page for this service'() {
+        when:
+        HttpResponse<String> response = get(appClient, "${appBaseUrl}/secure/users")
+
+        then:
+        response.statusCode() == 302
+
+        and: 'the redirect targets the CAS server the container is running'
+        location(response).startsWith("${casBaseUrl}/login")
+
+        and: 'it asks CAS to send the ticket back to this application'
+        location(response).contains('service=')
+        location(response).contains(URLEncoder.encode(CasTestConfig.serviceUrl(serverPort), 'UTF-8'))
+    }
+
+    void 'a user authenticated at CAS reaches a ROLE_USER action'() {
+        when:
+        HttpResponse<String> response = login('/secure/users', 'user', 'user')
+
+        then:
+        response.statusCode() == 200
+        response.body().contains('Logged in with ROLE_USER')
+    }
+
+    void 'an admin authenticated at CAS reaches a ROLE_ADMIN action'() {
+        when:
+        HttpResponse<String> response = login('/secure/admins', 'admin', 'admin')
+
+        then:
+        response.statusCode() == 200
+        response.body().contains('Logged in with ROLE_ADMIN')
+    }
+
+    void 'a user without the role is denied a ROLE_ADMIN action'() {
+        given:
+        login('/secure/users', 'user', 'user')
+
+        when:
+        HttpResponse<String> response = followRedirects(get(appClient, "${appBaseUrl}/secure/admins"))
+
+        then: 'access is refused rather than granted'
+        response.statusCode() == 403 || !response.body().contains('Logged in with ROLE_ADMIN')
+    }
+
+    void 'bad credentials do not authenticate'() {
+        when:
+        HttpResponse<String> challenge = get(appClient, "${appBaseUrl}/secure/users")
+        String loginUrl = location(challenge)
+        HttpResponse<String> form = get(casClient, loginUrl)
+        String execution = form.body().find(/name="execution"\s+value="([^"]+)"/) { full, token -> token }
+        HttpResponse<String> submitted = postForm(casClient, loginUrl,

Review Comment:
   This re-implements the execution-token extraction inline, duplicating `AbstractCasSpec.extractExecution` but dropping the null guard the other paths get. If a future `casContainerVersion` renders the login form with different attribute order or quoting, `execution` is null here and `postForm` fails with an opaque NPE from `URLEncoder.encode(null)` inside the when-block, instead of the clear missing-token assertion `authenticateAtCas` produces. Reuse the base-class extraction and guard.



##########
grails-test-examples/spring-security/cas/test1/grails-app/controllers/grails/plugin/springsecurity/cas/test/SecureController.groovy:
##########
@@ -0,0 +1,58 @@
+/*
+ *  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
+ *
+ *    https://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 grails.plugin.springsecurity.cas.test
+
+import grails.plugin.springsecurity.annotation.Secured
+import org.apereo.cas.client.authentication.AttributePrincipal
+import org.springframework.security.cas.authentication.CasAuthenticationToken
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.context.SecurityContextHolder
+
+class SecureController {
+

Review Comment:
   The body of this file is indented with tabs; `.editorconfig` sets `indent_style = space` (4 spaces) for Groovy sources. Since git treats this as a new file (the old controller path became `resources.groovy` in rename detection), it should come in with the standard indentation.



##########
grails-doc/src/en/guide/upgrading/upgrading80x.adoc:
##########
@@ -2632,3 +2632,36 @@ used to apply to its own message source.
 
 Adding or removing a base name now needs a restart, because Spring Boot reads the configured base names
 once when it builds the message source.
+
+==== 46. CAS Single Sign-Out Is Opt-In

Review Comment:
   Sections 46 (Startup Banner, line 2481) and 47 (Message Bundles, line 2517) already exist above, so this should be `==== 48.` to keep the numbering sequential.



##########
grails-spring-security/cas/plugin/src/main/groovy/grails/plugin/springsecurity/cas/SpringSecurityCasGrailsPlugin.groovy:
##########
@@ -87,10 +91,29 @@ class SpringSecurityCasGrailsPlugin extends Plugin {
 
             if (conf.cas.useSingleSignout) {
 
-                // session fixation prevention breaks single signout because
-                // the service ticket is mapped to the session id which changes
+                // Session fixation prevention breaks single signout because the service ticket is
+                // mapped to the session id, which changes when the session is replaced on login.
+                // Disabling it is a security trade-off the application has opted into, so say so.
+                String message = '''
+    WARNING: cas.useSingleSignout is enabled, so session fixation prevention has been disabled.
+    CAS maps the service ticket to the HTTP session id, and a logout request cannot be matched to a
+    session that was replaced when the user authenticated. Set cas.useSingleSignout to false to keep
+    session fixation prevention and handle logout in the application instead.
+    '''
+                println message

Review Comment:
   With `log.warn` removed, the `@Slf4j` annotation added above is now unused. Two options: drop the annotation, or keep both outputs like the core plugin does (`SpringSecurityCoreGrailsPlugin` pairs `println` with `log.warn` for its warnings). The docs added in this PR say the plugin "logs a warning at startup", and an app that captures logs but not stdout will never see a `println`, so pairing them seems preferable. Also, the line just below is whitespace-only and should be trimmed.



##########
grails-test-examples/spring-security/cas/test1/src/integration-test/groovy/grails/plugin/springsecurity/cas/test/AbstractCasSpec.groovy:
##########
@@ -0,0 +1,178 @@
+/*
+ *  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
+ *
+ *    https://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 grails.plugin.springsecurity.cas.test
+
+import grails.testing.mixin.integration.Integration
+import spock.lang.Requires
+import spock.lang.Specification
+
+import java.net.http.HttpClient
+import java.net.http.HttpRequest
+import java.net.http.HttpResponse
+import java.time.Duration
+
+/**
+ * Drives the CAS protocol by hand against the containerised CAS server.
+ *
+ * <p>The app and CAS are both reached on {@code localhost}, and cookies ignore ports, so a single
+ * cookie jar would send the app's session cookie to CAS and vice versa. Each side therefore gets
+ * its own client, and redirects are followed explicitly so the right one is used for each hop.</p>
+ */
+@Integration
+@Requires({ isDockerAvailable() })
+abstract class AbstractCasSpec extends Specification {
+
+    HttpClient appClient
+    HttpClient casClient
+
+    /**
+     * A cookie-less client for the calls CAS makes to the application on its own connection, such
+     * as the back-channel logout request. Using the authenticated client instead would hide which
+     * filter acted: an unconsumed POST to the CAS login path fails authentication and clears the
+     * session by itself, which looks the same from outside as single signout working.
+     */
+    HttpClient backChannelClient
+
+    /** The service ticket CAS issued during the most recent {@link #login} call. */
+    String lastServiceTicket
+
+    void setup() {
+        appClient = newClient()
+        casClient = newClient()
+        backChannelClient = newClient()
+    }
+
+    /**
+     * Mirrors the probe used by the hibernate7 specs. Checking the socket avoids the macOS failure
+     * mode where asking Testcontainers for a client throws when the daemon API version differs.
+     */
+    static boolean isDockerAvailable() {
+        List<String> candidates = [
+                System.getProperty('user.home') + '/.docker/run/docker.sock',
+                '/var/run/docker.sock',
+                System.getenv('DOCKER_HOST') ?: ''
+        ]
+        candidates.any { it && new File(it).exists() }
+    }
+
+    String getAppBaseUrl() {
+        "http://localhost:${serverPort}"
+    }
+
+    String getCasBaseUrl() {
+        CasContainerHolder.serverUrlPrefix
+    }
+
+    /** CAS redirects to the container-visible host name; the test client has to use localhost. */
+    static String toLocalUrl(String url) {
+        url.replace(CasTestConfig.CONTAINER_VISIBLE_HOST, 'localhost')
+    }
+
+    HttpResponse<String> get(HttpClient client, String url) {
+        client.send(HttpRequest.newBuilder(URI.create(url)).GET().build(),
+                HttpResponse.BodyHandlers.ofString())
+    }
+
+    HttpResponse<String> postForm(HttpClient client, String url, Map<String, String> form) {
+        String body = form.collect { k, v -> "${encode(k)}=${encode(v)}" }.join('&')
+        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
+                .header('Content-Type', 'application/x-www-form-urlencoded')
+                .POST(HttpRequest.BodyPublishers.ofString(body))
+                .build()
+        client.send(request, HttpResponse.BodyHandlers.ofString())
+    }
+
+    static String location(HttpResponse<?> response) {
+        response.headers().firstValue('Location').orElse(null)
+    }
+
+    /**
+     * Authenticates at CAS and returns the service ticket URL it redirects back to, already
+     * rewritten to localhost.
+     */
+    String authenticateAtCas(String loginUrl, String username, String password) {
+        HttpResponse<String> form = get(casClient, loginUrl)
+        assert form.statusCode() == 200
+        String execution = extractExecution(form.body())
+        assert execution, 'CAS login form did not contain an execution token'
+
+        HttpResponse<String> submitted = postForm(casClient, loginUrl,
+                [username: username, password: password, execution: execution, _eventId: 'submit'])
+        assert submitted.statusCode() == 302,
+                "expected CAS to redirect after login but got ${submitted.statusCode()}"
+        toLocalUrl(location(submitted))
+    }
+
+    /** Full login: hit a secured URL, authenticate at CAS, and follow the ticket back to the app. */
+    HttpResponse<String> login(String securedPath, String username, String password) {
+        HttpResponse<String> challenge = get(appClient, appBaseUrl + securedPath)
+        assert challenge.statusCode() == 302,
+                "expected a redirect to CAS but got ${challenge.statusCode()}"
+        String ticketUrl = authenticateAtCas(location(challenge), username, password)
+        lastServiceTicket = extractTicket(ticketUrl)
+        assert lastServiceTicket, "CAS redirect carried no service ticket: ${ticketUrl}"
+        followRedirects(get(appClient, ticketUrl))
+    }
+
+    HttpResponse<String> followRedirects(HttpResponse<String> response, int limit = 5) {
+        HttpResponse<String> current = response
+        for (int i = 0; i < limit && current.statusCode() in [301, 302, 303, 307, 308]; i++) {
+            current = get(appClient, absolute(location(current)))
+        }
+        current
+    }
+
+    String absolute(String location) {
+        String local = toLocalUrl(location)
+        local.startsWith('http') ? local : appBaseUrl + local
+    }
+
+    /** The message CAS sends to a service on back-channel logout. */
+    static String logoutRequest(String serviceTicket) {
+        """<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" \
+ID="LR-1-${System.nanoTime()}" Version="2.0" IssueInstant="2026-01-01T00:00:00Z">\
+<saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">@NOT_USED@</saml:NameID>\
+<samlp:SessionIndex>${serviceTicket}</samlp:SessionIndex>\
+</samlp:LogoutRequest>"""
+    }
+
+    static String extractTicket(String url) {
+        def matcher = url =~ /[?&]ticket=([^&]+)/
+        matcher.find() ? matcher.group(1) : null
+    }
+
+    private static String extractExecution(String html) {
+        def matcher = html =~ /name="execution"\s+value="([^"]+)"/
+        matcher.find() ? matcher.group(1) : null
+    }
+
+    private static String encode(String value) {
+        URLEncoder.encode(value, 'UTF-8')
+    }
+
+    private static HttpClient newClient() {
+        CookieManager cookieManager = new CookieManager(null, CookiePolicy.ACCEPT_ALL)
+        HttpClient.newBuilder()
+                .cookieHandler(cookieManager)
+                .followRedirects(HttpClient.Redirect.NEVER)
+                .connectTimeout(Duration.ofSeconds(30))
+                .build()

Review Comment:
   Only a connect timeout is set here — `client.send()` has no request timeout and none of the specs use `@Timeout`, so if the CAS container accepts the TCP connection but never responds (mid-startup stall, OOM), `get()`/`postForm()` block indefinitely and the CI matrix job hangs until the runner's job-level kill. Consider adding `.timeout(Duration.ofSeconds(...))` to the `HttpRequest` builders (or a Spock `@Timeout`) so a wedged container fails within minutes instead.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] fix(cas): repair proxy receptor and single sign-out, add integration tests [grails-core]

Posted by "sbglasius (via GitHub)" <gi...@apache.org>.
sbglasius commented on code in PR #16174:
URL: https://github.com/apache/grails-core/pull/16174#discussion_r3824012470


##########
grails-test-examples/spring-security/cas/README.md:
##########
@@ -16,19 +16,73 @@ limitations under the License.
 
 This is a CAS-enabled test application.  To run it successfully, a CAS
 server is required.  The URL for the CAS server is configured in the
-[application.groovy](test1/grails-app/conf/application.groovy)
-file.  Setting up a CAS server is out of the scope of this document, but
-good places to start are [Apereo CAS GitHub](https://github.com/apereo/cas)
-and the [CAS Initializr](https://getcas.apereo.org/ui) service.
+This is a CAS-enabled test application. It no longer needs a hand-run CAS server: an

Review Comment:
   Good catch — fixed in e4a3e8e. The rewrite extracted the license header by line count and stopped two lines short, so the opening of the old text ("To run it successfully, a CAS server is required...") stayed above the new paragraph. Removed. Also corrected "two configurations" to three while I was in there, since `casNoSingleSignout` was added later.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] fix(cas): repair proxy receptor and single sign-out, add integration tests [grails-core]

Posted by "testlens-app[bot] (via GitHub)" <gi...@apache.org>.
testlens-app[bot] commented on PR #16174:
URL: https://github.com/apache/grails-core/pull/16174#issuecomment-5360095007

   ## 🚨 TestLens detected 9 failed tests 🚨
   
   Here is what you can do:
   
   1) Inspect the test failures carefully.
   2) If you are convinced that some of the tests are flaky, you can mute them below.
   3) Finally, trigger a rerun by checking the rerun checkbox.
   
   ### Test Summary
   
   #### [CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot \(shard 1\)](https://github.com/apache/grails-core/actions/runs/32399627504/job/96527899962?pr=16174) > :grails-data-mongodb-core:test
   
   | Test | Runs | Flakiness |
   |---|---|--:|
   | MongoTransactionSpec > test a REQUIRES\_NEW inner transaction commits independently of a rolled back outer transaction | ❌ | [1%&nbsp;🟡](https://github.com/apache/grails-core/issues/16030) |
   | MongoTransactionSpec > test a committed transaction persists all writes atomically | ❌ | [1%&nbsp;🟡](https://github.com/apache/grails-core/issues/16030) |
   | MongoTransactionSpec > test a findOneAndDelete via the MongoEntity API participates in the transaction | ❌ | [1%&nbsp;🟡](https://github.com/apache/grails-core/issues/16030) |
   | MongoTransactionSpec > test a per-transaction timeout is rejected rather than silently ignored | ❌ | [1%&nbsp;🟡](https://github.com/apache/grails-core/issues/16030) |
   | MongoTransactionSpec > test a rolled back transaction discards a native Long id entity \(id generation is non-transactional\) | ❌ | [1%&nbsp;🟡](https://github.com/apache/grails-core/issues/16030) |
   | MongoTransactionSpec > test a rolled back transaction discards all writes on the server | ❌ | [1%&nbsp;🟡](https://github.com/apache/grails-core/issues/16030) |
   | MongoTransactionSpec > test native Long identifier generation works for entities committed in a transaction | ❌ | [1%&nbsp;🟡](https://github.com/apache/grails-core/issues/16030) |
   | MongoTransactionSpec > test read-your-writes within an active transaction | ❌ | [1%&nbsp;🟡](https://github.com/apache/grails-core/issues/16030) |
   | MongoTransactionSpec > test writes across multiple collections roll back together | ❌ | [1%&nbsp;🟡](https://github.com/apache/grails-core/issues/16030) |
   
   🏷️ Commit: e4a3e8ecc00309aa3061b7051240411694317979
   ▶️ Tests:  50900 executed
   🟡 Checks: 49/80 completed
   
   ### Test Failures
   
   <details>
   
   <summary><strong>MongoTransactionSpec > test a REQUIRES_NEW inner transaction commits independently of a rolled back outer transaction</strong> (:grails-data-mongodb-core:test in <a href="https://github.com/apache/grails-core/actions/runs/32399627504/job/96527899962?pr=16174">CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot (shard 1)</a>)</summary>
   
   ```
   com.mongodb.MongoTimeoutException: Timed out while waiting for a server that matches WritableServerSelector. Client view of cluster state is {type=UNKNOWN, servers=[{address=localhost:32839, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused}}]
   	at com.mongodb.internal.connection.BaseCluster.logAndThrowTimeoutException(BaseCluster.java:431)
   	at com.mongodb.internal.connection.BaseCluster.lambda$selectServer$0(BaseCluster.java:166)
   	at com.mongodb.internal.time.Timeout.lambda$onExistsAndExpired$16(Timeout.java:238)
   	at com.mongodb.internal.time.Timeout.lambda$run$10(Timeout.java:203)
   	at com.mongodb.internal.time.TimePoint.checkedCall(TimePoint.java:100)
   	at com.mongodb.internal.time.Timeout.call(Timeout.java:176)
   	at com.mongodb.internal.time.Timeout.run(Timeout.java:196)
   	at com.mongodb.internal.time.Timeout.onExistsAndExpired(Timeout.java:235)
   	at com.mongodb.internal.time.Timeout.onExpired(Timeout.java:228)
   	at com.mongodb.internal.connection.BaseCluster.selectServer(BaseCluster.java:165)
   	at com.mongodb.internal.connection.SingleServerCluster.selectServer(SingleServerCluster.java:47)
   	at com.mongodb.internal.binding.ClusterBinding.getWriteConnectionSource(ClusterBinding.java:91)
   	at com.mongodb.client.internal.ClientSessionBinding.getConnectionSource(ClientSessionBinding.java:109)
   	at com.mongodb.client.internal.ClientSessionBinding.getWriteConnectionSource(ClientSessionBinding.java:102)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:169)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSourceAndConnection(SyncOperationHelper.java:138)
   	at com.mongodb.internal.operation.SyncOperationHelper.withConnection(SyncOperationHelper.java:117)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:73)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:48)
   	at com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl.execute(MongoClusterImpl.java:472)
   	at com.mongodb.client.internal.MongoDatabaseImpl.executeDrop(MongoDatabaseImpl.java:230)
   	at com.mongodb.client.internal.MongoDatabaseImpl.drop(MongoDatabaseImpl.java:220)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup_closure1(MongoTransactionSpec.groovy:58)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession_closure23(GormStaticApi.groovy:767)
   	at org.grails.datastore.mapping.core.DatastoreUtils.executeWithNewSession(DatastoreUtils.java:371)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession(GormStaticApi.groovy:766)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.withNewSession(GormEntity.groovy:1157)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup(MongoTransactionSpec.groovy:57)
   ```
   
   </details>
   <details>
   
   <summary><strong>MongoTransactionSpec > test a committed transaction persists all writes atomically</strong> (:grails-data-mongodb-core:test in <a href="https://github.com/apache/grails-core/actions/runs/32399627504/job/96527899962?pr=16174">CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot (shard 1)</a>)</summary>
   
   ```
   com.mongodb.MongoSocketReadException: Prematurely reached end of stream
   	at com.mongodb.internal.connection.SocketStream.read(SocketStream.java:184)
   	at com.mongodb.internal.connection.InternalStreamConnection.receiveResponseBuffers(InternalStreamConnection.java:942)
   	at com.mongodb.internal.connection.InternalStreamConnection.receiveCommandMessageResponse(InternalStreamConnection.java:567)
   	at com.mongodb.internal.connection.InternalStreamConnection.sendAndReceiveInternal(InternalStreamConnection.java:494)
   	at com.mongodb.internal.connection.InternalStreamConnection.lambda$sendAndReceive$1(InternalStreamConnection.java:383)
   	at com.mongodb.internal.connection.InternalStreamConnection.sendAndReceive(InternalStreamConnection.java:386)
   	at com.mongodb.internal.connection.UsageTrackingInternalConnection.sendAndReceive(UsageTrackingInternalConnection.java:111)
   	at com.mongodb.internal.connection.DefaultConnectionPool$PooledConnection.sendAndReceive(DefaultConnectionPool.java:757)
   	at com.mongodb.internal.connection.CommandProtocolImpl.execute(CommandProtocolImpl.java:60)
   	at com.mongodb.internal.connection.DefaultServer$DefaultServerProtocolExecutor.execute(DefaultServer.java:207)
   	at com.mongodb.internal.connection.DefaultServerConnection.executeProtocol(DefaultServerConnection.java:112)
   	at com.mongodb.internal.connection.DefaultServerConnection.command(DefaultServerConnection.java:82)
   	at com.mongodb.internal.connection.DefaultServerConnection.command(DefaultServerConnection.java:74)
   	at com.mongodb.internal.connection.DefaultServer$OperationCountTrackingConnection.command(DefaultServer.java:297)
   	at com.mongodb.internal.operation.SyncOperationHelper.lambda$executeRetryableWrite$11(SyncOperationHelper.java:285)
   	at com.mongodb.internal.operation.SyncOperationHelper.lambda$withSourceAndConnection$1(SyncOperationHelper.java:146)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:177)
   	at com.mongodb.internal.operation.SyncOperationHelper.lambda$withSourceAndConnection$2(SyncOperationHelper.java:142)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:177)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSourceAndConnection(SyncOperationHelper.java:138)
   	at com.mongodb.internal.operation.SyncOperationHelper.lambda$executeRetryableWrite$12(SyncOperationHelper.java:270)
   	at com.mongodb.internal.operation.SyncOperationHelper.lambda$decorateWriteWithRetries$13(SyncOperationHelper.java:329)
   	at com.mongodb.internal.async.function.RetryingSyncSupplier.get(RetryingSyncSupplier.java:67)
   	at com.mongodb.internal.operation.SyncOperationHelper.executeRetryableWrite(SyncOperationHelper.java:297)
   	at com.mongodb.internal.operation.BaseFindAndModifyOperation.execute(BaseFindAndModifyOperation.java:81)
   	at com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl.execute(MongoClusterImpl.java:472)
   	at com.mongodb.client.internal.MongoCollectionImpl.executeFindOneAndUpdate(MongoCollectionImpl.java:787)
   	at com.mongodb.client.internal.MongoCollectionImpl.findOneAndUpdate(MongoCollectionImpl.java:766)
   	at org.grails.datastore.mapping.mongo.engine.MongoCodecEntityPersister.generateIdentifier(MongoCodecEntityPersister.groovy:443)
   	at org.grails.datastore.mapping.mongo.engine.MongoCodecEntityPersister.persistEntity(MongoCodecEntityPersister.groovy:226)
   	at org.grails.datastore.mapping.mongo.engine.MongoCodecEntityPersister.persistEntity(MongoCodecEntityPersister.groovy:567)
   	at org.grails.datastore.mapping.engine.EntityPersister.persist(EntityPersister.java:188)
   	at org.grails.datastore.mapping.core.AbstractSession.persist(AbstractSession.java:609)
   	at org.grails.datastore.gorm.GormInstanceApi.save_closure5(GormInstanceApi.groovy:213)
   	at org.grails.datastore.mapping.core.DatastoreUtils.execute(DatastoreUtils.java:334)
   	at org.grails.datastore.gorm.AbstractGormApi.execute(AbstractGormApi.groovy:164)
   	at org.grails.datastore.gorm.GormInstanceApi.save(GormInstanceApi.groovy:206)
   	at org.grails.datastore.gorm.GormInstanceApi.save(GormInstanceApi.groovy:173)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.save(GormEntity.groovy:110)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.test a committed transaction persists all writes atomically_closure2(MongoTransactionSpec.groovy:73)
   	at grails.gorm.transactions.GrailsTransactionTemplate$2.doInTransaction(GrailsTransactionTemplate.groovy:103)
   	at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:137)
   	at grails.gorm.transactions.GrailsTransactionTemplate.execute(GrailsTransactionTemplate.groovy:100)
   	at org.grails.datastore.gorm.GormStaticApi.withTransaction(GormStaticApi.groovy:703)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.withTransaction(GormEntity.groovy:1066)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.test a committed transaction persists all writes atomically(MongoTransactionSpec.groovy:71)
   ```
   
   </details>
   <details>
   
   <summary><strong>MongoTransactionSpec > test a findOneAndDelete via the MongoEntity API participates in the transaction</strong> (:grails-data-mongodb-core:test in <a href="https://github.com/apache/grails-core/actions/runs/32399627504/job/96527899962?pr=16174">CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot (shard 1)</a>)</summary>
   
   ```
   com.mongodb.MongoTimeoutException: Timed out while waiting for a server that matches WritableServerSelector. Client view of cluster state is {type=UNKNOWN, servers=[{address=localhost:32839, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused}}]
   	at com.mongodb.internal.connection.BaseCluster.logAndThrowTimeoutException(BaseCluster.java:431)
   	at com.mongodb.internal.connection.BaseCluster.lambda$selectServer$0(BaseCluster.java:166)
   	at com.mongodb.internal.time.Timeout.lambda$onExistsAndExpired$16(Timeout.java:238)
   	at com.mongodb.internal.time.Timeout.lambda$run$10(Timeout.java:203)
   	at com.mongodb.internal.time.TimePoint.checkedCall(TimePoint.java:100)
   	at com.mongodb.internal.time.Timeout.call(Timeout.java:176)
   	at com.mongodb.internal.time.Timeout.run(Timeout.java:196)
   	at com.mongodb.internal.time.Timeout.onExistsAndExpired(Timeout.java:235)
   	at com.mongodb.internal.time.Timeout.onExpired(Timeout.java:228)
   	at com.mongodb.internal.connection.BaseCluster.selectServer(BaseCluster.java:165)
   	at com.mongodb.internal.connection.SingleServerCluster.selectServer(SingleServerCluster.java:47)
   	at com.mongodb.internal.binding.ClusterBinding.getWriteConnectionSource(ClusterBinding.java:91)
   	at com.mongodb.client.internal.ClientSessionBinding.getConnectionSource(ClientSessionBinding.java:109)
   	at com.mongodb.client.internal.ClientSessionBinding.getWriteConnectionSource(ClientSessionBinding.java:102)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:169)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSourceAndConnection(SyncOperationHelper.java:138)
   	at com.mongodb.internal.operation.SyncOperationHelper.withConnection(SyncOperationHelper.java:117)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:73)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:48)
   	at com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl.execute(MongoClusterImpl.java:472)
   	at com.mongodb.client.internal.MongoDatabaseImpl.executeDrop(MongoDatabaseImpl.java:230)
   	at com.mongodb.client.internal.MongoDatabaseImpl.drop(MongoDatabaseImpl.java:220)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup_closure1(MongoTransactionSpec.groovy:58)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession_closure23(GormStaticApi.groovy:767)
   	at org.grails.datastore.mapping.core.DatastoreUtils.executeWithNewSession(DatastoreUtils.java:371)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession(GormStaticApi.groovy:766)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.withNewSession(GormEntity.groovy:1157)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup(MongoTransactionSpec.groovy:57)
   ```
   
   </details>
   <details>
   
   <summary><strong>MongoTransactionSpec > test a per-transaction timeout is rejected rather than silently ignored</strong> (:grails-data-mongodb-core:test in <a href="https://github.com/apache/grails-core/actions/runs/32399627504/job/96527899962?pr=16174">CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot (shard 1)</a>)</summary>
   
   ```
   com.mongodb.MongoTimeoutException: Timed out while waiting for a server that matches WritableServerSelector. Client view of cluster state is {type=UNKNOWN, servers=[{address=localhost:32839, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused}}]
   	at com.mongodb.internal.connection.BaseCluster.logAndThrowTimeoutException(BaseCluster.java:431)
   	at com.mongodb.internal.connection.BaseCluster.lambda$selectServer$0(BaseCluster.java:166)
   	at com.mongodb.internal.time.Timeout.lambda$onExistsAndExpired$16(Timeout.java:238)
   	at com.mongodb.internal.time.Timeout.lambda$run$10(Timeout.java:203)
   	at com.mongodb.internal.time.TimePoint.checkedCall(TimePoint.java:100)
   	at com.mongodb.internal.time.Timeout.call(Timeout.java:176)
   	at com.mongodb.internal.time.Timeout.run(Timeout.java:196)
   	at com.mongodb.internal.time.Timeout.onExistsAndExpired(Timeout.java:235)
   	at com.mongodb.internal.time.Timeout.onExpired(Timeout.java:228)
   	at com.mongodb.internal.connection.BaseCluster.selectServer(BaseCluster.java:165)
   	at com.mongodb.internal.connection.SingleServerCluster.selectServer(SingleServerCluster.java:47)
   	at com.mongodb.internal.binding.ClusterBinding.getWriteConnectionSource(ClusterBinding.java:91)
   	at com.mongodb.client.internal.ClientSessionBinding.getConnectionSource(ClientSessionBinding.java:109)
   	at com.mongodb.client.internal.ClientSessionBinding.getWriteConnectionSource(ClientSessionBinding.java:102)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:169)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSourceAndConnection(SyncOperationHelper.java:138)
   	at com.mongodb.internal.operation.SyncOperationHelper.withConnection(SyncOperationHelper.java:117)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:73)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:48)
   	at com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl.execute(MongoClusterImpl.java:472)
   	at com.mongodb.client.internal.MongoDatabaseImpl.executeDrop(MongoDatabaseImpl.java:230)
   	at com.mongodb.client.internal.MongoDatabaseImpl.drop(MongoDatabaseImpl.java:220)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup_closure1(MongoTransactionSpec.groovy:58)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession_closure23(GormStaticApi.groovy:767)
   	at org.grails.datastore.mapping.core.DatastoreUtils.executeWithNewSession(DatastoreUtils.java:371)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession(GormStaticApi.groovy:766)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.withNewSession(GormEntity.groovy:1157)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup(MongoTransactionSpec.groovy:57)
   ```
   
   </details>
   <details>
   
   <summary><strong>MongoTransactionSpec > test a rolled back transaction discards a native Long id entity (id generation is non-transactional)</strong> (:grails-data-mongodb-core:test in <a href="https://github.com/apache/grails-core/actions/runs/32399627504/job/96527899962?pr=16174">CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot (shard 1)</a>)</summary>
   
   ```
   com.mongodb.MongoTimeoutException: Timed out while waiting for a server that matches WritableServerSelector. Client view of cluster state is {type=UNKNOWN, servers=[{address=localhost:32839, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused}}]
   	at com.mongodb.internal.connection.BaseCluster.logAndThrowTimeoutException(BaseCluster.java:431)
   	at com.mongodb.internal.connection.BaseCluster.lambda$selectServer$0(BaseCluster.java:166)
   	at com.mongodb.internal.time.Timeout.lambda$onExistsAndExpired$16(Timeout.java:238)
   	at com.mongodb.internal.time.Timeout.lambda$run$10(Timeout.java:203)
   	at com.mongodb.internal.time.TimePoint.checkedCall(TimePoint.java:100)
   	at com.mongodb.internal.time.Timeout.call(Timeout.java:176)
   	at com.mongodb.internal.time.Timeout.run(Timeout.java:196)
   	at com.mongodb.internal.time.Timeout.onExistsAndExpired(Timeout.java:235)
   	at com.mongodb.internal.time.Timeout.onExpired(Timeout.java:228)
   	at com.mongodb.internal.connection.BaseCluster.selectServer(BaseCluster.java:165)
   	at com.mongodb.internal.connection.SingleServerCluster.selectServer(SingleServerCluster.java:47)
   	at com.mongodb.internal.binding.ClusterBinding.getWriteConnectionSource(ClusterBinding.java:91)
   	at com.mongodb.client.internal.ClientSessionBinding.getConnectionSource(ClientSessionBinding.java:109)
   	at com.mongodb.client.internal.ClientSessionBinding.getWriteConnectionSource(ClientSessionBinding.java:102)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:169)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSourceAndConnection(SyncOperationHelper.java:138)
   	at com.mongodb.internal.operation.SyncOperationHelper.withConnection(SyncOperationHelper.java:117)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:73)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:48)
   	at com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl.execute(MongoClusterImpl.java:472)
   	at com.mongodb.client.internal.MongoDatabaseImpl.executeDrop(MongoDatabaseImpl.java:230)
   	at com.mongodb.client.internal.MongoDatabaseImpl.drop(MongoDatabaseImpl.java:220)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup_closure1(MongoTransactionSpec.groovy:58)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession_closure23(GormStaticApi.groovy:767)
   	at org.grails.datastore.mapping.core.DatastoreUtils.executeWithNewSession(DatastoreUtils.java:371)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession(GormStaticApi.groovy:766)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.withNewSession(GormEntity.groovy:1157)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup(MongoTransactionSpec.groovy:57)
   ```
   
   </details>
   <details>
   
   <summary><strong>MongoTransactionSpec > test a rolled back transaction discards all writes on the server</strong> (:grails-data-mongodb-core:test in <a href="https://github.com/apache/grails-core/actions/runs/32399627504/job/96527899962?pr=16174">CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot (shard 1)</a>)</summary>
   
   ```
   com.mongodb.MongoTimeoutException: Timed out while waiting for a server that matches WritableServerSelector. Client view of cluster state is {type=UNKNOWN, servers=[{address=localhost:32839, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused}}]
   	at com.mongodb.internal.connection.BaseCluster.logAndThrowTimeoutException(BaseCluster.java:431)
   	at com.mongodb.internal.connection.BaseCluster.lambda$selectServer$0(BaseCluster.java:166)
   	at com.mongodb.internal.time.Timeout.lambda$onExistsAndExpired$16(Timeout.java:238)
   	at com.mongodb.internal.time.Timeout.lambda$run$10(Timeout.java:203)
   	at com.mongodb.internal.time.TimePoint.checkedCall(TimePoint.java:100)
   	at com.mongodb.internal.time.Timeout.call(Timeout.java:176)
   	at com.mongodb.internal.time.Timeout.run(Timeout.java:196)
   	at com.mongodb.internal.time.Timeout.onExistsAndExpired(Timeout.java:235)
   	at com.mongodb.internal.time.Timeout.onExpired(Timeout.java:228)
   	at com.mongodb.internal.connection.BaseCluster.selectServer(BaseCluster.java:165)
   	at com.mongodb.internal.connection.SingleServerCluster.selectServer(SingleServerCluster.java:47)
   	at com.mongodb.internal.binding.ClusterBinding.getWriteConnectionSource(ClusterBinding.java:91)
   	at com.mongodb.client.internal.ClientSessionBinding.getConnectionSource(ClientSessionBinding.java:109)
   	at com.mongodb.client.internal.ClientSessionBinding.getWriteConnectionSource(ClientSessionBinding.java:102)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:169)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSourceAndConnection(SyncOperationHelper.java:138)
   	at com.mongodb.internal.operation.SyncOperationHelper.withConnection(SyncOperationHelper.java:117)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:73)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:48)
   	at com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl.execute(MongoClusterImpl.java:472)
   	at com.mongodb.client.internal.MongoDatabaseImpl.executeDrop(MongoDatabaseImpl.java:230)
   	at com.mongodb.client.internal.MongoDatabaseImpl.drop(MongoDatabaseImpl.java:220)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup_closure1(MongoTransactionSpec.groovy:58)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession_closure23(GormStaticApi.groovy:767)
   	at org.grails.datastore.mapping.core.DatastoreUtils.executeWithNewSession(DatastoreUtils.java:371)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession(GormStaticApi.groovy:766)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.withNewSession(GormEntity.groovy:1157)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup(MongoTransactionSpec.groovy:57)
   ```
   
   </details>
   <details>
   
   <summary><strong>MongoTransactionSpec > test native Long identifier generation works for entities committed in a transaction</strong> (:grails-data-mongodb-core:test in <a href="https://github.com/apache/grails-core/actions/runs/32399627504/job/96527899962?pr=16174">CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot (shard 1)</a>)</summary>
   
   ```
   com.mongodb.MongoTimeoutException: Timed out while waiting for a server that matches WritableServerSelector. Client view of cluster state is {type=UNKNOWN, servers=[{address=localhost:32839, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused}}]
   	at com.mongodb.internal.connection.BaseCluster.logAndThrowTimeoutException(BaseCluster.java:431)
   	at com.mongodb.internal.connection.BaseCluster.lambda$selectServer$0(BaseCluster.java:166)
   	at com.mongodb.internal.time.Timeout.lambda$onExistsAndExpired$16(Timeout.java:238)
   	at com.mongodb.internal.time.Timeout.lambda$run$10(Timeout.java:203)
   	at com.mongodb.internal.time.TimePoint.checkedCall(TimePoint.java:100)
   	at com.mongodb.internal.time.Timeout.call(Timeout.java:176)
   	at com.mongodb.internal.time.Timeout.run(Timeout.java:196)
   	at com.mongodb.internal.time.Timeout.onExistsAndExpired(Timeout.java:235)
   	at com.mongodb.internal.time.Timeout.onExpired(Timeout.java:228)
   	at com.mongodb.internal.connection.BaseCluster.selectServer(BaseCluster.java:165)
   	at com.mongodb.internal.connection.SingleServerCluster.selectServer(SingleServerCluster.java:47)
   	at com.mongodb.internal.binding.ClusterBinding.getWriteConnectionSource(ClusterBinding.java:91)
   	at com.mongodb.client.internal.ClientSessionBinding.getConnectionSource(ClientSessionBinding.java:109)
   	at com.mongodb.client.internal.ClientSessionBinding.getWriteConnectionSource(ClientSessionBinding.java:102)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:169)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSourceAndConnection(SyncOperationHelper.java:138)
   	at com.mongodb.internal.operation.SyncOperationHelper.withConnection(SyncOperationHelper.java:117)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:73)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:48)
   	at com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl.execute(MongoClusterImpl.java:472)
   	at com.mongodb.client.internal.MongoDatabaseImpl.executeDrop(MongoDatabaseImpl.java:230)
   	at com.mongodb.client.internal.MongoDatabaseImpl.drop(MongoDatabaseImpl.java:220)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup_closure1(MongoTransactionSpec.groovy:58)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession_closure23(GormStaticApi.groovy:767)
   	at org.grails.datastore.mapping.core.DatastoreUtils.executeWithNewSession(DatastoreUtils.java:371)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession(GormStaticApi.groovy:766)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.withNewSession(GormEntity.groovy:1157)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup(MongoTransactionSpec.groovy:57)
   ```
   
   </details>
   <details>
   
   <summary><strong>MongoTransactionSpec > test read-your-writes within an active transaction</strong> (:grails-data-mongodb-core:test in <a href="https://github.com/apache/grails-core/actions/runs/32399627504/job/96527899962?pr=16174">CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot (shard 1)</a>)</summary>
   
   ```
   com.mongodb.MongoTimeoutException: Timed out while waiting for a server that matches WritableServerSelector. Client view of cluster state is {type=UNKNOWN, servers=[{address=localhost:32839, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused}}]
   	at com.mongodb.internal.connection.BaseCluster.logAndThrowTimeoutException(BaseCluster.java:431)
   	at com.mongodb.internal.connection.BaseCluster.lambda$selectServer$0(BaseCluster.java:166)
   	at com.mongodb.internal.time.Timeout.lambda$onExistsAndExpired$16(Timeout.java:238)
   	at com.mongodb.internal.time.Timeout.lambda$run$10(Timeout.java:203)
   	at com.mongodb.internal.time.TimePoint.checkedCall(TimePoint.java:100)
   	at com.mongodb.internal.time.Timeout.call(Timeout.java:176)
   	at com.mongodb.internal.time.Timeout.run(Timeout.java:196)
   	at com.mongodb.internal.time.Timeout.onExistsAndExpired(Timeout.java:235)
   	at com.mongodb.internal.time.Timeout.onExpired(Timeout.java:228)
   	at com.mongodb.internal.connection.BaseCluster.selectServer(BaseCluster.java:165)
   	at com.mongodb.internal.connection.SingleServerCluster.selectServer(SingleServerCluster.java:47)
   	at com.mongodb.internal.binding.ClusterBinding.getWriteConnectionSource(ClusterBinding.java:91)
   	at com.mongodb.client.internal.ClientSessionBinding.getConnectionSource(ClientSessionBinding.java:109)
   	at com.mongodb.client.internal.ClientSessionBinding.getWriteConnectionSource(ClientSessionBinding.java:102)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:169)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSourceAndConnection(SyncOperationHelper.java:138)
   	at com.mongodb.internal.operation.SyncOperationHelper.withConnection(SyncOperationHelper.java:117)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:73)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:48)
   	at com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl.execute(MongoClusterImpl.java:472)
   	at com.mongodb.client.internal.MongoDatabaseImpl.executeDrop(MongoDatabaseImpl.java:230)
   	at com.mongodb.client.internal.MongoDatabaseImpl.drop(MongoDatabaseImpl.java:220)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup_closure1(MongoTransactionSpec.groovy:58)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession_closure23(GormStaticApi.groovy:767)
   	at org.grails.datastore.mapping.core.DatastoreUtils.executeWithNewSession(DatastoreUtils.java:371)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession(GormStaticApi.groovy:766)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.withNewSession(GormEntity.groovy:1157)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup(MongoTransactionSpec.groovy:57)
   ```
   
   </details>
   <details>
   
   <summary><strong>MongoTransactionSpec > test writes across multiple collections roll back together</strong> (:grails-data-mongodb-core:test in <a href="https://github.com/apache/grails-core/actions/runs/32399627504/job/96527899962?pr=16174">CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot (shard 1)</a>)</summary>
   
   ```
   com.mongodb.MongoTimeoutException: Timed out while waiting for a server that matches WritableServerSelector. Client view of cluster state is {type=UNKNOWN, servers=[{address=localhost:32839, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused}}]
   	at com.mongodb.internal.connection.BaseCluster.logAndThrowTimeoutException(BaseCluster.java:431)
   	at com.mongodb.internal.connection.BaseCluster.lambda$selectServer$0(BaseCluster.java:166)
   	at com.mongodb.internal.time.Timeout.lambda$onExistsAndExpired$16(Timeout.java:238)
   	at com.mongodb.internal.time.Timeout.lambda$run$10(Timeout.java:203)
   	at com.mongodb.internal.time.TimePoint.checkedCall(TimePoint.java:100)
   	at com.mongodb.internal.time.Timeout.call(Timeout.java:176)
   	at com.mongodb.internal.time.Timeout.run(Timeout.java:196)
   	at com.mongodb.internal.time.Timeout.onExistsAndExpired(Timeout.java:235)
   	at com.mongodb.internal.time.Timeout.onExpired(Timeout.java:228)
   	at com.mongodb.internal.connection.BaseCluster.selectServer(BaseCluster.java:165)
   	at com.mongodb.internal.connection.SingleServerCluster.selectServer(SingleServerCluster.java:47)
   	at com.mongodb.internal.binding.ClusterBinding.getWriteConnectionSource(ClusterBinding.java:91)
   	at com.mongodb.client.internal.ClientSessionBinding.getConnectionSource(ClientSessionBinding.java:109)
   	at com.mongodb.client.internal.ClientSessionBinding.getWriteConnectionSource(ClientSessionBinding.java:102)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:169)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSourceAndConnection(SyncOperationHelper.java:138)
   	at com.mongodb.internal.operation.SyncOperationHelper.withConnection(SyncOperationHelper.java:117)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:73)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:48)
   	at com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl.execute(MongoClusterImpl.java:472)
   	at com.mongodb.client.internal.MongoDatabaseImpl.executeDrop(MongoDatabaseImpl.java:230)
   	at com.mongodb.client.internal.MongoDatabaseImpl.drop(MongoDatabaseImpl.java:220)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup_closure1(MongoTransactionSpec.groovy:58)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession_closure23(GormStaticApi.groovy:767)
   	at org.grails.datastore.mapping.core.DatastoreUtils.executeWithNewSession(DatastoreUtils.java:371)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession(GormStaticApi.groovy:766)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.withNewSession(GormEntity.groovy:1157)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup(MongoTransactionSpec.groovy:57)
   ```
   
   </details>
   
   ### Rerun Controls
   > [!NOTE]
   > Checks are currently running using the configuration below.
   
   Select tests to mute in this pull request:
   
   🔲 MongoTransactionSpec > test a REQUIRES\_NEW inner transaction commits independently of a rolled back outer transaction <!-- uniqueId=[engine:spock]/[spec:org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec]/[feature:$spock_feature_1_6] -->
   🔲 MongoTransactionSpec > test a committed transaction persists all writes atomically <!-- uniqueId=[engine:spock]/[spec:org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec]/[feature:$spock_feature_1_1] -->
   🔲 MongoTransactionSpec > test a findOneAndDelete via the MongoEntity API participates in the transaction <!-- uniqueId=[engine:spock]/[spec:org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec]/[feature:$spock_feature_1_5] -->
   🔲 MongoTransactionSpec > test a per-transaction timeout is rejected rather than silently ignored <!-- uniqueId=[engine:spock]/[spec:org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec]/[feature:$spock_feature_1_9] -->
   🔲 MongoTransactionSpec > test a rolled back transaction discards a native Long id entity \(id generation is non-transactional\) <!-- uniqueId=[engine:spock]/[spec:org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec]/[feature:$spock_feature_1_8] -->
   🔲 MongoTransactionSpec > test a rolled back transaction discards all writes on the server <!-- uniqueId=[engine:spock]/[spec:org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec]/[feature:$spock_feature_1_2] -->
   🔲 MongoTransactionSpec > test native Long identifier generation works for entities committed in a transaction <!-- uniqueId=[engine:spock]/[spec:org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec]/[feature:$spock_feature_1_7] -->
   🔲 MongoTransactionSpec > test read-your-writes within an active transaction <!-- uniqueId=[engine:spock]/[spec:org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec]/[feature:$spock_feature_1_4] -->
   🔲 MongoTransactionSpec > test writes across multiple collections roll back together <!-- uniqueId=[engine:spock]/[spec:org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec]/[feature:$spock_feature_1_3] -->
   
   Reuse successful test results:
   
   ☑️ ♻️ Only rerun the tests that failed or were muted before
   
   Click the checkbox to trigger a rerun:
   
   🔲 **Rerun jobs**
   
   ---
   _Learn more about TestLens at [testlens.app/docs](https://testlens.app/docs/features/pr-comment/)._
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] fix(cas): repair proxy receptor and single sign-out, add integration tests [grails-core]

Posted by "testlens-app[bot] (via GitHub)" <gi...@apache.org>.
testlens-app[bot] commented on PR #16174:
URL: https://github.com/apache/grails-core/pull/16174#issuecomment-5361589404

   ## 🚨 TestLens detected 4 failed tests 🚨
   
   Here is what you can do:
   
   1) Inspect the test failures carefully.
   2) If you are convinced that some of the tests are flaky, you can mute them below.
   3) Finally, trigger a rerun by checking the rerun checkbox.
   
   ### Test Summary
   
   #### [CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot \(shard 1\)](https://github.com/apache/grails-core/actions/runs/32414477298/job/96573485190?pr=16174) > :grails-data-mongodb-core:test
   
   | Test | Runs | Flakiness |
   |---|---|--:|
   | MongoTransactionSpec > test a committed transaction persists all writes atomically | ❌ | [2%&nbsp;🟡](https://github.com/apache/grails-core/issues/16030) |
   | MongoTransactionSpec > test a rolled back transaction discards all writes on the server | ❌ | [2%&nbsp;🟡](https://github.com/apache/grails-core/issues/16030) |
   | MongoTransactionSpec > test read-your-writes within an active transaction | ❌ | [2%&nbsp;🟡](https://github.com/apache/grails-core/issues/16030) |
   | MongoTransactionSpec > test writes across multiple collections roll back together | ❌ | [2%&nbsp;🟡](https://github.com/apache/grails-core/issues/16030) |
   
   🏷️ Commit: 95a9eb6f1500d98435414588b8aa169626a95d56
   ▶️ Tests:  30660 executed
   🟡 Checks: 33/80 completed
   
   ### Test Failures
   
   <details>
   
   <summary><strong>MongoTransactionSpec > test a committed transaction persists all writes atomically</strong> (:grails-data-mongodb-core:test in <a href="https://github.com/apache/grails-core/actions/runs/32414477298/job/96573485190?pr=16174">CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot (shard 1)</a>)</summary>
   
   ```
   com.mongodb.MongoSocketReadException: Prematurely reached end of stream
   	at com.mongodb.internal.connection.SocketStream.read(SocketStream.java:184)
   	at com.mongodb.internal.connection.InternalStreamConnection.receiveResponseBuffers(InternalStreamConnection.java:942)
   	at com.mongodb.internal.connection.InternalStreamConnection.receiveCommandMessageResponse(InternalStreamConnection.java:567)
   	at com.mongodb.internal.connection.InternalStreamConnection.sendAndReceiveInternal(InternalStreamConnection.java:494)
   	at com.mongodb.internal.connection.InternalStreamConnection.lambda$sendAndReceive$1(InternalStreamConnection.java:383)
   	at com.mongodb.internal.connection.InternalStreamConnection.sendAndReceive(InternalStreamConnection.java:386)
   	at com.mongodb.internal.connection.UsageTrackingInternalConnection.sendAndReceive(UsageTrackingInternalConnection.java:111)
   	at com.mongodb.internal.connection.DefaultConnectionPool$PooledConnection.sendAndReceive(DefaultConnectionPool.java:757)
   	at com.mongodb.internal.connection.CommandProtocolImpl.execute(CommandProtocolImpl.java:60)
   	at com.mongodb.internal.connection.DefaultServer$DefaultServerProtocolExecutor.execute(DefaultServer.java:207)
   	at com.mongodb.internal.connection.DefaultServerConnection.executeProtocol(DefaultServerConnection.java:112)
   	at com.mongodb.internal.connection.DefaultServerConnection.command(DefaultServerConnection.java:82)
   	at com.mongodb.internal.connection.DefaultServerConnection.command(DefaultServerConnection.java:74)
   	at com.mongodb.internal.connection.DefaultServer$OperationCountTrackingConnection.command(DefaultServer.java:297)
   	at com.mongodb.internal.operation.SyncOperationHelper.lambda$executeRetryableWrite$11(SyncOperationHelper.java:285)
   	at com.mongodb.internal.operation.SyncOperationHelper.lambda$withSourceAndConnection$1(SyncOperationHelper.java:146)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:177)
   	at com.mongodb.internal.operation.SyncOperationHelper.lambda$withSourceAndConnection$2(SyncOperationHelper.java:142)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:177)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSourceAndConnection(SyncOperationHelper.java:138)
   	at com.mongodb.internal.operation.SyncOperationHelper.lambda$executeRetryableWrite$12(SyncOperationHelper.java:270)
   	at com.mongodb.internal.operation.SyncOperationHelper.lambda$decorateWriteWithRetries$13(SyncOperationHelper.java:329)
   	at com.mongodb.internal.async.function.RetryingSyncSupplier.get(RetryingSyncSupplier.java:67)
   	at com.mongodb.internal.operation.SyncOperationHelper.executeRetryableWrite(SyncOperationHelper.java:297)
   	at com.mongodb.internal.operation.BaseFindAndModifyOperation.execute(BaseFindAndModifyOperation.java:81)
   	at com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl.execute(MongoClusterImpl.java:472)
   	at com.mongodb.client.internal.MongoCollectionImpl.executeFindOneAndUpdate(MongoCollectionImpl.java:787)
   	at com.mongodb.client.internal.MongoCollectionImpl.findOneAndUpdate(MongoCollectionImpl.java:766)
   	at org.grails.datastore.mapping.mongo.engine.MongoCodecEntityPersister.generateIdentifier(MongoCodecEntityPersister.groovy:443)
   	at org.grails.datastore.mapping.mongo.engine.MongoCodecEntityPersister.persistEntity(MongoCodecEntityPersister.groovy:226)
   	at org.grails.datastore.mapping.mongo.engine.MongoCodecEntityPersister.persistEntity(MongoCodecEntityPersister.groovy:567)
   	at org.grails.datastore.mapping.engine.EntityPersister.persist(EntityPersister.java:188)
   	at org.grails.datastore.mapping.core.AbstractSession.persist(AbstractSession.java:609)
   	at org.grails.datastore.gorm.GormInstanceApi.save_closure5(GormInstanceApi.groovy:213)
   	at org.grails.datastore.mapping.core.DatastoreUtils.execute(DatastoreUtils.java:334)
   	at org.grails.datastore.gorm.AbstractGormApi.execute(AbstractGormApi.groovy:164)
   	at org.grails.datastore.gorm.GormInstanceApi.save(GormInstanceApi.groovy:206)
   	at org.grails.datastore.gorm.GormInstanceApi.save(GormInstanceApi.groovy:173)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.save(GormEntity.groovy:110)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.test a committed transaction persists all writes atomically_closure2(MongoTransactionSpec.groovy:73)
   	at grails.gorm.transactions.GrailsTransactionTemplate$2.doInTransaction(GrailsTransactionTemplate.groovy:103)
   	at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:137)
   	at grails.gorm.transactions.GrailsTransactionTemplate.execute(GrailsTransactionTemplate.groovy:100)
   	at org.grails.datastore.gorm.GormStaticApi.withTransaction(GormStaticApi.groovy:703)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.withTransaction(GormEntity.groovy:1066)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.test a committed transaction persists all writes atomically(MongoTransactionSpec.groovy:71)
   ```
   
   </details>
   <details>
   
   <summary><strong>MongoTransactionSpec > test a rolled back transaction discards all writes on the server</strong> (:grails-data-mongodb-core:test in <a href="https://github.com/apache/grails-core/actions/runs/32414477298/job/96573485190?pr=16174">CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot (shard 1)</a>)</summary>
   
   ```
   com.mongodb.MongoTimeoutException: Timed out while waiting for a server that matches WritableServerSelector. Client view of cluster state is {type=UNKNOWN, servers=[{address=localhost:32864, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused}}]
   	at com.mongodb.internal.connection.BaseCluster.logAndThrowTimeoutException(BaseCluster.java:431)
   	at com.mongodb.internal.connection.BaseCluster.lambda$selectServer$0(BaseCluster.java:166)
   	at com.mongodb.internal.time.Timeout.lambda$onExistsAndExpired$16(Timeout.java:238)
   	at com.mongodb.internal.time.Timeout.lambda$run$10(Timeout.java:203)
   	at com.mongodb.internal.time.TimePoint.checkedCall(TimePoint.java:100)
   	at com.mongodb.internal.time.Timeout.call(Timeout.java:176)
   	at com.mongodb.internal.time.Timeout.run(Timeout.java:196)
   	at com.mongodb.internal.time.Timeout.onExistsAndExpired(Timeout.java:235)
   	at com.mongodb.internal.time.Timeout.onExpired(Timeout.java:228)
   	at com.mongodb.internal.connection.BaseCluster.selectServer(BaseCluster.java:165)
   	at com.mongodb.internal.connection.SingleServerCluster.selectServer(SingleServerCluster.java:47)
   	at com.mongodb.internal.binding.ClusterBinding.getWriteConnectionSource(ClusterBinding.java:91)
   	at com.mongodb.client.internal.ClientSessionBinding.getConnectionSource(ClientSessionBinding.java:109)
   	at com.mongodb.client.internal.ClientSessionBinding.getWriteConnectionSource(ClientSessionBinding.java:102)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:169)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSourceAndConnection(SyncOperationHelper.java:138)
   	at com.mongodb.internal.operation.SyncOperationHelper.withConnection(SyncOperationHelper.java:117)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:73)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:48)
   	at com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl.execute(MongoClusterImpl.java:472)
   	at com.mongodb.client.internal.MongoDatabaseImpl.executeDrop(MongoDatabaseImpl.java:230)
   	at com.mongodb.client.internal.MongoDatabaseImpl.drop(MongoDatabaseImpl.java:220)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup_closure1(MongoTransactionSpec.groovy:58)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession_closure23(GormStaticApi.groovy:767)
   	at org.grails.datastore.mapping.core.DatastoreUtils.executeWithNewSession(DatastoreUtils.java:371)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession(GormStaticApi.groovy:766)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.withNewSession(GormEntity.groovy:1157)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup(MongoTransactionSpec.groovy:57)
   ```
   
   </details>
   <details>
   
   <summary><strong>MongoTransactionSpec > test read-your-writes within an active transaction</strong> (:grails-data-mongodb-core:test in <a href="https://github.com/apache/grails-core/actions/runs/32414477298/job/96573485190?pr=16174">CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot (shard 1)</a>)</summary>
   
   ```
   com.mongodb.MongoTimeoutException: Timed out while waiting for a server that matches WritableServerSelector. Client view of cluster state is {type=UNKNOWN, servers=[{address=localhost:32864, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused}}]
   	at com.mongodb.internal.connection.BaseCluster.logAndThrowTimeoutException(BaseCluster.java:431)
   	at com.mongodb.internal.connection.BaseCluster.lambda$selectServer$0(BaseCluster.java:166)
   	at com.mongodb.internal.time.Timeout.lambda$onExistsAndExpired$16(Timeout.java:238)
   	at com.mongodb.internal.time.Timeout.lambda$run$10(Timeout.java:203)
   	at com.mongodb.internal.time.TimePoint.checkedCall(TimePoint.java:100)
   	at com.mongodb.internal.time.Timeout.call(Timeout.java:176)
   	at com.mongodb.internal.time.Timeout.run(Timeout.java:196)
   	at com.mongodb.internal.time.Timeout.onExistsAndExpired(Timeout.java:235)
   	at com.mongodb.internal.time.Timeout.onExpired(Timeout.java:228)
   	at com.mongodb.internal.connection.BaseCluster.selectServer(BaseCluster.java:165)
   	at com.mongodb.internal.connection.SingleServerCluster.selectServer(SingleServerCluster.java:47)
   	at com.mongodb.internal.binding.ClusterBinding.getWriteConnectionSource(ClusterBinding.java:91)
   	at com.mongodb.client.internal.ClientSessionBinding.getConnectionSource(ClientSessionBinding.java:109)
   	at com.mongodb.client.internal.ClientSessionBinding.getWriteConnectionSource(ClientSessionBinding.java:102)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:169)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSourceAndConnection(SyncOperationHelper.java:138)
   	at com.mongodb.internal.operation.SyncOperationHelper.withConnection(SyncOperationHelper.java:117)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:73)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:48)
   	at com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl.execute(MongoClusterImpl.java:472)
   	at com.mongodb.client.internal.MongoDatabaseImpl.executeDrop(MongoDatabaseImpl.java:230)
   	at com.mongodb.client.internal.MongoDatabaseImpl.drop(MongoDatabaseImpl.java:220)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup_closure1(MongoTransactionSpec.groovy:58)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession_closure23(GormStaticApi.groovy:767)
   	at org.grails.datastore.mapping.core.DatastoreUtils.executeWithNewSession(DatastoreUtils.java:371)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession(GormStaticApi.groovy:766)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.withNewSession(GormEntity.groovy:1157)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup(MongoTransactionSpec.groovy:57)
   ```
   
   </details>
   <details>
   
   <summary><strong>MongoTransactionSpec > test writes across multiple collections roll back together</strong> (:grails-data-mongodb-core:test in <a href="https://github.com/apache/grails-core/actions/runs/32414477298/job/96573485190?pr=16174">CI - Groovy Joint Validation Build / Build Grails with Groovy snapshot (shard 1)</a>)</summary>
   
   ```
   com.mongodb.MongoTimeoutException: Timed out while waiting for a server that matches WritableServerSelector. Client view of cluster state is {type=UNKNOWN, servers=[{address=localhost:32864, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {java.net.ConnectException: Connection refused}}]
   	at com.mongodb.internal.connection.BaseCluster.logAndThrowTimeoutException(BaseCluster.java:431)
   	at com.mongodb.internal.connection.BaseCluster.lambda$selectServer$0(BaseCluster.java:166)
   	at com.mongodb.internal.time.Timeout.lambda$onExistsAndExpired$16(Timeout.java:238)
   	at com.mongodb.internal.time.Timeout.lambda$run$10(Timeout.java:203)
   	at com.mongodb.internal.time.TimePoint.checkedCall(TimePoint.java:100)
   	at com.mongodb.internal.time.Timeout.call(Timeout.java:176)
   	at com.mongodb.internal.time.Timeout.run(Timeout.java:196)
   	at com.mongodb.internal.time.Timeout.onExistsAndExpired(Timeout.java:235)
   	at com.mongodb.internal.time.Timeout.onExpired(Timeout.java:228)
   	at com.mongodb.internal.connection.BaseCluster.selectServer(BaseCluster.java:165)
   	at com.mongodb.internal.connection.SingleServerCluster.selectServer(SingleServerCluster.java:47)
   	at com.mongodb.internal.binding.ClusterBinding.getWriteConnectionSource(ClusterBinding.java:91)
   	at com.mongodb.client.internal.ClientSessionBinding.getConnectionSource(ClientSessionBinding.java:109)
   	at com.mongodb.client.internal.ClientSessionBinding.getWriteConnectionSource(ClientSessionBinding.java:102)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSuppliedResource(SyncOperationHelper.java:169)
   	at com.mongodb.internal.operation.SyncOperationHelper.withSourceAndConnection(SyncOperationHelper.java:138)
   	at com.mongodb.internal.operation.SyncOperationHelper.withConnection(SyncOperationHelper.java:117)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:73)
   	at com.mongodb.internal.operation.DropDatabaseOperation.execute(DropDatabaseOperation.java:48)
   	at com.mongodb.client.internal.MongoClusterImpl$OperationExecutorImpl.execute(MongoClusterImpl.java:472)
   	at com.mongodb.client.internal.MongoDatabaseImpl.executeDrop(MongoDatabaseImpl.java:230)
   	at com.mongodb.client.internal.MongoDatabaseImpl.drop(MongoDatabaseImpl.java:220)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup_closure1(MongoTransactionSpec.groovy:58)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession_closure23(GormStaticApi.groovy:767)
   	at org.grails.datastore.mapping.core.DatastoreUtils.executeWithNewSession(DatastoreUtils.java:371)
   	at org.grails.datastore.gorm.GormStaticApi.withNewSession(GormStaticApi.groovy:766)
   	at org.grails.datastore.gorm.GormEntity$Trait$Helper.withNewSession(GormEntity.groovy:1157)
   	at org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec.setup(MongoTransactionSpec.groovy:57)
   ```
   
   </details>
   
   ### Rerun Controls
   > [!NOTE]
   > Checks are currently running using the configuration below.
   
   Select tests to mute in this pull request:
   
   🔲 MongoTransactionSpec > test a committed transaction persists all writes atomically <!-- uniqueId=[engine:spock]/[spec:org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec]/[feature:$spock_feature_1_1] -->
   🔲 MongoTransactionSpec > test a rolled back transaction discards all writes on the server <!-- uniqueId=[engine:spock]/[spec:org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec]/[feature:$spock_feature_1_2] -->
   🔲 MongoTransactionSpec > test read-your-writes within an active transaction <!-- uniqueId=[engine:spock]/[spec:org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec]/[feature:$spock_feature_1_4] -->
   🔲 MongoTransactionSpec > test writes across multiple collections roll back together <!-- uniqueId=[engine:spock]/[spec:org.grails.datastore.gorm.mongo.transactions.MongoTransactionSpec]/[feature:$spock_feature_1_3] -->
   
   Reuse successful test results:
   
   ☑️ ♻️ Only rerun the tests that failed or were muted before
   
   Click the checkbox to trigger a rerun:
   
   🔲 **Rerun jobs**
   
   ---
   _Learn more about TestLens at [testlens.app/docs](https://testlens.app/docs/features/pr-comment/)._
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] fix(cas): repair proxy receptor and single sign-out, add integration tests [grails-core]

Posted by "sbglasius (via GitHub)" <gi...@apache.org>.
sbglasius commented on code in PR #16174:
URL: https://github.com/apache/grails-core/pull/16174#discussion_r3819489415


##########
grails-test-examples/spring-security/cas/test1/src/integration-test/groovy/specs/AbstractCasSpec.groovy:
##########
@@ -0,0 +1,182 @@
+/*
+ *  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
+ *
+ *    https://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 specs

Review Comment:
   Aligned to be `grails.plugin.springsecurity.cas.test` 



##########
grails-test-examples/spring-security/cas/test1/src/main/groovy/spring/security/cas/test/CasTestConfig.groovy:
##########
@@ -0,0 +1,72 @@
+/*
+ *  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
+ *
+ *    https://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 spring.security.cas.test

Review Comment:
   Aligned to be `grails.plugin.springsecurity.cas.test` 



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] fix(cas): repair proxy receptor and single sign-out, add integration tests [grails-core]

Posted by "sbglasius (via GitHub)" <gi...@apache.org>.
sbglasius commented on code in PR #16174:
URL: https://github.com/apache/grails-core/pull/16174#discussion_r3824043639


##########
grails-spring-security/cas/plugin/src/main/groovy/grails/plugin/springsecurity/cas/SpringSecurityCasGrailsPlugin.groovy:
##########
@@ -35,11 +36,14 @@ import org.springframework.security.cas.authentication.CasAuthenticationProvider
 import org.springframework.security.cas.authentication.NullStatelessTicketCache
 import org.springframework.security.cas.web.CasAuthenticationEntryPoint
 import org.springframework.security.cas.web.CasAuthenticationFilter
+import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy
 
+import grails.plugin.springsecurity.BeanTypeResolver
 import grails.plugin.springsecurity.SecurityFilterPosition
 import grails.plugin.springsecurity.SpringSecurityUtils
 import grails.plugins.Plugin
 
+@Slf4j
 @CompileStatic
 class SpringSecurityCasGrailsPlugin extends Plugin {

Review Comment:
   * **Jasig → Apereo**: yes, clearly wrong — the plugin imports `org.apereo.cas.client.*`. Fixed the title and description, and repointed three dead `jasig.org` links in the guide at `apereo.github.io`.
   * `grailsVersion` / `organization` / `issueManagement` / `scm`: updated to `'8.0.0-SNAPSHOT > *'`, `grails.apache.org/`, and `apache/grails-core`, matching what `MongodbGrailsPlugin`, `GormGraphqlGrailsPlugin` and `MarkupViewGrailsPlugin` use on this branch.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] fix(cas): repair proxy receptor and single sign-out, add integration tests [grails-core]

Posted by "codecov[bot] (via GitHub)" <gi...@apache.org>.
codecov[bot] commented on PR #16174:
URL: https://github.com/apache/grails-core/pull/16174#issuecomment-5345108155

   ## [Codecov](https://app.codecov.io/gh/apache/grails-core/pull/16174?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) Report
   :white_check_mark: All modified and coverable lines are covered by tests.
   :white_check_mark: Project coverage is 52.3313%. Comparing base ([`deac428`](https://app.codecov.io/gh/apache/grails-core/commit/deac428c1e74577bc6255a5cfc3ed4b9d9da4646?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)) to head ([`06d8801`](https://app.codecov.io/gh/apache/grails-core/commit/06d8801b2c225103c27cff25c9ea3b65b9eacec2?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)).
   
   <details><summary>Additional details and impacted files</summary>
   
   
   
   [![Impacted file tree graph](https://app.codecov.io/gh/apache/grails-core/pull/16174/graphs/tree.svg?width=650&height=150&src=pr&token=qJrryhpk7u&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)](https://app.codecov.io/gh/apache/grails-core/pull/16174?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
   
   ```diff
   @@                Coverage Diff                 @@
   ##                8.0.x     #16174        +/-   ##
   ==================================================
   - Coverage     52.3364%   52.3313%   -0.0051%     
   + Complexity      18545      18543         -2     
   ==================================================
     Files            2039       2039                
     Lines           97521      97521                
     Branches        17143      17143                
   ==================================================
   - Hits            51039      51034         -5     
   - Misses          38994      38999         +5     
     Partials         7488       7488                
   ```
   [see 2 files with indirect coverage changes](https://app.codecov.io/gh/apache/grails-core/pull/16174/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
   </details>
   <details><summary> :rocket: New features to boost your workflow: </summary>
   
   - :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
   - :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
   </details>


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] fix(cas): repair proxy receptor and single sign-out, add integration tests [grails-core]

Posted by "sbglasius (via GitHub)" <gi...@apache.org>.
sbglasius commented on code in PR #16174:
URL: https://github.com/apache/grails-core/pull/16174#discussion_r3825111026


##########
grails-spring-security/cas/plugin/src/main/groovy/grails/plugin/springsecurity/cas/SpringSecurityCasGrailsPlugin.groovy:
##########
@@ -35,25 +36,28 @@ import org.springframework.security.cas.authentication.CasAuthenticationProvider
 import org.springframework.security.cas.authentication.NullStatelessTicketCache
 import org.springframework.security.cas.web.CasAuthenticationEntryPoint
 import org.springframework.security.cas.web.CasAuthenticationFilter
+import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy
 
+import grails.plugin.springsecurity.BeanTypeResolver
 import grails.plugin.springsecurity.SecurityFilterPosition
 import grails.plugin.springsecurity.SpringSecurityUtils
 import grails.plugins.Plugin
 
+@Slf4j
 @CompileStatic
 class SpringSecurityCasGrailsPlugin extends Plugin {
 
-    String grailsVersion = '7.0.0 > *'
+    String grailsVersion = '8.0.0-SNAPSHOT > *'
     String author = 'Burt Beckwith'

Review Comment:
   Done in 2391490. Removed both `author` and `authorEmail` (the latter was an empty string and meaningless without the former).



##########
grails-doc/src/en/guide/security/securityPlugins/springSecurity/cas/usage.adoc:
##########
@@ -25,7 +25,7 @@ under the License.
 Configuring your CAS server is beyond the scope of this document. There are many different approaches and this will most likely be done by IT staff. It's assumed here that you already have a running CAS server.
 ====
 
-https://www.jasig.org/cas[CAS] is a popular single sign-on implementation. It's open source and has an Apache-like license, and is easy to get started with but is also highly configurable. In addition it has clients written in Java, .Net, PHP, Perl, and other languages.
+https://apereo.github.io/cas[CAS] is a popular single sign-on implementation. It's open source and has an Apache-like license, and is easy to get started with but is also highly configurable. In addition it has clients written in Java, .Net, PHP, Perl, and other languages.

Review Comment:
   Good catch, that section directly contradicted the change. Rewritten in 2391490: it now leads with the fact that it's opt-in and *why* (enabling it disables session fixation prevention, because CAS maps the service ticket to the session id), shows the `useSingleSignout: true` snippet, and mentions the startup warning. The former "if you don't want the filter registered, disable it" paragraph is now framed as returning to the default.



##########
grails-doc/src/en/guide/upgrading/upgrading80x.adoc:
##########
@@ -2632,3 +2632,36 @@ used to apply to its own message source.
 
 Adding or removing a base name now needs a restart, because Spring Boot reads the configured base names
 once when it builds the message source.
+
+==== 46. CAS Single Sign-Out Is Opt-In

Review Comment:
   Fixed in 2391490 — renumbered to 48.
   
   Worth flagging that this was more than cosmetic: 45, 46 and 47 all landed upstream while this PR was open, so the file actually had *two* sections numbered 46 and would have produced duplicate asciidoctor anchors. I checked the whole file for other collisions and there are none.



##########
grails-test-examples/spring-security/cas/test1/src/integration-test/groovy/grails/plugin/springsecurity/cas/test/AbstractCasSpec.groovy:
##########
@@ -0,0 +1,178 @@
+/*
+ *  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
+ *
+ *    https://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 grails.plugin.springsecurity.cas.test
+
+import grails.testing.mixin.integration.Integration
+import spock.lang.Requires
+import spock.lang.Specification
+
+import java.net.http.HttpClient
+import java.net.http.HttpRequest
+import java.net.http.HttpResponse
+import java.time.Duration
+
+/**
+ * Drives the CAS protocol by hand against the containerised CAS server.
+ *
+ * <p>The app and CAS are both reached on {@code localhost}, and cookies ignore ports, so a single
+ * cookie jar would send the app's session cookie to CAS and vice versa. Each side therefore gets
+ * its own client, and redirects are followed explicitly so the right one is used for each hop.</p>
+ */
+@Integration
+@Requires({ isDockerAvailable() })
+abstract class AbstractCasSpec extends Specification {
+
+    HttpClient appClient
+    HttpClient casClient
+
+    /**
+     * A cookie-less client for the calls CAS makes to the application on its own connection, such
+     * as the back-channel logout request. Using the authenticated client instead would hide which
+     * filter acted: an unconsumed POST to the CAS login path fails authentication and clears the
+     * session by itself, which looks the same from outside as single signout working.
+     */
+    HttpClient backChannelClient
+
+    /** The service ticket CAS issued during the most recent {@link #login} call. */
+    String lastServiceTicket
+
+    void setup() {
+        appClient = newClient()
+        casClient = newClient()
+        backChannelClient = newClient()
+    }
+
+    /**
+     * Mirrors the probe used by the hibernate7 specs. Checking the socket avoids the macOS failure
+     * mode where asking Testcontainers for a client throws when the daemon API version differs.
+     */
+    static boolean isDockerAvailable() {
+        List<String> candidates = [
+                System.getProperty('user.home') + '/.docker/run/docker.sock',
+                '/var/run/docker.sock',
+                System.getenv('DOCKER_HOST') ?: ''
+        ]
+        candidates.any { it && new File(it).exists() }
+    }
+
+    String getAppBaseUrl() {
+        "http://localhost:${serverPort}"
+    }
+
+    String getCasBaseUrl() {
+        CasContainerHolder.serverUrlPrefix
+    }
+
+    /** CAS redirects to the container-visible host name; the test client has to use localhost. */
+    static String toLocalUrl(String url) {
+        url.replace(CasTestConfig.CONTAINER_VISIBLE_HOST, 'localhost')
+    }
+
+    HttpResponse<String> get(HttpClient client, String url) {
+        client.send(HttpRequest.newBuilder(URI.create(url)).GET().build(),
+                HttpResponse.BodyHandlers.ofString())
+    }
+
+    HttpResponse<String> postForm(HttpClient client, String url, Map<String, String> form) {
+        String body = form.collect { k, v -> "${encode(k)}=${encode(v)}" }.join('&')
+        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
+                .header('Content-Type', 'application/x-www-form-urlencoded')
+                .POST(HttpRequest.BodyPublishers.ofString(body))
+                .build()
+        client.send(request, HttpResponse.BodyHandlers.ofString())
+    }
+
+    static String location(HttpResponse<?> response) {
+        response.headers().firstValue('Location').orElse(null)
+    }
+
+    /**
+     * Authenticates at CAS and returns the service ticket URL it redirects back to, already
+     * rewritten to localhost.
+     */
+    String authenticateAtCas(String loginUrl, String username, String password) {
+        HttpResponse<String> form = get(casClient, loginUrl)
+        assert form.statusCode() == 200
+        String execution = extractExecution(form.body())
+        assert execution, 'CAS login form did not contain an execution token'
+
+        HttpResponse<String> submitted = postForm(casClient, loginUrl,
+                [username: username, password: password, execution: execution, _eventId: 'submit'])
+        assert submitted.statusCode() == 302,
+                "expected CAS to redirect after login but got ${submitted.statusCode()}"
+        toLocalUrl(location(submitted))
+    }
+
+    /** Full login: hit a secured URL, authenticate at CAS, and follow the ticket back to the app. */
+    HttpResponse<String> login(String securedPath, String username, String password) {
+        HttpResponse<String> challenge = get(appClient, appBaseUrl + securedPath)
+        assert challenge.statusCode() == 302,
+                "expected a redirect to CAS but got ${challenge.statusCode()}"
+        String ticketUrl = authenticateAtCas(location(challenge), username, password)
+        lastServiceTicket = extractTicket(ticketUrl)
+        assert lastServiceTicket, "CAS redirect carried no service ticket: ${ticketUrl}"
+        followRedirects(get(appClient, ticketUrl))
+    }
+
+    HttpResponse<String> followRedirects(HttpResponse<String> response, int limit = 5) {
+        HttpResponse<String> current = response
+        for (int i = 0; i < limit && current.statusCode() in [301, 302, 303, 307, 308]; i++) {
+            current = get(appClient, absolute(location(current)))
+        }
+        current
+    }
+
+    String absolute(String location) {
+        String local = toLocalUrl(location)
+        local.startsWith('http') ? local : appBaseUrl + local
+    }
+
+    /** The message CAS sends to a service on back-channel logout. */
+    static String logoutRequest(String serviceTicket) {
+        """<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" \
+ID="LR-1-${System.nanoTime()}" Version="2.0" IssueInstant="2026-01-01T00:00:00Z">\
+<saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">@NOT_USED@</saml:NameID>\
+<samlp:SessionIndex>${serviceTicket}</samlp:SessionIndex>\
+</samlp:LogoutRequest>"""
+    }
+
+    static String extractTicket(String url) {
+        def matcher = url =~ /[?&]ticket=([^&]+)/
+        matcher.find() ? matcher.group(1) : null
+    }
+
+    private static String extractExecution(String html) {
+        def matcher = html =~ /name="execution"\s+value="([^"]+)"/
+        matcher.find() ? matcher.group(1) : null
+    }
+
+    private static String encode(String value) {
+        URLEncoder.encode(value, 'UTF-8')
+    }
+
+    private static HttpClient newClient() {
+        CookieManager cookieManager = new CookieManager(null, CookiePolicy.ACCEPT_ALL)
+        HttpClient.newBuilder()
+                .cookieHandler(cookieManager)
+                .followRedirects(HttpClient.Redirect.NEVER)
+                .connectTimeout(Duration.ofSeconds(30))
+                .build()

Review Comment:
   Agreed, and this is the failure mode most likely to bite in CI. Fixed in 2391490: added a `REQUEST_TIMEOUT` of 60s applied to both `get()` and `postForm()` via `HttpRequest.timeout(...)`, so a container that accepts the connection but never answers fails the spec in a minute instead of hanging until the runner's job-level kill. Kept the 30s `connectTimeout` as-is.
   
   I went with a per-request timeout rather than `@Timeout` so the failure points at the specific stalled call rather than the whole feature method.



##########
grails-spring-security/cas/plugin/src/main/groovy/grails/plugin/springsecurity/cas/SpringSecurityCasGrailsPlugin.groovy:
##########
@@ -87,10 +91,29 @@ class SpringSecurityCasGrailsPlugin extends Plugin {
 
             if (conf.cas.useSingleSignout) {
 
-                // session fixation prevention breaks single signout because
-                // the service ticket is mapped to the session id which changes
+                // Session fixation prevention breaks single signout because the service ticket is
+                // mapped to the session id, which changes when the session is replaced on login.
+                // Disabling it is a security trade-off the application has opted into, so say so.
+                String message = '''
+    WARNING: cas.useSingleSignout is enabled, so session fixation prevention has been disabled.
+    CAS maps the service ticket to the HTTP session id, and a logout request cannot be matched to a
+    session that was replaced when the user authenticated. Set cas.useSingleSignout to false to keep
+    session fixation prevention and handle logout in the application instead.
+    '''
+                println message

Review Comment:
   You're right that the annotation was dead, and right about the reasoning: the docs this PR adds say the plugin logs a warning, and an app capturing logs but not stdout would never see a `println`. Restored `log.warn` alongside the `println` in 2391490, matching `SpringSecurityCoreGrailsPlugin:423-424`. Whitespace-only line trimmed too.



##########
grails-test-examples/spring-security/cas/README.md:
##########
@@ -14,21 +14,77 @@ See the License for the specific language governing permissions and
 limitations under the License.
 -->
 
-This is a CAS-enabled test application.  To run it successfully, a CAS
-server is required.  The URL for the CAS server is configured in the
-[application.groovy](test1/grails-app/conf/application.groovy)
-file.  Setting up a CAS server is out of the scope of this document, but
-good places to start are [Apereo CAS GitHub](https://github.com/apereo/cas)
-and the [CAS Initializr](https://getcas.apereo.org/ui) service.
+This is a CAS-enabled test application. It no longer needs a hand-run CAS server: an
+[Apereo CAS](https://github.com/apereo/cas) server is started in a container by
+[CasContainerHolder](test1/src/main/groovy/grails/plugin/springsecurity/cas/test/CasContainerHolder.groovy),
+and [CasTestEnvironmentPostProcessor](test1/src/main/groovy/grails/plugin/springsecurity/cas/test/CasTestEnvironmentPostProcessor.groovy)
+points the CAS plugin at it before the application context is built. Docker (or a compatible
+container runtime) is therefore required to run or test this application.
 
-The test application can be run with:
+## Running the tests
 
-`./gradlew :testapp-spring-security-cas-test1:bootRun`
+The application is exercised under three configurations, selected with the `TESTCONFIG` system
+property. Each has to be its own run, because the configuration is applied at application startup.
+
+The specs only run when one of these is selected, so a build that covers every Spring Security
+example does not repeat a configuration that the dedicated per-configuration CI job already runs.
+Running `check` without `-DTESTCONFIG` therefore reports no CAS tests.
+
+| `TESTCONFIG` | Configuration | Covered by |
+|---|---|---|
+| `cas` (default) | `proxyCallbackUrl` and `proxyReceptorUrl` unset, single signout enabled | `CasLoginSpec`, `CasNoProxyReceptorSpec`, `CasSingleSignOutSpec` |
+| `casProxy` | both proxy settings configured, single signout enabled | `CasLoginSpec`, `CasProxyTicketSpec`, `CasSingleSignOutSpec` |
+| `casNoSingleSignout` | `cas.useSingleSignout` left at its default | `CasLoginSpec`, `CasNoProxyReceptorSpec`, `CasNoSingleSignOutSpec` |
+
+```
+./gradlew :grails-test-examples-spring-security-cas-test1:check -DTESTCONFIG=cas
+./gradlew :grails-test-examples-spring-security-cas-test1:check -DTESTCONFIG=casProxy
+./gradlew :grails-test-examples-spring-security-cas-test1:check -DTESTCONFIG=casNoSingleSignout
+```
+
+`cas.useSingleSignout` is opt-in as of Grails 8. The app enables it for the first two configurations
+so the single signout filter is exercised; enabling it disables session fixation prevention, and the
+plugin warns about that at startup.
+
+The CAS image is pinned to a known-good version and can be overridden:
+
+```
+./gradlew :grails-test-examples-spring-security-cas-test1:check -PcasContainerVersion=7.3.6
+```
+
+The specs skip themselves when no Docker daemon is available.
+
+## Running the application
+
+```
+./gradlew :grails-test-examples-spring-security-cas-test1:bootRun
+```
 
 The test application URLs are:
 * [http://localhost:8081/secure/admins](http://localhost:8081/secure/admins)
 * [http://localhost:8081/secure/users](http://localhost:8081/secure/users)
+* [http://localhost:8081/secure/proxyStatus](http://localhost:8081/secure/proxyStatus) — asks CAS for a proxy ticket

Review Comment:
   Correct, and the root cause was worse than the docs: `bootRun` forks its own JVM, so `-DTESTCONFIG` never reached the app at all — there was no way to exercise the endpoint by hand.
   
   Rather than just note the limitation, `build.gradle` now forwards `TESTCONFIG` and `casContainerVersion` to `bootRun`, and the README documents `bootRun -DTESTCONFIG=casProxy` (2391490). Verified end-to-end: drove a login against the container and `/secure/proxyStatus` returned `PROXY_TICKET:PT-2-1B26izig…` instead of `NO_PROXY_TICKET`.



##########
grails-test-examples/spring-security/cas/test1/grails-app/controllers/grails/plugin/springsecurity/cas/test/SecureController.groovy:
##########
@@ -0,0 +1,58 @@
+/*
+ *  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
+ *
+ *    https://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 grails.plugin.springsecurity.cas.test
+
+import grails.plugin.springsecurity.annotation.Secured
+import org.apereo.cas.client.authentication.AttributePrincipal
+import org.springframework.security.cas.authentication.CasAuthenticationToken
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.context.SecurityContextHolder
+
+class SecureController {
+

Review Comment:
   Fixed in 2391490, 4 spaces per `.editorconfig`. `git diff --ignore-all-space` on that file is empty, so it's purely whitespace.



##########
grails-test-examples/spring-security/cas/test1/src/integration-test/groovy/grails/plugin/springsecurity/cas/test/CasSingleSignOutSpec.groovy:
##########
@@ -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
+ *
+ *    https://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 grails.plugin.springsecurity.cas.test
+
+import spock.lang.IgnoreIf
+
+import java.net.http.HttpResponse
+
+/**
+ * Covers single sign-out, which the plugin enables by default via {@code cas.useSingleSignout} by

Review Comment:
   Reworded in 2391490 to "which the app opts into via `cas.useSingleSignout`", and added a pointer to `CasNoSingleSignOutSpec` as the spec covering the default.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] fix(cas): repair proxy receptor and single sign-out, add integration tests [grails-core]

Posted by "sbglasius (via GitHub)" <gi...@apache.org>.
sbglasius commented on code in PR #16174:
URL: https://github.com/apache/grails-core/pull/16174#discussion_r3825112454


##########
grails-test-examples/spring-security/cas/test1/src/integration-test/groovy/grails/plugin/springsecurity/cas/test/CasLoginSpec.groovy:
##########
@@ -0,0 +1,92 @@
+/*
+ *  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
+ *
+ *    https://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 grails.plugin.springsecurity.cas.test
+
+
+import spock.lang.IgnoreIf
+
+import java.net.http.HttpResponse
+
+/**
+ * Covers the CAS handshake the plugin exists to perform: redirect to CAS, service ticket back,
+ * ticket validated against the CAS server, and the resulting authentication carrying the roles
+ * looked up in GORM.
+ */
+@IgnoreIf({ !CasTestConfig.configured })
+class CasLoginSpec extends AbstractCasSpec {
+
+    void 'an unauthenticated request is redirected to the CAS login page for this service'() {
+        when:
+        HttpResponse<String> response = get(appClient, "${appBaseUrl}/secure/users")
+
+        then:
+        response.statusCode() == 302
+
+        and: 'the redirect targets the CAS server the container is running'
+        location(response).startsWith("${casBaseUrl}/login")
+
+        and: 'it asks CAS to send the ticket back to this application'
+        location(response).contains('service=')
+        location(response).contains(URLEncoder.encode(CasTestConfig.serviceUrl(serverPort), 'UTF-8'))
+    }
+
+    void 'a user authenticated at CAS reaches a ROLE_USER action'() {
+        when:
+        HttpResponse<String> response = login('/secure/users', 'user', 'user')
+
+        then:
+        response.statusCode() == 200
+        response.body().contains('Logged in with ROLE_USER')
+    }
+
+    void 'an admin authenticated at CAS reaches a ROLE_ADMIN action'() {
+        when:
+        HttpResponse<String> response = login('/secure/admins', 'admin', 'admin')
+
+        then:
+        response.statusCode() == 200
+        response.body().contains('Logged in with ROLE_ADMIN')
+    }
+
+    void 'a user without the role is denied a ROLE_ADMIN action'() {
+        given:
+        login('/secure/users', 'user', 'user')
+
+        when:
+        HttpResponse<String> response = followRedirects(get(appClient, "${appBaseUrl}/secure/admins"))
+
+        then: 'access is refused rather than granted'
+        response.statusCode() == 403 || !response.body().contains('Logged in with ROLE_ADMIN')
+    }
+
+    void 'bad credentials do not authenticate'() {
+        when:
+        HttpResponse<String> challenge = get(appClient, "${appBaseUrl}/secure/users")
+        String loginUrl = location(challenge)
+        HttpResponse<String> form = get(casClient, loginUrl)
+        String execution = form.body().find(/name="execution"\s+value="([^"]+)"/) { full, token -> token }
+        HttpResponse<String> submitted = postForm(casClient, loginUrl,

Review Comment:
   Done in 2391490 — it now calls `extractExecution` from the base class with the same `assert execution` guard the other paths get.
   
   One thing your comment surfaced: `extractExecution` was `private static`, so the reuse you suggested wouldn't have compiled. Widened it to package-private to match the sibling helpers (`extractTicket`, `logoutRequest`).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] fix(cas): repair proxy receptor and single sign-out, add integration tests [grails-core]

Posted by "matrei (via GitHub)" <gi...@apache.org>.
matrei commented on code in PR #16174:
URL: https://github.com/apache/grails-core/pull/16174#discussion_r3821173034


##########
.github/workflows/gradle.yml:
##########
@@ -567,6 +567,46 @@ jobs:
           --stacktrace
           -DTESTCONFIG=${{ matrix.test-config }}
           -PskipCodeStyle
+  casSecurityConfig:
+    name: "Spring Security CAS Functional Tests"
+    if: ${{ !contains(github.event.head_commit.message, '[skip tests]') }}
+    strategy:
+      fail-fast: false
+      matrix:
+        # The CAS test app authenticates against a real Apereo CAS server started via Testcontainers.
+        # 'cas' leaves the proxy settings unset and is the default, so it is also covered by the main
+        # security job; 'casProxy' exercises the proxy-granting-ticket callback; 'casNoSingleSignout'
+        # asserts the shipped default, where single signout is not enabled.
+        # TESTCONFIG is applied at app startup, so every config must be its own run.
+        # Carried over from the standalone grails-spring-security CI convention - add, don't remove.
+        test-config: [ 'cas', 'casProxy', 'casNoSingleSignout' ]

Review Comment:
   Will the 'cas' test-config will be run twice? Once in `security` job and then again here?



##########
grails-spring-security/cas/plugin/src/main/groovy/grails/plugin/springsecurity/cas/SpringSecurityCasGrailsPlugin.groovy:
##########
@@ -35,11 +36,14 @@ import org.springframework.security.cas.authentication.CasAuthenticationProvider
 import org.springframework.security.cas.authentication.NullStatelessTicketCache
 import org.springframework.security.cas.web.CasAuthenticationEntryPoint
 import org.springframework.security.cas.web.CasAuthenticationFilter
+import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy
 
+import grails.plugin.springsecurity.BeanTypeResolver
 import grails.plugin.springsecurity.SecurityFilterPosition
 import grails.plugin.springsecurity.SpringSecurityUtils
 import grails.plugins.Plugin
 
+@Slf4j
 @CompileStatic
 class SpringSecurityCasGrailsPlugin extends Plugin {

Review Comment:
   While we are here:
   - Should we bump `grailsVersion`?
   - Jasig -> Apereo?
   - organization metadata?
   - issueManagement metadata?
   - scm metadata?



##########
grails-test-examples/spring-security/cas/README.md:
##########
@@ -16,19 +16,73 @@ limitations under the License.
 
 This is a CAS-enabled test application.  To run it successfully, a CAS
 server is required.  The URL for the CAS server is configured in the
-[application.groovy](test1/grails-app/conf/application.groovy)
-file.  Setting up a CAS server is out of the scope of this document, but
-good places to start are [Apereo CAS GitHub](https://github.com/apereo/cas)
-and the [CAS Initializr](https://getcas.apereo.org/ui) service.
+This is a CAS-enabled test application. It no longer needs a hand-run CAS server: an

Review Comment:
   The seems to be some leftover here...



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org