Quellcode durchsuchen

add remote response verification to new user profile

Jason Rivard vor 5 Jahren
Ursprung
Commit
b7a457156a

+ 107 - 0
rest-test-service/src/main/java/password/pwm/resttest/RestTestRemoteResponsesServlet.java

@@ -0,0 +1,107 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2019 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm.resttest;
+
+import com.google.gson.Gson;
+import lombok.Builder;
+import lombok.Value;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.util.Map;
+
+@WebServlet(
+    name = "RestTestRemoteResponsesServlet",
+    urlPatterns = { "/responses" }
+)
+public class RestTestRemoteResponsesServlet extends HttpServlet
+{
+    private static final String USERNAME_PARAMETER = "username";
+    private static final String SUCCESSFUL = "true";
+    private static final String UNSUCCESSFUL = "false";
+
+    @Override
+    protected void doPost( final HttpServletRequest req, final HttpServletResponse resp ) throws ServletException, IOException
+    {
+        final String reqBody = RestTestUtilities.readRequestBodyAsString( req );
+
+        System.out.println( "REST TEST: --Remote Responses=-" );
+        System.out.println( "  Received: " + reqBody );
+        final Gson gson = new Gson();
+        final RequestData requestData = gson.fromJson( reqBody, RequestData.class );
+
+        boolean correct = false;
+        if ( requestData.getUserResponses() != null
+            && requestData.getUserResponses().size() > 0
+            && requestData.getUserResponses().containsKey( "id1" )
+            && requestData.getUserResponses().get( "id1" ).equalsIgnoreCase( "answer1" ) )
+        {
+            correct = true;
+        }
+
+        final ResponseData responseData = ResponseData.builder()
+            .displayInstructions( "remote responses test server instructions" )
+            .verificationState( correct ? "COMPLETE" : "INPROGRESS" )
+            .userPrompts( new Prompt[]
+                {
+                    new Prompt( "prompt1", "id1" ),
+                    }
+            )
+            .errorMessage(  correct ? "" : "incorrect response for 'id1'.  ( correct response is 'answer1' ) " )
+            .build();
+
+        resp.setHeader( "Content-Type", "application/json" );
+        final PrintWriter writer = resp.getWriter();
+        System.out.println( "  Response: " + gson.toJson( responseData ) );
+        writer.print( gson.toJson( responseData ) );
+    }
+
+    @Value
+    public static class RequestData implements Serializable
+    {
+        private final String responseSessionID;
+        private final Map<String, String> userResponses;
+    }
+
+    @Value
+    @Builder
+    public static class ResponseData implements Serializable
+    {
+        private final String displayInstructions;
+        private final String verificationState;
+        private final Prompt[] userPrompts;
+        private final String errorMessage;
+
+    }
+
+    @Value
+    public static class Prompt implements Serializable
+    {
+        private final String displayPrompt;
+        private final String identifier;
+    }
+}

+ 7 - 33
server/src/main/java/password/pwm/bean/RemoteVerificationRequestBean.java

@@ -20,44 +20,18 @@
 
 package password.pwm.bean;
 
+import lombok.Builder;
+import lombok.Value;
 import password.pwm.bean.pub.PublicUserInfoBean;
 
 import java.io.Serializable;
 import java.util.Map;
 
+@Value
+@Builder
 public class RemoteVerificationRequestBean implements Serializable
 {
-    private String responseSessionID;
-    private PublicUserInfoBean userInfo;
-    private Map<String, String> userResponses;
-
-    public String getResponseSessionID( )
-    {
-        return responseSessionID;
-    }
-
-    public void setResponseSessionID( final String responseSessionID )
-    {
-        this.responseSessionID = responseSessionID;
-    }
-
-    public PublicUserInfoBean getUserInfo( )
-    {
-        return userInfo;
-    }
-
-    public void setUserInfo( final PublicUserInfoBean userInfo )
-    {
-        this.userInfo = userInfo;
-    }
-
-    public Map<String, String> getUserResponses( )
-    {
-        return userResponses;
-    }
-
-    public void setUserResponses( final Map<String, String> userResponses )
-    {
-        this.userResponses = userResponses;
-    }
+    private final String responseSessionID;
+    private final PublicUserInfoBean userInfo;
+    private final Map<String, String> userResponses;
 }

+ 2 - 40
server/src/main/java/password/pwm/bean/RemoteVerificationResponseBean.java

@@ -20,55 +20,17 @@
 
 package password.pwm.bean;
 
+import lombok.Value;
 import password.pwm.VerificationMethodSystem;
 
 import java.io.Serializable;
 import java.util.List;
 
+@Value
 public class RemoteVerificationResponseBean implements Serializable
 {
     private String displayInstructions;
     private VerificationMethodSystem.VerificationState verificationState;
     private List<VerificationMethodSystem.UserPromptBean> userPrompts;
     private String errorMessage;
-
-    public String getDisplayInstructions( )
-    {
-        return displayInstructions;
-    }
-
-    public void setDisplayInstructions( final String displayInstructions )
-    {
-        this.displayInstructions = displayInstructions;
-    }
-
-    public VerificationMethodSystem.VerificationState getVerificationState( )
-    {
-        return verificationState;
-    }
-
-    public void setVerificationState( final VerificationMethodSystem.VerificationState verificationState )
-    {
-        this.verificationState = verificationState;
-    }
-
-    public List<VerificationMethodSystem.UserPromptBean> getUserPrompts( )
-    {
-        return userPrompts;
-    }
-
-    public void setUserPrompts( final List<VerificationMethodSystem.UserPromptBean> userPrompts )
-    {
-        this.userPrompts = userPrompts;
-    }
-
-    public String getErrorMessage( )
-    {
-        return errorMessage;
-    }
-
-    public void setErrorMessage( final String errorMessage )
-    {
-        this.errorMessage = errorMessage;
-    }
 }

+ 3 - 1
server/src/main/java/password/pwm/config/PwmSetting.java

@@ -854,7 +854,9 @@ public enum PwmSetting
     NEWUSER_EMAIL_VERIFICATION(
             "newUser.email.verification", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.NEWUSER_PROFILE ),
     NEWUSER_SMS_VERIFICATION(
-            "newUser.sms.verification", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.NEWUSER_PROFILE ),
+        "newUser.sms.verification", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.NEWUSER_PROFILE ),
+    NEWUSER_EXTERNAL_VERIFICATION(
+        "newUser.external.verification", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.NEWUSER_PROFILE ),
     NEWUSER_PASSWORD_POLICY_USER(
             "newUser.passwordPolicy.user", PwmSettingSyntax.STRING, PwmSettingCategory.NEWUSER_PROFILE ),
     NEWUSER_MINIMUM_WAIT_TIME(

+ 1 - 0
server/src/main/java/password/pwm/http/JspUrl.java

@@ -79,6 +79,7 @@ public enum JspUrl
     UPDATE_ATTRIBUTES_CONFIRM( "updateprofile-confirm.jsp" ),
     NEW_USER( "newuser.jsp" ),
     NEW_USER_ENTER_CODE( "newuser-entercode.jsp" ),
+    NEW_USER_REMOTE( "newuser-remote.jsp" ),
     NEW_USER_TOKEN_SUCCESS( "newuser-tokensuccess.jsp" ),
     NEW_USER_WAIT( "newuser-wait.jsp" ),
     NEW_USER_PROFILE_CHOICE( "newuser-profilechoice.jsp" ),

+ 2 - 2
server/src/main/java/password/pwm/http/PwmRequestAttribute.java

@@ -77,8 +77,6 @@ public enum PwmRequestAttribute
 
     ForgottenPasswordChallengeSet,
     ForgottenPasswordOptionalPageView,
-    ForgottenPasswordPrompts,
-    ForgottenPasswordInstructions,
     ForgottenPasswordOtpRecord,
     ForgottenPasswordResendTokenEnabled,
     ForgottenPasswordInhibitPasswordReset,
@@ -100,6 +98,8 @@ public enum PwmRequestAttribute
     AppDashboardData,
 
     TokenDestItems,
+    ExternalResponsePrompts,
+    ExternalResponseInstructions,
 
     GoBackAction,;
 }

+ 6 - 4
server/src/main/java/password/pwm/http/bean/NewUserBean.java

@@ -21,9 +21,9 @@
 package password.pwm.http.bean;
 
 import com.google.gson.annotations.SerializedName;
-import lombok.Getter;
+import lombok.Data;
 import lombok.NoArgsConstructor;
-import lombok.Setter;
+import password.pwm.VerificationMethodSystem;
 import password.pwm.config.option.SessionBeanMode;
 import password.pwm.http.servlet.newuser.NewUserForm;
 
@@ -35,8 +35,7 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
-@Getter
-@Setter
+@Data
 @NoArgsConstructor
 public class NewUserBean extends PwmSessionBean
 {
@@ -70,7 +69,10 @@ public class NewUserBean extends PwmSessionBean
     @SerializedName( "ts" )
     private boolean tokenSent;
 
+    @SerializedName( "ep" )
+    private boolean externalResponsesPassed;
 
+    private transient VerificationMethodSystem remoteRecoveryMethod;
 
     @Override
     public Type getType( )

+ 3 - 16
server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java

@@ -564,20 +564,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean( pwmRequest );
         final VerificationMethodSystem remoteRecoveryMethod = forgottenPasswordBean.getProgress().getRemoteRecoveryMethod();
 
-        final Map<String, String> remoteResponses = new LinkedHashMap<>();
-        {
-            final Map<String, String> inputMap = pwmRequest.readParametersAsMap();
-            for ( final Map.Entry<String, String> entry : inputMap.entrySet() )
-            {
-                final String name = entry.getKey();
-                if ( name != null && name.startsWith( prefix ) )
-                {
-                    final String strippedName = name.substring( prefix.length(), name.length() );
-                    final String value = entry.getValue();
-                    remoteResponses.put( strippedName, value );
-                }
-            }
-        }
+        final Map<String, String> remoteResponses = RemoteVerificationMethod.readRemoteResponses( pwmRequest, prefix );
 
         final ErrorInformation errorInformation = remoteRecoveryMethod.respondToPrompts( remoteResponses );
 
@@ -1402,8 +1389,8 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
                 final List<VerificationMethodSystem.UserPrompt> prompts = remoteMethod.getCurrentPrompts();
                 final String displayInstructions = remoteMethod.getCurrentDisplayInstructions();
 
-                pwmRequest.setAttribute( PwmRequestAttribute.ForgottenPasswordPrompts, new ArrayList<>( prompts ) );
-                pwmRequest.setAttribute( PwmRequestAttribute.ForgottenPasswordInstructions, displayInstructions );
+                pwmRequest.setAttribute( PwmRequestAttribute.ExternalResponsePrompts, new ArrayList<>( prompts ) );
+                pwmRequest.setAttribute( PwmRequestAttribute.ExternalResponseInstructions, displayInstructions );
                 pwmRequest.forwardToJsp( JspUrl.RECOVER_PASSWORD_REMOTE );
             }
             break;

+ 32 - 11
server/src/main/java/password/pwm/http/servlet/forgottenpw/RemoteVerificationMethod.java

@@ -35,6 +35,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpContentType;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpMethod;
+import password.pwm.http.PwmRequest;
 import password.pwm.svc.httpclient.PwmHttpClient;
 import password.pwm.svc.httpclient.PwmHttpClientRequest;
 import password.pwm.svc.httpclient.PwmHttpClientResponse;
@@ -44,6 +45,7 @@ import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
@@ -74,12 +76,7 @@ public class RemoteVerificationMethod implements VerificationMethodSystem
             return null;
         }
 
-        final List<UserPrompt> returnObj = new ArrayList<>();
-        for ( final UserPromptBean userPromptBean : lastResponse.getUserPrompts() )
-        {
-            returnObj.add( userPromptBean );
-        }
-        return returnObj;
+        return new ArrayList<>( lastResponse.getUserPrompts() );
     }
 
     @Override
@@ -145,11 +142,13 @@ public class RemoteVerificationMethod implements VerificationMethodSystem
         headers.put( HttpHeader.ContentType.getHttpName(), HttpContentType.json.getHeaderValueWithEncoding() );
         headers.put( HttpHeader.AcceptLanguage.getHttpName(), locale.toLanguageTag() );
 
-        final RemoteVerificationRequestBean remoteVerificationRequestBean = new RemoteVerificationRequestBean();
-        remoteVerificationRequestBean.setResponseSessionID( this.remoteSessionID );
-        final MacroMachine macroMachine = MacroMachine.forUser( pwmApplication, PwmConstants.DEFAULT_LOCALE, SessionLabel.SYSTEM_LABEL, userInfo.getUserIdentity() );
-        remoteVerificationRequestBean.setUserInfo( PublicUserInfoBean.fromUserInfoBean( userInfo, pwmApplication.getConfig(), locale, macroMachine ) );
-        remoteVerificationRequestBean.setUserResponses( userResponses );
+        final MacroMachine macroMachine = MacroMachine.forUser( pwmApplication, sessionLabel, userInfo, null );
+
+        final RemoteVerificationRequestBean remoteVerificationRequestBean = RemoteVerificationRequestBean.builder()
+            .responseSessionID( this.remoteSessionID )
+            .userInfo( PublicUserInfoBean.fromUserInfoBean( userInfo, pwmApplication.getConfig(), locale, macroMachine ) )
+            .userResponses( userResponses )
+            .build();
 
         final PwmHttpClientRequest pwmHttpClientRequest = PwmHttpClientRequest.builder()
                 .method( HttpMethod.POST )
@@ -177,4 +176,26 @@ public class RemoteVerificationMethod implements VerificationMethodSystem
             throw new PwmUnrecoverableException( errorInformation );
         }
     }
+
+    public static Map<String, String> readRemoteResponses( final PwmRequest pwmRequest, final String prefix )
+        throws PwmUnrecoverableException
+    {
+        final Map<String, String> remoteResponses = new LinkedHashMap<>();
+        {
+            final Map<String, String> inputMap = pwmRequest.readParametersAsMap();
+            for ( final Map.Entry<String, String> entry : inputMap.entrySet() )
+            {
+                final String name = entry.getKey();
+                if ( name != null && name.startsWith( prefix ) )
+                {
+                    final String strippedName = name.substring( prefix.length(), name.length() );
+                    final String value = entry.getValue();
+                    remoteResponses.put( strippedName, value );
+                }
+            }
+        }
+        return Collections.unmodifiableMap( remoteResponses );
+    }
+
+
 }

+ 41 - 0
server/src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java

@@ -23,6 +23,7 @@ package password.pwm.http.servlet.newuser;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
+import password.pwm.VerificationMethodSystem;
 import password.pwm.bean.TokenDestinationItem;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
@@ -46,6 +47,7 @@ import password.pwm.http.filter.AuthenticationFilter;
 import password.pwm.http.servlet.AbstractPwmServlet;
 import password.pwm.http.servlet.ControlledPwmServlet;
 import password.pwm.http.servlet.PwmServletDefinition;
+import password.pwm.http.servlet.forgottenpw.RemoteVerificationMethod;
 import password.pwm.i18n.Message;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfoBean;
@@ -112,6 +114,7 @@ public class NewUserServlet extends ControlledPwmServlet
         processForm( HttpMethod.POST ),
         validate( HttpMethod.POST ),
         enterCode( HttpMethod.POST, HttpMethod.GET ),
+        enterRemoteResponse( HttpMethod.POST ),
         reset( HttpMethod.POST ),
         agree( HttpMethod.POST ),;
 
@@ -264,6 +267,10 @@ public class NewUserServlet extends ControlledPwmServlet
             return;
         }
 
+        if ( NewUserUtils.checkForExternalResponsesVerificationProgress( pwmRequest, newUserBean, newUserProfile ) == ProcessStatus.Halt )
+        {
+            return;
+        }
 
         final String newUserAgreementText = newUserProfile.readSettingAsLocalizedString( PwmSetting.NEWUSER_AGREEMENT_MESSAGE,
                 pwmSession.getSessionStateBean().getLocale() );
@@ -546,6 +553,40 @@ public class NewUserServlet extends ControlledPwmServlet
         return ProcessStatus.Continue;
     }
 
+    @ActionHandler( action = "enterRemoteResponse" )
+    private ProcessStatus processEnterRemoteResponse( final PwmRequest pwmRequest )
+        throws PwmUnrecoverableException, IOException, ServletException
+    {
+        final String prefix = "remote-";
+        final NewUserBean newUserBean = getNewUserBean( pwmRequest );
+        final VerificationMethodSystem remoteRecoveryMethod = NewUserUtils.readRemoteVerificationMethod( pwmRequest, newUserBean );
+
+        final Map<String, String> remoteResponses = RemoteVerificationMethod.readRemoteResponses( pwmRequest, prefix );
+
+        final ErrorInformation errorInformation = remoteRecoveryMethod.respondToPrompts( remoteResponses );
+
+        if ( remoteRecoveryMethod.getVerificationState() == VerificationMethodSystem.VerificationState.COMPLETE )
+        {
+            newUserBean.setExternalResponsesPassed( true );
+        }
+
+        if ( remoteRecoveryMethod.getVerificationState() == VerificationMethodSystem.VerificationState.FAILED )
+        {
+            newUserBean.setExternalResponsesPassed( false );
+            pwmRequest.respondWithError( errorInformation, true );
+            LOGGER.debug( pwmRequest, () -> "unsuccessful remote response verification input: " + errorInformation.toDebugStr() );
+            return ProcessStatus.Continue;
+        }
+
+        if ( errorInformation != null )
+        {
+            setLastError( pwmRequest, errorInformation );
+        }
+
+        return ProcessStatus.Continue;
+    }
+
+
     @ActionHandler( action = "profileChoice" )
     private ProcessStatus handleProfileChoiceRequest( final PwmRequest pwmRequest )
             throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException

+ 79 - 12
server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java

@@ -29,6 +29,7 @@ import com.novell.ldapchai.provider.ChaiSetting;
 import com.novell.ldapchai.provider.DirectoryVendor;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
+import password.pwm.VerificationMethodSystem;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.SessionLabel;
@@ -47,10 +48,13 @@ import password.pwm.error.PwmDataValidationException;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.JspUrl;
 import password.pwm.http.ProcessStatus;
 import password.pwm.http.PwmRequest;
+import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.NewUserBean;
+import password.pwm.http.servlet.forgottenpw.RemoteVerificationMethod;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfoBean;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
@@ -62,7 +66,6 @@ import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.token.TokenType;
 import password.pwm.svc.token.TokenUtil;
 import password.pwm.util.PasswordData;
-import password.pwm.util.password.RandomPasswordGenerator;
 import password.pwm.util.form.FormUtility;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
@@ -72,6 +75,7 @@ import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
 import password.pwm.util.operations.ActionExecutor;
 import password.pwm.util.password.PasswordUtility;
+import password.pwm.util.password.RandomPasswordGenerator;
 import password.pwm.ws.client.rest.form.FormDataRequestBean;
 import password.pwm.ws.client.rest.form.FormDataResponseBean;
 import password.pwm.ws.client.rest.form.RestFormDataClient;
@@ -482,29 +486,39 @@ class NewUserUtils
         );
     }
 
-    static MacroMachine createMacroMachineForNewUser(
+    static UserInfoBean createUserInfoBeanForNewUser(
             final PwmApplication pwmApplication,
-            final SessionLabel sessionLabel,
-            final NewUserForm newUserForm,
-            final TokenDestinationItem tokenDestinationItem
+            final NewUserForm newUserForm
     )
-            throws PwmUnrecoverableException
+        throws PwmUnrecoverableException
+
     {
         final Map<String, String> formValues = newUserForm.getFormData();
 
         final String emailAddressAttribute = pwmApplication.getConfig().getDefaultLdapProfile().readSettingAsString(
-                PwmSetting.EMAIL_USER_MAIL_ATTRIBUTE );
+            PwmSetting.EMAIL_USER_MAIL_ATTRIBUTE );
 
         final String usernameAttribute = pwmApplication.getConfig().getDefaultLdapProfile().readSettingAsString( PwmSetting.LDAP_USERNAME_ATTRIBUTE );
 
+        return UserInfoBean.builder()
+            .userEmailAddress( formValues.get( emailAddressAttribute ) )
+            .username( formValues.get( usernameAttribute ) )
+            .attributes( formValues )
+            .build();
+    }
+
+    static MacroMachine createMacroMachineForNewUser(
+            final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel,
+            final NewUserForm newUserForm,
+            final TokenDestinationItem tokenDestinationItem
+    )
+            throws PwmUnrecoverableException
+    {
         final LoginInfoBean stubLoginBean = new LoginInfoBean();
         stubLoginBean.setUserCurrentPassword( newUserForm.getNewUserPassword() );
 
-        final UserInfoBean stubUserBean = UserInfoBean.builder()
-                .userEmailAddress( formValues.get( emailAddressAttribute ) )
-                .username( formValues.get( usernameAttribute ) )
-                .attributes( formValues )
-                .build();
+        final UserInfoBean stubUserBean = createUserInfoBeanForNewUser( pwmApplication, newUserForm );
 
         final MacroMachine.StringReplacer stringReplacer = tokenDestinationItem == null
                 ? null
@@ -649,6 +663,59 @@ class NewUserUtils
         return Collections.unmodifiableMap( workingMap );
     }
 
+    static ProcessStatus checkForExternalResponsesVerificationProgress(
+            final PwmRequest pwmRequest,
+            final NewUserBean newUserBean,
+            final NewUserProfile newUserProfile
+    )
+            throws PwmUnrecoverableException, ServletException, IOException
+    {
+        if ( newUserProfile.readSettingAsBoolean( PwmSetting.NEWUSER_EXTERNAL_VERIFICATION ) )
+        {
+            if ( !newUserBean.isExternalResponsesPassed() )
+            {
+                final VerificationMethodSystem remoteMethod = readRemoteVerificationMethod( pwmRequest, newUserBean );
+
+                final List<VerificationMethodSystem.UserPrompt> prompts = remoteMethod.getCurrentPrompts();
+                final String displayInstructions = remoteMethod.getCurrentDisplayInstructions();
+
+                pwmRequest.setAttribute( PwmRequestAttribute.ExternalResponsePrompts, new ArrayList<>( prompts ) );
+                pwmRequest.setAttribute( PwmRequestAttribute.ExternalResponseInstructions, displayInstructions );
+                pwmRequest.forwardToJsp( JspUrl.NEW_USER_REMOTE );
+                return ProcessStatus.Halt;
+            }
+        }
+
+        return ProcessStatus.Continue;
+    }
+
+    static VerificationMethodSystem readRemoteVerificationMethod(
+            final PwmRequest pwmRequest,
+            final NewUserBean newUserBean
+    )
+            throws PwmUnrecoverableException
+    {
+        final UserInfo userInfo = createUserInfoBeanForNewUser( pwmRequest.getPwmApplication(), newUserBean.getNewUserForm() );
+
+        final VerificationMethodSystem remoteMethod;
+        if ( newUserBean.getRemoteRecoveryMethod() == null )
+        {
+            remoteMethod = new RemoteVerificationMethod();
+            remoteMethod.init(
+                    pwmRequest.getPwmApplication(),
+                    userInfo,
+                    pwmRequest.getLabel(),
+                    pwmRequest.getLocale()
+            );
+        }
+        else
+        {
+            remoteMethod = newUserBean.getRemoteRecoveryMethod();
+        }
+        newUserBean.setRemoteRecoveryMethod( remoteMethod );
+        return remoteMethod;
+    }
+
     static ProcessStatus checkForTokenVerificationProgress(
             final PwmRequest pwmRequest,
             final NewUserBean newUserBean,

+ 5 - 0
server/src/main/resources/password/pwm/config/PwmSetting.xml

@@ -2754,6 +2754,11 @@
             <value>false</value>
         </default>
     </setting>
+    <setting hidden="false" key="newUser.external.verification" level="1">
+    <default>
+        <value>false</value>
+    </default>
+</setting>
     <setting hidden="false" key="newUser.passwordPolicy.user" level="1" required="true">
         <default>
             <value><![CDATA[TESTUSER]]></value>

+ 2 - 0
server/src/main/resources/password/pwm/i18n/PwmSetting.properties

@@ -512,6 +512,7 @@ Setting_Description_newUser.profile.visible=Show this New User profile to users
 Setting_Description_newUser.promptForPassword=Prompt user for password during user registration.  If not enabled, a random password will be assigned to the user.  In most cases you will want this enabled.
 Setting_Description_newUser.redirectUrl=URL to redirect user to after new user registration process is completed.
 Setting_Description_newUser.sms.verification=Enable this option to have @PwmAppName@ send an SMS message to the new user's mobile phone number before it creates the account. The NewUser must verify receipt of the SMS message before @PwmAppName@ creates the account. please insure that the user has entered their SMS information.
+Setting_Description_newUser.external.verification=Enable this option to have @PwmAppName@ invoke the external verification method for a new user. The new user must verify the external responses before @PwmAppName@ creates the account.
 Setting_Description_newUser.token.lifetime=Specify the lifetime a new user email token is valid (in seconds). The default is 0.   When set to 0, the effective value is inherited from the setting <code>@PwmSettingReference\:token.lifetime@</code>
 Setting_Description_newUser.token.lifetime.sms=Specify the lifetime a new user SMS token is valid (in seconds). The default is 0.  When set to 0, the effective value is inherited from the setting <code>@PwmSettingReference\:token.lifetime@</code>
 Setting_Description_newUser.username.definition=<p>Specify the display name, or entry ID that is included in the LDAP naming attribute for the new registered users. Some directories use an LDAP entry instead of a user name.<p>When you enable this setting, the system generates an entryID or an LDAP entry that includes random characters by default.You must specify macros for this setting. For more information about macros, see <a href=https://www.netiq.com/documentation/self-service-password-reset-40/adminguide/data/b19nnbhy.html>Configuring Macros for Messages and Actions</a>.<p>If you leave this field blank, the system does not generate a random user name or entry ID.<p>For example, in the LDAP directory, specify the value as @User:Email@ to display the display name or entry ID for the new registered user as their email address.</p><p>When multiple values are entered, if the first value already exists, each value will be tried in order until an unused value is found.</p> 
@@ -1039,6 +1040,7 @@ Setting_Label_newUser.profile.visible=Profile Visible on Menu
 Setting_Label_newUser.promptForPassword=Prompt User for Password
 Setting_Label_newUser.redirectUrl=After Registration Redirect URL
 Setting_Label_newUser.sms.verification=Enable New User SMS Verification
+Setting_Label_newUser.external.verification=Enable New User External Verification
 Setting_Label_newUser.token.lifetime=New User Email Token Maximum Lifetime
 Setting_Label_newUser.token.lifetime.sms=New User SMS Token Maximum Lifetime
 Setting_Label_newUser.username.definition=LDAP Entry ID Definition

+ 4 - 4
webapp/src/main/webapp/WEB-INF/jsp/forgottenpassword-remote.jsp

@@ -26,9 +26,9 @@
 <!DOCTYPE html>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ page import="password.pwm.VerificationMethodSystem" %>
+<%@ page import="password.pwm.http.servlet.PwmServletDefinition" %>
+<%@ page import="password.pwm.http.servlet.newuser.NewUserServlet" %>
 <%@ page import="java.util.List" %>
-<%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
-<%@ page import="password.pwm.http.PwmRequestAttribute" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%@ include file="fragment/header.jsp" %>
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
@@ -40,8 +40,8 @@
     <div id="centerbody">
         <h1 id="page-content-title"><pwm:display key="Title_ForgottenPassword" displayIfMissing="true"/></h1>
         <%
-            final List<VerificationMethodSystem.UserPrompt> prompts = (List<VerificationMethodSystem.UserPrompt>)JspUtility.getAttribute(pageContext, PwmRequestAttribute.ForgottenPasswordPrompts);
-            final String instructions = (String)JspUtility.getAttribute(pageContext, PwmRequestAttribute.ForgottenPasswordInstructions);
+            final List<VerificationMethodSystem.UserPrompt> prompts = (List<VerificationMethodSystem.UserPrompt>)JspUtility.getAttribute(pageContext, PwmRequestAttribute.ExternalResponsePrompts );
+            final String instructions = (String)JspUtility.getAttribute(pageContext, PwmRequestAttribute.ExternalResponseInstructions );
         %>
         <p><%=instructions%></p>
         <form action="<pwm:current-url/>" method="post" enctype="application/x-www-form-urlencoded" name="search" class="pwm-form" autocomplete="off">

+ 86 - 0
webapp/src/main/webapp/WEB-INF/jsp/newuser-remote.jsp

@@ -0,0 +1,86 @@
+<%--
+ ~ Password Management Servlets (PWM)
+ ~ http://www.pwm-project.org
+ ~
+ ~ Copyright (c) 2006-2009 Novell, Inc.
+ ~ Copyright (c) 2009-2019 The PWM Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~     http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+--%>
+<%--
+       THIS FILE IS NOT INTENDED FOR END USER MODIFICATION.
+       See the README.TXT file in WEB-INF/jsp before making changes.
+--%>
+
+
+<!DOCTYPE html>
+<%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
+<%@ page import="password.pwm.VerificationMethodSystem" %>
+<%@ page import="password.pwm.http.servlet.PwmServletDefinition" %>
+<%@ page import="password.pwm.http.servlet.newuser.NewUserServlet" %>
+<%@ page import="java.util.List" %>
+<%@ taglib uri="pwm" prefix="pwm" %>
+<%@ include file="fragment/header.jsp" %>
+<html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
+<body class="nihilo">
+<div id="wrapper">
+    <jsp:include page="fragment/header-body.jsp">
+        <jsp:param name="pwm.PageName" value="Title_NewUser"/>
+    </jsp:include>
+    <div id="centerbody">
+        <h1 id="page-content-title"><pwm:display key="Title_NewUser" displayIfMissing="true"/></h1>
+        <%
+            final List<VerificationMethodSystem.UserPrompt> prompts = (List<VerificationMethodSystem.UserPrompt>)JspUtility.getAttribute(pageContext, PwmRequestAttribute.ExternalResponsePrompts );
+            final String instructions = (String)JspUtility.getAttribute(pageContext, PwmRequestAttribute.ExternalResponseInstructions );
+        %>
+        <p><%=instructions%></p>
+        <form action="<pwm:current-url/>" method="post" enctype="application/x-www-form-urlencoded" name="search" class="pwm-form" autocomplete="off">
+            <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
+            <br/>
+            <% for (final VerificationMethodSystem.UserPrompt userPrompt : prompts) { %>
+            <div class="formFieldLabel">
+                <%= userPrompt.getDisplayPrompt() %>
+            </div>
+
+            <input type="password" id="remote-<%=userPrompt.getIdentifier()%>" name="remote-<%=userPrompt.getIdentifier()%>" class="inputfield passwordfield" required="required" autofocus/>
+            <% } %>
+            <div class="buttonbar">
+                <button type="submit" class="btn" name="submitBtn" id="submitBtn">
+                    <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-forward"></span></pwm:if>
+                    <pwm:display key="Button_Continue"/>
+                </button>
+                <button type="button" id="button-goBack" name="button-goBack" class="btn" >
+                    <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-backward"></span></pwm:if>
+                    <pwm:display key="Button_GoBack"/>
+                </button>
+                <%@ include file="/WEB-INF/jsp/fragment/cancel-button.jsp" %>
+                <input type="hidden" id="processAction" name="processAction" value="<%=NewUserServlet.NewUserAction.enterRemoteResponse%>"/>
+                <input type="hidden" id="pwmFormID" name="pwmFormID" value="<pwm:FormID/>"/>
+            </div>
+        </form>
+    </div>
+    <div class="push"></div>
+</div>
+<pwm:script>
+    <script>
+        PWM_GLOBAL['startupFunctions'].push(function(){
+            PWM_MAIN.addEventHandler('button-goBack','click',function() {
+                PWM_MAIN.submitPostAction('<%=PwmServletDefinition.NewUser.servletUrlName()%>', '<%=NewUserServlet.NewUserAction.checkProgress%>');
+            });
+        });
+    </script>
+</pwm:script>
+<%@ include file="fragment/footer.jsp" %>
+</body>
+</html>
+