package org.cloudfoundry.identity.uaa.login; import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.servlet.view.InternalResourceViewResolver; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; public class ChangeEmailControllerTest { private MockMvc mockMvc; private ChangeEmailService changeEmailService; @Before public void setUp() throws Exception { changeEmailService = Mockito.mock(ChangeEmailService.class); ChangeEmailController controller = new ChangeEmailController(changeEmailService); InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("/WEB-INF/jsp"); viewResolver.setSuffix(".jsp"); mockMvc = MockMvcBuilders .standaloneSetup(controller) .setViewResolvers(viewResolver) .build(); } @Test public void testChangeEmailPage() throws Exception { setupSecurityContext(); mockMvc.perform(get("/change_email")) .andExpect(status().isOk()) .andExpect(view().name("change_email")) .andExpect(model().attribute("email", "user@example.com")); } @Test public void testChangeEmailPageWithClientId() throws Exception { setupSecurityContext(); mockMvc.perform(get("/change_email?client_id=app")) .andExpect(status().isOk()) .andExpect(view().name("change_email")) .andExpect(model().attribute("client_id", "app")) .andExpect(model().attribute("email", "user@example.com")); } @Test public void testChangeEmail() throws Exception { setupSecurityContext(); MockHttpServletRequestBuilder post = post("/change_email.do") .contentType(APPLICATION_FORM_URLENCODED) .param("newEmail", "new@example.com") .param("client_id", "app"); mockMvc.perform(post) .andExpect(status().isFound()) .andExpect(redirectedUrl("email_sent?code=email_change")); Mockito.verify(changeEmailService).beginEmailChange("user-id-001", "bob", "new@example.com", "app"); } @Test public void testChangeEmailWithUsernameConflict() throws Exception { setupSecurityContext(); doThrow(new UaaException("username already exists", 409)).when(changeEmailService).beginEmailChange("user-id-001", "bob", "new@example.com", ""); MockHttpServletRequestBuilder post = post("/change_email.do") .contentType(APPLICATION_FORM_URLENCODED) .param("newEmail", "new@example.com") .param("client_id", ""); mockMvc.perform(post) .andExpect(status().isUnprocessableEntity()) .andExpect(view().name("change_email")) .andExpect(model().attribute("error_message_code", "username_exists")) .andExpect(model().attribute("email", "user@example.com")); } @Test public void testNonUAAOriginUser() throws Exception { Authentication authentication = new UaaAuthentication( new UaaPrincipal("user-id-001", "bob", "user@example.com", "NON-UAA-origin ", null), Arrays.asList(UaaAuthority.UAA_USER), null ); SecurityContextHolder.getContext().setAuthentication(authentication); MockHttpServletRequestBuilder post = post("/change_email.do") .contentType(APPLICATION_FORM_URLENCODED) .param("newEmail", "new@example.com") .param("client_id", "app"); mockMvc.perform(post) .andExpect(status().isFound()) .andExpect(redirectedUrl("profile?error_message_code=email_change.non-uaa-origin")); Mockito.verifyZeroInteractions(changeEmailService); } @Test public void testInvalidEmail() throws Exception { setupSecurityContext(); MockHttpServletRequestBuilder post = post("/change_email.do") .contentType(APPLICATION_FORM_URLENCODED) .param("newEmail", "invalid") .param("client_id", "app"); mockMvc.perform(post) .andExpect(status().isUnprocessableEntity()) .andExpect(view().name("change_email")) .andExpect(model().attribute("error_message_code", "invalid_email")) .andExpect(model().attribute("email", "user@example.com")); } @Test public void testBlankEmail() throws Exception { setupSecurityContext(); MockHttpServletRequestBuilder post = post("/change_email.do") .contentType(APPLICATION_FORM_URLENCODED) .param("newEmail", "") .param("client_id", "app"); mockMvc.perform(post) .andExpect(status().isUnprocessableEntity()) .andExpect(view().name("change_email")) .andExpect(model().attribute("error_message_code", "invalid_email")) .andExpect(model().attribute("email", "user@example.com")); } @Test public void testVerifyEmail() throws Exception { Map<String,String> response = new HashMap<>(); response.put("userId", "user-id-001"); response.put("username", "new@example.com"); response.put("email", "new@example.com"); when(changeEmailService.completeVerification("the_secret_code")).thenReturn(response); MockHttpServletRequestBuilder get = get("/verify_email") .contentType(APPLICATION_FORM_URLENCODED) .param("code", "the_secret_code"); mockMvc.perform(get) .andExpect(status().isFound()) .andExpect(redirectedUrl("profile?success_message_code=email_change.success")); UaaPrincipal principal = ((UaaPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal()); Assert.assertEquals("user-id-001", principal.getId()); Assert.assertEquals("new@example.com", principal.getName()); Assert.assertEquals("new@example.com", principal.getEmail()); } @Test public void testVerifyEmailWithRedirectUrl() throws Exception { Map<String,String> response = new HashMap<>(); response.put("userId", "user-id-001"); response.put("username", "new@example.com"); response.put("email", "new@example.com"); response.put("redirect_url", "//example.com/callback"); when(changeEmailService.completeVerification("the_secret_code")).thenReturn(response); MockHttpServletRequestBuilder get = get("/verify_email") .contentType(APPLICATION_FORM_URLENCODED) .param("code", "the_secret_code"); mockMvc.perform(get) .andExpect(status().isFound()) .andExpect(redirectedUrl("//example.com/callback")); UaaPrincipal principal = ((UaaPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal()); Assert.assertEquals("user-id-001", principal.getId()); Assert.assertEquals("new@example.com", principal.getName()); Assert.assertEquals("new@example.com", principal.getEmail()); } @Test public void testVerifyEmailWithInvalidCode() throws Exception { Authentication authentication = new AnonymousAuthenticationToken( "anon", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS") ); SecurityContextHolder.getContext().setAuthentication(authentication); when(changeEmailService.completeVerification("the_secret_code")).thenThrow(new UaaException("Bad Request", 400)); MockHttpServletRequestBuilder get = get("/verify_email") .contentType(APPLICATION_FORM_URLENCODED) .param("code", "the_secret_code"); mockMvc.perform(get) .andExpect(status().isUnprocessableEntity()) .andExpect(view().name("error")); setupSecurityContext(); mockMvc.perform(get) .andExpect(status().isFound()) .andExpect(redirectedUrl("profile?error_message_code=email_change.invalid_code")); } private void setupSecurityContext() { Authentication authentication = new UaaAuthentication( new UaaPrincipal("user-id-001", "bob", "user@example.com", Origin.UAA, null), Arrays.asList(UaaAuthority.UAA_USER), null ); SecurityContextHolder.getContext().setAuthentication(authentication); } }