OpenIDConfigTests groovy->java

For the remember me test, there is some hand configuration that was
carried over from the groovy test as there isn't a way via the xml
config to achieve the same result.

For the attribute exchange test, in order to reduce the amount of
endpoint configuration, the test uses a bit of reflection to disable
the OpenID association step. This is because the xml config does not
support wiring a custom ConsumerManager, like the java configurer
does.

Issue: gh-4939
This commit is contained in:
Josh Cummings 2018-07-20 09:34:49 -06:00
parent 36cbdfe013
commit 195a6943e2
No known key found for this signature in database
GPG Key ID: 49EF60DD7FF83443
9 changed files with 456 additions and 166 deletions

View File

@ -1,166 +0,0 @@
package org.springframework.security.config.http
import javax.servlet.http.HttpServletRequest
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.config.BeanIds
import org.springframework.security.openid.OpenIDAuthenticationFilter
import org.springframework.security.openid.OpenIDAuthenticationToken
import org.springframework.security.openid.OpenIDConsumer
import org.springframework.security.openid.OpenIDConsumerException
import org.springframework.security.web.access.ExceptionTranslationFilter
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
import javax.servlet.Filter
/**
*
* @author Luke Taylor
*/
class OpenIDConfigTests extends AbstractHttpConfigTests {
def openIDAndFormLoginWorkTogether() {
xml.http() {
'openid-login'()
'form-login'()
}
createAppContext()
def etf = getFilter(ExceptionTranslationFilter)
def ap = etf.getAuthenticationEntryPoint();
expect:
ap.loginFormUrl == "/login"
// Default login filter should be present since we haven't specified any login URLs
getFilter(DefaultLoginPageGeneratingFilter) != null
}
def formLoginEntryPointTakesPrecedenceIfLoginUrlIsSet() {
xml.http() {
'openid-login'()
'form-login'('login-page': '/form-page')
}
createAppContext()
expect:
getFilter(ExceptionTranslationFilter).authenticationEntryPoint.loginFormUrl == '/form-page'
}
def openIDEntryPointTakesPrecedenceIfLoginUrlIsSet() {
xml.http() {
'openid-login'('login-page': '/openid-page')
'form-login'()
}
createAppContext()
expect:
getFilter(ExceptionTranslationFilter).authenticationEntryPoint.loginFormUrl == '/openid-page'
}
def multipleLoginPagesCausesError() {
when:
xml.http() {
'openid-login'('login-page': '/openid-page')
'form-login'('login-page': '/form-page')
}
createAppContext()
then:
thrown(BeanDefinitionParsingException)
}
def openIDAndRememberMeWorkTogether() {
xml.debug()
xml.http() {
interceptUrl('/**', 'denyAll')
'openid-login'()
'remember-me'()
'csrf'(disabled:true)
}
createAppContext()
// Default login filter should be present since we haven't specified any login URLs
def loginFilter = getFilter(DefaultLoginPageGeneratingFilter)
def openIDFilter = getFilter(OpenIDAuthenticationFilter)
openIDFilter.setConsumer(new OpenIDConsumer() {
public String beginConsumption(HttpServletRequest req, String claimedIdentity, String returnToUrl, String realm)
throws OpenIDConsumerException {
return "http://testopenid.com?openid.return_to=" + returnToUrl;
}
public OpenIDAuthenticationToken endConsumption(HttpServletRequest req) throws OpenIDConsumerException {
throw new UnsupportedOperationException();
}
})
Set<String> returnToUrlParameters = new HashSet<String>()
returnToUrlParameters.add(AbstractRememberMeServices.DEFAULT_PARAMETER)
openIDFilter.setReturnToUrlParameters(returnToUrlParameters)
assert loginFilter.openIDrememberMeParameter != null
MockHttpServletRequest request = new MockHttpServletRequest(method:'GET');
MockHttpServletResponse response = new MockHttpServletResponse();
when: "Initial request is made"
Filter fc = appContext.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN)
request.setServletPath("/something.html")
fc.doFilter(request, response, new MockFilterChain())
then: "Redirected to login"
response.getRedirectedUrl().endsWith("/login")
when: "Login page is requested"
request.setServletPath("/login")
request.setRequestURI("/login")
response = new MockHttpServletResponse()
fc.doFilter(request, response, new MockFilterChain())
then: "Remember-me choice is added to page"
response.getContentAsString().contains(AbstractRememberMeServices.DEFAULT_PARAMETER)
when: "Login is submitted with remember-me selected"
request.servletPath = "/login/openid"
request.setParameter(OpenIDAuthenticationFilter.DEFAULT_CLAIMED_IDENTITY_FIELD, "http://hey.openid.com/")
request.setParameter(AbstractRememberMeServices.DEFAULT_PARAMETER, "on")
response = new MockHttpServletResponse();
fc.doFilter(request, response, new MockFilterChain());
String expectedReturnTo = request.getRequestURL().append("?")
.append(AbstractRememberMeServices.DEFAULT_PARAMETER)
.append("=").append("on").toString();
then: "return_to URL contains remember-me choice"
response.getRedirectedUrl() == "http://testopenid.com?openid.return_to=" + expectedReturnTo
}
def openIDWithAttributeExchangeConfigurationIsParsedCorrectly() {
xml.http() {
'openid-login'() {
'attribute-exchange'() {
'openid-attribute'(name: 'nickname', type: 'http://schema.openid.net/namePerson/friendly')
'openid-attribute'(name: 'email', type: 'http://schema.openid.net/contact/email', required: 'true',
'count': '2')
}
}
}
createAppContext()
List attributes = getFilter(OpenIDAuthenticationFilter).consumer.attributesToFetchFactory.createAttributeList('http://someid')
expect:
attributes.size() == 2
attributes[0].name == 'nickname'
attributes[0].type == 'http://schema.openid.net/namePerson/friendly'
!attributes[0].required
attributes[1].required
attributes[1].getCount() == 2
}
def 'SEC-2919: DefaultLoginGeneratingFilter should not be present if login-page="/login"'() {
when:
xml.http() {
'openid-login'('login-page':'/login')
}
createAppContext()
then:
getFilter(DefaultLoginPageGeneratingFilter) == null
}
}

View File

@ -0,0 +1,223 @@
/*
* Copyright 2002-2018 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
*
* http://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.http;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.Filter;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.openid.OpenID4JavaConsumer;
import org.springframework.security.openid.OpenIDAuthenticationFilter;
import org.springframework.security.openid.OpenIDConsumer;
import org.springframework.security.util.FieldUtils;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.openid4java.discovery.yadis.YadisResolver.YADIS_XRDS_LOCATION;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests usage of the &lt;openid-login&gt; element
*
* @author Luke Taylor
*/
public class OpenIDConfigTests {
private static final String CONFIG_LOCATION_PREFIX =
"classpath:org/springframework/security/config/http/OpenIDConfigTests";
@Autowired
MockMvc mvc;
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Test
public void requestWhenOpenIDAndFormLoginBothConfiguredThenRedirectsToGeneratedLoginPage()
throws Exception {
this.spring.configLocations(this.xml("WithFormLogin")).autowire();
this.mvc.perform(get("/"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/login"));
assertThat(getFilter(DefaultLoginPageGeneratingFilter.class)).isNotNull();
}
@Test
public void requestWhenOpenIDAndFormLoginWithFormLoginPageConfiguredThenFormLoginPageWins()
throws Exception {
this.spring.configLocations(this.xml("WithFormLoginPage")).autowire();
this.mvc.perform(get("/"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/form-page"));
}
@Test
public void requestWhenOpenIDAndFormLoginWithOpenIDLoginPageConfiguredThenOpenIDLoginPageWins()
throws Exception {
this.spring.configLocations(this.xml("WithOpenIDLoginPageAndFormLogin")).autowire();
this.mvc.perform(get("/"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/openid-page"));
}
@Test
public void configureWhenOpenIDAndFormLoginBothConfigureLoginPagesThenWiringException()
throws Exception {
assertThatCode(() -> this.spring.configLocations(this.xml("WithFormLoginAndOpenIDLoginPages")).autowire())
.isInstanceOf(BeanDefinitionParsingException.class);
}
@Test
public void requestWhenOpenIDAndRememberMeConfiguredThenRememberMePassedToIdp()
throws Exception {
this.spring.configLocations(this.xml("WithRememberMe")).autowire();
OpenIDAuthenticationFilter openIDFilter = getFilter(OpenIDAuthenticationFilter.class);
String openIdEndpointUrl = "http://testopenid.com?openid.return_to=";
Set<String> returnToUrlParameters = new HashSet<>();
returnToUrlParameters.add(AbstractRememberMeServices.DEFAULT_PARAMETER);
openIDFilter.setReturnToUrlParameters(returnToUrlParameters);
OpenIDConsumer consumer = mock(OpenIDConsumer.class);
when(consumer.beginConsumption(any(HttpServletRequest.class), anyString(), anyString(), anyString()))
.then(invocation -> openIdEndpointUrl + invocation.getArgument(2));
openIDFilter.setConsumer(consumer);
String expectedReturnTo = new StringBuilder("http://localhost/login/openid").append("?")
.append(AbstractRememberMeServices.DEFAULT_PARAMETER)
.append("=").append("on").toString();
this.mvc.perform(get("/"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/login"));
this.mvc.perform(get("/login"))
.andExpect(status().isOk())
.andExpect(content().string(containsString(AbstractRememberMeServices.DEFAULT_PARAMETER)));
this.mvc.perform(get("/login/openid")
.param(OpenIDAuthenticationFilter.DEFAULT_CLAIMED_IDENTITY_FIELD, "http://hey.openid.com/")
.param(AbstractRememberMeServices.DEFAULT_PARAMETER, "on"))
.andExpect(status().isFound())
.andExpect(redirectedUrl(openIdEndpointUrl + expectedReturnTo));
}
@Test
public void requestWhenAttributeExchangeConfiguredThenFetchAttributesPassedToIdp()
throws Exception {
this.spring.configLocations(this.xml("WithOpenIDAttributes")).autowire();
OpenIDAuthenticationFilter openIDFilter = getFilter(OpenIDAuthenticationFilter.class);
OpenID4JavaConsumer consumer = getFieldValue(openIDFilter, "consumer");
ConsumerManager manager = getFieldValue(consumer, "consumerManager");
manager.setMaxAssocAttempts(0);
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)));
this.mvc.perform(get("/login/openid")
.param(OpenIDAuthenticationFilter.DEFAULT_CLAIMED_IDENTITY_FIELD, endpoint))
.andExpect(status().isFound())
.andExpect(result -> result.getResponse().getRedirectedUrl().endsWith(
"openid.ext1.type.nickname=http%3A%2F%2Fschema.openid.net%2FnamePerson%2Ffriendly&" +
"openid.ext1.if_available=nickname&" +
"openid.ext1.type.email=http%3A%2F%2Fschema.openid.net%2Fcontact%2Femail&" +
"openid.ext1.required=email&" +
"openid.ext1.count.email=2"));
}
}
/**
* SEC-2919
*/
@Test
public void requestWhenLoginPageConfiguredWithPhraseLoginThenRedirectsOnlyToUserGeneratedLoginPage()
throws Exception {
this.spring.configLocations(this.xml("Sec2919")).autowire();
assertThat(getFilter(DefaultLoginPageGeneratingFilter.class)).isNull();
this.mvc.perform(get("/login"))
.andExpect(status().isOk())
.andExpect(content().string("a custom login page"));
}
@RestController
static class CustomLoginController {
@GetMapping("/login")
public String custom() {
return "a custom login page";
}
}
private <T extends Filter> T getFilter(Class<T> clazz) {
FilterChainProxy filterChain = this.spring.getContext().getBean(FilterChainProxy.class);
return (T) filterChain.getFilters("/").stream()
.filter(clazz::isInstance)
.findFirst()
.orElse(null);
}
private String xml(String configName) {
return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml";
}
private static <T> T getFieldValue(Object bean, String fieldName) throws IllegalAccessException {
return (T) FieldUtils.getFieldValue(bean, fieldName);
}
}

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2002-2018 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
~
~ http://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.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<http use-expressions="true">
<intercept-url pattern="/login" access="permitAll"/>
<intercept-url pattern="/**" access="authenticated"/>
<openid-login login-page="/login"/>
</http>
<b:bean
name="customLogin"
class="org.springframework.security.config.http.OpenIDConfigTests.CustomLoginController"/>
<b:import resource="userservice.xml"/>
</b:beans>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2002-2018 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
~
~ http://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.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<http use-expressions="true">
<intercept-url pattern="/**" access="authenticated"/>
<openid-login/>
<form-login/>
</http>
<b:import resource="userservice.xml"/>
</b:beans>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2002-2018 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
~
~ http://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.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<http use-expressions="true">
<intercept-url pattern="/**" access="authenticated"/>
<openid-login login-page="/openid-page"/>
<form-login login-page="/form-page"/>
</http>
<b:import resource="userservice.xml"/>
</b:beans>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2002-2018 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
~
~ http://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.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<http use-expressions="true">
<intercept-url pattern="/**" access="authenticated"/>
<openid-login/>
<form-login login-page="/form-page"/>
</http>
<b:import resource="userservice.xml"/>
</b:beans>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2002-2018 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
~
~ http://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.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<http use-expressions="true">
<intercept-url pattern="/**" access="denyAll"/>
<openid-login>
<attribute-exchange>
<openid-attribute name="nickname" type="http://schema.openid.net/namePerson/friendly"/>
<openid-attribute name="email" type="http://schema.openid.net/contact/email" required="true" count="2"/>
</attribute-exchange>
</openid-login>
</http>
<b:import resource="userservice.xml"/>
</b:beans>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2002-2018 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
~
~ http://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.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<http use-expressions="true">
<intercept-url pattern="/**" access="authenticated"/>
<openid-login login-page="/openid-page"/>
<form-login/>
</http>
<b:import resource="userservice.xml"/>
</b:beans>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2002-2018 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
~
~ http://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.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<http use-expressions="true">
<intercept-url pattern="/**" access="denyAll"/>
<openid-login/>
<remember-me/>
<csrf disabled="true"/>
</http>
<b:import resource="userservice.xml"/>
</b:beans>