Skip to content

Commit

Permalink
Zonify logout: redirect links/parameters/whitelist/flag
Browse files Browse the repository at this point in the history
  • Loading branch information
fhanik committed Feb 9, 2016
1 parent 0023d44 commit 34a2317
Show file tree
Hide file tree
Showing 10 changed files with 317 additions and 17 deletions.
Expand Up @@ -17,8 +17,8 @@
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;


import java.util.HashSet; import java.util.Collections;
import java.util.Set; import java.util.List;


@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
Expand Down Expand Up @@ -59,7 +59,7 @@ public static class Logout {
private String redirectUrl = "/login"; private String redirectUrl = "/login";
private String redirectParameterName = "redirect"; private String redirectParameterName = "redirect";
private boolean disableRedirectParameter = false; private boolean disableRedirectParameter = false;
private Set<String> whitelist = new HashSet<>(); private List<String> whitelist = Collections.EMPTY_LIST;


public boolean isDisableRedirectParameter() { public boolean isDisableRedirectParameter() {
return disableRedirectParameter; return disableRedirectParameter;
Expand Down Expand Up @@ -88,11 +88,11 @@ public Logout setRedirectUrl(String redirectUrl) {
return this; return this;
} }


public Set<String> getWhitelist() { public List<String> getWhitelist() {
return whitelist; return whitelist;
} }


public Logout setWhitelist(Set<String> whitelist) { public Logout setWhitelist(List<String> whitelist) {
this.whitelist = whitelist; this.whitelist = whitelist;
return this; return this;
} }
Expand Down
Expand Up @@ -27,6 +27,20 @@ public WhitelistLogoutHandler(List<String> whitelist) {
this.whitelist = whitelist; this.whitelist = whitelist;
} }


@Override
protected String getTargetUrlParameter() {
return super.getTargetUrlParameter();
}

@Override
protected boolean isAlwaysUseDefaultTargetUrl() {
return super.isAlwaysUseDefaultTargetUrl();
}

public String getDefaultTargetUrl1() {
return super.getDefaultTargetUrl();
}

public List<String> getWhitelist() { public List<String> getWhitelist() {
return whitelist; return whitelist;
} }
Expand Down
@@ -0,0 +1,59 @@
/*
* *****************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2016] 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.authentication;


import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class ZoneAwareWhitelistLogoutHandler implements LogoutSuccessHandler {

private final ClientDetailsService clientDetailsService;

public ZoneAwareWhitelistLogoutHandler(ClientDetailsService clientDetailsService) {
this.clientDetailsService = clientDetailsService;
}

@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
getZoneHandler().onLogoutSuccess(request, response, authentication);
}

protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
return getZoneHandler().determineTargetUrl(request, response);
}

protected WhitelistLogoutHandler getZoneHandler() {
IdentityZoneConfiguration config = IdentityZoneHolder.get().getConfig();
if (config==null) {
config = new IdentityZoneConfiguration();
}
WhitelistLogoutHandler handler = new WhitelistLogoutHandler(config.getLinks().getLogout().getWhitelist());
handler.setTargetUrlParameter(config.getLinks().getLogout().getRedirectParameterName());
handler.setDefaultTargetUrl(config.getLinks().getLogout().getRedirectUrl());
handler.setAlwaysUseDefaultTargetUrl(config.getLinks().getLogout().isDisableRedirectParameter());
handler.setClientDetailsService(clientDetailsService);
return handler;
}

}
Expand Up @@ -18,8 +18,10 @@
import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.cloudfoundry.identity.uaa.zone.TokenPolicy;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;


import java.util.List;
import java.util.Map; import java.util.Map;


import static java.util.Objects.nonNull;
import static org.springframework.util.StringUtils.hasText; import static org.springframework.util.StringUtils.hasText;


public class IdentityZoneConfigurationBootstrap implements InitializingBean { public class IdentityZoneConfigurationBootstrap implements InitializingBean {
Expand All @@ -29,6 +31,10 @@ public class IdentityZoneConfigurationBootstrap implements InitializingBean {
private boolean selfServiceLinksEnabled = true; private boolean selfServiceLinksEnabled = true;
private String homeRedirect = null; private String homeRedirect = null;
private Map<String,String> selfServiceLinks; private Map<String,String> selfServiceLinks;
private List<String> logoutRedirectWhitelist;
private String logoutRedirectParameterName;
private String logoutDefaultRedirectUrl;
private boolean logoutDisableRedirectParameter = true;




public IdentityZoneConfigurationBootstrap(IdentityZoneProvisioning provisioning) { public IdentityZoneConfigurationBootstrap(IdentityZoneProvisioning provisioning) {
Expand All @@ -51,6 +57,17 @@ public void afterPropertiesSet() {
definition.getLinks().getSelfService().setPasswd(passwd); definition.getLinks().getSelfService().setPasswd(passwd);
} }
} }
if (nonNull(logoutRedirectWhitelist)) {
definition.getLinks().getLogout().setWhitelist(logoutRedirectWhitelist);
}
if (hasText(logoutRedirectParameterName)) {
definition.getLinks().getLogout().setRedirectParameterName(logoutRedirectParameterName);
}
if (hasText(logoutDefaultRedirectUrl)) {
definition.getLinks().getLogout().setRedirectUrl(logoutDefaultRedirectUrl);
}
definition.getLinks().getLogout().setDisableRedirectParameter(logoutDisableRedirectParameter);

identityZone.setConfig(definition); identityZone.setConfig(definition);
provisioning.update(identityZone); provisioning.update(identityZone);
} }
Expand All @@ -76,4 +93,20 @@ public void setHomeRedirect(String homeRedirect) {
public void setSelfServiceLinks(Map<String, String> links) { public void setSelfServiceLinks(Map<String, String> links) {
this.selfServiceLinks = links; this.selfServiceLinks = links;
} }

public void setLogoutDefaultRedirectUrl(String logoutDefaultRedirectUrl) {
this.logoutDefaultRedirectUrl = logoutDefaultRedirectUrl;
}

public void setLogoutDisableRedirectParameter(boolean logoutDisableRedirectParameter) {
this.logoutDisableRedirectParameter = logoutDisableRedirectParameter;
}

public void setLogoutRedirectParameterName(String logoutRedirectParameterName) {
this.logoutRedirectParameterName = logoutRedirectParameterName;
}

public void setLogoutRedirectWhitelist(List<String> logoutRedirectWhitelist) {
this.logoutRedirectWhitelist = logoutRedirectWhitelist;
}
} }
11 changes: 2 additions & 9 deletions server/src/main/resources/login-ui.xml
Expand Up @@ -308,15 +308,8 @@
<constructor-arg name="redirectNotLoggedIn" value="/login" /> <constructor-arg name="redirectNotLoggedIn" value="/login" />
</bean> </bean>


<bean id="logoutHandler" class="org.cloudfoundry.identity.uaa.authentication.WhitelistLogoutHandler"> <bean id="logoutHandler" class="org.cloudfoundry.identity.uaa.authentication.ZoneAwareWhitelistLogoutHandler">
<constructor-arg value="#{@config['logout']==null ? null : <constructor-arg ref="jdbcClientDetailsService"/>
@config['logout']['redirect']==null ? null :
@config['logout']['redirect']['parameter']==null ? null :
@config['logout']['redirect']['parameter']['whitelist']}"/>
<property name="targetUrlParameter" value="redirect" />
<property name="defaultTargetUrl" value="${logout.redirect.url:/login}" />
<property name="alwaysUseDefaultTargetUrl" value="${logout.redirect.parameter.disable:true}"/>
<property name="clientDetailsService" ref="jdbcClientDetailsService"/>
</bean> </bean>


<!--<mvc:resources location="/" mapping="/**" />--> <!--<mvc:resources location="/" mapping="/**" />-->
Expand Down
@@ -0,0 +1,156 @@
/*
* *****************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2016] 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.authentication;

import org.cloudfoundry.identity.uaa.zone.IdentityZone;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.NoSuchClientException;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;

import java.util.Arrays;
import java.util.Collections;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.security.oauth2.common.util.OAuth2Utils.CLIENT_ID;


public class ZoneAwareWhitelistLogoutHandlerTests {

private MockHttpServletRequest request = new MockHttpServletRequest();
private MockHttpServletResponse response = new MockHttpServletResponse();
private BaseClientDetails client = new BaseClientDetails(CLIENT_ID, "", "", "", "", "http://*.testing.com,http://testing.com");
private ClientDetailsService clientDetailsService = mock(ClientDetailsService.class);
private ZoneAwareWhitelistLogoutHandler handler;
IdentityZoneConfiguration configuration = new IdentityZoneConfiguration();
IdentityZoneConfiguration original;


@Before
public void setUp() throws Exception {
original = IdentityZone.getUaa().getConfig();
configuration.getLinks().getLogout()
.setRedirectUrl("/login")
.setDisableRedirectParameter(true)
.setRedirectParameterName("redirect");
when(clientDetailsService.loadClientByClientId(CLIENT_ID)).thenReturn(client);
handler = new ZoneAwareWhitelistLogoutHandler(clientDetailsService);
IdentityZoneHolder.get().setConfig(configuration);
}

@After
public void tearDown() throws Exception {
IdentityZoneHolder.clear();
IdentityZone.getUaa().setConfig(original);
}

@Test
public void test_defaults() throws Exception {
WhitelistLogoutHandler whandler = handler.getZoneHandler();
assertEquals(Collections.EMPTY_LIST, whandler.getWhitelist());
assertEquals("redirect", whandler.getTargetUrlParameter());
assertEquals("/login", whandler.getDefaultTargetUrl1());
assertTrue(whandler.isAlwaysUseDefaultTargetUrl());
}

@Test
public void test_null_config_defaults() throws Exception {
IdentityZoneHolder.get().setConfig(null);
test_default_redirect_uri();
}


@Test
public void test_default_redirect_uri() throws Exception {
assertEquals("/login", handler.determineTargetUrl(request, response));
assertEquals("/login", handler.determineTargetUrl(request, response));
configuration.getLinks().getLogout().setDisableRedirectParameter(false);
assertEquals("/login", handler.determineTargetUrl(request, response));
}

@Test
public void test_whitelist_reject() throws Exception {
configuration.getLinks().getLogout().setWhitelist(Arrays.asList("http://testing.com"));
configuration.getLinks().getLogout().setDisableRedirectParameter(false);
request.setParameter("redirect", "http://testing.com");
assertEquals("http://testing.com", handler.determineTargetUrl(request, response));
request.setParameter("redirect", "http://www.testing.com");
assertEquals("/login", handler.determineTargetUrl(request, response));
}

@Test
public void test_allow_open_redirect() throws Exception {
configuration.getLinks().getLogout().setWhitelist(null);
configuration.getLinks().getLogout().setDisableRedirectParameter(false);
request.setParameter("redirect", "http://testing.com");
assertEquals("http://testing.com", handler.determineTargetUrl(request, response));
request.setParameter("redirect", "http://www.testing.com");
assertEquals("http://www.testing.com", handler.determineTargetUrl(request, response));
}

@Test
public void test_whitelist_redirect() throws Exception {
configuration.getLinks().getLogout().setWhitelist(Arrays.asList("http://somethingelse.com"));
configuration.getLinks().getLogout().setDisableRedirectParameter(false);
request.setParameter("redirect", "http://somethingelse.com");
assertEquals("http://somethingelse.com", handler.determineTargetUrl(request, response));
}

@Test
public void test_whitelist_redirect_with_wildcard() throws Exception {
configuration.getLinks().getLogout().setWhitelist(Arrays.asList("http://*.somethingelse.com"));
configuration.getLinks().getLogout().setDisableRedirectParameter(false);
request.setParameter("redirect", "http://www.somethingelse.com");
assertEquals("http://www.somethingelse.com", handler.determineTargetUrl(request, response));
}

@Test
public void test_client_redirect() throws Exception {
configuration.getLinks().getLogout().setWhitelist(Arrays.asList("http://somethingelse.com"));
configuration.getLinks().getLogout().setDisableRedirectParameter(false);
request.setParameter("redirect", "http://testing.com");
request.setParameter(CLIENT_ID, CLIENT_ID);
assertEquals("http://testing.com", handler.determineTargetUrl(request, response));
}

@Test
public void client_not_found_exception() throws Exception {
when(clientDetailsService.loadClientByClientId("test")).thenThrow(new NoSuchClientException("test"));
configuration.getLinks().getLogout().setWhitelist(Arrays.asList("http://testing.com"));
configuration.getLinks().getLogout().setDisableRedirectParameter(false);
request.setParameter("redirect", "http://notwhitelisted.com");
request.setParameter(CLIENT_ID, "test");
assertEquals("/login", handler.determineTargetUrl(request, response));
}

@Test
public void test_client_redirect_using_wildcard() throws Exception {
configuration.getLinks().getLogout().setWhitelist(Arrays.asList("http://testing.com"));
configuration.getLinks().getLogout().setDisableRedirectParameter(false);
request.setParameter(CLIENT_ID, CLIENT_ID);
request.setParameter("redirect", "http://www.testing.com");
assertEquals("http://www.testing.com", handler.determineTargetUrl(request, response));
}

}
Expand Up @@ -23,6 +23,7 @@
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;


import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;


Expand Down Expand Up @@ -135,4 +136,18 @@ public void passwd_link_configured() throws Exception {
assertEquals("/create_account", zone.getConfig().getLinks().getSelfService().getSignup()); assertEquals("/create_account", zone.getConfig().getLinks().getSelfService().getSignup());
assertEquals("/configured_passwd", zone.getConfig().getLinks().getSelfService().getPasswd()); assertEquals("/configured_passwd", zone.getConfig().getLinks().getSelfService().getPasswd());
} }

@Test
public void test_logout_redirect() throws Exception {
bootstrap.setLogoutDefaultRedirectUrl("/configured_login");
bootstrap.setLogoutDisableRedirectParameter(false);
bootstrap.setLogoutRedirectParameterName("test");
bootstrap.setLogoutRedirectWhitelist(Arrays.asList("http://single-url"));
bootstrap.afterPropertiesSet();
IdentityZoneConfiguration config = provisioning.retrieve(IdentityZone.getUaa().getId()).getConfig();
assertEquals("/configured_login", config.getLinks().getLogout().getRedirectUrl());
assertEquals("test", config.getLinks().getLogout().getRedirectParameterName());
assertEquals(Arrays.asList("http://single-url"), config.getLinks().getLogout().getWhitelist());
assertFalse(config.getLinks().getLogout().isDisableRedirectParameter());
}
} }
12 changes: 11 additions & 1 deletion uaa/src/main/webapp/WEB-INF/spring-servlet.xml
Expand Up @@ -403,7 +403,17 @@
<property name="selfServiceLinksEnabled" value="${login.selfServiceLinksEnabled:true}"/> <property name="selfServiceLinksEnabled" value="${login.selfServiceLinksEnabled:true}"/>
<property name="selfServiceLinks" ref="links" /> <property name="selfServiceLinks" ref="links" />
<property name="homeRedirect" value="${login.homeRedirect:null}"/> <property name="homeRedirect" value="${login.homeRedirect:null}"/>
</bean> <property name="logoutRedirectWhitelist"
value="#{@config['logout']==null ? null :
@config['logout']['redirect']==null ? null :
@config['logout']['redirect']['parameter']==null ? null :
@config['logout']['redirect']['parameter']['whitelist']}"/>
<property name="logoutRedirectParameterName" value="redirect" />
<property name="logoutDefaultRedirectUrl" value="${logout.redirect.url:/login}" />
<property name="logoutDisableRedirectParameter" value="${logout.redirect.parameter.disable:true}"/>


</bean>


<bean id="ldapLoginAuthenticationMgr" class="org.cloudfoundry.identity.uaa.authentication.manager.LdapLoginAuthenticationManager"> <bean id="ldapLoginAuthenticationMgr" class="org.cloudfoundry.identity.uaa.authentication.manager.LdapLoginAuthenticationManager">
<property name="userDatabase" ref="userDatabase" /> <property name="userDatabase" ref="userDatabase" />
Expand Down

0 comments on commit 34a2317

Please sign in to comment.