Skip to content

Commit a8da325

Browse files
committed
fix: Move SPRING_REDIRECT_URI cookie invalidation to correct place
Invalidating SPRING_REDIRECT_URI cookie right after the cookie is read. JIRA: LX-1022 risk: high
1 parent 9f60b95 commit a8da325

File tree

2 files changed

+12
-54
lines changed

2 files changed

+12
-54
lines changed

Diff for: gooddata-server-oauth2-autoconfigure/src/main/kotlin/CookieServerRequestCache.kt

+3-20
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,12 @@
1818
*/
1919
package com.gooddata.oauth2.server
2020

21+
import java.net.URI
2122
import org.springframework.http.HttpMethod
22-
import org.springframework.http.HttpStatus
23-
import org.springframework.http.server.reactive.ServerHttpRequest
2423
import org.springframework.security.web.server.savedrequest.ServerRequestCache
2524
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers
2625
import org.springframework.web.server.ServerWebExchange
2726
import reactor.core.publisher.Mono
28-
import java.net.URI
2927

3028
/**
3129
* An implementation of [ServerRequestCache] that saves the requested URI in a cookie.
@@ -51,22 +49,7 @@ class CookieServerRequestCache(private val cookieService: ReactiveCookieService)
5149
Mono.just(exchange)
5250
.flatMap { cookieService.decodeCookie(it, SPRING_REDIRECT_URI) }
5351
.map { URI.create(it) }
52+
.doOnNext { cookieService.invalidateCookie(exchange, SPRING_REDIRECT_URI) }
5453

55-
override fun removeMatchingRequest(exchange: ServerWebExchange): Mono<ServerHttpRequest> {
56-
return Mono.just(exchange)
57-
.flatMap { webExchange ->
58-
if (webExchange.response.statusCode == HttpStatus.UNAUTHORIZED) {
59-
// For 401 responses, always preserve the redirect URI
60-
// If there's an existing one, keep it
61-
// If not, the saveRequest() will set it later
62-
Mono.just(webExchange.request)
63-
} else {
64-
// For non-401 responses (like successful authentication),
65-
// we can safely invalidate the cookie as we don't need it anymore
66-
Mono.just(webExchange)
67-
.doOnNext { cookieService.invalidateCookie(it, SPRING_REDIRECT_URI) }
68-
.thenReturn(webExchange.request)
69-
}
70-
}
71-
}
54+
override fun removeMatchingRequest(exchange: ServerWebExchange) = Mono.just(exchange.request)
7255
}

Diff for: gooddata-server-oauth2-autoconfigure/src/test/kotlin/CookieServerRequestCacheTest.kt

+9-34
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ import io.mockk.slot
2323
import io.mockk.spyk
2424
import io.mockk.verify
2525
import io.netty.handler.codec.http.cookie.CookieHeaderNames
26+
import java.net.URI
27+
import java.time.Duration
28+
import java.time.Instant
2629
import org.intellij.lang.annotations.Language
2730
import org.junit.jupiter.api.Test
2831
import org.springframework.http.HttpCookie
29-
import org.springframework.http.HttpStatus
3032
import org.springframework.mock.http.server.reactive.MockServerHttpRequest
3133
import org.springframework.mock.web.server.MockServerWebExchange
3234
import org.springframework.util.CollectionUtils
@@ -35,10 +37,6 @@ import reactor.core.publisher.Mono
3537
import strikt.api.expectThat
3638
import strikt.assertions.isEqualTo
3739
import strikt.assertions.isTrue
38-
import java.net.URI
39-
import java.time.Duration
40-
import java.time.Instant
41-
import strikt.assertions.isNotNull
4240

4341
internal class CookieServerRequestCacheTest {
4442

@@ -68,7 +66,9 @@ internal class CookieServerRequestCacheTest {
6866
"""
6967

7068
private val client: AuthenticationStoreClient = mockk {
71-
mockCookieSecurityProperties(this, ORG_ID,
69+
mockCookieSecurityProperties(
70+
this,
71+
ORG_ID,
7272
CookieSecurityProperties(
7373
keySet = CleartextKeysetHandle.read(JsonKeysetReader.withBytes(keyset.toByteArray())),
7474
lastRotation = Instant.now(),
@@ -169,11 +169,10 @@ internal class CookieServerRequestCacheTest {
169169
}
170170

171171
@Test
172-
fun `should preserve redirect URI during 401 response`() {
172+
fun `should invalidate cookie after reading redirect URI`() {
173173
val webExchange = mockk<ServerWebExchange> {
174174
every { request.uri.host } returns "localhost"
175175
every { attributes[OrganizationWebFilter.ORGANIZATION_CACHE_KEY] } returns Organization(ORG_ID)
176-
every { response.statusCode } returns HttpStatus.UNAUTHORIZED
177176
}
178177

179178
val redirect = "/requestURI?query=true"
@@ -182,36 +181,12 @@ internal class CookieServerRequestCacheTest {
182181
HttpCookie(SPRING_REDIRECT_URI, cookieSerializer.encodeCookieBlocking(webExchange, redirect))
183182
)
184183
)
185-
exchange.response.statusCode = HttpStatus.UNAUTHORIZED
186184

187-
val request = cache.removeMatchingRequest(exchange).block()
188-
189-
// Verify the cookie was not invalidated
190-
verify(exactly = 0) { cookieService.invalidateCookie(exchange, SPRING_REDIRECT_URI) }
191-
expectThat(request).isNotNull()
192-
}
193-
194-
@Test
195-
fun `should clear redirect URI for non-401 response`() {
196-
val webExchange = mockk<ServerWebExchange> {
197-
every { request.uri.host } returns "localhost"
198-
every { attributes[OrganizationWebFilter.ORGANIZATION_CACHE_KEY] } returns Organization(ORG_ID)
199-
every { response.statusCode } returns HttpStatus.FOUND
200-
}
201-
202-
val redirect = "/requestURI?query=true"
203-
val exchange = MockServerWebExchange.from(
204-
MockServerHttpRequest.get("http://localhost/").cookie(
205-
HttpCookie(SPRING_REDIRECT_URI, cookieSerializer.encodeCookieBlocking(webExchange, redirect))
206-
)
207-
)
208-
exchange.response.statusCode = HttpStatus.FOUND
185+
val uri = cache.getRedirectUri(exchange).block()
209186

210-
val request = cache.removeMatchingRequest(exchange).block()
187+
expectThat(uri).isEqualTo(URI.create(redirect))
211188

212-
// Verify the cookie was invalidated
213189
verify(exactly = 1) { cookieService.invalidateCookie(exchange, SPRING_REDIRECT_URI) }
214-
expectThat(request).isNotNull()
215190
}
216191

217192
companion object {

0 commit comments

Comments
 (0)