Add option to prevent entity stream consumption

If someone's app cannot handle the SingleSignOutFilter's consumption of entity streams
(via a `request.getParameter()` call) on all requests,
they can use this option in conjunction with setting up a service logout URL at the CAS server.
The filter will now only consume the stream on requests to this path.

Fixes https://github.com/apereo/java-cas-client/issues/210.
This commit is contained in:
Matt Drees 2018-07-23 17:12:37 -06:00
parent 93561a297f
commit ba5982e1eb
9 changed files with 73 additions and 4 deletions

View File

@ -663,6 +663,7 @@ The `SingleSignOutFilter` can affect character encoding. This becomes most obvio
| `relayStateParameterName` | Defaults to `RelayState` | No
| `eagerlyCreateSessions` | Defaults to `true` | No
| `artifactParameterOverPost` | Defaults to `false` | No
| `logoutPath` | The path which will receive logout callback requests from the CAS server. This is necessary if your app needs access to the raw input stream when handling form posts. If not configured, the default behavior will check every request for a logout parameter. | No
| `casServerUrlPrefix` | URL to root of CAS Web application context. | Yes
<a name="cas-protocol"></a>

View File

@ -78,4 +78,5 @@ public interface ConfigurationKeys {
ConfigurationKey<Class<? extends Cas20ServiceTicketValidator>> TICKET_VALIDATOR_CLASS = new ConfigurationKey<Class<? extends Cas20ServiceTicketValidator>>("ticketValidatorClass", null);
ConfigurationKey<String> PROXY_CALLBACK_URL = new ConfigurationKey<String>("proxyCallbackUrl", null);
ConfigurationKey<String> RELAY_STATE_PARAMETER_NAME = new ConfigurationKey<String>("relayStateParameterName", "RelayState");
ConfigurationKey<String> LOGOUT_PATH = new ConfigurationKey<String>("logoutPath", null);
}

View File

@ -48,6 +48,7 @@ public final class SingleSignOutFilter extends AbstractConfigurationFilter {
setLogoutParameterName(getString(ConfigurationKeys.LOGOUT_PARAMETER_NAME));
setRelayStateParameterName(getString(ConfigurationKeys.RELAY_STATE_PARAMETER_NAME));
setCasServerUrlPrefix(getString(ConfigurationKeys.CAS_SERVER_URL_PREFIX));
setLogoutPath(getString(ConfigurationKeys.LOGOUT_PATH));
HANDLER.setArtifactParameterOverPost(getBoolean(ConfigurationKeys.ARTIFACT_PARAMETER_OVER_POST));
HANDLER.setEagerlyCreateSessions(getBoolean(ConfigurationKeys.EAGERLY_CREATE_SESSIONS));
}
@ -71,6 +72,10 @@ public final class SingleSignOutFilter extends AbstractConfigurationFilter {
HANDLER.setCasServerUrlPrefix(casServerUrlPrefix);
}
public void setLogoutPath(String logoutPath) {
HANDLER.setLogoutPath(logoutPath);
}
public void setSessionMappingStorage(final SessionMappingStorage storage) {
HANDLER.setSessionMappingStorage(storage);
}

View File

@ -66,6 +66,9 @@ public final class SingleSignOutHandler {
/** The prefix url of the CAS server */
private String casServerUrlPrefix = "";
/** The logout path configured at the CAS server, if there is one */
private String logoutPath;
private boolean artifactParameterOverPost = false;
private boolean eagerlyCreateSessions = true;
@ -106,7 +109,14 @@ public final class SingleSignOutHandler {
public void setCasServerUrlPrefix(final String casServerUrlPrefix) {
this.casServerUrlPrefix = casServerUrlPrefix;
}
/**
* @param logoutPath The logout path configured at the CAS server.
*/
public void setLogoutPath(String logoutPath) {
this.logoutPath = logoutPath;
}
/**
* @param name Name of parameter containing the state of the CAS server webflow.
*/
@ -163,6 +173,7 @@ public final class SingleSignOutHandler {
private boolean isLogoutRequest(final HttpServletRequest request) {
if ("POST".equalsIgnoreCase(request.getMethod())) {
return !isMultipartRequest(request)
&& pathMatches(request)
&& CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName,
this.safeParameters));
}
@ -172,7 +183,19 @@ public final class SingleSignOutHandler {
}
return false;
}
private boolean pathMatches(HttpServletRequest request) {
return logoutPath == null || logoutPath.equals(getPath(request));
}
private String getPath(HttpServletRequest request) {
return request.getServletPath() + nullToEmpty(request.getPathInfo());
}
private String nullToEmpty(String string) {
return string == null ? "" : string;
}
/**
* Process a request regarding the SLO process: record the session or destroy it.
*

View File

@ -116,13 +116,36 @@ public final class SingleSignOutHandlerTests {
@Test
public void backChannelLogoutOK() {
final MockHttpSession session = doBackChannelLogout();
assertFalse(handler.process(request, response));
assertTrue(session.isInvalid());
}
@Test
public void backChannelLogoutDoesDoesNotRunIfLogoutPathDoesNotMatch() {
handler.setLogoutPath("/logout");
request.setServletPath("/not-a-logout");
final MockHttpSession session = doBackChannelLogout();
assertTrue(handler.process(request, response));
assertFalse(session.isInvalid());
}
@Test
public void backChannelLogoutRunsWhenLogoutPathDoesMatch() {
handler.setLogoutPath("/logout");
request.setServletPath("/logout");
final MockHttpSession session = doBackChannelLogout();
assertFalse(handler.process(request, response));
assertTrue(session.isInvalid());
}
private MockHttpSession doBackChannelLogout() {
final String logoutMessage = LogoutMessageGenerator.generateBackChannelLogoutMessage(TICKET);
request.setParameter(LOGOUT_PARAMETER_NAME, logoutMessage);
request.setMethod("POST");
final MockHttpSession session = new MockHttpSession();
handler.getSessionMappingStorage().addSessionById(TICKET, session);
assertFalse(handler.process(request, response));
assertTrue(session.isInvalid());
return session;
}
@Test

View File

@ -60,6 +60,10 @@ public class SingleSignOutValve extends AbstractLifecycleValve implements Sessio
this.handler.setCasServerUrlPrefix(casServerUrlPrefix);
}
public void setLogoutPath(String logoutPath) {
this.handler.setLogoutPath(logoutPath);
}
public void setSessionMappingStorage(final SessionMappingStorage storage) {
this.handler.setSessionMappingStorage(storage);
}

View File

@ -64,6 +64,10 @@ public class SingleSignOutValve extends ValveBase implements SessionListener {
this.handler.setCasServerUrlPrefix(casServerUrlPrefix);
}
public void setLogoutPath(String logoutPath) {
this.handler.setLogoutPath(logoutPath);
}
public void setSessionMappingStorage(final SessionMappingStorage storage) {
this.handler.setSessionMappingStorage(storage);
}

View File

@ -64,6 +64,10 @@ public class SingleSignOutValve extends ValveBase implements SessionListener {
this.handler.setCasServerUrlPrefix(casServerUrlPrefix);
}
public void setLogoutPath(String logoutPath) {
this.handler.setLogoutPath(logoutPath);
}
public void setSessionMappingStorage(final SessionMappingStorage storage) {
this.handler.setSessionMappingStorage(storage);
}

View File

@ -64,6 +64,10 @@ public class SingleSignOutValve extends ValveBase implements SessionListener {
this.handler.setCasServerUrlPrefix(casServerUrlPrefix);
}
public void setLogoutPath(String logoutPath) {
this.handler.setLogoutPath(logoutPath);
}
public void setSessionMappingStorage(final SessionMappingStorage storage) {
this.handler.setSessionMappingStorage(storage);
}