浏览代码

Merge remote-tracking branch 'origin'

oddkl 5 年之前
父节点
当前提交
89a47a8803
共有 32 个文件被更改,包括 616 次插入231 次删除
  1. 105 0
      rest-test-service/src/main/java/password/pwm/resttest/RestTestRemoteResponsesServlet.java
  2. 7 33
      server/src/main/java/password/pwm/bean/RemoteVerificationRequestBean.java
  3. 2 40
      server/src/main/java/password/pwm/bean/RemoteVerificationResponseBean.java
  4. 44 20
      server/src/main/java/password/pwm/config/Configuration.java
  5. 12 6
      server/src/main/java/password/pwm/config/PwmSetting.java
  6. 7 1
      server/src/main/java/password/pwm/config/profile/AbstractProfile.java
  7. 18 0
      server/src/main/java/password/pwm/config/profile/LdapProfile.java
  8. 29 3
      server/src/main/java/password/pwm/config/profile/NewUserProfile.java
  9. 6 4
      server/src/main/java/password/pwm/config/profile/ProfileDefinition.java
  10. 6 7
      server/src/main/java/password/pwm/config/profile/ProfileUtility.java
  11. 33 1
      server/src/main/java/password/pwm/health/ConfigurationChecker.java
  12. 3 0
      server/src/main/java/password/pwm/health/HealthMessage.java
  13. 1 1
      server/src/main/java/password/pwm/health/LDAPHealthChecker.java
  14. 1 0
      server/src/main/java/password/pwm/http/JspUrl.java
  15. 2 2
      server/src/main/java/password/pwm/http/PwmRequestAttribute.java
  16. 10 4
      server/src/main/java/password/pwm/http/bean/NewUserBean.java
  17. 1 1
      server/src/main/java/password/pwm/http/servlet/activation/ActivateUserUtils.java
  18. 8 7
      server/src/main/java/password/pwm/http/servlet/admin/UserDebugDataReader.java
  19. 3 16
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java
  20. 10 9
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java
  21. 32 11
      server/src/main/java/password/pwm/http/servlet/forgottenpw/RemoteVerificationMethod.java
  22. 42 0
      server/src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java
  23. 94 22
      server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java
  24. 3 3
      server/src/main/java/password/pwm/ldap/UserInfoReader.java
  25. 8 7
      server/src/main/java/password/pwm/util/password/PasswordUtility.java
  26. 21 27
      server/src/main/java/password/pwm/ws/server/rest/RestProfileServer.java
  27. 10 0
      server/src/main/resources/password/pwm/config/PwmSetting.xml
  28. 2 0
      server/src/main/resources/password/pwm/i18n/Health.properties
  29. 5 1
      server/src/main/resources/password/pwm/i18n/PwmSetting.properties
  30. 4 4
      webapp/src/main/webapp/WEB-INF/jsp/forgottenpassword-remote.jsp
  31. 86 0
      webapp/src/main/webapp/WEB-INF/jsp/newuser-remote.jsp
  32. 1 1
      webapp/src/main/webapp/public/resources/js/main.js

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

@@ -0,0 +1,105 @@
+/*
+ * 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.Collections;
+import java.util.List;
+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( Collections.singletonList( 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 List<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;
 package password.pwm.bean;
 
 
+import lombok.Builder;
+import lombok.Value;
 import password.pwm.bean.pub.PublicUserInfoBean;
 import password.pwm.bean.pub.PublicUserInfoBean;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
 import java.util.Map;
 import java.util.Map;
 
 
+@Value
+@Builder
 public class RemoteVerificationRequestBean implements Serializable
 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;
 package password.pwm.bean;
 
 
+import lombok.Value;
 import password.pwm.VerificationMethodSystem;
 import password.pwm.VerificationMethodSystem;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
 import java.util.List;
 import java.util.List;
 
 
+@Value
 public class RemoteVerificationResponseBean implements Serializable
 public class RemoteVerificationResponseBean implements Serializable
 {
 {
     private String displayInstructions;
     private String displayInstructions;
     private VerificationMethodSystem.VerificationState verificationState;
     private VerificationMethodSystem.VerificationState verificationState;
     private List<VerificationMethodSystem.UserPromptBean> userPrompts;
     private List<VerificationMethodSystem.UserPromptBean> userPrompts;
     private String errorMessage;
     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;
-    }
 }
 }

+ 44 - 20
server/src/main/java/password/pwm/config/Configuration.java

@@ -73,6 +73,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.util.PasswordData;
 import password.pwm.util.PasswordData;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.i18n.LocaleHelper;
+import password.pwm.util.java.LazySupplier;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
@@ -93,9 +94,11 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Optional;
 import java.util.Set;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeMap;
+import java.util.function.Supplier;
 
 
 /**
 /**
  * @author Jason D. Rivard
  * @author Jason D. Rivard
@@ -106,6 +109,8 @@ public class Configuration implements SettingReader
 
 
     private final StoredConfiguration storedConfiguration;
     private final StoredConfiguration storedConfiguration;
 
 
+    private final ConfigurationSuppliers configurationSuppliers = new ConfigurationSuppliers();
+
     private DataCache dataCache = new DataCache();
     private DataCache dataCache = new DataCache();
 
 
     public Configuration( final StoredConfiguration storedConfiguration )
     public Configuration( final StoredConfiguration storedConfiguration )
@@ -137,7 +142,7 @@ public class Configuration implements SettingReader
 
 
     public Map<String, LdapProfile> getLdapProfiles( )
     public Map<String, LdapProfile> getLdapProfiles( )
     {
     {
-        return getProfileMap( ProfileDefinition.LdapProfile, LdapProfile.class );
+        return configurationSuppliers.ldapProfilesSupplier.get();
     }
     }
 
 
     public EmailItemBean readSettingAsEmail( final PwmSetting setting, final Locale locale )
     public EmailItemBean readSettingAsEmail( final PwmSetting setting, final Locale locale )
@@ -796,14 +801,6 @@ public class Configuration implements SettingReader
 
 
     public LdapProfile getDefaultLdapProfile( ) throws PwmUnrecoverableException
     public LdapProfile getDefaultLdapProfile( ) throws PwmUnrecoverableException
     {
     {
-        if ( getLdapProfiles().isEmpty() )
-        {
-            throw new PwmUnrecoverableException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[]
-                    {
-                            "no ldap profiles are defined",
-                    }
-            ) );
-        }
         return getLdapProfiles().values().iterator().next();
         return getLdapProfiles().values().iterator().next();
     }
     }
 
 
@@ -990,6 +987,22 @@ public class Configuration implements SettingReader
         return readValue;
         return readValue;
     }
     }
 
 
+    private class ConfigurationSuppliers
+    {
+        private final Supplier<Map<String, LdapProfile>> ldapProfilesSupplier = new LazySupplier<>( () ->
+        {
+            final Map<String, LdapProfile> map = new LinkedHashMap<>();
+            for ( final Map.Entry<String, LdapProfile> entry : getProfileMap( ProfileDefinition.LdapProfile, LdapProfile.class ).entrySet() )
+            {
+                if ( entry.getValue().isEnabled() )
+                {
+                    map.put( entry.getKey(), entry.getValue() );
+                }
+            }
+            return Collections.unmodifiableMap( map );
+        } );
+    }
+
     private static class DataCache
     private static class DataCache
     {
     {
         private final Map<String, Map<Locale, PwmPasswordPolicy>> cachedPasswordPolicy = new LinkedHashMap<>();
         private final Map<String, Map<Locale, PwmPasswordPolicy>> cachedPasswordPolicy = new LinkedHashMap<>();
@@ -1077,26 +1090,37 @@ public class Configuration implements SettingReader
         final Map<String, Profile> returnMap = new LinkedHashMap<>();
         final Map<String, Profile> returnMap = new LinkedHashMap<>();
         for ( final String profileID : ProfileUtility.profileIDsForCategory( this, profileDefinition.getCategory() ) )
         for ( final String profileID : ProfileUtility.profileIDsForCategory( this, profileDefinition.getCategory() ) )
         {
         {
-            final Profile newProfile = newProfileForID( profileDefinition, profileID );
-            returnMap.put( profileID, newProfile );
+            if ( profileDefinition.getProfileFactoryClass().isPresent() )
+            {
+                final Profile newProfile = newProfileForID( profileDefinition, profileID );
+                returnMap.put( profileID, newProfile );
+            }
         }
         }
         return Collections.unmodifiableMap( returnMap );
         return Collections.unmodifiableMap( returnMap );
     }
     }
 
 
     private Profile newProfileForID( final ProfileDefinition profileDefinition, final String profileID )
     private Profile newProfileForID( final ProfileDefinition profileDefinition, final String profileID )
     {
     {
-        final Class<? extends Profile.ProfileFactory> profileFactoryClass = profileDefinition.getProfileFactoryClass();
+        Objects.requireNonNull( profileDefinition );
+        Objects.requireNonNull( profileID );
 
 
-        final Profile.ProfileFactory profileFactory;
-        try
-        {
-            profileFactory = profileFactoryClass.getDeclaredConstructor().newInstance();
-        }
-        catch ( final InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e )
+        final Optional<Class<? extends Profile.ProfileFactory>> optionalProfileFactoryClass = profileDefinition.getProfileFactoryClass();
+
+        if ( optionalProfileFactoryClass.isPresent() )
         {
         {
-            throw new IllegalStateException( "unable to create profile instance for " + profileDefinition );
+            final Profile.ProfileFactory profileFactory;
+            try
+            {
+                profileFactory = optionalProfileFactoryClass.get().getDeclaredConstructor().newInstance();
+                return profileFactory.makeFromStoredConfiguration( storedConfiguration, profileID );
+            }
+            catch ( final InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e )
+            {
+                throw new IllegalStateException( "unable to create profile instance for " + profileDefinition );
+            }
         }
         }
-        return profileFactory.makeFromStoredConfiguration( storedConfiguration, profileID );
+
+        throw new IllegalStateException( "unable to create profile instance for " + profileDefinition + " ( profile factory class not defined )" );
     }
     }
 
 
     public StoredConfiguration getStoredConfiguration( )
     public StoredConfiguration getStoredConfiguration( )

+ 12 - 6
server/src/main/java/password/pwm/config/PwmSetting.java

@@ -835,14 +835,22 @@ public enum PwmSetting
     // new user settings
     // new user settings
     NEWUSER_ENABLE(
     NEWUSER_ENABLE(
             "newUser.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.NEWUSER_SETTINGS ),
             "newUser.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.NEWUSER_SETTINGS ),
+
     NEWUSER_PROFILE_LIST(
     NEWUSER_PROFILE_LIST(
             "newUser.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL ),
             "newUser.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL ),
+
+    NEWUSER_FORM(
+            "newUser.form", PwmSettingSyntax.FORM, PwmSettingCategory.NEWUSER_PROFILE ),
+    NEWUSER_LDAP_PROFILE(
+            "newUser.ldapProfile", PwmSettingSyntax.STRING, PwmSettingCategory.NEWUSER_PROFILE ),
     NEWUSER_CONTEXT(
     NEWUSER_CONTEXT(
             "newUser.createContext", PwmSettingSyntax.STRING, PwmSettingCategory.NEWUSER_PROFILE ),
             "newUser.createContext", PwmSettingSyntax.STRING, PwmSettingCategory.NEWUSER_PROFILE ),
     NEWUSER_AGREEMENT_MESSAGE(
     NEWUSER_AGREEMENT_MESSAGE(
             "display.newuser.agreement", PwmSettingSyntax.LOCALIZED_TEXT_AREA, PwmSettingCategory.NEWUSER_PROFILE ),
             "display.newuser.agreement", PwmSettingSyntax.LOCALIZED_TEXT_AREA, PwmSettingCategory.NEWUSER_PROFILE ),
-    NEWUSER_FORM(
-            "newUser.form", PwmSettingSyntax.FORM, PwmSettingCategory.NEWUSER_PROFILE ),
+    NEWUSER_PROFILE_DISPLAY_NAME(
+            "newUser.profile.displayName", PwmSettingSyntax.LOCALIZED_STRING, PwmSettingCategory.NEWUSER_PROFILE ),
+    NEWUSER_PROFILE_DISPLAY_VISIBLE(
+            "newUser.profile.visible", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.NEWUSER_PROFILE ),
     NEWUSER_WRITE_ATTRIBUTES(
     NEWUSER_WRITE_ATTRIBUTES(
             "newUser.writeAttributes", PwmSettingSyntax.ACTION, PwmSettingCategory.NEWUSER_PROFILE ),
             "newUser.writeAttributes", PwmSettingSyntax.ACTION, PwmSettingCategory.NEWUSER_PROFILE ),
     NEWUSER_DELETE_ON_FAIL(
     NEWUSER_DELETE_ON_FAIL(
@@ -855,14 +863,12 @@ public enum PwmSetting
             "newUser.email.verification", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.NEWUSER_PROFILE ),
             "newUser.email.verification", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.NEWUSER_PROFILE ),
     NEWUSER_SMS_VERIFICATION(
     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_PASSWORD_POLICY_USER(
             "newUser.passwordPolicy.user", PwmSettingSyntax.STRING, PwmSettingCategory.NEWUSER_PROFILE ),
             "newUser.passwordPolicy.user", PwmSettingSyntax.STRING, PwmSettingCategory.NEWUSER_PROFILE ),
     NEWUSER_MINIMUM_WAIT_TIME(
     NEWUSER_MINIMUM_WAIT_TIME(
             "newUser.minimumWaitTime", PwmSettingSyntax.DURATION, PwmSettingCategory.NEWUSER_PROFILE ),
             "newUser.minimumWaitTime", PwmSettingSyntax.DURATION, PwmSettingCategory.NEWUSER_PROFILE ),
-    NEWUSER_PROFILE_DISPLAY_NAME(
-            "newUser.profile.displayName", PwmSettingSyntax.LOCALIZED_STRING, PwmSettingCategory.NEWUSER_PROFILE ),
-    NEWUSER_PROFILE_DISPLAY_VISIBLE(
-            "newUser.profile.visible", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.NEWUSER_PROFILE ),
     NEWUSER_REDIRECT_URL(
     NEWUSER_REDIRECT_URL(
             "newUser.redirectUrl", PwmSettingSyntax.STRING, PwmSettingCategory.NEWUSER_PROFILE ),
             "newUser.redirectUrl", PwmSettingSyntax.STRING, PwmSettingCategory.NEWUSER_PROFILE ),
     NEWUSER_PROMPT_FOR_PASSWORD(
     NEWUSER_PROMPT_FOR_PASSWORD(

+ 7 - 1
server/src/main/java/password/pwm/config/profile/AbstractProfile.java

@@ -44,6 +44,7 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.Set;
 
 
 public abstract class AbstractProfile implements Profile, SettingReader
 public abstract class AbstractProfile implements Profile, SettingReader
@@ -153,7 +154,12 @@ public abstract class AbstractProfile implements Profile, SettingReader
     @Override
     @Override
     public List<UserPermission> getPermissionMatches( )
     public List<UserPermission> getPermissionMatches( )
     {
     {
-        return readSettingAsUserPermission( profileType().getQueryMatch() );
+        final Optional<PwmSetting> optionalQueryMatchSetting = profileType().getQueryMatch();
+        if ( optionalQueryMatchSetting.isPresent() )
+        {
+            return readSettingAsUserPermission( optionalQueryMatchSetting.get() );
+        }
+        return Collections.emptyList();
     }
     }
 
 
     static Map<PwmSetting, StoredValue> makeValueMap(
     static Map<PwmSetting, StoredValue> makeValueMap(

+ 18 - 0
server/src/main/java/password/pwm/config/profile/LdapProfile.java

@@ -31,6 +31,8 @@ import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.config.StoredValue;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.value.data.UserPermission;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.svc.cache.CacheKey;
 import password.pwm.svc.cache.CacheKey;
 import password.pwm.svc.cache.CachePolicy;
 import password.pwm.svc.cache.CachePolicy;
@@ -111,6 +113,7 @@ public class LdapProfile extends AbstractProfile implements Profile
 
 
     public ChaiProvider getProxyChaiProvider( final PwmApplication pwmApplication ) throws PwmUnrecoverableException
     public ChaiProvider getProxyChaiProvider( final PwmApplication pwmApplication ) throws PwmUnrecoverableException
     {
     {
+        verifyIsEnabled();
         return pwmApplication.getProxyChaiProvider( this.getIdentifier() );
         return pwmApplication.getProxyChaiProvider( this.getIdentifier() );
     }
     }
 
 
@@ -215,6 +218,21 @@ public class LdapProfile extends AbstractProfile implements Profile
         }
         }
     }
     }
 
 
+    private void verifyIsEnabled()
+            throws PwmUnrecoverableException
+    {
+        if ( !isEnabled() )
+        {
+            final String msg = "ldap profile '" + getIdentifier() + "' is not enabled";
+            throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, msg ) );
+        }
+    }
+
+    public boolean isEnabled()
+    {
+        return readSettingAsBoolean( PwmSetting.LDAP_PROFILE_ENABLED );
+    }
+
     @Override
     @Override
     public String toString()
     public String toString()
     {
     {

+ 29 - 3
server/src/main/java/password/pwm/config/profile/NewUserProfile.java

@@ -34,6 +34,7 @@ import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.password.PasswordUtility;
 import password.pwm.util.password.PasswordUtility;
 
 
@@ -51,9 +52,12 @@ public class NewUserProfile extends AbstractProfile implements Profile
     private Instant newUserPasswordPolicyCacheTime;
     private Instant newUserPasswordPolicyCacheTime;
     private final Map<Locale, PwmPasswordPolicy> newUserPasswordPolicyCache = new HashMap<>();
     private final Map<Locale, PwmPasswordPolicy> newUserPasswordPolicyCache = new HashMap<>();
 
 
-    protected NewUserProfile( final String identifier, final Map<PwmSetting, StoredValue> storedValueMap )
+    private final StoredConfiguration storedConfiguration;
+
+    protected NewUserProfile( final String identifier, final Map<PwmSetting, StoredValue> storedValueMap, final StoredConfiguration storedConfiguration )
     {
     {
         super( identifier, storedValueMap );
         super( identifier, storedValueMap );
+        this.storedConfiguration = storedConfiguration;
     }
     }
 
 
     @Override
     @Override
@@ -117,7 +121,7 @@ public class NewUserProfile extends AbstractProfile implements Profile
                 lookupDN = configuredNewUserPasswordDN;
                 lookupDN = configuredNewUserPasswordDN;
             }
             }
 
 
-            if ( lookupDN.isEmpty() )
+            if ( StringUtil.isEmpty( lookupDN ) )
             {
             {
                 throw new PwmUnrecoverableException( new ErrorInformation(
                 throw new PwmUnrecoverableException( new ErrorInformation(
                         PwmError.ERROR_INVALID_CONFIG,
                         PwmError.ERROR_INVALID_CONFIG,
@@ -172,7 +176,29 @@ public class NewUserProfile extends AbstractProfile implements Profile
         @Override
         @Override
         public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
         public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
         {
         {
-            return new NewUserProfile( identifier, makeValueMap( storedConfiguration, identifier, PROFILE_TYPE.getCategory() ) );
+            return new NewUserProfile( identifier, makeValueMap( storedConfiguration, identifier, PROFILE_TYPE.getCategory() ), storedConfiguration );
+        }
+    }
+
+    public LdapProfile getLdapProfile()
+            throws PwmUnrecoverableException
+    {
+        final Configuration configuration = new Configuration( storedConfiguration );
+        final String configuredProfile = readSettingAsString( PwmSetting.NEWUSER_LDAP_PROFILE );
+        if ( !StringUtil.isEmpty( configuredProfile ) )
+        {
+            final LdapProfile ldapProfile = configuration.getLdapProfiles().get( configuredProfile );
+            if ( ldapProfile == null )
+            {
+                throw new PwmUnrecoverableException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[]
+                        {
+                                "configured ldap profile for new user profile is invalid.  check setting "
+                                        + PwmSetting.NEWUSER_LDAP_PROFILE.toMenuLocationDebug( this.getIdentifier(), PwmConstants.DEFAULT_LOCALE ),
+                                }
+                ) );
+            }
+            return ldapProfile;
         }
         }
+        return new Configuration( storedConfiguration ).getDefaultLdapProfile();
     }
     }
 }
 }

+ 6 - 4
server/src/main/java/password/pwm/config/profile/ProfileDefinition.java

@@ -23,6 +23,8 @@ package password.pwm.config.profile;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.PwmSettingCategory;
 
 
+import java.util.Optional;
+
 public enum ProfileDefinition
 public enum ProfileDefinition
 {
 {
     Helpdesk(
     Helpdesk(
@@ -142,9 +144,9 @@ public enum ProfileDefinition
         return category;
         return category;
     }
     }
 
 
-    public PwmSetting getQueryMatch( )
+    public Optional<PwmSetting> getQueryMatch( )
     {
     {
-        return queryMatch;
+        return Optional.ofNullable( queryMatch );
     }
     }
 
 
     public Class<? extends Profile> getProfileImplClass()
     public Class<? extends Profile> getProfileImplClass()
@@ -152,8 +154,8 @@ public enum ProfileDefinition
         return profileImplClass;
         return profileImplClass;
     }
     }
 
 
-    public Class<? extends Profile.ProfileFactory> getProfileFactoryClass()
+    public Optional<Class<? extends Profile.ProfileFactory>> getProfileFactoryClass()
     {
     {
-        return profileFactoryClass;
+        return Optional.ofNullable( profileFactoryClass );
     }
     }
 }
 }

+ 6 - 7
server/src/main/java/password/pwm/config/profile/ProfileUtility.java

@@ -41,15 +41,14 @@ public class ProfileUtility
 {
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( ProfileUtility.class );
     private static final PwmLogger LOGGER = PwmLogger.forClass( ProfileUtility.class );
 
 
-    public static Optional<String> discoverProfileIDforUser(
+    public static Optional<String> discoverProfileIDForUser(
             final CommonValues commonValues,
             final CommonValues commonValues,
             final UserIdentity userIdentity,
             final UserIdentity userIdentity,
             final ProfileDefinition profileDefinition
             final ProfileDefinition profileDefinition
     )
     )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final String profileID = discoverProfileIDforUser( commonValues.getPwmApplication(), commonValues.getSessionLabel(), userIdentity, profileDefinition );
-        return Optional.ofNullable( profileID );
+        return discoverProfileIDForUser( commonValues.getPwmApplication(), commonValues.getSessionLabel(), userIdentity, profileDefinition );
     }
     }
 
 
     public static <T extends Profile> T profileForUser(
     public static <T extends Profile> T profileForUser(
@@ -60,7 +59,7 @@ public class ProfileUtility
     )
     )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final Optional<String> profileID = discoverProfileIDforUser( commonValues, userIdentity, profileDefinition );
+        final Optional<String> profileID = discoverProfileIDForUser( commonValues, userIdentity, profileDefinition );
         if ( !profileID.isPresent() )
         if ( !profileID.isPresent() )
         {
         {
             throw PwmUnrecoverableException.newException( PwmError.ERROR_NO_PROFILE_ASSIGNED, "profile of type " + profileDefinition + " is required but not assigned" );
             throw PwmUnrecoverableException.newException( PwmError.ERROR_NO_PROFILE_ASSIGNED, "profile of type " + profileDefinition + " is required but not assigned" );
@@ -70,7 +69,7 @@ public class ProfileUtility
     }
     }
 
 
 
 
-    public static String discoverProfileIDforUser(
+    public static Optional<String> discoverProfileIDForUser(
             final PwmApplication pwmApplication,
             final PwmApplication pwmApplication,
             final SessionLabel sessionLabel,
             final SessionLabel sessionLabel,
             final UserIdentity userIdentity,
             final UserIdentity userIdentity,
@@ -85,10 +84,10 @@ public class ProfileUtility
             final boolean match = LdapPermissionTester.testUserPermissions( pwmApplication, sessionLabel, userIdentity, queryMatches );
             final boolean match = LdapPermissionTester.testUserPermissions( pwmApplication, sessionLabel, userIdentity, queryMatches );
             if ( match )
             if ( match )
             {
             {
-                return profile.getIdentifier();
+                return Optional.of( profile.getIdentifier() );
             }
             }
         }
         }
-        return null;
+        return Optional.empty();
     }
     }
 
 
     public static List<String> profileIDsForCategory( final Configuration configuration, final PwmSettingCategory pwmSettingCategory )
     public static List<String> profileIDsForCategory( final Configuration configuration, final PwmSettingCategory pwmSettingCategory )

+ 33 - 1
server/src/main/java/password/pwm/health/ConfigurationChecker.java

@@ -138,7 +138,8 @@ public class ConfigurationChecker implements HealthChecker
             VerifyResponseLdapAttribute.class,
             VerifyResponseLdapAttribute.class,
             VerifyDbConfiguredIfNeeded.class,
             VerifyDbConfiguredIfNeeded.class,
             VerifyIfDeprecatedSendMethodValuesUsed.class,
             VerifyIfDeprecatedSendMethodValuesUsed.class,
-            VerifyIfDeprecatedJsFormOptionUsed.class
+            VerifyIfDeprecatedJsFormOptionUsed.class,
+            VerifyNewUserLdapProfile.class
     ) );
     ) );
 
 
     static class VerifyBasicConfigs implements ConfigHealthCheck
     static class VerifyBasicConfigs implements ConfigHealthCheck
@@ -174,6 +175,11 @@ public class ConfigurationChecker implements HealthChecker
                 records.add( HealthRecord.forMessage( HealthMessage.Config_ShowDetailedErrors, PwmSetting.DISPLAY_SHOW_DETAILED_ERRORS.toMenuLocationDebug( null, locale ) ) );
                 records.add( HealthRecord.forMessage( HealthMessage.Config_ShowDetailedErrors, PwmSetting.DISPLAY_SHOW_DETAILED_ERRORS.toMenuLocationDebug( null, locale ) ) );
             }
             }
 
 
+            if ( config.getLdapProfiles().isEmpty() )
+            {
+                records.add( HealthRecord.forMessage( HealthMessage.Config_NoLdapProfiles ) );
+            }
+
             for ( final LdapProfile ldapProfile : config.getLdapProfiles().values() )
             for ( final LdapProfile ldapProfile : config.getLdapProfiles().values() )
             {
             {
                 final String testUserDN = ldapProfile.readSettingAsString( PwmSetting.LDAP_TEST_USER_DN );
                 final String testUserDN = ldapProfile.readSettingAsString( PwmSetting.LDAP_TEST_USER_DN );
@@ -380,6 +386,31 @@ public class ConfigurationChecker implements HealthChecker
         }
         }
     }
     }
 
 
+    static class VerifyNewUserLdapProfile implements ConfigHealthCheck
+    {
+        @Override
+        public List<HealthRecord> healthCheck( final Configuration config, final Locale locale )
+        {
+            final List<HealthRecord> records = new ArrayList<>();
+            for ( final NewUserProfile newUserProfile : config.getNewUserProfiles().values() )
+            {
+                final String configuredProfile = newUserProfile.readSettingAsString( PwmSetting.NEWUSER_LDAP_PROFILE );
+                if ( !StringUtil.isEmpty( configuredProfile ) )
+                {
+                    final LdapProfile ldapProfile = config.getLdapProfiles().get( configuredProfile );
+
+                    if ( ldapProfile == null )
+                    {
+                        records.add( HealthRecord.forMessage(
+                                HealthMessage.Config_InvalidLdapProfile,
+                                PwmSetting.NEWUSER_LDAP_PROFILE.toMenuLocationDebug( newUserProfile.getIdentifier(), locale ) ) );
+                    }
+                }
+            }
+            return Collections.unmodifiableList( records );
+        }
+    }
+
     static class VerifyIfDeprecatedJsFormOptionUsed implements ConfigHealthCheck
     static class VerifyIfDeprecatedJsFormOptionUsed implements ConfigHealthCheck
     {
     {
         @Override
         @Override
@@ -512,6 +543,7 @@ public class ConfigurationChecker implements HealthChecker
         }
         }
     }
     }
 
 
+
     interface ConfigHealthCheck
     interface ConfigHealthCheck
     {
     {
         List<HealthRecord> healthCheck(
         List<HealthRecord> healthCheck(

+ 3 - 0
server/src/main/java/password/pwm/health/HealthMessage.java

@@ -71,6 +71,9 @@ public enum HealthMessage
     Config_Certificate( HealthStatus.WARN, HealthTopic.Configuration ),
     Config_Certificate( HealthStatus.WARN, HealthTopic.Configuration ),
     Config_InvalidSendMethod( HealthStatus.CAUTION, HealthTopic.Configuration ),
     Config_InvalidSendMethod( HealthStatus.CAUTION, HealthTopic.Configuration ),
     Config_DeprecatedJSForm( HealthStatus.CONFIG, HealthTopic.Configuration ),
     Config_DeprecatedJSForm( HealthStatus.CONFIG, HealthTopic.Configuration ),
+    Config_InvalidLdapProfile( HealthStatus.CONFIG, HealthTopic.Configuration ),
+    Config_NoLdapProfiles( HealthStatus.CONFIG, HealthTopic.Configuration ),
+
     LDAP_VendorsNotSame( HealthStatus.CONFIG, HealthTopic.LDAP ),
     LDAP_VendorsNotSame( HealthStatus.CONFIG, HealthTopic.LDAP ),
     LDAP_OK( HealthStatus.GOOD, HealthTopic.LDAP ),
     LDAP_OK( HealthStatus.GOOD, HealthTopic.LDAP ),
     LDAP_RecentlyUnreachable( HealthStatus.CAUTION, HealthTopic.LDAP ),
     LDAP_RecentlyUnreachable( HealthStatus.CAUTION, HealthTopic.LDAP ),

+ 1 - 1
server/src/main/java/password/pwm/health/LDAPHealthChecker.java

@@ -924,7 +924,7 @@ public class LDAPHealthChecker implements HealthChecker
 
 
             try
             try
             {
             {
-                final LdapProfile ldapProfile = configuration.getDefaultLdapProfile();
+                final LdapProfile ldapProfile = newUserProfile.getLdapProfile();
                 if ( NewUserProfile.TEST_USER_CONFIG_VALUE.equals( policyUserStr ) )
                 if ( NewUserProfile.TEST_USER_CONFIG_VALUE.equals( policyUserStr ) )
                 {
                 {
                     final UserIdentity testUser = ldapProfile.getTestUser( pwmApplication );
                     final UserIdentity testUser = ldapProfile.getTestUser( pwmApplication );

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

@@ -79,6 +79,7 @@ public enum JspUrl
     UPDATE_ATTRIBUTES_CONFIRM( "updateprofile-confirm.jsp" ),
     UPDATE_ATTRIBUTES_CONFIRM( "updateprofile-confirm.jsp" ),
     NEW_USER( "newuser.jsp" ),
     NEW_USER( "newuser.jsp" ),
     NEW_USER_ENTER_CODE( "newuser-entercode.jsp" ),
     NEW_USER_ENTER_CODE( "newuser-entercode.jsp" ),
+    NEW_USER_REMOTE( "newuser-remote.jsp" ),
     NEW_USER_TOKEN_SUCCESS( "newuser-tokensuccess.jsp" ),
     NEW_USER_TOKEN_SUCCESS( "newuser-tokensuccess.jsp" ),
     NEW_USER_WAIT( "newuser-wait.jsp" ),
     NEW_USER_WAIT( "newuser-wait.jsp" ),
     NEW_USER_PROFILE_CHOICE( "newuser-profilechoice.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,
     ForgottenPasswordChallengeSet,
     ForgottenPasswordOptionalPageView,
     ForgottenPasswordOptionalPageView,
-    ForgottenPasswordPrompts,
-    ForgottenPasswordInstructions,
     ForgottenPasswordOtpRecord,
     ForgottenPasswordOtpRecord,
     ForgottenPasswordResendTokenEnabled,
     ForgottenPasswordResendTokenEnabled,
     ForgottenPasswordInhibitPasswordReset,
     ForgottenPasswordInhibitPasswordReset,
@@ -100,6 +98,8 @@ public enum PwmRequestAttribute
     AppDashboardData,
     AppDashboardData,
 
 
     TokenDestItems,
     TokenDestItems,
+    ExternalResponsePrompts,
+    ExternalResponseInstructions,
 
 
     GoBackAction,;
     GoBackAction,;
 }
 }

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

@@ -21,9 +21,10 @@
 package password.pwm.http.bean;
 package password.pwm.http.bean;
 
 
 import com.google.gson.annotations.SerializedName;
 import com.google.gson.annotations.SerializedName;
-import lombok.Getter;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
 import lombok.NoArgsConstructor;
-import lombok.Setter;
+import password.pwm.VerificationMethodSystem;
 import password.pwm.config.option.SessionBeanMode;
 import password.pwm.config.option.SessionBeanMode;
 import password.pwm.http.servlet.newuser.NewUserForm;
 import password.pwm.http.servlet.newuser.NewUserForm;
 
 
@@ -35,11 +36,13 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Map;
 import java.util.Set;
 import java.util.Set;
 
 
-@Getter
-@Setter
+@Data
 @NoArgsConstructor
 @NoArgsConstructor
+@EqualsAndHashCode( callSuper = false )
 public class NewUserBean extends PwmSessionBean
 public class NewUserBean extends PwmSessionBean
 {
 {
+    private static final long serialVersionUID = 1L;
+
     @SerializedName( "p" )
     @SerializedName( "p" )
     private String profileID;
     private String profileID;
 
 
@@ -70,7 +73,10 @@ public class NewUserBean extends PwmSessionBean
     @SerializedName( "ts" )
     @SerializedName( "ts" )
     private boolean tokenSent;
     private boolean tokenSent;
 
 
+    @SerializedName( "ep" )
+    private boolean externalResponsesPassed;
 
 
+    private transient VerificationMethodSystem remoteRecoveryMethod;
 
 
     @Override
     @Override
     public Type getType( )
     public Type getType( )

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/activation/ActivateUserUtils.java

@@ -342,7 +342,7 @@ class ActivateUserUtils
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final ActivateUserBean activateUserBean = pwmApplication.getSessionStateService().getBean( pwmRequest, ActivateUserBean.class );
         final ActivateUserBean activateUserBean = pwmApplication.getSessionStateService().getBean( pwmRequest, ActivateUserBean.class );
 
 
-        final Optional<String> profileID = ProfileUtility.discoverProfileIDforUser( pwmRequest.commonValues(), userIdentity, ProfileDefinition.ActivateUser );
+        final Optional<String> profileID = ProfileUtility.discoverProfileIDForUser( pwmRequest.commonValues(), userIdentity, ProfileDefinition.ActivateUser );
 
 
         if ( !profileID.isPresent() || !pwmApplication.getConfig().getUserActivationProfiles().containsKey( profileID.get() ) )
         if ( !profileID.isPresent() || !pwmApplication.getConfig().getUserActivationProfiles().containsKey( profileID.get() ) )
         {
         {

+ 8 - 7
server/src/main/java/password/pwm/http/servlet/admin/UserDebugDataReader.java

@@ -43,6 +43,7 @@ import password.pwm.util.macro.MacroMachine;
 import password.pwm.util.password.PasswordUtility;
 import password.pwm.util.password.PasswordUtility;
 
 
 import java.util.Collections;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
@@ -138,23 +139,23 @@ public class UserDebugDataReader
             final PwmApplication pwmApplication,
             final PwmApplication pwmApplication,
             final SessionLabel sessionLabel,
             final SessionLabel sessionLabel,
             final UserIdentity userIdentity
             final UserIdentity userIdentity
-    ) throws PwmUnrecoverableException
+    )
+        throws PwmUnrecoverableException
     {
     {
-        final Map<ProfileDefinition, String> results = new TreeMap<>();
+        final Map<ProfileDefinition, String> results = new TreeMap<>( Comparator.comparing( Enum::name ) );
         for ( final ProfileDefinition profileDefinition : ProfileDefinition.values() )
         for ( final ProfileDefinition profileDefinition : ProfileDefinition.values() )
         {
         {
-            if ( profileDefinition.isAuthenticated() )
+            if ( profileDefinition.getQueryMatch().isPresent() && profileDefinition.getProfileFactoryClass().isPresent() )
             {
             {
-                final String id = ProfileUtility.discoverProfileIDforUser(
+                ProfileUtility.discoverProfileIDForUser(
                         pwmApplication,
                         pwmApplication,
                         sessionLabel,
                         sessionLabel,
                         userIdentity,
                         userIdentity,
                         profileDefinition
                         profileDefinition
-                );
-
-                results.put( profileDefinition, id );
+                ).ifPresent( ( id ) -> results.put( profileDefinition, id ) );
             }
             }
         }
         }
+
         return Collections.unmodifiableMap( results );
         return Collections.unmodifiableMap( results );
     }
     }
 
 

+ 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 ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean( pwmRequest );
         final VerificationMethodSystem remoteRecoveryMethod = forgottenPasswordBean.getProgress().getRemoteRecoveryMethod();
         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 );
         final ErrorInformation errorInformation = remoteRecoveryMethod.respondToPrompts( remoteResponses );
 
 
@@ -1402,8 +1389,8 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
                 final List<VerificationMethodSystem.UserPrompt> prompts = remoteMethod.getCurrentPrompts();
                 final List<VerificationMethodSystem.UserPrompt> prompts = remoteMethod.getCurrentPrompts();
                 final String displayInstructions = remoteMethod.getCurrentDisplayInstructions();
                 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 );
                 pwmRequest.forwardToJsp( JspUrl.RECOVER_PASSWORD_REMOTE );
             }
             }
             break;
             break;

+ 10 - 9
server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java

@@ -83,6 +83,7 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.Set;
 
 
 public class ForgottenPasswordUtil
 public class ForgottenPasswordUtil
@@ -630,20 +631,20 @@ public class ForgottenPasswordUtil
     )
     )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final String forgottenProfileID = ProfileUtility.discoverProfileIDforUser(
-                pwmApplication,
-                sessionLabel,
-                userIdentity,
-                ProfileDefinition.ForgottenPassword
+        final Optional<String> profileID = ProfileUtility.discoverProfileIDForUser(
+            pwmApplication,
+            sessionLabel,
+            userIdentity,
+            ProfileDefinition.ForgottenPassword
         );
         );
 
 
-        if ( StringUtil.isEmpty( forgottenProfileID ) )
+        if ( profileID.isPresent() )
         {
         {
-            final String msg = "user does not have a forgotten password profile assigned";
-            throw PwmUnrecoverableException.newException( PwmError.ERROR_NO_PROFILE_ASSIGNED, msg );
+            return pwmApplication.getConfig().getForgottenPasswordProfiles().get( profileID.get() );
         }
         }
 
 
-        return pwmApplication.getConfig().getForgottenPasswordProfiles().get( forgottenProfileID );
+        final String msg = "user does not have a forgotten password profile assigned";
+        throw PwmUnrecoverableException.newException( PwmError.ERROR_NO_PROFILE_ASSIGNED, msg );
     }
     }
 
 
     static ForgottenPasswordProfile forgottenPasswordProfile(
     static ForgottenPasswordProfile forgottenPasswordProfile(

+ 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.HttpContentType;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.HttpMethod;
+import password.pwm.http.PwmRequest;
 import password.pwm.svc.httpclient.PwmHttpClient;
 import password.pwm.svc.httpclient.PwmHttpClient;
 import password.pwm.svc.httpclient.PwmHttpClientRequest;
 import password.pwm.svc.httpclient.PwmHttpClientRequest;
 import password.pwm.svc.httpclient.PwmHttpClientResponse;
 import password.pwm.svc.httpclient.PwmHttpClientResponse;
@@ -44,6 +45,7 @@ import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
 import password.pwm.util.macro.MacroMachine;
 
 
 import java.util.ArrayList;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
@@ -74,12 +76,7 @@ public class RemoteVerificationMethod implements VerificationMethodSystem
             return null;
             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
     @Override
@@ -145,11 +142,13 @@ public class RemoteVerificationMethod implements VerificationMethodSystem
         headers.put( HttpHeader.ContentType.getHttpName(), HttpContentType.json.getHeaderValueWithEncoding() );
         headers.put( HttpHeader.ContentType.getHttpName(), HttpContentType.json.getHeaderValueWithEncoding() );
         headers.put( HttpHeader.AcceptLanguage.getHttpName(), locale.toLanguageTag() );
         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()
         final PwmHttpClientRequest pwmHttpClientRequest = PwmHttpClientRequest.builder()
                 .method( HttpMethod.POST )
                 .method( HttpMethod.POST )
@@ -177,4 +176,26 @@ public class RemoteVerificationMethod implements VerificationMethodSystem
             throw new PwmUnrecoverableException( errorInformation );
             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 );
+    }
+
+
 }
 }

+ 42 - 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 com.novell.ldapchai.exception.ChaiUnavailableException;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
+import password.pwm.VerificationMethodSystem;
 import password.pwm.bean.TokenDestinationItem;
 import password.pwm.bean.TokenDestinationItem;
 import password.pwm.config.Configuration;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 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.AbstractPwmServlet;
 import password.pwm.http.servlet.ControlledPwmServlet;
 import password.pwm.http.servlet.ControlledPwmServlet;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.http.servlet.PwmServletDefinition;
+import password.pwm.http.servlet.forgottenpw.RemoteVerificationMethod;
 import password.pwm.i18n.Message;
 import password.pwm.i18n.Message;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfoBean;
 import password.pwm.ldap.UserInfoBean;
@@ -112,6 +114,7 @@ public class NewUserServlet extends ControlledPwmServlet
         processForm( HttpMethod.POST ),
         processForm( HttpMethod.POST ),
         validate( HttpMethod.POST ),
         validate( HttpMethod.POST ),
         enterCode( HttpMethod.POST, HttpMethod.GET ),
         enterCode( HttpMethod.POST, HttpMethod.GET ),
+        enterRemoteResponse( HttpMethod.POST ),
         reset( HttpMethod.POST ),
         reset( HttpMethod.POST ),
         agree( HttpMethod.POST ),;
         agree( HttpMethod.POST ),;
 
 
@@ -264,6 +267,10 @@ public class NewUserServlet extends ControlledPwmServlet
             return;
             return;
         }
         }
 
 
+        if ( NewUserUtils.checkForExternalResponsesVerificationProgress( pwmRequest, newUserBean, newUserProfile ) == ProcessStatus.Halt )
+        {
+            return;
+        }
 
 
         final String newUserAgreementText = newUserProfile.readSettingAsLocalizedString( PwmSetting.NEWUSER_AGREEMENT_MESSAGE,
         final String newUserAgreementText = newUserProfile.readSettingAsLocalizedString( PwmSetting.NEWUSER_AGREEMENT_MESSAGE,
                 pwmSession.getSessionStateBean().getLocale() );
                 pwmSession.getSessionStateBean().getLocale() );
@@ -273,6 +280,7 @@ public class NewUserServlet extends ControlledPwmServlet
             {
             {
                 final MacroMachine macroMachine = NewUserUtils.createMacroMachineForNewUser(
                 final MacroMachine macroMachine = NewUserUtils.createMacroMachineForNewUser(
                         pwmApplication,
                         pwmApplication,
+                        newUserProfile,
                         pwmRequest.getLabel(),
                         pwmRequest.getLabel(),
                         newUserBean.getNewUserForm(),
                         newUserBean.getNewUserForm(),
                         null
                         null
@@ -546,6 +554,40 @@ public class NewUserServlet extends ControlledPwmServlet
         return ProcessStatus.Continue;
         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" )
     @ActionHandler( action = "profileChoice" )
     private ProcessStatus handleProfileChoiceRequest( final PwmRequest pwmRequest )
     private ProcessStatus handleProfileChoiceRequest( final PwmRequest pwmRequest )
             throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException
             throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException

+ 94 - 22
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 com.novell.ldapchai.provider.DirectoryVendor;
 import password.pwm.AppProperty;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
+import password.pwm.VerificationMethodSystem;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SessionLabel;
@@ -47,10 +48,13 @@ import password.pwm.error.PwmDataValidationException;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.JspUrl;
 import password.pwm.http.ProcessStatus;
 import password.pwm.http.ProcessStatus;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequest;
+import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.PwmSession;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.NewUserBean;
 import password.pwm.http.bean.NewUserBean;
+import password.pwm.http.servlet.forgottenpw.RemoteVerificationMethod;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfoBean;
 import password.pwm.ldap.UserInfoBean;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
 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.TokenType;
 import password.pwm.svc.token.TokenUtil;
 import password.pwm.svc.token.TokenUtil;
 import password.pwm.util.PasswordData;
 import password.pwm.util.PasswordData;
-import password.pwm.util.password.RandomPasswordGenerator;
 import password.pwm.util.form.FormUtility;
 import password.pwm.util.form.FormUtility;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 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.macro.MacroMachine;
 import password.pwm.util.operations.ActionExecutor;
 import password.pwm.util.operations.ActionExecutor;
 import password.pwm.util.password.PasswordUtility;
 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.FormDataRequestBean;
 import password.pwm.ws.client.rest.form.FormDataResponseBean;
 import password.pwm.ws.client.rest.form.FormDataResponseBean;
 import password.pwm.ws.client.rest.form.RestFormDataClient;
 import password.pwm.ws.client.rest.form.RestFormDataClient;
@@ -162,11 +166,11 @@ class NewUserUtils
 
 
         // add the auto-add object classes
         // add the auto-add object classes
         {
         {
-            final LdapProfile defaultLDAPProfile = pwmApplication.getConfig().getDefaultLdapProfile();
+            final LdapProfile defaultLDAPProfile = newUserProfile.getLdapProfile();
             createObjectClasses.addAll( defaultLDAPProfile.readSettingAsStringArray( PwmSetting.AUTO_ADD_OBJECT_CLASSES ) );
             createObjectClasses.addAll( defaultLDAPProfile.readSettingAsStringArray( PwmSetting.AUTO_ADD_OBJECT_CLASSES ) );
         }
         }
 
 
-        final ChaiProvider chaiProvider = pwmApplication.getConfig().getDefaultLdapProfile().getProxyChaiProvider( pwmApplication );
+        final ChaiProvider chaiProvider = newUserProfile.getLdapProfile().getProxyChaiProvider( pwmApplication );
         try
         try
         {
         {
             // create the ldap entry
             // create the ldap entry
@@ -302,7 +306,7 @@ class NewUserUtils
         remoteWriteFormData( pwmRequest, newUserForm );
         remoteWriteFormData( pwmRequest, newUserForm );
 
 
         // authenticate the user to pwm
         // authenticate the user to pwm
-        final UserIdentity userIdentity = new UserIdentity( newUserDN, pwmApplication.getConfig().getDefaultLdapProfile().getIdentifier() );
+        final UserIdentity userIdentity = new UserIdentity( newUserDN, newUserProfile.getLdapProfile().getIdentifier() );
         final SessionAuthenticator sessionAuthenticator = new SessionAuthenticator( pwmApplication, pwmRequest, PwmAuthenticationSource.NEW_USER_REGISTRATION );
         final SessionAuthenticator sessionAuthenticator = new SessionAuthenticator( pwmApplication, pwmRequest, PwmAuthenticationSource.NEW_USER_REGISTRATION );
         sessionAuthenticator.authenticateUser( userIdentity, userPassword );
         sessionAuthenticator.authenticateUser( userIdentity, userPassword );
 
 
@@ -345,8 +349,9 @@ class NewUserUtils
     {
     {
         try
         try
         {
         {
+            final NewUserProfile newUserProfile = NewUserServlet.getNewUserProfile( pwmRequest );
             NewUserUtils.LOGGER.warn( pwmRequest, () -> "deleting ldap user account " + userDN );
             NewUserUtils.LOGGER.warn( pwmRequest, () -> "deleting ldap user account " + userDN );
-            pwmRequest.getConfig().getDefaultLdapProfile().getProxyChaiProvider( pwmRequest.getPwmApplication() ).deleteEntry( userDN );
+            newUserProfile.getLdapProfile().getProxyChaiProvider( pwmRequest.getPwmApplication() ).deleteEntry( userDN );
             NewUserUtils.LOGGER.warn( pwmRequest, () -> "ldap user account " + userDN + " has been deleted" );
             NewUserUtils.LOGGER.warn( pwmRequest, () -> "ldap user account " + userDN + " has been deleted" );
         }
         }
         catch ( final ChaiUnavailableException | ChaiOperationException e )
         catch ( final ChaiUnavailableException | ChaiOperationException e )
@@ -363,8 +368,8 @@ class NewUserUtils
     )
     )
             throws PwmUnrecoverableException, ChaiUnavailableException
             throws PwmUnrecoverableException, ChaiUnavailableException
     {
     {
-        final MacroMachine macroMachine = createMacroMachineForNewUser( pwmRequest.getPwmApplication(), pwmRequest.getLabel(), formValues, null );
         final NewUserProfile newUserProfile = NewUserServlet.getNewUserProfile( pwmRequest );
         final NewUserProfile newUserProfile = NewUserServlet.getNewUserProfile( pwmRequest );
+        final MacroMachine macroMachine = createMacroMachineForNewUser( pwmRequest.getPwmApplication(), newUserProfile, pwmRequest.getLabel(), formValues, null );
         final List<String> configuredNames = newUserProfile.readSettingAsStringArray( PwmSetting.NEWUSER_USERNAME_DEFINITION );
         final List<String> configuredNames = newUserProfile.readSettingAsStringArray( PwmSetting.NEWUSER_USERNAME_DEFINITION );
         final List<String> failedValues = new ArrayList<>();
         final List<String> failedValues = new ArrayList<>();
 
 
@@ -374,7 +379,7 @@ class NewUserUtils
 
 
         if ( configuredNames == null || configuredNames.isEmpty() || configuredNames.iterator().next().isEmpty() )
         if ( configuredNames == null || configuredNames.isEmpty() || configuredNames.iterator().next().isEmpty() )
         {
         {
-            final String namingAttribute = pwmRequest.getConfig().getDefaultLdapProfile().readSettingAsString( PwmSetting.LDAP_NAMING_ATTRIBUTE );
+            final String namingAttribute = newUserProfile.getLdapProfile().readSettingAsString( PwmSetting.LDAP_NAMING_ATTRIBUTE );
             String namingValue = null;
             String namingValue = null;
             for ( final String formKey : formValues.getFormData().keySet() )
             for ( final String formKey : formValues.getFormData().keySet() )
             {
             {
@@ -408,7 +413,7 @@ class NewUserUtils
                 if ( !testIfEntryNameExists( pwmRequest, expandedName ) )
                 if ( !testIfEntryNameExists( pwmRequest, expandedName ) )
                 {
                 {
                     NewUserUtils.LOGGER.trace( pwmRequest, () -> "generated entry name for new user is unique: " + expandedName );
                     NewUserUtils.LOGGER.trace( pwmRequest, () -> "generated entry name for new user is unique: " + expandedName );
-                    final String namingAttribute = pwmRequest.getConfig().getDefaultLdapProfile().readSettingAsString( PwmSetting.LDAP_NAMING_ATTRIBUTE );
+                    final String namingAttribute = newUserProfile.getLdapProfile().readSettingAsString( PwmSetting.LDAP_NAMING_ATTRIBUTE );
                     final String escapedName = StringUtil.escapeLdapDN( expandedName );
                     final String escapedName = StringUtil.escapeLdapDN( expandedName );
                     generatedDN = namingAttribute + "=" + escapedName + "," + expandedContext;
                     generatedDN = namingAttribute + "=" + escapedName + "," + expandedContext;
                     NewUserUtils.LOGGER.debug( pwmRequest, () -> "generated dn for new user: " + generatedDN );
                     NewUserUtils.LOGGER.debug( pwmRequest, () -> "generated dn for new user: " + generatedDN );
@@ -482,29 +487,41 @@ class NewUserUtils
         );
         );
     }
     }
 
 
+    static UserInfoBean createUserInfoBeanForNewUser(
+            final PwmApplication pwmApplication,
+            final NewUserProfile newUserProfile,
+            final NewUserForm newUserForm
+    )
+        throws PwmUnrecoverableException
+
+    {
+        final Map<String, String> formValues = newUserForm.getFormData();
+
+        final String emailAddressAttribute = newUserProfile.getLdapProfile().readSettingAsString(
+            PwmSetting.EMAIL_USER_MAIL_ATTRIBUTE );
+
+        final String usernameAttribute = newUserProfile.getLdapProfile().readSettingAsString( PwmSetting.LDAP_USERNAME_ATTRIBUTE );
+
+        return UserInfoBean.builder()
+            .userEmailAddress( formValues.get( emailAddressAttribute ) )
+            .username( formValues.get( usernameAttribute ) )
+            .attributes( formValues )
+            .build();
+    }
+
     static MacroMachine createMacroMachineForNewUser(
     static MacroMachine createMacroMachineForNewUser(
             final PwmApplication pwmApplication,
             final PwmApplication pwmApplication,
+            final NewUserProfile newUserProfile,
             final SessionLabel sessionLabel,
             final SessionLabel sessionLabel,
             final NewUserForm newUserForm,
             final NewUserForm newUserForm,
             final TokenDestinationItem tokenDestinationItem
             final TokenDestinationItem tokenDestinationItem
     )
     )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final Map<String, String> formValues = newUserForm.getFormData();
-
-        final String emailAddressAttribute = pwmApplication.getConfig().getDefaultLdapProfile().readSettingAsString(
-                PwmSetting.EMAIL_USER_MAIL_ATTRIBUTE );
-
-        final String usernameAttribute = pwmApplication.getConfig().getDefaultLdapProfile().readSettingAsString( PwmSetting.LDAP_USERNAME_ATTRIBUTE );
-
         final LoginInfoBean stubLoginBean = new LoginInfoBean();
         final LoginInfoBean stubLoginBean = new LoginInfoBean();
         stubLoginBean.setUserCurrentPassword( newUserForm.getNewUserPassword() );
         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, newUserProfile, newUserForm );
 
 
         final MacroMachine.StringReplacer stringReplacer = tokenDestinationItem == null
         final MacroMachine.StringReplacer stringReplacer = tokenDestinationItem == null
                 ? null
                 ? null
@@ -608,7 +625,7 @@ class NewUserUtils
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
         final List<FormConfiguration> formFields = newUserProfile.readSettingAsForm( PwmSetting.NEWUSER_FORM );
         final List<FormConfiguration> formFields = newUserProfile.readSettingAsForm( PwmSetting.NEWUSER_FORM );
-        final LdapProfile defaultLDAPProfile = pwmRequest.getConfig().getDefaultLdapProfile();
+        final LdapProfile defaultLDAPProfile = newUserProfile.getLdapProfile();
 
 
         final Map<String, TokenDestinationItem.Type> workingMap = new LinkedHashMap<>( FormUtility.identifyFormItemsNeedingPotentialTokenValidation(
         final Map<String, TokenDestinationItem.Type> workingMap = new LinkedHashMap<>( FormUtility.identifyFormItemsNeedingPotentialTokenValidation(
                 defaultLDAPProfile,
                 defaultLDAPProfile,
@@ -649,6 +666,60 @@ class NewUserUtils
         return Collections.unmodifiableMap( workingMap );
         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 NewUserProfile newUserProfile = NewUserServlet.getNewUserProfile( pwmRequest );
+        final UserInfo userInfo = createUserInfoBeanForNewUser( pwmRequest.getPwmApplication(), newUserProfile, 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(
     static ProcessStatus checkForTokenVerificationProgress(
             final PwmRequest pwmRequest,
             final PwmRequest pwmRequest,
             final NewUserBean newUserBean,
             final NewUserBean newUserBean,
@@ -689,6 +760,7 @@ class NewUserUtils
                     final Map<String, String> tokenPayloadMap = NewUserFormUtils.toTokenPayload( pwmRequest, newUserBean );
                     final Map<String, String> tokenPayloadMap = NewUserFormUtils.toTokenPayload( pwmRequest, newUserBean );
                     final MacroMachine macroMachine = createMacroMachineForNewUser(
                     final MacroMachine macroMachine = createMacroMachineForNewUser(
                             pwmRequest.getPwmApplication(),
                             pwmRequest.getPwmApplication(),
+                            newUserProfile,
                             pwmRequest.getLabel(),
                             pwmRequest.getLabel(),
                             newUserBean.getNewUserForm(),
                             newUserBean.getNewUserForm(),
                             tokenDestinationItem );
                             tokenDestinationItem );
@@ -733,7 +805,7 @@ class NewUserUtils
         }
         }
 
 
         final List<FormConfiguration> formFields = newUserProfile.readSettingAsForm( PwmSetting.NEWUSER_FORM );
         final List<FormConfiguration> formFields = newUserProfile.readSettingAsForm( PwmSetting.NEWUSER_FORM );
-        final LdapProfile defaultLDAPProfile = pwmRequest.getConfig().getDefaultLdapProfile();
+        final LdapProfile defaultLDAPProfile = newUserProfile.getLdapProfile();
 
 
         final Map<String, TokenDestinationItem.Type> tokenTypeMap = FormUtility.identifyFormItemsNeedingPotentialTokenValidation(
         final Map<String, TokenDestinationItem.Type> tokenTypeMap = FormUtility.identifyFormItemsNeedingPotentialTokenValidation(
                 defaultLDAPProfile,
                 defaultLDAPProfile,

+ 3 - 3
server/src/main/java/password/pwm/ldap/UserInfoReader.java

@@ -650,10 +650,10 @@ public class UserInfoReader implements UserInfo
         {
         {
             if ( profileDefinition.isAuthenticated() )
             if ( profileDefinition.isAuthenticated() )
             {
             {
-                final String profileID = ProfileUtility.discoverProfileIDforUser( pwmApplication, sessionLabel, userIdentity, profileDefinition );
-                returnMap.put( profileDefinition, profileID );
-                if ( profileID != null )
+                final Optional<String> profileID = ProfileUtility.discoverProfileIDForUser( pwmApplication, sessionLabel, userIdentity, profileDefinition );
+                if ( profileID.isPresent() )
                 {
                 {
+                    returnMap.put( profileDefinition, profileID.get() );
                     LOGGER.debug( sessionLabel, () -> "assigned " + profileDefinition.toString() + " profileID \"" + profileID + "\" to " + userIdentity.toDisplayString() );
                     LOGGER.debug( sessionLabel, () -> "assigned " + profileDefinition.toString() + " profileID \"" + profileID + "\" to " + userIdentity.toDisplayString() );
                 }
                 }
                 else
                 else

+ 8 - 7
server/src/main/java/password/pwm/util/password/PasswordUtility.java

@@ -99,6 +99,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
+import java.util.Optional;
 
 
 /**
 /**
  * @author Jason D. Rivard
  * @author Jason D. Rivard
@@ -597,20 +598,20 @@ public class PasswordUtility
         final boolean sendPassword = helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_SEND_PASSWORD );
         final boolean sendPassword = helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_SEND_PASSWORD );
         if ( sendPassword )
         if ( sendPassword )
         {
         {
-            final MessageSendMethod messageSendMethod;
+            final Optional<String> profileID = ProfileUtility.discoverProfileIDForUser( pwmApplication, sessionLabel, userIdentity, ProfileDefinition.ForgottenPassword );
+            if ( profileID.isPresent() )
             {
             {
-                final String profileID = ProfileUtility.discoverProfileIDforUser( pwmApplication, sessionLabel, userIdentity, ProfileDefinition.ForgottenPassword );
-                final ForgottenPasswordProfile forgottenPasswordProfile = pwmApplication.getConfig().getForgottenPasswordProfiles().get( profileID );
-                messageSendMethod = forgottenPasswordProfile.readSettingAsEnum( PwmSetting.RECOVERY_SENDNEWPW_METHOD, MessageSendMethod.class );
+                final ForgottenPasswordProfile forgottenPasswordProfile = pwmApplication.getConfig().getForgottenPasswordProfiles().get( profileID.get() );
+                final MessageSendMethod messageSendMethod = forgottenPasswordProfile.readSettingAsEnum( PwmSetting.RECOVERY_SENDNEWPW_METHOD, MessageSendMethod.class );
 
 
-            }
-            PasswordUtility.sendNewPassword(
+                PasswordUtility.sendNewPassword(
                     userInfo,
                     userInfo,
                     pwmApplication,
                     pwmApplication,
                     newPassword,
                     newPassword,
                     pwmRequest.getLocale(),
                     pwmRequest.getLocale(),
                     messageSendMethod
                     messageSendMethod
-            );
+                );
+            }
         }
         }
     }
     }
 
 

+ 21 - 27
server/src/main/java/password/pwm/ws/server/rest/RestProfileServer.java

@@ -46,7 +46,6 @@ import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.util.FormMap;
 import password.pwm.util.FormMap;
 import password.pwm.util.form.FormUtility;
 import password.pwm.util.form.FormUtility;
-import password.pwm.util.java.StringUtil;
 import password.pwm.util.macro.MacroMachine;
 import password.pwm.util.macro.MacroMachine;
 import password.pwm.ws.server.RestMethodHandler;
 import password.pwm.ws.server.RestMethodHandler;
 import password.pwm.ws.server.RestRequest;
 import password.pwm.ws.server.RestRequest;
@@ -62,6 +61,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.Set;
 
 
 @WebServlet(
 @WebServlet(
@@ -122,19 +122,7 @@ public class RestProfileServer extends RestServlet
     {
     {
         final TargetUserIdentity targetUserIdentity = RestUtility.resolveRequestedUsername( restRequest, username );
         final TargetUserIdentity targetUserIdentity = RestUtility.resolveRequestedUsername( restRequest, username );
 
 
-        final String updateProfileID = ProfileUtility.discoverProfileIDforUser(
-                restRequest.getPwmApplication(),
-                restRequest.getSessionLabel(),
-                targetUserIdentity.getUserIdentity(),
-                ProfileDefinition.UpdateAttributes
-        );
-
-        if ( StringUtil.isEmpty( updateProfileID ) )
-        {
-            throw new PwmUnrecoverableException( PwmError.ERROR_NO_PROFILE_ASSIGNED );
-        }
-
-        final UpdateProfileProfile updateProfileProfile = restRequest.getPwmApplication().getConfig().getUpdateAttributesProfile().get( updateProfileID );
+        final UpdateProfileProfile updateProfileProfile = getProfile( restRequest, targetUserIdentity );
 
 
         final Map<String, String> profileData = new HashMap<>();
         final Map<String, String> profileData = new HashMap<>();
         {
         {
@@ -187,6 +175,24 @@ public class RestProfileServer extends RestServlet
         }
         }
     }
     }
 
 
+    private static UpdateProfileProfile getProfile( final RestRequest restRequest, final TargetUserIdentity targetUserIdentity )
+        throws PwmUnrecoverableException
+    {
+        final Optional<String> updateProfileID = ProfileUtility.discoverProfileIDForUser(
+            restRequest.getPwmApplication(),
+            restRequest.getSessionLabel(),
+            targetUserIdentity.getUserIdentity(),
+            ProfileDefinition.UpdateAttributes
+        );
+
+        if ( !updateProfileID.isPresent() )
+        {
+            throw new PwmUnrecoverableException( PwmError.ERROR_NO_PROFILE_ASSIGNED );
+        }
+
+        return restRequest.getPwmApplication().getConfig().getUpdateAttributesProfile().get( updateProfileID.get() );
+    }
+
     private static RestResultBean doPostProfileDataImpl(
     private static RestResultBean doPostProfileDataImpl(
             final RestRequest restRequest,
             final RestRequest restRequest,
             final JsonProfileData jsonInput
             final JsonProfileData jsonInput
@@ -201,19 +207,7 @@ public class RestProfileServer extends RestServlet
 
 
         final TargetUserIdentity targetUserIdentity = RestUtility.resolveRequestedUsername( restRequest, username );
         final TargetUserIdentity targetUserIdentity = RestUtility.resolveRequestedUsername( restRequest, username );
 
 
-        final String updateProfileID = ProfileUtility.discoverProfileIDforUser(
-                restRequest.getPwmApplication(),
-                restRequest.getSessionLabel(),
-                targetUserIdentity.getUserIdentity(),
-                ProfileDefinition.UpdateAttributes
-        );
-
-        if ( StringUtil.isEmpty( updateProfileID ) )
-        {
-            throw new PwmUnrecoverableException( PwmError.ERROR_NO_PROFILE_ASSIGNED );
-        }
-
-        final UpdateProfileProfile updateProfileProfile = restRequest.getPwmApplication().getConfig().getUpdateAttributesProfile().get( updateProfileID );
+        final UpdateProfileProfile updateProfileProfile = getProfile( restRequest, targetUserIdentity );
 
 
         {
         {
             final List<UserPermission> userPermission = updateProfileProfile.readSettingAsUserPermission( PwmSetting.UPDATE_PROFILE_QUERY_MATCH );
             final List<UserPermission> userPermission = updateProfileProfile.readSettingAsUserPermission( PwmSetting.UPDATE_PROFILE_QUERY_MATCH );

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

@@ -2687,6 +2687,11 @@
             <value>default</value>
             <value>default</value>
         </default>
         </default>
     </setting>
     </setting>
+    <setting hidden="false" key="newUser.ldapProfile" level="1" required="true">
+        <default>
+            <value/>
+        </default>
+    </setting>
     <setting hidden="false" key="newUser.createContext" level="1" required="true">
     <setting hidden="false" key="newUser.createContext" level="1" required="true">
         <flag>ldapDNsyntax</flag>
         <flag>ldapDNsyntax</flag>
         <default>
         <default>
@@ -2754,6 +2759,11 @@
             <value>false</value>
             <value>false</value>
         </default>
         </default>
     </setting>
     </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">
     <setting hidden="false" key="newUser.passwordPolicy.user" level="1" required="true">
         <default>
         <default>
             <value><![CDATA[TESTUSER]]></value>
             <value><![CDATA[TESTUSER]]></value>

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

@@ -62,6 +62,8 @@ HealthMessage_Config_DNValueValidity=LDAP DN configuration setting %1% issue: %2
 HealthMessage_Config_Certificate=Certificate for setting %1% issue: %2%
 HealthMessage_Config_Certificate=Certificate for setting %1% issue: %2%
 HealthMessage_Config_InvalidSendMethod=The send method '%1%' is no longer available for setting %2%.  Please modify the configuration to use an alternate value.
 HealthMessage_Config_InvalidSendMethod=The send method '%1%' is no longer available for setting %2%.  Please modify the configuration to use an alternate value.
 HealthMessage_Config_DeprecatedJSForm=The javascript form option in the form setting %1% has been deprecated and will be removed in a future version.  Use the setting %2% instead.
 HealthMessage_Config_DeprecatedJSForm=The javascript form option in the form setting %1% has been deprecated and will be removed in a future version.  Use the setting %2% instead.
+HealthMessage_Config_InvalidLdapProfile=The configured LDAP profile is invalid or inactive for setting %1%.
+HealthMessage_Config_NoLdapProfiles=There are no enabled LDAP Profiles configured.
 HealthMessage_LDAP_VendorsNotSame=LDAP directories of different vendor types are in use.  This configuration may cause undesirable side effects and is not supported.  %1%
 HealthMessage_LDAP_VendorsNotSame=LDAP directories of different vendor types are in use.  This configuration may cause undesirable side effects and is not supported.  %1%
 HealthMessage_LDAP_Ad_History_Asn_Missing=%1% is enabled, but the server at %2% does not support this feature.  Check to be sure it is upgraded to Windows Server 2008 R2 SP1 or greater.  Password changes against this server may fail until this is resolved.
 HealthMessage_LDAP_Ad_History_Asn_Missing=%1% is enabled, but the server at %2% does not support this feature.  Check to be sure it is upgraded to Windows Server 2008 R2 SP1 or greater.  Password changes against this server may fail until this is resolved.
 HealthMessage_LDAP_RecentlyUnreachable=LDAP profile %1% was recently unavailable (%2% ago at %3%): %4%
 HealthMessage_LDAP_RecentlyUnreachable=LDAP profile %1% was recently unavailable (%2% ago at %3%): %4%

+ 5 - 1
server/src/main/resources/password/pwm/i18n/PwmSetting.properties

@@ -498,7 +498,8 @@ Setting_Description_network.allowMultiIPSession=Enable this option to allow @Pwm
 Setting_Description_network.ip.permittedRange=Enable this option to have @PwmAppName@ only permit connections originating from the specified IP address ranges.  If disabled (default), @PwmAppName@ permits any source IP address. <p>Supported range specifications are\:<p><ul><li>Full IPv4 address, such as <b>12.34.56.78</b></li><li>Full IPv6 address, such as <b>2001\:18e8\:3\:171\:218\:8bff\:fe2a\:56a4</b></li><li>Partial IPv4 address, such as <b>12.34</b> (which matches any IP addres starting <b>12.34</b></li><li>IPv4 network/netmask, such as <b>18.25.0.0/255.255.0.0</b></li><li>IPv4 or IPv6 CIDR slash notation, such as <b>18.25.0.0/16</b> or <b>2001\:18e8\:3\:171\:\:/64</b></li></ul>
 Setting_Description_network.ip.permittedRange=Enable this option to have @PwmAppName@ only permit connections originating from the specified IP address ranges.  If disabled (default), @PwmAppName@ permits any source IP address. <p>Supported range specifications are\:<p><ul><li>Full IPv4 address, such as <b>12.34.56.78</b></li><li>Full IPv6 address, such as <b>2001\:18e8\:3\:171\:218\:8bff\:fe2a\:56a4</b></li><li>Partial IPv4 address, such as <b>12.34</b> (which matches any IP addres starting <b>12.34</b></li><li>IPv4 network/netmask, such as <b>18.25.0.0/255.255.0.0</b></li><li>IPv4 or IPv6 CIDR slash notation, such as <b>18.25.0.0/16</b> or <b>2001\:18e8\:3\:171\:\:/64</b></li></ul>
 Setting_Description_network.requiredHttpHeaders=<p>If specified, any HTTP/S request sent to this @PwmAppName@ application server must include these headers.  This feature is useful if you have an upstream security gateway, proxy or web server and wish to only allow sessions from the gateway, and deny direct access to this @PwmAppName@ application server from clients.</p><p>The settings must be in <code>name\=value</code> format.  If the upstream security gateway, proxy or web server is not setting these name/value headers, you will no longer be able to access this @PwmAppName@ application server.</p><p><b>WARNING:</b>If the client you are using to access this server is not setting the headers configured here, this @PwmAppName@ server will become inaccessible.</p>
 Setting_Description_network.requiredHttpHeaders=<p>If specified, any HTTP/S request sent to this @PwmAppName@ application server must include these headers.  This feature is useful if you have an upstream security gateway, proxy or web server and wish to only allow sessions from the gateway, and deny direct access to this @PwmAppName@ application server from clients.</p><p>The settings must be in <code>name\=value</code> format.  If the upstream security gateway, proxy or web server is not setting these name/value headers, you will no longer be able to access this @PwmAppName@ application server.</p><p><b>WARNING:</b>If the client you are using to access this server is not setting the headers configured here, this @PwmAppName@ server will become inaccessible.</p>
 Setting_Description_network.reverseDNS.enable=Enable this option to have @PwmAppName@ use its reverse DNS system to record the hostname of the client.  In some cases this can cause performance issues so you can disable it if you do not requrie it.
 Setting_Description_network.reverseDNS.enable=Enable this option to have @PwmAppName@ use its reverse DNS system to record the hostname of the client.  In some cases this can cause performance issues so you can disable it if you do not requrie it.
-Setting_Description_newUser.createContext=Specify the LDAP context where you would like @PwmAppName@ to create new users. You can use macros in this setting. @PwmAppName@ uses the default LDAP profile when creating new user.
+Setting_Description_newUser.createContext=Specify the LDAP context where you would like @PwmAppName@ to create new users. You can use macros in this setting.
+Setting_Description_newUser.ldapProfile=Specify the LDAP profile where you would like @PwmAppName@ to create new users. If blank, the default LDAP profile will be used when creating new user.
 Setting_Description_newUser.deleteOnFail=Enable this option to have @PwmAppName@ delete the new user account if the creation fails for some reason.  It deletes the (potentially partially-created) "broken" account in LDAP.
 Setting_Description_newUser.deleteOnFail=Enable this option to have @PwmAppName@ delete the new user account if the creation fails for some reason.  It deletes the (potentially partially-created) "broken" account in LDAP.
 Setting_Description_newUser.email.verification=Enable this option to have @PwmAppName@ send an email to the new user's email address before it creates the account.  The new user must verify receipt of the email before @PwmAppName@ creates the account. All of your email settings must also be filled out before this will work. Testing the email settings should take place to verify that this email will be sent.
 Setting_Description_newUser.email.verification=Enable this option to have @PwmAppName@ send an email to the new user's email address before it creates the account.  The new user must verify receipt of the email before @PwmAppName@ creates the account. All of your email settings must also be filled out before this will work. Testing the email settings should take place to verify that this email will be sent.
 Setting_Description_newUser.enable=Enable this option to allow @PwmAppName@ to display the new user registration.
 Setting_Description_newUser.enable=Enable this option to allow @PwmAppName@ to display the new user registration.
@@ -512,6 +513,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.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.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.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=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.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> 
 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> 
@@ -1026,6 +1028,7 @@ Setting_Label_network.ip.permittedRange=Permitted IP Network Addresses
 Setting_Label_network.requiredHttpHeaders=Required HTTP Headers
 Setting_Label_network.requiredHttpHeaders=Required HTTP Headers
 Setting_Label_network.reverseDNS.enable=Enable Reverse DNS
 Setting_Label_network.reverseDNS.enable=Enable Reverse DNS
 Setting_Label_newUser.createContext=Creation Context
 Setting_Label_newUser.createContext=Creation Context
+Setting_Label_newUser.ldapProfile=LDAP Profile
 Setting_Label_newUser.deleteOnFail=Delete On Creation Failure
 Setting_Label_newUser.deleteOnFail=Delete On Creation Failure
 Setting_Label_newUser.email.verification=Enable New User Email Verification
 Setting_Label_newUser.email.verification=Enable New User Email Verification
 Setting_Label_newUser.enable=Enable New User Registration
 Setting_Label_newUser.enable=Enable New User Registration
@@ -1039,6 +1042,7 @@ Setting_Label_newUser.profile.visible=Profile Visible on Menu
 Setting_Label_newUser.promptForPassword=Prompt User for Password
 Setting_Label_newUser.promptForPassword=Prompt User for Password
 Setting_Label_newUser.redirectUrl=After Registration Redirect URL
 Setting_Label_newUser.redirectUrl=After Registration Redirect URL
 Setting_Label_newUser.sms.verification=Enable New User SMS Verification
 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=New User Email Token Maximum Lifetime
 Setting_Label_newUser.token.lifetime.sms=New User SMS Token Maximum Lifetime
 Setting_Label_newUser.token.lifetime.sms=New User SMS Token Maximum Lifetime
 Setting_Label_newUser.username.definition=LDAP Entry ID Definition
 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>
 <!DOCTYPE html>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ page import="password.pwm.VerificationMethodSystem" %>
 <%@ 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="java.util.List" %>
-<%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
-<%@ page import="password.pwm.http.PwmRequestAttribute" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%@ include file="fragment/header.jsp" %>
 <%@ include file="fragment/header.jsp" %>
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
@@ -40,8 +40,8 @@
     <div id="centerbody">
     <div id="centerbody">
         <h1 id="page-content-title"><pwm:display key="Title_ForgottenPassword" displayIfMissing="true"/></h1>
         <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>
         <p><%=instructions%></p>
         <form action="<pwm:current-url/>" method="post" enctype="application/x-www-form-urlencoded" name="search" class="pwm-form" autocomplete="off">
         <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>
+

+ 1 - 1
webapp/src/main/webapp/public/resources/js/main.js

@@ -1213,7 +1213,7 @@ PWM_MAIN.messageDivFloatHandler = function() {
 };
 };
 
 
 PWM_MAIN.pwmFormValidator = function(validationProps, reentrant) {
 PWM_MAIN.pwmFormValidator = function(validationProps, reentrant) {
-    var CONSOLE_DEBUG = true;
+    var CONSOLE_DEBUG = false;
 
 
     var serviceURL = validationProps['serviceURL'];
     var serviceURL = validationProps['serviceURL'];
     var readDataFunction = validationProps['readDataFunction'];
     var readDataFunction = validationProps['readDataFunction'];