Skip to content

Commit 7f10897

Browse files
committed
SecurityMockMvcResultMatchers.withAuthorities(String...)
Closes gh-17974
1 parent 0e99324 commit 7f10897

File tree

3 files changed

+65
-1
lines changed

3 files changed

+65
-1
lines changed

docs/modules/ROOT/pages/whats-new.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ http.csrf((csrf) -> csrf.spa());
7070
* Made so that SLO still returns `<saml2:LogoutResponse>` even when validation fails
7171
* Removed Open SAML 4 support; applications should migrate to Open SAML 5
7272

73+
== Test
74+
75+
* https://github.com/spring-projects/spring-security/issues/17974[Add SecurityMockMvcResultMatchers.withAuthorities(String...)]
76+
7377
== Web
7478

7579
* Removed `MvcRequestMatcher` and `AntPathRequestMatcher` in favor of `PathPatternRequestMatcher`

test/src/main/java/org/springframework/security/test/web/servlet/response/SecurityMockMvcResultMatchers.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.security.test.web.servlet.response;
1818

1919
import java.util.ArrayList;
20+
import java.util.Arrays;
2021
import java.util.Collection;
2122
import java.util.List;
2223
import java.util.function.Consumer;
@@ -38,6 +39,7 @@
3839
import org.springframework.test.web.servlet.MockMvc;
3940
import org.springframework.test.web.servlet.MvcResult;
4041
import org.springframework.test.web.servlet.ResultMatcher;
42+
import org.springframework.util.Assert;
4143

4244
/**
4345
* Security related {@link MockMvc} {@link ResultMatcher}s.
@@ -96,6 +98,8 @@ public static final class AuthenticatedMatcher extends AuthenticationMatcher<Aut
9698

9799
private @Nullable Collection<? extends GrantedAuthority> expectedGrantedAuthorities;
98100

101+
private @Nullable Collection<String> expectedAuthorities;
102+
99103
private Predicate<GrantedAuthority> ignoreAuthorities = (authority) -> false;
100104

101105
private @Nullable Consumer<Authentication> assertAuthentication;
@@ -145,6 +149,20 @@ public void match(MvcResult result) {
145149
this.expectedGrantedAuthorities + " does not contain the same authorities as " + authorities,
146150
this.expectedGrantedAuthorities.containsAll(authorities));
147151
}
152+
if (this.expectedAuthorities != null) {
153+
AssertionErrors.assertTrue("Authentication cannot be null", auth != null);
154+
List<String> authorities = auth.getAuthorities()
155+
.stream()
156+
.filter(Predicate.not(this.ignoreAuthorities))
157+
.map(GrantedAuthority::getAuthority)
158+
.toList();
159+
AssertionErrors.assertTrue(
160+
authorities + " does not contain the same authorities as " + this.expectedAuthorities,
161+
this.expectedAuthorities.containsAll(authorities));
162+
AssertionErrors.assertTrue(
163+
this.expectedAuthorities + " does not contain the same authorities as " + authorities,
164+
authorities.containsAll(this.expectedAuthorities));
165+
}
148166
}
149167

150168
/**
@@ -206,6 +224,17 @@ public AuthenticatedMatcher withAuthenticationName(String expected) {
206224
return this;
207225
}
208226

227+
/**
228+
* Specifies the {@link GrantedAuthority#getAuthority()}
229+
* @param authorities the authorityNames
230+
* @return the {@link AuthenticatedMatcher} for further customization
231+
*/
232+
public AuthenticatedMatcher withAuthorities(String... authorities) {
233+
Assert.notNull(authorities, "authorities cannot be null");
234+
this.expectedAuthorities = Arrays.asList(authorities);
235+
return this;
236+
}
237+
209238
/**
210239
* Specifies the {@link Authentication#getAuthorities()}
211240
* @param expected the {@link Authentication#getAuthorities()}

test/src/test/java/org/springframework/security/test/web/servlet/response/SecurityMockWithAuthoritiesMvcResultMatchersTests.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.context.annotation.Configuration;
2929
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
3030
import org.springframework.security.core.GrantedAuthorities;
31+
import org.springframework.security.core.GrantedAuthority;
3132
import org.springframework.security.core.authority.SimpleGrantedAuthority;
3233
import org.springframework.security.core.userdetails.User;
3334
import org.springframework.security.core.userdetails.UserDetails;
@@ -53,6 +54,8 @@
5354
@WebAppConfiguration
5455
public class SecurityMockWithAuthoritiesMvcResultMatchersTests {
5556

57+
private static final String ROLE_CUSTOM = "ROLE_CUSTOM";
58+
5659
@Autowired
5760
private WebApplicationContext context;
5861

@@ -80,6 +83,12 @@ public void withAuthoritiesFailsIfNotAllRoles() throws Exception {
8083
() -> this.mockMvc.perform(formLogin()).andExpect(authenticated().withAuthorities(grantedAuthorities)));
8184
}
8285

86+
@Test
87+
public void withAuthoritiesStringSupportsCustomAuthority() throws Exception {
88+
this.mockMvc.perform(formLogin().user("custom"))
89+
.andExpect(authenticated().withAuthorities(ROLE_CUSTOM, GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY));
90+
}
91+
8392
@Configuration
8493
@EnableWebSecurity
8594
@EnableWebMvc
@@ -89,7 +98,8 @@ static class Config {
8998
UserDetailsService userDetailsService() {
9099
// @formatter:off
91100
UserDetails user = User.withDefaultPasswordEncoder().username("user").password("password").roles("ADMIN", "SELLER").build();
92-
return new InMemoryUserDetailsManager(user);
101+
UserDetails customAuthorityUser = User.withDefaultPasswordEncoder().username("custom").password("password").authorities(new CustomAuthority(ROLE_CUSTOM)).build();
102+
return new InMemoryUserDetailsManager(user, customAuthorityUser);
93103
// @formatter:on
94104
}
95105

@@ -105,4 +115,25 @@ String ok() {
105115

106116
}
107117

118+
/**
119+
* A custom {@link GrantedAuthority} for testing.
120+
*
121+
* @author Rob Winch
122+
* @since 7.0
123+
*/
124+
static class CustomAuthority implements GrantedAuthority {
125+
126+
private final String authority;
127+
128+
CustomAuthority(String authority) {
129+
this.authority = authority;
130+
}
131+
132+
@Override
133+
public String getAuthority() {
134+
return this.authority;
135+
}
136+
137+
}
138+
108139
}

0 commit comments

Comments
 (0)