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.
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;

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

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

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

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

public Logout setWhitelist(Set<String> whitelist) {
public Logout setWhitelist(List<String> whitelist) {
this.whitelist = whitelist;
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@ public WhitelistLogoutHandler(List<String> 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() {
return whitelist;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
import org.cloudfoundry.identity.uaa.zone.TokenPolicy;
import org.springframework.beans.factory.InitializingBean;

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

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

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


public IdentityZoneConfigurationBootstrap(IdentityZoneProvisioning provisioning) {
Expand All @@ -51,6 +57,17 @@ public void afterPropertiesSet() {
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);
provisioning.update(identityZone);
}
Expand All @@ -76,4 +93,20 @@ public void setHomeRedirect(String homeRedirect) {
public void setSelfServiceLinks(Map<String, String> 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
Original file line number Diff line number Diff line change
Expand Up @@ -308,15 +308,8 @@
<constructor-arg name="redirectNotLoggedIn" value="/login" />
</bean>

<bean id="logoutHandler" class="org.cloudfoundry.identity.uaa.authentication.WhitelistLogoutHandler">
<constructor-arg value="#{@config['logout']==null ? null :
@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 id="logoutHandler" class="org.cloudfoundry.identity.uaa.authentication.ZoneAwareWhitelistLogoutHandler">
<constructor-arg ref="jdbcClientDetailsService"/>
</bean>

<!--<mvc:resources location="/" mapping="/**" />-->
Expand Down
Original file line number Diff line number Diff line change
@@ -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));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;
import java.util.HashMap;
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("/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
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,17 @@
<property name="selfServiceLinksEnabled" value="${login.selfServiceLinksEnabled:true}"/>
<property name="selfServiceLinks" ref="links" />
<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">
<property name="userDatabase" ref="userDatabase" />
Expand Down
Loading

0 comments on commit 34a2317

Please sign in to comment.