Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add in a flag that disables the response_type id_token for URLs /oaut…
…h/authorize https://www.pivotaltracker.com/story/show/104458868 [#104458868]
- Loading branch information
Showing
4 changed files
with
341 additions
and
29 deletions.
There are no files selected for viewing
129 changes: 129 additions & 0 deletions
129
...n/src/main/java/org/cloudfoundry/identity/uaa/oauth/DisableIdTokenResponseTypeFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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; | |||
} | |||
} | |||
} | |||
} |
158 changes: 158 additions & 0 deletions
158
...c/test/java/org/cloudfoundry/identity/uaa/oauth/DisableIdTokenResponseTypeFilterTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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]); | |||
} | |||
|
|||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.