package org.cloudfoundry.identity.uaa.login; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.login.test.ThymeleafConfig; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.http.HttpStatus; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.thymeleaf.spring4.SpringTemplateEngine; import java.nio.charset.Charset; import java.util.Map; import java.util.concurrent.TimeUnit; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.when; import static org.springframework.http.HttpMethod.GET; import static org.springframework.http.HttpMethod.PUT; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.test.web.client.match.MockRestRequestMatchers.jsonPath; import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration(classes = EmailInvitationsServiceTests.ContextConfiguration.class) @DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD) public class EmailInvitationsServiceTests { private MockRestServiceServer mockUaaServer; @Autowired ConfigurableWebApplicationContext webApplicationContext; @Autowired ExpiringCodeService expiringCodeService; @Autowired EmailInvitationsService emailInvitationsService; @Autowired AccountCreationService accountCreationService; @Autowired MessageService messageService; @Autowired RestTemplate authorizationTemplate; @Before public void setUp() throws Exception { mockUaaServer = MockRestServiceServer.createServer(authorizationTemplate); MockMvcBuilders.webAppContextSetup(webApplicationContext) .build(); } @Test public void testSendInviteEmail() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.setProtocol("http"); request.setContextPath("/login"); RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); ScimUser user = new ScimUser(); user.setId("user-id-001"); when(accountCreationService.createUser("user@example.com", null)).thenReturn(user); ArgumentCaptor<Map<String,String>> captor = ArgumentCaptor.forClass((Class)Map.class); when(expiringCodeService.generateCode(captor.capture(), anyInt(), eq(TimeUnit.DAYS))).thenReturn("the_secret_code"); emailInvitationsService.inviteUser("user@example.com", "current-user"); Map<String,String> data = captor.getValue(); assertEquals("user-id-001", data.get("user_id")); ArgumentCaptor<String> emailBodyArgument = ArgumentCaptor.forClass(String.class); Mockito.verify(messageService).sendMessage( eq("user-id-001"), eq("user@example.com"), eq(MessageType.INVITATION), eq("Invitation to join Pivotal"), emailBodyArgument.capture() ); String emailBody = emailBodyArgument.getValue(); assertThat(emailBody, containsString("current-user")); assertThat(emailBody, containsString("Pivotal")); assertThat(emailBody, containsString("<a href=\"http://localhost/login/invitations/accept?code=the_secret_code\">Accept Invite</a>")); assertThat(emailBody, not(containsString("Cloud Foundry"))); } @Test(expected = UaaException.class) public void testSendInviteEmailToUserThatIsAlreadyVerified() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.setProtocol("http"); request.setContextPath("/login"); byte[] errorResponse = "{\"error\":\"invalid_user\",\"message\":\"error message\",\"user_id\":\"existing-user-id\",\"verified\":true,\"active\":true}".getBytes(); when(accountCreationService.createUser("user@example.com", null)).thenThrow(new HttpClientErrorException(HttpStatus.CONFLICT,"invalid user",errorResponse,Charset.forName("UTF-8"))); emailInvitationsService.inviteUser("user@example.com", "current-user"); } @Test public void testSendInviteEmailToUnverifiedUser() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.setProtocol("http"); request.setContextPath("/login"); RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); byte[] errorResponse = "{\"error\":\"invalid_user\",\"message\":\"error message\",\"user_id\":\"existing-user-id\",\"verified\":false,\"active\":true}".getBytes(); when(accountCreationService.createUser("user@example.com", null)).thenThrow(new HttpClientErrorException(HttpStatus.CONFLICT,"invalid user",errorResponse,Charset.forName("UTF-8"))); ArgumentCaptor<Map<String,String>> captor = ArgumentCaptor.forClass((Class)Map.class); when(expiringCodeService.generateCode(captor.capture(), anyInt(), eq(TimeUnit.DAYS))).thenReturn("the_secret_code"); emailInvitationsService.inviteUser("user@example.com", "current-user"); Map<String,String> data = captor.getValue(); assertEquals("existing-user-id", data.get("user_id")); ArgumentCaptor<String> emailBodyArgument = ArgumentCaptor.forClass(String.class); Mockito.verify(messageService).sendMessage( eq("existing-user-id"), eq("user@example.com"), eq(MessageType.INVITATION), eq("Invitation to join Pivotal"), emailBodyArgument.capture() ); String emailBody = emailBodyArgument.getValue(); assertThat(emailBody, containsString("current-user")); assertThat(emailBody, containsString("Pivotal")); assertThat(emailBody, containsString("<a href=\"http://localhost/login/invitations/accept?code=the_secret_code\">Accept Invite</a>")); assertThat(emailBody, not(containsString("Cloud Foundry"))); } @Test public void testSendInviteEmailWithOSSBrand() throws Exception { emailInvitationsService.setBrand("oss"); MockHttpServletRequest request = new MockHttpServletRequest(); request.setProtocol("http"); request.setContextPath("/login"); RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); ScimUser user = new ScimUser(); user.setId("user-id-001"); when(accountCreationService.createUser("user@example.com", null)).thenReturn(user); ArgumentCaptor<Map<String,String>> captor = ArgumentCaptor.forClass((Class)Map.class); when(expiringCodeService.generateCode(captor.capture(), anyInt(), eq(TimeUnit.DAYS))).thenReturn("the_secret_code"); emailInvitationsService.inviteUser("user@example.com", "current-user"); Map<String,String> data = captor.getValue(); assertEquals("user-id-001", data.get("user_id")); ArgumentCaptor<String> emailBodyArgument = ArgumentCaptor.forClass(String.class); Mockito.verify(messageService).sendMessage( eq("user-id-001"), eq("user@example.com"), eq(MessageType.INVITATION), eq("Invitation to join Cloud Foundry"), emailBodyArgument.capture() ); String emailBody = emailBodyArgument.getValue(); assertThat(emailBody, containsString("current-user")); assertThat(emailBody, containsString("<a href=\"http://localhost/login/invitations/accept?code=the_secret_code\">Accept Invite</a>")); assertThat(emailBody, containsString("Cloud Foundry")); assertThat(emailBody, not(containsString("Pivotal"))); } @Test public void testAcceptInvitation() throws Exception { mockUaaServer.expect(requestTo("http://uaa.example.com/Users/user-id-001/verify")) .andExpect(method(GET)) .andRespond(withSuccess("{}",APPLICATION_JSON)); String clientDetails = "{" + "\"client_id\": \"app\"," + "\"invitation_redirect_url\": \"http://example.com/redirect\"" + "}"; mockUaaServer.expect(requestTo("http://uaa.example.com/Users/user-id-001/password")) .andExpect(method(PUT)) .andExpect(jsonPath("$.password").value("secret")) .andRespond(withSuccess()); mockUaaServer.expect(requestTo("http://uaa.example.com/oauth/clients/app")) .andExpect(method(GET)) .andRespond(withSuccess(clientDetails, APPLICATION_JSON)); String redirectLocation = emailInvitationsService.acceptInvitation("user-id-001", "user@example.com", "secret", "app"); mockUaaServer.verify(); Mockito.verifyZeroInteractions(expiringCodeService); assertEquals("http://example.com/redirect", redirectLocation); } @Test public void testAcceptInvitationWithNoClientRedirect() throws Exception { mockUaaServer.expect(requestTo("http://uaa.example.com/Users/user-id-001/verify")) .andExpect(method(GET)) .andRespond(withSuccess("{}",APPLICATION_JSON)); mockUaaServer.expect(requestTo("http://uaa.example.com/Users/user-id-001/password")) .andExpect(method(PUT)) .andExpect(jsonPath("$.password").value("secret")) .andRespond(withSuccess()); String redirectLocation = emailInvitationsService.acceptInvitation("user-id-001", "user@example.com", "secret", ""); mockUaaServer.verify(); Mockito.verifyZeroInteractions(expiringCodeService); assertNull(redirectLocation); } @Configuration @EnableWebMvc @Import(ThymeleafConfig.class) static class ContextConfiguration extends WebMvcConfigurerAdapter { @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } @Autowired @Qualifier("mailTemplateEngine") SpringTemplateEngine templateEngine; @Bean ExpiringCodeService expiringCodeService() { return Mockito.mock(ExpiringCodeService.class); } @Bean MessageService messageService() { return Mockito.mock(MessageService.class); } @Bean AccountCreationService accountCreationService() { return Mockito.mock(AccountCreationService.class); } @Bean EmailInvitationsService emailInvitationsService() { return new EmailInvitationsService(templateEngine, messageService(), "pivotal", "http://uaa.example.com"); } @Bean RestTemplate authorizationTemplate() { return new RestTemplate(); } } }