CVE-2016-6637
Description
Multiple cross-site request forgery (CSRF) vulnerabilities in Pivotal Cloud Foundry (PCF) before 242; UAA 2.x before 2.7.4.7, 3.x before 3.3.0.5, and 3.4.x before 3.4.4; UAA BOSH before 11.5 and 12.x before 12.5; Elastic Runtime before 1.6.40, 1.7.x before 1.7.21, and 1.8.x before 1.8.2; and Ops Manager 1.7.x before 1.7.13 and 1.8.x before 1.8.1 allow remote attackers to hijack the authentication of unspecified victims for requests that approve or deny a scope via a profile or authorize approval page.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven | >= 2.0.0, < 2.7.4.7 | 2.7.4.7 |
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven | >= 3.0.0, < 3.3.0.5 | 3.3.0.5 |
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven | >= 3.4.0, < 3.4.4 | 3.4.4 |
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven | >= 3.5.0, < 3.7.0 | 3.7.0 |
Patches
3cded6164a3b9Implement csrf on approvals pages
14 files changed · +116 −46
server/src/main/java/org/cloudfoundry/identity/uaa/approval/LoginUaaApprovalsService.java+1 −6 modified@@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -12,11 +12,6 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.approval; -import org.cloudfoundry.identity.uaa.approval.ApprovalsService; -import org.cloudfoundry.identity.uaa.approval.DescribedApproval; -import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.approval.Approval; -import org.cloudfoundry.identity.uaa.approval.ApprovalsControllerService; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList;
server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CookieBasedCsrfTokenRepository.java+1 −0 modified@@ -84,6 +84,7 @@ public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletRe Cookie csrfCookie = new Cookie(token.getParameterName(), token.getToken()); csrfCookie.setHttpOnly(true); csrfCookie.setSecure(secure || request.getProtocol().equals("https")); + if (expire) { csrfCookie.setMaxAge(0); } else {
server/src/main/resources/login-ui.xml+21 −4 modified@@ -55,7 +55,7 @@ <!-- TODO: add entry point that can redirect back to client app? --> <anonymous enabled="false" /> <custom-filter ref="autologinAuthenticationFilter" position="FORM_LOGIN_FILTER" /> - <csrf disabled="true"/> + <csrf disabled="false" token-repository-ref="loginCookieCsrfRepository"/> </http> <http name="secFilterCodeLogin" request-matcher-ref="autologinRequestMatcher" entry-point-ref="loginEntryPoint" @@ -156,8 +156,22 @@ <access-denied-handler ref="loginEntryPoint"/> </http> - <bean id="uiLoginRequestMatcher" class="org.springframework.security.web.util.matcher.AntPathRequestMatcher"> - <constructor-arg value="/login.do" /> + <bean id="uiCookeCsrfRequestMatcher" class="org.springframework.security.web.util.matcher.OrRequestMatcher"> + <constructor-arg> + <list> + <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher"> + <constructor-arg value="/login.do" /> + </bean> + <bean class="org.cloudfoundry.identity.uaa.security.web.UaaRequestMatcher"> + <constructor-arg value="/oauth/authorize" /> + <property name="method" value="POST"/> + </bean> + <bean class="org.cloudfoundry.identity.uaa.security.web.UaaRequestMatcher"> + <constructor-arg value="/profile" /> + <property name="method" value="POST"/> + </bean> + </list> + </constructor-arg> </bean> <bean id="uiLogoutRequestMatcher" class="org.springframework.security.web.util.matcher.AntPathRequestMatcher"> @@ -186,6 +200,9 @@ <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher"> <constructor-arg value="/logout.do**" /> </bean> + <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher"> + <constructor-arg value="/profile" /> + </bean> </list> </constructor-arg> </bean> @@ -231,7 +248,7 @@ <!--<logout logout-url="/logout.do" success-handler-ref="logoutHandler" invalidate-session="true"/>--> <custom-filter ref="logoutFilter" after="LOGOUT_FILTER"/> <custom-filter ref="samlLogoutFilter" before="LOGOUT_FILTER"/> - <csrf disabled="false" token-repository-ref="loginCookieCsrfRepository" request-matcher-ref="uiLoginRequestMatcher"/> + <csrf disabled="false" token-repository-ref="loginCookieCsrfRepository" request-matcher-ref="uiCookeCsrfRequestMatcher"/> <access-denied-handler error-page="/login?error=invalid_login_request"/> <request-cache ref="savedRequestCache"/> </http>
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/CheckTokenEndpointIntegrationTests.java+3 −2 modified@@ -14,7 +14,6 @@ import org.cloudfoundry.identity.uaa.ServerRunning; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; -import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.junit.Rule; @@ -37,6 +36,7 @@ import java.util.Arrays; import java.util.Map; +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -96,7 +96,7 @@ public void testDecodeToken() throws Exception { MultiValueMap<String, String> formData = new LinkedMultiValueMap<>(); formData.add("username", testAccounts.getUserName()); formData.add("password", testAccounts.getPassword()); - formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, csrf); + formData.add(DEFAULT_CSRF_COOKIE_NAME, csrf); // Should be redirected to the original URL, but now authenticated result = serverRunning.postForResponse("/login.do", headers, formData); @@ -115,6 +115,7 @@ public void testDecodeToken() throws Exception { assertTrue(response.getBody().contains("<h1>Application Authorization</h1>")); formData.clear(); + formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); formData.add(USER_OAUTH_APPROVAL, "true"); result = serverRunning.postForResponse("/oauth/authorize", headers, formData); assertEquals(HttpStatus.FOUND, result.getStatusCode());
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/AutologinIT.java+37 −28 modified@@ -13,7 +13,6 @@ package org.cloudfoundry.identity.uaa.integration.feature; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; -import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.junit.After; import org.junit.Assert; @@ -44,8 +43,8 @@ import java.util.List; import java.util.Map; +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; import static org.junit.Assert.assertEquals; -import static org.springframework.security.oauth2.common.util.OAuth2Utils.USER_OAUTH_APPROVAL; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = DefaultIntegrationTestConfig.class) @@ -126,9 +125,9 @@ public void testSimpleAutologinFlow() throws Exception { //generate an autologin code with our credentials ResponseEntity<Map> autologinResponseEntity = restOperations.exchange(baseUrl + "/autologin", - HttpMethod.POST, - new HttpEntity<>(requestBody.toSingleValueMap(), headers), - Map.class); + HttpMethod.POST, + new HttpEntity<>(requestBody.toSingleValueMap(), headers), + Map.class); String autologinCode = (String) autologinResponseEntity.getBody().get("code"); //start the authorization flow - this will issue a login event @@ -144,28 +143,32 @@ public void testSimpleAutologinFlow() throws Exception { //rest template that does NOT follow redirects RestTemplate template = new RestTemplate(new DefaultIntegrationTestConfig.HttpClientFactory()); headers.remove("Authorization"); - ResponseEntity<Map> authorizeResponse = template.exchange(authorizeUrl, - HttpMethod.GET, - new HttpEntity<>(new HashMap<String,String>(),headers), - Map.class); + headers.add(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE); + ResponseEntity<String> authorizeResponse = + template.exchange(authorizeUrl, + HttpMethod.GET, + new HttpEntity<>(new HashMap<String, String>(), headers), + String.class); //we are now logged in. retrieve the JSESSIONID List<String> cookies = authorizeResponse.getHeaders().get("Set-Cookie"); - assertEquals(1, cookies.size()); + assertEquals(2, cookies.size()); headers = getAppBasicAuthHttpHeaders(); headers.add("Cookie", cookies.get(0)); + headers.add("Cookie", cookies.get(1)); //if we receive a 200, then we must approve our scopes if (HttpStatus.OK == authorizeResponse.getStatusCode()) { authorizeUrl = UriComponentsBuilder.fromHttpUrl(baseUrl) .path("/oauth/authorize") - .queryParam(USER_OAUTH_APPROVAL, "true") + .queryParam("user_oauth_approval", "true") + .queryParam(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(authorizeResponse.getBody())) .build().toUriString(); authorizeResponse = template.exchange(authorizeUrl, - HttpMethod.POST, - new HttpEntity<>(new HashMap<String,String>(),headers), - Map.class); + HttpMethod.POST, + new HttpEntity<>(new HashMap<String,String>(),headers), + String.class); } //approval is complete, we receive a token code back @@ -195,43 +198,49 @@ public void testSimpleAutologinFlow() throws Exception { headers.set(headers.ACCEPT, MediaType.TEXT_HTML_VALUE); ResponseEntity<String> loginResponse = template.exchange(baseUrl + "/login", - HttpMethod.GET, - new HttpEntity<>(null, headers), - String.class); + 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); + requestBody.add(DEFAULT_CSRF_COOKIE_NAME, csrf); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); loginResponse = restOperations.exchange(baseUrl + "/login.do", - HttpMethod.POST, - new HttpEntity<>(requestBody, headers), - String.class); + HttpMethod.POST, + new HttpEntity<>(requestBody, headers), + String.class); cookies = loginResponse.getHeaders().get("Set-Cookie"); assertEquals(3, cookies.size()); headers.clear(); for (String cookie : loginResponse.getHeaders().get("Set-Cookie")) { - headers.add("Cookie", cookie); + if (!cookie.contains("1970")) { //deleted cookie + headers.add("Cookie", cookie); + } } - restOperations.exchange(baseUrl + "/profile", - HttpMethod.GET, - new HttpEntity<>(null, headers),Void.class); + headers.add(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE); + ResponseEntity<String> profilePage = + restOperations.exchange(baseUrl + "/profile", + HttpMethod.GET, + new HttpEntity<>(null, headers), String.class); + String revokeApprovalsUrl = UriComponentsBuilder.fromHttpUrl(baseUrl) .path("/profile") .build().toUriString(); requestBody.clear(); requestBody.add("clientId","app"); requestBody.add("delete",""); + requestBody.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(profilePage.getBody())); ResponseEntity<Void> revokeResponse = template.exchange(revokeApprovalsUrl, - HttpMethod.POST, - new HttpEntity<>(requestBody, headers), - Void.class); + HttpMethod.POST, + new HttpEntity<>(requestBody, headers), + Void.class); assertEquals(HttpStatus.FOUND, revokeResponse.getStatusCode()); }
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OpenIdTokenGrantsIT.java+2 −0 modified@@ -58,6 +58,7 @@ import java.util.Map; import java.util.Set; +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; @@ -349,6 +350,7 @@ private void doOpenIdHybridFlowIdTokenAndCode(Set<String> responseTypes, String formData.clear(); formData.add(USER_OAUTH_APPROVAL, "true"); + formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); result = restOperations.exchange(loginUrl + "/oauth/authorize", HttpMethod.POST, new HttpEntity<>(formData, headers), Void.class); assertEquals(HttpStatus.FOUND, result.getStatusCode()); location = UriUtils.decode(result.getHeaders().getLocation().toString(), "UTF-8");
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/OpenIdTokenAuthorizationWithApprovalIntegrationTests.java+2 −0 modified@@ -56,6 +56,7 @@ import java.util.Map; import java.util.Set; +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -276,6 +277,7 @@ private String doOpenIdHybridFlowIdTokenAndReturnCode(Set<String> responseTypes, formData.clear(); formData.add(USER_OAUTH_APPROVAL, "true"); + formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); result = serverRunning.postForResponse("/oauth/authorize", headers, formData); assertEquals(HttpStatus.FOUND, result.getStatusCode()); location = UriUtils.decode(result.getHeaders().getLocation().toString(), "UTF-8");
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/RefreshTokenSupportIntegrationTests.java+3 −2 modified@@ -15,7 +15,6 @@ import org.cloudfoundry.identity.uaa.ServerRunning; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; import org.cloudfoundry.identity.uaa.oauth.jwt.JwtHelper; -import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.junit.Before; @@ -36,6 +35,7 @@ import java.util.Arrays; import java.util.Map; +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -98,7 +98,7 @@ 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())); + formData.add(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); @@ -118,6 +118,7 @@ public void testTokenRefreshedCorrectFlow() throws Exception { formData.clear(); formData.add(USER_OAUTH_APPROVAL, "true"); + formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); result = serverRunning.postForResponse("/oauth/authorize", headers, formData); assertEquals(HttpStatus.FOUND, result.getStatusCode()); location = result.getHeaders().getLocation().toString();
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimGroupEndpointsIntegrationTests.java+2 −0 modified@@ -57,6 +57,7 @@ import java.util.List; import java.util.Map; +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -520,6 +521,7 @@ private OAuth2AccessToken getAccessToken(String clientId, String clientSecret, S assertTrue(response.getBody().contains("<h1>Application Authorization</h1>")); formData.clear(); + formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); formData.add(USER_OAUTH_APPROVAL, "true"); formData.add("scope.0", "scope." + CFID); result = serverRunning.postForResponse("/oauth/authorize", headers, formData);
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java+2 −0 modified@@ -84,6 +84,7 @@ import java.util.regex.Pattern; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_NAME_ATTRIBUTE_PREFIX; +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -1062,6 +1063,7 @@ public static Map<String,String> getAuthorizationCodeTokenMap(ServerRunning serv formData.clear(); formData.add(USER_OAUTH_APPROVAL, "true"); + formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); result = serverRunning.postForResponse("/oauth/authorize", headers, formData); assertEquals(HttpStatus.FOUND, result.getStatusCode()); location = result.getHeaders().getLocation().toString();
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java+1 −0 modified@@ -593,6 +593,7 @@ public void testUserCreatedEventDuringLoginServerAuthorize() throws Exception { "loginsecret", "oauth.login"); MockHttpServletRequestBuilder userPost = post("/oauth/authorize") + .with(cookieCsrf()) .accept(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_JSON) .header("Authorization", "Bearer " + loginToken)
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/AbstractTokenMockMvcTests.java+20 −0 modified@@ -170,6 +170,26 @@ protected ScimUser setUpUser(String username, String scopes, String origin, Stri return userProvisioning.retrieve(user.getId()); } + protected ScimUser syncGroups(ScimUser user) { + if (user == null) { + return user; + } + + Set<ScimGroup> directGroups = groupMembershipManager.getGroupsWithMember(user.getId(), false); + Set<ScimGroup> indirectGroups = groupMembershipManager.getGroupsWithMember(user.getId(), true); + indirectGroups.removeAll(directGroups); + Set<ScimUser.Group> groups = new HashSet<ScimUser.Group>(); + for (ScimGroup group : directGroups) { + groups.add(new ScimUser.Group(group.getId(), group.getDisplayName(), ScimUser.Group.Type.DIRECT)); + } + for (ScimGroup group : indirectGroups) { + groups.add(new ScimUser.Group(group.getId(), group.getDisplayName(), ScimUser.Group.Type.INDIRECT)); + } + + user.setGroups(groups); + return user; + } + protected ScimGroupMember addMember(ScimUser user, ScimGroup group) { ScimGroupMember gm = new ScimGroupMember(user.getId()); try {
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java+3 −0 modified@@ -89,6 +89,7 @@ import java.util.Set; import java.util.TreeSet; +import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.createClient; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.createUser; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.getClientCredentialsOAuthAccessToken; @@ -742,6 +743,7 @@ public void testOpenIdTokenHybridFlowWithNoImplicitGrantWhenLenientWhenAppNotApp MvcResult result = getMockMvc().perform( post("/oauth/authorize") .session(session) + .with(cookieCsrf()) .param(OAuth2Utils.USER_OAUTH_APPROVAL, "true") .param("scope.0","openid") ).andExpect(status().is3xxRedirection()).andReturn(); @@ -787,6 +789,7 @@ public void testOpenIdTokenHybridFlowWithNoImplicitGrantWhenStrictWhenAppNotAppr post("/oauth/authorize") .session(session) .param(OAuth2Utils.USER_OAUTH_APPROVAL, "true") + .with(cookieCsrf()) .param("scope.0", "openid") ).andExpect(status().is3xxRedirection()).andReturn();
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java+18 −4 modified@@ -1026,20 +1026,34 @@ 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(); + String tokenValue = 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]; + addCsrfCookie(request, cookie, cookies); + } + request.setParameter(token.getParameterName(), useInvalidToken ? "invalid" + tokenValue : tokenValue); + return request; + } + + protected void addCsrfCookie(MockHttpServletRequest request, Cookie cookie, Cookie[] cookies) { + boolean replaced = false; + for (int i=0; i<cookies.length; i++) { + Cookie c = cookies[i]; + if (cookie.getName()==c.getName()) { + cookies[i] = cookie; + replaced = true; + } + } + if (!replaced) { + 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() {
f3d8a9e1ee1aAdd test for csrf token for approvals pages
16 files changed · +510 −56
server/src/main/java/org/cloudfoundry/identity/uaa/approval/LoginUaaApprovalsService.java+1 −5 modified@@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -12,11 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.approval; -import org.cloudfoundry.identity.uaa.approval.ApprovalsService; -import org.cloudfoundry.identity.uaa.approval.DescribedApproval; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.approval.Approval; -import org.cloudfoundry.identity.uaa.approval.ApprovalsControllerService; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList;
server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CookieBasedCsrfTokenRepository.java+1 −0 modified@@ -84,6 +84,7 @@ public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletRe Cookie csrfCookie = new Cookie(token.getParameterName(), token.getToken()); csrfCookie.setHttpOnly(true); csrfCookie.setSecure(secure || request.getProtocol().equals("https")); + if (expire) { csrfCookie.setMaxAge(0); } else {
server/src/main/resources/login-ui.xml+21 −4 modified@@ -55,7 +55,7 @@ <!-- TODO: add entry point that can redirect back to client app? --> <anonymous enabled="false" /> <custom-filter ref="autologinAuthenticationFilter" position="FORM_LOGIN_FILTER" /> - <csrf disabled="true"/> + <csrf disabled="false" token-repository-ref="loginCookieCsrfRepository"/> </http> <http name="secFilterCodeLogin" request-matcher-ref="autologinRequestMatcher" entry-point-ref="loginEntryPoint" @@ -156,8 +156,22 @@ <access-denied-handler ref="loginEntryPoint"/> </http> - <bean id="uiLoginRequestMatcher" class="org.springframework.security.web.util.matcher.AntPathRequestMatcher"> - <constructor-arg value="/login.do" /> + <bean id="uiCookeCsrfRequestMatcher" class="org.springframework.security.web.util.matcher.OrRequestMatcher"> + <constructor-arg> + <list> + <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher"> + <constructor-arg value="/login.do" /> + </bean> + <bean class="org.cloudfoundry.identity.uaa.security.web.UaaRequestMatcher"> + <constructor-arg value="/oauth/authorize" /> + <property name="method" value="POST"/> + </bean> + <bean class="org.cloudfoundry.identity.uaa.security.web.UaaRequestMatcher"> + <constructor-arg value="/profile" /> + <property name="method" value="POST"/> + </bean> + </list> + </constructor-arg> </bean> <bean id="uiLogoutRequestMatcher" class="org.springframework.security.web.util.matcher.AntPathRequestMatcher"> @@ -186,6 +200,9 @@ <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher"> <constructor-arg value="/logout.do**" /> </bean> + <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher"> + <constructor-arg value="/profile" /> + </bean> </list> </constructor-arg> </bean> @@ -231,7 +248,7 @@ <!--<logout logout-url="/logout.do" success-handler-ref="logoutHandler" invalidate-session="true"/>--> <custom-filter ref="logoutFilter" after="LOGOUT_FILTER"/> <custom-filter ref="samlLogoutFilter" before="LOGOUT_FILTER"/> - <csrf disabled="false" token-repository-ref="loginCookieCsrfRepository" request-matcher-ref="uiLoginRequestMatcher"/> + <csrf disabled="false" token-repository-ref="loginCookieCsrfRepository" request-matcher-ref="uiCookeCsrfRequestMatcher"/> <access-denied-handler error-page="/login?error=invalid_login_request"/> <request-cache ref="savedRequestCache"/> </http>
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/CheckTokenEndpointIntegrationTests.java+11 −10 modified@@ -12,19 +12,10 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.integration; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.net.URI; -import java.util.Arrays; -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.security.web.CookieBasedCsrfTokenRepository; import org.junit.Rule; import org.junit.Test; import org.springframework.http.HttpHeaders; @@ -41,6 +32,15 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import java.net.URI; +import java.util.Arrays; +import java.util.Map; + +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + /** * @author Dave Syer */ @@ -95,7 +95,7 @@ public void testDecodeToken() throws Exception { MultiValueMap<String, String> formData = new LinkedMultiValueMap<>(); formData.add("username", testAccounts.getUserName()); formData.add("password", testAccounts.getPassword()); - formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, csrf); + formData.add(DEFAULT_CSRF_COOKIE_NAME, csrf); // Should be redirected to the original URL, but now authenticated result = serverRunning.postForResponse("/login.do", headers, formData); @@ -115,6 +115,7 @@ public void testDecodeToken() throws Exception { formData.clear(); formData.add("user_oauth_approval", "true"); + formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); result = serverRunning.postForResponse("/oauth/authorize", headers, formData); assertEquals(HttpStatus.FOUND, result.getStatusCode()); location = result.getHeaders().getLocation().toString();
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/AutologinIT.java+36 −26 modified@@ -13,7 +13,6 @@ package org.cloudfoundry.identity.uaa.integration.feature; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; -import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.junit.After; import org.junit.Assert; @@ -44,6 +43,7 @@ import java.util.List; import java.util.Map; +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; import static org.junit.Assert.assertEquals; @RunWith(SpringJUnit4ClassRunner.class) @@ -125,9 +125,9 @@ public void testSimpleAutologinFlow() throws Exception { //generate an autologin code with our credentials ResponseEntity<Map> autologinResponseEntity = restOperations.exchange(baseUrl + "/autologin", - HttpMethod.POST, - new HttpEntity<>(requestBody.toSingleValueMap(), headers), - Map.class); + HttpMethod.POST, + new HttpEntity<>(requestBody.toSingleValueMap(), headers), + Map.class); String autologinCode = (String) autologinResponseEntity.getBody().get("code"); //start the authorization flow - this will issue a login event @@ -143,28 +143,32 @@ public void testSimpleAutologinFlow() throws Exception { //rest template that does NOT follow redirects RestTemplate template = new RestTemplate(new DefaultIntegrationTestConfig.HttpClientFactory()); headers.remove("Authorization"); - ResponseEntity<Map> authorizeResponse = template.exchange(authorizeUrl, - HttpMethod.GET, - new HttpEntity<>(new HashMap<String,String>(),headers), - Map.class); + headers.add(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE); + ResponseEntity<String> authorizeResponse = + template.exchange(authorizeUrl, + HttpMethod.GET, + new HttpEntity<>(new HashMap<String, String>(), headers), + String.class); //we are now logged in. retrieve the JSESSIONID List<String> cookies = authorizeResponse.getHeaders().get("Set-Cookie"); - assertEquals(1, cookies.size()); + assertEquals(2, cookies.size()); headers = getAppBasicAuthHttpHeaders(); headers.add("Cookie", cookies.get(0)); + headers.add("Cookie", cookies.get(1)); //if we receive a 200, then we must approve our scopes if (HttpStatus.OK == authorizeResponse.getStatusCode()) { authorizeUrl = UriComponentsBuilder.fromHttpUrl(baseUrl) .path("/oauth/authorize") .queryParam("user_oauth_approval", "true") + .queryParam(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(authorizeResponse.getBody())) .build().toUriString(); authorizeResponse = template.exchange(authorizeUrl, - HttpMethod.POST, - new HttpEntity<>(new HashMap<String,String>(),headers), - Map.class); + HttpMethod.POST, + new HttpEntity<>(new HashMap<String,String>(),headers), + String.class); } //approval is complete, we receive a token code back @@ -194,43 +198,49 @@ public void testSimpleAutologinFlow() throws Exception { headers.set(headers.ACCEPT, MediaType.TEXT_HTML_VALUE); ResponseEntity<String> loginResponse = template.exchange(baseUrl + "/login", - HttpMethod.GET, - new HttpEntity<>(null, headers), - String.class); + 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); + requestBody.add(DEFAULT_CSRF_COOKIE_NAME, csrf); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); loginResponse = restOperations.exchange(baseUrl + "/login.do", - HttpMethod.POST, - new HttpEntity<>(requestBody, headers), - String.class); + HttpMethod.POST, + new HttpEntity<>(requestBody, headers), + String.class); cookies = loginResponse.getHeaders().get("Set-Cookie"); assertEquals(3, cookies.size()); headers.clear(); for (String cookie : loginResponse.getHeaders().get("Set-Cookie")) { - headers.add("Cookie", cookie); + if (!cookie.contains("1970")) { //deleted cookie + headers.add("Cookie", cookie); + } } - restOperations.exchange(baseUrl + "/profile", - HttpMethod.GET, - new HttpEntity<>(null, headers),Void.class); + headers.add(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE); + ResponseEntity<String> profilePage = + restOperations.exchange(baseUrl + "/profile", + HttpMethod.GET, + new HttpEntity<>(null, headers), String.class); + String revokeApprovalsUrl = UriComponentsBuilder.fromHttpUrl(baseUrl) .path("/profile") .build().toUriString(); requestBody.clear(); requestBody.add("clientId","app"); requestBody.add("delete",""); + requestBody.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(profilePage.getBody())); ResponseEntity<Void> revokeResponse = template.exchange(revokeApprovalsUrl, - HttpMethod.POST, - new HttpEntity<>(requestBody, headers), - Void.class); + HttpMethod.POST, + new HttpEntity<>(requestBody, headers), + Void.class); assertEquals(HttpStatus.FOUND, revokeResponse.getStatusCode()); }
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OpenIdTokenGrantsIT.java+2 −0 modified@@ -58,6 +58,7 @@ import java.util.Map; import java.util.Set; +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; @@ -348,6 +349,7 @@ private void doOpenIdHybridFlowIdTokenAndCode(Set<String> responseTypes, String formData.clear(); formData.add("user_oauth_approval", "true"); + formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); result = restOperations.exchange(loginUrl + "/oauth/authorize", HttpMethod.POST, new HttpEntity<>(formData, headers), Void.class); assertEquals(HttpStatus.FOUND, result.getStatusCode()); location = UriUtils.decode(result.getHeaders().getLocation().toString(), "UTF-8");
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/OpenIdTokenAuthorizationWithApprovalIntegrationTests.java+2 −0 modified@@ -56,6 +56,7 @@ import java.util.Map; import java.util.Set; +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -275,6 +276,7 @@ private String doOpenIdHybridFlowIdTokenAndReturnCode(Set<String> responseTypes, formData.clear(); formData.add("user_oauth_approval", "true"); + formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); result = serverRunning.postForResponse("/oauth/authorize", headers, formData); assertEquals(HttpStatus.FOUND, result.getStatusCode()); location = UriUtils.decode(result.getHeaders().getLocation().toString(), "UTF-8");
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/RefreshTokenSupportIntegrationTests.java+3 −2 modified@@ -15,7 +15,6 @@ import org.cloudfoundry.identity.uaa.ServerRunning; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; import org.cloudfoundry.identity.uaa.oauth.jwt.JwtHelper; -import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.junit.Before; @@ -36,6 +35,7 @@ import java.util.Arrays; import java.util.Map; +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -97,7 +97,7 @@ 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())); + formData.add(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); @@ -117,6 +117,7 @@ public void testTokenRefreshedCorrectFlow() throws Exception { formData.clear(); formData.add("user_oauth_approval", "true"); + formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); result = serverRunning.postForResponse("/oauth/authorize", headers, formData); assertEquals(HttpStatus.FOUND, result.getStatusCode()); location = result.getHeaders().getLocation().toString();
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimGroupEndpointsIntegrationTests.java+2 −0 modified@@ -56,6 +56,7 @@ import java.util.List; import java.util.Map; +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -519,6 +520,7 @@ private OAuth2AccessToken getAccessToken(String clientId, String clientSecret, S formData.clear(); formData.add("user_oauth_approval", "true"); + formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); formData.add("scope.0", "scope." + CFID); result = serverRunning.postForResponse("/oauth/authorize", headers, formData); assertEquals(HttpStatus.FOUND, result.getStatusCode());
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java+3 −0 modified@@ -84,10 +84,12 @@ import java.util.regex.Pattern; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_NAME_ATTRIBUTE_PREFIX; +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; + public class IntegrationTestUtils { public static final DefaultResponseErrorHandler fiveHundredErrorHandler = new DefaultResponseErrorHandler(){ @@ -1060,6 +1062,7 @@ public static Map<String,String> getAuthorizationCodeTokenMap(ServerRunning serv formData.clear(); formData.add("user_oauth_approval", "true"); + formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); result = serverRunning.postForResponse("/oauth/authorize", headers, formData); assertEquals(HttpStatus.FOUND, result.getStatusCode()); location = result.getHeaders().getLocation().toString();
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/approvals/ApprovalsMockMvcTests.java+181 −0 added@@ -0,0 +1,181 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.mock.approvals; + + +import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.mock.token.AbstractTokenMockMvcTests; +import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; +import org.cloudfoundry.identity.uaa.scim.ScimUser; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; +import static org.cloudfoundry.identity.uaa.oauth.UaaTokenServicesTests.AUTHORIZATION_CODE; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.springframework.security.oauth2.common.util.OAuth2Utils.CLIENT_ID; +import static org.springframework.security.oauth2.common.util.OAuth2Utils.RESPONSE_TYPE; +import static org.springframework.security.oauth2.common.util.OAuth2Utils.STATE; +import static org.springframework.security.oauth2.common.util.OAuth2Utils.USER_OAUTH_APPROVAL; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +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.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class ApprovalsMockMvcTests extends AbstractTokenMockMvcTests { + + private String scopes = "test.scope1,test.scope2,test.scope3"; + private RandomValueStringGenerator generator = new RandomValueStringGenerator(); + private ScimUser user1; + private ClientDetails client1; + + + @Before + public void createData() { + user1= syncGroups(setUpUser(generator.generate(), scopes, OriginKeys.UAA, IdentityZone.getUaa().getId())); + client1 = setUpClients(generator.generate(), null, scopes, AUTHORIZATION_CODE, false); + } + + + @Test + public void test_oauth_authorize_without_csrf() throws Exception { + String state = generator.generate(); + + MockHttpSession session = getAuthenticatedSession(user1); + getMockMvc().perform( + get("/oauth/authorize") + .session(session) + .param(RESPONSE_TYPE, "code") + .param(STATE, state) + .param(CLIENT_ID, client1.getClientId())) + .andExpect(status().isOk()); //200 means the approvals page + + + assertNotNull(session.getAttribute("authorizationRequest")); + + //no token + getMockMvc().perform( + post("/oauth/authorize") + .session(session) + .param(USER_OAUTH_APPROVAL, "true") + .param("scope.0","test.scope1") + ) + .andExpect(status().is4xxClientError()); + + //invalid token + getMockMvc().perform( + post("/oauth/authorize") + .with(cookieCsrf().useInvalidToken()) + .session(session) + .param(USER_OAUTH_APPROVAL, "true") + .param("scope.0","test.scope1") + ) + .andExpect(status().is4xxClientError()); + + assertNotNull(session.getAttribute("authorizationRequest")); + + //valid token + getMockMvc().perform( + post("/oauth/authorize") + .with(cookieCsrf()) + .session(session) + .param(USER_OAUTH_APPROVAL, "true") + .param("scope.0","test.scope1") + .param("scope.1","test.scope2") + ) + .andExpect(status().isFound()) + .andExpect(redirectedUrlPattern("**/*code=*")); + + assertNull(session.getAttribute("authorizationRequest")); + + getMockMvc().perform( + get("/oauth/authorize") + .session(session) + .param(RESPONSE_TYPE, "code") + .param(STATE, state) + .param(CLIENT_ID, client1.getClientId())) + .andExpect(status().isFound()); //approval page no longer showing up + } + + @Test + public void test_get_approvals() throws Exception { + test_oauth_authorize_without_csrf(); + MockHttpSession session = getAuthenticatedSession(user1); + getMockMvc().perform( + get("/profile") + .session(session) + ) + .andExpect(status().isOk()) + .andExpect(content().string(containsString(client1.getClientId() + "-test.scope1"))); + } + + @Test + public void test_post_approval_csrf() throws Exception { + test_get_approvals(); + MockHttpSession session = getAuthenticatedSession(user1); + MockHttpServletRequestBuilder post = post("/profile") + .session(session) + .param("checkScopes", client1.getClientId() + "-test.scope1", client1.getClientId() + "-test.scope2"); + getMockMvc().perform( + post + ) + .andDo(print()) + .andExpect(status().isForbidden()); + + getMockMvc().perform( + post.with(cookieCsrf().useInvalidToken()) + ).andExpect(status().isForbidden()); + + getMockMvc().perform( + post.with(cookieCsrf()) + ) + .andExpect(status().isFound()) + .andExpect(redirectedUrlPattern("**/profile")); + } + + public MockHttpSession getAuthenticatedSession(ScimUser user) { + List<SimpleGrantedAuthority> authorities = user.getGroups().stream().map(g -> new SimpleGrantedAuthority(g.getValue())).collect(Collectors.toList()); + UaaPrincipal p = new UaaPrincipal(user.getId(), user.getUserName(), user.getPrimaryEmail(), OriginKeys.UAA, "", IdentityZoneHolder.get().getId()); + UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(p, "", authorities); + Assert.assertTrue(auth.isAuthenticated()); + SecurityContextHolder.getContext().setAuthentication(auth); + MockHttpSession session = new MockHttpSession(); + session.setAttribute( + HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, + new MockMvcUtils.MockSecurityContext(auth) + ); + return session; + } + +}
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java+1 −0 modified@@ -593,6 +593,7 @@ public void testUserCreatedEventDuringLoginServerAuthorize() throws Exception { "loginsecret", "oauth.login"); MockHttpServletRequestBuilder userPost = post("/oauth/authorize") + .with(cookieCsrf()) .accept(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_JSON) .header("Authorization", "Bearer " + loginToken)
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/AbstractTokenMockMvcTests.java+209 −0 added@@ -0,0 +1,209 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.mock.token; + +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; +import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; +import org.cloudfoundry.identity.uaa.oauth.UaaTokenServices; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.token.RevocableTokenProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +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.scim.exception.MemberAlreadyExistsException; +import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupMembershipManager; +import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupProvisioning; +import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimUserProvisioning; +import org.cloudfoundry.identity.uaa.test.TestClient; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; +import org.junit.Before; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; +import org.springframework.util.StringUtils; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public abstract class AbstractTokenMockMvcTests extends InjectedMockContextTest { + + public static final String SECRET = "secret"; + public static final String GRANT_TYPES = "password,implicit,client_credentials,authorization_code"; + public static final String TEST_REDIRECT_URI = "http://test.example.org/redirect"; + + protected TestClient testClient; + protected JdbcClientDetailsService clientDetailsService; + protected JdbcScimUserProvisioning userProvisioning; + protected JdbcScimGroupProvisioning groupProvisioning; + protected JdbcScimGroupMembershipManager groupMembershipManager; + protected UaaTokenServices tokenServices; + protected Set<String> defaultAuthorities; + + protected IdentityZoneProvisioning identityZoneProvisioning; + protected JdbcScimUserProvisioning jdbcScimUserProvisioning; + protected IdentityProviderProvisioning identityProviderProvisioning; + protected String adminToken; + protected RevocableTokenProvisioning tokenProvisioning; + + @Before + public void setUpContext() throws Exception { + testClient = new TestClient(getMockMvc()); + clientDetailsService = (JdbcClientDetailsService) getWebApplicationContext().getBean("jdbcClientDetailsService"); + userProvisioning = (JdbcScimUserProvisioning) getWebApplicationContext().getBean("scimUserProvisioning"); + groupProvisioning = (JdbcScimGroupProvisioning) getWebApplicationContext().getBean("scimGroupProvisioning"); + groupMembershipManager = (JdbcScimGroupMembershipManager) getWebApplicationContext().getBean("groupMembershipManager"); + tokenServices = (UaaTokenServices) getWebApplicationContext().getBean("tokenServices"); + defaultAuthorities = (Set<String>) getWebApplicationContext().getBean("defaultUserAuthorities"); + identityZoneProvisioning = getWebApplicationContext().getBean(IdentityZoneProvisioning.class); + jdbcScimUserProvisioning = getWebApplicationContext().getBean(JdbcScimUserProvisioning.class); + identityProviderProvisioning = getWebApplicationContext().getBean(IdentityProviderProvisioning.class); + IdentityZoneHolder.clear(); + + adminToken = MockMvcUtils.utils() + .getClientCredentialsOAuthAccessToken( + getMockMvc(), + "admin", + "adminsecret", + "uaa.admin", + null + ); + tokenProvisioning = (RevocableTokenProvisioning) getWebApplicationContext().getBean("revocableTokenProvisioning"); + } + + protected IdentityZone setupIdentityZone(String subdomain) { + IdentityZone zone = new IdentityZone(); + zone.getConfig().getTokenPolicy().setKeys(Collections.singletonMap(subdomain+"_key", "key_for_"+subdomain)); + zone.setId(UUID.randomUUID().toString()); + zone.setName(subdomain); + zone.setSubdomain(subdomain); + zone.setDescription(subdomain); + identityZoneProvisioning.create(zone); + return zone; + } + + protected IdentityProvider setupIdentityProvider() { + return setupIdentityProvider(OriginKeys.UAA); + } + protected IdentityProvider setupIdentityProvider(String origin) { + IdentityProvider defaultIdp = new IdentityProvider(); + defaultIdp.setName(origin); + defaultIdp.setType(origin); + defaultIdp.setOriginKey(origin); + defaultIdp.setIdentityZoneId(IdentityZoneHolder.get().getId()); + return identityProviderProvisioning.create(defaultIdp); + } + + protected BaseClientDetails setUpClients(String id, String authorities, String scopes, String grantTypes, Boolean autoapprove) { + return setUpClients(id, authorities, scopes, grantTypes, autoapprove, null); + } + protected BaseClientDetails setUpClients(String id, String authorities, String scopes, String grantTypes, Boolean autoapprove, String redirectUri) { + return setUpClients(id, authorities, scopes, grantTypes, autoapprove, redirectUri, null); + } + protected BaseClientDetails setUpClients(String id, String authorities, String scopes, String grantTypes, Boolean autoapprove, String redirectUri, List<String> allowedIdps) { + return setUpClients(id, authorities, scopes, grantTypes, autoapprove, redirectUri, allowedIdps, -1); + } + protected BaseClientDetails setUpClients(String id, String authorities, String scopes, String grantTypes, Boolean autoapprove, String redirectUri, List<String> allowedIdps, int accessTokenValidity) { + BaseClientDetails c = new BaseClientDetails(id, "", scopes, grantTypes, authorities); + if (!"implicit".equals(grantTypes)) { + c.setClientSecret(SECRET); + } + c.setRegisteredRedirectUri(new HashSet<>(Arrays.asList(TEST_REDIRECT_URI))); + c.setAutoApproveScopes(Collections.singleton(autoapprove.toString())); + Map<String, Object> additional = new HashMap<>(); + if (allowedIdps!=null && !allowedIdps.isEmpty()) { + additional.put(ClientConstants.ALLOWED_PROVIDERS, allowedIdps); + } + c.setAdditionalInformation(additional); + if (StringUtils.hasText(redirectUri)) { + c.setRegisteredRedirectUri(new HashSet<>(Arrays.asList(redirectUri))); + } + if (accessTokenValidity>0) { + c.setAccessTokenValiditySeconds(accessTokenValidity); + } + clientDetailsService.addClientDetails(c); + return (BaseClientDetails) clientDetailsService.loadClientByClientId(c.getClientId()); + } + + protected ScimUser setUpUser(String username, String scopes, String origin, String zoneId) { + ScimUser user = new ScimUser(null, username, "GivenName", "FamilyName"); + user.setPassword(SECRET); + ScimUser.Email email = new ScimUser.Email(); + email.setValue("test@test.org"); + email.setPrimary(true); + user.setEmails(Arrays.asList(email)); + user.setVerified(true); + user.setOrigin(origin); + + user = userProvisioning.createUser(user, SECRET); + + Set<String> scopeSet = StringUtils.commaDelimitedListToSet(scopes); + Set<ScimGroup> groups = new HashSet<>(); + for (String scope : scopeSet) { + ScimGroup g = createIfNotExist(scope,zoneId); + groups.add(g); + addMember(user, g); + } + + return userProvisioning.retrieve(user.getId()); + } + + protected ScimUser syncGroups(ScimUser user) { + if (user == null) { + return user; + } + + Set<ScimGroup> directGroups = groupMembershipManager.getGroupsWithMember(user.getId(), false); + Set<ScimGroup> indirectGroups = groupMembershipManager.getGroupsWithMember(user.getId(), true); + indirectGroups.removeAll(directGroups); + Set<ScimUser.Group> groups = new HashSet<ScimUser.Group>(); + for (ScimGroup group : directGroups) { + groups.add(new ScimUser.Group(group.getId(), group.getDisplayName(), ScimUser.Group.Type.DIRECT)); + } + for (ScimGroup group : indirectGroups) { + groups.add(new ScimUser.Group(group.getId(), group.getDisplayName(), ScimUser.Group.Type.INDIRECT)); + } + + user.setGroups(groups); + return user; + } + + protected ScimGroupMember addMember(ScimUser user, ScimGroup group) { + ScimGroupMember gm = new ScimGroupMember(user.getId()); + try { + return groupMembershipManager.addMember(group.getId(), gm); + }catch (MemberAlreadyExistsException x) { + return gm; + } + } + + protected ScimGroup createIfNotExist(String scope, String zoneId) { + List<ScimGroup> exists = groupProvisioning.query("displayName eq \"" + scope + "\" and identity_zone_id eq \""+zoneId+"\""); + if (exists.size() > 0) { + return exists.get(0); + } else { + return groupProvisioning.create(new ScimGroup(null,scope,zoneId)); + } + } +}
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java+14 −4 modified@@ -19,7 +19,6 @@ import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; -import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.oauth.DisableIdTokenResponseTypeFilter; import org.cloudfoundry.identity.uaa.oauth.KeyInfo; import org.cloudfoundry.identity.uaa.oauth.UaaTokenServices; @@ -100,6 +99,7 @@ import java.util.TreeSet; import java.util.UUID; +import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.createClient; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.createUser; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.getClientCredentialsOAuthAccessToken; @@ -113,14 +113,22 @@ import static org.hamcrest.Matchers.stringContainsInOrder; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.StringStartsWith.startsWith; -import static org.junit.Assert.*; -import static org.springframework.security.oauth2.common.util.OAuth2Utils.GRANT_TYPE; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +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.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + public class TokenMvcMockTests extends InjectedMockContextTest { private String SECRET = "secret"; @@ -701,6 +709,7 @@ public void testOpenIdTokenHybridFlowWithNoImplicitGrantWhenLenientWhenAppNotApp MvcResult result = getMockMvc().perform( post("/oauth/authorize") .session(session) + .with(cookieCsrf()) .param(OAuth2Utils.USER_OAUTH_APPROVAL, "true") .param("scope.0","openid") ).andExpect(status().is3xxRedirection()).andReturn(); @@ -746,6 +755,7 @@ public void testOpenIdTokenHybridFlowWithNoImplicitGrantWhenStrictWhenAppNotAppr post("/oauth/authorize") .session(session) .param(OAuth2Utils.USER_OAUTH_APPROVAL, "true") + .with(cookieCsrf()) .param("scope.0", "openid") ).andExpect(status().is3xxRedirection()).andReturn();
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/UaaJunitSuiteRunner.java+5 −1 modified@@ -30,6 +30,7 @@ import java.lang.reflect.Modifier; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; /** * Suite that runs classes that extend the @@ -42,7 +43,10 @@ public class UaaJunitSuiteRunner extends Suite { protected static Class<?>[] allSuiteClasses() { Reflections reflections = new Reflections("org.cloudfoundry.identity.uaa"); - Set<Class<? extends InjectedMockContextTest>> subTypes = reflections.getSubTypesOf(InjectedMockContextTest.class); + Set<Class<? extends InjectedMockContextTest>> subTypes = + reflections.getSubTypesOf(InjectedMockContextTest.class).stream().filter( + c -> !Modifier.isAbstract(c.getModifiers()) + ).collect(Collectors.toSet()); return subTypes.toArray(new Class[subTypes.size()]); }
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java+18 −4 modified@@ -1016,20 +1016,34 @@ 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(); + String tokenValue = 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]; + addCsrfCookie(request, cookie, cookies); + } + request.setParameter(token.getParameterName(), useInvalidToken ? "invalid" + tokenValue : tokenValue); + return request; + } + + protected void addCsrfCookie(MockHttpServletRequest request, Cookie cookie, Cookie[] cookies) { + boolean replaced = false; + for (int i=0; i<cookies.length; i++) { + Cookie c = cookies[i]; + if (cookie.getName()==c.getName()) { + cookies[i] = cookie; + replaced = true; + } + } + if (!replaced) { + 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() {
37e0384c52d3Add test for csrf token for approvals pages
16 files changed · +500 −52
server/src/main/java/org/cloudfoundry/identity/uaa/approval/LoginUaaApprovalsService.java+1 −5 modified@@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -12,11 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.approval; -import org.cloudfoundry.identity.uaa.approval.ApprovalsService; -import org.cloudfoundry.identity.uaa.approval.DescribedApproval; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.approval.Approval; -import org.cloudfoundry.identity.uaa.approval.ApprovalsControllerService; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList;
server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CookieBasedCsrfTokenRepository.java+1 −0 modified@@ -84,6 +84,7 @@ public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletRe Cookie csrfCookie = new Cookie(token.getParameterName(), token.getToken()); csrfCookie.setHttpOnly(true); csrfCookie.setSecure(secure || request.getProtocol().equals("https")); + if (expire) { csrfCookie.setMaxAge(0); } else {
server/src/main/resources/login-ui.xml+21 −4 modified@@ -55,7 +55,7 @@ <!-- TODO: add entry point that can redirect back to client app? --> <anonymous enabled="false" /> <custom-filter ref="autologinAuthenticationFilter" position="FORM_LOGIN_FILTER" /> - <csrf disabled="true"/> + <csrf disabled="false" token-repository-ref="loginCookieCsrfRepository"/> </http> <http name="secFilterCodeLogin" request-matcher-ref="autologinRequestMatcher" entry-point-ref="loginEntryPoint" @@ -156,8 +156,22 @@ <access-denied-handler ref="loginEntryPoint"/> </http> - <bean id="uiLoginRequestMatcher" class="org.springframework.security.web.util.matcher.AntPathRequestMatcher"> - <constructor-arg value="/login.do" /> + <bean id="uiCookeCsrfRequestMatcher" class="org.springframework.security.web.util.matcher.OrRequestMatcher"> + <constructor-arg> + <list> + <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher"> + <constructor-arg value="/login.do" /> + </bean> + <bean class="org.cloudfoundry.identity.uaa.security.web.UaaRequestMatcher"> + <constructor-arg value="/oauth/authorize" /> + <property name="method" value="POST"/> + </bean> + <bean class="org.cloudfoundry.identity.uaa.security.web.UaaRequestMatcher"> + <constructor-arg value="/profile" /> + <property name="method" value="POST"/> + </bean> + </list> + </constructor-arg> </bean> <bean id="uiLogoutRequestMatcher" class="org.springframework.security.web.util.matcher.AntPathRequestMatcher"> @@ -186,6 +200,9 @@ <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher"> <constructor-arg value="/logout.do**" /> </bean> + <bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher"> + <constructor-arg value="/profile" /> + </bean> </list> </constructor-arg> </bean> @@ -231,7 +248,7 @@ <!--<logout logout-url="/logout.do" success-handler-ref="logoutHandler" invalidate-session="true"/>--> <custom-filter ref="logoutFilter" after="LOGOUT_FILTER"/> <custom-filter ref="samlLogoutFilter" before="LOGOUT_FILTER"/> - <csrf disabled="false" token-repository-ref="loginCookieCsrfRepository" request-matcher-ref="uiLoginRequestMatcher"/> + <csrf disabled="false" token-repository-ref="loginCookieCsrfRepository" request-matcher-ref="uiCookeCsrfRequestMatcher"/> <access-denied-handler error-page="/login?error=invalid_login_request"/> <request-cache ref="savedRequestCache"/> </http>
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/CheckTokenEndpointIntegrationTests.java+11 −10 modified@@ -12,19 +12,10 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.integration; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.net.URI; -import java.util.Arrays; -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.security.web.CookieBasedCsrfTokenRepository; import org.junit.Rule; import org.junit.Test; import org.springframework.http.HttpHeaders; @@ -41,6 +32,15 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import java.net.URI; +import java.util.Arrays; +import java.util.Map; + +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + /** * @author Dave Syer */ @@ -95,7 +95,7 @@ public void testDecodeToken() throws Exception { MultiValueMap<String, String> formData = new LinkedMultiValueMap<>(); formData.add("username", testAccounts.getUserName()); formData.add("password", testAccounts.getPassword()); - formData.add(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, csrf); + formData.add(DEFAULT_CSRF_COOKIE_NAME, csrf); // Should be redirected to the original URL, but now authenticated result = serverRunning.postForResponse("/login.do", headers, formData); @@ -115,6 +115,7 @@ public void testDecodeToken() throws Exception { formData.clear(); formData.add("user_oauth_approval", "true"); + formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); result = serverRunning.postForResponse("/oauth/authorize", headers, formData); assertEquals(HttpStatus.FOUND, result.getStatusCode()); location = result.getHeaders().getLocation().toString();
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/AutologinIT.java+36 −26 modified@@ -13,7 +13,6 @@ package org.cloudfoundry.identity.uaa.integration.feature; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; -import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.junit.After; import org.junit.Assert; @@ -44,6 +43,7 @@ import java.util.List; import java.util.Map; +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; import static org.junit.Assert.assertEquals; @RunWith(SpringJUnit4ClassRunner.class) @@ -125,9 +125,9 @@ public void testSimpleAutologinFlow() throws Exception { //generate an autologin code with our credentials ResponseEntity<Map> autologinResponseEntity = restOperations.exchange(baseUrl + "/autologin", - HttpMethod.POST, - new HttpEntity<>(requestBody.toSingleValueMap(), headers), - Map.class); + HttpMethod.POST, + new HttpEntity<>(requestBody.toSingleValueMap(), headers), + Map.class); String autologinCode = (String) autologinResponseEntity.getBody().get("code"); //start the authorization flow - this will issue a login event @@ -143,28 +143,32 @@ public void testSimpleAutologinFlow() throws Exception { //rest template that does NOT follow redirects RestTemplate template = new RestTemplate(new DefaultIntegrationTestConfig.HttpClientFactory()); headers.remove("Authorization"); - ResponseEntity<Map> authorizeResponse = template.exchange(authorizeUrl, - HttpMethod.GET, - new HttpEntity<>(new HashMap<String,String>(),headers), - Map.class); + headers.add(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE); + ResponseEntity<String> authorizeResponse = + template.exchange(authorizeUrl, + HttpMethod.GET, + new HttpEntity<>(new HashMap<String, String>(), headers), + String.class); //we are now logged in. retrieve the JSESSIONID List<String> cookies = authorizeResponse.getHeaders().get("Set-Cookie"); - assertEquals(1, cookies.size()); + assertEquals(2, cookies.size()); headers = getAppBasicAuthHttpHeaders(); headers.add("Cookie", cookies.get(0)); + headers.add("Cookie", cookies.get(1)); //if we receive a 200, then we must approve our scopes if (HttpStatus.OK == authorizeResponse.getStatusCode()) { authorizeUrl = UriComponentsBuilder.fromHttpUrl(baseUrl) .path("/oauth/authorize") .queryParam("user_oauth_approval", "true") + .queryParam(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(authorizeResponse.getBody())) .build().toUriString(); authorizeResponse = template.exchange(authorizeUrl, - HttpMethod.POST, - new HttpEntity<>(new HashMap<String,String>(),headers), - Map.class); + HttpMethod.POST, + new HttpEntity<>(new HashMap<String,String>(),headers), + String.class); } //approval is complete, we receive a token code back @@ -194,43 +198,49 @@ public void testSimpleAutologinFlow() throws Exception { headers.set(headers.ACCEPT, MediaType.TEXT_HTML_VALUE); ResponseEntity<String> loginResponse = template.exchange(baseUrl + "/login", - HttpMethod.GET, - new HttpEntity<>(null, headers), - String.class); + 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); + requestBody.add(DEFAULT_CSRF_COOKIE_NAME, csrf); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); loginResponse = restOperations.exchange(baseUrl + "/login.do", - HttpMethod.POST, - new HttpEntity<>(requestBody, headers), - String.class); + HttpMethod.POST, + new HttpEntity<>(requestBody, headers), + String.class); cookies = loginResponse.getHeaders().get("Set-Cookie"); assertEquals(3, cookies.size()); headers.clear(); for (String cookie : loginResponse.getHeaders().get("Set-Cookie")) { - headers.add("Cookie", cookie); + if (!cookie.contains("1970")) { //deleted cookie + headers.add("Cookie", cookie); + } } - restOperations.exchange(baseUrl + "/profile", - HttpMethod.GET, - new HttpEntity<>(null, headers),Void.class); + headers.add(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE); + ResponseEntity<String> profilePage = + restOperations.exchange(baseUrl + "/profile", + HttpMethod.GET, + new HttpEntity<>(null, headers), String.class); + String revokeApprovalsUrl = UriComponentsBuilder.fromHttpUrl(baseUrl) .path("/profile") .build().toUriString(); requestBody.clear(); requestBody.add("clientId","app"); requestBody.add("delete",""); + requestBody.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(profilePage.getBody())); ResponseEntity<Void> revokeResponse = template.exchange(revokeApprovalsUrl, - HttpMethod.POST, - new HttpEntity<>(requestBody, headers), - Void.class); + HttpMethod.POST, + new HttpEntity<>(requestBody, headers), + Void.class); assertEquals(HttpStatus.FOUND, revokeResponse.getStatusCode()); }
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OpenIdTokenGrantsIT.java+2 −0 modified@@ -58,6 +58,7 @@ import java.util.Map; import java.util.Set; +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; @@ -348,6 +349,7 @@ private void doOpenIdHybridFlowIdTokenAndCode(Set<String> responseTypes, String formData.clear(); formData.add("user_oauth_approval", "true"); + formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); result = restOperations.exchange(loginUrl + "/oauth/authorize", HttpMethod.POST, new HttpEntity<>(formData, headers), Void.class); assertEquals(HttpStatus.FOUND, result.getStatusCode()); location = UriUtils.decode(result.getHeaders().getLocation().toString(), "UTF-8");
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/OpenIdTokenAuthorizationWithApprovalIntegrationTests.java+2 −0 modified@@ -56,6 +56,7 @@ import java.util.Map; import java.util.Set; +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -275,6 +276,7 @@ private String doOpenIdHybridFlowIdTokenAndReturnCode(Set<String> responseTypes, formData.clear(); formData.add("user_oauth_approval", "true"); + formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); result = serverRunning.postForResponse("/oauth/authorize", headers, formData); assertEquals(HttpStatus.FOUND, result.getStatusCode()); location = UriUtils.decode(result.getHeaders().getLocation().toString(), "UTF-8");
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/RefreshTokenSupportIntegrationTests.java+3 −2 modified@@ -15,7 +15,6 @@ import org.cloudfoundry.identity.uaa.ServerRunning; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; import org.cloudfoundry.identity.uaa.oauth.jwt.JwtHelper; -import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.junit.Before; @@ -36,6 +35,7 @@ import java.util.Arrays; import java.util.Map; +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -97,7 +97,7 @@ 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())); + formData.add(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); @@ -117,6 +117,7 @@ public void testTokenRefreshedCorrectFlow() throws Exception { formData.clear(); formData.add("user_oauth_approval", "true"); + formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); result = serverRunning.postForResponse("/oauth/authorize", headers, formData); assertEquals(HttpStatus.FOUND, result.getStatusCode()); location = result.getHeaders().getLocation().toString();
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimGroupEndpointsIntegrationTests.java+2 −0 modified@@ -57,6 +57,7 @@ import java.util.List; import java.util.Map; +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -520,6 +521,7 @@ private OAuth2AccessToken getAccessToken(String clientId, String clientSecret, S formData.clear(); formData.add("user_oauth_approval", "true"); + formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); formData.add("scope.0", "scope." + CFID); result = serverRunning.postForResponse("/oauth/authorize", headers, formData); assertEquals(HttpStatus.FOUND, result.getStatusCode());
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java+3 −0 modified@@ -84,10 +84,12 @@ import java.util.regex.Pattern; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_NAME_ATTRIBUTE_PREFIX; +import static org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; + public class IntegrationTestUtils { public static final DefaultResponseErrorHandler fiveHundredErrorHandler = new DefaultResponseErrorHandler(){ @@ -1060,6 +1062,7 @@ public static Map<String,String> getAuthorizationCodeTokenMap(ServerRunning serv formData.clear(); formData.add("user_oauth_approval", "true"); + formData.add(DEFAULT_CSRF_COOKIE_NAME, IntegrationTestUtils.extractCookieCsrf(response.getBody())); result = serverRunning.postForResponse("/oauth/authorize", headers, formData); assertEquals(HttpStatus.FOUND, result.getStatusCode()); location = result.getHeaders().getLocation().toString();
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/approvals/ApprovalsMockMvcTests.java+181 −0 added@@ -0,0 +1,181 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.mock.approvals; + + +import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.mock.token.AbstractTokenMockMvcTests; +import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; +import org.cloudfoundry.identity.uaa.scim.ScimUser; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; +import static org.cloudfoundry.identity.uaa.oauth.UaaTokenServicesTests.AUTHORIZATION_CODE; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.springframework.security.oauth2.common.util.OAuth2Utils.CLIENT_ID; +import static org.springframework.security.oauth2.common.util.OAuth2Utils.RESPONSE_TYPE; +import static org.springframework.security.oauth2.common.util.OAuth2Utils.STATE; +import static org.springframework.security.oauth2.common.util.OAuth2Utils.USER_OAUTH_APPROVAL; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +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.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class ApprovalsMockMvcTests extends AbstractTokenMockMvcTests { + + private String scopes = "test.scope1,test.scope2,test.scope3"; + private RandomValueStringGenerator generator = new RandomValueStringGenerator(); + private ScimUser user1; + private ClientDetails client1; + + + @Before + public void createData() { + user1= syncGroups(setUpUser(generator.generate(), scopes, OriginKeys.UAA, IdentityZone.getUaa().getId())); + client1 = setUpClients(generator.generate(), null, scopes, AUTHORIZATION_CODE, false); + } + + + @Test + public void test_oauth_authorize_without_csrf() throws Exception { + String state = generator.generate(); + + MockHttpSession session = getAuthenticatedSession(user1); + getMockMvc().perform( + get("/oauth/authorize") + .session(session) + .param(RESPONSE_TYPE, "code") + .param(STATE, state) + .param(CLIENT_ID, client1.getClientId())) + .andExpect(status().isOk()); //200 means the approvals page + + + assertNotNull(session.getAttribute("authorizationRequest")); + + //no token + getMockMvc().perform( + post("/oauth/authorize") + .session(session) + .param(USER_OAUTH_APPROVAL, "true") + .param("scope.0","test.scope1") + ) + .andExpect(status().is4xxClientError()); + + //invalid token + getMockMvc().perform( + post("/oauth/authorize") + .with(cookieCsrf().useInvalidToken()) + .session(session) + .param(USER_OAUTH_APPROVAL, "true") + .param("scope.0","test.scope1") + ) + .andExpect(status().is4xxClientError()); + + assertNotNull(session.getAttribute("authorizationRequest")); + + //valid token + getMockMvc().perform( + post("/oauth/authorize") + .with(cookieCsrf()) + .session(session) + .param(USER_OAUTH_APPROVAL, "true") + .param("scope.0","test.scope1") + .param("scope.1","test.scope2") + ) + .andExpect(status().isFound()) + .andExpect(redirectedUrlPattern("**/*code=*")); + + assertNull(session.getAttribute("authorizationRequest")); + + getMockMvc().perform( + get("/oauth/authorize") + .session(session) + .param(RESPONSE_TYPE, "code") + .param(STATE, state) + .param(CLIENT_ID, client1.getClientId())) + .andExpect(status().isFound()); //approval page no longer showing up + } + + @Test + public void test_get_approvals() throws Exception { + test_oauth_authorize_without_csrf(); + MockHttpSession session = getAuthenticatedSession(user1); + getMockMvc().perform( + get("/profile") + .session(session) + ) + .andExpect(status().isOk()) + .andExpect(content().string(containsString(client1.getClientId() + "-test.scope1"))); + } + + @Test + public void test_post_approval_csrf() throws Exception { + test_get_approvals(); + MockHttpSession session = getAuthenticatedSession(user1); + MockHttpServletRequestBuilder post = post("/profile") + .session(session) + .param("checkScopes", client1.getClientId() + "-test.scope1", client1.getClientId() + "-test.scope2"); + getMockMvc().perform( + post + ) + .andDo(print()) + .andExpect(status().isForbidden()); + + getMockMvc().perform( + post.with(cookieCsrf().useInvalidToken()) + ).andExpect(status().isForbidden()); + + getMockMvc().perform( + post.with(cookieCsrf()) + ) + .andExpect(status().isFound()) + .andExpect(redirectedUrlPattern("**/profile")); + } + + public MockHttpSession getAuthenticatedSession(ScimUser user) { + List<SimpleGrantedAuthority> authorities = user.getGroups().stream().map(g -> new SimpleGrantedAuthority(g.getValue())).collect(Collectors.toList()); + UaaPrincipal p = new UaaPrincipal(user.getId(), user.getUserName(), user.getPrimaryEmail(), OriginKeys.UAA, "", IdentityZoneHolder.get().getId()); + UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(p, "", authorities); + Assert.assertTrue(auth.isAuthenticated()); + SecurityContextHolder.getContext().setAuthentication(auth); + MockHttpSession session = new MockHttpSession(); + session.setAttribute( + HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, + new MockMvcUtils.MockSecurityContext(auth) + ); + return session; + } + +}
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java+1 −0 modified@@ -593,6 +593,7 @@ public void testUserCreatedEventDuringLoginServerAuthorize() throws Exception { "loginsecret", "oauth.login"); MockHttpServletRequestBuilder userPost = post("/oauth/authorize") + .with(cookieCsrf()) .accept(MediaType.APPLICATION_JSON_VALUE) .contentType(MediaType.APPLICATION_JSON) .header("Authorization", "Bearer " + loginToken)
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/AbstractTokenMockMvcTests.java+209 −0 added@@ -0,0 +1,209 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.mock.token; + +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; +import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; +import org.cloudfoundry.identity.uaa.oauth.UaaTokenServices; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.token.RevocableTokenProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +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.scim.exception.MemberAlreadyExistsException; +import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupMembershipManager; +import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupProvisioning; +import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimUserProvisioning; +import org.cloudfoundry.identity.uaa.test.TestClient; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; +import org.junit.Before; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; +import org.springframework.util.StringUtils; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public abstract class AbstractTokenMockMvcTests extends InjectedMockContextTest { + + public static final String SECRET = "secret"; + public static final String GRANT_TYPES = "password,implicit,client_credentials,authorization_code"; + public static final String TEST_REDIRECT_URI = "http://test.example.org/redirect"; + + protected TestClient testClient; + protected JdbcClientDetailsService clientDetailsService; + protected JdbcScimUserProvisioning userProvisioning; + protected JdbcScimGroupProvisioning groupProvisioning; + protected JdbcScimGroupMembershipManager groupMembershipManager; + protected UaaTokenServices tokenServices; + protected Set<String> defaultAuthorities; + + protected IdentityZoneProvisioning identityZoneProvisioning; + protected JdbcScimUserProvisioning jdbcScimUserProvisioning; + protected IdentityProviderProvisioning identityProviderProvisioning; + protected String adminToken; + protected RevocableTokenProvisioning tokenProvisioning; + + @Before + public void setUpContext() throws Exception { + testClient = new TestClient(getMockMvc()); + clientDetailsService = (JdbcClientDetailsService) getWebApplicationContext().getBean("jdbcClientDetailsService"); + userProvisioning = (JdbcScimUserProvisioning) getWebApplicationContext().getBean("scimUserProvisioning"); + groupProvisioning = (JdbcScimGroupProvisioning) getWebApplicationContext().getBean("scimGroupProvisioning"); + groupMembershipManager = (JdbcScimGroupMembershipManager) getWebApplicationContext().getBean("groupMembershipManager"); + tokenServices = (UaaTokenServices) getWebApplicationContext().getBean("tokenServices"); + defaultAuthorities = (Set<String>) getWebApplicationContext().getBean("defaultUserAuthorities"); + identityZoneProvisioning = getWebApplicationContext().getBean(IdentityZoneProvisioning.class); + jdbcScimUserProvisioning = getWebApplicationContext().getBean(JdbcScimUserProvisioning.class); + identityProviderProvisioning = getWebApplicationContext().getBean(IdentityProviderProvisioning.class); + IdentityZoneHolder.clear(); + + adminToken = MockMvcUtils.utils() + .getClientCredentialsOAuthAccessToken( + getMockMvc(), + "admin", + "adminsecret", + "uaa.admin", + null + ); + tokenProvisioning = (RevocableTokenProvisioning) getWebApplicationContext().getBean("revocableTokenProvisioning"); + } + + protected IdentityZone setupIdentityZone(String subdomain) { + IdentityZone zone = new IdentityZone(); + zone.getConfig().getTokenPolicy().setKeys(Collections.singletonMap(subdomain+"_key", "key_for_"+subdomain)); + zone.setId(UUID.randomUUID().toString()); + zone.setName(subdomain); + zone.setSubdomain(subdomain); + zone.setDescription(subdomain); + identityZoneProvisioning.create(zone); + return zone; + } + + protected IdentityProvider setupIdentityProvider() { + return setupIdentityProvider(OriginKeys.UAA); + } + protected IdentityProvider setupIdentityProvider(String origin) { + IdentityProvider defaultIdp = new IdentityProvider(); + defaultIdp.setName(origin); + defaultIdp.setType(origin); + defaultIdp.setOriginKey(origin); + defaultIdp.setIdentityZoneId(IdentityZoneHolder.get().getId()); + return identityProviderProvisioning.create(defaultIdp); + } + + protected BaseClientDetails setUpClients(String id, String authorities, String scopes, String grantTypes, Boolean autoapprove) { + return setUpClients(id, authorities, scopes, grantTypes, autoapprove, null); + } + protected BaseClientDetails setUpClients(String id, String authorities, String scopes, String grantTypes, Boolean autoapprove, String redirectUri) { + return setUpClients(id, authorities, scopes, grantTypes, autoapprove, redirectUri, null); + } + protected BaseClientDetails setUpClients(String id, String authorities, String scopes, String grantTypes, Boolean autoapprove, String redirectUri, List<String> allowedIdps) { + return setUpClients(id, authorities, scopes, grantTypes, autoapprove, redirectUri, allowedIdps, -1); + } + protected BaseClientDetails setUpClients(String id, String authorities, String scopes, String grantTypes, Boolean autoapprove, String redirectUri, List<String> allowedIdps, int accessTokenValidity) { + BaseClientDetails c = new BaseClientDetails(id, "", scopes, grantTypes, authorities); + if (!"implicit".equals(grantTypes)) { + c.setClientSecret(SECRET); + } + c.setRegisteredRedirectUri(new HashSet<>(Arrays.asList(TEST_REDIRECT_URI))); + c.setAutoApproveScopes(Collections.singleton(autoapprove.toString())); + Map<String, Object> additional = new HashMap<>(); + if (allowedIdps!=null && !allowedIdps.isEmpty()) { + additional.put(ClientConstants.ALLOWED_PROVIDERS, allowedIdps); + } + c.setAdditionalInformation(additional); + if (StringUtils.hasText(redirectUri)) { + c.setRegisteredRedirectUri(new HashSet<>(Arrays.asList(redirectUri))); + } + if (accessTokenValidity>0) { + c.setAccessTokenValiditySeconds(accessTokenValidity); + } + clientDetailsService.addClientDetails(c); + return (BaseClientDetails) clientDetailsService.loadClientByClientId(c.getClientId()); + } + + protected ScimUser setUpUser(String username, String scopes, String origin, String zoneId) { + ScimUser user = new ScimUser(null, username, "GivenName", "FamilyName"); + user.setPassword(SECRET); + ScimUser.Email email = new ScimUser.Email(); + email.setValue("test@test.org"); + email.setPrimary(true); + user.setEmails(Arrays.asList(email)); + user.setVerified(true); + user.setOrigin(origin); + + user = userProvisioning.createUser(user, SECRET); + + Set<String> scopeSet = StringUtils.commaDelimitedListToSet(scopes); + Set<ScimGroup> groups = new HashSet<>(); + for (String scope : scopeSet) { + ScimGroup g = createIfNotExist(scope,zoneId); + groups.add(g); + addMember(user, g); + } + + return userProvisioning.retrieve(user.getId()); + } + + protected ScimUser syncGroups(ScimUser user) { + if (user == null) { + return user; + } + + Set<ScimGroup> directGroups = groupMembershipManager.getGroupsWithMember(user.getId(), false); + Set<ScimGroup> indirectGroups = groupMembershipManager.getGroupsWithMember(user.getId(), true); + indirectGroups.removeAll(directGroups); + Set<ScimUser.Group> groups = new HashSet<ScimUser.Group>(); + for (ScimGroup group : directGroups) { + groups.add(new ScimUser.Group(group.getId(), group.getDisplayName(), ScimUser.Group.Type.DIRECT)); + } + for (ScimGroup group : indirectGroups) { + groups.add(new ScimUser.Group(group.getId(), group.getDisplayName(), ScimUser.Group.Type.INDIRECT)); + } + + user.setGroups(groups); + return user; + } + + protected ScimGroupMember addMember(ScimUser user, ScimGroup group) { + ScimGroupMember gm = new ScimGroupMember(user.getId()); + try { + return groupMembershipManager.addMember(group.getId(), gm); + }catch (MemberAlreadyExistsException x) { + return gm; + } + } + + protected ScimGroup createIfNotExist(String scope, String zoneId) { + List<ScimGroup> exists = groupProvisioning.query("displayName eq \"" + scope + "\" and identity_zone_id eq \""+zoneId+"\""); + if (exists.size() > 0) { + return exists.get(0); + } else { + return groupProvisioning.create(new ScimGroup(null,scope,zoneId)); + } + } +}
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java+4 −0 modified@@ -101,6 +101,7 @@ import java.util.TreeSet; import java.util.UUID; +import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.createClient; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.createUser; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.getClientCredentialsOAuthAccessToken; @@ -130,6 +131,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + public class TokenMvcMockTests extends InjectedMockContextTest { private String SECRET = "secret"; @@ -823,6 +825,7 @@ public void testOpenIdTokenHybridFlowWithNoImplicitGrantWhenLenientWhenAppNotApp MvcResult result = getMockMvc().perform( post("/oauth/authorize") .session(session) + .with(cookieCsrf()) .param(OAuth2Utils.USER_OAUTH_APPROVAL, "true") .param("scope.0","openid") ).andExpect(status().is3xxRedirection()).andReturn(); @@ -868,6 +871,7 @@ public void testOpenIdTokenHybridFlowWithNoImplicitGrantWhenStrictWhenAppNotAppr post("/oauth/authorize") .session(session) .param(OAuth2Utils.USER_OAUTH_APPROVAL, "true") + .with(cookieCsrf()) .param("scope.0", "openid") ).andExpect(status().is3xxRedirection()).andReturn();
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/UaaJunitSuiteRunner.java+5 −1 modified@@ -30,6 +30,7 @@ import java.lang.reflect.Modifier; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; /** * Suite that runs classes that extend the @@ -42,7 +43,10 @@ public class UaaJunitSuiteRunner extends Suite { protected static Class<?>[] allSuiteClasses() { Reflections reflections = new Reflections("org.cloudfoundry.identity.uaa"); - Set<Class<? extends InjectedMockContextTest>> subTypes = reflections.getSubTypesOf(InjectedMockContextTest.class); + Set<Class<? extends InjectedMockContextTest>> subTypes = + reflections.getSubTypesOf(InjectedMockContextTest.class).stream().filter( + c -> !Modifier.isAbstract(c.getModifiers()) + ).collect(Collectors.toSet()); return subTypes.toArray(new Class[subTypes.size()]); }
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java+18 −4 modified@@ -1017,20 +1017,34 @@ 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(); + String tokenValue = 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]; + addCsrfCookie(request, cookie, cookies); + } + request.setParameter(token.getParameterName(), useInvalidToken ? "invalid" + tokenValue : tokenValue); + return request; + } + + protected void addCsrfCookie(MockHttpServletRequest request, Cookie cookie, Cookie[] cookies) { + boolean replaced = false; + for (int i=0; i<cookies.length; i++) { + Cookie c = cookies[i]; + if (cookie.getName()==c.getName()) { + cookies[i] = cookie; + replaced = true; + } + } + if (!replaced) { + 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() {
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
8- github.com/advisories/GHSA-4m8c-h7fr-gq5cghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2016-6637ghsaADVISORY
- pivotal.io/security/cve-2016-6637nvdVendor AdvisoryWEB
- github.com/cloudfoundry/uaa/commit/37e0384c52d3337a3fa4eef6647032229edecfa4ghsaWEB
- github.com/cloudfoundry/uaa/commit/cded6164a3b90e791688a954069aea3cfde59b69ghsaWEB
- github.com/cloudfoundry/uaa/commit/f3d8a9e1ee1acac5bf1f8487ac9461f4cf4505cghsaWEB
- web.archive.org/web/20200227221542/http://www.securityfocus.com/bid/93245ghsaWEB
- www.securityfocus.com/bid/93245nvd
News mentions
0No linked articles in our index yet.