package org.cloudfoundry.identity.uaa.invitations; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.authentication.manager.DynamicLdapAuthenticationManager; import org.cloudfoundry.identity.uaa.authentication.manager.DynamicZoneAwareAuthenticationManager; 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.home.BuildInfo; import org.cloudfoundry.identity.uaa.login.ThymeleafConfig; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.OIDCIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.ldap.ExtendedLdapUserDetails; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; import org.cloudfoundry.identity.uaa.scim.validate.PasswordValidator; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; 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.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.provider.ClientDetailsService; 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.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.client.HttpClientErrorException; 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; import java.net.URL; import java.sql.Timestamp; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import static org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType.INVITATION; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LDAP; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.http.HttpStatus.BAD_REQUEST; 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.content; 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; @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 ExpiringCodeStore expiringCodeStore; @Autowired PasswordValidator passwordValidator; @Autowired ClientDetailsService clientDetailsService; @Autowired IdentityProviderProvisioning providerProvisioning; @Autowired UaaUserDatabase userDatabase; @Autowired DynamicZoneAwareAuthenticationManager zoneAwareAuthenticationManager; @Autowired ScimUserProvisioning scimUserProvisioning; @Before public void setUp() throws Exception { SecurityContextHolder.clearContext(); mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) .build(); } @After public void tearDown() { SecurityContextHolder.clearContext(); } @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"); codeData.put("client_id", "client-id"); codeData.put("redirect_uri", "blah.test.com"); when(expiringCodeStore.retrieveCode("code")).thenReturn(createCode(codeData), null); when(expiringCodeStore.generateCode(anyString(), anyObject(), eq(INVITATION.name()))).thenReturn(createCode(codeData)); IdentityProvider provider = new IdentityProvider(); provider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin(any(), any())).thenReturn(provider); mockMvc.perform(get("/invitations/accept").param("code", "code")) .andExpect(status().isOk()) .andExpect(model().attribute("email", "user@example.com")) .andExpect(model().attribute("code", "code")) .andExpect(view().name("invitations/accept_invite")); UaaPrincipal principal = ((UaaPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal()); assertTrue(SecurityContextHolder.getContext().getAuthentication() instanceof AnonymousAuthenticationToken); assertEquals("user-id-001", principal.getId()); assertEquals("user@example.com", principal.getName()); assertEquals("user@example.com", principal.getEmail()); mockMvc.perform(get("/invitations/accept").param("code", "code")) .andExpect(status().isUnprocessableEntity()) .andExpect(view().name("invitations/accept_invite")) .andExpect(model().attribute("error_message_code", "code_expired")); } @Test public void incorrectCodeIntent() throws Exception { Map<String,String> codeData = new HashMap<>(); codeData.put("user_id", "user-id-001"); codeData.put("email", "user@example.com"); codeData.put("client_id", "client-id"); codeData.put("redirect_uri", "blah.test.com"); when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), "incorrect-code-intent"));; MockHttpServletRequestBuilder get = get("/invitations/accept") .param("code", "the_secret_code"); mockMvc.perform(get).andExpect(status().isUnprocessableEntity()); } @Test public void acceptInvitePage_for_unverifiedSamlUser() throws Exception { Map<String,String> codeData = getInvitationsCode("test-saml"); when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(createCode(codeData)); when(expiringCodeStore.generateCode(anyString(), anyObject(), eq(INVITATION.name()))).thenReturn(createCode(codeData)); IdentityProvider provider = new IdentityProvider(); SamlIdentityProviderDefinition definition = new SamlIdentityProviderDefinition() .setMetaDataLocation("http://test.saml.com") .setIdpEntityAlias("test-saml") .setNameID("test") .setLinkText("testsaml") .setIconUrl("test.com") .setZoneId(IdentityZone.getUaa().getId()); provider.setConfig(definition); provider.setType(OriginKeys.SAML); when(providerProvisioning.retrieveByOrigin(eq("test-saml"), anyString())).thenReturn(provider); MockHttpServletRequestBuilder get = get("/invitations/accept") .param("code", "the_secret_code"); MvcResult result = mockMvc.perform(get) .andExpect(redirectedUrl("/saml/discovery?returnIDParam=idp&entityID=sp-entity-id&idp=test-saml&isPassive=true")) .andReturn(); assertEquals(true, result.getRequest().getSession().getAttribute("IS_INVITE_ACCEPTANCE")); assertEquals("user-id-001", result.getRequest().getSession().getAttribute("user_id")); } @Test public void acceptInvitePage_for_unverifiedOIDCUser() throws Exception { Map<String,String> codeData = getInvitationsCode("test-oidc"); when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(createCode(codeData)); when(expiringCodeStore.generateCode(anyString(), anyObject(), eq(INVITATION.name()))).thenReturn(createCode(codeData)); OIDCIdentityProviderDefinition definition = new OIDCIdentityProviderDefinition(); definition.setAuthUrl(new URL("https://oidc10.auth.url")); IdentityProvider provider = new IdentityProvider(); provider.setConfig(definition); provider.setType(OriginKeys.OIDC10); when(providerProvisioning.retrieveByOrigin(eq("test-oidc"), anyString())).thenReturn(provider); MockHttpServletRequestBuilder get = get("/invitations/accept") .param("code", "the_secret_code"); MvcResult result = mockMvc.perform(get) .andExpect(redirectedUrl("https://oidc10.auth.url?client_id=" + definition.getRelyingPartyId() + "&response_type=code&redirect_uri=http://null/login/callback/" + provider.getOriginKey())) .andReturn(); assertEquals(true, result.getRequest().getSession().getAttribute("IS_INVITE_ACCEPTANCE")); assertEquals("user-id-001", result.getRequest().getSession().getAttribute("user_id")); } @Test public void acceptInvitePage_for_unverifiedLdapUser() throws Exception { Map<String, String> codeData = getInvitationsCode(LDAP); when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(createCode(codeData)); when(expiringCodeStore.generateCode(anyString(), anyObject(), eq(INVITATION.name()))).thenReturn(createCode(codeData)); IdentityProvider provider = new IdentityProvider(); provider.setType(LDAP); when(providerProvisioning.retrieveByOrigin(eq(LDAP), anyString())).thenReturn(provider); MockHttpServletRequestBuilder get = get("/invitations/accept") .param("code", "the_secret_code"); mockMvc.perform(get) .andExpect(view().name("invitations/accept_invite")) .andExpect(status().isOk()) .andExpect(content().string(containsString("Email: " + "user@example.com"))) .andExpect(content().string(containsString("Sign in with enterprise credentials:"))) .andExpect(content().string(containsString("username"))) .andExpect(model().attribute("code", "code")) .andReturn(); } private Map<String, String> getInvitationsCode(String origin) { Map<String, String> codeData = new HashMap<>(); codeData.put("user_id", "user-id-001"); codeData.put("email", "user@example.com"); codeData.put("client_id", "client-id"); codeData.put("redirect_uri", "blah.test.com"); codeData.put("origin", origin); return codeData; } @Test public void unverifiedLdapUser_acceptsInvite_byLoggingIn() throws Exception { Map<String, String> codeData = getInvitationsCode(LDAP); when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); when(expiringCodeStore.generateCode(anyString(),anyObject(), eq(null))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); DynamicLdapAuthenticationManager ldapAuthenticationManager = mock(DynamicLdapAuthenticationManager.class); when(zoneAwareAuthenticationManager.getLdapAuthenticationManager(anyObject(), anyObject())).thenReturn(ldapAuthenticationManager); AuthenticationManager ldapActual = mock(AuthenticationManager.class); when(ldapAuthenticationManager.getLdapManagerActual()).thenReturn(ldapActual); Authentication auth = mock(Authentication.class); when(auth.isAuthenticated()).thenReturn(true); when(ldapActual.authenticate(anyObject())).thenReturn(auth); ExtendedLdapUserDetails extendedLdapUserDetails = mock(ExtendedLdapUserDetails.class); when(auth.getPrincipal()).thenReturn(extendedLdapUserDetails); when(extendedLdapUserDetails.getEmailAddress()).thenReturn("user@example.com"); when(extendedLdapUserDetails.getUsername()).thenReturn("test-ldap-user"); ScimUser invitedUser = new ScimUser("user-id-001", "user@example.com", "g", "f"); invitedUser.setPrimaryEmail("user@example.com"); when(scimUserProvisioning.retrieve("user-id-001")).thenReturn(invitedUser); when(invitationsService.acceptInvitation(anyString(), anyString())).thenReturn(new InvitationsService.AcceptedInvitation("blah.test.com", new ScimUser())); when(expiringCodeStore.generateCode(anyString(), anyObject(), eq(null))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); mockMvc.perform(post("/invitations/accept_enterprise.do") .param("enterprise_username", "test-ldap-user") .param("enterprise_password", "password") .param("enterprise_email", "email") .param("code", "the_secret_code")) .andExpect(redirectedUrl("blah.test.com")) .andReturn(); verify(ldapActual).authenticate(anyObject()); ArgumentCaptor<ScimUser> userArgumentCaptor = ArgumentCaptor.forClass(ScimUser.class); verify(scimUserProvisioning).update(anyString(), userArgumentCaptor.capture()); ScimUser value = userArgumentCaptor.getValue(); assertEquals("test-ldap-user", value.getUserName()); assertEquals("user@example.com", value.getPrimaryEmail()); verify(ldapAuthenticationManager).authenticate(anyObject()); } @Test public void unverifiedLdapUser_acceptsInvite_byLoggingIn_bad_credentials() throws Exception { Map<String, String> codeData = getInvitationsCode("ldap"); when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); when(expiringCodeStore.generateCode(anyString(),anyObject(), eq(null))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); DynamicLdapAuthenticationManager ldapAuthenticationManager = mock(DynamicLdapAuthenticationManager.class); when(zoneAwareAuthenticationManager.getLdapAuthenticationManager(anyObject(), anyObject())).thenReturn(ldapAuthenticationManager); AuthenticationManager ldapActual = mock(AuthenticationManager.class); when(ldapAuthenticationManager.getLdapManagerActual()).thenReturn(ldapActual); Authentication auth = mock(Authentication.class); when(auth.isAuthenticated()).thenReturn(true); when(ldapActual.authenticate(anyObject())).thenThrow(new BadCredentialsException("bad creds")); mockMvc.perform(post("/invitations/accept_enterprise.do") .param("enterprise_username", "test-ldap-user") .param("enterprise_password", "password") .param("enterprise_email", "email") .param("code", "the_secret_code")) .andExpect(model().attribute("ldap", true)) .andExpect(model().attribute("email", "email")) .andExpect(model().attribute("error_message", "bad_credentials")) .andReturn(); } @Test public void unverifiedLdapUser_acceptsInvite_byLoggingIn_whereEmailDoesNotMatchAuthenticatedEmail() throws Exception { Map<String, String> codeData = getInvitationsCode(LDAP); when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); DynamicLdapAuthenticationManager ldapAuthenticationManager = mock(DynamicLdapAuthenticationManager.class); when(zoneAwareAuthenticationManager.getLdapAuthenticationManager(anyObject(), anyObject())).thenReturn(ldapAuthenticationManager); AuthenticationManager ldapActual = mock(AuthenticationManager.class); when(ldapAuthenticationManager.getLdapManagerActual()).thenReturn(ldapActual); Authentication auth = mock(Authentication.class); when(ldapActual.authenticate(anyObject())).thenReturn(auth); ExtendedLdapUserDetails extendedLdapUserDetails = mock(ExtendedLdapUserDetails.class); when(auth.getPrincipal()).thenReturn(extendedLdapUserDetails); when(extendedLdapUserDetails.getEmailAddress()).thenReturn("different-email@example.com"); ScimUser invitedUser = new ScimUser("user-id-001", "user@example.com", "g", "f"); invitedUser.setPrimaryEmail("user@example.com"); when(scimUserProvisioning.retrieve("user-id-001")).thenReturn(invitedUser); when(expiringCodeStore.generateCode(anyString(), anyObject(), eq(null))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); mockMvc.perform(post("/invitations/accept_enterprise.do") .param("enterprise_username", "test-ldap-user") .param("enterprise_password", "password") .param("enterprise_email", "email") .param("code", "the_secret_code")) .andExpect(status().isUnprocessableEntity()) .andExpect(view().name("invitations/accept_invite")) .andExpect(content().string(containsString("Email: " + "user@example.com"))) .andExpect(content().string(containsString("Sign in with enterprise credentials:"))) .andExpect(content().string(containsString("username"))) .andExpect(model().attribute("code", "code")) .andExpect(model().attribute("error_message", "invite.email_mismatch")) .andReturn(); verify(ldapActual).authenticate(anyObject()); } @Test public void acceptInvitePage_for_verifiedUser() throws Exception { UaaUser user = new UaaUser("user@example.com", "", "user@example.com", "Given", "family"); user.modifyId("verified-user"); user.setVerified(true); when(userDatabase.retrieveUserById("verified-user")).thenReturn(user); Map<String,String> codeData = new HashMap<>(); codeData.put("user_id", "verified-user"); codeData.put("email", "user@example.com"); when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(createCode(codeData), null); when(expiringCodeStore.generateCode(anyString(), anyObject(), eq(INVITATION.name()))).thenReturn(createCode(codeData)); when(invitationsService.acceptInvitation(anyString(), eq(""))).thenReturn(new InvitationsService.AcceptedInvitation("blah.test.com", new ScimUser())); IdentityProvider provider = new IdentityProvider(); provider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin(anyString(), anyString())).thenReturn(provider); MockHttpServletRequestBuilder get = get("/invitations/accept") .param("code", "the_secret_code"); mockMvc.perform(get) .andExpect(redirectedUrl("blah.test.com")); } private ExpiringCode createCode(Map<String, String> codeData) { return new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), INVITATION.name()); } @Test public void incorrectGeneratedCodeIntent_for_verifiedUser() throws Exception { UaaUser user = new UaaUser("user@example.com", "", "user@example.com", "Given", "family"); user.modifyId("verified-user"); user.setVerified(true); when(userDatabase.retrieveUserById("verified-user")).thenReturn(user); Map<String,String> codeData = new HashMap<>(); codeData.put("user_id", "verified-user"); codeData.put("email", "user@example.com"); when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), "incorrect-code-intent")); when(expiringCodeStore.generateCode(anyString(), anyObject(), eq(null))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), "incorrect-code-intent")); doThrow(new HttpClientErrorException(BAD_REQUEST)).when(invitationsService).acceptInvitation(eq("incorrect-code-intent"), eq("")); MockHttpServletRequestBuilder get = get("/invitations/accept") .param("code", "the_secret_code"); mockMvc.perform(get).andExpect(status().isUnprocessableEntity()); } @Test public void testAcceptInvitePageWithExpiredCode() throws Exception { when(expiringCodeStore.retrieveCode(anyString())).thenReturn(null); 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 testAcceptInviteWithContraveningPassword() throws Exception { doThrow(new InvalidPasswordException(Arrays.asList("Msg 2c", "Msg 1c"))).when(passwordValidator).validate("a"); MockHttpServletRequestBuilder post = startAcceptInviteFlow("a", "a"); Map<String,String> codeData = getInvitationsCode(OriginKeys.UAA); when(expiringCodeStore.retrieveCode("thecode")).thenReturn(new ExpiringCode("thecode", new Timestamp(1), "{\"origin\":\"uaa\"}", "intent"), null); IdentityProvider identityProvider = new IdentityProvider(); identityProvider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin("uaa", "uaa")).thenReturn(identityProvider); when(expiringCodeStore.generateCode(anyString(), anyObject(), anyString())).thenReturn(createCode(codeData)); mockMvc.perform(post) .andExpect(status().isFound()) .andExpect(model().attribute("error_message", "Msg 1c Msg 2c")) .andExpect(model().attribute("code", "code")) .andExpect(view().name("redirect:accept")); verify(expiringCodeStore).retrieveCode("thecode"); verify(expiringCodeStore).generateCode(anyString(),anyObject(),anyString()); verify(invitationsService, never()).acceptInvitation(anyString(), anyString()); } @Test public void testAcceptInvite() throws Exception { ScimUser user = new ScimUser("user-id-001", "user@example.com","fname", "lname"); user.setPrimaryEmail(user.getUserName()); MockHttpServletRequestBuilder post = startAcceptInviteFlow("passw0rd","passw0rd"); when(invitationsService.acceptInvitation(anyString(), eq("passw0rd"))).thenReturn(new InvitationsService.AcceptedInvitation("/home", user)); mockMvc.perform(post) .andExpect(status().isFound()) .andExpect(redirectedUrl("/home")); verify(invitationsService).acceptInvitation(anyString(), eq("passw0rd")); } private MockHttpServletRequestBuilder startAcceptInviteFlow(String password, String passwordConfirmation) { UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", OriginKeys.UAA, null, IdentityZoneHolder.get().getId()); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(uaaPrincipal, null, UaaAuthority.USER_AUTHORITIES); SecurityContextHolder.getContext().setAuthentication(token); return post("/invitations/accept.do") .param("code","thecode") .param("password", password) .param("password_confirmation", passwordConfirmation); } @Test public void acceptInviteWithValidClientRedirect() throws Exception { UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", OriginKeys.UAA, null,IdentityZoneHolder.get().getId()); ScimUser user = new ScimUser(uaaPrincipal.getId(), uaaPrincipal.getName(),"fname", "lname"); user.setPrimaryEmail(user.getUserName()); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(uaaPrincipal, null, UaaAuthority.USER_AUTHORITIES); SecurityContextHolder.getContext().setAuthentication(token); when(invitationsService.acceptInvitation(anyString(), eq("password"))).thenReturn(new InvitationsService.AcceptedInvitation("valid.redirect.com", user)); MockHttpServletRequestBuilder post = post("/invitations/accept.do") .param("password", "password") .param("password_confirmation", "password") .param("code", "thecode"); mockMvc.perform(post) .andExpect(status().isFound()) .andExpect(redirectedUrl("valid.redirect.com")); } @Test public void acceptInviteWithInvalidClientRedirect() throws Exception { UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", OriginKeys.UAA, null,IdentityZoneHolder.get().getId()); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(uaaPrincipal, null, UaaAuthority.USER_AUTHORITIES); SecurityContextHolder.getContext().setAuthentication(token); ScimUser user = new ScimUser(uaaPrincipal.getId(), uaaPrincipal.getName(),"fname", "lname"); user.setPrimaryEmail(user.getUserName()); when(invitationsService.acceptInvitation(anyString(), eq("password"))).thenReturn(new InvitationsService.AcceptedInvitation("/home", user)); MockHttpServletRequestBuilder post = post("/invitations/accept.do") .param("code","thecode") .param("password", "password") .param("password_confirmation", "password"); mockMvc.perform(post) .andExpect(status().isFound()) .andExpect(redirectedUrl("/home")); } @Test public void invalidCodeOnAcceptPost() throws Exception { UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", OriginKeys.UAA, null,IdentityZoneHolder.get().getId()); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(uaaPrincipal, null, UaaAuthority.USER_AUTHORITIES); SecurityContextHolder.getContext().setAuthentication(token); doThrow(new HttpClientErrorException(BAD_REQUEST)).when(invitationsService).acceptInvitation(anyString(), anyString()); MockHttpServletRequestBuilder post = post("/invitations/accept.do") .param("code","thecode") .param("password", "password") .param("password_confirmation", "password"); mockMvc.perform(post) .andExpect(status().isUnprocessableEntity()) .andExpect(model().attribute("error_message_code", "code_expired")) .andExpect(view().name("invitations/accept_invite")); } @Test public void testAcceptInviteWithoutMatchingPasswords() throws Exception { MockHttpServletRequestBuilder post = startAcceptInviteFlow("a","b"); Map<String,String> codeData = getInvitationsCode("test-oidc"); when(expiringCodeStore.retrieveCode("thecode")).thenReturn(new ExpiringCode("thecode", new Timestamp(1), "{\"origin\":\"uaa\"}", "intent"), null); IdentityProvider identityProvider = new IdentityProvider(); identityProvider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin("uaa", "uaa")).thenReturn(identityProvider); when(expiringCodeStore.generateCode(anyString(), anyObject(), anyString())).thenReturn(createCode(codeData)); mockMvc.perform(post) .andExpect(status().isFound()) .andExpect(model().attribute("error_message_code", "form_error")) .andExpect(model().attribute("code", "code")) .andExpect(view().name("redirect:accept")); verify(expiringCodeStore).retrieveCode("thecode"); verify(expiringCodeStore).generateCode(anyString(),anyObject(),anyString()); verify(invitationsService, never()).acceptInvitation(anyString(), anyString()); } @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 UaaUserDatabase userDatabase() { UaaUserDatabase userDatabase = mock(UaaUserDatabase.class); UaaUser user = new UaaUser("user@example.com","","user@example.com","Given","family"); user = user.modifyId("user-id-001"); when (userDatabase.retrieveUserById(user.getId())).thenReturn(user); return userDatabase; } @Bean public DynamicZoneAwareAuthenticationManager dynamicZoneAwareAuthenticationManager() { return mock(DynamicZoneAwareAuthenticationManager.class); } @Bean public ScimUserProvisioning userProvisioning() { return mock(ScimUserProvisioning.class); } @Bean public ResourceBundleMessageSource messageSource() { ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource(); resourceBundleMessageSource.setBasename("messages"); return resourceBundleMessageSource; } @Bean InvitationsService invitationsService() { return mock(InvitationsService.class); } @Bean InvitationsController invitationsController(InvitationsService invitationsService, ExpiringCodeStore codeStore, PasswordValidator passwordPolicyValidator, IdentityProviderProvisioning providerProvisioning, UaaUserDatabase userDatabase, ScimUserProvisioning provisioning, DynamicZoneAwareAuthenticationManager zoneAwareAuthenticationManager) { InvitationsController result = new InvitationsController(invitationsService); result.setExpiringCodeStore(codeStore); result.setPasswordValidator(passwordPolicyValidator); result.setProviderProvisioning(providerProvisioning); result.setUserDatabase(userDatabase); result.setSpEntityID("sp-entity-id"); result.setZoneAwareAuthenticationManager(zoneAwareAuthenticationManager); result.setUserProvisioning(provisioning); return result; } @Bean ExpiringCodeStore expiringCodeStore() { return mock(ExpiringCodeStore.class); } @Bean PasswordValidator uaaPasswordValidator() { return mock(PasswordValidator.class); } @Bean IdentityProviderProvisioning providerProvisioning() { return mock (IdentityProviderProvisioning.class); } @Bean ClientDetailsService clientDetailsService() { return mock(ClientDetailsService.class); } @Bean DynamicZoneAwareAuthenticationManager zoneAwareAuthenticationManager() { return mock(DynamicZoneAwareAuthenticationManager.class); } } }