InvitationsController { @RequestMapping(value = "/accept.do", method = POST) public String acceptInvitation(@RequestParam("password") String password, @RequestParam("password_confirmation") String passwordConfirmation, @RequestParam("code") String code, @RequestParam(value = "does_user_consent", required = false) boolean doesUserConsent, Model model, HttpServletResponse response) { PasswordConfirmationValidation validation = new PasswordConfirmationValidation(password, passwordConfirmation); UaaPrincipal principal = (UaaPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); final ExpiringCode expiringCode = expiringCodeStore.retrieveCode(code, IdentityZoneHolder.get().getId()); if (expiringCode == null || expiringCode.getData() == null) { logger.debug("Failing invitation. Code not found."); SecurityContextHolder.clearContext(); return handleUnprocessableEntity(model, response, "error_message_code", "code_expired", "invitations/accept_invite"); } Map<String, String> data = JsonUtils.readValue(expiringCode.getData(), new TypeReference<>() { }); if (principal == null || data.get("user_id") == null || !data.get("user_id").equals(principal.getId())) { logger.debug("Failing invitation. Code and user ID mismatch."); SecurityContextHolder.clearContext(); return handleUnprocessableEntity(model, response, "error_message_code", "code_expired", "invitations/accept_invite"); } final String newCode = expiringCodeStore.generateCode(expiringCode.getData(), new Timestamp(System.currentTimeMillis() + (10 * 60 * 1000)), expiringCode.getIntent(), IdentityZoneHolder.get().getId()).getCode(); BrandingInformation zoneBranding = IdentityZoneHolder.get().getConfig().getBranding(); if (zoneBranding != null && zoneBranding.getConsent() != null && !doesUserConsent) { return processErrorReload(newCode, model, principal.getEmail(), response, "error_message_code", "missing_consent"); } if (!validation.valid()) { return processErrorReload(newCode, model, principal.getEmail(), response, "error_message_code", validation.getMessageCode()); } try { passwordValidator.validate(password); } catch (InvalidPasswordException e) { return processErrorReload(newCode, model, principal.getEmail(), response, "error_message", e.getMessagesAsOneString()); } AcceptedInvitation invitation; try { invitation = invitationsService.acceptInvitation(newCode, password); } catch (HttpClientErrorException e) { return handleUnprocessableEntity(model, response, "error_message_code", "code_expired", "invitations/accept_invite"); } String res = "redirect:/login?success=invite_accepted"; if (!invitation.getRedirectUri().equals("/home")) { res += "&" + FORM_REDIRECT_PARAMETER + "=" + invitation.getRedirectUri(); } return res; } InvitationsController(
            final InvitationsService invitationsService,
            final ExpiringCodeStore expiringCodeStore,
            final PasswordValidator passwordValidator,
            final IdentityProviderProvisioning identityProviderProvisioning,
            final DynamicZoneAwareAuthenticationManager zoneAwareAuthenticationManager,
            final UaaUserDatabase userDatabase,
            final @Qualifier("samlEntityID") String spEntityID,
            final ScimUserProvisioning userProvisioning,
            final @Qualifier("externalOAuthProviderConfigurator") ExternalOAuthProviderConfigurator externalOAuthProviderConfigurator); @RequestMapping(value = {"/sent", "/new", "/new.do"}) void return404(HttpServletResponse response); @RequestMapping(value = "/accept", method = GET, params = {"code"}) String acceptInvitePage(@RequestParam String code, Model model, HttpServletRequest request, HttpServletResponse response); @RequestMapping(value = "/accept.do", method = POST) String acceptInvitation(@RequestParam("password") String password,
                                   @RequestParam("password_confirmation") String passwordConfirmation,
                                   @RequestParam("code") String code,
                                   @RequestParam(value = "does_user_consent", required = false) boolean doesUserConsent,
                                   Model model,
                                   HttpServletResponse response); @RequestMapping(value = "/accept_enterprise.do", method = POST) String acceptLdapInvitation(@RequestParam("enterprise_username") String username,
                                       @RequestParam("enterprise_password") String password,
                                       @RequestParam("enterprise_email") String email,
                                       @RequestParam("code") String code,
                                       Model model, HttpServletResponse response);  }
@Test public void unverifiedLdapUser_acceptsInvite_byLoggingIn() throws Exception { Map<String, String> codeData = getInvitationsCode(LDAP); when(expiringCodeStore.retrieveCode("the_secret_code", IdentityZoneHolder.get().getId())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); when(expiringCodeStore.generateCode(anyString(), any(), eq(null), eq(IdentityZoneHolder.get().getId()))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); DynamicLdapAuthenticationManager ldapAuthenticationManager = mock(DynamicLdapAuthenticationManager.class); when(zoneAwareAuthenticationManager.getLdapAuthenticationManager(any(), any())).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(any())).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", IdentityZoneHolder.get().getId())).thenReturn(invitedUser); when(invitationsService.acceptInvitation(anyString(), anyString())).thenReturn(new AcceptedInvitation("blah.test.com", new ScimUser())); when(expiringCodeStore.generateCode(anyString(), any(), eq(null), eq(IdentityZoneHolder.get().getId()))).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("/login?success=invite_accepted&form_redirect_uri=blah.test.com")) .andReturn(); verify(ldapActual).authenticate(any()); ArgumentCaptor<ScimUser> userArgumentCaptor = ArgumentCaptor.forClass(ScimUser.class); verify(scimUserProvisioning).update(anyString(), userArgumentCaptor.capture(), eq(IdentityZoneHolder.get().getId())); ScimUser value = userArgumentCaptor.getValue(); assertEquals("test-ldap-user", value.getUserName()); assertEquals("user@example.com", value.getPrimaryEmail()); verify(ldapAuthenticationManager).authenticate(any()); }
@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"); codeData.put("origin", "some-origin"); when(expiringCodeStore.retrieveCode("the_secret_code", IdentityZoneHolder.get().getId())).thenReturn(createCode(codeData), null); when(expiringCodeStore.generateCode(anyString(), any(), eq(INVITATION.name()), eq(IdentityZoneHolder.get().getId()))).thenReturn(createCode(codeData)); when(invitationsService.acceptInvitation(anyString(), eq(""))).thenReturn(new 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")); }
@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", IdentityZoneHolder.get().getId())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), "incorrect-code-intent")); when(expiringCodeStore.generateCode(anyString(), any(), eq(null), eq(IdentityZoneHolder.get().getId()))).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 missing_code() throws Exception { MockHttpServletRequestBuilder post = startAcceptInviteFlow("a", "a"); when(expiringCodeStore.retrieveCode("thecode", IdentityZoneHolder.get().getId())).thenReturn(null); IdentityProvider identityProvider = new IdentityProvider(); identityProvider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin("uaa", "uaa")).thenReturn(identityProvider); mockMvc.perform(post) .andExpect(status().isUnprocessableEntity()) .andExpect(model().attribute("error_message_code", "code_expired")) .andExpect(view().name("invitations/accept_invite")); verify(expiringCodeStore).retrieveCode("thecode", IdentityZoneHolder.get().getId()); verify(expiringCodeStore, never()).generateCode(anyString(), any(), anyString(), eq(IdentityZoneHolder.get().getId())); verify(invitationsService, never()).acceptInvitation(anyString(), anyString()); }
@Test public void invalid_principal_id() throws Exception { MockHttpServletRequestBuilder post = startAcceptInviteFlow("a", "a"); Map<String,String> codeData = getInvitationsCode(OriginKeys.UAA); codeData.put("user_id", "invalid id"); String codeDataString = JsonUtils.writeValueAsString(codeData); when(expiringCodeStore.retrieveCode("thecode", IdentityZoneHolder.get().getId())).thenReturn(new ExpiringCode("thecode", new Timestamp(1), codeDataString, INVITATION.name()), null); IdentityProvider identityProvider = new IdentityProvider(); identityProvider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin("uaa", "uaa")).thenReturn(identityProvider); mockMvc.perform(post) .andExpect(status().isUnprocessableEntity()) .andExpect(model().attribute("error_message_code", "code_expired")) .andExpect(view().name("invitations/accept_invite")); verify(expiringCodeStore).retrieveCode("thecode", IdentityZoneHolder.get().getId()); verify(expiringCodeStore, never()).generateCode(anyString(), any(), anyString(), eq(IdentityZoneHolder.get().getId())); verify(invitationsService, never()).acceptInvitation(anyString(), anyString()); }
@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); String codeDataString = JsonUtils.writeValueAsString(codeData); when(expiringCodeStore.retrieveCode("thecode", IdentityZoneHolder.get().getId())).thenReturn(new ExpiringCode("thecode", new Timestamp(1), codeDataString, INVITATION.name()), null); when(expiringCodeStore.retrieveCode("thenewcode", IdentityZoneHolder.get().getId())).thenReturn(new ExpiringCode("thenewcode", new Timestamp(1), codeDataString, INVITATION.name()), null); when(expiringCodeStore.generateCode(eq(codeDataString), any(), eq(INVITATION.name()), eq(IdentityZoneHolder.get().getId()))).thenReturn( new ExpiringCode("thenewcode", new Timestamp(1), codeDataString, INVITATION.name()), new ExpiringCode("thenewcode2", new Timestamp(1), codeDataString, INVITATION.name()) ); IdentityProvider identityProvider = new IdentityProvider(); identityProvider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin("uaa", "uaa")).thenReturn(identityProvider); mockMvc.perform(post) .andExpect(status().isFound()) .andExpect(model().attribute("error_message", "Msg 1c Msg 2c")) .andExpect(model().attribute("code", "thenewcode2")) .andExpect(view().name("redirect:accept")); verify(expiringCodeStore).retrieveCode("thecode", IdentityZoneHolder.get().getId()); verify(expiringCodeStore, times(2)).generateCode(anyString(), any(), anyString(), eq(IdentityZoneHolder.get().getId())); 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"); Map<String,String> codeData = getInvitationsCode(OriginKeys.UAA); String codeDataString = JsonUtils.writeValueAsString(codeData); ExpiringCode thecode = new ExpiringCode("thecode", new Timestamp(1), codeDataString, INVITATION.name()); ExpiringCode thenewcode = new ExpiringCode("thenewcode", new Timestamp(1), codeDataString, INVITATION.name()); ExpiringCode thenewcode2 = new ExpiringCode("thenewcode2", new Timestamp(1), codeDataString, INVITATION.name()); when(expiringCodeStore.retrieveCode("thecode", IdentityZoneHolder.get().getId())).thenReturn(thecode, null); when(expiringCodeStore.retrieveCode("thenewcode", IdentityZoneHolder.get().getId())).thenReturn(thenewcode, null); when(expiringCodeStore.generateCode(eq(codeDataString), any(), eq(INVITATION.name()), eq(IdentityZoneHolder.get().getId()))) .thenReturn(thenewcode) .thenReturn(thenewcode2); when(invitationsService.acceptInvitation(anyString(), eq("passw0rd"))).thenReturn(new AcceptedInvitation("/home", user)); MvcResult res = mockMvc.perform(post) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?success=invite_accepted")).andReturn(); verify(invitationsService).acceptInvitation(anyString(), eq("passw0rd")); }
@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); Map<String,String> codeData = getInvitationsCode(OriginKeys.UAA); String codeDataString = JsonUtils.writeValueAsString(codeData); when(expiringCodeStore.retrieveCode("thecode", IdentityZoneHolder.get().getId())).thenReturn(new ExpiringCode("thecode", new Timestamp(1), codeDataString, INVITATION.name()), null); when(expiringCodeStore.generateCode(eq(codeDataString), any(), eq(INVITATION.name()), eq(IdentityZoneHolder.get().getId()))).thenReturn(new ExpiringCode("thenewcode", new Timestamp(1), codeDataString, INVITATION.name())); when(invitationsService.acceptInvitation(anyString(), eq("password"))).thenReturn(new 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("/login?success=invite_accepted&form_redirect_uri=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()); Map<String,String> codeData = getInvitationsCode(OriginKeys.UAA); String codeDataString = JsonUtils.writeValueAsString(codeData); when(expiringCodeStore.retrieveCode("thecode", IdentityZoneHolder.get().getId())).thenReturn(new ExpiringCode("thecode", new Timestamp(1), codeDataString, INVITATION.name()), null); when(expiringCodeStore.generateCode(eq(codeDataString), any(), eq(INVITATION.name()), eq(IdentityZoneHolder.get().getId()))).thenReturn(new ExpiringCode("thenewcode", new Timestamp(1), codeDataString, INVITATION.name())); when(invitationsService.acceptInvitation(anyString(), eq("password"))).thenReturn(new 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("/login?success=invite_accepted")); }
@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); Map<String,String> codeData = getInvitationsCode(OriginKeys.UAA); String codeDataString = JsonUtils.writeValueAsString(codeData); when(expiringCodeStore.retrieveCode("thecode", IdentityZoneHolder.get().getId())).thenReturn(new ExpiringCode("thecode", new Timestamp(1), codeDataString, INVITATION.name()), null); when(expiringCodeStore.generateCode(eq(codeDataString), any(), eq(INVITATION.name()), eq(IdentityZoneHolder.get().getId()))).thenReturn(new ExpiringCode("thenewcode", new Timestamp(1), codeDataString, INVITATION.name())); 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(OriginKeys.UAA); String codeDataString = JsonUtils.writeValueAsString(codeData); when(expiringCodeStore.retrieveCode("thecode", IdentityZoneHolder.get().getId())).thenReturn(new ExpiringCode("thecode", new Timestamp(1), codeDataString, INVITATION.name()), null); when(expiringCodeStore.retrieveCode("thenewcode", IdentityZoneHolder.get().getId())).thenReturn(new ExpiringCode("thenewcode", new Timestamp(1), codeDataString, INVITATION.name()), null); when(expiringCodeStore.generateCode(eq(codeDataString), any(), eq(INVITATION.name()), eq(IdentityZoneHolder.get().getId()))).thenReturn( new ExpiringCode("thenewcode", new Timestamp(1), codeDataString, INVITATION.name()), new ExpiringCode("thenewcode2", new Timestamp(1), codeDataString, INVITATION.name()) ); IdentityProvider identityProvider = new IdentityProvider(); identityProvider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin("uaa", "uaa")).thenReturn(identityProvider); mockMvc.perform(post) .andExpect(status().isFound()) .andExpect(model().attribute("error_message_code", "form_error")) .andExpect(model().attribute("code", "thenewcode2")) .andExpect(view().name("redirect:accept")); verify(expiringCodeStore).retrieveCode("thecode", IdentityZoneHolder.get().getId()); verify(expiringCodeStore, times(2)).generateCode(anyString(), any(), anyString(), eq(IdentityZoneHolder.get().getId())); verify(invitationsService, never()).acceptInvitation(anyString(), anyString()); }
@Test public void testAcceptInvite_worksWithConsentProvided() throws Exception { IdentityZone defaultZone = IdentityZoneHolder.get(); BrandingInformation branding = new BrandingInformation(); branding.setConsent(new Consent("paying Jaskanwal Pawar & Jennifer Hamon each a million dollars", null)); defaultZone.getConfig().setBranding(branding); IdentityProvider identityProvider = new IdentityProvider(); identityProvider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin(anyString(), anyString())).thenReturn(identityProvider); Map<String,String> codeData = getInvitationsCode(OriginKeys.UAA); String codeDataString = JsonUtils.writeValueAsString(codeData); ExpiringCode expiringCode = new ExpiringCode("thecode", new Timestamp(1), codeDataString, INVITATION.name()); when(expiringCodeStore.retrieveCode(anyString(), eq(IdentityZoneHolder.get().getId()))) .thenReturn(expiringCode); when(expiringCodeStore.generateCode(anyString(), any(), eq(INVITATION.name()), eq(IdentityZoneHolder.get().getId()))) .thenReturn(expiringCode); when(invitationsService.acceptInvitation(anyString(), anyString())) .thenReturn(new AcceptedInvitation(codeData.get("redirect_uri"), null)); MvcResult mvcResult = mockMvc.perform(startAcceptInviteFlow("password", "password") .param("does_user_consent", "true")) .andReturn(); assertThat(mvcResult.getResponse().getHeader("Location"), containsString(codeData.get("redirect_uri"))); defaultZone.getConfig().setBranding(null); }