package org.cloudfoundry.identity.uaa.login;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
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;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath;
import java.util.HashMap;
import java.util.Map;
import org.cloudfoundry.identity.uaa.authentication.Origin;
import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal;
import org.cloudfoundry.identity.uaa.error.UaaException;
import org.cloudfoundry.identity.uaa.login.ExpiringCodeService.CodeNotFoundException;
import org.cloudfoundry.identity.uaa.login.test.ThymeleafConfig;
import org.cloudfoundry.identity.uaa.user.UaaAuthority;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.test.annotation.DirtiesContext;
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.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = InvitationsControllerTest.ContextConfiguration.class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class InvitationsControllerTest {
private MockMvc mockMvc;
@Autowired
ConfigurableWebApplicationContext webApplicationContext;
@Autowired
InvitationsService invitationsService;
@Autowired
ExpiringCodeService expiringCodeService;
@Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.build();
}
@After
public void tearDown() {
SecurityContextHolder.clearContext();
}
@Test
public void testNewInvitePage() throws Exception {
MockHttpServletRequestBuilder get = get("/invitations/new");
mockMvc.perform(get)
.andExpect(status().isOk())
.andExpect(view().name("invitations/new_invite"));
}
@Test
public void testSendInvitationEmail() throws Exception {
UaaPrincipal p = new UaaPrincipal("123","marissa","marissa@test.org", Origin.UAA,"");
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(p, "", UaaAuthority.USER_AUTHORITIES);
assertTrue(auth.isAuthenticated());
MockSecurityContext mockSecurityContext = new MockSecurityContext(auth);
SecurityContextHolder.setContext(mockSecurityContext);
MockHttpSession session = new MockHttpSession();
session.setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
mockSecurityContext
);
MockHttpServletRequestBuilder post = post("/invitations/new.do")
.param("email", "user1@example.com");
mockMvc.perform(post)
.andExpect(status().isFound())
.andExpect(redirectedUrl("sent"));
verify(invitationsService).inviteUser("user1@example.com", "marissa");
}
@Test
public void testSendInvitationEmailToExistingVerifiedUser() throws Exception {
UaaPrincipal p = new UaaPrincipal("123","marissa","marissa@test.org", Origin.UAA,"");
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(p, "", UaaAuthority.USER_AUTHORITIES);
assertTrue(auth.isAuthenticated());
MockSecurityContext mockSecurityContext = new MockSecurityContext(auth);
SecurityContextHolder.setContext(mockSecurityContext);
MockHttpSession session = new MockHttpSession();
session.setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
mockSecurityContext
);
MockHttpServletRequestBuilder post = post("/invitations/new.do")
.param("email", "user1@example.com");
doThrow(new UaaException("",409)).when(invitationsService).inviteUser("user1@example.com", "marissa");
mockMvc.perform(post)
.andExpect(status().isUnprocessableEntity())
.andExpect(view().name("invitations/new_invite"))
.andExpect(model().attribute("error_message_code", "existing_user"));
}
@Test
public void testSendInvitationWithInvalidEmail() throws Exception {
UaaPrincipal p = new UaaPrincipal("123","marissa","marissa@test.org", Origin.UAA,"");
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(p, "", UaaAuthority.USER_AUTHORITIES);
assertTrue(auth.isAuthenticated());
MockSecurityContext mockSecurityContext = new MockSecurityContext(auth);
SecurityContextHolder.setContext(mockSecurityContext);
MockHttpSession session = new MockHttpSession();
session.setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
mockSecurityContext
);
MockHttpServletRequestBuilder post = post("/invitations/new.do")
.param("email", "not_a_real_email");
mockMvc.perform(post)
.andExpect(status().isUnprocessableEntity())
.andExpect(model().attribute("error_message_code", "invalid_email"))
.andExpect(view().name("invitations/new_invite"));
verifyZeroInteractions(invitationsService);
}
@Test
public void testAcceptInvitationsPage() throws Exception {
Map<String,String> codeData = new HashMap<>();
codeData.put("user_id", "user-id-001");
codeData.put("email", "user@example.com");
when(expiringCodeService.verifyCode("the_secret_code")).thenReturn(codeData);
MockHttpServletRequestBuilder get = get("/invitations/accept")
.param("code", "the_secret_code");
mockMvc.perform(get)
.andExpect(status().isOk())
.andExpect(model().attribute("user_id", "user-id-001"))
.andExpect(model().attribute("email", "user@example.com"))
.andExpect(view().name("invitations/accept_invite"));
UaaPrincipal principal = ((UaaPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal());
assertEquals("user-id-001", principal.getId());
assertEquals("user@example.com", principal.getName());
assertEquals("user@example.com", principal.getEmail());
}
@Test
public void testAcceptInvitePageWithExpiredCode() throws Exception {
doThrow(new CodeNotFoundException("code expired")).when(expiringCodeService).verifyCode("the_secret_code");
MockHttpServletRequestBuilder get = get("/invitations/accept").param("code", "the_secret_code");
mockMvc.perform(get)
.andExpect(status().isUnprocessableEntity())
.andExpect(model().attribute("error_message_code", "code_expired"))
.andExpect(view().name("invitations/accept_invite"))
.andExpect(xpath("//*[@class='email-display']").doesNotExist())
.andExpect(xpath("//form").doesNotExist());
assertNull(SecurityContextHolder.getContext().getAuthentication());
}
@Test
public void testAcceptInvite() throws Exception {
UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", Origin.UAA, null);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(uaaPrincipal, null, UaaAuthority.USER_AUTHORITIES);
SecurityContextHolder.getContext().setAuthentication(token);
MockHttpServletRequestBuilder post = post("/invitations/accept.do")
.param("password", "password")
.param("password_confirmation", "password")
.param("client_id", "");
mockMvc.perform(post)
.andExpect(status().isFound())
.andExpect(redirectedUrl("/home"));
verify(invitationsService).acceptInvitation("user-id-001","user@example.com", "password", "");
}
@Test
public void testAcceptInviteWithClientRedirect() throws Exception {
UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", Origin.UAA, null);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(uaaPrincipal, null, UaaAuthority.USER_AUTHORITIES);
SecurityContextHolder.getContext().setAuthentication(token);
when(invitationsService.acceptInvitation("user-id-001", "user@example.com", "password", "app")).thenReturn("http://localhost:8080/app");
MockHttpServletRequestBuilder post = post("/invitations/accept.do")
.param("password", "password")
.param("password_confirmation", "password")
.param("client_id", "app");
mockMvc.perform(post)
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost:8080/app"));
}
@Test
public void testAcceptInviteWithoutMatchingPasswords() throws Exception {
UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", Origin.UAA, null);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(uaaPrincipal, null, UaaAuthority.USER_AUTHORITIES);
SecurityContextHolder.getContext().setAuthentication(token);
MockHttpServletRequestBuilder post = post("/invitations/accept.do")
.param("password", "password")
.param("password_confirmation", "does not match")
.param("client_id", "");
mockMvc.perform(post)
.andExpect(status().isUnprocessableEntity())
.andExpect(model().attribute("error_message_code", "form_error"))
.andExpect(model().attribute("email", "user@example.com"))
.andExpect(view().name("invitations/accept_invite"));
verifyZeroInteractions(invitationsService);
}
public static class MockSecurityContext implements SecurityContext {
private static final long serialVersionUID = -1386535243513362694L;
private Authentication authentication;
public MockSecurityContext(Authentication authentication) {
this.authentication = authentication;
}
@Override
public Authentication getAuthentication() {
return this.authentication;
}
@Override
public void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
}
@Configuration
@EnableWebMvc
@Import(ThymeleafConfig.class)
static class ContextConfiguration extends WebMvcConfigurerAdapter {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Bean
BuildInfo buildInfo() {
return new BuildInfo();
}
@Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource();
resourceBundleMessageSource.setBasename("messages");
return resourceBundleMessageSource;
}
@Bean
InvitationsService invitationsService() {
return Mockito.mock(InvitationsService.class);
}
@Bean
InvitationsController invitationsController(InvitationsService invitationsService) {
return new InvitationsController(invitationsService);
}
@Bean
ExpiringCodeService expiringCodeService() {
return Mockito.mock(ExpiringCodeService.class);
}
}
}