NamespaceHttpOpenIDLoginTests groovy->java

Issue gh-4939
This commit is contained in:
Josh Cummings 2019-08-30 15:54:43 -06:00
parent ffc43e02c3
commit bf5b693549
No known key found for this signature in database
GPG Key ID: 49EF60DD7FF83443
2 changed files with 304 additions and 228 deletions

View File

@ -1,228 +0,0 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed 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 org.springframework.security.config.annotation.web.configurers
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.BaseWebConfig;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.openid.OpenID4JavaConsumer
import org.springframework.security.openid.OpenIDAuthenticationFilter
import org.springframework.security.openid.OpenIDAuthenticationProvider
import org.springframework.security.openid.OpenIDAuthenticationToken;
import org.springframework.security.web.FilterChainProxy
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
/**
* Tests to verify that all the functionality of <openid-login> attributes is present
*
* @author Rob Winch
*
*/
public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec {
def "http/openid-login"() {
when:
loadConfig(OpenIDLoginConfig)
then:
findFilter(OpenIDAuthenticationFilter).consumer.class == OpenID4JavaConsumer
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.getRedirectedUrl() == "http://localhost/login"
when: "fail to log in"
super.setup()
request.servletPath = "/login/openid"
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to login error page"
response.getRedirectedUrl() == "/login?error"
}
@Configuration
static class OpenIDLoginConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) {
http
.authorizeRequests()
.anyRequest().hasRole("USER")
.and()
.openidLogin()
.permitAll();
}
}
def "http/openid-login/attribute-exchange"() {
when:
loadConfig(OpenIDLoginAttributeExchangeConfig)
OpenID4JavaConsumer consumer = findFilter(OpenIDAuthenticationFilter).consumer
then:
consumer.class == OpenID4JavaConsumer
def googleAttrs = consumer.attributesToFetchFactory.createAttributeList("https://www.google.com/1")
googleAttrs[0].name == "email"
googleAttrs[0].type == "https://axschema.org/contact/email"
googleAttrs[0].required
googleAttrs[1].name == "firstname"
googleAttrs[1].type == "https://axschema.org/namePerson/first"
googleAttrs[1].required
googleAttrs[2].name == "lastname"
googleAttrs[2].type == "https://axschema.org/namePerson/last"
googleAttrs[2].required
def yahooAttrs = consumer.attributesToFetchFactory.createAttributeList("https://rwinch.yahoo.com/rwinch/id")
yahooAttrs[0].name == "email"
yahooAttrs[0].type == "https://schema.openid.net/contact/email"
yahooAttrs[0].required
yahooAttrs[1].name == "fullname"
yahooAttrs[1].type == "https://axschema.org/namePerson"
yahooAttrs[1].required
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.getRedirectedUrl() == "http://localhost/login"
when: "fail to log in"
super.setup()
request.servletPath = "/login/openid"
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to login error page"
response.getRedirectedUrl() == "/login?error"
}
@Configuration
static class OpenIDLoginAttributeExchangeConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) {
http
.authorizeRequests()
.anyRequest().hasRole("USER")
.and()
.openidLogin()
.attributeExchange("https://www.google.com/.*") // attribute-exchange@identifier-match
.attribute("email") // openid-attribute@name
.type("https://axschema.org/contact/email") // openid-attribute@type
.required(true) // openid-attribute@required
.count(1) // openid-attribute@count
.and()
.attribute("firstname")
.type("https://axschema.org/namePerson/first")
.required(true)
.and()
.attribute("lastname")
.type("https://axschema.org/namePerson/last")
.required(true)
.and()
.and()
.attributeExchange(".*yahoo.com.*")
.attribute("email")
.type("https://schema.openid.net/contact/email")
.required(true)
.and()
.attribute("fullname")
.type("https://axschema.org/namePerson")
.required(true)
.and()
.and()
.permitAll();
}
}
def "http/openid-login custom"() {
setup:
loadConfig(OpenIDLoginCustomConfig)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.getRedirectedUrl() == "http://localhost/authentication/login"
when: "fail to log in"
super.setup()
request.servletPath = "/authentication/login/process"
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to login error page"
response.getRedirectedUrl() == "/authentication/login?failed"
}
@Configuration
static class OpenIDLoginCustomConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
boolean alwaysUseDefaultSuccess = true;
http
.authorizeRequests()
.anyRequest().hasRole("USER")
.and()
.openidLogin()
.permitAll()
.loginPage("/authentication/login") // openid-login@login-page
.failureUrl("/authentication/login?failed") // openid-login@authentication-failure-url
.loginProcessingUrl("/authentication/login/process") // openid-login@login-processing-url
.defaultSuccessUrl("/default", alwaysUseDefaultSuccess) // openid-login@default-target-url / openid-login@always-use-default-target
}
}
def "http/openid-login custom refs"() {
when:
OpenIDLoginCustomRefsConfig.AUDS = Mock(AuthenticationUserDetailsService)
loadConfig(OpenIDLoginCustomRefsConfig)
then: "CustomWebAuthenticationDetailsSource is used"
findFilter(OpenIDAuthenticationFilter).authenticationDetailsSource.class == CustomWebAuthenticationDetailsSource
findAuthenticationProvider(OpenIDAuthenticationProvider).userDetailsService == OpenIDLoginCustomRefsConfig.AUDS
when: "fail to log in"
request.servletPath = "/login/openid"
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to login error page"
response.getRedirectedUrl() == "/custom/failure"
}
@Configuration
static class OpenIDLoginCustomRefsConfig extends BaseWebConfig {
static AuthenticationUserDetailsService AUDS
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().hasRole("USER")
.and()
.openidLogin()
// if using UserDetailsService wrap with new UserDetailsByNameServiceWrapper<OpenIDAuthenticationToken>()
.authenticationUserDetailsService(AUDS) // openid-login@user-service-ref
.failureHandler(new SimpleUrlAuthenticationFailureHandler("/custom/failure")) // openid-login@authentication-failure-handler-ref
.successHandler(new SavedRequestAwareAuthenticationSuccessHandler( defaultTargetUrl : "/custom/targetUrl" )) // openid-login@authentication-success-handler-ref
.authenticationDetailsSource(new CustomWebAuthenticationDetailsSource()); // openid-login@authentication-details-source-ref
}
// only necessary to have easy access to the AuthenticationManager for testing/verification
@Bean
@Override
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
}
static class CustomWebAuthenticationDetailsSource extends WebAuthenticationDetailsSource {}
}

View File

@ -0,0 +1,304 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed 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 org.springframework.security.config.annotation.web.configurers;
import java.util.Arrays;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.Rule;
import org.junit.Test;
import org.openid4java.consumer.ConsumerManager;
import org.openid4java.discovery.DiscoveryInformation;
import org.openid4java.message.AuthRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.openid.OpenIDAttribute;
import org.springframework.security.openid.OpenIDAuthenticationFilter;
import org.springframework.security.openid.OpenIDAuthenticationStatus;
import org.springframework.security.openid.OpenIDAuthenticationToken;
import org.springframework.security.openid.OpenIDConsumer;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.openid4java.discovery.yadis.YadisResolver.YADIS_XRDS_LOCATION;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests to verify that all the functionality of <openid-login> attributes is present
*
* @author Rob Winch
* @author Josh Cummings
*/
public class NamespaceHttpOpenIDLoginTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired
MockMvc mvc;
@Test
public void openidLoginWhenUsingDefaultsThenMatchesNamespace() throws Exception {
this.spring.register(OpenIDLoginConfig.class).autowire();
this.mvc.perform(get("/"))
.andExpect(redirectedUrl("http://localhost/login"));
this.mvc.perform(post("/login/openid").with(csrf()))
.andExpect(redirectedUrl("/login?error"));
}
@Configuration
@EnableWebSecurity
static class OpenIDLoginConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().hasRole("USER")
.and()
.openidLogin()
.permitAll();
}
}
@Test
public void openidLoginWhenAttributeExchangeConfiguredThenFetchAttributesMatchAttributeList() throws Exception {
OpenIDLoginAttributeExchangeConfig.CONSUMER_MANAGER = mock(ConsumerManager.class);
AuthRequest mockAuthRequest = mock(AuthRequest.class);
DiscoveryInformation mockDiscoveryInformation = mock(DiscoveryInformation.class);
when(mockAuthRequest.getDestinationUrl(anyBoolean())).thenReturn("mockUrl");
when(OpenIDLoginAttributeExchangeConfig.CONSUMER_MANAGER.associate(any()))
.thenReturn(mockDiscoveryInformation);
when(OpenIDLoginAttributeExchangeConfig.CONSUMER_MANAGER.authenticate(any(DiscoveryInformation.class), any(), any()))
.thenReturn(mockAuthRequest);
this.spring.register(OpenIDLoginAttributeExchangeConfig.class).autowire();
try (MockWebServer server = new MockWebServer()) {
String endpoint = server.url("/").toString();
server.enqueue(new MockResponse()
.addHeader(YADIS_XRDS_LOCATION, endpoint));
server.enqueue(new MockResponse()
.setBody(String.format("<XRDS><XRD><Service><URI>%s</URI></Service></XRD></XRDS>", endpoint)));
MvcResult mvcResult = this.mvc.perform(get("/login/openid")
.param(OpenIDAuthenticationFilter.DEFAULT_CLAIMED_IDENTITY_FIELD, "https://www.google.com/1"))
.andExpect(status().isFound())
.andReturn();
Object attributeObject = mvcResult.getRequest().getSession().getAttribute("SPRING_SECURITY_OPEN_ID_ATTRIBUTES_FETCH_LIST");
assertThat(attributeObject).isInstanceOf(List.class);
List<OpenIDAttribute> attributeList = (List<OpenIDAttribute>) attributeObject;
assertThat(attributeList.stream().anyMatch(attribute ->
"firstname".equals(attribute.getName())
&& "https://axschema.org/namePerson/first".equals(attribute.getType())
&& attribute.isRequired()))
.isTrue();
assertThat(attributeList.stream().anyMatch(attribute ->
"lastname".equals(attribute.getName())
&& "https://axschema.org/namePerson/last".equals(attribute.getType())
&& attribute.isRequired()))
.isTrue();
assertThat(attributeList.stream().anyMatch(attribute ->
"email".equals(attribute.getName())
&& "https://axschema.org/contact/email".equals(attribute.getType())
&& attribute.isRequired()))
.isTrue();
}
}
@Configuration
@EnableWebSecurity
static class OpenIDLoginAttributeExchangeConfig extends WebSecurityConfigurerAdapter {
static ConsumerManager CONSUMER_MANAGER;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().hasRole("USER")
.and()
.openidLogin()
.consumerManager(CONSUMER_MANAGER)
.attributeExchange("https://www.google.com/.*") // attribute-exchange@identifier-match
.attribute("email") // openid-attribute@name
.type("https://axschema.org/contact/email") // openid-attribute@type
.required(true) // openid-attribute@required
.count(1) // openid-attribute@count
.and()
.attribute("firstname")
.type("https://axschema.org/namePerson/first")
.required(true)
.and()
.attribute("lastname")
.type("https://axschema.org/namePerson/last")
.required(true)
.and()
.and()
.attributeExchange(".*yahoo.com.*")
.attribute("email")
.type("https://schema.openid.net/contact/email")
.required(true)
.and()
.attribute("fullname")
.type("https://axschema.org/namePerson")
.required(true)
.and()
.and()
.permitAll();
}
}
@Test
public void openidLoginWhenUsingCustomEndpointsThenMatchesNamespace() throws Exception {
this.spring.register(OpenIDLoginCustomConfig.class).autowire();
this.mvc.perform(get("/"))
.andExpect(redirectedUrl("http://localhost/authentication/login"));
this.mvc.perform(post("/authentication/login/process").with(csrf()))
.andExpect(redirectedUrl("/authentication/login?failed"));
}
@Configuration
@EnableWebSecurity
static class OpenIDLoginCustomConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
boolean alwaysUseDefaultSuccess = true;
http
.authorizeRequests()
.anyRequest().hasRole("USER")
.and()
.openidLogin()
.permitAll()
.loginPage("/authentication/login") // openid-login@login-page
.failureUrl("/authentication/login?failed") // openid-login@authentication-failure-url
.loginProcessingUrl("/authentication/login/process") // openid-login@login-processing-url
.defaultSuccessUrl("/default", alwaysUseDefaultSuccess); // openid-login@default-target-url / openid-login@always-use-default-target
}
}
@Test
public void openidLoginWithCustomHandlersThenBehaviorMatchesNamespace() throws Exception {
OpenIDAuthenticationToken token = new OpenIDAuthenticationToken(
OpenIDAuthenticationStatus.SUCCESS,
"identityUrl",
"message",
Arrays.asList(new OpenIDAttribute("name", "type")));
OpenIDLoginCustomRefsConfig.AUDS = mock(AuthenticationUserDetailsService.class);
when(OpenIDLoginCustomRefsConfig.AUDS.loadUserDetails(any(Authentication.class)))
.thenReturn(new User("user", "password", AuthorityUtils.createAuthorityList("ROLE_USER")));
OpenIDLoginCustomRefsConfig.ADS = spy(new WebAuthenticationDetailsSource());
OpenIDLoginCustomRefsConfig.CONSUMER = mock(OpenIDConsumer.class);
this.spring.register(OpenIDLoginCustomRefsConfig.class, UserDetailsServiceConfig.class).autowire();
when(OpenIDLoginCustomRefsConfig.CONSUMER.endConsumption(any(HttpServletRequest.class)))
.thenThrow(new AuthenticationServiceException("boom"));
this.mvc.perform(post("/login/openid").with(csrf())
.param("openid.identity", "identity"))
.andExpect(redirectedUrl("/custom/failure"));
reset(OpenIDLoginCustomRefsConfig.CONSUMER);
when(OpenIDLoginCustomRefsConfig.CONSUMER.endConsumption(any(HttpServletRequest.class)))
.thenReturn(token);
this.mvc.perform(post("/login/openid").with(csrf())
.param("openid.identity", "identity"))
.andExpect(redirectedUrl("/custom/targetUrl"));
verify(OpenIDLoginCustomRefsConfig.AUDS).loadUserDetails(any(Authentication.class));
verify(OpenIDLoginCustomRefsConfig.ADS).buildDetails(any(Object.class));
}
@Configuration
@EnableWebSecurity
static class OpenIDLoginCustomRefsConfig extends WebSecurityConfigurerAdapter {
static AuthenticationUserDetailsService AUDS;
static AuthenticationDetailsSource ADS;
static OpenIDConsumer CONSUMER;
@Override
protected void configure(HttpSecurity http) throws Exception {
SavedRequestAwareAuthenticationSuccessHandler handler =
new SavedRequestAwareAuthenticationSuccessHandler();
handler.setDefaultTargetUrl("/custom/targetUrl");
http
.authorizeRequests()
.anyRequest().hasRole("USER")
.and()
.openidLogin()
// if using UserDetailsService wrap with new UserDetailsByNameServiceWrapper<OpenIDAuthenticationToken>()
.authenticationUserDetailsService(AUDS) // openid-login@user-service-ref
.failureHandler(new SimpleUrlAuthenticationFailureHandler("/custom/failure")) // openid-login@authentication-failure-handler-ref
.successHandler(handler) // openid-login@authentication-success-handler-ref
.authenticationDetailsSource(ADS) // openid-login@authentication-details-source-ref
.withObjectPostProcessor(new ObjectPostProcessor<OpenIDAuthenticationFilter>() {
@Override
public <O extends OpenIDAuthenticationFilter> O postProcess(O filter) {
filter.setConsumer(CONSUMER);
return filter;
}
});
}
}
@Configuration
static class UserDetailsServiceConfig {
@Bean
public UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build());
}
}
}