package org.cloudfoundry.identity.uaa.scim.endpoints; import org.cloudfoundry.identity.uaa.TestClassNullifier; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.resources.QueryableResourceManager; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.event.UserModifiedEvent; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.springframework.context.ApplicationEventPublisher; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import java.sql.Timestamp; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import static org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType.EMAIL; import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public class ChangeEmailEndpointsTest extends TestClassNullifier { private ScimUserProvisioning scimUserProvisioning; private MockMvc mockMvc; private ExpiringCodeStore expiringCodeStore; private ApplicationEventPublisher publisher; private QueryableResourceManager<ClientDetails> clientDetailsService; @Before public void setUp() throws Exception { scimUserProvisioning = mock(ScimUserProvisioning.class); expiringCodeStore = Mockito.mock(ExpiringCodeStore.class); publisher = Mockito.mock(ApplicationEventPublisher.class); clientDetailsService = Mockito.mock(QueryableResourceManager.class); ChangeEmailEndpoints changeEmailEndpoints = new ChangeEmailEndpoints(scimUserProvisioning, expiringCodeStore, clientDetailsService); changeEmailEndpoints.setApplicationEventPublisher(publisher); mockMvc = MockMvcBuilders.standaloneSetup(changeEmailEndpoints).build(); } @Test public void testGenerateEmailChangeCode() throws Exception { String data = "{\"userId\":\"user-id-001\",\"email\":\"new@example.com\",\"client_id\":null}"; when(expiringCodeStore.generateCode(eq(data), any(Timestamp.class), eq(EMAIL.name()))) .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + 1000), data, EMAIL.name())); ScimUser userChangingEmail = new ScimUser("user-id-001", "user@example.com", null, null); userChangingEmail.setOrigin("test"); userChangingEmail.setPrimaryEmail("user@example.com"); when(scimUserProvisioning.retrieve("user-id-001")).thenReturn(userChangingEmail); MockHttpServletRequestBuilder post = post("/email_verifications") .contentType(APPLICATION_JSON) .content(data) .accept(APPLICATION_JSON); mockMvc.perform(post) .andExpect(status().isCreated()) .andExpect(content().string("secret_code")); } @Test public void testGenerateEmailChangeCodeWithExistingUsernameChange() throws Exception { String data = "{\"userId\":\"user-id-001\",\"email\":\"new@example.com\",\"client_id\":null}"; ScimUser userChangingEmail = new ScimUser("id001", "user@example.com", null, null); userChangingEmail.setPrimaryEmail("user@example.com"); when(scimUserProvisioning.retrieve("user-id-001")).thenReturn(userChangingEmail); ScimUser existingUser = new ScimUser("id001", "new@example.com", null, null); when(scimUserProvisioning.query("userName eq \"new@example.com\" and origin eq \"" + OriginKeys.UAA + "\"")) .thenReturn(Arrays.asList(existingUser)); MockHttpServletRequestBuilder post = post("/email_verifications") .contentType(APPLICATION_JSON) .content(data) .accept(APPLICATION_JSON); mockMvc.perform(post) .andExpect(status().isConflict()); } @Test public void testChangeEmail() throws Exception { when(expiringCodeStore.retrieveCode("the_secret_code")) .thenReturn(new ExpiringCode("the_secret_code", new Timestamp(System.currentTimeMillis()), "{\"userId\":\"user-id-001\",\"email\":\"new@example.com\", \"client_id\":\"app\"}", EMAIL.name())); BaseClientDetails clientDetails = new BaseClientDetails(); Map<String, String> additionalInformation = new HashMap<>(); additionalInformation.put(ChangeEmailEndpoints.CHANGE_EMAIL_REDIRECT_URL, "app_callback_url"); clientDetails.setAdditionalInformation(additionalInformation); when(clientDetailsService.retrieve("app")) .thenReturn(clientDetails); ScimUser scimUser = new ScimUser(); scimUser.setUserName("user@example.com"); scimUser.setPrimaryEmail("user@example.com"); when(scimUserProvisioning.retrieve("user-id-001")).thenReturn(scimUser); mockMvc.perform(post("/email_changes") .contentType(APPLICATION_JSON) .content("the_secret_code") .accept(APPLICATION_JSON)) .andExpect(MockMvcResultMatchers.jsonPath("$.userId").value("user-id-001")) .andExpect(MockMvcResultMatchers.jsonPath("$.username").value("new@example.com")) .andExpect(MockMvcResultMatchers.jsonPath("$.email").value("new@example.com")) .andExpect(MockMvcResultMatchers.jsonPath("$.redirect_url").value("app_callback_url")) .andExpect(MockMvcResultMatchers.status().isOk()); ArgumentCaptor<ScimUser> user = ArgumentCaptor.forClass(ScimUser.class); verify(scimUserProvisioning).update(eq("user-id-001"), user.capture()); Assert.assertEquals("new@example.com", user.getValue().getPrimaryEmail()); Assert.assertEquals("new@example.com", user.getValue().getUserName()); ArgumentCaptor<UserModifiedEvent> event = ArgumentCaptor.forClass(UserModifiedEvent.class); verify(publisher).publishEvent(event.capture()); Assert.assertEquals("user-id-001", event.getValue().getUserId()); Assert.assertEquals("new@example.com", event.getValue().getUsername()); Assert.assertEquals("new@example.com", event.getValue().getEmail()); } @Test public void testChangeEmailWhenUsernameNotTheSame() throws Exception { when(expiringCodeStore.retrieveCode("the_secret_code")) .thenReturn(new ExpiringCode("the_secret_code", new Timestamp(System.currentTimeMillis()), "{\"userId\":\"user-id-001\",\"email\":\"new@example.com\",\"client_id\":null}", EMAIL.name())); ScimUser scimUser = new ScimUser(); scimUser.setUserName("username"); scimUser.setPrimaryEmail("user@example.com"); when(scimUserProvisioning.retrieve("user-id-001")).thenReturn(scimUser); mockMvc.perform(post("/email_changes") .contentType(APPLICATION_JSON) .content("the_secret_code") .accept(APPLICATION_JSON)) .andExpect(MockMvcResultMatchers.status().isOk()); ArgumentCaptor<ScimUser> user = ArgumentCaptor.forClass(ScimUser.class); verify(scimUserProvisioning).update(eq("user-id-001"), user.capture()); Assert.assertEquals("new@example.com", user.getValue().getPrimaryEmail()); Assert.assertEquals("username", user.getValue().getUserName()); } @Test public void changeEmail_withIncorrectCode() throws Exception { when(expiringCodeStore.retrieveCode("the_secret_code")) .thenReturn(new ExpiringCode("the_secret_code", new Timestamp(System.currentTimeMillis()), "{\"userId\":\"user-id-001\",\"email\":\"new@example.com\",\"client_id\":null}", "incorrect-code")); mockMvc.perform(post("/email_changes") .contentType(APPLICATION_JSON) .content("the_secret_code") .accept(APPLICATION_JSON)) .andExpect(MockMvcResultMatchers.status().isUnprocessableEntity()); } }