Przeglądaj źródła

SecurityMockMvcResultMatchers.withAuthorities(String...)

Closes gh-17974
Rob Winch 1 tydzień temu
rodzic
commit
7f10897de3

+ 4 - 0
docs/modules/ROOT/pages/whats-new.adoc

@@ -70,6 +70,10 @@ http.csrf((csrf) -> csrf.spa());
 * Made so that SLO still returns `<saml2:LogoutResponse>` even when validation fails
 * Removed Open SAML 4 support; applications should migrate to Open SAML 5
 
+== Test
+
+* https://github.com/spring-projects/spring-security/issues/17974[Add SecurityMockMvcResultMatchers.withAuthorities(String...)]
+
 == Web
 
 * Removed `MvcRequestMatcher` and `AntPathRequestMatcher` in favor of `PathPatternRequestMatcher`

+ 29 - 0
test/src/main/java/org/springframework/security/test/web/servlet/response/SecurityMockMvcResultMatchers.java

@@ -17,6 +17,7 @@
 package org.springframework.security.test.web.servlet.response;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.function.Consumer;
@@ -38,6 +39,7 @@ import org.springframework.test.util.AssertionErrors;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MvcResult;
 import org.springframework.test.web.servlet.ResultMatcher;
+import org.springframework.util.Assert;
 
 /**
  * Security related {@link MockMvc} {@link ResultMatcher}s.
@@ -96,6 +98,8 @@ public final class SecurityMockMvcResultMatchers {
 
 		private @Nullable Collection<? extends GrantedAuthority> expectedGrantedAuthorities;
 
+		private @Nullable Collection<String> expectedAuthorities;
+
 		private Predicate<GrantedAuthority> ignoreAuthorities = (authority) -> false;
 
 		private @Nullable Consumer<Authentication> assertAuthentication;
@@ -145,6 +149,20 @@ public final class SecurityMockMvcResultMatchers {
 						this.expectedGrantedAuthorities + " does not contain the same authorities as " + authorities,
 						this.expectedGrantedAuthorities.containsAll(authorities));
 			}
+			if (this.expectedAuthorities != null) {
+				AssertionErrors.assertTrue("Authentication cannot be null", auth != null);
+				List<String> authorities = auth.getAuthorities()
+					.stream()
+					.filter(Predicate.not(this.ignoreAuthorities))
+					.map(GrantedAuthority::getAuthority)
+					.toList();
+				AssertionErrors.assertTrue(
+						authorities + " does not contain the same authorities as " + this.expectedAuthorities,
+						this.expectedAuthorities.containsAll(authorities));
+				AssertionErrors.assertTrue(
+						this.expectedAuthorities + " does not contain the same authorities as " + authorities,
+						authorities.containsAll(this.expectedAuthorities));
+			}
 		}
 
 		/**
@@ -206,6 +224,17 @@ public final class SecurityMockMvcResultMatchers {
 			return this;
 		}
 
+		/**
+		 * Specifies the {@link GrantedAuthority#getAuthority()}
+		 * @param authorities the authorityNames
+		 * @return the {@link AuthenticatedMatcher} for further customization
+		 */
+		public AuthenticatedMatcher withAuthorities(String... authorities) {
+			Assert.notNull(authorities, "authorities cannot be null");
+			this.expectedAuthorities = Arrays.asList(authorities);
+			return this;
+		}
+
 		/**
 		 * Specifies the {@link Authentication#getAuthorities()}
 		 * @param expected the {@link Authentication#getAuthorities()}

+ 32 - 1
test/src/test/java/org/springframework/security/test/web/servlet/response/SecurityMockWithAuthoritiesMvcResultMatchersTests.java

@@ -28,6 +28,7 @@ import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.core.GrantedAuthorities;
+import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserDetails;
@@ -53,6 +54,8 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMv
 @WebAppConfiguration
 public class SecurityMockWithAuthoritiesMvcResultMatchersTests {
 
+	private static final String ROLE_CUSTOM = "ROLE_CUSTOM";
+
 	@Autowired
 	private WebApplicationContext context;
 
@@ -80,6 +83,12 @@ public class SecurityMockWithAuthoritiesMvcResultMatchersTests {
 				() -> this.mockMvc.perform(formLogin()).andExpect(authenticated().withAuthorities(grantedAuthorities)));
 	}
 
+	@Test
+	public void withAuthoritiesStringSupportsCustomAuthority() throws Exception {
+		this.mockMvc.perform(formLogin().user("custom"))
+			.andExpect(authenticated().withAuthorities(ROLE_CUSTOM, GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY));
+	}
+
 	@Configuration
 	@EnableWebSecurity
 	@EnableWebMvc
@@ -89,7 +98,8 @@ public class SecurityMockWithAuthoritiesMvcResultMatchersTests {
 		UserDetailsService userDetailsService() {
 			// @formatter:off
 			UserDetails user = User.withDefaultPasswordEncoder().username("user").password("password").roles("ADMIN", "SELLER").build();
-			return new InMemoryUserDetailsManager(user);
+			UserDetails customAuthorityUser = User.withDefaultPasswordEncoder().username("custom").password("password").authorities(new CustomAuthority(ROLE_CUSTOM)).build();
+			return new InMemoryUserDetailsManager(user, customAuthorityUser);
 			// @formatter:on
 		}
 
@@ -105,4 +115,25 @@ public class SecurityMockWithAuthoritiesMvcResultMatchersTests {
 
 	}
 
+	/**
+	 * A custom {@link GrantedAuthority} for testing.
+	 *
+	 * @author Rob Winch
+	 * @since 7.0
+	 */
+	static class CustomAuthority implements GrantedAuthority {
+
+		private final String authority;
+
+		CustomAuthority(String authority) {
+			this.authority = authority;
+		}
+
+		@Override
+		public String getAuthority() {
+			return this.authority;
+		}
+
+	}
+
 }