Skip to content

Commit

Permalink
Add in a flag that disables the response_type id_token for URLs /oaut…
Browse files Browse the repository at this point in the history
  • Loading branch information
fhanik committed Sep 29, 2015
1 parent bbea639 commit 050a782
Show file tree
Hide file tree
Showing 4 changed files with 341 additions and 29 deletions.
@@ -0,0 +1,129 @@
/*
* *****************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved.
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product includes a number of subcomponents with
* separate copyright notices and license terms. Your use of these
* subcomponents is subject to the terms and conditions of the
* subcomponent's license, as noted in the LICENSE file.
* *****************************************************************************
*/

package org.cloudfoundry.identity.uaa.oauth;

import org.springframework.core.env.Environment;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.springframework.security.oauth2.common.util.OAuth2Utils.RESPONSE_TYPE;

public class DisableIdTokenResponseTypeFilter extends OncePerRequestFilter {

public static final String CONFIG = "oauth.id_token.disable";
public static final String ID_TOKEN = "id_token";

private boolean active;
private final List<String> paths;

public DisableIdTokenResponseTypeFilter(boolean active, List<String> paths) {
this.paths = paths;
this.active = active;
}

public boolean isIdTokenDisabled() {
return active;
}

public void setIdTokenDisabled(boolean disabled) {
this.active = disabled;
}

protected boolean applyPath(String path) {
if (paths==null || paths.size()==0 || path == null) {
return false;
}
AntPathMatcher matcher = new AntPathMatcher();
for (String pattern : paths) {
if (matcher.isPattern(pattern)) {
if (matcher.match(pattern, path)) {
return true;
}
} else { //exact match
if (pattern.equals(path)) {
return true;
}
}
}
return false;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest requestWrapper = request;
if (isIdTokenDisabled() && applyPath(request.getPathInfo())) {
requestWrapper = new RemoveIdTokenParameterValueWrapper(request);
}
filterChain.doFilter(requestWrapper, response);
}

public class RemoveIdTokenParameterValueWrapper extends HttpServletRequestWrapper {

public RemoveIdTokenParameterValueWrapper(HttpServletRequest request) {
super(request);
}

@Override
public String getParameter(String name) {
if (RESPONSE_TYPE.equals(name)) {
return removeIdTokenValue(super.getParameter(name));
} else {
return super.getParameter(name);
}
}

@Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> map = super.getParameterMap();
if (map.containsKey(RESPONSE_TYPE)) {
HashMap<String, String[]> result = new HashMap<>(map);
result.put(RESPONSE_TYPE, getParameterValues(RESPONSE_TYPE));
map = result;
}
return map;
}

@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (RESPONSE_TYPE.equals(name)) {
for (int i=0; values!=null && i<values.length; i++) {
values[i] = removeIdTokenValue(values[i]);
}
}
return values;
}


private String removeIdTokenValue(String value) {
if (StringUtils.hasText(value) && value.contains(ID_TOKEN)) {
return value.replace(ID_TOKEN, "").trim();
} else {
return value;
}
}
}
}
@@ -0,0 +1,158 @@
/*
* *****************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved.
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product includes a number of subcomponents with
* separate copyright notices and license terms. Your use of these
* subcomponents is subject to the terms and conditions of the
* subcomponent's license, as noted in the LICENSE file.
* *****************************************************************************
*/

package org.cloudfoundry.identity.uaa.oauth;

import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.springframework.security.oauth2.common.util.OAuth2Utils.RESPONSE_TYPE;

public class DisableIdTokenResponseTypeFilterTest {

DisableIdTokenResponseTypeFilter filter;
DisableIdTokenResponseTypeFilter disabledFilter;
List<String> applyPaths = Arrays.asList("/oauth/authorze", "/**/oauth/authorize");
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
ArgumentCaptor<HttpServletRequest> captor = ArgumentCaptor.forClass(HttpServletRequest.class);
FilterChain chain = mock(FilterChain.class);

@Before
public void setUp() {
filter = new DisableIdTokenResponseTypeFilter(false, applyPaths);
disabledFilter = new DisableIdTokenResponseTypeFilter(true, applyPaths);
request.setPathInfo("/oauth/authorize");
}

@Test
public void testIsIdTokenDisabled() throws Exception {
assertFalse(filter.isIdTokenDisabled());
assertTrue(disabledFilter.isIdTokenDisabled());
}

@Test
public void testApplyPath() throws Exception {
shouldApplyPath("/oauth/token", false);
shouldApplyPath("/someotherpath/uaa/oauth/authorize", true);
shouldApplyPath("/uaa/oauth/authorize", true);
shouldApplyPath("/oauth/authorize", true);
shouldApplyPath(null, false);
shouldApplyPath("", false);
}

public void shouldApplyPath(String path, boolean expectedOutCome) {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setPathInfo(path);
assertEquals(expectedOutCome, filter.applyPath(path));
assertEquals(expectedOutCome, disabledFilter.applyPath(path));
}

@Test
public void testDoFilterInternal_NO_Response_Type_Parameter() throws Exception {
filter.doFilterInternal(request, response, chain);
verify(chain).doFilter(captor.capture(), anyObject());
assertSame(request, captor.getValue());
reset(chain);

disabledFilter.doFilterInternal(request, response, chain);
verify(chain).doFilter(captor.capture(), anyObject());
assertNotSame(request, captor.getValue());
}

@Test
public void testDoFilterInternal_Code_Response_Type_Parameter() throws Exception {
String responseType = "code";
request.addParameter(RESPONSE_TYPE, responseType);
filter.doFilterInternal(request, response, chain);
verify(chain).doFilter(captor.capture(), anyObject());
assertSame(request, captor.getValue());
reset(chain);
assertEquals(responseType, captor.getValue().getParameter(RESPONSE_TYPE));
assertEquals(1, captor.getValue().getParameterMap().get(RESPONSE_TYPE).length);
assertEquals(responseType, captor.getValue().getParameterMap().get(RESPONSE_TYPE)[0]);
assertEquals(1, captor.getValue().getParameterValues(RESPONSE_TYPE).length);
assertEquals(responseType, captor.getValue().getParameterValues(RESPONSE_TYPE)[0]);

disabledFilter.doFilterInternal(request, response, chain);
verify(chain).doFilter(captor.capture(), anyObject());
assertNotSame(request, captor.getValue());
assertEquals(responseType, captor.getValue().getParameter(RESPONSE_TYPE));
assertEquals(1, captor.getValue().getParameterMap().get(RESPONSE_TYPE).length);
assertEquals(responseType, captor.getValue().getParameterMap().get(RESPONSE_TYPE)[0]);
assertEquals(1, captor.getValue().getParameterValues(RESPONSE_TYPE).length);
assertEquals(responseType, captor.getValue().getParameterValues(RESPONSE_TYPE)[0]);
}

@Test
public void testDoFilterInternal_Code_and_IdToken_Response_Type_Parameter() throws Exception {
String responseType = "code id_token";
String removedType = "code";
validate_filter(responseType, removedType);
}

@Test
public void testDoFilterInternal_IdToken_and_Code_Response_Type_Parameter() throws Exception {
String responseType = "code id_token";
String removedType = "code";
validate_filter(responseType, removedType);
}

@Test
public void testDoFilterInternal_Token_and_IdToken_and_Code_Response_Type_Parameter() throws Exception {
String responseType = "token code id_token";
String removedType = "token code";
validate_filter(responseType, removedType);
}

public void validate_filter(String responseType, String removedType) throws Exception {
request.addParameter(RESPONSE_TYPE, responseType);
filter.doFilterInternal(request, response, chain);
verify(chain).doFilter(captor.capture(), anyObject());
assertSame(request, captor.getValue());
reset(chain);
assertEquals(responseType, captor.getValue().getParameter(RESPONSE_TYPE));
assertEquals(1, captor.getValue().getParameterMap().get(RESPONSE_TYPE).length);
assertEquals(responseType, captor.getValue().getParameterMap().get(RESPONSE_TYPE)[0]);
assertEquals(1, captor.getValue().getParameterValues(RESPONSE_TYPE).length);
assertEquals(responseType, captor.getValue().getParameterValues(RESPONSE_TYPE)[0]);

disabledFilter.doFilterInternal(request, response, chain);
verify(chain).doFilter(captor.capture(), anyObject());
assertNotSame(request, captor.getValue());
assertEquals(removedType, captor.getValue().getParameter(RESPONSE_TYPE));
assertEquals(1, captor.getValue().getParameterMap().get(RESPONSE_TYPE).length);
assertEquals(removedType, captor.getValue().getParameterMap().get(RESPONSE_TYPE)[0]);
assertEquals(1, captor.getValue().getParameterValues(RESPONSE_TYPE).length);
assertEquals(removedType, captor.getValue().getParameterValues(RESPONSE_TYPE)[0]);
}

}
14 changes: 13 additions & 1 deletion uaa/src/main/webapp/WEB-INF/spring-servlet.xml
Expand Up @@ -32,6 +32,15 @@
</bean> </bean>


<bean id="backwardsCompatibleScopeParameter" class="org.cloudfoundry.identity.uaa.oauth.BackwardsCompatibleScopeParsingFilter"/> <bean id="backwardsCompatibleScopeParameter" class="org.cloudfoundry.identity.uaa.oauth.BackwardsCompatibleScopeParsingFilter"/>
<bean id="disableIdTokenResponseFilter" class="org.cloudfoundry.identity.uaa.oauth.DisableIdTokenResponseTypeFilter">
<constructor-arg index="0" value="${oauth.id_token.disable:false}"/>
<constructor-arg index="1">
<list>
<value>/**/oauth/authorize</value>
<value>/oauth/authorize</value>
</list>
</constructor-arg>
</bean>


<import resource="classpath:spring/data-source.xml" /> <import resource="classpath:spring/data-source.xml" />
<import resource="classpath:spring/env.xml" /> <import resource="classpath:spring/env.xml" />
Expand Down Expand Up @@ -87,6 +96,9 @@
key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).position(0)}" /> key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).position(0)}" />
<entry value-ref="identityZoneResolvingFilter" <entry value-ref="identityZoneResolvingFilter"
key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).position(1)}"/> key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).position(1)}"/>
<!-- Add in a flag that removes id_token from /oauth/authorize requests-->
<entry value-ref="disableIdTokenResponseFilter"
key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).position(2)}"/>
<!-- Zone switcher goes *after* class OAuth2AuthenticationProcessingFilter as it requires a token to be present to work --> <!-- Zone switcher goes *after* class OAuth2AuthenticationProcessingFilter as it requires a token to be present to work -->
<entry value-ref="identityZoneSwitchingFilter" <entry value-ref="identityZoneSwitchingFilter"
key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).after(@oauth2TokenParseFilter)}"/> key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).after(@oauth2TokenParseFilter)}"/>
Expand All @@ -96,7 +108,7 @@
key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).after(T(org.cloudfoundry.identity.uaa.zone.AllowUserManagementSecurityFilter))}"/> key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).after(T(org.cloudfoundry.identity.uaa.zone.AllowUserManagementSecurityFilter))}"/>
<entry value-ref="xFrameOptionsFilter" key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).position(101)}"/> <entry value-ref="xFrameOptionsFilter" key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).position(101)}"/>
<entry value-ref="sessionResetFilter" key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).position(102)}"/> <entry value-ref="sessionResetFilter" key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).position(102)}"/>
</map> </map>
</property> </property>
</bean> </bean>


Expand Down

0 comments on commit 050a782

Please sign in to comment.