/******************************************************************************* * 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.client; import org.cloudfoundry.identity.uaa.approval.ApprovalStore; import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent; import org.cloudfoundry.identity.uaa.audit.event.SystemDeletable; import org.cloudfoundry.identity.uaa.client.ClientDetailsValidator.Mode; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.oauth.client.ClientDetailsModification; import org.cloudfoundry.identity.uaa.oauth.client.SecretChangeRequest; import org.cloudfoundry.identity.uaa.resources.QueryableResourceManager; import org.cloudfoundry.identity.uaa.resources.ResourceMonitor; import org.cloudfoundry.identity.uaa.resources.SearchResults; import org.cloudfoundry.identity.uaa.resources.SimpleAttributeNameMapper; import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor; import org.cloudfoundry.identity.uaa.security.StubSecurityContextAccessor; import org.cloudfoundry.identity.uaa.zone.ClientServicesExtension; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.oauth2.common.exceptions.BadClientCredentialsException; import org.springframework.security.oauth2.provider.ClientAlreadyExistsException; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import java.util.Arrays; import java.util.Collection; 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 static org.cloudfoundry.identity.uaa.oauth.client.SecretChangeRequest.ChangeMode.ADD; import static org.cloudfoundry.identity.uaa.oauth.client.SecretChangeRequest.ChangeMode.DELETE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; public class ClientAdminEndpointsTests { private ClientAdminEndpoints endpoints = null; private BaseClientDetails input = null; private ClientDetailsModification[] inputs = new ClientDetailsModification[5]; private BaseClientDetails detail = null; private BaseClientDetails[] details = new BaseClientDetails[inputs.length]; private QueryableResourceManager<ClientDetails> clientDetailsService = null; private ClientMetadataProvisioning clientMetadataProvisioning = null; private SecurityContextAccessor securityContextAccessor = null; private ClientServicesExtension clientRegistrationService = null; private AuthenticationManager authenticationManager = null; private ApprovalStore approvalStore = null; private ClientAdminEndpointsValidator clientDetailsValidator = null; private static final Set<String> SINGLE_REDIRECT_URL = Collections.singleton("http://redirect.url"); @Rule public ExpectedException expected = ExpectedException.none(); private ResourceMonitor<ClientDetails> clientDetailsResourceMonitor; private static abstract class NoOpClientDetailsResourceManager implements QueryableResourceManager<ClientDetails> { @Override public ClientDetails create(ClientDetails resource) { Map<String, Object> additionalInformation = new HashMap<>(resource.getAdditionalInformation()); additionalInformation.put("lastModified", 1463510591); BaseClientDetails altered = new BaseClientDetails(resource); altered.setAdditionalInformation(additionalInformation); return altered; } } @Before public void setUp() throws Exception { endpoints = spy(new ClientAdminEndpoints()); clientDetailsService = Mockito.mock(NoOpClientDetailsResourceManager.class); when(clientDetailsService.create(any(ClientDetails.class))).thenCallRealMethod(); clientDetailsResourceMonitor = Mockito.mock(ResourceMonitor.class); securityContextAccessor = Mockito.mock(SecurityContextAccessor.class); clientRegistrationService = Mockito.mock(ClientServicesExtension.class, withSettings().extraInterfaces(SystemDeletable.class)); authenticationManager = Mockito.mock(AuthenticationManager.class); approvalStore = mock(ApprovalStore.class); clientDetailsValidator = new ClientAdminEndpointsValidator(); clientMetadataProvisioning = mock(ClientMetadataProvisioning.class); clientDetailsValidator.setClientDetailsService(clientDetailsService); clientDetailsValidator.setSecurityContextAccessor(securityContextAccessor); endpoints.setClientDetailsService(clientDetailsService); endpoints.setClientRegistrationService(clientRegistrationService); endpoints.setSecurityContextAccessor(securityContextAccessor); endpoints.setAuthenticationManager(authenticationManager); endpoints.setApprovalStore(approvalStore); endpoints.setClientDetailsValidator(clientDetailsValidator); endpoints.setRestrictedScopesValidator(new RestrictUaaScopesClientValidator(new UaaScopes())); endpoints.setClientDetailsResourceMonitor(clientDetailsResourceMonitor); Map<String, String> attributeNameMap = new HashMap<String, String>(); attributeNameMap.put("client_id", "clientId"); attributeNameMap.put("resource_ids", "resourceIds"); attributeNameMap.put("authorized_grant_types", "authorizedGrantTypes"); attributeNameMap.put("redirect_uri", "registeredRedirectUri"); attributeNameMap.put("access_token_validity", "accessTokenValiditySeconds"); attributeNameMap.put("refresh_token_validity", "refreshTokenValiditySeconds"); attributeNameMap.put("autoapprove", "autoApproveScopes"); attributeNameMap.put("additionalinformation", "additionalInformation"); endpoints.setAttributeNameMapper(new SimpleAttributeNameMapper(attributeNameMap)); input = new BaseClientDetails(); input.setClientId("foo"); input.setClientSecret("secret"); input.setAuthorizedGrantTypes(Arrays.asList("authorization_code")); input.setRegisteredRedirectUri(SINGLE_REDIRECT_URL); for (int i=0; i<inputs.length; i++) { inputs[i] = new ClientDetailsModification(); inputs[i].setClientId("foo-"+i); inputs[i].setClientSecret("secret-"+i); inputs[i].setAuthorizedGrantTypes(Arrays.asList("authorization_code")); inputs[i].setRegisteredRedirectUri(new HashSet(Arrays.asList("https://foo-"+i))); inputs[i].setAccessTokenValiditySeconds(300); } detail = new BaseClientDetails(input); detail.setResourceIds(Arrays.asList("none")); // refresh token is added automatically by endpoint validation detail.setAuthorizedGrantTypes(Arrays.asList("authorization_code", "refresh_token")); detail.setScope(Arrays.asList("uaa.none")); detail.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList("uaa.none")); for (int i=0; i<details.length; i++) { details[i] = new BaseClientDetails(inputs[i]); details[i].setResourceIds(Arrays.asList("none")); // refresh token is added automatically by endpoint validation details[i].setAuthorizedGrantTypes(Arrays.asList("authorization_code", "refresh_token")); details[i].setScope(Arrays.asList("uaa.none")); details[i].setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList("uaa.none")); } endpoints.setApplicationEventPublisher( new ApplicationEventPublisher() { @Override public void publishEvent(ApplicationEvent event) { if (event instanceof EntityDeletedEvent) { ClientDetails client = (ClientDetails)((EntityDeletedEvent)event).getDeleted(); clientRegistrationService.removeClientDetails(client.getClientId()); } } @Override public void publishEvent(Object event) {} } ); endpoints.afterPropertiesSet(); } private void setSecurityContextAccessor(SecurityContextAccessor securityContextAccessor) { endpoints.setSecurityContextAccessor(securityContextAccessor); clientDetailsValidator.setSecurityContextAccessor(securityContextAccessor); } @Test public void testValidateClientsTransferAutoApproveScopeSet() throws Exception { List<String> scopes = Arrays.asList("scope1", "scope2"); input.setAutoApproveScopes(new HashSet<String>(scopes)); ClientDetails test = endpoints.getClientDetailsValidator().validate(input, Mode.CREATE); for (String scope:scopes) { assertTrue("Client should have "+scope+" autoapprove.", test.isAutoApprove(scope)); } } @Test public void testAccessors() throws Exception { ApprovalStore as = mock(ApprovalStore.class); endpoints.setApprovalStore(as); assertSame(as, endpoints.getApprovalStore()); } @Test(expected = UnsupportedOperationException.class) public void testNoApprovalStore() { endpoints.setApprovalStore(null); endpoints.deleteApprovals("someclient"); } @Test public void testStatistics() throws Exception { assertEquals(0, endpoints.getClientDeletes()); assertEquals(0, endpoints.getClientSecretChanges()); assertEquals(0, endpoints.getClientUpdates()); assertEquals(0, endpoints.getErrorCounts().size()); assertEquals(0, endpoints.getTotalClients()); } @Test public void testCreateClientDetails() throws Exception { when(clientDetailsService.retrieve(anyString())).thenReturn(input); ClientDetails result = endpoints.createClientDetails(input); assertNull(result.getClientSecret()); verify(clientDetailsService).create(detail); assertEquals(1463510591, result.getAdditionalInformation().get("lastModified")); } @Test public void test_Get_Restricted_Scopes_List() throws Exception { assertEquals(new UaaScopes().getUaaScopes(), endpoints.getRestrictedClientScopes()); endpoints.setRestrictedScopesValidator(null); assertNull(endpoints.getRestrictedClientScopes()); } @Test(expected = InvalidClientDetailsException.class) public void testCannot_Create_Restricted_Client_Invalid_Scopes() throws Exception { input.setScope(new UaaScopes().getUaaScopes()); endpoints.createRestrictedClientDetails(input); } @Test(expected = InvalidClientDetailsException.class) public void testCannot_Create_Restricted_Client_Invalid_Authorities() throws Exception { input.setAuthorities(new UaaScopes().getUaaAuthorities()); endpoints.createRestrictedClientDetails(input); } @Test(expected = InvalidClientDetailsException.class) public void testCannot_Update_Restricted_Client_Invalid_Scopes() throws Exception { input.setScope(new UaaScopes().getUaaScopes()); endpoints.updateRestrictedClientDetails(input, input.getClientId()); } @Test(expected = InvalidClientDetailsException.class) public void testCannot_Update_Restricted_Client_Invalid_Authorities() throws Exception { input.setAuthorities(new UaaScopes().getUaaAuthorities()); endpoints.updateRestrictedClientDetails(input, input.getClientId()); } @Test(expected = NoSuchClientException.class) public void testMultipleCreateClientDetailsNullArray() throws Exception { endpoints.createClientDetailsTx(null); } @Test(expected = NoSuchClientException.class) public void testMultipleCreateClientDetailsEmptyArray() throws Exception { endpoints.createClientDetailsTx(new ClientDetailsModification[0]); } @Test(expected = InvalidClientDetailsException.class) public void testMultipleCreateClientDetailsNonExistent() throws Exception { ClientDetailsModification detailsModification = new ClientDetailsModification(); detailsModification.setClientId("unknown"); ClientDetailsModification nonexist = detailsModification; endpoints.createClientDetailsTx(new ClientDetailsModification[]{nonexist}); } @Test(expected = InvalidClientDetailsException.class) public void testMultipleUpdateClientDetailsNullArray() throws Exception { endpoints.updateClientDetailsTx(null); } @Test(expected = InvalidClientDetailsException.class) public void testMultipleUpdateClientDetailsEmptyArray() throws Exception { endpoints.updateClientDetailsTx(new ClientDetailsModification[0]); } @Test public void testMultipleCreateClientDetails() throws Exception { ClientDetails[] results = endpoints.createClientDetailsTx(inputs); assertEquals("We should have created "+inputs.length+" clients.", inputs.length, results.length); for (int i=0; i<inputs.length; i++) { ClientDetails result = results[i]; assertNull(result.getClientSecret()); } //TODO figure out how to verify all five invocations //Mockito.verify(clientRegistrationService, times(inputs.length)).addClientDetails(details[0]); } @Test(expected = InvalidClientDetailsException.class) public void testCreateClientDetailsWithReservedId() throws Exception { input.setClientId("uaa"); ClientDetails result = endpoints.createClientDetails(input); } @Test(expected = InvalidClientDetailsException.class) public void testCreateMultipleClientDetailsWithReservedId() throws Exception { inputs[inputs.length-1].setClientId("uaa"); ClientDetails[] result = endpoints.createClientDetailsTx(inputs); } @Test(expected = InvalidClientDetailsException.class) public void testCreateClientDetailsWithNoGrantType() throws Exception { input.setAuthorizedGrantTypes(Collections.<String>emptySet()); ClientDetails result = endpoints.createClientDetails(input); } @Test(expected = InvalidClientDetailsException.class) public void testCreateMultipleClientDetailsWithNoGrantType() throws Exception { inputs[inputs.length-1].setAuthorizedGrantTypes(Collections.<String>emptySet()); ClientDetails[] result = endpoints.createClientDetailsTx(inputs); } @Test public void testCreateClientDetailsWithClientCredentials() throws Exception { when(clientDetailsService.retrieve(anyString())).thenReturn(input); input.setAuthorizedGrantTypes(Arrays.asList("client_credentials")); detail.setAuthorizedGrantTypes(input.getAuthorizedGrantTypes()); ClientDetails result = endpoints.createClientDetails(input); assertNull(result.getClientSecret()); verify(clientDetailsService).create(detail); } @Test public void testCreateClientDetailsWithAdditionalInformation() throws Exception { when(clientDetailsService.retrieve(anyString())).thenReturn(input); input.setAdditionalInformation(Collections.singletonMap("foo", "bar")); detail.setAdditionalInformation(input.getAdditionalInformation()); ClientDetails result = endpoints.createClientDetails(input); assertNull(result.getClientSecret()); verify(clientDetailsService).create(detail); } @Test public void testResourceServerCreation() throws Exception { detail.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList("uaa.resource")); detail.setScope(Arrays.asList(detail.getClientId() + ".some")); detail.setAuthorizedGrantTypes(Arrays.asList("client_credentials")); endpoints.createClientDetails(detail); } @Test(expected = InvalidClientDetailsException.class) public void testCreateClientDetailsWithPasswordGrant() throws Exception { input.setAuthorizedGrantTypes(Arrays.asList("password")); ClientDetails result = endpoints.createClientDetails(input); assertNull(result.getClientSecret()); verify(clientRegistrationService).addClientDetails(detail); } @Test public void testFindClientDetails() throws Exception { Mockito.when(clientDetailsService.query("filter", "sortBy", true)).thenReturn( Arrays.<ClientDetails> asList(detail)); SearchResults<?> result = endpoints.listClientDetails("client_id", "filter", "sortBy", "ascending", 1, 100); assertEquals(1, result.getResources().size()); verify(clientDetailsService).query("filter", "sortBy", true); result = endpoints.listClientDetails("", "filter", "sortBy", "ascending", 1, 100); assertEquals(1, result.getResources().size()); } @Test(expected = UaaException.class) public void testFindClientDetailsInvalidFilter() throws Exception { Mockito.when(clientDetailsService.query("filter", "sortBy", true)).thenThrow(new IllegalArgumentException()); endpoints.listClientDetails("client_id", "filter", "sortBy", "ascending", 1, 100); } @Test public void testFindClientDetails_Test_Attribute_Filter() throws Exception { when(clientDetailsService.query(anyString(), anyString(), anyBoolean())).thenReturn(Arrays.asList(inputs)); for (String attribute : Arrays.asList("client_id", "resource_ids", "authorized_grant_types", "redirect_uri", "access_token_validity", "refresh_token_validity", "autoapprove","additionalinformation")) { SearchResults<Map<String, Object>> result = (SearchResults<Map<String, Object>>) endpoints.listClientDetails(attribute, "client_id pr", "sortBy", "ascending", 1, 100); validateAttributeResults(result, 5, Arrays.asList(attribute)); } } protected void validateAttributeResults(SearchResults<Map<String,Object>> result , int size, List<String> attributes) { assertEquals(5, result.getResources().size()); for (String s : attributes) { result.getResources().stream().forEach((map) -> assertTrue("Expecting attribute "+s+" to be present", map.containsKey(s)) ); } } @Test(expected = InvalidClientDetailsException.class) public void testUpdateClientDetailsWithNullCallerAndInvalidScope() throws Exception { Mockito.when(clientDetailsService.retrieve(input.getClientId())).thenReturn( new BaseClientDetails(input)); input.setScope(Arrays.asList("read")); ClientDetails result = endpoints.updateClientDetails(input, input.getClientId()); assertNull(result.getClientSecret()); detail.setScope(Arrays.asList("read")); verify(clientRegistrationService).updateClientDetails(detail); } @Test(expected = InvalidClientDetailsException.class) public void testNonExistentClient1() throws Exception { Mockito.when(clientDetailsService.retrieve(input.getClientId())).thenThrow(new InvalidClientDetailsException("")); endpoints.getClientDetails(input.getClientId()); } @Test(expected = NoSuchClientException.class) public void testNonExistentClient2() throws Exception { Mockito.when(clientDetailsService.retrieve(input.getClientId())).thenThrow(new BadClientCredentialsException()); endpoints.getClientDetails(input.getClientId()); } @Test public void testGetClientDetails() throws Exception { Mockito.when(clientDetailsService.retrieve(input.getClientId())).thenReturn(input); input.setScope(Arrays.asList(input.getClientId() + ".read")); input.setAdditionalInformation(Collections.singletonMap("foo", "bar")); ClientDetails result = endpoints.getClientDetails(input.getClientId()); assertNull(result.getClientSecret()); assertEquals(input.getAdditionalInformation(), result.getAdditionalInformation()); } @Test public void testUpdateClientDetails() throws Exception { Mockito.when(clientDetailsService.retrieve(input.getClientId())).thenReturn( new BaseClientDetails(input)); input.setScope(Arrays.asList(input.getClientId() + ".read")); ClientDetails result = endpoints.updateClientDetails(input, input.getClientId()); assertNull(result.getClientSecret()); detail.setScope(Arrays.asList(input.getClientId() + ".read")); verify(clientRegistrationService).updateClientDetails(detail); } @Test public void testUpdateClientDetailsWithAdditionalInformation() throws Exception { Mockito.when(clientDetailsService.retrieve(input.getClientId())).thenReturn( new BaseClientDetails(input)); input.setScope(Arrays.asList(input.getClientId() + ".read")); input.setAdditionalInformation(Collections.singletonMap("foo", "bar")); ClientDetails result = endpoints.updateClientDetails(input, input.getClientId()); assertNull(result.getClientSecret()); detail.setScope(input.getScope()); detail.setAdditionalInformation(input.getAdditionalInformation()); verify(clientRegistrationService).updateClientDetails(detail); } @Test public void testUpdateClientDetailsRemoveAdditionalInformation() throws Exception { input.setAdditionalInformation(Collections.singletonMap("foo", "bar")); Mockito.when(clientDetailsService.retrieve(input.getClientId())).thenReturn( new BaseClientDetails(input)); input.setAdditionalInformation(Collections.<String, Object> emptyMap()); ClientDetails result = endpoints.updateClientDetails(input, input.getClientId()); assertNull(result.getClientSecret()); verify(clientRegistrationService).updateClientDetails(detail); } @Test public void testPartialUpdateClientDetails() throws Exception { BaseClientDetails updated = new BaseClientDetails(detail); input = new BaseClientDetails(); input.setClientId("foo"); Mockito.when(clientDetailsService.retrieve(input.getClientId())).thenReturn(detail); input.setScope(Arrays.asList("foo.write")); updated.setScope(input.getScope()); updated.setClientSecret(null); updated.setRegisteredRedirectUri(SINGLE_REDIRECT_URL); ClientDetails result = endpoints.updateClientDetails(input, input.getClientId()); assertNull(result.getClientSecret()); verify(clientRegistrationService).updateClientDetails(updated); } @Test public void testChangeSecret() throws Exception { Authentication auth = mock(Authentication.class); when(auth.isAuthenticated()).thenReturn(true); when(authenticationManager.authenticate(any(Authentication.class))).thenReturn(auth); when(clientDetailsService.retrieve(detail.getClientId())).thenReturn(detail); SecurityContextAccessor sca = mock(SecurityContextAccessor.class); when(sca.getClientId()).thenReturn(detail.getClientId()); when(sca.isClient()).thenReturn(true); setSecurityContextAccessor(sca); SecretChangeRequest change = new SecretChangeRequest(); change.setOldSecret(detail.getClientSecret()); change.setSecret("newpassword"); endpoints.changeSecret(detail.getClientId(), change); verify(clientRegistrationService).updateClientSecret(detail.getClientId(), "newpassword"); } @Test public void testAddSecret() { SecurityContextAccessor sca = mock(SecurityContextAccessor.class); when(sca.getClientId()).thenReturn("bar"); when(sca.isClient()).thenReturn(true); when(sca.isAdmin()).thenReturn(true); setSecurityContextAccessor(sca); when(clientDetailsService.retrieve(detail.getClientId())).thenReturn(detail); SecretChangeRequest change = new SecretChangeRequest(); change.setSecret("newpassword"); change.setChangeMode(ADD); endpoints.changeSecret(detail.getClientId(), change); verify(clientRegistrationService).addClientSecret(detail.getClientId(), "newpassword"); } @Test public void testAddingThirdSecretForClient() { SecurityContextAccessor sca = mock(SecurityContextAccessor.class); when(sca.getClientId()).thenReturn("bar"); when(sca.isClient()).thenReturn(true); when(sca.isAdmin()).thenReturn(true); setSecurityContextAccessor(sca); detail.setClientSecret("hash1 hash2"); when(clientDetailsService.retrieve(detail.getClientId())).thenReturn(detail); SecretChangeRequest change = new SecretChangeRequest(); change.setSecret("newpassword"); change.setOldSecret("hash1"); change.setChangeMode(ADD); expected.expect(InvalidClientDetailsException.class); expected.expectMessage("client secret is either empty or client already has two secrets."); endpoints.changeSecret(detail.getClientId(), change); } @Test public void testDeleteSecret() { SecurityContextAccessor sca = mock(SecurityContextAccessor.class); when(sca.getClientId()).thenReturn("bar"); when(sca.isClient()).thenReturn(true); when(sca.isAdmin()).thenReturn(true); setSecurityContextAccessor(sca); detail.setClientSecret("hash1 hash2"); when(clientDetailsService.retrieve(detail.getClientId())).thenReturn(detail); SecretChangeRequest change = new SecretChangeRequest(); change.setChangeMode(DELETE); endpoints.changeSecret(detail.getClientId(), change); verify(clientRegistrationService).deleteClientSecret(detail.getClientId()); } @Test public void testDeleteSecretWhenOnlyOneSecret() { SecurityContextAccessor sca = mock(SecurityContextAccessor.class); when(sca.getClientId()).thenReturn("bar"); when(sca.isClient()).thenReturn(true); when(sca.isAdmin()).thenReturn(true); setSecurityContextAccessor(sca); detail.setClientSecret("hash1"); when(clientDetailsService.retrieve(detail.getClientId())).thenReturn(detail); SecretChangeRequest change = new SecretChangeRequest(); change.setChangeMode(DELETE); expected.expect(InvalidClientDetailsException.class); expected.expectMessage("client secret is either empty or client has only one secret."); endpoints.changeSecret(detail.getClientId(), change); } @Test public void testChangeSecretDeniedForUser() throws Exception { when(clientDetailsService.retrieve(detail.getClientId())).thenReturn(detail); SecurityContextAccessor sca = mock(SecurityContextAccessor.class); when(sca.getClientId()).thenReturn(detail.getClientId()); when(sca.isClient()).thenReturn(false); setSecurityContextAccessor(sca); SecretChangeRequest change = new SecretChangeRequest(); change.setOldSecret(detail.getClientSecret()); change.setSecret("newpassword"); expected.expect(InvalidClientDetailsException.class); expected.expectMessage("Only a client"); endpoints.changeSecret(detail.getClientId(), change); } @Test public void testChangeSecretDeniedForNonAdmin() throws Exception { when(clientDetailsService.retrieve(detail.getClientId())).thenReturn(detail); SecurityContextAccessor sca = mock(SecurityContextAccessor.class); when(sca.getClientId()).thenReturn("bar"); when(sca.isClient()).thenReturn(true); when(sca.isAdmin()).thenReturn(false); setSecurityContextAccessor(sca); SecretChangeRequest change = new SecretChangeRequest(); change.setSecret("newpassword"); expected.expect(InvalidClientDetailsException.class); expected.expectMessage("Not permitted to change"); endpoints.changeSecret(detail.getClientId(), change); } @Test public void testAddSecretDeniedForNonAdmin() throws Exception { when(clientDetailsService.retrieve(detail.getClientId())).thenReturn(detail); SecurityContextAccessor sca = mock(SecurityContextAccessor.class); when(sca.getClientId()).thenReturn("bar"); when(sca.isClient()).thenReturn(true); when(sca.isAdmin()).thenReturn(false); setSecurityContextAccessor(sca); SecretChangeRequest change = new SecretChangeRequest(); change.setSecret("newpassword"); change.setChangeMode(ADD); expected.expect(InvalidClientDetailsException.class); expected.expectMessage("Not permitted to change"); endpoints.changeSecret(detail.getClientId(), change); } @Test public void testChangeSecretDeniedWhenOldSecretNotProvided() throws Exception { when(clientDetailsService.retrieve(detail.getClientId())).thenReturn(detail); when(authenticationManager.authenticate(any(Authentication.class))).thenThrow(new BadCredentialsException("")); SecurityContextAccessor sca = mock(SecurityContextAccessor.class); when(sca.getClientId()).thenReturn(detail.getClientId()); when(sca.isClient()).thenReturn(true); when(sca.isAdmin()).thenReturn(false); setSecurityContextAccessor(sca); SecretChangeRequest change = new SecretChangeRequest(); change.setSecret("newpassword"); expected.expect(InvalidClientDetailsException.class); expected.expectMessage("Previous secret is required"); endpoints.changeSecret(detail.getClientId(), change); } @Test public void testChangeSecretByAdmin() throws Exception { when(clientDetailsService.retrieve(detail.getClientId())).thenReturn(detail); SecurityContextAccessor sca = mock(SecurityContextAccessor.class); when(sca.getClientId()).thenReturn("admin"); when(sca.isClient()).thenReturn(true); when(sca.isAdmin()).thenReturn(true); setSecurityContextAccessor(sca); SecretChangeRequest change = new SecretChangeRequest(); change.setOldSecret(detail.getClientSecret()); change.setSecret("newpassword"); endpoints.changeSecret(detail.getClientId(), change); verify(clientRegistrationService).updateClientSecret(detail.getClientId(), "newpassword"); } @Test public void testRemoveClientDetailsAdminCaller() throws Exception { Mockito.when(securityContextAccessor.isAdmin()).thenReturn(true); Mockito.when(clientDetailsService.retrieve("foo")).thenReturn(detail); ClientDetails result = endpoints.removeClientDetails("foo"); assertNull(result.getClientSecret()); ArgumentCaptor<EntityDeletedEvent> captor = ArgumentCaptor.forClass(EntityDeletedEvent.class); verify(endpoints).publish(captor.capture()); verify(clientRegistrationService).removeClientDetails("foo"); assertNotNull(captor.getValue()); Object deleted = captor.getValue().getDeleted(); assertNotNull(deleted); assertTrue(deleted instanceof ClientDetails); assertEquals("foo", ((ClientDetails)deleted).getClientId()); } @Test(expected = InvalidClientDetailsException.class) public void testScopeIsRestrictedByCaller() throws Exception { BaseClientDetails caller = new BaseClientDetails("caller", null, "none", "client_credentials,implicit", "uaa.none"); when(clientDetailsService.retrieve("caller")).thenReturn(caller); setSecurityContextAccessor(new StubSecurityContextAccessor() { @Override public String getClientId() { return "caller"; } }); detail.setScope(Arrays.asList("some")); endpoints.createClientDetails(detail); } @Test public void testValidScopeIsNotRestrictedByCaller() throws Exception { BaseClientDetails caller = new BaseClientDetails("caller", null, "none", "client_credentials,implicit", "uaa.none"); when(clientDetailsService.retrieve("caller")).thenReturn(caller); setSecurityContextAccessor(new StubSecurityContextAccessor() { @Override public String getClientId() { return "caller"; } }); detail.setScope(Arrays.asList("none")); endpoints.createClientDetails(detail); } @Test public void testClientPrefixScopeIsNotRestrictedByClient() throws Exception { BaseClientDetails caller = new BaseClientDetails("caller", null, "none", "client_credentials,implicit", "uaa.none"); when(clientDetailsService.retrieve("caller")).thenReturn(caller); setSecurityContextAccessor(new StubSecurityContextAccessor() { @Override public String getClientId() { return "caller"; } }); detail.setScope(Arrays.asList(detail.getClientId() + ".read")); endpoints.createClientDetails(detail); } @Test(expected = InvalidClientDetailsException.class) public void testAuthorityIsRestrictedByCaller() throws Exception { BaseClientDetails caller = new BaseClientDetails("caller", null, "none", "client_credentials,implicit", "uaa.none"); when(clientDetailsService.retrieve("caller")).thenReturn(caller); setSecurityContextAccessor(new StubSecurityContextAccessor() { @Override public String getClientId() { return "caller"; } }); detail.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList("uaa.some")); endpoints.createClientDetails(detail); } @Test public void testAuthorityAllowedByCaller() throws Exception { BaseClientDetails caller = new BaseClientDetails("caller", null, "uaa.none", "client_credentials,implicit", "uaa.none"); when(clientDetailsService.retrieve("caller")).thenReturn(caller); setSecurityContextAccessor(new StubSecurityContextAccessor() { @Override public String getClientId() { return "caller"; } }); detail.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList("uaa.none")); endpoints.createClientDetails(detail); } @Test(expected = InvalidClientDetailsException.class) public void cannotExpandScope() throws Exception { BaseClientDetails caller = new BaseClientDetails(); caller.setScope(Arrays.asList("none")); when(clientDetailsService.retrieve("caller")).thenReturn(caller); detail.setAuthorizedGrantTypes(Arrays.asList("implicit")); detail.setClientSecret("hello"); endpoints.createClientDetails(detail); } @Test(expected = InvalidClientDetailsException.class) public void implicitClientWithNonEmptySecretIsRejected() throws Exception { detail.setAuthorizedGrantTypes(Arrays.asList("implicit")); detail.setClientSecret("hello"); endpoints.createClientDetails(detail); } @Test(expected = InvalidClientDetailsException.class) public void implicitAndAuthorizationCodeClientIsRejected() throws Exception { detail.setAuthorizedGrantTypes(Arrays.asList("implicit", "authorization_code")); detail.setClientSecret("hello"); endpoints.createClientDetails(detail); } @Test(expected = InvalidClientDetailsException.class) public void implicitAndAuthorizationCodeClientIsRejectedWithNullPassword() throws Exception { detail.setAuthorizedGrantTypes(Arrays.asList("implicit", "authorization_code")); detail.setClientSecret(null); endpoints.createClientDetails(detail); } @Test(expected = InvalidClientDetailsException.class) public void implicitAndAuthorizationCodeClientIsRejectedForAdmin() throws Exception { setSecurityContextAccessor(new StubSecurityContextAccessor() { @Override public boolean isAdmin() { return true; } }); detail.setAuthorizedGrantTypes(Arrays.asList("implicit", "authorization_code")); detail.setClientSecret("hello"); endpoints.createClientDetails(detail); } @Test(expected = InvalidClientDetailsException.class) public void nonImplicitClientWithEmptySecretIsRejected() throws Exception { detail.setAuthorizedGrantTypes(Arrays.asList("authorization_code")); detail.setClientSecret(""); endpoints.createClientDetails(detail); } @Test public void updateNonImplicitClientWithEmptySecretIsOk() throws Exception { Mockito.when(securityContextAccessor.isAdmin()).thenReturn(true); detail.setAuthorizedGrantTypes(Arrays.asList("authorization_code")); detail.setClientSecret(null); endpoints.updateClientDetails(detail, detail.getClientId()); } @Test(expected = InvalidClientDetailsException.class) public void updateNonImplicitClientAndMakeItImplicit() throws Exception { assertFalse(detail.getAuthorizedGrantTypes().contains("implicit")); detail.setAuthorizedGrantTypes(Arrays.asList("authorization_code", "implicit")); detail.setClientSecret(null); endpoints.updateClientDetails(detail, detail.getClientId()); } @Test(expected = InvalidClientDetailsException.class) public void invalidGrantTypeIsRejected() throws Exception { detail.setAuthorizedGrantTypes(Arrays.asList("not_a_grant_type")); endpoints.createClientDetails(detail); } @Test public void testHandleNoSuchClient() throws Exception { ResponseEntity<Void> result = endpoints.handleNoSuchClient(new NoSuchClientException("No such client: foo")); assertEquals(HttpStatus.NOT_FOUND, result.getStatusCode()); } @Test public void testHandleClientAlreadyExists() throws Exception { ResponseEntity<InvalidClientDetailsException> result = endpoints .handleClientAlreadyExists(new ClientAlreadyExistsException("No such client: foo")); assertEquals(HttpStatus.CONFLICT, result.getStatusCode()); } @Test public void testErrorHandler() throws Exception { ResponseEntity<InvalidClientDetailsException> result = endpoints .handleInvalidClientDetails(new InvalidClientDetailsException("No such client: foo")); assertEquals(HttpStatus.BAD_REQUEST, result.getStatusCode()); assertEquals(1, endpoints.getErrorCounts().size()); } @Test public void testCreateClientWithAutoapproveScopesList() throws Exception { when(clientDetailsService.retrieve(anyString())).thenReturn(input); List<String> scopes = Arrays.asList("foo.read","foo.write"); List<String> autoApproveScopes = Arrays.asList("foo.read"); input.setScope(scopes); detail.setScope(scopes); input.setAutoApproveScopes(autoApproveScopes); detail.setAutoApproveScopes(autoApproveScopes); detail.setAuthorizedGrantTypes(input.getAuthorizedGrantTypes()); ClientDetails result = endpoints.createClientDetails(input); assertNull(result.getClientSecret()); ArgumentCaptor<BaseClientDetails> clientCaptor = ArgumentCaptor.forClass(BaseClientDetails.class); verify(clientDetailsService).create(clientCaptor.capture()); BaseClientDetails created = clientCaptor.getValue(); assertSetEquals(autoApproveScopes, created.getAutoApproveScopes()); assertTrue(created.isAutoApprove("foo.read")); assertFalse(created.isAutoApprove("foo.write")); } private static void assertSetEquals(Collection<?> a, Collection<?> b) { assertTrue("expected " + a + " but was " + b, a == null && b == null || a != null && b != null && a.containsAll(b) && b.containsAll(a)); } @Test public void testCreateClientWithAutoapproveScopesTrue() throws Exception { when(clientDetailsService.retrieve(anyString())).thenReturn(input); List<String> scopes = Arrays.asList("foo.read","foo.write"); List<String> autoApproveScopes = Arrays.asList("true"); input.setScope(scopes); detail.setScope(scopes); input.setAutoApproveScopes(autoApproveScopes); detail.setAutoApproveScopes(autoApproveScopes); detail.setAuthorizedGrantTypes(input.getAuthorizedGrantTypes()); ClientDetails result = endpoints.createClientDetails(input); assertNull(result.getClientSecret()); ArgumentCaptor<BaseClientDetails> clientCaptor = ArgumentCaptor.forClass(BaseClientDetails.class); verify(clientDetailsService).create(clientCaptor.capture()); BaseClientDetails created = clientCaptor.getValue(); assertSetEquals(autoApproveScopes, created.getAutoApproveScopes()); assertTrue(created.isAutoApprove("foo.read")); assertTrue(created.isAutoApprove("foo.write")); } @Test public void testUpdateClientWithAutoapproveScopesList() throws Exception { List<String> scopes = Arrays.asList("foo.read","foo.write"); List<String> autoApproveScopes = Arrays.asList("foo.read"); input.setScope(scopes); detail.setScope(scopes); detail.setAutoApproveScopes(autoApproveScopes); Mockito.when(clientDetailsService.retrieve(input.getClientId())).thenReturn( new BaseClientDetails(input)); ClientDetails result = endpoints.updateClientDetails(detail, input.getClientId()); assertNull(result.getClientSecret()); ArgumentCaptor<BaseClientDetails> clientCaptor = ArgumentCaptor.forClass(BaseClientDetails.class); verify(clientRegistrationService).updateClientDetails(clientCaptor.capture()); BaseClientDetails updated = clientCaptor.getValue(); assertSetEquals(autoApproveScopes, updated.getAutoApproveScopes()); assertTrue(updated.isAutoApprove("foo.read")); assertFalse(updated.isAutoApprove("foo.write")); } @Test public void testUpdateClientWithAutoapproveScopesTrue() throws Exception { List<String> scopes = Arrays.asList("foo.read","foo.write"); List<String> autoApproveScopes = Arrays.asList("true"); input.setScope(scopes); detail.setScope(scopes); detail.setAutoApproveScopes(autoApproveScopes); Mockito.when(clientDetailsService.retrieve(input.getClientId())).thenReturn( new BaseClientDetails(input)); ArgumentCaptor<BaseClientDetails> clientCaptor = ArgumentCaptor.forClass(BaseClientDetails.class); ClientDetails result = endpoints.updateClientDetails(detail, input.getClientId()); assertNull(result.getClientSecret()); verify(clientRegistrationService).updateClientDetails(clientCaptor.capture()); BaseClientDetails updated = clientCaptor.getValue(); assertSetEquals(autoApproveScopes, updated.getAutoApproveScopes()); assertTrue(updated.isAutoApprove("foo.read")); assertTrue(updated.isAutoApprove("foo.write")); } }