CVE-2017-4973
Description
An issue was discovered in Cloud Foundry Foundation cf-release versions prior to v257; UAA release 2.x versions prior to v2.7.4.14, 3.6.x versions prior to v3.6.8, 3.9.x versions prior to v3.9.10, and other versions prior to v3.15.0; and UAA bosh release (uaa-release) 13.x versions prior to v13.12, 24.x versions prior to v24.7, and other versions prior to v30. A vulnerability has been identified with the groups endpoint in UAA allowing users to elevate their privileges.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven | >= 2.0.0, < 2.7.4.14 | 2.7.4.14 |
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven | >= 3.0.0, < 3.6.8 | 3.6.8 |
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven | >= 3.7.0, < 3.9.10 | 3.9.10 |
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven | >= 3.10.0, < 3.15.0 | 3.15.0 |
Affected products
59cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:*:*:*:*:*:*:*:*+ 21 more
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:*:*:*:*:*:*:*:*range: <=30
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.1:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.10:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.11:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.2:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.3:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.4:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.5:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.6:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.7:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.8:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:13.9:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:24:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:24.1:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:24.2:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:24.3:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:24.4:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:24.5:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:24.6:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:30.1:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:30.2:*:*:*:*:*:*:*
- cpe:2.3:a:cloudfoundry:cloud_foundry_uaa_bosh:30.3:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_cf:*:*:*:*:*:*:*:*Range: <=256
cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.2.5.4:*:*:*:*:*:*:*+ 34 more
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.2.5.4:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.1:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.2:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.3:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.1:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.11:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.12:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.13:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.2:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.3:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.4:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.5:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.6:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.7:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.8:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:2.7.4.9:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.6.1:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.6.2:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.6.3:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.6.4:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.6.5:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.6.6:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.6.7:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.1:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.12:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.13:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.2:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.3:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.4:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.5:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.6:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.7:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.8:*:*:*:*:*:*:*
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:3.9.9:*:*:*:*:*:*:*
- Range: Cloud Foundry UAA
Patches
85 files changed · +86 −11
common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpoints.java+26 −11 modified@@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -12,15 +12,6 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.oauth.approval; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.client.ClientConstants; @@ -40,12 +31,28 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.stereotype.Controller; import org.springframework.util.Assert; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.View; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + @Controller public class ApprovalsAdminEndpoints implements InitializingBean, ApprovalsControllerService { @@ -170,6 +177,7 @@ public List<Approval> updateApprovals(@RequestBody Approval[] approvals) { @ResponseBody @Override public List<Approval> updateClientApprovals(@PathVariable String clientId, @RequestBody Approval[] approvals) { + clientDetailsService.loadClientByClientId(clientId); String currentUserId = getCurrentUserId(); logger.debug("Updating approvals for user: " + currentUserId); approvalStore.revokeApprovals(String.format(USER_AND_CLIENT_FILTER_TEMPLATE, currentUserId, clientId)); @@ -203,12 +211,19 @@ private boolean isValidUser(String userId) { @ResponseBody @Override public SimpleMessage revokeApprovals(@RequestParam(required = true) String clientId) { + clientDetailsService.loadClientByClientId(clientId); String username = getCurrentUserId(); logger.debug("Revoking all existing approvals for user: " + username + " and client " + clientId); approvalStore.revokeApprovals(String.format(USER_AND_CLIENT_FILTER_TEMPLATE, username, clientId)); return new SimpleMessage("ok", "Approvals of user " + username + " and client " + clientId + " revoked"); } + @ExceptionHandler + public View handleException(NoSuchClientException nsce) { + logger.debug("Client not found:" + nsce.getMessage()); + return handleException(new UaaException(nsce.getMessage(), 404)); + } + @ExceptionHandler public View handleException(Exception t) { UaaException e = t instanceof UaaException ? (UaaException) t : new UaaException("Unexpected error",
login/src/main/java/org/cloudfoundry/identity/uaa/login/ProfileController.java+13 −0 modified@@ -12,6 +12,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.login; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.client.ClientConstants; @@ -20,11 +22,15 @@ import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.servlet.View; +import org.springframework.web.servlet.view.RedirectView; import java.util.ArrayList; import java.util.Collection; @@ -35,6 +41,7 @@ @Controller public class ProfileController { + protected static Log logger = LogFactory.getLog(ProfileController.class); private final ApprovalsService approvalsService; private final ClientDetailsService clientDetailsService; @@ -113,4 +120,10 @@ private boolean isUaaManagedUser(Authentication authentication) { } return false; } + + @ExceptionHandler + public View handleException(NoSuchClientException nsce) { + logger.debug("Unable to find client for approvals:" + nsce.getMessage()); + return new RedirectView("profile?error_message_code=request.invalid_parameter", true); + } }
login/src/test/java/org/cloudfoundry/identity/uaa/login/ProfileControllerTests.java+14 −0 modified@@ -33,6 +33,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -108,6 +109,7 @@ public void setUp() throws Exception { approvalsByClientId.put("app", Arrays.asList(readApproval, writeApproval)); Mockito.when(approvalsService.getCurrentApprovalsByClientId()).thenReturn(approvalsByClientId); + Mockito.doThrow(new NoSuchClientException("invalidId")).when(approvalsService).deleteApprovalsForClient("invalidId"); BaseClientDetails appClient = new BaseClientDetails("app","thing","thing.read,thing.write","authorization_code", ""); appClient.addAdditionalInformation(ClientConstants.CLIENT_NAME, THE_ULTIMATE_APP); @@ -205,6 +207,18 @@ public void testUpdateProfile() throws Exception { Assert.assertEquals(DENIED, writeApproval.getStatus()); } + @Test + public void validate_client_id_on_revoke() throws Exception { + MockHttpServletRequestBuilder post = post("/profile") + .param("checkedScopes", "app-resource.read") + .param("delete", "") + .param("clientId", "invalidId"); + + mockMvc.perform(post) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("profile?error_message_code=request.invalid_parameter")); + } + @Test public void testRevokeApp() throws Exception { MockHttpServletRequestBuilder post = post("/profile")
uaa/src/main/resources/messages.properties+1 −0 modified@@ -53,6 +53,7 @@ login.account_not_verified=Your account is not verified. You can get another ver login.account_locked=Your account has been locked because of too many failed attempts to login. login.invalid_login_request=Invalid login attempt, request does not meet our security standards, please try again. account_activation.invite.email_mismatch=The authenticated email does not match the invited email. Please log in using a different account. +request.invalid_parameter=The request contains an invalid parameter that can not be processed. # Passay Properties HISTORY_VIOLATION=Password matches one of %1$s previous passwords.
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/approvals/ApprovalsMockMvcTests.java+32 −0 modified@@ -71,6 +71,7 @@ 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.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -188,6 +189,37 @@ public void test_oauth_authorize_without_csrf() throws Exception { .andExpect(status().isFound()); //approval page no longer showing up } + @Test + public void revoke() throws Exception { + test_oauth_authorize_without_csrf(); + MockHttpSession session = getAuthenticatedSession(user1); + getMockMvc().perform( + post("/profile") + .with(cookieCsrf()) + .param("delete","true") + .param("clientId", client1.getClientId()) + .session(session) + ) + .andExpect(status().isFound()) + .andExpect(header().string("Location", "profile")); + + } + + @Test + public void revoke_invalid_client() throws Exception { + test_oauth_authorize_without_csrf(); + MockHttpSession session = getAuthenticatedSession(user1); + getMockMvc().perform( + post("/profile") + .with(cookieCsrf()) + .param("delete","true") + .param("clientId", "invalid_id") + .session(session) + ) + .andExpect(status().isFound()) + .andExpect(header().string("Location", "profile?error_message_code=request.invalid_parameter")); + } + @Test public void test_get_approvals() throws Exception { test_oauth_authorize_without_csrf();
0762cc768592Remove non used endpoint
2 files changed · +22 −6
uaa/src/main/webapp/WEB-INF/spring/scim-endpoints.xml+1 −0 modified@@ -143,6 +143,7 @@ <intercept-url pattern="/Groups/External/**" access="#oauth2.hasScope('scim.write') or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="DELETE" /> <intercept-url pattern="/Groups/**" access="#oauth2.hasScope('scim.write') or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="DELETE" /> <intercept-url pattern="/Groups/**" access="#oauth2.hasAnyScope('scim.write', 'groups.update') or @groupRole.isGroupWriter(request, 1) or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="PUT" /> + <intercept-url pattern="/Groups/**" access="#oauth2.hasAnyScope('scim.write', 'groups.update') or @groupRole.isGroupWriter(request, 1) or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="POST" /> <intercept-url pattern="/Groups/**" access="#oauth2.hasScope('scim.read') or @groupRole.isGroupReader(request, 1) or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="GET" /> <intercept-url pattern="/Groups" access="#oauth2.hasScope('scim.write') or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="POST" /> <!--<intercept-url pattern="/**" access="ROLE_NONEXISTENT" />-->
uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java+21 −6 modified@@ -63,6 +63,7 @@ import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.utils; import static org.junit.Assert.assertTrue; import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; 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; @@ -82,6 +83,7 @@ public class ScimGroupEndpointsMockMvcTests extends InjectedMockContextTest { private RandomValueStringGenerator generator = new RandomValueStringGenerator(); private List<String> defaultExternalMembers; private List<ScimGroupExternalMember> databaseExternalMembers; + private TestClient testClient; @Before public void setUp() throws Exception { @@ -94,22 +96,35 @@ public void setUp() throws Exception { ScimExternalGroupBootstrap bootstrap = getWebApplicationContext().getBean(ScimExternalGroupBootstrap.class); bootstrap.afterPropertiesSet(); - TestClient testClient = new TestClient(getMockMvc()); + testClient = new TestClient(getMockMvc()); String adminToken = testClient.getClientCredentialsOAuthAccessToken("admin", "adminsecret", - "clients.read clients.write clients.secret clients.admin"); + "clients.read clients.write clients.secret clients.admin"); String clientId = generator.generate().toLowerCase(); String clientSecret = generator.generate().toLowerCase(); String authorities = "scim.read,scim.write,password.write,oauth.approvals,scim.create"; utils().createClient(this.getMockMvc(), adminToken, clientId, clientSecret, Collections.singletonList("oauth"), Collections.singletonList("foo,bar"), Collections.singletonList("client_credentials"), authorities); - scimReadToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret,"scim.read password.write"); - scimWriteToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret,"scim.write password.write"); + scimReadToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret, "scim.read password.write"); + scimWriteToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret, "scim.write password.write"); defaultExternalMembers = new LinkedList<>(originalDefaultExternalMembers); databaseExternalMembers = new LinkedList<>(originalDatabaseExternalMembers); scimUser = createUser(scimWriteToken, new HashSet(Arrays.asList("scim.read", "scim.write", "scim.me"))); - scimReadUserToken = testClient.getUserOAuthAccessToken("cf","", scimUser.getUserName(), "password", "scim.read"); - identityClientToken = testClient.getClientCredentialsOAuthAccessToken("identity","identitysecret",""); + scimReadUserToken = testClient.getUserOAuthAccessToken("cf", "", scimUser.getUserName(), "password", "scim.read"); + identityClientToken = testClient.getClientCredentialsOAuthAccessToken("identity", "identitysecret", ""); + } + + @Test + public void test_member_add() throws Exception { + String token = testClient.getUserOAuthAccessToken("cf","", scimUser.getUserName(), "password", "openid"); + String groupId = getGroupId("scim.read"); + ScimGroupMember scimGroupMember = new ScimGroupMember(scimUser.getId(), ScimGroupMember.Type.USER, Arrays.asList(ScimGroupMember.Role.MEMBER, ScimGroupMember.Role.READER)); + MockHttpServletRequestBuilder post = post("/Groups/" + groupId + "/members") + .header("Authorization", "Bearer " + token) + .header("Content-Type", APPLICATION_JSON_VALUE) + .content(JsonUtils.writeValueAsString(scimGroupMember)); + getMockMvc().perform(post) + .andExpect(status().isForbidden()); } @Test
18cf22ba9177Remove non used endpoint.
2 files changed · +34 −1
uaa/src/main/webapp/WEB-INF/spring/scim-endpoints.xml+1 −0 modified@@ -143,6 +143,7 @@ <intercept-url pattern="/Groups/External/**" access="#oauth2.hasScope('scim.write') or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="DELETE" /> <intercept-url pattern="/Groups/**" access="#oauth2.hasScope('scim.write') or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="DELETE" /> <intercept-url pattern="/Groups/**" access="#oauth2.hasAnyScope('scim.write', 'groups.update') or @groupRole.isGroupWriter(request, 1) or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="PUT" /> + <intercept-url pattern="/Groups/**" access="#oauth2.hasAnyScope('scim.write', 'groups.update') or @groupRole.isGroupWriter(request, 1) or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="POST" /> <intercept-url pattern="/Groups/**" access="#oauth2.hasScope('scim.read') or @groupRole.isGroupReader(request, 1) or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="GET" /> <intercept-url pattern="/Groups" access="#oauth2.hasScope('scim.write') or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="POST" /> <!--<intercept-url pattern="/**" access="ROLE_NONEXISTENT" />-->
uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java+33 −1 modified@@ -75,8 +75,11 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; 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.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.util.StringUtils.hasText; @@ -124,7 +127,7 @@ public void setUp() throws Exception { "clients.read clients.write clients.secret clients.admin"); clientId = generator.generate().toLowerCase(); clientSecret = generator.generate().toLowerCase(); - String authorities = "scim.read,scim.write,password.write,oauth.approvals,scim.create"; + String authorities = "scim.read,scim.write,password.write,oauth.approvals,scim.create,other.scope"; utils().createClient(this.getMockMvc(), adminToken, clientId, clientSecret, Collections.singleton("oauth"), Arrays.asList("foo","bar","scim.read"), Arrays.asList("client_credentials", "password"), authorities); scimReadToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret,"scim.read password.write"); scimWriteToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret,"scim.write password.write"); @@ -1118,6 +1121,35 @@ public void delete_nonexistent_user() throws Exception { .andExpect(status().isNotFound()); } + @Test + public void patch_has_one_path() throws Exception { + getMockMvc().perform( + patch("/Group/groupId/members") + .header("Authorization", "Bearer " + scimWriteToken) + .header("Content-Type", APPLICATION_JSON_VALUE) + ) + .andDo(print()) + .andExpect(status().isFound()) //gets caught by the ui filter for unknown URIs + .andExpect(redirectedUrl("http://localhost/login")); + } + + @Test + public void add_member_bad_token() throws Exception { + ScimUser user = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.EMPTY_SET); + String groupId = getGroupId("scim.read"); + String anyOldToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret,"other.scope"); + + ScimGroupMember scimGroupMember = new ScimGroupMember(user.getId(), ScimGroupMember.Type.USER, Arrays.asList(ScimGroupMember.Role.MEMBER, ScimGroupMember.Role.READER)); + + MockHttpServletRequestBuilder post = post("/Groups/" + groupId + "/members") + .header("Authorization", "Bearer " + anyOldToken) + .header("Content-Type", APPLICATION_JSON_VALUE) + .content(JsonUtils.writeValueAsString(scimGroupMember)); + getMockMvc().perform(post) + .andExpect(status().isForbidden()); + + } + @Test public void add_member_to_nonexistent_group() throws Exception { ScimUser user = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.EMPTY_SET);
52acfabd11c3Remove non used endpoint.
3 files changed · +35 −3
server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java+1 −2 modified@@ -385,8 +385,7 @@ public ScimGroup updateGroup(@RequestBody ScimGroup group, @PathVariable String } } - @RequestMapping(value = { "/Group/{groupId}", "/Groups/{groupId}" }, - method = RequestMethod.PATCH) + @RequestMapping(value = { "/Groups/{groupId}" }, method = RequestMethod.PATCH) @ResponseBody public ScimGroup patchGroup(@RequestBody ScimGroup patch, @PathVariable String groupId,
uaa/src/main/webapp/WEB-INF/spring/scim-endpoints.xml+1 −0 modified@@ -143,6 +143,7 @@ <intercept-url pattern="/Groups/External/**" access="#oauth2.hasScope('scim.write') or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="DELETE" /> <intercept-url pattern="/Groups/**" access="#oauth2.hasScope('scim.write') or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="DELETE" /> <intercept-url pattern="/Groups/**" access="#oauth2.hasAnyScope('scim.write', 'groups.update') or @groupRole.isGroupWriter(request, 1) or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="PUT" /> + <intercept-url pattern="/Groups/**" access="#oauth2.hasAnyScope('scim.write', 'groups.update') or @groupRole.isGroupWriter(request, 1) or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="POST" /> <intercept-url pattern="/Groups/**" access="#oauth2.hasScope('scim.read') or @groupRole.isGroupReader(request, 1) or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="GET" /> <intercept-url pattern="/Groups/**" access="#oauth2.hasAnyScope('scim.write', 'groups.update') or @groupRole.isGroupWriter(request, 1) or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="PATCH" /> <intercept-url pattern="/Groups" access="#oauth2.hasScope('scim.write') or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="POST" />
uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java+33 −1 modified@@ -75,8 +75,11 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; 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.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.util.StringUtils.hasText; @@ -122,7 +125,7 @@ public void setUp() throws Exception { "clients.read clients.write clients.secret clients.admin"); clientId = generator.generate().toLowerCase(); clientSecret = generator.generate().toLowerCase(); - String authorities = "scim.read,scim.write,password.write,oauth.approvals,scim.create"; + String authorities = "scim.read,scim.write,password.write,oauth.approvals,scim.create,other.scope"; utils().createClient(this.getMockMvc(), adminToken, clientId, clientSecret, Collections.singleton("oauth"), Arrays.asList("foo","bar","scim.read"), Arrays.asList("client_credentials", "password"), authorities); scimReadToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret,"scim.read password.write"); scimWriteToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret,"scim.write password.write"); @@ -1138,6 +1141,35 @@ public void delete_nonexistent_user() throws Exception { .andExpect(status().isNotFound()); } + @Test + public void patch_has_one_path() throws Exception { + getMockMvc().perform( + patch("/Group/groupId/members") + .header("Authorization", "Bearer " + scimWriteToken) + .header("Content-Type", APPLICATION_JSON_VALUE) + ) + .andDo(print()) + .andExpect(status().isFound()) //gets caught by the ui filter for unknown URIs + .andExpect(redirectedUrl("http://localhost/login")); + } + + @Test + public void add_member_bad_token() throws Exception { + ScimUser user = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.EMPTY_SET); + String groupId = getGroupId("scim.read"); + String anyOldToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret,"other.scope"); + + ScimGroupMember scimGroupMember = new ScimGroupMember(user.getId(), ScimGroupMember.Type.USER, Arrays.asList(ScimGroupMember.Role.MEMBER, ScimGroupMember.Role.READER)); + + MockHttpServletRequestBuilder post = post("/Groups/" + groupId + "/members") + .header("Authorization", "Bearer " + anyOldToken) + .header("Content-Type", APPLICATION_JSON_VALUE) + .content(JsonUtils.writeValueAsString(scimGroupMember)); + getMockMvc().perform(post) + .andExpect(status().isForbidden()); + + } + @Test public void add_member_to_nonexistent_group() throws Exception { ScimUser user = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.EMPTY_SET);
9d44cb0c7c25Remove non used endpoint.
3 files changed · +35 −3
server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java+1 −2 modified@@ -385,8 +385,7 @@ public ScimGroup updateGroup(@RequestBody ScimGroup group, @PathVariable String } } - @RequestMapping(value = { "/Group/{groupId}", "/Groups/{groupId}" }, - method = RequestMethod.PATCH) + @RequestMapping(value = { "/Groups/{groupId}" }, method = RequestMethod.PATCH) @ResponseBody public ScimGroup patchGroup(@RequestBody ScimGroup patch, @PathVariable String groupId,
uaa/src/main/webapp/WEB-INF/spring/scim-endpoints.xml+1 −0 modified@@ -144,6 +144,7 @@ <intercept-url pattern="/Groups/External/**" access="#oauth2.hasScope('scim.write') or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="DELETE" /> <intercept-url pattern="/Groups/**" access="#oauth2.hasScope('scim.write') or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="DELETE" /> <intercept-url pattern="/Groups/**" access="#oauth2.hasAnyScope('scim.write', 'groups.update') or @groupRole.isGroupWriter(request, 1) or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="PUT" /> + <intercept-url pattern="/Groups/**" access="#oauth2.hasAnyScope('scim.write', 'groups.update') or @groupRole.isGroupWriter(request, 1) or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="POST" /> <intercept-url pattern="/Groups/**" access="#oauth2.hasScope('scim.read') or @groupRole.isGroupReader(request, 1) or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="GET" /> <intercept-url pattern="/Groups/**" access="#oauth2.hasAnyScope('scim.write', 'groups.update') or @groupRole.isGroupWriter(request, 1) or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="PATCH" /> <intercept-url pattern="/Groups" access="#oauth2.hasScope('scim.write') or #oauth2.hasScopeInAuthZone('zones.{zone.id}.admin')" method="POST" />
uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java+33 −1 modified@@ -75,8 +75,11 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; 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.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.util.StringUtils.hasText; @@ -122,7 +125,7 @@ public void setUp() throws Exception { "clients.read clients.write clients.secret clients.admin"); clientId = generator.generate().toLowerCase(); clientSecret = generator.generate().toLowerCase(); - String authorities = "scim.read,scim.write,password.write,oauth.approvals,scim.create"; + String authorities = "scim.read,scim.write,password.write,oauth.approvals,scim.create,other.scope"; utils().createClient(this.getMockMvc(), adminToken, clientId, clientSecret, Collections.singleton("oauth"), Arrays.asList("foo","bar","scim.read"), Arrays.asList("client_credentials", "password"), authorities); scimReadToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret,"scim.read password.write"); scimWriteToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret,"scim.write password.write"); @@ -1138,6 +1141,35 @@ public void delete_nonexistent_user() throws Exception { .andExpect(status().isNotFound()); } + @Test + public void patch_has_one_path() throws Exception { + getMockMvc().perform( + patch("/Group/groupId/members") + .header("Authorization", "Bearer " + scimWriteToken) + .header("Content-Type", APPLICATION_JSON_VALUE) + ) + .andDo(print()) + .andExpect(status().isFound()) //gets caught by the ui filter for unknown URIs + .andExpect(redirectedUrl("http://localhost/login")); + } + + @Test + public void add_member_bad_token() throws Exception { + ScimUser user = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.EMPTY_SET); + String groupId = getGroupId("scim.read"); + String anyOldToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret,"other.scope"); + + ScimGroupMember scimGroupMember = new ScimGroupMember(user.getId(), ScimGroupMember.Type.USER, Arrays.asList(ScimGroupMember.Role.MEMBER, ScimGroupMember.Role.READER)); + + MockHttpServletRequestBuilder post = post("/Groups/" + groupId + "/members") + .header("Authorization", "Bearer " + anyOldToken) + .header("Content-Type", APPLICATION_JSON_VALUE) + .content(JsonUtils.writeValueAsString(scimGroupMember)); + getMockMvc().perform(post) + .andExpect(status().isForbidden()); + + } + @Test public void add_member_to_nonexistent_group() throws Exception { ScimUser user = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.EMPTY_SET);
24c270ce725dValidate client ID for approvals
4 files changed · +117 −38
server/src/main/java/org/cloudfoundry/identity/uaa/account/ProfileController.java+18 −3 modified@@ -12,21 +12,27 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.account; -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.cloudfoundry.identity.uaa.approval.Approval; import org.cloudfoundry.identity.uaa.approval.ApprovalsService; import org.cloudfoundry.identity.uaa.approval.DescribedApproval; -import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.approval.Approval; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.servlet.View; +import org.springframework.web.servlet.view.RedirectView; import java.util.ArrayList; import java.util.Collection; @@ -37,6 +43,8 @@ @Controller public class ProfileController { + protected static Log logger = LogFactory.getLog(ProfileController.class); + private final ApprovalsService approvalsService; private final ClientDetailsService clientDetailsService; @@ -108,6 +116,13 @@ else if (null != delete) { return "redirect:profile"; } + @ExceptionHandler + public View handleException(NoSuchClientException nsce) { + logger.debug("Unable to find client for approvals:"+nsce.getMessage()); + return new RedirectView("profile?error_message_code=request.invalid_parameter", true); + } + + private boolean isUaaManagedUser(Authentication authentication) { if (authentication.getPrincipal() instanceof UaaPrincipal) { UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal();
server/src/main/java/org/cloudfoundry/identity/uaa/approval/ApprovalsAdminEndpoints.java+26 −14 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,41 +12,45 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.approval; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; -import org.cloudfoundry.identity.uaa.web.ConvertingExceptionView; -import org.cloudfoundry.identity.uaa.web.ExceptionReport; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.resources.ActionResult; import org.cloudfoundry.identity.uaa.security.DefaultSecurityContextAccessor; import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.UaaPagingUtils; +import org.cloudfoundry.identity.uaa.web.ConvertingExceptionView; +import org.cloudfoundry.identity.uaa.web.ExceptionReport; import org.springframework.beans.factory.InitializingBean; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.stereotype.Controller; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.View; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + @Controller public class ApprovalsAdminEndpoints implements InitializingBean, ApprovalsControllerService { @@ -168,6 +172,7 @@ public List<Approval> updateApprovals(@RequestBody Approval[] approvals) { @ResponseBody @Override public List<Approval> updateClientApprovals(@PathVariable String clientId, @RequestBody Approval[] approvals) { + clientDetailsService.loadClientByClientId(clientId); String currentUserId = getCurrentUserId(); logger.debug("Updating approvals for user: " + currentUserId); approvalStore.revokeApprovals(String.format(USER_AND_CLIENT_FILTER_TEMPLATE, currentUserId, clientId)); @@ -202,10 +207,17 @@ private boolean isValidUser(String userId) { public ActionResult revokeApprovals(@RequestParam(required = true) String clientId) { String username = getCurrentUserId(); logger.debug("Revoking all existing approvals for user: " + username + " and client " + clientId); + clientDetailsService.loadClientByClientId(clientId); approvalStore.revokeApprovals(String.format(USER_AND_CLIENT_FILTER_TEMPLATE, username, clientId)); return new ActionResult("ok", "Approvals of user " + username + " and client " + clientId + " revoked"); } + @ExceptionHandler + public View handleException(NoSuchClientException nsce) { + logger.debug("Client not found:"+nsce.getMessage()); + return handleException(new UaaException(nsce.getMessage(), 404)); + } + @ExceptionHandler public View handleException(Exception t) { UaaException e = t instanceof UaaException ? (UaaException) t : new UaaException("Unexpected error",
server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java+41 −21 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,30 +12,12 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.oauth.approval; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.APPROVED; -import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.DENIED; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - import com.fasterxml.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus; import org.cloudfoundry.identity.uaa.approval.ApprovalsAdminEndpoints; import org.cloudfoundry.identity.uaa.approval.JdbcApprovalStore; -import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.error.UaaException; -import org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus; import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.resources.jdbc.SimpleSearchQueryConverter; import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor; @@ -48,14 +30,34 @@ import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.APPROVED; +import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.DENIED; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + public class ApprovalsAdminEndpointsTests extends JdbcTestBase { private UaaTestAccounts testAccounts = null; - + private JdbcApprovalStore dao; private UaaUserDatabase userDao = null; @@ -64,6 +66,9 @@ public class ApprovalsAdminEndpointsTests extends JdbcTestBase { private ApprovalsAdminEndpoints endpoints; + @Rule + public ExpectedException exception = ExpectedException.none(); + @Before public void initApprovalsAdminEndpointsTests() { testAccounts = UaaTestAccounts.standard(null); @@ -114,6 +119,21 @@ public void cleanupDataSource() throws Exception { assertThat(jdbcTemplate.queryForObject("select count(*) from users", Integer.class), is(0)); } + @Test + public void validate_client_id_on_revoke() throws Exception { + exception.expect(NoSuchClientException.class); + exception.expectMessage("No client with requested id: invalid_id"); + endpoints.revokeApprovals("invalid_id"); + } + + @Test + public void validate_client_id_on_update() throws Exception { + exception.expect(NoSuchClientException.class); + exception.expectMessage("No client with requested id: invalid_id"); + endpoints.updateClientApprovals("invalid_id", new Approval[0]); + } + + @Test public void canGetApprovals() { addApproval(marissa.getId(), "c1", "uaa.user", 6000, APPROVED);
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/approvals/ApprovalsMockMvcTests.java+32 −0 modified@@ -50,6 +50,7 @@ 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.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -128,6 +129,37 @@ public void test_oauth_authorize_without_csrf() throws Exception { .andExpect(status().isFound()); //approval page no longer showing up } + @Test + public void revoke() throws Exception { + test_oauth_authorize_without_csrf(); + MockHttpSession session = getAuthenticatedSession(user1); + getMockMvc().perform( + post("/profile") + .with(cookieCsrf()) + .param("delete","true") + .param("clientId", client1.getClientId()) + .session(session) + ) + .andExpect(status().isFound()) + .andExpect(header().string("Location", "profile")); + + } + + @Test + public void revoke_invalid_client() throws Exception { + test_oauth_authorize_without_csrf(); + MockHttpSession session = getAuthenticatedSession(user1); + getMockMvc().perform( + post("/profile") + .with(cookieCsrf()) + .param("delete","true") + .param("clientId", "invalid_id") + .session(session) + ) + .andExpect(status().isFound()) + .andExpect(header().string("Location", "profile?error_message_code=request.invalid_parameter")); + } + @Test public void test_get_approvals() throws Exception { test_oauth_authorize_without_csrf();
5eb43757d5a3Validate client ID for approvals
4 files changed · +117 −38
server/src/main/java/org/cloudfoundry/identity/uaa/account/ProfileController.java+18 −3 modified@@ -12,21 +12,27 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.account; -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.cloudfoundry.identity.uaa.approval.Approval; import org.cloudfoundry.identity.uaa.approval.ApprovalsService; import org.cloudfoundry.identity.uaa.approval.DescribedApproval; -import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.approval.Approval; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.servlet.View; +import org.springframework.web.servlet.view.RedirectView; import java.util.ArrayList; import java.util.Collection; @@ -37,6 +43,8 @@ @Controller public class ProfileController { + protected static Log logger = LogFactory.getLog(ProfileController.class); + private final ApprovalsService approvalsService; private final ClientDetailsService clientDetailsService; @@ -108,6 +116,13 @@ else if (null != delete) { return "redirect:profile"; } + @ExceptionHandler + public View handleException(NoSuchClientException nsce) { + logger.debug("Unable to find client for approvals:"+nsce.getMessage()); + return new RedirectView("profile?error_message_code=request.invalid_parameter", true); + } + + private boolean isUaaManagedUser(Authentication authentication) { if (authentication.getPrincipal() instanceof UaaPrincipal) { UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal();
server/src/main/java/org/cloudfoundry/identity/uaa/approval/ApprovalsAdminEndpoints.java+26 −14 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,41 +12,45 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.approval; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; -import org.cloudfoundry.identity.uaa.web.ConvertingExceptionView; -import org.cloudfoundry.identity.uaa.web.ExceptionReport; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.resources.ActionResult; import org.cloudfoundry.identity.uaa.security.DefaultSecurityContextAccessor; import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.UaaPagingUtils; +import org.cloudfoundry.identity.uaa.web.ConvertingExceptionView; +import org.cloudfoundry.identity.uaa.web.ExceptionReport; import org.springframework.beans.factory.InitializingBean; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.stereotype.Controller; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.View; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + @Controller public class ApprovalsAdminEndpoints implements InitializingBean, ApprovalsControllerService { @@ -168,6 +172,7 @@ public List<Approval> updateApprovals(@RequestBody Approval[] approvals) { @ResponseBody @Override public List<Approval> updateClientApprovals(@PathVariable String clientId, @RequestBody Approval[] approvals) { + clientDetailsService.loadClientByClientId(clientId); String currentUserId = getCurrentUserId(); logger.debug("Updating approvals for user: " + currentUserId); approvalStore.revokeApprovals(String.format(USER_AND_CLIENT_FILTER_TEMPLATE, currentUserId, clientId)); @@ -202,10 +207,17 @@ private boolean isValidUser(String userId) { public ActionResult revokeApprovals(@RequestParam(required = true) String clientId) { String username = getCurrentUserId(); logger.debug("Revoking all existing approvals for user: " + username + " and client " + clientId); + clientDetailsService.loadClientByClientId(clientId); approvalStore.revokeApprovals(String.format(USER_AND_CLIENT_FILTER_TEMPLATE, username, clientId)); return new ActionResult("ok", "Approvals of user " + username + " and client " + clientId + " revoked"); } + @ExceptionHandler + public View handleException(NoSuchClientException nsce) { + logger.debug("Client not found:"+nsce.getMessage()); + return handleException(new UaaException(nsce.getMessage(), 404)); + } + @ExceptionHandler public View handleException(Exception t) { UaaException e = t instanceof UaaException ? (UaaException) t : new UaaException("Unexpected error",
server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java+41 −21 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,30 +12,12 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.oauth.approval; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.APPROVED; -import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.DENIED; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - import com.fasterxml.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus; import org.cloudfoundry.identity.uaa.approval.ApprovalsAdminEndpoints; import org.cloudfoundry.identity.uaa.approval.JdbcApprovalStore; -import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.error.UaaException; -import org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus; import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.resources.jdbc.SimpleSearchQueryConverter; import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor; @@ -48,14 +30,34 @@ import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.APPROVED; +import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.DENIED; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + public class ApprovalsAdminEndpointsTests extends JdbcTestBase { private UaaTestAccounts testAccounts = null; - + private JdbcApprovalStore dao; private UaaUserDatabase userDao = null; @@ -64,6 +66,9 @@ public class ApprovalsAdminEndpointsTests extends JdbcTestBase { private ApprovalsAdminEndpoints endpoints; + @Rule + public ExpectedException exception = ExpectedException.none(); + @Before public void initApprovalsAdminEndpointsTests() { testAccounts = UaaTestAccounts.standard(null); @@ -114,6 +119,21 @@ public void cleanupDataSource() throws Exception { assertThat(jdbcTemplate.queryForObject("select count(*) from users", Integer.class), is(0)); } + @Test + public void validate_client_id_on_revoke() throws Exception { + exception.expect(NoSuchClientException.class); + exception.expectMessage("No client with requested id: invalid_id"); + endpoints.revokeApprovals("invalid_id"); + } + + @Test + public void validate_client_id_on_update() throws Exception { + exception.expect(NoSuchClientException.class); + exception.expectMessage("No client with requested id: invalid_id"); + endpoints.updateClientApprovals("invalid_id", new Approval[0]); + } + + @Test public void canGetApprovals() { addApproval(marissa.getId(), "c1", "uaa.user", 6000, APPROVED);
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/approvals/ApprovalsMockMvcTests.java+32 −0 modified@@ -50,6 +50,7 @@ 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.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -128,6 +129,37 @@ public void test_oauth_authorize_without_csrf() throws Exception { .andExpect(status().isFound()); //approval page no longer showing up } + @Test + public void revoke() throws Exception { + test_oauth_authorize_without_csrf(); + MockHttpSession session = getAuthenticatedSession(user1); + getMockMvc().perform( + post("/profile") + .with(cookieCsrf()) + .param("delete","true") + .param("clientId", client1.getClientId()) + .session(session) + ) + .andExpect(status().isFound()) + .andExpect(header().string("Location", "profile")); + + } + + @Test + public void revoke_invalid_client() throws Exception { + test_oauth_authorize_without_csrf(); + MockHttpSession session = getAuthenticatedSession(user1); + getMockMvc().perform( + post("/profile") + .with(cookieCsrf()) + .param("delete","true") + .param("clientId", "invalid_id") + .session(session) + ) + .andExpect(status().isFound()) + .andExpect(header().string("Location", "profile?error_message_code=request.invalid_parameter")); + } + @Test public void test_get_approvals() throws Exception { test_oauth_authorize_without_csrf();
3c456f0285e9Validate client ID for approvals
4 files changed · +117 −38
server/src/main/java/org/cloudfoundry/identity/uaa/account/ProfileController.java+18 −3 modified@@ -12,21 +12,27 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.account; -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.cloudfoundry.identity.uaa.approval.Approval; import org.cloudfoundry.identity.uaa.approval.ApprovalsService; import org.cloudfoundry.identity.uaa.approval.DescribedApproval; -import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.approval.Approval; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.servlet.View; +import org.springframework.web.servlet.view.RedirectView; import java.util.ArrayList; import java.util.Collection; @@ -37,6 +43,8 @@ @Controller public class ProfileController { + protected static Log logger = LogFactory.getLog(ProfileController.class); + private final ApprovalsService approvalsService; private final ClientDetailsService clientDetailsService; @@ -108,6 +116,13 @@ else if (null != delete) { return "redirect:profile"; } + @ExceptionHandler + public View handleException(NoSuchClientException nsce) { + logger.debug("Unable to find client for approvals:"+nsce.getMessage()); + return new RedirectView("profile?error_message_code=request.invalid_parameter", true); + } + + private boolean isUaaManagedUser(Authentication authentication) { if (authentication.getPrincipal() instanceof UaaPrincipal) { UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal();
server/src/main/java/org/cloudfoundry/identity/uaa/approval/ApprovalsAdminEndpoints.java+26 −14 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,41 +12,45 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.approval; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; -import org.cloudfoundry.identity.uaa.web.ConvertingExceptionView; -import org.cloudfoundry.identity.uaa.web.ExceptionReport; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.resources.ActionResult; import org.cloudfoundry.identity.uaa.security.DefaultSecurityContextAccessor; import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.UaaPagingUtils; +import org.cloudfoundry.identity.uaa.web.ConvertingExceptionView; +import org.cloudfoundry.identity.uaa.web.ExceptionReport; import org.springframework.beans.factory.InitializingBean; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.stereotype.Controller; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.View; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + @Controller public class ApprovalsAdminEndpoints implements InitializingBean, ApprovalsControllerService { @@ -168,6 +172,7 @@ public List<Approval> updateApprovals(@RequestBody Approval[] approvals) { @ResponseBody @Override public List<Approval> updateClientApprovals(@PathVariable String clientId, @RequestBody Approval[] approvals) { + clientDetailsService.loadClientByClientId(clientId); String currentUserId = getCurrentUserId(); logger.debug("Updating approvals for user: " + currentUserId); approvalStore.revokeApprovals(String.format(USER_AND_CLIENT_FILTER_TEMPLATE, currentUserId, clientId)); @@ -202,10 +207,17 @@ private boolean isValidUser(String userId) { public ActionResult revokeApprovals(@RequestParam(required = true) String clientId) { String username = getCurrentUserId(); logger.debug("Revoking all existing approvals for user: " + username + " and client " + clientId); + clientDetailsService.loadClientByClientId(clientId); approvalStore.revokeApprovals(String.format(USER_AND_CLIENT_FILTER_TEMPLATE, username, clientId)); return new ActionResult("ok", "Approvals of user " + username + " and client " + clientId + " revoked"); } + @ExceptionHandler + public View handleException(NoSuchClientException nsce) { + logger.debug("Client not found:"+nsce.getMessage()); + return handleException(new UaaException(nsce.getMessage(), 404)); + } + @ExceptionHandler public View handleException(Exception t) { UaaException e = t instanceof UaaException ? (UaaException) t : new UaaException("Unexpected error",
server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java+41 −21 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,30 +12,12 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.oauth.approval; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.APPROVED; -import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.DENIED; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - import com.fasterxml.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus; import org.cloudfoundry.identity.uaa.approval.ApprovalsAdminEndpoints; import org.cloudfoundry.identity.uaa.approval.JdbcApprovalStore; -import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.error.UaaException; -import org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus; import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.resources.jdbc.SimpleSearchQueryConverter; import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor; @@ -48,14 +30,34 @@ import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.APPROVED; +import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.DENIED; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + public class ApprovalsAdminEndpointsTests extends JdbcTestBase { private UaaTestAccounts testAccounts = null; - + private JdbcApprovalStore dao; private UaaUserDatabase userDao = null; @@ -64,6 +66,9 @@ public class ApprovalsAdminEndpointsTests extends JdbcTestBase { private ApprovalsAdminEndpoints endpoints; + @Rule + public ExpectedException exception = ExpectedException.none(); + @Before public void initApprovalsAdminEndpointsTests() { testAccounts = UaaTestAccounts.standard(null); @@ -114,6 +119,21 @@ public void cleanupDataSource() throws Exception { assertThat(jdbcTemplate.queryForObject("select count(*) from users", Integer.class), is(0)); } + @Test + public void validate_client_id_on_revoke() throws Exception { + exception.expect(NoSuchClientException.class); + exception.expectMessage("No client with requested id: invalid_id"); + endpoints.revokeApprovals("invalid_id"); + } + + @Test + public void validate_client_id_on_update() throws Exception { + exception.expect(NoSuchClientException.class); + exception.expectMessage("No client with requested id: invalid_id"); + endpoints.updateClientApprovals("invalid_id", new Approval[0]); + } + + @Test public void canGetApprovals() { addApproval(marissa.getId(), "c1", "uaa.user", 6000, APPROVED);
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/approvals/ApprovalsMockMvcTests.java+32 −0 modified@@ -50,6 +50,7 @@ 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.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -128,6 +129,37 @@ public void test_oauth_authorize_without_csrf() throws Exception { .andExpect(status().isFound()); //approval page no longer showing up } + @Test + public void revoke() throws Exception { + test_oauth_authorize_without_csrf(); + MockHttpSession session = getAuthenticatedSession(user1); + getMockMvc().perform( + post("/profile") + .with(cookieCsrf()) + .param("delete","true") + .param("clientId", client1.getClientId()) + .session(session) + ) + .andExpect(status().isFound()) + .andExpect(header().string("Location", "profile")); + + } + + @Test + public void revoke_invalid_client() throws Exception { + test_oauth_authorize_without_csrf(); + MockHttpSession session = getAuthenticatedSession(user1); + getMockMvc().perform( + post("/profile") + .with(cookieCsrf()) + .param("delete","true") + .param("clientId", "invalid_id") + .session(session) + ) + .andExpect(status().isFound()) + .andExpect(header().string("Location", "profile?error_message_code=request.invalid_parameter")); + } + @Test public void test_get_approvals() throws Exception { test_oauth_authorize_without_csrf();
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
12- github.com/advisories/GHSA-pgjc-gc7g-p2c6ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2017-4973ghsaADVISORY
- www.cloudfoundry.org/cve-2017-4973/nvdVendor Advisory
- github.com/cloudfoundry/uaa/commit/0762cc768592abc4fb1c6afd9974ea6fb964f0f2ghsaWEB
- github.com/cloudfoundry/uaa/commit/18cf22ba9177f1124f85f99651b474b48f12cd28ghsaWEB
- github.com/cloudfoundry/uaa/commit/24bc5ade80560cedb9300940d2b398163ab0dc6ghsaWEB
- github.com/cloudfoundry/uaa/commit/24c270ce725df890727b2bd7d8a4f338a3a58b7ghsaWEB
- github.com/cloudfoundry/uaa/commit/3c456f0285e92713a0a9ce54c3e57d8636b9183cghsaWEB
- github.com/cloudfoundry/uaa/commit/52acfabd11c3c77c2a3f5229b32f56de0e8d26adghsaWEB
- github.com/cloudfoundry/uaa/commit/5eb43757d5a3a2c9e7aae1ef3d0b9b7e2a38851eghsaWEB
- github.com/cloudfoundry/uaa/commit/9d44cb0c7c25ccae95bfa1c2d59ce46200c643cbghsaWEB
- www.cloudfoundry.org/cve-2017-4973ghsaWEB
News mentions
0No linked articles in our index yet.