Skip to content

Commit 10394c8

Browse files
committed
OTT Tests use Mocks Instead of Comparing Expires
Previously, expires was compared to test if a custom implementations were used. Now the tests verify this through mocks. Closes gh-16515
1 parent b566501 commit 10394c8

File tree

2 files changed

+89
-51
lines changed

2 files changed

+89
-51
lines changed

config/src/test/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurerTests.java

+43-27
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.io.IOException;
2020
import java.time.Duration;
2121
import java.time.Instant;
22-
import java.time.ZoneOffset;
2322

2423
import jakarta.servlet.ServletException;
2524
import jakarta.servlet.http.HttpServletRequest;
@@ -32,8 +31,10 @@
3231
import org.springframework.context.annotation.Bean;
3332
import org.springframework.context.annotation.Configuration;
3433
import org.springframework.context.annotation.Import;
34+
import org.springframework.security.authentication.ott.DefaultOneTimeToken;
3535
import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest;
3636
import org.springframework.security.authentication.ott.OneTimeToken;
37+
import org.springframework.security.authentication.ott.OneTimeTokenService;
3738
import org.springframework.security.config.Customizer;
3839
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3940
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -44,7 +45,6 @@
4445
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
4546
import org.springframework.security.web.SecurityFilterChain;
4647
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
47-
import org.springframework.security.web.authentication.ott.DefaultGenerateOneTimeTokenRequestResolver;
4848
import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenRequestResolver;
4949
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
5050
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler;
@@ -55,6 +55,11 @@
5555

5656
import static org.assertj.core.api.Assertions.assertThat;
5757
import static org.assertj.core.api.Assertions.assertThatException;
58+
import static org.mockito.ArgumentMatchers.any;
59+
import static org.mockito.ArgumentMatchers.eq;
60+
import static org.mockito.BDDMockito.given;
61+
import static org.mockito.Mockito.mock;
62+
import static org.mockito.Mockito.verify;
5863
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
5964
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
6065
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
@@ -72,6 +77,15 @@ public class OneTimeTokenLoginConfigurerTests {
7277
@Autowired(required = false)
7378
MockMvc mvc;
7479

80+
@Autowired(required = false)
81+
private GenerateOneTimeTokenRequestResolver resolver;
82+
83+
@Autowired(required = false)
84+
private OneTimeTokenService tokenService;
85+
86+
@Autowired(required = false)
87+
private OneTimeTokenGenerationSuccessHandler tokenGenerationSuccessHandler;
88+
7589
@Test
7690
void oneTimeTokenWhenCorrectTokenThenCanAuthenticate() throws Exception {
7791
this.spring.register(OneTimeTokenDefaultConfig.class).autowire();
@@ -202,21 +216,18 @@ Please provide it as a bean or pass it to the oneTimeTokenLogin() DSL.
202216

203217
@Test
204218
void oneTimeTokenWhenCustomTokenExpirationTimeSetThenAuthenticate() throws Exception {
205-
this.spring.register(OneTimeTokenConfigWithCustomTokenExpirationTime.class).autowire();
206-
this.mvc.perform(post("/ott/generate").param("username", "user").with(csrf()))
207-
.andExpectAll(status().isFound(), redirectedUrl("/login/ott"));
208-
209-
OneTimeToken token = getLastToken();
210-
211-
this.mvc.perform(post("/login/ott").param("token", token.getTokenValue()).with(csrf()))
212-
.andExpectAll(status().isFound(), redirectedUrl("/"), authenticated());
213-
assertThat(getCurrentMinutes(token.getExpiresAt())).isEqualTo(10);
214-
}
215-
216-
private int getCurrentMinutes(Instant expiresAt) {
217-
int expiresMinutes = expiresAt.atZone(ZoneOffset.UTC).getMinute();
218-
int currentMinutes = Instant.now().atZone(ZoneOffset.UTC).getMinute();
219-
return expiresMinutes - currentMinutes;
219+
this.spring.register(OneTimeTokenConfigWithCustomImpls.class).autowire();
220+
GenerateOneTimeTokenRequest expectedGenerateRequest = new GenerateOneTimeTokenRequest("username-123",
221+
Duration.ofMinutes(10));
222+
OneTimeToken ott = new DefaultOneTimeToken("token-123", expectedGenerateRequest.getUsername(),
223+
Instant.now().plus(expectedGenerateRequest.getExpiresIn()));
224+
given(this.resolver.resolve(any())).willReturn(expectedGenerateRequest);
225+
given(this.tokenService.generate(expectedGenerateRequest)).willReturn(ott);
226+
this.mvc.perform(post("/ott/generate").param("username", "user").with(csrf()));
227+
228+
verify(this.resolver).resolve(any());
229+
verify(this.tokenService).generate(expectedGenerateRequest);
230+
verify(this.tokenGenerationSuccessHandler).handle(any(), any(), eq(ott));
220231
}
221232

222233
private OneTimeToken getLastToken() {
@@ -228,35 +239,40 @@ private OneTimeToken getLastToken() {
228239
@Configuration(proxyBeanMethods = false)
229240
@EnableWebSecurity
230241
@Import(UserDetailsServiceConfig.class)
231-
static class OneTimeTokenConfigWithCustomTokenExpirationTime {
242+
static class OneTimeTokenConfigWithCustomImpls {
232243

233244
@Bean
234245
SecurityFilterChain securityFilterChain(HttpSecurity http,
246+
GenerateOneTimeTokenRequestResolver ottRequestResolver, OneTimeTokenService ottTokenService,
235247
OneTimeTokenGenerationSuccessHandler ottSuccessHandler) throws Exception {
248+
236249
// @formatter:off
237-
http
250+
http
238251
.authorizeHttpRequests((authz) -> authz
239252
.anyRequest().authenticated()
240253
)
241254
.oneTimeTokenLogin((ott) -> ott
255+
.generateRequestResolver(ottRequestResolver)
256+
.tokenService(ottTokenService)
242257
.tokenGenerationSuccessHandler(ottSuccessHandler)
243258
);
244259
// @formatter:on
245260
return http.build();
246261
}
247262

248263
@Bean
249-
TestOneTimeTokenGenerationSuccessHandler ottSuccessHandler() {
250-
return new TestOneTimeTokenGenerationSuccessHandler();
264+
GenerateOneTimeTokenRequestResolver generateOneTimeTokenRequestResolver() {
265+
return mock(GenerateOneTimeTokenRequestResolver.class);
251266
}
252267

253268
@Bean
254-
GenerateOneTimeTokenRequestResolver generateOneTimeTokenRequestResolver() {
255-
DefaultGenerateOneTimeTokenRequestResolver delegate = new DefaultGenerateOneTimeTokenRequestResolver();
256-
return (request) -> {
257-
GenerateOneTimeTokenRequest generate = delegate.resolve(request);
258-
return new GenerateOneTimeTokenRequest(generate.getUsername(), Duration.ofSeconds(600));
259-
};
269+
OneTimeTokenService ottService() {
270+
return mock(OneTimeTokenService.class);
271+
}
272+
273+
@Bean
274+
OneTimeTokenGenerationSuccessHandler ottSuccessHandler() {
275+
return mock(OneTimeTokenGenerationSuccessHandler.class);
260276
}
261277

262278
}

config/src/test/kotlin/org/springframework/security/config/annotation/web/OneTimeTokenLoginDslTests.kt

+46-24
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
package org.springframework.security.config.annotation.web
1818

19+
import io.mockk.every
20+
import io.mockk.justRun
21+
import io.mockk.mockk
22+
import io.mockk.verify
1923
import jakarta.servlet.http.HttpServletRequest
2024
import jakarta.servlet.http.HttpServletResponse
2125
import org.assertj.core.api.Assertions.assertThat
@@ -25,7 +29,10 @@ import org.springframework.beans.factory.annotation.Autowired
2529
import org.springframework.context.annotation.Bean
2630
import org.springframework.context.annotation.Configuration
2731
import org.springframework.context.annotation.Import
32+
import org.springframework.security.authentication.ott.DefaultOneTimeToken
33+
import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest
2834
import org.springframework.security.authentication.ott.OneTimeToken
35+
import org.springframework.security.authentication.ott.OneTimeTokenService
2936
import org.springframework.security.config.annotation.web.builders.HttpSecurity
3037
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
3138
import org.springframework.security.config.test.SpringTestContext
@@ -38,6 +45,7 @@ import org.springframework.security.test.web.servlet.response.SecurityMockMvcRes
3845
import org.springframework.security.web.SecurityFilterChain
3946
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler
4047
import org.springframework.security.web.authentication.ott.DefaultGenerateOneTimeTokenRequestResolver
48+
import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenRequestResolver
4149
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler
4250
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler
4351
import org.springframework.test.web.servlet.MockMvc
@@ -60,6 +68,15 @@ class OneTimeTokenLoginDslTests {
6068
@Autowired
6169
private lateinit var mockMvc: MockMvc
6270

71+
@Autowired(required = false)
72+
private lateinit var resolver: GenerateOneTimeTokenRequestResolver
73+
74+
@Autowired(required = false)
75+
private lateinit var tokenService: OneTimeTokenService
76+
77+
@Autowired(required = false)
78+
private lateinit var tokenGenerationSuccessHandler: OneTimeTokenGenerationSuccessHandler
79+
6380
@Test
6481
fun `oneTimeToken when correct token then can authenticate`() {
6582
spring.register(OneTimeTokenConfig::class.java).autowire()
@@ -110,29 +127,22 @@ class OneTimeTokenLoginDslTests {
110127
}
111128

112129
@Test
113-
fun `oneTimeToken when custom resolver set then use custom token`() {
114-
spring.register(OneTimeTokenConfigWithCustomTokenResolver::class.java).autowire()
115-
130+
fun `oneTimeToken when custom impls set then used`() {
131+
spring.register(OneTimeTokenConfigWithCustomImpls::class.java).autowire()
132+
val expectedGenerateRequest = GenerateOneTimeTokenRequest("username-123", Duration.ofMinutes(10));
133+
val ott = DefaultOneTimeToken("token-123", expectedGenerateRequest.username, Instant.now().plus(expectedGenerateRequest.expiresIn))
134+
every { resolver.resolve(any()) } returns expectedGenerateRequest
135+
every { tokenService.generate(expectedGenerateRequest) } returns ott
136+
justRun { tokenGenerationSuccessHandler.handle(any(), any(), eq(ott)) }
116137
this.mockMvc.perform(
117138
MockMvcRequestBuilders.post("/ott/generate").param("username", "user")
118139
.with(SecurityMockMvcRequestPostProcessors.csrf())
119-
).andExpectAll(
120-
MockMvcResultMatchers
121-
.status()
122-
.isFound(),
123-
MockMvcResultMatchers
124-
.redirectedUrl("/login/ott")
125140
)
126141

127-
val token = getLastToken()
128-
129-
assertThat(getCurrentMinutes(token!!.expiresAt)).isEqualTo(10)
130-
}
142+
verify { resolver.resolve(any()) }
143+
verify { tokenService.generate(expectedGenerateRequest) }
144+
verify { tokenGenerationSuccessHandler.handle(any(), any(), eq(ott)) }
131145

132-
private fun getCurrentMinutes(expiresAt: Instant): Int {
133-
val expiresMinutes = expiresAt.atZone(ZoneOffset.UTC).minute
134-
val currentMinutes = Instant.now().atZone(ZoneOffset.UTC).minute
135-
return expiresMinutes - currentMinutes
136146
}
137147

138148
private fun getLastToken(): OneTimeToken {
@@ -170,29 +180,41 @@ class OneTimeTokenLoginDslTests {
170180
@Configuration
171181
@EnableWebSecurity
172182
@Import(UserDetailsServiceConfig::class)
173-
open class OneTimeTokenConfigWithCustomTokenResolver {
183+
open class OneTimeTokenConfigWithCustomImpls {
174184

175185
@Bean
176-
open fun securityFilterChain(http: HttpSecurity, ottSuccessHandler: OneTimeTokenGenerationSuccessHandler): SecurityFilterChain {
186+
open fun securityFilterChain(http: HttpSecurity,
187+
ottRequestResolver: GenerateOneTimeTokenRequestResolver,
188+
ottService: OneTimeTokenService,
189+
ottSuccessHandler: OneTimeTokenGenerationSuccessHandler): SecurityFilterChain {
177190
// @formatter:off
178191
http {
179192
authorizeHttpRequests {
180193
authorize(anyRequest, authenticated)
181194
}
182195
oneTimeTokenLogin {
196+
generateRequestResolver = ottRequestResolver
197+
tokenService = ottService
183198
oneTimeTokenGenerationSuccessHandler = ottSuccessHandler
184-
generateRequestResolver = DefaultGenerateOneTimeTokenRequestResolver().apply {
185-
this.setExpiresIn(Duration.ofMinutes(10))
186-
}
187199
}
188200
}
189201
// @formatter:on
190202
return http.build()
191203
}
192204

193205
@Bean
194-
open fun ottSuccessHandler(): TestOneTimeTokenGenerationSuccessHandler {
195-
return TestOneTimeTokenGenerationSuccessHandler()
206+
open fun ottRequestResolver(): GenerateOneTimeTokenRequestResolver {
207+
return mockk()
208+
}
209+
210+
@Bean
211+
open fun ottService(): OneTimeTokenService {
212+
return mockk()
213+
}
214+
215+
@Bean
216+
open fun ottSuccessHandler(): OneTimeTokenGenerationSuccessHandler {
217+
return mockk()
196218
}
197219

198220
}

0 commit comments

Comments
 (0)