TotpMfaEndpoint implements ApplicationEventPublisherAware { @RequestMapping(value = {"/verify.do"}, method = RequestMethod.POST) public ModelAndView validateCode(Model model, @RequestParam("code") String code, @ModelAttribute("uaaMfaCredentials") UserGoogleMfaCredentials credentials, HttpServletRequest request, SessionStatus sessionStatus) throws UaaPrincipalIsNotInSession { UaaAuthentication uaaAuth = getUaaAuthentication(); UaaPrincipal uaaPrincipal = getSessionAuthPrincipal(); if (!this.mfaPolicy.isAllowed(uaaPrincipal.getId()).isAllowed()) { throw new AuthenticationPolicyRejectionException("Your account has been locked because of too many failed attempts to login."); } try { Integer codeValue = Integer.valueOf(code); if (mfaCredentialsProvisioning.isValidCode(credentials, codeValue)) { if (mfaCredentialsProvisioning.getUserGoogleMfaCredentials(uaaPrincipal.getId()) == null) { mfaCredentialsProvisioning.saveUserCredentials(credentials); } Set<String> authMethods = new HashSet<>(uaaAuth.getAuthenticationMethods()); authMethods.addAll(Arrays.asList("otp", "mfa")); uaaAuth.setAuthenticationMethods(authMethods); publish(new MfaAuthenticationSuccessEvent(getUaaUser(uaaPrincipal), uaaAuth, getMfaProvider().getType().toValue(), IdentityZoneHolder.getCurrentZoneId())); sessionStatus.setComplete(); SessionUtils.setSecurityContext(request.getSession(), SecurityContextHolder.getContext()); return new ModelAndView(new RedirectView(mfaCompleteUrl, true)); } logger.debug("Code authorization failed for user: " + uaaPrincipal.getId()); publish(new MfaAuthenticationFailureEvent(getUaaUser(uaaPrincipal), uaaAuth, getMfaProvider().getType().toValue(), IdentityZoneHolder.getCurrentZoneId())); model.addAttribute("error", "Incorrect code, please try again."); } catch (NumberFormatException | GoogleAuthenticatorException e) { logger.debug("Error validating the code for user: " + uaaPrincipal.getId() + ". Error: " + e.getMessage()); publish(new MfaAuthenticationFailureEvent(getUaaUser(uaaPrincipal), uaaAuth, getMfaProvider().getType().toValue(), IdentityZoneHolder.getCurrentZoneId())); model.addAttribute("error", "Incorrect code, please try again."); } return renderEnterCodePage(model, uaaPrincipal); } TotpMfaEndpoint(
            final UserGoogleMfaCredentialsProvisioning mfaCredentialsProvisioning,
            final MfaProviderProvisioning mfaProviderProvisioning,
            final @Qualifier("mfaCompleteUrl") String mfaCompleteUrl,
            final UaaUserDatabase userDatabase,
            final @Qualifier("mfaGlobalUserLoginPolicy") CommonLoginPolicy mfaPolicy); @ModelAttribute("uaaMfaCredentials") UserGoogleMfaCredentials getUaaMfaCredentials(); @RequestMapping(value = {"/register"}, method = RequestMethod.GET) String generateQrUrl(Model model,
                                @ModelAttribute("uaaMfaCredentials") UserGoogleMfaCredentials credentials); @RequestMapping(value = {"/manual"}, method = RequestMethod.GET) String manualRegistration(
      Model model,
      @ModelAttribute("uaaMfaCredentials") UserGoogleMfaCredentials credentials
    ); @RequestMapping(value = {"/verify"}, method = RequestMethod.GET) ModelAndView totpAuthorize(Model model); @RequestMapping(value = {"/verify.do"}, method = RequestMethod.POST) ModelAndView validateCode(Model model,
                                     @RequestParam("code") String code,
                                     @ModelAttribute("uaaMfaCredentials") UserGoogleMfaCredentials credentials,
                                     HttpServletRequest request,
                                     SessionStatus sessionStatus); @ExceptionHandler(UaaPrincipalIsNotInSession.class) ModelAndView handleUaaPrincipalIsNotInSession(); @ExceptionHandler(AuthenticationPolicyRejectionException.class) ModelAndView handleMFALockedOut(); @Override void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher);  }
@Test public void testValidOTPTakesToHomePage() throws Exception{ int code = 1234; when(userGoogleMfaCredentialsProvisioning.isValidCode(ArgumentMatchers.any(UserGoogleMfaCredentials.class), eq(code))).thenReturn(true); when(uaaAuthentication.getPrincipal()).thenReturn(new UaaPrincipal(userId, "Marissa", null, "uaa", null, null), null, null); when(mfaProviderProvisioning.retrieveByName(mfaProvider.getName(), IdentityZoneHolder.get().getId())).thenReturn(mfaProvider); when(userDb.retrieveUserByName("Marissa", "uaa")).thenReturn(new UaaUser(new UaaUserPrototype().withUsername("Marissa").withOrigin("uaa").withId("1234").withEmail("marissa@example.com"))); IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); SessionStatus sessionStatus = mock(SessionStatus.class); ModelAndView returnView = endpoint.validateCode( mock(Model.class), Integer.toString(code), mock(UserGoogleMfaCredentials.class), new MockHttpServletRequest(), sessionStatus); assertEquals("/login/mfa/completed", ((RedirectView)returnView.getView()).getUrl()); verify(sessionStatus, times(1)).setComplete(); verifyMfaEvent(MfaAuthenticationSuccessEvent.class); }
@Test public void testValidOTPActivatesUser() throws Exception { int code = 1234; when(userGoogleMfaCredentialsProvisioning.isValidCode(ArgumentMatchers.any(UserGoogleMfaCredentials.class), eq(code))).thenReturn(true); when(uaaAuthentication.getPrincipal()).thenReturn(new UaaPrincipal(userId, "Marissa", null, "uaa", null, null), null, null); when(mfaProviderProvisioning.retrieveByName(mfaProvider.getName(), IdentityZoneHolder.get().getId())).thenReturn(mfaProvider); when(userDb.retrieveUserByName("Marissa", "uaa")).thenReturn(new UaaUser(new UaaUserPrototype().withUsername("Marissa").withOrigin("uaa").withId("1234").withEmail("marissa@example.com"))); IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); SessionStatus sessionStatus = mock(SessionStatus.class); endpoint.validateCode(mock(Model.class), Integer.toString(code), mock(UserGoogleMfaCredentials.class), new MockHttpServletRequest(), sessionStatus ); verify(userGoogleMfaCredentialsProvisioning).saveUserCredentials(ArgumentMatchers.any(UserGoogleMfaCredentials.class)); verify(sessionStatus).setComplete(); verifyMfaEvent(MfaAuthenticationSuccessEvent.class); }
@Test public void testInvalidOTPReturnsError() throws Exception{ int code = 1234; when(userGoogleMfaCredentialsProvisioning.isValidCode(ArgumentMatchers.any(UserGoogleMfaCredentials.class), eq(code))).thenReturn(false); when(uaaAuthentication.getPrincipal()).thenReturn(new UaaPrincipal(userId, "Marissa", null, "uaa", null, null), null, null); when(mfaProviderProvisioning.retrieveByName(mfaProvider.getName(), IdentityZoneHolder.get().getId())).thenReturn(mfaProvider); when(userDb.retrieveUserByName("Marissa", "uaa")).thenReturn(new UaaUser(new UaaUserPrototype().withUsername("Marissa").withOrigin("uaa").withId("1234").withEmail("marissa@example.com"))); IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); SessionStatus sessionStatus = mock(SessionStatus.class); ModelAndView returnView = endpoint.validateCode( mock(Model.class), Integer.toString(code), mock(UserGoogleMfaCredentials.class), new MockHttpServletRequest(), sessionStatus ); assertEquals("mfa/enter_code", returnView.getViewName()); verifyZeroInteractions(sessionStatus); verifyMfaEvent(MfaAuthenticationFailureEvent.class); }
@Test public void testValidOTPReturnsErrorWhenLockedOut() throws Exception{ exception.expect(AuthenticationPolicyRejectionException.class); int code = 1234; when(mockMfaPolicy.isAllowed(anyString())).thenReturn(new LoginPolicy.Result(false, 0)); when(userGoogleMfaCredentialsProvisioning.isValidCode(ArgumentMatchers.any(UserGoogleMfaCredentials.class), eq(code))).thenReturn(true); when(uaaAuthentication.getPrincipal()).thenReturn(new UaaPrincipal(userId, "Marissa", null, "uaa", null, null), null, null); when(mfaProviderProvisioning.retrieveByName(mfaProvider.getName(), IdentityZoneHolder.get().getId())).thenReturn(mfaProvider); when(userDb.retrieveUserByName("Marissa", "uaa")).thenReturn(new UaaUser(new UaaUserPrototype().withUsername("Marissa").withOrigin("uaa").withId("1234").withEmail("marissa@example.com"))); IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); SessionStatus sessionStatus = mock(SessionStatus.class); endpoint.validateCode( mock(Model.class), Integer.toString(code), mock(UserGoogleMfaCredentials.class), new MockHttpServletRequest(), sessionStatus ); verifyZeroInteractions(sessionStatus); verifyMfaEvent(MfaAuthenticationFailureEvent.class); }
@Test public void testValidateCodeThrowsException() throws Exception{ int code = 1234; when(userGoogleMfaCredentialsProvisioning.isValidCode(ArgumentMatchers.any(UserGoogleMfaCredentials.class), eq(code))).thenThrow(new GoogleAuthenticatorException("Thou shall not pass")); when(uaaAuthentication.getPrincipal()).thenReturn(new UaaPrincipal(userId, "Marissa", null, "uaa", null, null), null, null); when(mfaProviderProvisioning.retrieveByName(mfaProvider.getName(), IdentityZoneHolder.get().getId())).thenReturn(mfaProvider); when(userDb.retrieveUserByName("Marissa", "uaa")).thenReturn(new UaaUser(new UaaUserPrototype().withUsername("Marissa").withOrigin("uaa").withId("1234").withEmail("marissa@example.com"))); IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); SessionStatus sessionStatus = mock(SessionStatus.class); ModelAndView returnView = endpoint.validateCode( mock(Model.class), Integer.toString(code), mock(UserGoogleMfaCredentials.class), new MockHttpServletRequest(), sessionStatus ); assertEquals("mfa/enter_code", returnView.getViewName()); verifyZeroInteractions(sessionStatus); verifyMfaEvent(MfaAuthenticationFailureEvent.class); }
@Test public void testEmptyOTP() throws Exception{ when(uaaAuthentication.getPrincipal()).thenReturn(new UaaPrincipal(userId, "Marissa", null, "uaa", null, null), null, null); when(mfaProviderProvisioning.retrieveByName(mfaProvider.getName(), IdentityZoneHolder.get().getId())).thenReturn(mfaProvider); when(userDb.retrieveUserByName("Marissa", "uaa")).thenReturn(new UaaUser(new UaaUserPrototype().withUsername("Marissa").withOrigin("uaa").withId("1234").withEmail("marissa@example.com"))); IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); SessionStatus sessionStatus = mock(SessionStatus.class); ModelAndView returnView = endpoint.validateCode( mock(Model.class), "", mock(UserGoogleMfaCredentials.class), new MockHttpServletRequest(), sessionStatus ); assertEquals("mfa/enter_code", returnView.getViewName()); verifyZeroInteractions(sessionStatus); verifyMfaEvent(MfaAuthenticationFailureEvent.class); }
@Test public void testNonNumericOTP() throws Exception{ when(uaaAuthentication.getPrincipal()).thenReturn(new UaaPrincipal(userId, "Marissa", null, "uaa", null, null), null, null); when(mfaProviderProvisioning.retrieveByName(mfaProvider.getName(), IdentityZoneHolder.get().getId())).thenReturn(mfaProvider); when(userDb.retrieveUserByName("Marissa", "uaa")).thenReturn(new UaaUser(new UaaUserPrototype().withUsername("Marissa").withOrigin("uaa").withId("1234").withEmail("marissa@example.com"))); IdentityZoneHolder.get().getConfig().getMfaConfig().setEnabled(true).setProviderName(mfaProvider.getName()); SessionStatus sessionStatus = mock(SessionStatus.class); ModelAndView returnView = endpoint.validateCode( mock(Model.class), "asdf123", mock(UserGoogleMfaCredentials.class), new MockHttpServletRequest(), sessionStatus ); assertEquals("mfa/enter_code", returnView.getViewName()); verifyZeroInteractions(sessionStatus); verifyMfaEvent(MfaAuthenticationFailureEvent.class); }