VYPR
High severity8.8NVD Advisory· Published Oct 24, 2017· Updated May 13, 2026

CVE-2015-5170

CVE-2015-5170

Description

Cloud Foundry Runtime cf-release before 216, UAA before 2.5.2, and Pivotal Cloud Foundry (PCF) Elastic Runtime before 1.7.0 allow remote attackers to conduct cross-site request forgery (CSRF) attacks on PWS and log a user into an arbitrary account by leveraging lack of CSRF checks.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven
< 2.5.22.5.2

Affected products

3

Patches

3
a54f3fb8225e

Merge branch 'feature/stateless_csrf_upon_login' into develop

https://github.com/cloudfoundry/uaaFilip HanikAug 7, 2015via ghsa
25 files changed · +719 212
  • common/src/main/java/org/cloudfoundry/identity/uaa/security/web/DelegatingRequestMatcher.java+0 43 removed
    @@ -1,43 +0,0 @@
    -/*******************************************************************************
    - *     Cloud Foundry
    - *     Copyright (c) [2009-2014] 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.security.web;
    -
    -import org.springframework.security.web.util.matcher.RequestMatcher;
    -
    -import java.util.ArrayList;
    -import java.util.List;
    -
    -import javax.servlet.http.HttpServletRequest;
    -
    -/**
    - * @author Luke Taylor
    - */
    -public class DelegatingRequestMatcher implements RequestMatcher {
    -    private final List<RequestMatcher> matchers;
    -
    -    public DelegatingRequestMatcher(List<RequestMatcher> matchers) {
    -        this.matchers = new ArrayList<>(matchers);
    -    }
    -
    -    @Override
    -    public boolean matches(HttpServletRequest request) {
    -        for (RequestMatcher m : matchers) {
    -            if (m.matches(request)) {
    -                return true;
    -            }
    -        }
    -
    -        return false;
    -    }
    -}
    
  • common/src/main/java/org/cloudfoundry/identity/uaa/web/CookieBasedCsrfTokenRepository.java+105 0 added
    @@ -0,0 +1,105 @@
    +/*
    + * ******************************************************************************
    + *  *     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.web;
    +
    +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
    +import org.springframework.security.web.csrf.CsrfToken;
    +import org.springframework.security.web.csrf.CsrfTokenRepository;
    +import org.springframework.security.web.csrf.DefaultCsrfToken;
    +
    +import javax.servlet.http.Cookie;
    +import javax.servlet.http.HttpServletRequest;
    +import javax.servlet.http.HttpServletResponse;
    +
    +public class CookieBasedCsrfTokenRepository implements CsrfTokenRepository {
    +
    +    public static final String DEFAULT_CSRF_HEADER_NAME = "X-CSRF-TOKEN";
    +    public static final String DEFAULT_CSRF_COOKIE_NAME = "X-Uaa-Csrf";
    +    public static final int DEFAULT_COOKIE_MAX_AGE = 300;
    +
    +    private RandomValueStringGenerator generator = new RandomValueStringGenerator(6);
    +    private String parameterName = DEFAULT_CSRF_COOKIE_NAME;
    +    private String headerName = DEFAULT_CSRF_HEADER_NAME;
    +    private int cookieMaxAge = DEFAULT_COOKIE_MAX_AGE;
    +
    +    public int getCookieMaxAge() {
    +        return cookieMaxAge;
    +    }
    +
    +    public void setCookieMaxAge(int cookieMaxAge) {
    +        this.cookieMaxAge = cookieMaxAge;
    +    }
    +
    +    public String getHeaderName() {
    +        return headerName;
    +    }
    +
    +    public void setHeaderName(String headerName) {
    +        this.headerName = headerName;
    +    }
    +
    +    public String getParameterName() {
    +        return parameterName;
    +    }
    +
    +    public void setParameterName(String parameterName) {
    +        this.parameterName = parameterName;
    +    }
    +
    +    public void setGenerator(RandomValueStringGenerator generator) {
    +        this.generator = generator;
    +    }
    +
    +    public RandomValueStringGenerator getGenerator() {
    +        return generator;
    +    }
    +
    +    @Override
    +    public CsrfToken generateToken(HttpServletRequest request) {
    +        String token = generator.generate();
    +        return new DefaultCsrfToken(getHeaderName(), getParameterName(), token);
    +    }
    +
    +    @Override
    +    public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
    +        boolean expire = false;
    +        if (token==null) {
    +            token = generateToken(request);
    +            expire = true;
    +        }
    +        Cookie csrfCookie = new Cookie(token.getParameterName(), token.getToken());
    +        csrfCookie.setHttpOnly(true);
    +        if (expire) {
    +            csrfCookie.setMaxAge(0);
    +        } else {
    +            csrfCookie.setMaxAge(getCookieMaxAge());
    +        }
    +        response.addCookie(csrfCookie);
    +    }
    +
    +    @Override
    +    public CsrfToken loadToken(HttpServletRequest request) {
    +        Cookie[] cookies = request.getCookies();
    +        if (cookies!=null) {
    +            for (Cookie cookie : request.getCookies()) {
    +                if (getParameterName().equals(cookie.getName())) {
    +                    return new DefaultCsrfToken(getHeaderName(), getParameterName(), cookie.getValue());
    +                }
    +            }
    +        }
    +        return null;
    +    }
    +}
    
  • common/src/test/java/org/cloudfoundry/identity/uaa/ServerRunning.java+4 2 modified
    @@ -272,9 +272,11 @@ public ResponseEntity<Void> postForRedirect(String path, HttpHeaders headers, Mu
                 throw new IllegalStateException("Expected 302 but server returned status code " + exchange.getStatusCode());
             }
     
    +        headers.remove("Cookie");
             if (exchange.getHeaders().containsKey("Set-Cookie")) {
    -            String cookie = exchange.getHeaders().getFirst("Set-Cookie");
    -            headers.set("Cookie", cookie);
    +            for (String cookie : exchange.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", cookie);
    +            }
             }
     
             String location = exchange.getHeaders().getLocation().toString();
    
  • common/src/test/java/org/cloudfoundry/identity/uaa/web/CookieBasedCsrfTokenRepositoryTests.java+76 0 added
    @@ -0,0 +1,76 @@
    +/*
    + * ******************************************************************************
    + *  *     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.web;
    +
    +import org.junit.Test;
    +import org.springframework.mock.web.MockHttpServletRequest;
    +import org.springframework.mock.web.MockHttpServletResponse;
    +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
    +import org.springframework.security.web.csrf.CsrfToken;
    +
    +import javax.servlet.http.Cookie;
    +
    +import static org.junit.Assert.assertEquals;
    +import static org.junit.Assert.assertNotNull;
    +
    +public class CookieBasedCsrfTokenRepositoryTests {
    +
    +    @Test
    +    public void testGetHeader_and_Parameter_Name() throws Exception {
    +        CookieBasedCsrfTokenRepository repo = new CookieBasedCsrfTokenRepository();
    +        assertEquals(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, repo.getParameterName());
    +        repo.setParameterName("testcookie");
    +        assertEquals("testcookie", repo.getParameterName());
    +
    +        assertEquals(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_HEADER_NAME, repo.getHeaderName());
    +        repo.setHeaderName("testheader");
    +        assertEquals("testheader", repo.getHeaderName());
    +
    +        repo.setGenerator(new RandomValueStringGenerator() {
    +            @Override
    +            public String generate() {
    +                return "token-id";
    +            }
    +        });
    +
    +        CsrfToken token = repo.generateToken(new MockHttpServletRequest());
    +        assertEquals("testheader", token.getHeaderName());
    +        assertEquals("testcookie", token.getParameterName());
    +        assertEquals("token-id", token.getToken());
    +    }
    +
    +    @Test
    +    public void testSave_and_Load_Token() throws Exception {
    +        CookieBasedCsrfTokenRepository repo = new CookieBasedCsrfTokenRepository();
    +        MockHttpServletRequest request = new MockHttpServletRequest();
    +        MockHttpServletResponse response = new MockHttpServletResponse();
    +        CsrfToken token = repo.generateToken(request);
    +        repo.saveToken(token, request, response);
    +
    +        Cookie cookie = response.getCookie(token.getParameterName());
    +        assertNotNull(cookie);
    +        assertEquals(token.getToken(), cookie.getValue());
    +        assertEquals(true, cookie.isHttpOnly());
    +
    +        request.setCookies(cookie);
    +
    +        CsrfToken saved = repo.loadToken(request);
    +        assertEquals(token.getToken(), saved.getToken());
    +        assertEquals(token.getHeaderName(), saved.getHeaderName());
    +        assertEquals(token.getParameterName(), saved.getParameterName());
    +    }
    +
    +}
    \ No newline at end of file
    
  • login/src/main/resources/login-ui.xml+66 6 modified
    @@ -124,16 +124,76 @@
             <access-denied-handler ref="loginEntryPoint"/>
         </http>
     
    +    <bean id="uiLoginRequestMatcher" class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    +        <constructor-arg value="/login.do" />
    +    </bean>
    +
    +    <bean id="uiLogoutRequestMatcher" class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    +        <constructor-arg value="/logout.do" />
    +    </bean>
    +
    +    <bean id="uiAuthorizeRequestMatcher" class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    +        <constructor-arg value="/oauth/authorize**" />
    +    </bean>
    +
    +    <bean id="uiRequestMatcher" class="org.springframework.security.web.util.matcher.OrRequestMatcher">
    +        <constructor-arg>
    +            <list>
    +                <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    +                    <constructor-arg value="/" />
    +                </bean>
    +                <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    +                    <constructor-arg value="/oauth/**" />
    +                </bean>
    +                <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    +                    <constructor-arg value="/login**" />
    +                </bean>
    +                <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    +                    <constructor-arg value="/logout.do**" />
    +                </bean>
    +            </list>
    +        </constructor-arg>
    +    </bean>
    +
    +
    +
    +    <bean id="loginCookieCsrfRepository" class="org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository"/>
    +    <bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
    +        <constructor-arg index="0" ref="logoutHandler"/>
    +        <constructor-arg index="1">
    +            <util:list>
    +                <bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
    +                <bean class="org.springframework.security.web.csrf.CsrfLogoutHandler">
    +                    <constructor-arg ref="loginCookieCsrfRepository"/>
    +                </bean>
    +                <bean class="org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler">
    +                    <constructor-arg index="0">
    +                        <util:list><value>JSESSIONID</value></util:list>
    +                    </constructor-arg>
    +                </bean>
    +            </util:list>
    +        </constructor-arg>
    +        <property name="logoutRequestMatcher" ref="uiLogoutRequestMatcher"/>
    +    </bean>
    +    <bean id="savedRequestCache" class="org.springframework.security.web.savedrequest.HttpSessionRequestCache">
    +        <property name="requestMatcher" ref="uiAuthorizeRequestMatcher"/>
    +    </bean>
         <http name="uiSecurity" request-matcher-ref="uiRequestMatcher" use-expressions="false"
               authentication-manager-ref="zoneAwareAuthzAuthenticationManager" xmlns="http://www.springframework.org/schema/security">
    -      <access-denied-handler error-page="/"/>
           <intercept-url pattern="/login**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
           <intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY" />
    -      <form-login login-page="/login" username-parameter="username" password-parameter="password"
    -                  login-processing-url="/login.do" authentication-failure-handler-ref="loginAuthenticationFailureHandler"
    -                  authentication-details-source-ref="authenticationDetailsSource"/>
    -      <logout logout-url="/logout.do" success-handler-ref="logoutHandler" />
    -      <csrf disabled="true"/>
    +      <form-login login-page="/login"
    +                  username-parameter="username"
    +                  password-parameter="password"
    +                  login-processing-url="/login.do"
    +                  authentication-failure-handler-ref="loginAuthenticationFailureHandler"
    +                  authentication-details-source-ref="authenticationDetailsSource"
    +                  default-target-url="/"/>
    +      <!--<logout logout-url="/logout.do" success-handler-ref="logoutHandler" invalidate-session="true"/>-->
    +      <custom-filter ref="logoutFilter" before="LOGOUT_FILTER"/>
    +      <csrf disabled="false"  token-repository-ref="loginCookieCsrfRepository" request-matcher-ref="uiLoginRequestMatcher"/>
    +      <access-denied-handler error-page="/login?error=invalid_login_request"/>
    +      <request-cache ref="savedRequestCache"/>
         </http>
     
     
    
  • samples/app/src/test/java/org/cloudfoundry/identity/app/integration/AuthenticationIntegrationTests.java+43 18 modified
    @@ -17,9 +17,13 @@
     import static org.junit.Assert.assertTrue;
     
     import java.util.Arrays;
    +import java.util.regex.Matcher;
    +import java.util.regex.Pattern;
     
    +import org.cloudfoundry.identity.uaa.test.IntegrationTestContextLoader;
     import org.cloudfoundry.identity.uaa.test.TestAccountSetup;
     import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.junit.Rule;
     import org.junit.Test;
     import org.springframework.http.HttpHeaders;
    @@ -49,55 +53,65 @@ public class AuthenticationIntegrationTests {
         @Test
         public void formLoginSucceeds() throws Exception {
     
    -        ResponseEntity<Void> result;
    +        ResponseEntity<String> result;
             String location;
    -        String cookie;
     
             HttpHeaders uaaHeaders = new HttpHeaders();
             HttpHeaders appHeaders = new HttpHeaders();
             uaaHeaders.setAccept(Arrays.asList(MediaType.TEXT_HTML));
             appHeaders.setAccept(Arrays.asList(MediaType.TEXT_HTML));
     
             // *** GET /app/id
    -        result = serverRunning.getForResponse("/id", appHeaders);
    +        result = serverRunning.getForString("/id", appHeaders);
             assertEquals(HttpStatus.FOUND, result.getStatusCode());
             location = result.getHeaders().getLocation().toString();
     
    -        cookie = result.getHeaders().getFirst("Set-Cookie");
    -        assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    -        appHeaders.set("Cookie", cookie);
    +        for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +            assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    +            appHeaders.add("Cookie", cookie);
    +        }
     
             assertTrue("Wrong location: " + location, location.contains("/oauth/authorize"));
             // *** GET /uaa/oauth/authorize
    -        result = serverRunning.getForResponse(location, uaaHeaders);
    +        result = serverRunning.getForString(location, uaaHeaders);
             assertEquals(HttpStatus.FOUND, result.getStatusCode());
             location = result.getHeaders().getLocation().toString();
     
    -        cookie = result.getHeaders().getFirst("Set-Cookie");
    -        assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    -        uaaHeaders.set("Cookie", cookie);
    +        for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +            assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    +            uaaHeaders.add("Cookie", cookie);
    +        }
     
             assertTrue("Wrong location: " + location, location.contains("/login"));
    +
    +        result = serverRunning.getForString(location, uaaHeaders);
    +        for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +            assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    +            uaaHeaders.add("Cookie", cookie);
    +        }
    +
             location = serverRunning.getAuthServerUrl("/login.do");
     
             MultiValueMap<String, String> formData;
    -        formData = new LinkedMultiValueMap<String, String>();
    +        formData = new LinkedMultiValueMap<>();
             formData.add("username", testAccounts.getUserName());
             formData.add("password", testAccounts.getPassword());
    +        formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, extractCookieCsrf(result.getBody()));
     
             // *** POST /uaa/login.do
    -        result = serverRunning.postForResponse(location, uaaHeaders, formData);
    +        result = serverRunning.postForString(location, formData, uaaHeaders);
     
    -        cookie = result.getHeaders().getFirst("Set-Cookie");
    -        assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    -        uaaHeaders.set("Cookie", cookie);
    +        for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +            assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    +            uaaHeaders.add("Cookie", cookie);
    +        }
     
             assertEquals(HttpStatus.FOUND, result.getStatusCode());
             location = result.getHeaders().getLocation().toString();
     
             assertTrue("Wrong location: " + location, location.contains("/oauth/authorize"));
             // *** GET /uaa/oauth/authorize
    -        result = serverRunning.getForResponse(location, uaaHeaders);
    +        result = serverRunning.getForString(location, uaaHeaders);
     
             // If there is no token in place already for this client we get the
             // approval page.
    @@ -109,7 +123,7 @@ public void formLoginSucceeds() throws Exception {
                 formData.add("user_oauth_approval", "true");
     
                 // *** POST /uaa/oauth/authorize
    -            result = serverRunning.postForResponse(location, uaaHeaders, formData);
    +            result = serverRunning.postForString(location, formData, uaaHeaders);
             }
     
             location = result.getHeaders().getLocation().toString();
    @@ -118,8 +132,19 @@ public void formLoginSucceeds() throws Exception {
             assertTrue("Wrong location: " + location, location.contains("/id"));
     
             // *** GET /app/id
    -        result = serverRunning.getForResponse(location, appHeaders);
    +        result = serverRunning.getForString(location, appHeaders);
             // System.err.println(result.getHeaders());
             assertEquals(HttpStatus.OK, result.getStatusCode());
         }
    +
    +    public static String extractCookieCsrf(String body) {
    +        String pattern = "\\<input type=\\\"hidden\\\" name=\\\"X-Uaa-Csrf\\\" value=\\\"(.*?)\\\"";
    +
    +        Pattern linkPattern = Pattern.compile(pattern);
    +        Matcher matcher = linkPattern.matcher(body);
    +        if (matcher.find()) {
    +            return matcher.group(1);
    +        }
    +        return null;
    +    }
     }
    
  • uaa/src/main/resources/messages.properties+1 0 modified
    @@ -49,6 +49,7 @@ login.password_expired=Your current password has expired. Please reset your pass
     login.invalid_idp=The application is not authorized for your account.
     login.account_not_verified=Your account is not verified. Please check your email for a verification link.
     login.account_locked=Your account has been locked because of too many failed attempts to login.
    +login.invalid_login_request=Invalid login attempt, request does not meet our security standards, please try again.
     new_invite.invalid_email=Please enter a valid email address.
     new_invite.existing_user=There is already a user with that username.
     
    
  • uaa/src/main/webapp/WEB-INF/spring-servlet.xml+0 22 modified
    @@ -205,28 +205,6 @@
     
         <bean id="http403EntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint" />
     
    -    <bean id="uiRequestMatcher" class="org.cloudfoundry.identity.uaa.security.web.DelegatingRequestMatcher">
    -        <constructor-arg>
    -            <list>
    -                <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    -                    <constructor-arg value="/" />
    -                </bean>
    -                <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    -                    <constructor-arg value="/spring_security_login" />
    -                </bean>
    -                <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    -                    <constructor-arg value="/oauth/**" />
    -                </bean>
    -                <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    -                    <constructor-arg value="/login**" />
    -                </bean>
    -                <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    -                    <constructor-arg value="/logout.do*" />
    -                </bean>
    -            </list>
    -        </constructor-arg>
    -    </bean>
    -
         <bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
     
         <bean id="cachingPasswordEncoder" class="org.cloudfoundry.identity.uaa.util.CachingPasswordEncoder">
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/CheckTokenEndpointIntegrationTests.java+20 5 modified
    @@ -21,8 +21,10 @@
     import java.util.Map;
     
     import org.cloudfoundry.identity.uaa.ServerRunning;
    +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils;
     import org.cloudfoundry.identity.uaa.test.TestAccountSetup;
     import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.junit.Rule;
     import org.junit.Test;
     import org.springframework.http.HttpHeaders;
    @@ -68,27 +70,40 @@ public void testDecodeToken() throws Exception {
             String location = result.getHeaders().getLocation().toString();
     
             if (result.getHeaders().containsKey("Set-Cookie")) {
    -            String cookie = result.getHeaders().getFirst("Set-Cookie");
    -            headers.set("Cookie", cookie);
    +            for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +                assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    +                headers.add("Cookie", cookie);
    +            }
             }
     
             ResponseEntity<String> response = serverRunning.getForString(location, headers);
    +
    +        if (response.getHeaders().containsKey("Set-Cookie")) {
    +            for (String cookie : response.getHeaders().get("Set-Cookie")) {
    +                assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    +                headers.add("Cookie", cookie);
    +            }
    +        }
             // should be directed to the login screen...
             assertTrue(response.getBody().contains("/login.do"));
             assertTrue(response.getBody().contains("username"));
             assertTrue(response.getBody().contains("password"));
    +        String csrf = IntegrationTestUtils.extractCookieCsrf(response.getBody());
     
    -        MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
    +        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
             formData.add("username", testAccounts.getUserName());
             formData.add("password", testAccounts.getPassword());
    +        formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, csrf);
     
             // Should be redirected to the original URL, but now authenticated
             result = serverRunning.postForResponse("/login.do", headers, formData);
             assertEquals(HttpStatus.FOUND, result.getStatusCode());
     
             if (result.getHeaders().containsKey("Set-Cookie")) {
    -            String cookie = result.getHeaders().getFirst("Set-Cookie");
    -            headers.set("Cookie", cookie);
    +            for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +                assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    +                headers.add("Cookie", cookie);
    +            }
             }
     
             response = serverRunning.getForString(result.getHeaders().getLocation().toString(), headers);
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/AutologinIT.java+24 4 modified
    @@ -17,7 +17,10 @@
     import java.util.Map;
     
     import static org.junit.Assert.assertEquals;
    +
    +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils;
     import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.junit.Assert;
     import org.junit.Rule;
     import org.junit.Test;
    @@ -173,15 +176,32 @@ public void testSimpleAutologinFlow() throws Exception {
     
             //here we must reset our state. we do that by following the logout flow.
             headers.clear();
    +
    +        headers.set(headers.ACCEPT, MediaType.TEXT_HTML_VALUE);
    +        ResponseEntity<String> loginResponse = template.exchange(baseUrl + "/login",
    +            HttpMethod.GET,
    +            new HttpEntity<>(null, headers),
    +            String.class);
    +
    +        if (loginResponse.getHeaders().containsKey("Set-Cookie")) {
    +            for (String cookie : loginResponse.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", cookie);
    +            }
    +        }
    +        String csrf = IntegrationTestUtils.extractCookieCsrf(loginResponse.getBody());
    +        requestBody.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, csrf);
    +
             headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    -        ResponseEntity<Void> loginResponse = restOperations.exchange(baseUrl + "/login.do",
    +        loginResponse = restOperations.exchange(baseUrl + "/login.do",
                 HttpMethod.POST,
                 new HttpEntity<>(requestBody, headers),
    -            Void.class);
    +            String.class);
             cookies = loginResponse.getHeaders().get("Set-Cookie");
    -        assertEquals(1, cookies.size());
    +        assertEquals(2, cookies.size());
             headers.clear();
    -        headers.add("Cookie", cookies.get(0));
    +        for (String cookie : loginResponse.getHeaders().get("Set-Cookie")) {
    +            headers.add("Cookie", cookie);
    +        }
             restOperations.exchange(baseUrl + "/profile",
                 HttpMethod.GET,
                 new HttpEntity<>(null, headers),Void.class);
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/LoginIT.java+54 4 modified
    @@ -14,6 +14,8 @@
     
     import com.dumbster.smtp.SimpleSmtpServer;
     import com.dumbster.smtp.SmtpMessage;
    +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.hamcrest.Matchers;
     import org.junit.After;
     import org.junit.Assert;
    @@ -26,15 +28,20 @@
     import org.springframework.beans.factory.annotation.Autowired;
     import org.springframework.beans.factory.annotation.Value;
     import org.springframework.http.HttpEntity;
    +import org.springframework.http.HttpHeaders;
     import org.springframework.http.HttpMethod;
     import org.springframework.http.HttpStatus;
    +import org.springframework.http.MediaType;
     import org.springframework.http.ResponseEntity;
    +import org.springframework.http.client.ClientHttpResponse;
     import org.springframework.security.oauth2.client.test.TestAccounts;
     import org.springframework.test.context.ContextConfiguration;
     import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
     import org.springframework.util.LinkedMultiValueMap;
    +import org.springframework.web.client.ResponseErrorHandler;
     import org.springframework.web.client.RestTemplate;
     
    +import java.io.IOException;
     import java.security.SecureRandom;
     import java.util.Iterator;
     
    @@ -43,6 +50,7 @@
     import static org.junit.Assert.assertEquals;
     import static org.junit.Assert.assertFalse;
     import static org.junit.Assert.assertThat;
    +import static org.junit.Assert.assertTrue;
     
     @RunWith(SpringJUnit4ClassRunner.class)
     @ContextConfiguration(classes = DefaultIntegrationTestConfig.class)
    @@ -100,16 +108,58 @@ public void testFailedLogin() throws Exception {
             assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString("Welcome!"));
         }
     
    +    @Test
    +    public void testAccessDeniedIfCsrfIsMissing() throws Exception {
    +        RestTemplate template = new RestTemplate();
    +        template.setErrorHandler(new ResponseErrorHandler() {
    +            @Override
    +            public boolean hasError(ClientHttpResponse response) throws IOException {
    +                return response.getRawStatusCode() >= 500;
    +            }
    +
    +            @Override
    +            public void handleError(ClientHttpResponse response) throws IOException {
    +            }
    +
    +        });
    +        LinkedMultiValueMap<String,String> body = new LinkedMultiValueMap<>();
    +        body.add("username", testAccounts.getUserName());
    +        body.add("password", testAccounts.getPassword());
    +        HttpHeaders headers = new HttpHeaders();
    +        headers.add(headers.ACCEPT, MediaType.TEXT_HTML_VALUE);
    +        ResponseEntity<String> loginResponse = template.exchange(baseUrl + "/login.do",
    +            HttpMethod.POST,
    +            new HttpEntity<>(body, headers),
    +            String.class);
    +        assertEquals(HttpStatus.FORBIDDEN, loginResponse.getStatusCode());
    +        assertTrue("CSRF message should be shown", loginResponse.getBody().contains("Invalid login attempt, request does not meet our security standards, please try again."));
    +    }
    +
         @Test
         public void testRedirectAfterFailedLogin() throws Exception {
             RestTemplate template = new RestTemplate();
    +
    +        HttpHeaders headers = new HttpHeaders();
    +        headers.set(headers.ACCEPT, MediaType.TEXT_HTML_VALUE);
    +        ResponseEntity<String> loginResponse = template.exchange(baseUrl + "/login",
    +            HttpMethod.GET,
    +            new HttpEntity<>(null, headers),
    +            String.class);
    +
    +        if (loginResponse.getHeaders().containsKey("Set-Cookie")) {
    +            for (String cookie : loginResponse.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", cookie);
    +            }
    +        }
    +        String csrf = IntegrationTestUtils.extractCookieCsrf(loginResponse.getBody());
             LinkedMultiValueMap<String,String> body = new LinkedMultiValueMap<>();
             body.add("username", testAccounts.getUserName());
             body.add("password", "invalidpassword");
    -        ResponseEntity<Void> loginResponse = template.exchange(baseUrl + "/login.do",
    +        body.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, csrf);
    +        loginResponse = template.exchange(baseUrl + "/login.do",
                 HttpMethod.POST,
    -            new HttpEntity<>(body, null),
    -            Void.class);
    +            new HttpEntity<>(body, headers),
    +            String.class);
             assertEquals(HttpStatus.FOUND, loginResponse.getStatusCode());
         }
     
    @@ -139,7 +189,7 @@ public void testBuildInfo() throws Exception {
             webDriver.get(baseUrl + "/login");
     
             String regex = "Version: \\S+, Commit: \\w{7}, Timestamp: .+, UAA: " + baseUrl;
    -        Assert.assertTrue(webDriver.findElement(By.cssSelector(".footer .copyright")).getAttribute("title").matches(regex));
    +        assertTrue(webDriver.findElement(By.cssSelector(".footer .copyright")).getAttribute("title").matches(regex));
         }
     
         private String createAnotherUser() {
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OpenIdTokenGrantsIT.java+18 4 modified
    @@ -17,6 +17,7 @@
     import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils;
     import org.cloudfoundry.identity.uaa.scim.ScimUser;
     import org.cloudfoundry.identity.uaa.util.JsonUtils;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.hamcrest.Matchers;
     import org.junit.Assert;
     import org.junit.Before;
    @@ -60,6 +61,7 @@
     import static org.hamcrest.Matchers.is;
     import static org.hamcrest.Matchers.not;
     import static org.junit.Assert.assertEquals;
    +import static org.junit.Assert.assertNotNull;
     import static org.junit.Assert.assertTrue;
     
     @RunWith(SpringJUnit4ClassRunner.class)
    @@ -258,8 +260,9 @@ private void doOpenIdHybridFlowIdTokenAndCode(Set<String> responseTypes, String
             String clientId = "app";
             String clientSecret = "appclientsecret";
             String redirectUri = "http://anywhere.com";
    -        String uri = loginUrl + "/oauth/authorize?response_type={response_type}&"+
    -            "state={state}&client_id={client_id}&redirect_uri={redirect_uri}";
    +        String uri = loginUrl +
    +                     "/oauth/authorize?response_type={response_type}&"+
    +                     "state={state}&client_id={client_id}&redirect_uri={redirect_uri}";
     
             ResponseEntity<Void> result = restOperations.exchange(
                 uri,
    @@ -288,20 +291,31 @@ private void doOpenIdHybridFlowIdTokenAndCode(Set<String> responseTypes, String
             assertTrue(response.getBody().contains("/login.do"));
             assertTrue(response.getBody().contains("username"));
             assertTrue(response.getBody().contains("password"));
    +        String csrf = IntegrationTestUtils.extractCookieCsrf(response.getBody());
    +
    +        if (response.getHeaders().containsKey("Set-Cookie")) {
    +            for (String cookie : response.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", cookie);
    +            }
    +        }
     
             MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
             formData.add("username", user.getUserName());
             formData.add("password", secret);
    +        formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, csrf);
     
             // Should be redirected to the original URL, but now authenticated
             result = restOperations.exchange(loginUrl + "/login.do", HttpMethod.POST, new HttpEntity<>(formData, headers), Void.class);
             assertEquals(HttpStatus.FOUND, result.getStatusCode());
     
    +        headers.remove("Cookie");
             if (result.getHeaders().containsKey("Set-Cookie")) {
    -            String cookie = result.getHeaders().getFirst("Set-Cookie");
    -            headers.set("Cookie", cookie);
    +            for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", cookie);
    +            }
             }
     
    +
             location = UriUtils.decode(result.getHeaders().getLocation().toString(), "UTF-8");
             response = restOperations.exchange(
                 location,
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/FormLoginIntegrationTests.java+92 55 modified
    @@ -12,26 +12,39 @@
      *******************************************************************************/
     package org.cloudfoundry.identity.uaa.integration;
     
    -import static org.junit.Assert.assertEquals;
    -import static org.junit.Assert.assertTrue;
    -
    -import java.util.Arrays;
    -
    +import org.apache.http.Header;
    +import org.apache.http.HttpHeaders;
    +import org.apache.http.client.config.RequestConfig;
    +import org.apache.http.client.methods.CloseableHttpResponse;
    +import org.apache.http.client.methods.HttpGet;
    +import org.apache.http.client.methods.HttpPost;
    +import org.apache.http.client.methods.HttpUriRequest;
    +import org.apache.http.client.methods.RequestBuilder;
    +import org.apache.http.impl.client.BasicCookieStore;
    +import org.apache.http.impl.client.CloseableHttpClient;
    +import org.apache.http.impl.client.HttpClients;
    +import org.apache.http.message.BasicHeader;
    +import org.apache.http.util.EntityUtils;
     import org.cloudfoundry.identity.uaa.ServerRunning;
    +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils;
     import org.cloudfoundry.identity.uaa.test.TestAccountSetup;
     import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
    +import org.junit.After;
    +import org.junit.Before;
     import org.junit.Rule;
     import org.junit.Test;
    -import org.springframework.http.HttpHeaders;
     import org.springframework.http.HttpStatus;
     import org.springframework.http.MediaType;
    -import org.springframework.http.ResponseEntity;
    -import org.springframework.util.LinkedMultiValueMap;
    -import org.springframework.util.MultiValueMap;
     
    -/**
    - * @author Dave Syer
    - */
    +import java.util.Arrays;
    +import java.util.List;
    +
    +import static org.junit.Assert.assertEquals;
    +import static org.junit.Assert.assertTrue;
    +import static org.springframework.http.HttpStatus.FOUND;
    +import static org.springframework.http.HttpStatus.OK;
    +
     public class FormLoginIntegrationTests {
     
         @Rule
    @@ -42,57 +55,81 @@ public class FormLoginIntegrationTests {
         @Rule
         public TestAccountSetup testAccountSetup = TestAccountSetup.standard(serverRunning, testAccounts);
     
    -    @Test
    -    public void testUnauthenticatedRedirect() throws Exception {
    +    Header header = new BasicHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE);
    +    List<Header> headers = Arrays.asList(header);
    +
    +    BasicCookieStore cookieStore = new BasicCookieStore();
    +    CloseableHttpClient httpclient;
     
    -        HttpHeaders headers = new HttpHeaders();
    -        headers.setAccept(Arrays.asList(MediaType.TEXT_HTML, MediaType.ALL));
    +    @Before
    +    public void createHttpClient() throws Exception {
    +        httpclient = HttpClients.custom()
    +            .setDefaultRequestConfig(RequestConfig.DEFAULT)
    +            .setDefaultHeaders(headers)
    +            .setDefaultCookieStore(cookieStore)
    +            .build();
    +    }
    +
    +    @After
    +    public void closeClient() throws Exception {
    +        httpclient.close();
    +    }
     
    -        String location = "/";
    -        ResponseEntity<Void> result = serverRunning.getForResponse(location, headers);
    -        // should be directed to the login screen...
    -        assertEquals(HttpStatus.FOUND, result.getStatusCode());
     
    -        location = result.getHeaders().getLocation().toString();
    +    @Test
    +    public void testUnauthenticatedRedirect() throws Exception {
    +        String location = serverRunning.getBaseUrl() + "/";
    +        HttpGet httpget = new HttpGet(location);
    +        httpget.setConfig(
    +            RequestConfig.custom().setRedirectsEnabled(false).build()
    +        );
    +        CloseableHttpResponse response = httpclient.execute(httpget);
    +        assertEquals(FOUND.value(), response.getStatusLine().getStatusCode());
    +        location = response.getFirstHeader("Location").getValue();
    +        response.close();
    +        httpget.completed();
             assertTrue(location.contains("/login"));
         }
     
         @Test
         public void testSuccessfulAuthenticationFlow() throws Exception {
    -
    -        HttpHeaders headers = new HttpHeaders();
    -        headers.setAccept(Arrays.asList(MediaType.TEXT_HTML, MediaType.ALL));
    -
    -        String location = "/";
    -        ResponseEntity<Void> result = serverRunning.getForResponse(location, headers);
    -        // should be directed to the login screen...
    -        assertEquals(HttpStatus.FOUND, result.getStatusCode());
    -
    -        location = result.getHeaders().getLocation().toString();
    -        ResponseEntity<String> response = serverRunning.getForString(location, headers);
    -        assertTrue(response.getBody().contains("/login.do"));
    -        assertTrue(response.getBody().contains("username"));
    -        assertTrue(response.getBody().contains("password"));
    -        
    -
    -        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
    -        formData.add("username", testAccounts.getUserName());
    -        formData.add("password", testAccounts.getPassword());
    -
    -        // Should be redirected to the original URL, but now authenticated
    -        result = serverRunning.postForResponse("/login.do", headers, formData);
    -        assertEquals(HttpStatus.FOUND, result.getStatusCode());
    -
    -        if (result.getHeaders().containsKey("Set-Cookie")) {
    -            String cookie = result.getHeaders().getFirst("Set-Cookie");
    -            headers.set("Cookie", cookie);
    -        }
    -
    -        response = serverRunning.getForString(result.getHeaders().getLocation().toString(), headers);
    -        assertEquals(HttpStatus.OK, response.getStatusCode());
    -        // The home page should be returned
    -        assertTrue(response.getBody().contains("Sign Out"));
    -
    +        //request home page /
    +        String location = serverRunning.getBaseUrl() + "/";
    +        HttpGet httpget = new HttpGet(location);
    +        CloseableHttpResponse response = httpclient.execute(httpget);
    +
    +        assertEquals(OK.value(), response.getStatusLine().getStatusCode());
    +
    +        String body = EntityUtils.toString(response.getEntity());
    +        EntityUtils.consume(response.getEntity());
    +        response.close();
    +        httpget.completed();
    +
    +        assertTrue(body.contains("/login.do"));
    +        assertTrue(body.contains("username"));
    +        assertTrue(body.contains("password"));
    +
    +        String csrf = IntegrationTestUtils.extractCookieCsrf(body);
    +
    +        HttpUriRequest loginPost = RequestBuilder.post()
    +            .setUri(serverRunning.getBaseUrl() + "/login.do")
    +            .addParameter("username",testAccounts.getUserName())
    +            .addParameter("password",testAccounts.getPassword())
    +            .addParameter(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, csrf)
    +            .build();
    +
    +        response = httpclient.execute(loginPost);
    +        assertEquals(FOUND.value(), response.getStatusLine().getStatusCode());
    +        location = response.getFirstHeader("Location").getValue();
    +        response.close();
    +
    +        httpget = new HttpGet(location);
    +        response = httpclient.execute(httpget);
    +        assertEquals(OK.value(), response.getStatusLine().getStatusCode());
    +
    +        body = EntityUtils.toString(response.getEntity());
    +        response.close();
    +        assertTrue(body.contains("Sign Out"));
         }
     
     }
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ImplicitTokenGrantIntegrationTests.java+10 1 modified
    @@ -20,8 +20,10 @@
     import java.util.Arrays;
     
     import org.cloudfoundry.identity.uaa.ServerRunning;
    +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils;
     import org.cloudfoundry.identity.uaa.test.TestAccountSetup;
     import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.junit.Rule;
     import org.junit.Test;
     import org.springframework.http.HttpHeaders;
    @@ -126,16 +128,23 @@ public void authzWithIntermediateFormLoginSucceeds() throws Exception {
             headers.set("Cookie", cookie);
     
             ResponseEntity<String> response = serverRunning.getForString(location, headers);
    +        if (response.getHeaders().containsKey("Set-Cookie")) {
    +            for (String c : response.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", c);
    +            }
    +        }
             // should be directed to the login screen...
             assertTrue(response.getBody().contains("/login.do"));
             assertTrue(response.getBody().contains("username"));
             assertTrue(response.getBody().contains("password"));
     
    +
             location = "/login.do";
     
    -        MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
    +        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
             formData.add("username", testAccounts.getUserName());
             formData.add("password", testAccounts.getPassword());
    +        formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody()));
     
             result = serverRunning.postForRedirect(location, headers, formData);
     
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/OpenIdTokenAuthorizationWithApprovalIntegrationTests.java+12 2 modified
    @@ -26,9 +26,11 @@
     import org.apache.http.client.HttpClient;
     import org.apache.http.impl.client.HttpClientBuilder;
     import org.cloudfoundry.identity.uaa.ServerRunning;
    +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils;
     import org.cloudfoundry.identity.uaa.scim.ScimUser;
     import org.cloudfoundry.identity.uaa.test.TestAccountSetup;
     import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.hamcrest.Matchers;
     import org.junit.Assert;
     import org.junit.Assume;
    @@ -231,6 +233,11 @@ private String doOpenIdHybridFlowIdTokenAndReturnCode(Set<String> responseTypes,
             }
     
             ResponseEntity<String> response = serverRunning.getForString(location, headers);
    +        if (response.getHeaders().containsKey("Set-Cookie")) {
    +            for (String cookie : response.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", cookie);
    +            }
    +        }
             // should be directed to the login screen...
             assertTrue(response.getBody().contains("/login.do"));
             assertTrue(response.getBody().contains("username"));
    @@ -239,14 +246,17 @@ private String doOpenIdHybridFlowIdTokenAndReturnCode(Set<String> responseTypes,
             MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
             formData.add("username", user.getUserName());
             formData.add("password", "s3Cret");
    +        formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody()));
     
             // Should be redirected to the original URL, but now authenticated
             result = serverRunning.postForResponse("/login.do", headers, formData);
             assertEquals(HttpStatus.FOUND, result.getStatusCode());
     
    +        headers.remove("Cookie");
             if (result.getHeaders().containsKey("Set-Cookie")) {
    -            String cookie = result.getHeaders().getFirst("Set-Cookie");
    -            headers.set("Cookie", cookie);
    +            for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", cookie);
    +            }
             }
     
             location = UriUtils.decode(result.getHeaders().getLocation().toString(), "UTF-8");
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/RefreshTokenSupportIntegrationTests.java+14 4 modified
    @@ -22,8 +22,10 @@
     import java.util.Map;
     
     import org.cloudfoundry.identity.uaa.ServerRunning;
    +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils;
     import org.cloudfoundry.identity.uaa.test.TestAccountSetup;
     import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.junit.Before;
     import org.junit.Rule;
     import org.junit.Test;
    @@ -81,6 +83,11 @@ public void testTokenRefreshedCorrectFlow() throws Exception {
             }
     
             ResponseEntity<String> response = serverRunning.getForString(location, headers);
    +        if (response.getHeaders().containsKey("Set-Cookie")) {
    +            for (String c : response.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", c);
    +            }
    +        }
             // should be directed to the login screen...
             assertTrue(response.getBody().contains("/login.do"));
             assertTrue(response.getBody().contains("username"));
    @@ -89,15 +96,18 @@ public void testTokenRefreshedCorrectFlow() throws Exception {
             MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
             formData.add("username", testAccounts.getUserName());
             formData.add("password", testAccounts.getPassword());
    +        formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody()));
     
             // Should be redirected to the original URL, but now authenticated
             result = serverRunning.postForResponse("/login.do", headers, formData);
    -        assertEquals(HttpStatus.FOUND, result.getStatusCode());
    -
    +        headers.remove("Cookie");
             if (result.getHeaders().containsKey("Set-Cookie")) {
    -            String cookie = result.getHeaders().getFirst("Set-Cookie");
    -            headers.set("Cookie", cookie);
    +            for (String c : result.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", c);
    +            }
             }
    +        assertEquals(HttpStatus.FOUND, result.getStatusCode());
    +
     
             response = serverRunning.getForString(result.getHeaders().getLocation().toString(), headers);
             if (response.getStatusCode() == HttpStatus.OK) {
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimGroupEndpointsIntegrationTests.java+13 3 modified
    @@ -15,11 +15,13 @@
     import org.apache.commons.logging.Log;
     import org.apache.commons.logging.LogFactory;
     import org.cloudfoundry.identity.uaa.ServerRunning;
    +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils;
     import org.cloudfoundry.identity.uaa.scim.ScimGroup;
     import org.cloudfoundry.identity.uaa.scim.ScimGroupMember;
     import org.cloudfoundry.identity.uaa.scim.ScimUser;
     import org.cloudfoundry.identity.uaa.test.TestAccountSetup;
     import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.cloudfoundry.identity.uaa.zone.IdentityZone;
     import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
     import org.junit.After;
    @@ -488,17 +490,25 @@ private OAuth2AccessToken getAccessToken(String clientId, String clientSecret, S
             assertTrue(response.getBody().contains("username"));
             assertTrue(response.getBody().contains("password"));
     
    -        MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
    +        if (response.getHeaders().containsKey("Set-Cookie")) {
    +            String cookie = response.getHeaders().getFirst("Set-Cookie");
    +            headers.add("Cookie", cookie);
    +        }
    +
    +        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
             formData.add("username", username);
             formData.add("password", password);
    +        formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody()));
     
             // Should be redirected to the original URL, but now authenticated
             result = serverRunning.postForResponse("/login.do", headers, formData);
             assertEquals(HttpStatus.FOUND, result.getStatusCode());
     
    +        headers.remove("Cookie");
             if (result.getHeaders().containsKey("Set-Cookie")) {
    -            String cookie = result.getHeaders().getFirst("Set-Cookie");
    -            headers.set("Cookie", cookie);
    +            for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", cookie);
    +            }
             }
     
             response = serverRunning.getForString(result.getHeaders().getLocation().toString(), headers);
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java+80 12 modified
    @@ -23,6 +23,7 @@
     import org.cloudfoundry.identity.uaa.scim.ScimUser;
     import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
     import org.cloudfoundry.identity.uaa.util.JsonUtils;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.cloudfoundry.identity.uaa.zone.IdentityProvider;
     import org.cloudfoundry.identity.uaa.zone.IdentityZone;
     import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter;
    @@ -57,8 +58,11 @@
     import java.util.List;
     import java.util.Map;
     import java.util.HashMap;
    +import java.util.regex.Matcher;
    +import java.util.regex.Pattern;
     
     import static org.junit.Assert.assertEquals;
    +import static org.junit.Assert.assertNotNull;
     import static org.junit.Assert.assertTrue;
     
     public class IntegrationTestUtils {
    @@ -75,7 +79,7 @@ public static ClientCredentialsResourceDetails getClientCredentialsResource(Stri
                 resource.setScope(Arrays.asList(scope));
             }
             resource.setClientAuthenticationScheme(AuthenticationScheme.header);
    -        resource.setAccessTokenUri(url+"/oauth/token");
    +        resource.setAccessTokenUri(url + "/oauth/token");
             return resource;
         }
     
    @@ -119,7 +123,7 @@ public static String getUserId(String zoneAdminToken, String url, String origin,
             RestTemplate template = new RestTemplate();
             MultiValueMap<String,String> headers = new LinkedMultiValueMap<>();
             headers.add("Accept", MediaType.APPLICATION_JSON_VALUE);
    -        headers.add("Authorization", "bearer "+zoneAdminToken);
    +        headers.add("Authorization", "bearer " + zoneAdminToken);
             headers.add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
             HttpEntity getHeaders = new HttpEntity(headers);
             ResponseEntity<String> userInfoGet = template.exchange(
    @@ -148,7 +152,7 @@ public static void deleteUser(String zoneAdminToken, String url, String userId)
             RestTemplate template = new RestTemplate();
             MultiValueMap<String,String> headers = new LinkedMultiValueMap<>();
             headers.add("Accept", MediaType.APPLICATION_JSON_VALUE);
    -        headers.add("Authorization", "bearer "+zoneAdminToken);
    +        headers.add("Authorization", "bearer " + zoneAdminToken);
             headers.add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
             HttpEntity deleteHeaders = new HttpEntity(headers);
             ResponseEntity<String> userDelete = template.exchange(
    @@ -194,7 +198,7 @@ public static ScimGroup getGroup(RestTemplate client,
                                          String groupName) {
             String id = findGroupId(client, url, groupName);
             if (id!=null) {
    -            ResponseEntity<ScimGroup> group = client.getForEntity(url+"/Groups/{id}", ScimGroup.class, id);
    +            ResponseEntity<ScimGroup> group = client.getForEntity(url + "/Groups/{id}", ScimGroup.class, id);
                 return group.getBody();
             }
             return null;
    @@ -375,8 +379,8 @@ public static IdentityZone fixtureIdentityZone(String id, String subdomain) {
             IdentityZone identityZone = new IdentityZone();
             identityZone.setId(id);
             identityZone.setSubdomain(subdomain);
    -        identityZone.setName("The Twiglet Zone["+id+"]");
    -        identityZone.setDescription("Like the Twilight Zone but tastier["+id+"].");
    +        identityZone.setName("The Twiglet Zone[" + id + "]");
    +        identityZone.setDescription("Like the Twilight Zone but tastier[" + id + "].");
             return identityZone;
         }
     
    @@ -406,6 +410,18 @@ public static String getAuthorizationCodeToken(ServerRunning serverRunning,
                                                        String clientSecret,
                                                        String username,
                                                        String password) throws Exception {
    +
    +        return getAuthorizationCodeTokenMap(serverRunning, testAccounts, clientId, clientSecret, username, password)
    +            .get("access_token");
    +    }
    +
    +    public static Map<String,String> getAuthorizationCodeTokenMap(ServerRunning serverRunning,
    +                                                                  UaaTestAccounts testAccounts,
    +                                                                  String clientId,
    +                                                                  String clientSecret,
    +                                                                  String username,
    +                                                                  String password) throws Exception {
    +        // TODO Fix to use json API rather than HTML
             HttpHeaders headers = new HttpHeaders();
             // TODO: should be able to handle just TEXT_HTML
             headers.setAccept(Arrays.asList(MediaType.TEXT_HTML, MediaType.ALL));
    @@ -422,27 +438,39 @@ public static String getAuthorizationCodeToken(ServerRunning serverRunning,
             String location = result.getHeaders().getLocation().toString();
     
             if (result.getHeaders().containsKey("Set-Cookie")) {
    -            String cookie = result.getHeaders().getFirst("Set-Cookie");
    -            headers.set("Cookie", cookie);
    +            for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +                assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    +                headers.add("Cookie", cookie);
    +            }
             }
     
             ResponseEntity<String> response = serverRunning.getForString(location, headers);
    +
    +        if (response.getHeaders().containsKey("Set-Cookie")) {
    +            for (String cookie : response.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", cookie);
    +            }
    +        }
             // should be directed to the login screen...
             assertTrue(response.getBody().contains("/login.do"));
             assertTrue(response.getBody().contains("username"));
             assertTrue(response.getBody().contains("password"));
    +        String csrf = IntegrationTestUtils.extractCookieCsrf(response.getBody());
     
    -        MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
    +        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
             formData.add("username", username);
             formData.add("password", password);
    +        formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, csrf);
     
             // Should be redirected to the original URL, but now authenticated
             result = serverRunning.postForResponse("/login.do", headers, formData);
             assertEquals(HttpStatus.FOUND, result.getStatusCode());
     
    +        headers.remove("Cookie");
             if (result.getHeaders().containsKey("Set-Cookie")) {
    -            String cookie = result.getHeaders().getFirst("Set-Cookie");
    -            headers.set("Cookie", cookie);
    +            for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", cookie);
    +            }
             }
     
             response = serverRunning.getForString(result.getHeaders().getLocation().toString(), headers);
    @@ -475,9 +503,21 @@ public static String getAuthorizationCodeToken(ServerRunning serverRunning,
             @SuppressWarnings("rawtypes")
             ResponseEntity<Map> tokenResponse = serverRunning.postForMap("/oauth/token", formData, tokenHeaders);
             assertEquals(HttpStatus.OK, tokenResponse.getStatusCode());
    +
             @SuppressWarnings("unchecked")
    +        OAuth2AccessToken accessToken = DefaultOAuth2AccessToken.valueOf(tokenResponse.getBody());
             Map<String, String> body = tokenResponse.getBody();
    -        return body.get("access_token");
    +
    +        formData = new LinkedMultiValueMap<>();
    +        headers.set("Authorization",
    +            testAccounts.getAuthorizationHeader(resource.getClientId(), resource.getClientSecret()));
    +        formData.add("token", accessToken.getValue());
    +
    +        tokenResponse = serverRunning.postForMap("/check_token", formData, headers);
    +        assertEquals(HttpStatus.OK, tokenResponse.getStatusCode());
    +        //System.err.println(tokenResponse.getBody());
    +        assertNotNull(tokenResponse.getBody().get("iss"));
    +        return body;
         }
     
         public static boolean hasAuthority(String authority, Collection<GrantedAuthority> authorities) {
    @@ -489,6 +529,34 @@ public static boolean hasAuthority(String authority, Collection<GrantedAuthority
             return false;
         }
     
    +    public static String extractCookieCsrf(String body) {
    +        String pattern = "\\<input type=\\\"hidden\\\" name=\\\"X-Uaa-Csrf\\\" value=\\\"(.*?)\\\"";
    +
    +        Pattern linkPattern = Pattern.compile(pattern);
    +        Matcher matcher = linkPattern.matcher(body);
    +        if (matcher.find()) {
    +            return matcher.group(1);
    +        }
    +        return null;
    +    }
    +
    +    public static void clearAllButJsessionID(HttpHeaders headers) {
    +        String jsessionid = null;
    +        List<String> cookies = headers.get("Cookie");
    +        if (cookies!=null) {
    +            for (String cookie : cookies) {
    +                if (cookie.contains("JSESSIONID")) {
    +                    jsessionid = cookie;
    +                }
    +            }
    +        }
    +        if (jsessionid!=null) {
    +            headers.set("Cookie", jsessionid);
    +        } else {
    +            headers.remove("Cookie");
    +        }
    +    }
    +
         public static class StatelessRequestFactory extends HttpComponentsClientHttpRequestFactory {
             @Override
             public HttpClient getHttpClient() {
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java+16 12 modified
    @@ -73,6 +73,7 @@
     import java.util.Map;
     import java.util.Properties;
     
    +import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf;
     import static org.hamcrest.Matchers.containsString;
     import static org.hamcrest.Matchers.hasEntry;
     import static org.hamcrest.Matchers.hasKey;
    @@ -281,7 +282,7 @@ public void testLogOutNullWhitelistedRedirectParameter() throws Exception {
         public void testLogOutEmptyWhitelistedRedirectParameter() throws Exception {
             WhitelistLogoutHandler logoutSuccessHandler = getWebApplicationContext().getBean(WhitelistLogoutHandler.class);
             logoutSuccessHandler.setAlwaysUseDefaultTargetUrl(false);
    -        logoutSuccessHandler.setWhitelist(Collections.<String>emptyList());
    +        logoutSuccessHandler.setWhitelist(Collections.EMPTY_LIST);
             try {
                 getMockMvc().perform(get("/logout.do").param("redirect", "https://www.google.com"))
                     .andExpect(status().isFound())
    @@ -1097,9 +1098,10 @@ public void login_LockoutPolicySucceeds_ForDefaultZone() throws Exception {
             ScimUser userToLockout = createUser("", adminToken);
             attemptFailedLogin(5, userToLockout.getUserName(), "");
             getMockMvc().perform(post("/login.do")
    -                .param("username", userToLockout.getUserName())
    -                .param("password", userToLockout.getPassword()))
    -                .andExpect(redirectedUrl("/login?error=account_locked"));
    +            .with(cookieCsrf())
    +            .param("username", userToLockout.getUserName())
    +            .param("password", userToLockout.getPassword()))
    +            .andExpect(redirectedUrl("/login?error=account_locked"));
         }
     
         @Test
    @@ -1117,10 +1119,11 @@ public void login_LockoutPolicySucceeds_WhenPolicyIsUpdatedByApi() throws Except
             attemptFailedLogin(2, userToLockout.getUserName(), subdomain);
     
             getMockMvc().perform(post("/login.do")
    -                .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))
    -                .param("username", userToLockout.getUserName())
    -                .param("password", userToLockout.getPassword()))
    -                .andExpect(redirectedUrl("/login?error=account_locked"));
    +            .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))
    +            .with(cookieCsrf())
    +            .param("username", userToLockout.getUserName())
    +            .param("password", userToLockout.getPassword()))
    +            .andExpect(redirectedUrl("/login?error=account_locked"));
         }
     
         private void changeLockoutPolicyForIdpInZone(IdentityZone zone) throws Exception {
    @@ -1150,12 +1153,13 @@ private void changeLockoutPolicyForIdpInZone(IdentityZone zone) throws Exception
         private void attemptFailedLogin(int numberOfAttempts, String username, String subdomain) throws Exception {
             String requestDomain = subdomain.equals("") ? "localhost" : subdomain + ".localhost";
             MockHttpServletRequestBuilder post = post("/login.do")
    -                .with(new SetServerNameRequestPostProcessor(requestDomain))
    -                .param("username", username)
    -                .param("password", "wrong_password");
    +            .with(new SetServerNameRequestPostProcessor(requestDomain))
    +            .with(cookieCsrf())
    +            .param("username", username)
    +            .param("password", "wrong_password");
             for (int i = 0; i < numberOfAttempts ; i++) {
                 getMockMvc().perform(post)
    -                    .andExpect(redirectedUrl("/login?error=login_failure"));
    +                .andExpect(redirectedUrl("/login?error=login_failure"));
             }
         }
     }
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java+7 0 modified
    @@ -54,6 +54,7 @@
     import org.junit.Before;
     import org.junit.Test;
     import org.mockito.ArgumentCaptor;
    +import org.mockito.Mock;
     import org.springframework.context.ApplicationEvent;
     import org.springframework.context.ApplicationListener;
     import org.springframework.http.MediaType;
    @@ -70,6 +71,7 @@
     import java.util.List;
     import java.util.Map;
     
    +import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf;
     import static org.hamcrest.Matchers.containsString;
     import static org.junit.Assert.assertEquals;
     import static org.junit.Assert.assertTrue;
    @@ -145,6 +147,7 @@ public void resetLoginClient() {
         @Test
         public void userLoginTest() throws Exception {
             MockHttpServletRequestBuilder loginPost = post("/login.do")
    +            .with(cookieCsrf())
                 .accept(MediaType.TEXT_HTML_VALUE)
                 .param("username", testUser.getUserName())
                 .param("password", testPassword);
    @@ -182,6 +185,7 @@ public void userLoginAuthenticateEndpointTest() throws Exception {
         @Test
         public void invalidPasswordLoginFailedTest() throws Exception {
             MockHttpServletRequestBuilder loginPost = post("/login.do")
    +            .with(cookieCsrf())
                 .accept(MediaType.TEXT_HTML_VALUE)
                 .param("username", testUser.getUserName())
                 .param("password", "");
    @@ -306,6 +310,7 @@ public void userNotFoundLoginFailedTest() throws Exception {
             String username = "test1234";
     
             MockHttpServletRequestBuilder loginPost = post("/login.do")
    +            .with(cookieCsrf())
                 .accept(MediaType.TEXT_HTML_VALUE)
                 .param("username", username)
                 .param("password", testPassword);
    @@ -325,6 +330,7 @@ public void userNotFoundLoginFailedTest() throws Exception {
         @Test
         public void userChangePasswordTest() throws Exception {
             MockHttpServletRequestBuilder loginPost = post("/login.do")
    +            .with(cookieCsrf())
                 .accept(MediaType.APPLICATION_JSON_VALUE)
                 .param("username", testUser.getUserName())
                 .param("password", testPassword);
    @@ -365,6 +371,7 @@ public void userChangePasswordTest() throws Exception {
         @Test
         public void userChangeInvalidPasswordTest() throws Exception {
             MockHttpServletRequestBuilder loginPost = post("/login.do")
    +            .with(cookieCsrf())
                 .accept(MediaType.APPLICATION_JSON_VALUE)
                 .param("username", testUser.getUserName())
                 .param("password", testPassword);
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java+16 9 modified
    @@ -75,6 +75,7 @@
     import java.util.List;
     import java.util.Set;
     
    +import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf;
     import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
     import static org.hamcrest.Matchers.containsString;
     import static org.junit.Assert.assertEquals;
    @@ -465,6 +466,7 @@ public void testLoginInNonDefaultZone() throws Exception {
     
             //IDP not yet created
             mockMvc.perform(post("/login.do").accept(TEXT_HTML_VALUE)
    +            .with(cookieCsrf())
                 .with(new SetServerNameRequestPostProcessor(zone.getSubdomain()+".localhost"))
                 .param("username", "marissa2")
                 .param("password", "ldap"))
    @@ -498,6 +500,7 @@ public void testLoginInNonDefaultZone() throws Exception {
             provider = MockMvcUtils.utils().createIdpUsingWebRequest(mockMvc, zone.getId(), zoneAdminToken, provider, status().isCreated());
     
             mockMvc.perform(post("/login.do").accept(TEXT_HTML_VALUE)
    +            .with(cookieCsrf())
                 .with(new SetServerNameRequestPostProcessor(zone.getSubdomain()+".localhost"))
                 .param("username", "marissa2")
                 .param("password", "ldap"))
    @@ -514,6 +517,7 @@ public void testLoginInNonDefaultZone() throws Exception {
             provider.setActive(false);
             MockMvcUtils.utils().createIdpUsingWebRequest(mockMvc, zone.getId(), zoneAdminToken, provider, status().isOk(), true);
             mockMvc.perform(post("/login.do").accept(TEXT_HTML_VALUE)
    +            .with(cookieCsrf())
                 .with(new SetServerNameRequestPostProcessor(zone.getSubdomain()+".localhost"))
                 .param("username", "marissa2")
                 .param("password", "ldap"))
    @@ -542,7 +546,8 @@ public void testLoginInNonDefaultZone() throws Exception {
             MockMvcUtils.utils().createIdpUsingWebRequest(mockMvc, zone.getId(), zoneAdminToken, provider, status().isOk(), true);
     
             mockMvc.perform(post("/login.do").accept(TEXT_HTML_VALUE)
    -            .with(new SetServerNameRequestPostProcessor(zone.getSubdomain()+".localhost"))
    +            .with(cookieCsrf())
    +            .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))
                 .param("username", "marissa2")
                 .param("password", "ldap"))
                 .andExpect(status().isFound())
    @@ -610,16 +615,18 @@ public void testLogin() throws Exception {
                     .andExpect(model().attributeDoesNotExist("saml"));
     
             mockMvc.perform(post("/login.do").accept(TEXT_HTML_VALUE)
    -                        .param("username", "marissa")
    -                        .param("password", "koaladsada"))
    -                .andExpect(status().isFound())
    -                .andExpect(redirectedUrl("/login?error=login_failure"));
    +            .with(cookieCsrf())
    +            .param("username", "marissa")
    +            .param("password", "koaladsada"))
    +            .andExpect(status().isFound())
    +            .andExpect(redirectedUrl("/login?error=login_failure"));
     
             mockMvc.perform(post("/login.do").accept(TEXT_HTML_VALUE)
    -                        .param("username", "marissa2")
    -                        .param("password", "ldap"))
    -                .andExpect(status().isFound())
    -                .andExpect(redirectedUrl("/"));
    +            .with(cookieCsrf())
    +            .param("username", "marissa2")
    +            .param("password", "ldap"))
    +            .andExpect(status().isFound())
    +            .andExpect(redirectedUrl("/"));
         }
     
         public void testAuthenticate() throws Exception {
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/password/PasswordChangeEndpointMockMvcTests.java+5 0 modified
    @@ -14,6 +14,7 @@
     
     import org.cloudfoundry.identity.uaa.message.PasswordChangeRequest;
     import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest;
    +import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils;
     import org.cloudfoundry.identity.uaa.scim.ScimUser;
     import org.cloudfoundry.identity.uaa.test.TestClient;
     import org.cloudfoundry.identity.uaa.util.JsonUtils;
    @@ -27,6 +28,7 @@
     
     import javax.servlet.http.HttpSession;
     
    +import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf;
     import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.utils;
     import static org.junit.Assert.assertNotNull;
     import static org.junit.Assert.assertNotSame;
    @@ -116,6 +118,7 @@ public void changePassword_Resets_Session() throws Exception {
     
             MockHttpSession session = new MockHttpSession();
             MockHttpSession afterLoginSession = (MockHttpSession) getMockMvc().perform(post("/login.do")
    +            .with(cookieCsrf())
                 .session(session)
                 .accept(TEXT_HTML_VALUE)
                 .param("username", user.getUserName())
    @@ -152,6 +155,7 @@ public void changePassword_Resets_All_Sessions() throws Exception {
     
             MockHttpSession session = new MockHttpSession();
             MockHttpSession afterLoginSessionA = (MockHttpSession) getMockMvc().perform(post("/login.do")
    +            .with(cookieCsrf())
                 .session(session)
                 .accept(TEXT_HTML_VALUE)
                 .param("username", user.getUserName())
    @@ -162,6 +166,7 @@ public void changePassword_Resets_All_Sessions() throws Exception {
     
             session = new MockHttpSession();
             MockHttpSession afterLoginSessionB = (MockHttpSession) getMockMvc().perform(post("/login.do")
    +            .with(cookieCsrf())
                 .session(session)
                 .accept(TEXT_HTML_VALUE)
                 .param("username", user.getUserName())
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenKeyEndpointMockMvcTests.java+0 3 modified
    @@ -30,7 +30,6 @@
     import static org.junit.Assert.assertNull;
     import static org.junit.Assert.assertTrue;
     import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
    -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
     import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
     
     public class TokenKeyEndpointMockMvcTests extends InjectedMockContextTest {
    @@ -112,7 +111,6 @@ public void checkTokenKeyValues() throws Exception {
                 get("/token_key")
                     .accept(MediaType.APPLICATION_JSON)
                     .header("Authorization", basicDigestHeaderValue))
    -            .andDo(print())
                 .andExpect(status().isOk())
                 .andReturn();
     
    @@ -126,7 +124,6 @@ public void checkTokenKeyValuesAnonymous() throws Exception {
             MvcResult result = getMockMvc().perform(
                 get("/token_key")
                     .accept(MediaType.APPLICATION_JSON))
    -            .andDo(print())
                 .andExpect(status().isOk())
                 .andReturn();
     
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java+43 0 modified
    @@ -32,6 +32,7 @@
     import org.cloudfoundry.identity.uaa.user.UaaUserDatabase;
     import org.cloudfoundry.identity.uaa.util.JsonUtils;
     import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.cloudfoundry.identity.uaa.zone.IdentityProvider;
     import org.cloudfoundry.identity.uaa.zone.IdentityZone;
     import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
    @@ -44,6 +45,8 @@
     import org.springframework.context.ConfigurableApplicationContext;
     import org.springframework.context.event.ApplicationEventMulticaster;
     import org.springframework.http.MediaType;
    +import org.springframework.mock.web.MockHttpServletRequest;
    +import org.springframework.mock.web.MockHttpServletResponse;
     import org.springframework.mock.web.MockHttpSession;
     import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
     import org.springframework.security.core.Authentication;
    @@ -54,14 +57,20 @@
     import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
     import org.springframework.security.oauth2.provider.ClientDetails;
     import org.springframework.security.oauth2.provider.client.BaseClientDetails;
    +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;
    +import org.springframework.security.test.web.support.WebTestUtils;
     import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
    +import org.springframework.security.web.csrf.CsrfToken;
    +import org.springframework.security.web.csrf.CsrfTokenRepository;
     import org.springframework.test.web.servlet.MockMvc;
     import org.springframework.test.web.servlet.MvcResult;
     import org.springframework.test.web.servlet.ResultMatcher;
     import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
    +import org.springframework.test.web.servlet.request.RequestPostProcessor;
     import org.springframework.util.StringUtils;
     import org.springframework.web.util.UriComponentsBuilder;
     
    +import javax.servlet.http.Cookie;
     import java.util.Arrays;
     import java.util.Collections;
     import java.util.LinkedList;
    @@ -508,4 +517,38 @@ public void setAuthentication(Authentication authentication) {
             }
         }
     
    +    public static class CookieCsrfPostProcessor implements RequestPostProcessor {
    +
    +        private boolean useInvalidToken = false;
    +        public CookieCsrfPostProcessor useInvalidToken() {
    +            useInvalidToken = true;
    +            return this;
    +        }
    +
    +        public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
    +
    +            CsrfTokenRepository repository = new CookieBasedCsrfTokenRepository();
    +            CsrfToken token = repository.generateToken(request);
    +            repository.saveToken(token, request, new MockHttpServletResponse());
    +            String tokenValue = useInvalidToken ? "invalid" + token.getToken() : token.getToken();
    +            Cookie cookie = new Cookie(token.getParameterName(), tokenValue);
    +            cookie.setHttpOnly(true);
    +            Cookie[] cookies = request.getCookies();
    +            if (cookies==null) {
    +                request.setCookies(cookie);
    +            } else {
    +                Cookie[] newcookies = new Cookie[cookies.length+1];
    +                System.arraycopy(cookies, 0, newcookies, 0, cookies.length);
    +                newcookies[cookies.length] = cookie;
    +                request.setCookies(newcookies);
    +            }
    +            request.setParameter(token.getParameterName(), tokenValue);
    +            return request;
    +        }
    +
    +        public static CookieCsrfPostProcessor cookieCsrf() {
    +            return new CookieCsrfPostProcessor();
    +        }
    +    }
    +
     }
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java+0 3 modified
    @@ -22,7 +22,6 @@
     import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor;
     import org.cloudfoundry.identity.uaa.zone.IdentityZone;
     import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter;
    -import org.junit.Assert;
     import org.junit.Before;
     import org.junit.Test;
     import org.springframework.http.HttpStatus;
    @@ -33,7 +32,6 @@
     
     import static org.springframework.http.MediaType.APPLICATION_JSON;
     import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
    -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
     import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
     import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
     import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
    @@ -80,7 +78,6 @@ private ScimUser createUser(ScimUser user, String token, String subdomain, Strin
             if (switchZone!=null) post.header(IdentityZoneSwitchingFilter.HEADER, switchZone);
     
             MvcResult result = getMockMvc().perform(post)
    -            .andDo(print())
                 .andExpect(status().isCreated())
                 .andExpect(header().string("ETag", "\"0\""))
                 .andExpect(jsonPath("$.userName").value(user.getUserName()))
    
bdb1a39a1e72

Make the unauthorized endpoints stateless. Only save the request if it is /oauth/authorize

https://github.com/cloudfoundry/uaaFilip HanikAug 7, 2015via ghsa
3 files changed · +30 66
  • common/src/main/java/org/cloudfoundry/identity/uaa/security/web/DelegatingRequestMatcher.java+0 43 removed
    @@ -1,43 +0,0 @@
    -/*******************************************************************************
    - *     Cloud Foundry
    - *     Copyright (c) [2009-2014] 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.security.web;
    -
    -import org.springframework.security.web.util.matcher.RequestMatcher;
    -
    -import java.util.ArrayList;
    -import java.util.List;
    -
    -import javax.servlet.http.HttpServletRequest;
    -
    -/**
    - * @author Luke Taylor
    - */
    -public class DelegatingRequestMatcher implements RequestMatcher {
    -    private final List<RequestMatcher> matchers;
    -
    -    public DelegatingRequestMatcher(List<RequestMatcher> matchers) {
    -        this.matchers = new ArrayList<>(matchers);
    -    }
    -
    -    @Override
    -    public boolean matches(HttpServletRequest request) {
    -        for (RequestMatcher m : matchers) {
    -            if (m.matches(request)) {
    -                return true;
    -            }
    -        }
    -
    -        return false;
    -    }
    -}
    
  • login/src/main/resources/login-ui.xml+30 1 modified
    @@ -132,6 +132,30 @@
             <constructor-arg value="/logout.do" />
         </bean>
     
    +    <bean id="uiAuthorizeRequestMatcher" class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    +        <constructor-arg value="/oauth/authorize**" />
    +    </bean>
    +
    +    <bean id="uiRequestMatcher" class="org.springframework.security.web.util.matcher.OrRequestMatcher">
    +        <constructor-arg>
    +            <list>
    +                <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    +                    <constructor-arg value="/" />
    +                </bean>
    +                <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    +                    <constructor-arg value="/oauth/**" />
    +                </bean>
    +                <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    +                    <constructor-arg value="/login**" />
    +                </bean>
    +                <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    +                    <constructor-arg value="/logout.do**" />
    +                </bean>
    +            </list>
    +        </constructor-arg>
    +    </bean>
    +
    +
     
         <bean id="loginCookieCsrfRepository" class="org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository"/>
         <bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
    @@ -151,6 +175,9 @@
             </constructor-arg>
             <property name="logoutRequestMatcher" ref="uiLogoutRequestMatcher"/>
         </bean>
    +    <bean id="savedRequestCache" class="org.springframework.security.web.savedrequest.HttpSessionRequestCache">
    +        <property name="requestMatcher" ref="uiAuthorizeRequestMatcher"/>
    +    </bean>
         <http name="uiSecurity" request-matcher-ref="uiRequestMatcher" use-expressions="false"
               authentication-manager-ref="zoneAwareAuthzAuthenticationManager" xmlns="http://www.springframework.org/schema/security">
           <intercept-url pattern="/login**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
    @@ -160,11 +187,13 @@
                       password-parameter="password"
                       login-processing-url="/login.do"
                       authentication-failure-handler-ref="loginAuthenticationFailureHandler"
    -                  authentication-details-source-ref="authenticationDetailsSource"/>
    +                  authentication-details-source-ref="authenticationDetailsSource"
    +                  default-target-url="/"/>
           <!--<logout logout-url="/logout.do" success-handler-ref="logoutHandler" invalidate-session="true"/>-->
           <custom-filter ref="logoutFilter" before="LOGOUT_FILTER"/>
           <csrf disabled="false"  token-repository-ref="loginCookieCsrfRepository" request-matcher-ref="uiLoginRequestMatcher"/>
           <access-denied-handler error-page="/login?error=invalid_login_request"/>
    +      <request-cache ref="savedRequestCache"/>
         </http>
     
     
    
  • uaa/src/main/webapp/WEB-INF/spring-servlet.xml+0 22 modified
    @@ -205,28 +205,6 @@
     
         <bean id="http403EntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint" />
     
    -    <bean id="uiRequestMatcher" class="org.cloudfoundry.identity.uaa.security.web.DelegatingRequestMatcher">
    -        <constructor-arg>
    -            <list>
    -                <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    -                    <constructor-arg value="/" />
    -                </bean>
    -                <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    -                    <constructor-arg value="/spring_security_login" />
    -                </bean>
    -                <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    -                    <constructor-arg value="/oauth/**" />
    -                </bean>
    -                <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    -                    <constructor-arg value="/login**" />
    -                </bean>
    -                <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    -                    <constructor-arg value="/logout.do**" />
    -                </bean>
    -            </list>
    -        </constructor-arg>
    -    </bean>
    -
         <bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
     
         <bean id="cachingPasswordEncoder" class="org.cloudfoundry.identity.uaa.util.CachingPasswordEncoder">
    
41dba9d81dbd

Implement cookie based CSRF for login form

https://github.com/cloudfoundry/uaaFilip HanikAug 7, 2015via ghsa
24 files changed · +680 147
  • common/src/main/java/org/cloudfoundry/identity/uaa/web/CookieBasedCsrfTokenRepository.java+105 0 added
    @@ -0,0 +1,105 @@
    +/*
    + * ******************************************************************************
    + *  *     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.web;
    +
    +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
    +import org.springframework.security.web.csrf.CsrfToken;
    +import org.springframework.security.web.csrf.CsrfTokenRepository;
    +import org.springframework.security.web.csrf.DefaultCsrfToken;
    +
    +import javax.servlet.http.Cookie;
    +import javax.servlet.http.HttpServletRequest;
    +import javax.servlet.http.HttpServletResponse;
    +
    +public class CookieBasedCsrfTokenRepository implements CsrfTokenRepository {
    +
    +    public static final String DEFAULT_CSRF_HEADER_NAME = "X-CSRF-TOKEN";
    +    public static final String DEFAULT_CSRF_COOKIE_NAME = "X-Uaa-Csrf";
    +    public static final int DEFAULT_COOKIE_MAX_AGE = 300;
    +
    +    private RandomValueStringGenerator generator = new RandomValueStringGenerator(6);
    +    private String parameterName = DEFAULT_CSRF_COOKIE_NAME;
    +    private String headerName = DEFAULT_CSRF_HEADER_NAME;
    +    private int cookieMaxAge = DEFAULT_COOKIE_MAX_AGE;
    +
    +    public int getCookieMaxAge() {
    +        return cookieMaxAge;
    +    }
    +
    +    public void setCookieMaxAge(int cookieMaxAge) {
    +        this.cookieMaxAge = cookieMaxAge;
    +    }
    +
    +    public String getHeaderName() {
    +        return headerName;
    +    }
    +
    +    public void setHeaderName(String headerName) {
    +        this.headerName = headerName;
    +    }
    +
    +    public String getParameterName() {
    +        return parameterName;
    +    }
    +
    +    public void setParameterName(String parameterName) {
    +        this.parameterName = parameterName;
    +    }
    +
    +    public void setGenerator(RandomValueStringGenerator generator) {
    +        this.generator = generator;
    +    }
    +
    +    public RandomValueStringGenerator getGenerator() {
    +        return generator;
    +    }
    +
    +    @Override
    +    public CsrfToken generateToken(HttpServletRequest request) {
    +        String token = generator.generate();
    +        return new DefaultCsrfToken(getHeaderName(), getParameterName(), token);
    +    }
    +
    +    @Override
    +    public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
    +        boolean expire = false;
    +        if (token==null) {
    +            token = generateToken(request);
    +            expire = true;
    +        }
    +        Cookie csrfCookie = new Cookie(token.getParameterName(), token.getToken());
    +        csrfCookie.setHttpOnly(true);
    +        if (expire) {
    +            csrfCookie.setMaxAge(0);
    +        } else {
    +            csrfCookie.setMaxAge(getCookieMaxAge());
    +        }
    +        response.addCookie(csrfCookie);
    +    }
    +
    +    @Override
    +    public CsrfToken loadToken(HttpServletRequest request) {
    +        Cookie[] cookies = request.getCookies();
    +        if (cookies!=null) {
    +            for (Cookie cookie : request.getCookies()) {
    +                if (getParameterName().equals(cookie.getName())) {
    +                    return new DefaultCsrfToken(getHeaderName(), getParameterName(), cookie.getValue());
    +                }
    +            }
    +        }
    +        return null;
    +    }
    +}
    
  • common/src/test/java/org/cloudfoundry/identity/uaa/ServerRunning.java+4 2 modified
    @@ -272,9 +272,11 @@ public ResponseEntity<Void> postForRedirect(String path, HttpHeaders headers, Mu
                 throw new IllegalStateException("Expected 302 but server returned status code " + exchange.getStatusCode());
             }
     
    +        headers.remove("Cookie");
             if (exchange.getHeaders().containsKey("Set-Cookie")) {
    -            String cookie = exchange.getHeaders().getFirst("Set-Cookie");
    -            headers.set("Cookie", cookie);
    +            for (String cookie : exchange.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", cookie);
    +            }
             }
     
             String location = exchange.getHeaders().getLocation().toString();
    
  • common/src/test/java/org/cloudfoundry/identity/uaa/web/CookieBasedCsrfTokenRepositoryTests.java+76 0 added
    @@ -0,0 +1,76 @@
    +/*
    + * ******************************************************************************
    + *  *     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.web;
    +
    +import org.junit.Test;
    +import org.springframework.mock.web.MockHttpServletRequest;
    +import org.springframework.mock.web.MockHttpServletResponse;
    +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
    +import org.springframework.security.web.csrf.CsrfToken;
    +
    +import javax.servlet.http.Cookie;
    +
    +import static org.junit.Assert.assertEquals;
    +import static org.junit.Assert.assertNotNull;
    +
    +public class CookieBasedCsrfTokenRepositoryTests {
    +
    +    @Test
    +    public void testGetHeader_and_Parameter_Name() throws Exception {
    +        CookieBasedCsrfTokenRepository repo = new CookieBasedCsrfTokenRepository();
    +        assertEquals(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, repo.getParameterName());
    +        repo.setParameterName("testcookie");
    +        assertEquals("testcookie", repo.getParameterName());
    +
    +        assertEquals(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_HEADER_NAME, repo.getHeaderName());
    +        repo.setHeaderName("testheader");
    +        assertEquals("testheader", repo.getHeaderName());
    +
    +        repo.setGenerator(new RandomValueStringGenerator() {
    +            @Override
    +            public String generate() {
    +                return "token-id";
    +            }
    +        });
    +
    +        CsrfToken token = repo.generateToken(new MockHttpServletRequest());
    +        assertEquals("testheader", token.getHeaderName());
    +        assertEquals("testcookie", token.getParameterName());
    +        assertEquals("token-id", token.getToken());
    +    }
    +
    +    @Test
    +    public void testSave_and_Load_Token() throws Exception {
    +        CookieBasedCsrfTokenRepository repo = new CookieBasedCsrfTokenRepository();
    +        MockHttpServletRequest request = new MockHttpServletRequest();
    +        MockHttpServletResponse response = new MockHttpServletResponse();
    +        CsrfToken token = repo.generateToken(request);
    +        repo.saveToken(token, request, response);
    +
    +        Cookie cookie = response.getCookie(token.getParameterName());
    +        assertNotNull(cookie);
    +        assertEquals(token.getToken(), cookie.getValue());
    +        assertEquals(true, cookie.isHttpOnly());
    +
    +        request.setCookies(cookie);
    +
    +        CsrfToken saved = repo.loadToken(request);
    +        assertEquals(token.getToken(), saved.getToken());
    +        assertEquals(token.getHeaderName(), saved.getHeaderName());
    +        assertEquals(token.getParameterName(), saved.getParameterName());
    +    }
    +
    +}
    \ No newline at end of file
    
  • login/src/main/resources/login-ui.xml+26 5 modified
    @@ -124,16 +124,37 @@
             <access-denied-handler ref="loginEntryPoint"/>
         </http>
     
    +    <bean id="uiLoginRequestMatcher" class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    +        <constructor-arg value="/login.do" />
    +    </bean>
    +
    +    <bean id="uiLogoutRequestMatcher" class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    +        <constructor-arg value="/logout.do" />
    +    </bean>
    +
    +
    +    <bean id="loginCookieCsrfRepository" class="org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository"/>
    +    <bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
    +        <constructor-arg ref="logoutHandler"/>
    +        <constructor-arg>
    +            <bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
    +        </constructor-arg>
    +        <property name="logoutRequestMatcher" ref="uiLogoutRequestMatcher"/>
    +    </bean>
         <http name="uiSecurity" request-matcher-ref="uiRequestMatcher" use-expressions="false"
               authentication-manager-ref="zoneAwareAuthzAuthenticationManager" xmlns="http://www.springframework.org/schema/security">
    -      <access-denied-handler error-page="/"/>
           <intercept-url pattern="/login**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
           <intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY" />
    -      <form-login login-page="/login" username-parameter="username" password-parameter="password"
    -                  login-processing-url="/login.do" authentication-failure-handler-ref="loginAuthenticationFailureHandler"
    +      <form-login login-page="/login"
    +                  username-parameter="username"
    +                  password-parameter="password"
    +                  login-processing-url="/login.do"
    +                  authentication-failure-handler-ref="loginAuthenticationFailureHandler"
                       authentication-details-source-ref="authenticationDetailsSource"/>
    -      <logout logout-url="/logout.do" success-handler-ref="logoutHandler" />
    -      <csrf disabled="true"/>
    +      <!--<logout logout-url="/logout.do" success-handler-ref="logoutHandler" invalidate-session="true"/>-->
    +      <custom-filter ref="logoutFilter" before="LOGOUT_FILTER"/>
    +      <csrf disabled="false"  token-repository-ref="loginCookieCsrfRepository" request-matcher-ref="uiLoginRequestMatcher"/>
    +      <access-denied-handler error-page="/login?error=invalid_login_request"/>
         </http>
     
     
    
  • samples/app/src/test/java/org/cloudfoundry/identity/app/integration/AuthenticationIntegrationTests.java+43 18 modified
    @@ -17,9 +17,13 @@
     import static org.junit.Assert.assertTrue;
     
     import java.util.Arrays;
    +import java.util.regex.Matcher;
    +import java.util.regex.Pattern;
     
    +import org.cloudfoundry.identity.uaa.test.IntegrationTestContextLoader;
     import org.cloudfoundry.identity.uaa.test.TestAccountSetup;
     import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.junit.Rule;
     import org.junit.Test;
     import org.springframework.http.HttpHeaders;
    @@ -49,55 +53,65 @@ public class AuthenticationIntegrationTests {
         @Test
         public void formLoginSucceeds() throws Exception {
     
    -        ResponseEntity<Void> result;
    +        ResponseEntity<String> result;
             String location;
    -        String cookie;
     
             HttpHeaders uaaHeaders = new HttpHeaders();
             HttpHeaders appHeaders = new HttpHeaders();
             uaaHeaders.setAccept(Arrays.asList(MediaType.TEXT_HTML));
             appHeaders.setAccept(Arrays.asList(MediaType.TEXT_HTML));
     
             // *** GET /app/id
    -        result = serverRunning.getForResponse("/id", appHeaders);
    +        result = serverRunning.getForString("/id", appHeaders);
             assertEquals(HttpStatus.FOUND, result.getStatusCode());
             location = result.getHeaders().getLocation().toString();
     
    -        cookie = result.getHeaders().getFirst("Set-Cookie");
    -        assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    -        appHeaders.set("Cookie", cookie);
    +        for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +            assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    +            appHeaders.add("Cookie", cookie);
    +        }
     
             assertTrue("Wrong location: " + location, location.contains("/oauth/authorize"));
             // *** GET /uaa/oauth/authorize
    -        result = serverRunning.getForResponse(location, uaaHeaders);
    +        result = serverRunning.getForString(location, uaaHeaders);
             assertEquals(HttpStatus.FOUND, result.getStatusCode());
             location = result.getHeaders().getLocation().toString();
     
    -        cookie = result.getHeaders().getFirst("Set-Cookie");
    -        assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    -        uaaHeaders.set("Cookie", cookie);
    +        for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +            assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    +            uaaHeaders.add("Cookie", cookie);
    +        }
     
             assertTrue("Wrong location: " + location, location.contains("/login"));
    +
    +        result = serverRunning.getForString(location, uaaHeaders);
    +        for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +            assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    +            uaaHeaders.add("Cookie", cookie);
    +        }
    +
             location = serverRunning.getAuthServerUrl("/login.do");
     
             MultiValueMap<String, String> formData;
    -        formData = new LinkedMultiValueMap<String, String>();
    +        formData = new LinkedMultiValueMap<>();
             formData.add("username", testAccounts.getUserName());
             formData.add("password", testAccounts.getPassword());
    +        formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, extractCookieCsrf(result.getBody()));
     
             // *** POST /uaa/login.do
    -        result = serverRunning.postForResponse(location, uaaHeaders, formData);
    +        result = serverRunning.postForString(location, formData, uaaHeaders);
     
    -        cookie = result.getHeaders().getFirst("Set-Cookie");
    -        assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    -        uaaHeaders.set("Cookie", cookie);
    +        for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +            assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    +            uaaHeaders.add("Cookie", cookie);
    +        }
     
             assertEquals(HttpStatus.FOUND, result.getStatusCode());
             location = result.getHeaders().getLocation().toString();
     
             assertTrue("Wrong location: " + location, location.contains("/oauth/authorize"));
             // *** GET /uaa/oauth/authorize
    -        result = serverRunning.getForResponse(location, uaaHeaders);
    +        result = serverRunning.getForString(location, uaaHeaders);
     
             // If there is no token in place already for this client we get the
             // approval page.
    @@ -109,7 +123,7 @@ public void formLoginSucceeds() throws Exception {
                 formData.add("user_oauth_approval", "true");
     
                 // *** POST /uaa/oauth/authorize
    -            result = serverRunning.postForResponse(location, uaaHeaders, formData);
    +            result = serverRunning.postForString(location, formData, uaaHeaders);
             }
     
             location = result.getHeaders().getLocation().toString();
    @@ -118,8 +132,19 @@ public void formLoginSucceeds() throws Exception {
             assertTrue("Wrong location: " + location, location.contains("/id"));
     
             // *** GET /app/id
    -        result = serverRunning.getForResponse(location, appHeaders);
    +        result = serverRunning.getForString(location, appHeaders);
             // System.err.println(result.getHeaders());
             assertEquals(HttpStatus.OK, result.getStatusCode());
         }
    +
    +    public static String extractCookieCsrf(String body) {
    +        String pattern = "\\<input type=\\\"hidden\\\" name=\\\"X-Uaa-Csrf\\\" value=\\\"(.*?)\\\"";
    +
    +        Pattern linkPattern = Pattern.compile(pattern);
    +        Matcher matcher = linkPattern.matcher(body);
    +        if (matcher.find()) {
    +            return matcher.group(1);
    +        }
    +        return null;
    +    }
     }
    
  • uaa/src/main/resources/messages.properties+1 0 modified
    @@ -49,6 +49,7 @@ login.password_expired=Your current password has expired. Please reset your pass
     login.invalid_idp=The application is not authorized for your account.
     login.account_not_verified=Your account is not verified. Please check your email for a verification link.
     login.account_locked=Your account has been locked because of too many failed attempts to login.
    +login.invalid_login_request=Invalid login attempt, request does not meet our security standards, please try again.
     new_invite.invalid_email=Please enter a valid email address.
     new_invite.existing_user=There is already a user with that username.
     
    
  • uaa/src/main/webapp/WEB-INF/spring-servlet.xml+1 1 modified
    @@ -221,7 +221,7 @@
                         <constructor-arg value="/login**" />
                     </bean>
                     <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
    -                    <constructor-arg value="/logout.do*" />
    +                    <constructor-arg value="/logout.do**" />
                     </bean>
                 </list>
             </constructor-arg>
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/CheckTokenEndpointIntegrationTests.java+20 5 modified
    @@ -21,8 +21,10 @@
     import java.util.Map;
     
     import org.cloudfoundry.identity.uaa.ServerRunning;
    +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils;
     import org.cloudfoundry.identity.uaa.test.TestAccountSetup;
     import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.junit.Rule;
     import org.junit.Test;
     import org.springframework.http.HttpHeaders;
    @@ -68,27 +70,40 @@ public void testDecodeToken() throws Exception {
             String location = result.getHeaders().getLocation().toString();
     
             if (result.getHeaders().containsKey("Set-Cookie")) {
    -            String cookie = result.getHeaders().getFirst("Set-Cookie");
    -            headers.set("Cookie", cookie);
    +            for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +                assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    +                headers.add("Cookie", cookie);
    +            }
             }
     
             ResponseEntity<String> response = serverRunning.getForString(location, headers);
    +
    +        if (response.getHeaders().containsKey("Set-Cookie")) {
    +            for (String cookie : response.getHeaders().get("Set-Cookie")) {
    +                assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    +                headers.add("Cookie", cookie);
    +            }
    +        }
             // should be directed to the login screen...
             assertTrue(response.getBody().contains("/login.do"));
             assertTrue(response.getBody().contains("username"));
             assertTrue(response.getBody().contains("password"));
    +        String csrf = IntegrationTestUtils.extractCookieCsrf(response.getBody());
     
    -        MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
    +        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
             formData.add("username", testAccounts.getUserName());
             formData.add("password", testAccounts.getPassword());
    +        formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, csrf);
     
             // Should be redirected to the original URL, but now authenticated
             result = serverRunning.postForResponse("/login.do", headers, formData);
             assertEquals(HttpStatus.FOUND, result.getStatusCode());
     
             if (result.getHeaders().containsKey("Set-Cookie")) {
    -            String cookie = result.getHeaders().getFirst("Set-Cookie");
    -            headers.set("Cookie", cookie);
    +            for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +                assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    +                headers.add("Cookie", cookie);
    +            }
             }
     
             response = serverRunning.getForString(result.getHeaders().getLocation().toString(), headers);
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/AutologinIT.java+24 4 modified
    @@ -17,7 +17,10 @@
     import java.util.Map;
     
     import static org.junit.Assert.assertEquals;
    +
    +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils;
     import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.junit.Assert;
     import org.junit.Rule;
     import org.junit.Test;
    @@ -173,15 +176,32 @@ public void testSimpleAutologinFlow() throws Exception {
     
             //here we must reset our state. we do that by following the logout flow.
             headers.clear();
    +
    +        headers.set(headers.ACCEPT, MediaType.TEXT_HTML_VALUE);
    +        ResponseEntity<String> loginResponse = template.exchange(baseUrl + "/login",
    +            HttpMethod.GET,
    +            new HttpEntity<>(null, headers),
    +            String.class);
    +
    +        if (loginResponse.getHeaders().containsKey("Set-Cookie")) {
    +            for (String cookie : loginResponse.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", cookie);
    +            }
    +        }
    +        String csrf = IntegrationTestUtils.extractCookieCsrf(loginResponse.getBody());
    +        requestBody.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, csrf);
    +
             headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    -        ResponseEntity<Void> loginResponse = restOperations.exchange(baseUrl + "/login.do",
    +        loginResponse = restOperations.exchange(baseUrl + "/login.do",
                 HttpMethod.POST,
                 new HttpEntity<>(requestBody, headers),
    -            Void.class);
    +            String.class);
             cookies = loginResponse.getHeaders().get("Set-Cookie");
    -        assertEquals(1, cookies.size());
    +        assertEquals(2, cookies.size());
             headers.clear();
    -        headers.add("Cookie", cookies.get(0));
    +        for (String cookie : loginResponse.getHeaders().get("Set-Cookie")) {
    +            headers.add("Cookie", cookie);
    +        }
             restOperations.exchange(baseUrl + "/profile",
                 HttpMethod.GET,
                 new HttpEntity<>(null, headers),Void.class);
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/LoginIT.java+54 4 modified
    @@ -14,6 +14,8 @@
     
     import com.dumbster.smtp.SimpleSmtpServer;
     import com.dumbster.smtp.SmtpMessage;
    +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.hamcrest.Matchers;
     import org.junit.After;
     import org.junit.Assert;
    @@ -26,15 +28,20 @@
     import org.springframework.beans.factory.annotation.Autowired;
     import org.springframework.beans.factory.annotation.Value;
     import org.springframework.http.HttpEntity;
    +import org.springframework.http.HttpHeaders;
     import org.springframework.http.HttpMethod;
     import org.springframework.http.HttpStatus;
    +import org.springframework.http.MediaType;
     import org.springframework.http.ResponseEntity;
    +import org.springframework.http.client.ClientHttpResponse;
     import org.springframework.security.oauth2.client.test.TestAccounts;
     import org.springframework.test.context.ContextConfiguration;
     import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
     import org.springframework.util.LinkedMultiValueMap;
    +import org.springframework.web.client.ResponseErrorHandler;
     import org.springframework.web.client.RestTemplate;
     
    +import java.io.IOException;
     import java.security.SecureRandom;
     import java.util.Iterator;
     
    @@ -43,6 +50,7 @@
     import static org.junit.Assert.assertEquals;
     import static org.junit.Assert.assertFalse;
     import static org.junit.Assert.assertThat;
    +import static org.junit.Assert.assertTrue;
     
     @RunWith(SpringJUnit4ClassRunner.class)
     @ContextConfiguration(classes = DefaultIntegrationTestConfig.class)
    @@ -100,16 +108,58 @@ public void testFailedLogin() throws Exception {
             assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString("Welcome!"));
         }
     
    +    @Test
    +    public void testAccessDeniedIfCsrfIsMissing() throws Exception {
    +        RestTemplate template = new RestTemplate();
    +        template.setErrorHandler(new ResponseErrorHandler() {
    +            @Override
    +            public boolean hasError(ClientHttpResponse response) throws IOException {
    +                return response.getRawStatusCode() >= 500;
    +            }
    +
    +            @Override
    +            public void handleError(ClientHttpResponse response) throws IOException {
    +            }
    +
    +        });
    +        LinkedMultiValueMap<String,String> body = new LinkedMultiValueMap<>();
    +        body.add("username", testAccounts.getUserName());
    +        body.add("password", testAccounts.getPassword());
    +        HttpHeaders headers = new HttpHeaders();
    +        headers.add(headers.ACCEPT, MediaType.TEXT_HTML_VALUE);
    +        ResponseEntity<String> loginResponse = template.exchange(baseUrl + "/login.do",
    +            HttpMethod.POST,
    +            new HttpEntity<>(body, headers),
    +            String.class);
    +        assertEquals(HttpStatus.FORBIDDEN, loginResponse.getStatusCode());
    +        assertTrue("CSRF message should be shown", loginResponse.getBody().contains("Invalid login attempt, request does not meet our security standards, please try again."));
    +    }
    +
         @Test
         public void testRedirectAfterFailedLogin() throws Exception {
             RestTemplate template = new RestTemplate();
    +
    +        HttpHeaders headers = new HttpHeaders();
    +        headers.set(headers.ACCEPT, MediaType.TEXT_HTML_VALUE);
    +        ResponseEntity<String> loginResponse = template.exchange(baseUrl + "/login",
    +            HttpMethod.GET,
    +            new HttpEntity<>(null, headers),
    +            String.class);
    +
    +        if (loginResponse.getHeaders().containsKey("Set-Cookie")) {
    +            for (String cookie : loginResponse.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", cookie);
    +            }
    +        }
    +        String csrf = IntegrationTestUtils.extractCookieCsrf(loginResponse.getBody());
             LinkedMultiValueMap<String,String> body = new LinkedMultiValueMap<>();
             body.add("username", testAccounts.getUserName());
             body.add("password", "invalidpassword");
    -        ResponseEntity<Void> loginResponse = template.exchange(baseUrl + "/login.do",
    +        body.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, csrf);
    +        loginResponse = template.exchange(baseUrl + "/login.do",
                 HttpMethod.POST,
    -            new HttpEntity<>(body, null),
    -            Void.class);
    +            new HttpEntity<>(body, headers),
    +            String.class);
             assertEquals(HttpStatus.FOUND, loginResponse.getStatusCode());
         }
     
    @@ -139,7 +189,7 @@ public void testBuildInfo() throws Exception {
             webDriver.get(baseUrl + "/login");
     
             String regex = "Version: \\S+, Commit: \\w{7}, Timestamp: .+, UAA: " + baseUrl;
    -        Assert.assertTrue(webDriver.findElement(By.cssSelector(".footer .copyright")).getAttribute("title").matches(regex));
    +        assertTrue(webDriver.findElement(By.cssSelector(".footer .copyright")).getAttribute("title").matches(regex));
         }
     
         private String createAnotherUser() {
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OpenIdTokenGrantsIT.java+18 4 modified
    @@ -17,6 +17,7 @@
     import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils;
     import org.cloudfoundry.identity.uaa.scim.ScimUser;
     import org.cloudfoundry.identity.uaa.util.JsonUtils;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.hamcrest.Matchers;
     import org.junit.Assert;
     import org.junit.Before;
    @@ -60,6 +61,7 @@
     import static org.hamcrest.Matchers.is;
     import static org.hamcrest.Matchers.not;
     import static org.junit.Assert.assertEquals;
    +import static org.junit.Assert.assertNotNull;
     import static org.junit.Assert.assertTrue;
     
     @RunWith(SpringJUnit4ClassRunner.class)
    @@ -258,8 +260,9 @@ private void doOpenIdHybridFlowIdTokenAndCode(Set<String> responseTypes, String
             String clientId = "app";
             String clientSecret = "appclientsecret";
             String redirectUri = "http://anywhere.com";
    -        String uri = loginUrl + "/oauth/authorize?response_type={response_type}&"+
    -            "state={state}&client_id={client_id}&redirect_uri={redirect_uri}";
    +        String uri = loginUrl +
    +                     "/oauth/authorize?response_type={response_type}&"+
    +                     "state={state}&client_id={client_id}&redirect_uri={redirect_uri}";
     
             ResponseEntity<Void> result = restOperations.exchange(
                 uri,
    @@ -288,20 +291,31 @@ private void doOpenIdHybridFlowIdTokenAndCode(Set<String> responseTypes, String
             assertTrue(response.getBody().contains("/login.do"));
             assertTrue(response.getBody().contains("username"));
             assertTrue(response.getBody().contains("password"));
    +        String csrf = IntegrationTestUtils.extractCookieCsrf(response.getBody());
    +
    +        if (response.getHeaders().containsKey("Set-Cookie")) {
    +            for (String cookie : response.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", cookie);
    +            }
    +        }
     
             MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
             formData.add("username", user.getUserName());
             formData.add("password", secret);
    +        formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, csrf);
     
             // Should be redirected to the original URL, but now authenticated
             result = restOperations.exchange(loginUrl + "/login.do", HttpMethod.POST, new HttpEntity<>(formData, headers), Void.class);
             assertEquals(HttpStatus.FOUND, result.getStatusCode());
     
    +        headers.remove("Cookie");
             if (result.getHeaders().containsKey("Set-Cookie")) {
    -            String cookie = result.getHeaders().getFirst("Set-Cookie");
    -            headers.set("Cookie", cookie);
    +            for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", cookie);
    +            }
             }
     
    +
             location = UriUtils.decode(result.getHeaders().getLocation().toString(), "UTF-8");
             response = restOperations.exchange(
                 location,
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/FormLoginIntegrationTests.java+92 55 modified
    @@ -12,26 +12,39 @@
      *******************************************************************************/
     package org.cloudfoundry.identity.uaa.integration;
     
    -import static org.junit.Assert.assertEquals;
    -import static org.junit.Assert.assertTrue;
    -
    -import java.util.Arrays;
    -
    +import org.apache.http.Header;
    +import org.apache.http.HttpHeaders;
    +import org.apache.http.client.config.RequestConfig;
    +import org.apache.http.client.methods.CloseableHttpResponse;
    +import org.apache.http.client.methods.HttpGet;
    +import org.apache.http.client.methods.HttpPost;
    +import org.apache.http.client.methods.HttpUriRequest;
    +import org.apache.http.client.methods.RequestBuilder;
    +import org.apache.http.impl.client.BasicCookieStore;
    +import org.apache.http.impl.client.CloseableHttpClient;
    +import org.apache.http.impl.client.HttpClients;
    +import org.apache.http.message.BasicHeader;
    +import org.apache.http.util.EntityUtils;
     import org.cloudfoundry.identity.uaa.ServerRunning;
    +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils;
     import org.cloudfoundry.identity.uaa.test.TestAccountSetup;
     import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
    +import org.junit.After;
    +import org.junit.Before;
     import org.junit.Rule;
     import org.junit.Test;
    -import org.springframework.http.HttpHeaders;
     import org.springframework.http.HttpStatus;
     import org.springframework.http.MediaType;
    -import org.springframework.http.ResponseEntity;
    -import org.springframework.util.LinkedMultiValueMap;
    -import org.springframework.util.MultiValueMap;
     
    -/**
    - * @author Dave Syer
    - */
    +import java.util.Arrays;
    +import java.util.List;
    +
    +import static org.junit.Assert.assertEquals;
    +import static org.junit.Assert.assertTrue;
    +import static org.springframework.http.HttpStatus.FOUND;
    +import static org.springframework.http.HttpStatus.OK;
    +
     public class FormLoginIntegrationTests {
     
         @Rule
    @@ -42,57 +55,81 @@ public class FormLoginIntegrationTests {
         @Rule
         public TestAccountSetup testAccountSetup = TestAccountSetup.standard(serverRunning, testAccounts);
     
    -    @Test
    -    public void testUnauthenticatedRedirect() throws Exception {
    +    Header header = new BasicHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE);
    +    List<Header> headers = Arrays.asList(header);
    +
    +    BasicCookieStore cookieStore = new BasicCookieStore();
    +    CloseableHttpClient httpclient;
     
    -        HttpHeaders headers = new HttpHeaders();
    -        headers.setAccept(Arrays.asList(MediaType.TEXT_HTML, MediaType.ALL));
    +    @Before
    +    public void createHttpClient() throws Exception {
    +        httpclient = HttpClients.custom()
    +            .setDefaultRequestConfig(RequestConfig.DEFAULT)
    +            .setDefaultHeaders(headers)
    +            .setDefaultCookieStore(cookieStore)
    +            .build();
    +    }
    +
    +    @After
    +    public void closeClient() throws Exception {
    +        httpclient.close();
    +    }
     
    -        String location = "/";
    -        ResponseEntity<Void> result = serverRunning.getForResponse(location, headers);
    -        // should be directed to the login screen...
    -        assertEquals(HttpStatus.FOUND, result.getStatusCode());
     
    -        location = result.getHeaders().getLocation().toString();
    +    @Test
    +    public void testUnauthenticatedRedirect() throws Exception {
    +        String location = serverRunning.getBaseUrl() + "/";
    +        HttpGet httpget = new HttpGet(location);
    +        httpget.setConfig(
    +            RequestConfig.custom().setRedirectsEnabled(false).build()
    +        );
    +        CloseableHttpResponse response = httpclient.execute(httpget);
    +        assertEquals(FOUND.value(), response.getStatusLine().getStatusCode());
    +        location = response.getFirstHeader("Location").getValue();
    +        response.close();
    +        httpget.completed();
             assertTrue(location.contains("/login"));
         }
     
         @Test
         public void testSuccessfulAuthenticationFlow() throws Exception {
    -
    -        HttpHeaders headers = new HttpHeaders();
    -        headers.setAccept(Arrays.asList(MediaType.TEXT_HTML, MediaType.ALL));
    -
    -        String location = "/";
    -        ResponseEntity<Void> result = serverRunning.getForResponse(location, headers);
    -        // should be directed to the login screen...
    -        assertEquals(HttpStatus.FOUND, result.getStatusCode());
    -
    -        location = result.getHeaders().getLocation().toString();
    -        ResponseEntity<String> response = serverRunning.getForString(location, headers);
    -        assertTrue(response.getBody().contains("/login.do"));
    -        assertTrue(response.getBody().contains("username"));
    -        assertTrue(response.getBody().contains("password"));
    -        
    -
    -        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
    -        formData.add("username", testAccounts.getUserName());
    -        formData.add("password", testAccounts.getPassword());
    -
    -        // Should be redirected to the original URL, but now authenticated
    -        result = serverRunning.postForResponse("/login.do", headers, formData);
    -        assertEquals(HttpStatus.FOUND, result.getStatusCode());
    -
    -        if (result.getHeaders().containsKey("Set-Cookie")) {
    -            String cookie = result.getHeaders().getFirst("Set-Cookie");
    -            headers.set("Cookie", cookie);
    -        }
    -
    -        response = serverRunning.getForString(result.getHeaders().getLocation().toString(), headers);
    -        assertEquals(HttpStatus.OK, response.getStatusCode());
    -        // The home page should be returned
    -        assertTrue(response.getBody().contains("Sign Out"));
    -
    +        //request home page /
    +        String location = serverRunning.getBaseUrl() + "/";
    +        HttpGet httpget = new HttpGet(location);
    +        CloseableHttpResponse response = httpclient.execute(httpget);
    +
    +        assertEquals(OK.value(), response.getStatusLine().getStatusCode());
    +
    +        String body = EntityUtils.toString(response.getEntity());
    +        EntityUtils.consume(response.getEntity());
    +        response.close();
    +        httpget.completed();
    +
    +        assertTrue(body.contains("/login.do"));
    +        assertTrue(body.contains("username"));
    +        assertTrue(body.contains("password"));
    +
    +        String csrf = IntegrationTestUtils.extractCookieCsrf(body);
    +
    +        HttpUriRequest loginPost = RequestBuilder.post()
    +            .setUri(serverRunning.getBaseUrl() + "/login.do")
    +            .addParameter("username",testAccounts.getUserName())
    +            .addParameter("password",testAccounts.getPassword())
    +            .addParameter(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, csrf)
    +            .build();
    +
    +        response = httpclient.execute(loginPost);
    +        assertEquals(FOUND.value(), response.getStatusLine().getStatusCode());
    +        location = response.getFirstHeader("Location").getValue();
    +        response.close();
    +
    +        httpget = new HttpGet(location);
    +        response = httpclient.execute(httpget);
    +        assertEquals(OK.value(), response.getStatusLine().getStatusCode());
    +
    +        body = EntityUtils.toString(response.getEntity());
    +        response.close();
    +        assertTrue(body.contains("Sign Out"));
         }
     
     }
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ImplicitTokenGrantIntegrationTests.java+10 1 modified
    @@ -20,8 +20,10 @@
     import java.util.Arrays;
     
     import org.cloudfoundry.identity.uaa.ServerRunning;
    +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils;
     import org.cloudfoundry.identity.uaa.test.TestAccountSetup;
     import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.junit.Rule;
     import org.junit.Test;
     import org.springframework.http.HttpHeaders;
    @@ -126,16 +128,23 @@ public void authzWithIntermediateFormLoginSucceeds() throws Exception {
             headers.set("Cookie", cookie);
     
             ResponseEntity<String> response = serverRunning.getForString(location, headers);
    +        if (response.getHeaders().containsKey("Set-Cookie")) {
    +            for (String c : response.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", c);
    +            }
    +        }
             // should be directed to the login screen...
             assertTrue(response.getBody().contains("/login.do"));
             assertTrue(response.getBody().contains("username"));
             assertTrue(response.getBody().contains("password"));
     
    +
             location = "/login.do";
     
    -        MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
    +        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
             formData.add("username", testAccounts.getUserName());
             formData.add("password", testAccounts.getPassword());
    +        formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody()));
     
             result = serverRunning.postForRedirect(location, headers, formData);
     
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/OpenIdTokenAuthorizationWithApprovalIntegrationTests.java+12 2 modified
    @@ -26,9 +26,11 @@
     import org.apache.http.client.HttpClient;
     import org.apache.http.impl.client.HttpClientBuilder;
     import org.cloudfoundry.identity.uaa.ServerRunning;
    +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils;
     import org.cloudfoundry.identity.uaa.scim.ScimUser;
     import org.cloudfoundry.identity.uaa.test.TestAccountSetup;
     import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.hamcrest.Matchers;
     import org.junit.Assert;
     import org.junit.Assume;
    @@ -231,6 +233,11 @@ private String doOpenIdHybridFlowIdTokenAndReturnCode(Set<String> responseTypes,
             }
     
             ResponseEntity<String> response = serverRunning.getForString(location, headers);
    +        if (response.getHeaders().containsKey("Set-Cookie")) {
    +            for (String cookie : response.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", cookie);
    +            }
    +        }
             // should be directed to the login screen...
             assertTrue(response.getBody().contains("/login.do"));
             assertTrue(response.getBody().contains("username"));
    @@ -239,14 +246,17 @@ private String doOpenIdHybridFlowIdTokenAndReturnCode(Set<String> responseTypes,
             MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
             formData.add("username", user.getUserName());
             formData.add("password", "s3Cret");
    +        formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody()));
     
             // Should be redirected to the original URL, but now authenticated
             result = serverRunning.postForResponse("/login.do", headers, formData);
             assertEquals(HttpStatus.FOUND, result.getStatusCode());
     
    +        headers.remove("Cookie");
             if (result.getHeaders().containsKey("Set-Cookie")) {
    -            String cookie = result.getHeaders().getFirst("Set-Cookie");
    -            headers.set("Cookie", cookie);
    +            for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", cookie);
    +            }
             }
     
             location = UriUtils.decode(result.getHeaders().getLocation().toString(), "UTF-8");
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/RefreshTokenSupportIntegrationTests.java+14 4 modified
    @@ -22,8 +22,10 @@
     import java.util.Map;
     
     import org.cloudfoundry.identity.uaa.ServerRunning;
    +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils;
     import org.cloudfoundry.identity.uaa.test.TestAccountSetup;
     import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.junit.Before;
     import org.junit.Rule;
     import org.junit.Test;
    @@ -81,6 +83,11 @@ public void testTokenRefreshedCorrectFlow() throws Exception {
             }
     
             ResponseEntity<String> response = serverRunning.getForString(location, headers);
    +        if (response.getHeaders().containsKey("Set-Cookie")) {
    +            for (String c : response.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", c);
    +            }
    +        }
             // should be directed to the login screen...
             assertTrue(response.getBody().contains("/login.do"));
             assertTrue(response.getBody().contains("username"));
    @@ -89,15 +96,18 @@ public void testTokenRefreshedCorrectFlow() throws Exception {
             MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
             formData.add("username", testAccounts.getUserName());
             formData.add("password", testAccounts.getPassword());
    +        formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody()));
     
             // Should be redirected to the original URL, but now authenticated
             result = serverRunning.postForResponse("/login.do", headers, formData);
    -        assertEquals(HttpStatus.FOUND, result.getStatusCode());
    -
    +        headers.remove("Cookie");
             if (result.getHeaders().containsKey("Set-Cookie")) {
    -            String cookie = result.getHeaders().getFirst("Set-Cookie");
    -            headers.set("Cookie", cookie);
    +            for (String c : result.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", c);
    +            }
             }
    +        assertEquals(HttpStatus.FOUND, result.getStatusCode());
    +
     
             response = serverRunning.getForString(result.getHeaders().getLocation().toString(), headers);
             if (response.getStatusCode() == HttpStatus.OK) {
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimGroupEndpointsIntegrationTests.java+13 3 modified
    @@ -15,11 +15,13 @@
     import org.apache.commons.logging.Log;
     import org.apache.commons.logging.LogFactory;
     import org.cloudfoundry.identity.uaa.ServerRunning;
    +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils;
     import org.cloudfoundry.identity.uaa.scim.ScimGroup;
     import org.cloudfoundry.identity.uaa.scim.ScimGroupMember;
     import org.cloudfoundry.identity.uaa.scim.ScimUser;
     import org.cloudfoundry.identity.uaa.test.TestAccountSetup;
     import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.cloudfoundry.identity.uaa.zone.IdentityZone;
     import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
     import org.junit.After;
    @@ -488,17 +490,25 @@ private OAuth2AccessToken getAccessToken(String clientId, String clientSecret, S
             assertTrue(response.getBody().contains("username"));
             assertTrue(response.getBody().contains("password"));
     
    -        MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
    +        if (response.getHeaders().containsKey("Set-Cookie")) {
    +            String cookie = response.getHeaders().getFirst("Set-Cookie");
    +            headers.add("Cookie", cookie);
    +        }
    +
    +        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
             formData.add("username", username);
             formData.add("password", password);
    +        formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody()));
     
             // Should be redirected to the original URL, but now authenticated
             result = serverRunning.postForResponse("/login.do", headers, formData);
             assertEquals(HttpStatus.FOUND, result.getStatusCode());
     
    +        headers.remove("Cookie");
             if (result.getHeaders().containsKey("Set-Cookie")) {
    -            String cookie = result.getHeaders().getFirst("Set-Cookie");
    -            headers.set("Cookie", cookie);
    +            for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", cookie);
    +            }
             }
     
             response = serverRunning.getForString(result.getHeaders().getLocation().toString(), headers);
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java+80 12 modified
    @@ -23,6 +23,7 @@
     import org.cloudfoundry.identity.uaa.scim.ScimUser;
     import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
     import org.cloudfoundry.identity.uaa.util.JsonUtils;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.cloudfoundry.identity.uaa.zone.IdentityProvider;
     import org.cloudfoundry.identity.uaa.zone.IdentityZone;
     import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter;
    @@ -57,8 +58,11 @@
     import java.util.List;
     import java.util.Map;
     import java.util.HashMap;
    +import java.util.regex.Matcher;
    +import java.util.regex.Pattern;
     
     import static org.junit.Assert.assertEquals;
    +import static org.junit.Assert.assertNotNull;
     import static org.junit.Assert.assertTrue;
     
     public class IntegrationTestUtils {
    @@ -75,7 +79,7 @@ public static ClientCredentialsResourceDetails getClientCredentialsResource(Stri
                 resource.setScope(Arrays.asList(scope));
             }
             resource.setClientAuthenticationScheme(AuthenticationScheme.header);
    -        resource.setAccessTokenUri(url+"/oauth/token");
    +        resource.setAccessTokenUri(url + "/oauth/token");
             return resource;
         }
     
    @@ -119,7 +123,7 @@ public static String getUserId(String zoneAdminToken, String url, String origin,
             RestTemplate template = new RestTemplate();
             MultiValueMap<String,String> headers = new LinkedMultiValueMap<>();
             headers.add("Accept", MediaType.APPLICATION_JSON_VALUE);
    -        headers.add("Authorization", "bearer "+zoneAdminToken);
    +        headers.add("Authorization", "bearer " + zoneAdminToken);
             headers.add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
             HttpEntity getHeaders = new HttpEntity(headers);
             ResponseEntity<String> userInfoGet = template.exchange(
    @@ -148,7 +152,7 @@ public static void deleteUser(String zoneAdminToken, String url, String userId)
             RestTemplate template = new RestTemplate();
             MultiValueMap<String,String> headers = new LinkedMultiValueMap<>();
             headers.add("Accept", MediaType.APPLICATION_JSON_VALUE);
    -        headers.add("Authorization", "bearer "+zoneAdminToken);
    +        headers.add("Authorization", "bearer " + zoneAdminToken);
             headers.add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
             HttpEntity deleteHeaders = new HttpEntity(headers);
             ResponseEntity<String> userDelete = template.exchange(
    @@ -194,7 +198,7 @@ public static ScimGroup getGroup(RestTemplate client,
                                          String groupName) {
             String id = findGroupId(client, url, groupName);
             if (id!=null) {
    -            ResponseEntity<ScimGroup> group = client.getForEntity(url+"/Groups/{id}", ScimGroup.class, id);
    +            ResponseEntity<ScimGroup> group = client.getForEntity(url + "/Groups/{id}", ScimGroup.class, id);
                 return group.getBody();
             }
             return null;
    @@ -375,8 +379,8 @@ public static IdentityZone fixtureIdentityZone(String id, String subdomain) {
             IdentityZone identityZone = new IdentityZone();
             identityZone.setId(id);
             identityZone.setSubdomain(subdomain);
    -        identityZone.setName("The Twiglet Zone["+id+"]");
    -        identityZone.setDescription("Like the Twilight Zone but tastier["+id+"].");
    +        identityZone.setName("The Twiglet Zone[" + id + "]");
    +        identityZone.setDescription("Like the Twilight Zone but tastier[" + id + "].");
             return identityZone;
         }
     
    @@ -406,6 +410,18 @@ public static String getAuthorizationCodeToken(ServerRunning serverRunning,
                                                        String clientSecret,
                                                        String username,
                                                        String password) throws Exception {
    +
    +        return getAuthorizationCodeTokenMap(serverRunning, testAccounts, clientId, clientSecret, username, password)
    +            .get("access_token");
    +    }
    +
    +    public static Map<String,String> getAuthorizationCodeTokenMap(ServerRunning serverRunning,
    +                                                                  UaaTestAccounts testAccounts,
    +                                                                  String clientId,
    +                                                                  String clientSecret,
    +                                                                  String username,
    +                                                                  String password) throws Exception {
    +        // TODO Fix to use json API rather than HTML
             HttpHeaders headers = new HttpHeaders();
             // TODO: should be able to handle just TEXT_HTML
             headers.setAccept(Arrays.asList(MediaType.TEXT_HTML, MediaType.ALL));
    @@ -422,27 +438,39 @@ public static String getAuthorizationCodeToken(ServerRunning serverRunning,
             String location = result.getHeaders().getLocation().toString();
     
             if (result.getHeaders().containsKey("Set-Cookie")) {
    -            String cookie = result.getHeaders().getFirst("Set-Cookie");
    -            headers.set("Cookie", cookie);
    +            for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +                assertNotNull("Expected cookie in " + result.getHeaders(), cookie);
    +                headers.add("Cookie", cookie);
    +            }
             }
     
             ResponseEntity<String> response = serverRunning.getForString(location, headers);
    +
    +        if (response.getHeaders().containsKey("Set-Cookie")) {
    +            for (String cookie : response.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", cookie);
    +            }
    +        }
             // should be directed to the login screen...
             assertTrue(response.getBody().contains("/login.do"));
             assertTrue(response.getBody().contains("username"));
             assertTrue(response.getBody().contains("password"));
    +        String csrf = IntegrationTestUtils.extractCookieCsrf(response.getBody());
     
    -        MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
    +        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
             formData.add("username", username);
             formData.add("password", password);
    +        formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, csrf);
     
             // Should be redirected to the original URL, but now authenticated
             result = serverRunning.postForResponse("/login.do", headers, formData);
             assertEquals(HttpStatus.FOUND, result.getStatusCode());
     
    +        headers.remove("Cookie");
             if (result.getHeaders().containsKey("Set-Cookie")) {
    -            String cookie = result.getHeaders().getFirst("Set-Cookie");
    -            headers.set("Cookie", cookie);
    +            for (String cookie : result.getHeaders().get("Set-Cookie")) {
    +                headers.add("Cookie", cookie);
    +            }
             }
     
             response = serverRunning.getForString(result.getHeaders().getLocation().toString(), headers);
    @@ -475,9 +503,21 @@ public static String getAuthorizationCodeToken(ServerRunning serverRunning,
             @SuppressWarnings("rawtypes")
             ResponseEntity<Map> tokenResponse = serverRunning.postForMap("/oauth/token", formData, tokenHeaders);
             assertEquals(HttpStatus.OK, tokenResponse.getStatusCode());
    +
             @SuppressWarnings("unchecked")
    +        OAuth2AccessToken accessToken = DefaultOAuth2AccessToken.valueOf(tokenResponse.getBody());
             Map<String, String> body = tokenResponse.getBody();
    -        return body.get("access_token");
    +
    +        formData = new LinkedMultiValueMap<>();
    +        headers.set("Authorization",
    +            testAccounts.getAuthorizationHeader(resource.getClientId(), resource.getClientSecret()));
    +        formData.add("token", accessToken.getValue());
    +
    +        tokenResponse = serverRunning.postForMap("/check_token", formData, headers);
    +        assertEquals(HttpStatus.OK, tokenResponse.getStatusCode());
    +        //System.err.println(tokenResponse.getBody());
    +        assertNotNull(tokenResponse.getBody().get("iss"));
    +        return body;
         }
     
         public static boolean hasAuthority(String authority, Collection<GrantedAuthority> authorities) {
    @@ -489,6 +529,34 @@ public static boolean hasAuthority(String authority, Collection<GrantedAuthority
             return false;
         }
     
    +    public static String extractCookieCsrf(String body) {
    +        String pattern = "\\<input type=\\\"hidden\\\" name=\\\"X-Uaa-Csrf\\\" value=\\\"(.*?)\\\"";
    +
    +        Pattern linkPattern = Pattern.compile(pattern);
    +        Matcher matcher = linkPattern.matcher(body);
    +        if (matcher.find()) {
    +            return matcher.group(1);
    +        }
    +        return null;
    +    }
    +
    +    public static void clearAllButJsessionID(HttpHeaders headers) {
    +        String jsessionid = null;
    +        List<String> cookies = headers.get("Cookie");
    +        if (cookies!=null) {
    +            for (String cookie : cookies) {
    +                if (cookie.contains("JSESSIONID")) {
    +                    jsessionid = cookie;
    +                }
    +            }
    +        }
    +        if (jsessionid!=null) {
    +            headers.set("Cookie", jsessionid);
    +        } else {
    +            headers.remove("Cookie");
    +        }
    +    }
    +
         public static class StatelessRequestFactory extends HttpComponentsClientHttpRequestFactory {
             @Override
             public HttpClient getHttpClient() {
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java+16 12 modified
    @@ -73,6 +73,7 @@
     import java.util.Map;
     import java.util.Properties;
     
    +import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf;
     import static org.hamcrest.Matchers.containsString;
     import static org.hamcrest.Matchers.hasEntry;
     import static org.hamcrest.Matchers.hasKey;
    @@ -281,7 +282,7 @@ public void testLogOutNullWhitelistedRedirectParameter() throws Exception {
         public void testLogOutEmptyWhitelistedRedirectParameter() throws Exception {
             WhitelistLogoutHandler logoutSuccessHandler = getWebApplicationContext().getBean(WhitelistLogoutHandler.class);
             logoutSuccessHandler.setAlwaysUseDefaultTargetUrl(false);
    -        logoutSuccessHandler.setWhitelist(Collections.<String>emptyList());
    +        logoutSuccessHandler.setWhitelist(Collections.EMPTY_LIST);
             try {
                 getMockMvc().perform(get("/logout.do").param("redirect", "https://www.google.com"))
                     .andExpect(status().isFound())
    @@ -1097,9 +1098,10 @@ public void login_LockoutPolicySucceeds_ForDefaultZone() throws Exception {
             ScimUser userToLockout = createUser("", adminToken);
             attemptFailedLogin(5, userToLockout.getUserName(), "");
             getMockMvc().perform(post("/login.do")
    -                .param("username", userToLockout.getUserName())
    -                .param("password", userToLockout.getPassword()))
    -                .andExpect(redirectedUrl("/login?error=account_locked"));
    +            .with(cookieCsrf())
    +            .param("username", userToLockout.getUserName())
    +            .param("password", userToLockout.getPassword()))
    +            .andExpect(redirectedUrl("/login?error=account_locked"));
         }
     
         @Test
    @@ -1117,10 +1119,11 @@ public void login_LockoutPolicySucceeds_WhenPolicyIsUpdatedByApi() throws Except
             attemptFailedLogin(2, userToLockout.getUserName(), subdomain);
     
             getMockMvc().perform(post("/login.do")
    -                .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))
    -                .param("username", userToLockout.getUserName())
    -                .param("password", userToLockout.getPassword()))
    -                .andExpect(redirectedUrl("/login?error=account_locked"));
    +            .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost"))
    +            .with(cookieCsrf())
    +            .param("username", userToLockout.getUserName())
    +            .param("password", userToLockout.getPassword()))
    +            .andExpect(redirectedUrl("/login?error=account_locked"));
         }
     
         private void changeLockoutPolicyForIdpInZone(IdentityZone zone) throws Exception {
    @@ -1150,12 +1153,13 @@ private void changeLockoutPolicyForIdpInZone(IdentityZone zone) throws Exception
         private void attemptFailedLogin(int numberOfAttempts, String username, String subdomain) throws Exception {
             String requestDomain = subdomain.equals("") ? "localhost" : subdomain + ".localhost";
             MockHttpServletRequestBuilder post = post("/login.do")
    -                .with(new SetServerNameRequestPostProcessor(requestDomain))
    -                .param("username", username)
    -                .param("password", "wrong_password");
    +            .with(new SetServerNameRequestPostProcessor(requestDomain))
    +            .with(cookieCsrf())
    +            .param("username", username)
    +            .param("password", "wrong_password");
             for (int i = 0; i < numberOfAttempts ; i++) {
                 getMockMvc().perform(post)
    -                    .andExpect(redirectedUrl("/login?error=login_failure"));
    +                .andExpect(redirectedUrl("/login?error=login_failure"));
             }
         }
     }
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java+7 0 modified
    @@ -54,6 +54,7 @@
     import org.junit.Before;
     import org.junit.Test;
     import org.mockito.ArgumentCaptor;
    +import org.mockito.Mock;
     import org.springframework.context.ApplicationEvent;
     import org.springframework.context.ApplicationListener;
     import org.springframework.http.MediaType;
    @@ -70,6 +71,7 @@
     import java.util.List;
     import java.util.Map;
     
    +import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf;
     import static org.hamcrest.Matchers.containsString;
     import static org.junit.Assert.assertEquals;
     import static org.junit.Assert.assertTrue;
    @@ -145,6 +147,7 @@ public void resetLoginClient() {
         @Test
         public void userLoginTest() throws Exception {
             MockHttpServletRequestBuilder loginPost = post("/login.do")
    +            .with(cookieCsrf())
                 .accept(MediaType.TEXT_HTML_VALUE)
                 .param("username", testUser.getUserName())
                 .param("password", testPassword);
    @@ -182,6 +185,7 @@ public void userLoginAuthenticateEndpointTest() throws Exception {
         @Test
         public void invalidPasswordLoginFailedTest() throws Exception {
             MockHttpServletRequestBuilder loginPost = post("/login.do")
    +            .with(cookieCsrf())
                 .accept(MediaType.TEXT_HTML_VALUE)
                 .param("username", testUser.getUserName())
                 .param("password", "");
    @@ -306,6 +310,7 @@ public void userNotFoundLoginFailedTest() throws Exception {
             String username = "test1234";
     
             MockHttpServletRequestBuilder loginPost = post("/login.do")
    +            .with(cookieCsrf())
                 .accept(MediaType.TEXT_HTML_VALUE)
                 .param("username", username)
                 .param("password", testPassword);
    @@ -325,6 +330,7 @@ public void userNotFoundLoginFailedTest() throws Exception {
         @Test
         public void userChangePasswordTest() throws Exception {
             MockHttpServletRequestBuilder loginPost = post("/login.do")
    +            .with(cookieCsrf())
                 .accept(MediaType.APPLICATION_JSON_VALUE)
                 .param("username", testUser.getUserName())
                 .param("password", testPassword);
    @@ -365,6 +371,7 @@ public void userChangePasswordTest() throws Exception {
         @Test
         public void userChangeInvalidPasswordTest() throws Exception {
             MockHttpServletRequestBuilder loginPost = post("/login.do")
    +            .with(cookieCsrf())
                 .accept(MediaType.APPLICATION_JSON_VALUE)
                 .param("username", testUser.getUserName())
                 .param("password", testPassword);
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java+16 9 modified
    @@ -75,6 +75,7 @@
     import java.util.List;
     import java.util.Set;
     
    +import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf;
     import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
     import static org.hamcrest.Matchers.containsString;
     import static org.junit.Assert.assertEquals;
    @@ -465,6 +466,7 @@ public void testLoginInNonDefaultZone() throws Exception {
     
             //IDP not yet created
             mockMvc.perform(post("/login.do").accept(TEXT_HTML_VALUE)
    +            .with(cookieCsrf())
                 .with(new SetServerNameRequestPostProcessor(zone.getSubdomain()+".localhost"))
                 .param("username", "marissa2")
                 .param("password", "ldap"))
    @@ -498,6 +500,7 @@ public void testLoginInNonDefaultZone() throws Exception {
             provider = MockMvcUtils.utils().createIdpUsingWebRequest(mockMvc, zone.getId(), zoneAdminToken, provider, status().isCreated());
     
             mockMvc.perform(post("/login.do").accept(TEXT_HTML_VALUE)
    +            .with(cookieCsrf())
                 .with(new SetServerNameRequestPostProcessor(zone.getSubdomain()+".localhost"))
                 .param("username", "marissa2")
                 .param("password", "ldap"))
    @@ -514,6 +517,7 @@ public void testLoginInNonDefaultZone() throws Exception {
             provider.setActive(false);
             MockMvcUtils.utils().createIdpUsingWebRequest(mockMvc, zone.getId(), zoneAdminToken, provider, status().isOk(), true);
             mockMvc.perform(post("/login.do").accept(TEXT_HTML_VALUE)
    +            .with(cookieCsrf())
                 .with(new SetServerNameRequestPostProcessor(zone.getSubdomain()+".localhost"))
                 .param("username", "marissa2")
                 .param("password", "ldap"))
    @@ -542,7 +546,8 @@ public void testLoginInNonDefaultZone() throws Exception {
             MockMvcUtils.utils().createIdpUsingWebRequest(mockMvc, zone.getId(), zoneAdminToken, provider, status().isOk(), true);
     
             mockMvc.perform(post("/login.do").accept(TEXT_HTML_VALUE)
    -            .with(new SetServerNameRequestPostProcessor(zone.getSubdomain()+".localhost"))
    +            .with(cookieCsrf())
    +            .with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost"))
                 .param("username", "marissa2")
                 .param("password", "ldap"))
                 .andExpect(status().isFound())
    @@ -610,16 +615,18 @@ public void testLogin() throws Exception {
                     .andExpect(model().attributeDoesNotExist("saml"));
     
             mockMvc.perform(post("/login.do").accept(TEXT_HTML_VALUE)
    -                        .param("username", "marissa")
    -                        .param("password", "koaladsada"))
    -                .andExpect(status().isFound())
    -                .andExpect(redirectedUrl("/login?error=login_failure"));
    +            .with(cookieCsrf())
    +            .param("username", "marissa")
    +            .param("password", "koaladsada"))
    +            .andExpect(status().isFound())
    +            .andExpect(redirectedUrl("/login?error=login_failure"));
     
             mockMvc.perform(post("/login.do").accept(TEXT_HTML_VALUE)
    -                        .param("username", "marissa2")
    -                        .param("password", "ldap"))
    -                .andExpect(status().isFound())
    -                .andExpect(redirectedUrl("/"));
    +            .with(cookieCsrf())
    +            .param("username", "marissa2")
    +            .param("password", "ldap"))
    +            .andExpect(status().isFound())
    +            .andExpect(redirectedUrl("/"));
         }
     
         public void testAuthenticate() throws Exception {
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/password/PasswordChangeEndpointMockMvcTests.java+5 0 modified
    @@ -14,6 +14,7 @@
     
     import org.cloudfoundry.identity.uaa.message.PasswordChangeRequest;
     import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest;
    +import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils;
     import org.cloudfoundry.identity.uaa.scim.ScimUser;
     import org.cloudfoundry.identity.uaa.test.TestClient;
     import org.cloudfoundry.identity.uaa.util.JsonUtils;
    @@ -27,6 +28,7 @@
     
     import javax.servlet.http.HttpSession;
     
    +import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf;
     import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.utils;
     import static org.junit.Assert.assertNotNull;
     import static org.junit.Assert.assertNotSame;
    @@ -116,6 +118,7 @@ public void changePassword_Resets_Session() throws Exception {
     
             MockHttpSession session = new MockHttpSession();
             MockHttpSession afterLoginSession = (MockHttpSession) getMockMvc().perform(post("/login.do")
    +            .with(cookieCsrf())
                 .session(session)
                 .accept(TEXT_HTML_VALUE)
                 .param("username", user.getUserName())
    @@ -152,6 +155,7 @@ public void changePassword_Resets_All_Sessions() throws Exception {
     
             MockHttpSession session = new MockHttpSession();
             MockHttpSession afterLoginSessionA = (MockHttpSession) getMockMvc().perform(post("/login.do")
    +            .with(cookieCsrf())
                 .session(session)
                 .accept(TEXT_HTML_VALUE)
                 .param("username", user.getUserName())
    @@ -162,6 +166,7 @@ public void changePassword_Resets_All_Sessions() throws Exception {
     
             session = new MockHttpSession();
             MockHttpSession afterLoginSessionB = (MockHttpSession) getMockMvc().perform(post("/login.do")
    +            .with(cookieCsrf())
                 .session(session)
                 .accept(TEXT_HTML_VALUE)
                 .param("username", user.getUserName())
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenKeyEndpointMockMvcTests.java+0 3 modified
    @@ -30,7 +30,6 @@
     import static org.junit.Assert.assertNull;
     import static org.junit.Assert.assertTrue;
     import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
    -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
     import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
     
     public class TokenKeyEndpointMockMvcTests extends InjectedMockContextTest {
    @@ -112,7 +111,6 @@ public void checkTokenKeyValues() throws Exception {
                 get("/token_key")
                     .accept(MediaType.APPLICATION_JSON)
                     .header("Authorization", basicDigestHeaderValue))
    -            .andDo(print())
                 .andExpect(status().isOk())
                 .andReturn();
     
    @@ -126,7 +124,6 @@ public void checkTokenKeyValuesAnonymous() throws Exception {
             MvcResult result = getMockMvc().perform(
                 get("/token_key")
                     .accept(MediaType.APPLICATION_JSON))
    -            .andDo(print())
                 .andExpect(status().isOk())
                 .andReturn();
     
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java+43 0 modified
    @@ -32,6 +32,7 @@
     import org.cloudfoundry.identity.uaa.user.UaaUserDatabase;
     import org.cloudfoundry.identity.uaa.util.JsonUtils;
     import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor;
    +import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository;
     import org.cloudfoundry.identity.uaa.zone.IdentityProvider;
     import org.cloudfoundry.identity.uaa.zone.IdentityZone;
     import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
    @@ -44,6 +45,8 @@
     import org.springframework.context.ConfigurableApplicationContext;
     import org.springframework.context.event.ApplicationEventMulticaster;
     import org.springframework.http.MediaType;
    +import org.springframework.mock.web.MockHttpServletRequest;
    +import org.springframework.mock.web.MockHttpServletResponse;
     import org.springframework.mock.web.MockHttpSession;
     import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
     import org.springframework.security.core.Authentication;
    @@ -54,14 +57,20 @@
     import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
     import org.springframework.security.oauth2.provider.ClientDetails;
     import org.springframework.security.oauth2.provider.client.BaseClientDetails;
    +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;
    +import org.springframework.security.test.web.support.WebTestUtils;
     import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
    +import org.springframework.security.web.csrf.CsrfToken;
    +import org.springframework.security.web.csrf.CsrfTokenRepository;
     import org.springframework.test.web.servlet.MockMvc;
     import org.springframework.test.web.servlet.MvcResult;
     import org.springframework.test.web.servlet.ResultMatcher;
     import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
    +import org.springframework.test.web.servlet.request.RequestPostProcessor;
     import org.springframework.util.StringUtils;
     import org.springframework.web.util.UriComponentsBuilder;
     
    +import javax.servlet.http.Cookie;
     import java.util.Arrays;
     import java.util.Collections;
     import java.util.LinkedList;
    @@ -508,4 +517,38 @@ public void setAuthentication(Authentication authentication) {
             }
         }
     
    +    public static class CookieCsrfPostProcessor implements RequestPostProcessor {
    +
    +        private boolean useInvalidToken = false;
    +        public CookieCsrfPostProcessor useInvalidToken() {
    +            useInvalidToken = true;
    +            return this;
    +        }
    +
    +        public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
    +
    +            CsrfTokenRepository repository = new CookieBasedCsrfTokenRepository();
    +            CsrfToken token = repository.generateToken(request);
    +            repository.saveToken(token, request, new MockHttpServletResponse());
    +            String tokenValue = useInvalidToken ? "invalid" + token.getToken() : token.getToken();
    +            Cookie cookie = new Cookie(token.getParameterName(), tokenValue);
    +            cookie.setHttpOnly(true);
    +            Cookie[] cookies = request.getCookies();
    +            if (cookies==null) {
    +                request.setCookies(cookie);
    +            } else {
    +                Cookie[] newcookies = new Cookie[cookies.length+1];
    +                System.arraycopy(cookies, 0, newcookies, 0, cookies.length);
    +                newcookies[cookies.length] = cookie;
    +                request.setCookies(newcookies);
    +            }
    +            request.setParameter(token.getParameterName(), tokenValue);
    +            return request;
    +        }
    +
    +        public static CookieCsrfPostProcessor cookieCsrf() {
    +            return new CookieCsrfPostProcessor();
    +        }
    +    }
    +
     }
    
  • uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java+0 3 modified
    @@ -22,7 +22,6 @@
     import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor;
     import org.cloudfoundry.identity.uaa.zone.IdentityZone;
     import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter;
    -import org.junit.Assert;
     import org.junit.Before;
     import org.junit.Test;
     import org.springframework.http.HttpStatus;
    @@ -33,7 +32,6 @@
     
     import static org.springframework.http.MediaType.APPLICATION_JSON;
     import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
    -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
     import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
     import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
     import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
    @@ -80,7 +78,6 @@ private ScimUser createUser(ScimUser user, String token, String subdomain, Strin
             if (switchZone!=null) post.header(IdentityZoneSwitchingFilter.HEADER, switchZone);
     
             MvcResult result = getMockMvc().perform(post)
    -            .andDo(print())
                 .andExpect(status().isCreated())
                 .andExpect(header().string("ETag", "\"0\""))
                 .andExpect(jsonPath("$.userName").value(user.getUserName()))
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.