VYPR
Critical severity9.6NVD Advisory· Published Sep 30, 2016· Updated May 6, 2026

CVE-2016-6637

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.

PackageAffected versionsPatched versions
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven
>= 2.0.0, < 2.7.4.72.7.4.7
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven
>= 3.0.0, < 3.3.0.53.3.0.5
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven
>= 3.4.0, < 3.4.43.4.4
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven
>= 3.5.0, < 3.7.03.7.0

Patches

3
cded6164a3b9

Implement csrf on approvals pages

https://github.com/cloudfoundry/uaaFilip HanikAug 30, 2016via ghsa
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() {
    
f3d8a9e1ee1a

Add test for csrf token for approvals pages

https://github.com/cloudfoundry/uaaFilip HanikAug 30, 2016via ghsa
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() {
    
37e0384c52d3

Add test for csrf token for approvals pages

https://github.com/cloudfoundry/uaaFilip HanikAug 30, 2016via ghsa
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

News mentions

0

No linked articles in our index yet.