Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial degraded operating mode filter
[#151024202] https://www.pivotaltracker.com/story/show/151024202
- Loading branch information
Showing
7 changed files
with
367 additions
and
68 deletions.
There are no files selected for viewing
53 changes: 0 additions & 53 deletions
53
server/src/main/java/org/cloudfoundry/identity/uaa/mode/degraded/DegradedModeUaaFilter.java
This file was deleted.
Oops, something went wrong.
134 changes: 134 additions & 0 deletions
134
server/src/main/java/org/cloudfoundry/identity/uaa/web/DegradedModeUaaFilter.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 | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
/* | ||
* **************************************************************************** | ||
* Cloud Foundry | ||
* Copyright (c) [2009-2017] 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.web; | ||
|
||
import org.apache.commons.logging.Log; | ||
import org.apache.commons.logging.LogFactory; | ||
import org.cloudfoundry.identity.uaa.util.JsonUtils; | ||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
|
||
import javax.servlet.FilterChain; | ||
import javax.servlet.ServletException; | ||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpServletResponse; | ||
import java.io.IOException; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
|
||
import static java.lang.String.format; | ||
import static java.util.Collections.emptyList; | ||
import static java.util.Collections.emptySet; | ||
import static java.util.Optional.ofNullable; | ||
import static java.util.stream.Collectors.toList; | ||
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE; | ||
|
||
public class DegradedModeUaaFilter extends OncePerRequestFilter { | ||
|
||
private static Log logger = LogFactory.getLog(DegradedModeUaaFilter.class); | ||
|
||
private Set<String> permittedEndpoints = emptySet(); | ||
private Set<String> permittedMethods = emptySet(); | ||
private List<AntPathRequestMatcher> endpoints = emptyList(); | ||
private boolean enabled = false; | ||
|
||
|
||
@Override | ||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { | ||
if (isEnabled()) { | ||
if ( isMethodAllowed(request) || isEndpointAllowed(request)) { | ||
filterChain.doFilter(request, response); | ||
} else { | ||
logger.debug(format("Operation Not permitted in degraded mode for URL:%s and method:%s", | ||
request.getRequestURI(), | ||
request.getMethod() | ||
) | ||
); | ||
Map<String, String> json = getErrorData(); | ||
if (acceptsJson(request)) { | ||
response.setStatus(SC_SERVICE_UNAVAILABLE); | ||
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); | ||
response.getWriter().write(JsonUtils.writeValueAsString(json)); | ||
response.getWriter().flush(); | ||
response.getWriter().close(); | ||
} else { | ||
response.sendError(SC_SERVICE_UNAVAILABLE, json.get("description")); | ||
} | ||
} | ||
} else { | ||
filterChain.doFilter(request, response); | ||
} | ||
} | ||
|
||
protected Map<String, String> getErrorData() { | ||
String error = "uaa_unavailable"; | ||
String description = "UAA intentionally in degraded mode, operation not permitted. Please try later."; | ||
Map<String, String> json = new HashMap<>(); | ||
json.put("error", error); | ||
json.put("error_description", description); | ||
return json; | ||
} | ||
|
||
protected boolean acceptsJson(HttpServletRequest request) { | ||
List<MediaType> mediaTypes = MediaType.parseMediaTypes(request.getHeader(HttpHeaders.ACCEPT)); | ||
return mediaTypes.stream().anyMatch(m -> m.isCompatibleWith(MediaType.APPLICATION_JSON)); | ||
} | ||
|
||
protected boolean isMethodAllowed(HttpServletRequest request) { | ||
return getPermittedMethods().contains(request.getMethod().toUpperCase()); | ||
} | ||
|
||
public boolean isEndpointAllowed(HttpServletRequest request) { | ||
return endpoints.stream().anyMatch(m -> m.matches(request)); | ||
} | ||
|
||
public void setPermittedEndpoints(Set<String> permittedEndpoints) { | ||
this.permittedEndpoints = permittedEndpoints; | ||
if (permittedEndpoints==null) { | ||
this.endpoints = emptyList(); | ||
} else { | ||
this.endpoints = | ||
permittedEndpoints | ||
.stream() | ||
.map(s -> new AntPathRequestMatcher(s)) | ||
.collect(toList()); | ||
} | ||
} | ||
|
||
|
||
public Set<String> getPermittedEndpoints() { | ||
return permittedEndpoints; | ||
} | ||
|
||
public Set<String> getPermittedMethods() { | ||
return permittedMethods; | ||
} | ||
|
||
public void setPermittedMethods(Set<String> permittedMethods) { | ||
this.permittedMethods = ofNullable(permittedMethods).orElse(emptySet()); | ||
} | ||
|
||
public void setEnabled(boolean enabled) { | ||
this.enabled = enabled; | ||
} | ||
|
||
public boolean isEnabled() { | ||
return enabled; | ||
} | ||
} |
147 changes: 147 additions & 0 deletions
147
server/src/test/java/org/cloudfoundry/identity/uaa/web/DegradedModeUaaFilterTests.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 | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
/* | ||
* **************************************************************************** | ||
* Cloud Foundry | ||
* Copyright (c) [2009-2017] 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.web; | ||
|
||
import org.cloudfoundry.identity.uaa.util.JsonUtils; | ||
import org.junit.Before; | ||
import org.junit.Test; | ||
import org.springframework.mock.web.MockHttpServletRequest; | ||
import org.springframework.mock.web.MockHttpServletResponse; | ||
|
||
import javax.servlet.FilterChain; | ||
import java.util.Arrays; | ||
import java.util.HashSet; | ||
|
||
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE; | ||
import static org.junit.Assert.assertEquals; | ||
import static org.mockito.ArgumentMatchers.same; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.reset; | ||
import static org.mockito.Mockito.times; | ||
import static org.mockito.Mockito.verify; | ||
import static org.mockito.Mockito.verifyZeroInteractions; | ||
import static org.springframework.http.HttpHeaders.ACCEPT; | ||
import static org.springframework.http.HttpMethod.GET; | ||
import static org.springframework.http.HttpMethod.POST; | ||
|
||
public class DegradedModeUaaFilterTests { | ||
|
||
private MockHttpServletRequest request; | ||
private MockHttpServletResponse response; | ||
private FilterChain chain; | ||
private DegradedModeUaaFilter filter; | ||
|
||
@Before | ||
public void setup() throws Exception { | ||
request = new MockHttpServletRequest(); | ||
request.addHeader(ACCEPT, "*/*"); | ||
response = new MockHttpServletResponse(); | ||
chain = mock(FilterChain.class); | ||
filter = new DegradedModeUaaFilter(); | ||
} | ||
|
||
public void setPathInfo(String pathInfo) { | ||
request.setServletPath(""); | ||
request.setPathInfo(pathInfo); | ||
request.setContextPath("/uaa"); | ||
request.setRequestURI(request.getContextPath()+request.getPathInfo()); | ||
} | ||
|
||
@Test | ||
public void disabled() throws Exception { | ||
filter.doFilterInternal(request, response, chain); | ||
verify(chain, times(1)).doFilter(same(request), same(response)); | ||
} | ||
|
||
@Test | ||
public void enabled_no_whitelist_post() throws Exception { | ||
request.setMethod(POST.name()); | ||
filter.setEnabled(true); | ||
filter.doFilterInternal(request, response, chain); | ||
verifyZeroInteractions(chain); | ||
assertEquals(SC_SERVICE_UNAVAILABLE, response.getStatus()); | ||
} | ||
|
||
@Test | ||
public void enabled_no_whitelist_get() throws Exception { | ||
request.setMethod(GET.name()); | ||
filter.setEnabled(true); | ||
filter.setPermittedMethods(new HashSet<>(Arrays.asList(GET.toString()))); | ||
filter.doFilterInternal(request, response, chain); | ||
verify(chain, times(1)).doFilter(same(request), same(response)); | ||
} | ||
|
||
@Test | ||
public void enabled_matching_url_post() throws Exception { | ||
request.setMethod(POST.name()); | ||
filter.setPermittedEndpoints(new HashSet(Arrays.asList("/oauth/token/**"))); | ||
filter.setEnabled(true); | ||
for (String pathInfo : Arrays.asList("/oauth/token", "/oauth/token/alias/something")) { | ||
setPathInfo(pathInfo); | ||
reset(chain); | ||
filter.doFilterInternal(request, response, chain); | ||
verify(chain, times(1)).doFilter(same(request), same(response)); | ||
} | ||
} | ||
|
||
@Test | ||
public void enabled_not_matching_post() throws Exception { | ||
request.setMethod(POST.name()); | ||
filter.setPermittedEndpoints(new HashSet(Arrays.asList("/oauth/token/**"))); | ||
filter.setEnabled(true); | ||
for (String pathInfo : Arrays.asList("/url", "/other/url")) { | ||
response = new MockHttpServletResponse(); | ||
setPathInfo(pathInfo); | ||
reset(chain); | ||
filter.doFilterInternal(request, response, chain); | ||
verifyZeroInteractions(chain); | ||
assertEquals(SC_SERVICE_UNAVAILABLE, response.getStatus()); | ||
} | ||
} | ||
|
||
@Test | ||
public void error_is_json() throws Exception { | ||
filter.setPermittedEndpoints(new HashSet(Arrays.asList("/oauth/token/**"))); | ||
filter.setEnabled(true); | ||
for (String accept : Arrays.asList("application/json", "text/html,*/*")) { | ||
request = new MockHttpServletRequest(); | ||
response = new MockHttpServletResponse(); | ||
setPathInfo("/not/allowed"); | ||
request.setMethod(POST.name()); | ||
request.addHeader(ACCEPT, accept); | ||
filter.doFilterInternal(request, response, chain); | ||
assertEquals(SC_SERVICE_UNAVAILABLE, response.getStatus()); | ||
assertEquals(JsonUtils.writeValueAsString(filter.getErrorData()), response.getContentAsString()); | ||
} | ||
} | ||
|
||
@Test | ||
public void error_is_not() throws Exception { | ||
filter.setPermittedEndpoints(new HashSet(Arrays.asList("/oauth/token/**"))); | ||
filter.setEnabled(true); | ||
for (String accept : Arrays.asList("text/html", "text/plain")) { | ||
request = new MockHttpServletRequest(); | ||
response = new MockHttpServletResponse(); | ||
setPathInfo("/not/allowed"); | ||
request.setMethod(POST.name()); | ||
request.addHeader(ACCEPT, accept); | ||
filter.doFilterInternal(request, response, chain); | ||
assertEquals(SC_SERVICE_UNAVAILABLE, response.getStatus()); | ||
assertEquals(filter.getErrorData().get("description"), response.getErrorMessage()); | ||
} | ||
} | ||
|
||
} |
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
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.