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.
| Package | Affected versions | Patched versions |
|---|---|---|
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven | < 2.5.2 | 2.5.2 |
Affected products
3- cpe:2.3:a:pivotal_software:cloud_foundry_elastic_runtime:*:*:*:*:*:*:*:*Range: <1.7.0
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:*:*:*:*:*:*:*:*Range: <2.5.2
Patches
3a54f3fb8225eMerge branch 'feature/stateless_csrf_upon_login' into develop
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()))
bdb1a39a1e72Make the unauthorized endpoints stateless. Only save the request if it is /oauth/authorize
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">
41dba9d81dbdImplement cookie based CSRF for login form
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- www.securityfocus.com/bid/101579nvdThird Party AdvisoryVDB EntryWEB
- github.com/advisories/GHSA-mpv3-g527-fqrjghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2015-5170ghsaADVISORY
- pivotal.io/security/cve-2015-5170-5173nvdVendor AdvisoryWEB
- github.com/cloudfoundry/uaa/commit/41dba9d81dbdf24ede4fb9719de28b1b88b3e1b4ghsaWEB
- github.com/cloudfoundry/uaa/commit/a54f3fb8225ef7d5021ca7d4fb52bef1e884568eghsaWEB
- github.com/cloudfoundry/uaa/commit/bdb1a39a1e72f615f2e7a429a896a11e7ee5ec17ghsaWEB
News mentions
0No linked articles in our index yet.