瀏覽代碼

Merge remote-tracking branch 'origin/master' into CEF

rkeil 7 年之前
父節點
當前提交
b690a2017e
共有 25 個文件被更改,包括 618 次插入329 次删除
  1. 2 2
      client/src/pages/changepassword/password-suggestions.scss
  2. 5 5
      server/pom.xml
  3. 10 6
      server/src/main/java/password/pwm/PwmApplication.java
  4. 1 1
      server/src/main/java/password/pwm/bean/SmsItemBean.java
  5. 6 4
      server/src/main/java/password/pwm/config/PwmSetting.java
  6. 59 0
      server/src/main/java/password/pwm/config/function/SMSGatewayCertImportFunction.java
  7. 19 13
      server/src/main/java/password/pwm/http/servlet/ActivateUserServlet.java
  8. 3 5
      server/src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java
  9. 22 3
      server/src/main/java/password/pwm/http/servlet/UpdateProfileServlet.java
  10. 26 2
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java
  11. 12 9
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java
  12. 19 15
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java
  13. 22 4
      server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java
  14. 55 52
      server/src/main/java/password/pwm/svc/token/TokenService.java
  15. 3 3
      server/src/main/java/password/pwm/util/RandomPasswordGenerator.java
  16. 104 22
      server/src/main/java/password/pwm/util/db/DatabaseAccessorImpl.java
  17. 4 5
      server/src/main/java/password/pwm/util/operations/PasswordUtility.java
  18. 121 99
      server/src/main/java/password/pwm/util/queue/SmsQueueManager.java
  19. 37 25
      server/src/main/java/password/pwm/ws/server/rest/RestRandomPasswordServer.java
  20. 9 3
      server/src/main/resources/password/pwm/config/PwmSetting.xml
  21. 1 1
      server/src/main/resources/password/pwm/i18n/Admin.properties
  22. 2 0
      server/src/main/resources/password/pwm/i18n/PwmSetting.properties
  23. 48 23
      server/src/main/webapp/public/resources/js/changepassword.js
  24. 1 1
      server/src/main/webapp/public/resources/js/configeditor-settings.js
  25. 27 26
      server/src/main/webapp/public/resources/js/main.js

+ 2 - 2
client/src/pages/changepassword/password-suggestions.scss

@@ -1,3 +1,3 @@
-#dialogPopup .dialogBody, #html5Dialog .dialogBody {
-    height: 395px;
+.randomPasswordDialog {
+    height: 395px; width: 350px; max-width: 350px; margin-top: auto; margin-bottom: auto;
 }

+ 5 - 5
server/pom.xml

@@ -252,7 +252,7 @@
                     <dependency>
                         <groupId>com.puppycrawl.tools</groupId>
                         <artifactId>checkstyle</artifactId>
-                        <version>8.5</version>
+                        <version>8.7</version>
                     </dependency>
                 </dependencies>
                 <executions>
@@ -662,7 +662,7 @@
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-text</artifactId>
-            <version>1.1</version>
+            <version>1.2</version>
         </dependency>
         <dependency>
             <groupId>org.apache.commons</groupId>
@@ -722,12 +722,12 @@
         <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcprov-jdk15on</artifactId>
-            <version>1.58</version>
+            <version>1.59</version>
         </dependency>
         <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcpkix-jdk15on</artifactId>
-            <version>1.58</version>
+            <version>1.59</version>
         </dependency>
         <dependency>
             <groupId>javax.xml</groupId>
@@ -782,7 +782,7 @@
         <dependency>
             <groupId>com.github.ben-manes.caffeine</groupId>
             <artifactId>caffeine</artifactId>
-            <version>2.6.0</version>
+            <version>2.6.1</version>
         </dependency>
         <dependency>
             <groupId>com.nulab-inc</groupId>

+ 10 - 6
server/src/main/java/password/pwm/PwmApplication.java

@@ -25,6 +25,7 @@ package password.pwm;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
+import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SmsItemBean;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
@@ -620,22 +621,25 @@ public class PwmApplication {
     }
 
     public void sendSmsUsingQueue(
-            final SmsItemBean smsItem,
+            final String to,
+            final String message,
+            final SessionLabel sessionLabel,
             final MacroMachine macroMachine
     ) {
         final SmsQueueManager smsQueue = getSmsQueue();
         if (smsQueue == null) {
-            LOGGER.error("SMS queue is unavailable, unable to send SMS: " + smsItem.toString());
+            LOGGER.error(sessionLabel, "SMS queue is unavailable, unable to send SMS to: " + to);
             return;
         }
 
-        final SmsItemBean rewrittenSmsItem = new SmsItemBean(
-                macroMachine.expandMacros(smsItem.getTo()),
-                macroMachine.expandMacros(smsItem.getMessage())
+        final SmsItemBean smsItemBean = new SmsItemBean(
+                macroMachine.expandMacros(to),
+                macroMachine.expandMacros(message),
+                sessionLabel
         );
 
         try {
-            smsQueue.addSmsToQueue(rewrittenSmsItem);
+            smsQueue.addSmsToQueue(smsItemBean);
         } catch (PwmUnrecoverableException e) {
             LOGGER.warn("unable to add sms to queue: " + e.getMessage());
         }

+ 1 - 1
server/src/main/java/password/pwm/bean/SmsItemBean.java

@@ -34,7 +34,7 @@ import java.io.Serializable;
 public class SmsItemBean implements Serializable {
     private final String to;
     private final String message;
-
+    private final SessionLabel sessionLabel;
 
     public String toString() {
         return "SMS Item: " + JsonUtil.serialize(this);

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

@@ -334,10 +334,10 @@ public enum PwmSetting {
 
 
     // sms settings
-    SMS_MAX_QUEUE_AGE(
-            "sms.queueMaxAge", PwmSettingSyntax.DURATION, PwmSettingCategory.SMS_GATEWAY),
     SMS_GATEWAY_URL(
             "sms.gatewayURL", PwmSettingSyntax.STRING, PwmSettingCategory.SMS_GATEWAY),
+    SMS_GATEWAY_CERTIFICATES(
+            "sms.gatewayCertificates", PwmSettingSyntax.X509CERT, PwmSettingCategory.SMS_GATEWAY),
     SMS_GATEWAY_USER(
             "sms.gatewayUser", PwmSettingSyntax.STRING, PwmSettingCategory.SMS_GATEWAY),
     SMS_GATEWAY_PASSWORD(
@@ -356,8 +356,6 @@ public enum PwmSetting {
             "sms.httpRequestHeaders", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.SMS_GATEWAY),
     SMS_MAX_TEXT_LENGTH(
             "sms.maxTextLength", PwmSettingSyntax.NUMERIC, PwmSettingCategory.SMS_GATEWAY),
-    SMS_RESPONSE_OK_REGEX(
-            "sms.responseOkRegex", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.SMS_GATEWAY),
     SMS_SENDER_ID(
             "sms.senderID", PwmSettingSyntax.STRING, PwmSettingCategory.SMS_GATEWAY),
     SMS_PHONE_NUMBER_FORMAT(
@@ -370,12 +368,16 @@ public enum PwmSetting {
             "sms.requestId.length", PwmSettingSyntax.NUMERIC, PwmSettingCategory.SMS_GATEWAY),
     SMS_USE_URL_SHORTENER(
             "sms.useUrlShortener", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.SMS_GATEWAY),
+    SMS_RESPONSE_OK_REGEX(
+            "sms.responseOkRegex", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.SMS_GATEWAY),
     SMS_SUCCESS_RESULT_CODE(
             "sms.successResultCodes", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.SMS_GATEWAY),
     URL_SHORTENER_CLASS(
             "urlshortener.classname", PwmSettingSyntax.STRING, PwmSettingCategory.SMS_GATEWAY),
     URL_SHORTENER_PARAMETERS(
             "urlshortener.parameters", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.SMS_GATEWAY),
+    SMS_MAX_QUEUE_AGE(
+            "sms.queueMaxAge", PwmSettingSyntax.DURATION, PwmSettingCategory.SMS_GATEWAY),
 
     SMS_CHALLENGE_TOKEN_TEXT(
             "sms.challenge.token.message", PwmSettingSyntax.LOCALIZED_STRING, PwmSettingCategory.SMS_MESSAGES),

+ 59 - 0
server/src/main/java/password/pwm/config/function/SMSGatewayCertImportFunction.java

@@ -0,0 +1,59 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.config.function;
+
+import password.pwm.PwmConstants;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.stored.StoredConfigurationImpl;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmOperationalException;
+
+import java.net.URI;
+
+public class SMSGatewayCertImportFunction extends AbstractUriCertImportFunction {
+
+
+    @Override
+    String getUri(final StoredConfigurationImpl storedConfiguration, final PwmSetting pwmSetting, final String profile, final String extraData) throws PwmOperationalException {
+
+
+        final String uriString;
+        final String menuDebugLocation;
+
+        uriString = (String)storedConfiguration.readSetting(PwmSetting.SMS_GATEWAY_URL).toNativeObject();
+        menuDebugLocation = PwmSetting.SMS_GATEWAY_URL.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE );
+
+        if (uriString.isEmpty()) {
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,"Setting " + menuDebugLocation + " must first be configured");
+            throw new PwmOperationalException(errorInformation);
+        }
+        try {
+            URI.create(uriString);
+        } catch (IllegalArgumentException e) {
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,"Setting " + menuDebugLocation + " has an invalid URL syntax");
+            throw new PwmOperationalException(errorInformation);
+        }
+        return uriString;
+    }
+}

+ 19 - 13
server/src/main/java/password/pwm/http/servlet/ActivateUserServlet.java

@@ -32,7 +32,6 @@ import password.pwm.PwmConstants;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.bean.LoginInfoBean;
-import password.pwm.bean.SmsItemBean;
 import password.pwm.bean.TokenDestinationItem;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
@@ -581,8 +580,12 @@ public class ActivateUserServlet extends AbstractPwmServlet {
             return false;
         }
 
-        final SmsItemBean smsItem = new SmsItemBean(toSmsNumber, message);
-        pwmApplication.sendSmsUsingQueue(smsItem, pwmSession.getSessionManager().getMacroMachine(pwmApplication));
+        pwmApplication.sendSmsUsingQueue(
+                toSmsNumber,
+                message,
+                pwmRequest.getSessionLabel(),
+                pwmSession.getSessionManager().getMacroMachine(pwmApplication)
+        );
         return true;
     }
 
@@ -719,7 +722,7 @@ public class ActivateUserServlet extends AbstractPwmServlet {
             final String toSmsNumber,
             final String tokenKey
     )
-            throws PwmUnrecoverableException, ChaiUnavailableException
+            throws PwmUnrecoverableException
     {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final Configuration config = pwmApplication.getConfig();
@@ -730,15 +733,18 @@ public class ActivateUserServlet extends AbstractPwmServlet {
         final MacroMachine macroMachine = MacroMachine.forUser(pwmRequest, userIdentity);
 
         final List<TokenDestinationItem.Type> sentTypes = TokenService.TokenSender.sendToken(
-                pwmApplication,
-                null,
-                macroMachine,
-                emailItemBean,
-                pref,
-                toAddress,
-                toSmsNumber,
-                smsMessage,
-                tokenKey
+                TokenService.TokenSendInfo.builder()
+                .pwmApplication( pwmApplication )
+                .userInfo( null )
+                .macroMachine( macroMachine )
+                .configuredEmailSetting( emailItemBean )
+                .tokenSendMethod( pref )
+                .emailAddress( toAddress )
+                .smsNumber( toSmsNumber )
+                .smsMessage( smsMessage )
+                .tokenKey( tokenKey )
+                .sessionLabel( pwmRequest.getSessionLabel() )
+                .build()
         );
 
         return TokenService.TokenSender.figureDisplayString(

+ 3 - 5
server/src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java

@@ -27,13 +27,11 @@ import password.pwm.PwmConstants;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.bean.SessionLabel;
-import password.pwm.bean.SmsItemBean;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
-import password.pwm.config.value.data.FormConfiguration;
-import password.pwm.util.form.FormUtility;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.MessageSendMethod;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
@@ -49,6 +47,7 @@ import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.util.CaptchaUtility;
+import password.pwm.util.form.FormUtility;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
@@ -307,8 +306,7 @@ public class ForgottenUsernameServlet extends AbstractPwmServlet {
 
         final MacroMachine macroMachine = new MacroMachine(pwmApplication, sessionLabel, userInfo, null);
 
-        final SmsItemBean smsItem = new SmsItemBean(toNumber, smsMessage);
-        pwmApplication.sendSmsUsingQueue(smsItem, macroMachine);
+        pwmApplication.sendSmsUsingQueue(toNumber, smsMessage, sessionLabel, macroMachine);
         return null;
     }
 

+ 22 - 3
server/src/main/java/password/pwm/http/servlet/UpdateProfileServlet.java

@@ -716,7 +716,17 @@ public class UpdateProfileServlet extends ControlledPwmServlet {
                         pwmSession.getSessionStateBean().getLocale());
 
                 try {
-                    TokenService.TokenSender.sendSmsToken(pwmApplication, null, macroMachine, toNum, message, tokenKey);
+                    TokenService.TokenSender.sendSmsToken(
+                            TokenService.TokenSendInfo.builder()
+                            .pwmApplication( pwmApplication )
+                            .userInfo( null )
+                            .macroMachine( macroMachine )
+                            .smsNumber( toNum )
+                            .smsMessage( message )
+                            .tokenKey( tokenKey )
+                            .sessionLabel( pwmSession.getLabel() )
+                            .build()
+                    );
                 } catch (Exception e) {
                     throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN));
                 }
@@ -762,8 +772,17 @@ public class UpdateProfileServlet extends ControlledPwmServlet {
                         configuredEmailSetting.getBodyHtml().replace("%TOKEN%", tokenKey));
 
                 try {
-                    TokenService.TokenSender.sendEmailToken(pwmApplication, null, macroMachine, emailItemBean,
-                            toAddress, tokenKey);
+                    TokenService.TokenSender.sendEmailToken(
+                            TokenService.TokenSendInfo.builder()
+                            .pwmApplication( pwmApplication )
+                            .userInfo( null )
+                            .macroMachine( macroMachine )
+                            .configuredEmailSetting( emailItemBean )
+                            .emailAddress( toAddress )
+                            .tokenKey( tokenKey )
+                            .sessionLabel( pwmRequest.getSessionLabel() )
+                                    .build()
+                    );
                 } catch (Exception e) {
                     throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN));
                 }

+ 26 - 2
server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java

@@ -36,6 +36,7 @@ import password.pwm.config.PwmSettingTemplate;
 import password.pwm.config.PwmSettingTemplateSet;
 import password.pwm.config.SettingUIFunction;
 import password.pwm.config.StoredValue;
+import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.config.stored.ValueMetaData;
@@ -71,6 +72,7 @@ import password.pwm.i18n.Message;
 import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.ldap.LdapBrowser;
 import password.pwm.util.PasswordData;
+import password.pwm.util.RandomPasswordGenerator;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
@@ -79,6 +81,7 @@ import password.pwm.util.macro.MacroMachine;
 import password.pwm.util.queue.SmsQueueManager;
 import password.pwm.util.secure.HttpsServerCertificateManager;
 import password.pwm.ws.server.RestResultBean;
+import password.pwm.ws.server.rest.RestRandomPasswordServer;
 import password.pwm.ws.server.rest.bean.HealthData;
 
 import javax.servlet.ServletException;
@@ -137,6 +140,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet {
         testMacro(HttpMethod.POST),
         browseLdap(HttpMethod.POST),
         copyProfile(HttpMethod.POST),
+        randomPassword(HttpMethod.POST),
 
         ;
 
@@ -639,9 +643,14 @@ public class ConfigEditorServlet extends ControlledPwmServlet {
             returnRecords.add(new HealthRecord(HealthStatus.INFO, HealthTopic.SMS, "SMS not configured"));
         } else {
             final Map<String, String> testParams = pwmRequest.readBodyAsJsonStringMap();
-            final SmsItemBean testSmsItem = new SmsItemBean(testParams.get("to"), testParams.get("message"));
+            final SmsItemBean testSmsItem = new SmsItemBean(testParams.get("to"), testParams.get("message"), pwmRequest.getSessionLabel());
             try {
-                final String responseBody = SmsQueueManager.sendDirectMessage(config, testSmsItem);
+                final String responseBody = SmsQueueManager.sendDirectMessage(
+                        pwmRequest.getPwmApplication(),
+                        config,
+                        pwmRequest.getSessionLabel(),
+                        testSmsItem
+                );
                 returnRecords.add(new HealthRecord(HealthStatus.INFO, HealthTopic.SMS, "message sent"));
                 returnRecords.add(new HealthRecord(HealthStatus.INFO, HealthTopic.SMS, "response body: \n" + StringUtil.escapeHtml(responseBody)));
             } catch (PwmException e) {
@@ -927,4 +936,19 @@ public class ConfigEditorServlet extends ControlledPwmServlet {
         }
         return ProcessStatus.Halt;
     }
+
+    @ActionHandler( action = "randomPassword")
+    private ProcessStatus restRandomPassword(final PwmRequest pwmRequest)
+            throws IOException, PwmUnrecoverableException
+    {
+        final RestRandomPasswordServer.JsonInput jsonInput = JsonUtil.deserialize( pwmRequest.readRequestBodyAsString(), RestRandomPasswordServer.JsonInput.class );
+        final RandomPasswordGenerator.RandomGeneratorConfig randomConfig = RestRandomPasswordServer.jsonInputToRandomConfig( jsonInput, PwmPasswordPolicy.defaultPolicy() );
+        final PasswordData randomPassword = RandomPasswordGenerator.createRandomPassword(pwmRequest.getSessionLabel(), randomConfig, pwmRequest.getPwmApplication());
+        final RestRandomPasswordServer.JsonOutput outputMap = new RestRandomPasswordServer.JsonOutput();
+        outputMap.setPassword( randomPassword.getStringValue() );
+
+        pwmRequest.outputJsonResult( RestResultBean.withData( outputMap ) );
+
+        return ProcessStatus.Halt;
+    }
 }

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

@@ -463,15 +463,18 @@ class ForgottenPasswordUtil {
         final String smsMessage = config.readSettingAsLocalizedString(PwmSetting.SMS_CHALLENGE_TOKEN_TEXT, pwmRequest.getLocale());
 
         final List<TokenDestinationItem.Type> sentTypes = TokenService.TokenSender.sendToken(
-                pwmRequest.getPwmApplication(),
-                userInfo,
-                macroMachine,
-                emailItemBean,
-                tokenSendMethod,
-                outputDestrestTokenDataClient.getEmail(),
-                outputDestrestTokenDataClient.getSms(),
-                smsMessage,
-                tokenKey
+                TokenService.TokenSendInfo.builder()
+                .pwmApplication( pwmRequest.getPwmApplication() )
+                .userInfo( userInfo )
+                .macroMachine( macroMachine )
+                .configuredEmailSetting( emailItemBean )
+                .tokenSendMethod( tokenSendMethod )
+                .emailAddress( outputDestrestTokenDataClient.getEmail() )
+                .smsNumber( outputDestrestTokenDataClient.getSms() )
+                .smsMessage( smsMessage )
+                .tokenKey( tokenKey )
+                .sessionLabel( pwmRequest.getSessionLabel() )
+                .build()
         );
 
         StatisticsManager.incrementStat(pwmRequest, Statistic.RECOVERY_TOKENS_SENT);

+ 19 - 15
server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java

@@ -738,15 +738,18 @@ public class HelpdeskServlet extends ControlledPwmServlet {
 
         try {
             TokenService.TokenSender.sendToken(
-                    pwmRequest.getPwmApplication(),
-                    userInfo,
-                    macroMachine,
-                    emailItemBean,
-                    tokenSendMethod,
-                    destEmailAddress,
-                    userInfo.getUserSmsNumber(),
-                    smsMessage,
-                    tokenKey
+                    TokenService.TokenSendInfo.builder()
+                    .pwmApplication( pwmRequest.getPwmApplication() )
+                    .userInfo( userInfo )
+                    .macroMachine( macroMachine )
+                    .configuredEmailSetting( emailItemBean )
+                    .tokenSendMethod( tokenSendMethod )
+                    .emailAddress( destEmailAddress )
+                    .smsNumber( userInfo.getUserSmsNumber() )
+                    .smsMessage( smsMessage )
+                    .tokenKey( tokenKey )
+                    .sessionLabel( pwmRequest.getSessionLabel() )
+                    .build()
             );
         } catch (PwmException e) {
             LOGGER.error(pwmRequest, e.getErrorInformation());
@@ -1107,7 +1110,10 @@ public class HelpdeskServlet extends ControlledPwmServlet {
         );
 
         final UserIdentity userIdentity = UserIdentity.fromKey(jsonInput.getUsername(), pwmRequest.getPwmApplication());
-        final HelpdeskProfile helpdeskProfile = pwmRequest.getPwmSession().getSessionManager().getHelpdeskProfile(pwmRequest.getPwmApplication());
+        final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest );
+
+        HelpdeskServletUtil.checkIfUserIdentityViewable(pwmRequest, helpdeskProfile, userIdentity);
+
         final ChaiUser chaiUser = getChaiUser(pwmRequest, getHelpdeskProfile(pwmRequest), userIdentity);
         final UserInfo userInfo = UserInfoFactory.newUserInfo(
                 pwmRequest.getPwmApplication(),
@@ -1117,18 +1123,15 @@ public class HelpdeskServlet extends ControlledPwmServlet {
                 chaiUser.getChaiProvider()
         );
 
-        HelpdeskServletUtil.checkIfUserIdentityViewable(pwmRequest, helpdeskProfile, userIdentity);
-
         {
-            final HelpdeskUIMode mode = helpdeskProfile.readSettingAsEnum(PwmSetting.HELPDESK_CLEAR_RESPONSES, HelpdeskUIMode.class);
+            final HelpdeskUIMode mode = helpdeskProfile.readSettingAsEnum(PwmSetting.HELPDESK_SET_PASSWORD_MODE, HelpdeskUIMode.class);
             if (mode == HelpdeskUIMode.none) {
                 throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_SECURITY_VIOLATION,"setting "
-                        + PwmSetting.HELPDESK_CLEAR_RESPONSES.toMenuLocationDebug(helpdeskProfile.getIdentifier(), pwmRequest.getLocale())
+                        + PwmSetting.HELPDESK_SET_PASSWORD_MODE.toMenuLocationDebug(helpdeskProfile.getIdentifier(), pwmRequest.getLocale())
                         + " must not be set to none"));
             }
         }
 
-
         final PasswordUtility.PasswordCheckInfo passwordCheckInfo = PasswordUtility.checkEnteredPassword(
                 pwmRequest.getPwmApplication(),
                 pwmRequest.getLocale(),
@@ -1140,6 +1143,7 @@ public class HelpdeskServlet extends ControlledPwmServlet {
         );
 
         final RestCheckPasswordServer.JsonOutput jsonResponse = RestCheckPasswordServer.JsonOutput.fromPasswordCheckInfo(passwordCheckInfo);
+
         final RestResultBean restResultBean = RestResultBean.withData(jsonResponse);
         pwmRequest.outputJsonResult(restResultBean);
 

+ 22 - 4
server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java

@@ -514,8 +514,17 @@ class NewUserUtils {
                         pwmSession.getSessionStateBean().getLocale());
 
                 try {
-                    TokenService.TokenSender.sendSmsToken(pwmApplication, null, macroMachine,
-                            outputDestTokenData.getSms(), message, tokenKey);
+                    TokenService.TokenSender.sendSmsToken(
+                            TokenService.TokenSendInfo.builder()
+                            .pwmApplication( pwmApplication )
+                            .userInfo( null )
+                            .macroMachine( macroMachine )
+                            .smsNumber( outputDestTokenData.getSms() )
+                            .smsMessage( message )
+                            .tokenKey( tokenKey )
+                            .sessionLabel( pwmRequest.getSessionLabel() )
+                            .build()
+                    );
                 } catch (Exception e) {
                     throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN));
                 }
@@ -573,8 +582,17 @@ class NewUserUtils {
                         configuredEmailSetting.getBodyHtml().replace("%TOKEN%", tokenKey));
 
                 try {
-                    TokenService.TokenSender.sendEmailToken(pwmApplication, null, macroMachine, emailItemBean,
-                            outputDestTokenData.getEmail(), tokenKey);
+                    TokenService.TokenSender.sendEmailToken(
+                            TokenService.TokenSendInfo.builder()
+                                    .pwmApplication( pwmApplication )
+                                    .userInfo( null )
+                                    .macroMachine( macroMachine )
+                                    .configuredEmailSetting( emailItemBean )
+                                    .emailAddress( outputDestTokenData.getEmail() )
+                                    .tokenKey( tokenKey )
+                                    .sessionLabel( pwmRequest.getSessionLabel() )
+                                    .build()
+                    );
                 } catch (Exception e) {
                     throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN));
                 }

+ 55 - 52
server/src/main/java/password/pwm/svc/token/TokenService.java

@@ -23,12 +23,13 @@
 package password.pwm.svc.token;
 
 import com.novell.ldapchai.exception.ChaiUnavailableException;
+import lombok.Builder;
+import lombok.Value;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.SessionLabel;
-import password.pwm.bean.SmsItemBean;
 import password.pwm.bean.TokenDestinationItem;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
@@ -61,6 +62,7 @@ import password.pwm.util.db.DatabaseDataStore;
 import password.pwm.util.db.DatabaseTable;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDBDataStore;
@@ -613,17 +615,24 @@ public class TokenService implements PwmService {
         return tokenPayload;
     }
 
+    @Value
+    @Builder
+    public static class TokenSendInfo {
+        private PwmApplication pwmApplication;
+        private UserInfo userInfo;
+        private MacroMachine macroMachine;
+        private EmailItemBean configuredEmailSetting;
+        private MessageSendMethod tokenSendMethod;
+        private String emailAddress;
+        private String smsNumber;
+        private String smsMessage;
+        private String tokenKey;
+        private SessionLabel sessionLabel;
+    }
+
     public static class TokenSender {
         public static List<TokenDestinationItem.Type> sendToken(
-                final PwmApplication pwmApplication,
-                final UserInfo userInfo,
-                final MacroMachine macroMachine,
-                final EmailItemBean configuredEmailSetting,
-                final MessageSendMethod tokenSendMethod,
-                final String emailAddress,
-                final String smsNumber,
-                final String smsMessage,
-                final String tokenKey
+                final TokenSendInfo tokenSendInfo
         )
                 throws PwmUnrecoverableException
         {
@@ -631,82 +640,76 @@ public class TokenService implements PwmService {
 
             final List<TokenDestinationItem.Type> sentTypes = new ArrayList<>();
 
-            try {
-                switch (tokenSendMethod) {
-                    case NONE:
-                        // should never read here
-                        LOGGER.error("attempt to send token to destination type 'NONE'");
-                        throw new PwmUnrecoverableException(PwmError.ERROR_UNKNOWN);
-                    case SMSONLY:
-                        // Only try SMS
-                        success = sendSmsToken(pwmApplication, userInfo, macroMachine, smsNumber, smsMessage, tokenKey);
-                        sentTypes.add(TokenDestinationItem.Type.sms);
-                        break;
-                    case EMAILONLY:
-                    default:
-                        // Only try email
-                        success = sendEmailToken(pwmApplication, userInfo, macroMachine, configuredEmailSetting, emailAddress, tokenKey);
-                        sentTypes.add(TokenDestinationItem.Type.email);
-                        break;
-                }
-            } catch (ChaiUnavailableException e) {
-                throw new PwmUnrecoverableException(PwmError.forChaiError(e.getErrorCode()));
+            switch (tokenSendInfo.getTokenSendMethod()) {
+                case NONE:
+                    // should never read here
+                    LOGGER.error("attempt to send token to destination type 'NONE'");
+                    throw new PwmUnrecoverableException(PwmError.ERROR_UNKNOWN);
+                case SMSONLY:
+                    // Only try SMS
+                    success = sendSmsToken(tokenSendInfo);
+                    sentTypes.add(TokenDestinationItem.Type.sms);
+                    break;
+                case EMAILONLY:
+                default:
+                    // Only try email
+                    success = sendEmailToken(tokenSendInfo);
+                    sentTypes.add(TokenDestinationItem.Type.email);
+                    break;
             }
 
             if (!success) {
                 throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_TOKEN_MISSING_CONTACT));
             }
+
+            final PwmApplication pwmApplication = tokenSendInfo.getPwmApplication();
             pwmApplication.getStatisticsManager().incrementValue(Statistic.TOKENS_SENT);
 
             return sentTypes;
         }
 
         public static boolean sendEmailToken(
-                final PwmApplication pwmApplication,
-                final UserInfo userInfo,
-                final MacroMachine macroMachine,
-                final EmailItemBean configuredEmailSetting,
-                final String toAddress,
-                final String tokenKey
+                final TokenSendInfo tokenSendInfo
         )
-                throws PwmUnrecoverableException, ChaiUnavailableException
+                throws PwmUnrecoverableException
         {
-            if (toAddress == null || toAddress.length() < 1) {
+            final String toAddress = tokenSendInfo.getEmailAddress();
+            if ( StringUtil.isEmpty( toAddress ) ) {
                 return false;
             }
 
+            final PwmApplication pwmApplication = tokenSendInfo.getPwmApplication();
             pwmApplication.getIntruderManager().mark(RecordType.TOKEN_DEST, toAddress, null);
 
+            final EmailItemBean configuredEmailSetting = tokenSendInfo.getConfiguredEmailSetting();
             pwmApplication.getEmailQueue().submitEmailImmediate(new EmailItemBean(
                     toAddress,
                     configuredEmailSetting.getFrom(),
                     configuredEmailSetting.getSubject(),
-                    configuredEmailSetting.getBodyPlain().replace("%TOKEN%", tokenKey),
-                    configuredEmailSetting.getBodyHtml().replace("%TOKEN%", tokenKey)
-            ), userInfo, macroMachine);
+                    configuredEmailSetting.getBodyPlain().replace("%TOKEN%", tokenSendInfo.getTokenKey()),
+                    configuredEmailSetting.getBodyHtml().replace("%TOKEN%", tokenSendInfo.getTokenKey())
+            ), tokenSendInfo.getUserInfo(), tokenSendInfo.getMacroMachine());
             LOGGER.debug("token email added to send queue for " + toAddress);
             return true;
         }
 
         public static boolean sendSmsToken(
-                final PwmApplication pwmApplication,
-                final UserInfo userInfo,
-                final MacroMachine macroMachine,
-                final String smsNumber,
-                final String smsMessage,
-                final String tokenKey
+                final TokenSendInfo tokenSendInfo
         )
-                throws PwmUnrecoverableException, ChaiUnavailableException
+                throws PwmUnrecoverableException
         {
-            if (smsNumber == null || smsNumber.length() < 1) {
+            final String smsNumber = tokenSendInfo.getSmsNumber();
+            if ( StringUtil.isEmpty( smsNumber) ) {
                 return false;
             }
 
-            final String modifiedMessage = smsMessage.replaceAll("%TOKEN%", tokenKey);
 
-            pwmApplication.getIntruderManager().mark(RecordType.TOKEN_DEST, smsNumber, null);
+            final String modifiedMessage = tokenSendInfo.getSmsMessage().replaceAll("%TOKEN%", tokenSendInfo.getTokenKey());
+
+            final PwmApplication pwmApplication = tokenSendInfo.getPwmApplication();
+            pwmApplication.getIntruderManager().mark(RecordType.TOKEN_DEST, smsNumber, tokenSendInfo.getSessionLabel());
 
-            pwmApplication.sendSmsUsingQueue(new SmsItemBean(smsNumber, modifiedMessage), macroMachine);
+            pwmApplication.sendSmsUsingQueue(smsNumber, modifiedMessage, tokenSendInfo.getSessionLabel(), tokenSendInfo.getMacroMachine());
             LOGGER.debug("token SMS added to send queue for " + smsNumber);
             return true;
         }

+ 3 - 3
server/src/main/java/password/pwm/util/RandomPasswordGenerator.java

@@ -591,11 +591,11 @@ public class RandomPasswordGenerator {
         }
 
         public int getMinimumStrength() {
-            int policyMin = this.minimumLength;
+            int policyMin = this.minimumStrength;
             if (this.getPasswordPolicy() != null) {
-                policyMin = this.getPasswordPolicy().getRuleHelper().readIntValue(PwmPasswordRule.MinimumLength);
+                policyMin = this.getPasswordPolicy().getRuleHelper().readIntValue(PwmPasswordRule.MinimumStrength);
             }
-            return Math.max(this.minimumLength, policyMin);
+            return Math.max(this.minimumStrength, policyMin);
         }
 
         void validateSettings(final PwmApplication pwmApplication)

+ 104 - 22
server/src/main/java/password/pwm/util/db/DatabaseAccessorImpl.java

@@ -32,7 +32,12 @@ import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.ReentrantLock;
 
 /**
@@ -48,6 +53,14 @@ class DatabaseAccessorImpl implements DatabaseAccessor {
 
     private final boolean traceLogEnabled;
 
+    private static final AtomicInteger ACCESSOR_COUNTER = new AtomicInteger(0);
+    private final int accessorNumber = ACCESSOR_COUNTER.getAndIncrement();
+
+    private static final AtomicInteger ITERATOR_COUNTER = new AtomicInteger(0);
+    private final Set<DBIterator> outstandingIterators = ConcurrentHashMap.newKeySet();
+
+    private final AtomicBoolean closed = new AtomicBoolean(false);
+
     private final ReentrantLock LOCK = new ReentrantLock();
 
     DatabaseAccessorImpl(
@@ -76,7 +89,6 @@ class DatabaseAccessorImpl implements DatabaseAccessor {
         throw databaseException;
     }
 
-
     @Override
     public boolean put(
             final DatabaseTable table,
@@ -85,6 +97,8 @@ class DatabaseAccessorImpl implements DatabaseAccessor {
     )
             throws DatabaseException
     {
+        preCheck();
+
         final DatabaseUtil.DebugInfo debugInfo = DatabaseUtil.DebugInfo.create("put", table, key, value);
 
         return execute(debugInfo, () -> {
@@ -119,6 +133,8 @@ class DatabaseAccessorImpl implements DatabaseAccessor {
     )
             throws DatabaseException
     {
+        preCheck();
+
         final DatabaseUtil.DebugInfo debugInfo = DatabaseUtil.DebugInfo.create("putIfAbsent", table, key, value);
 
         return execute(debugInfo, () -> {
@@ -146,6 +162,8 @@ class DatabaseAccessorImpl implements DatabaseAccessor {
     )
             throws DatabaseException
     {
+        preCheck();
+
         final DatabaseUtil.DebugInfo debugInfo = DatabaseUtil.DebugInfo.create("contains", table, key, null);
 
         return execute(debugInfo, () -> {
@@ -166,6 +184,8 @@ class DatabaseAccessorImpl implements DatabaseAccessor {
     )
             throws DatabaseException
     {
+        preCheck();
+
         final DatabaseUtil.DebugInfo debugInfo = DatabaseUtil.DebugInfo.create("get", table, key, null);
 
         return execute(debugInfo, () -> {
@@ -206,6 +226,8 @@ class DatabaseAccessorImpl implements DatabaseAccessor {
     )
             throws DatabaseException
     {
+        preCheck();
+
         final DatabaseUtil.DebugInfo debugInfo = DatabaseUtil.DebugInfo.create("remove", table, key, null);
 
         execute(debugInfo, () -> {
@@ -219,9 +241,11 @@ class DatabaseAccessorImpl implements DatabaseAccessor {
     }
 
     @Override
-    public int size(final DatabaseTable table) throws
-            DatabaseException
+    public int size(final DatabaseTable table)
+            throws DatabaseException
     {
+        preCheck();
+
         final DatabaseUtil.DebugInfo debugInfo = DatabaseUtil.DebugInfo.create("size", table, null, null);
 
         return execute(debugInfo, () -> {
@@ -242,6 +266,8 @@ class DatabaseAccessorImpl implements DatabaseAccessor {
     }
 
     boolean isValid() {
+        preCheck();
+
         if (connection == null) {
             return false;
         }
@@ -268,28 +294,36 @@ class DatabaseAccessorImpl implements DatabaseAccessor {
 
     public class DBIterator implements ClosableIterator<String> {
         private final DatabaseTable table;
-        private final ResultSet resultSet;
+        private ResultSet resultSet;
+        private PreparedStatement statement;
         private String nextValue;
         private boolean finished;
+        private int counter = ITERATOR_COUNTER.getAndIncrement();
 
         DBIterator(final DatabaseTable table)
                 throws DatabaseException
         {
             this.table = table;
-            this.resultSet = init();
+            init();
             getNextItem();
         }
 
-        private ResultSet init() throws DatabaseException {
-            final String sqlText = "SELECT " + DatabaseService.KEY_COLUMN + " FROM " + table.name();
+        private void init() throws DatabaseException {
+            final DatabaseUtil.DebugInfo debugInfo = DatabaseUtil.DebugInfo.create(
+                    "iterator #" + counter + " open", table, null, null);
+            traceBegin(debugInfo);
 
-
-            try (PreparedStatement statement = connection.prepareStatement(sqlText)) {
-                return statement.executeQuery();
+            final String sqlText = "SELECT " + DatabaseService.KEY_COLUMN + " FROM " + table.name();
+            try {
+                outstandingIterators.add(this);
+                statement = connection.prepareStatement(sqlText);
+                resultSet = statement.executeQuery();
+                connection.commit();
             } catch (SQLException e) {
                 processSqlException(null, e);
             }
-            return null; // unreachable
+
+            traceResult(debugInfo, null);
         }
 
         public boolean hasNext() {
@@ -324,14 +358,38 @@ class DatabaseAccessorImpl implements DatabaseAccessor {
         }
 
         public void close() {
-            if (resultSet != null) {
-                try {
-                    resultSet.close();
-                } catch (SQLException e) {
-                    LOGGER.error("error closing inner resultSet in iterator: " + e.getMessage());
+            final DatabaseUtil.DebugInfo debugInfo = DatabaseUtil.DebugInfo.create(
+                    "iterator #" + counter + " close", table, null, null);
+            traceBegin(debugInfo);
+
+            try {
+                LOCK.lock();
+                outstandingIterators.remove(this);
+
+                if (resultSet != null) {
+                    try {
+                        resultSet.close();
+                        resultSet = null;
+                    } catch (SQLException e) {
+                        LOGGER.error("error closing inner resultSet in iterator: " + e.getMessage());
+                    }
                 }
+
+                if (statement != null) {
+                    try {
+                        statement.close();
+                        statement = null;
+                    } catch (SQLException e) {
+                        LOGGER.error("error closing inner statement in iterator: " + e.getMessage());
+                    }
+                }
+
+                finished = true;
+            } finally {
+                LOCK.unlock();
             }
-            finished = true;
+
+            traceResult(debugInfo, "outstandingIterators=" + outstandingIterators.size());
         }
     }
 
@@ -340,7 +398,7 @@ class DatabaseAccessorImpl implements DatabaseAccessor {
             return;
         }
 
-        LOGGER.trace("begin operation: " + StringUtil.mapToString(JsonUtil.deserializeStringMap(JsonUtil.serialize(debugInfo))));
+        LOGGER.trace("accessor #" + accessorNumber + " begin operation: " + JsonUtil.serialize(debugInfo));
     }
 
     private void traceResult(
@@ -356,14 +414,15 @@ class DatabaseAccessorImpl implements DatabaseAccessor {
         if (result != null) {
             map.put("result", String.valueOf(result));
         }
-        LOGGER.trace("operation result: " + StringUtil.mapToString(map));
+        LOGGER.trace("accessor #" + accessorNumber + " operation result: " + StringUtil.mapToString(map));
     }
 
     private interface SqlFunction<T>  {
         T execute() throws DatabaseException;
     }
 
-    private <T> T execute(final DatabaseUtil.DebugInfo debugInfo, final SqlFunction<T> sqlFunction) throws DatabaseException
+    private <T> T execute(final DatabaseUtil.DebugInfo debugInfo, final SqlFunction<T> sqlFunction)
+            throws DatabaseException
     {
         traceBegin(debugInfo);
 
@@ -391,8 +450,21 @@ class DatabaseAccessorImpl implements DatabaseAccessor {
     }
 
     void close() {
+        closed.set(true);
+
         try {
             LOCK.lock();
+            try {
+                if (!outstandingIterators.isEmpty()) {
+                    LOGGER.warn("closing outstanding " + outstandingIterators.size() + " iterators");
+                }
+                for (final DBIterator iterator : new HashSet<>(outstandingIterators)) {
+                    iterator.close();
+                }
+            } catch (Exception e) {
+                LOGGER.warn("error while closing connection: " + e.getMessage());
+            }
+
             try {
                 connection.close();
             } catch (SQLException e) {
@@ -401,9 +473,12 @@ class DatabaseAccessorImpl implements DatabaseAccessor {
         } finally {
             LOCK.unlock();
         }
+
+        LOGGER.trace("closed accessor #" + accessorNumber);
     }
 
-    private boolean containsImpl(final DatabaseTable table, final String key) throws SQLException
+    private boolean containsImpl(final DatabaseTable table, final String key)
+            throws SQLException
     {
         final String sqlStatement = "SELECT COUNT(" + DatabaseService.KEY_COLUMN + ") FROM " + table.name()
                 + " WHERE " + DatabaseService.KEY_COLUMN + " = ?";
@@ -422,7 +497,8 @@ class DatabaseAccessorImpl implements DatabaseAccessor {
         return false;
     }
 
-    private void executeUpdate(final String sqlStatement, final DatabaseUtil.DebugInfo debugInfo, final String... params) throws DatabaseException
+    private void executeUpdate(final String sqlStatement, final DatabaseUtil.DebugInfo debugInfo, final String... params)
+            throws DatabaseException
     {
         try (PreparedStatement statement = connection.prepareStatement(sqlStatement) ){
             for (int i = 0; i < params.length; i++) {
@@ -433,4 +509,10 @@ class DatabaseAccessorImpl implements DatabaseAccessor {
             processSqlException(debugInfo, e);
         }
     }
+
+    private void preCheck() {
+        if (closed.get()) {
+            throw new IllegalStateException("call to perform database operation but accessor has been closed");
+        }
+    }
 }

+ 4 - 5
server/src/main/java/password/pwm/util/operations/PasswordUtility.java

@@ -43,15 +43,12 @@ import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.PasswordStatus;
 import password.pwm.bean.SessionLabel;
-import password.pwm.bean.SmsItemBean;
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.option.StrengthMeterType;
-import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.option.HelpdeskClearResponseMode;
 import password.pwm.config.option.MessageSendMethod;
+import password.pwm.config.option.StrengthMeterType;
 import password.pwm.config.profile.ForgottenPasswordProfile;
 import password.pwm.config.profile.HelpdeskProfile;
 import password.pwm.config.profile.LdapProfile;
@@ -59,6 +56,8 @@ import password.pwm.config.profile.ProfileType;
 import password.pwm.config.profile.ProfileUtility;
 import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.config.profile.PwmPasswordRule;
+import password.pwm.config.value.data.ActionConfiguration;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmDataValidationException;
 import password.pwm.error.PwmError;
@@ -160,7 +159,7 @@ public class PasswordUtility {
 
         message = message.replace("%TOKEN%", newPassword.getStringValue());
 
-        pwmApplication.sendSmsUsingQueue(new SmsItemBean(toNumber, message), macroMachine);
+        pwmApplication.sendSmsUsingQueue(toNumber, message, null, macroMachine);
         LOGGER.debug(String.format("password SMS added to send queue for %s", toNumber));
         return null;
     }

+ 121 - 99
server/src/main/java/password/pwm/util/queue/SmsQueueManager.java

@@ -22,15 +22,9 @@
 
 package password.pwm.util.queue;
 
-import org.apache.http.HttpResponse;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.methods.HttpRequestBase;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.util.EntityUtils;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
+import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SmsItemBean;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
@@ -43,22 +37,25 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthMessage;
 import password.pwm.health.HealthRecord;
 import password.pwm.http.HttpHeader;
+import password.pwm.http.HttpMethod;
 import password.pwm.http.client.PwmHttpClient;
+import password.pwm.http.client.PwmHttpClientConfiguration;
+import password.pwm.http.client.PwmHttpClientRequest;
+import password.pwm.http.client.PwmHttpClientResponse;
 import password.pwm.svc.PwmService;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.util.BasicAuthInfo;
 import password.pwm.util.PasswordData;
-import password.pwm.util.java.TimeDuration;
-import password.pwm.util.localdb.WorkQueueProcessor;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
+import password.pwm.util.java.TimeDuration;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDBStoredQueue;
+import password.pwm.util.localdb.WorkQueueProcessor;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmRandom;
 
-import java.io.IOException;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -132,7 +129,7 @@ public class SmsQueueManager implements PwmService {
 
         workQueueProcessor = new WorkQueueProcessor<>(pwmApplication, localDBStoredQueue, settings, new SmsItemProcessor(), this.getClass());
 
-        smsSendEngine = new SmsSendEngine(pwmApplication.getConfig());
+        smsSendEngine = new SmsSendEngine(pwmApplication, pwmApplication.getConfig());
 
         status = STATUS.OPEN;
     }
@@ -142,7 +139,7 @@ public class SmsQueueManager implements PwmService {
         public WorkQueueProcessor.ProcessResult process(final SmsItemBean workItem) {
             try {
                 for (final String msgPart : splitMessage(workItem.getMessage())) {
-                    smsSendEngine.sendSms(workItem.getTo(), msgPart);
+                    smsSendEngine.sendSms(workItem.getTo(), msgPart, workItem.getSessionLabel());
                 }
                 StatisticsManager.incrementStat(pwmApplication, Statistic.SMS_SEND_SUCCESSES);
                 lastError = null;
@@ -186,12 +183,16 @@ public class SmsQueueManager implements PwmService {
         }
     }
 
-    SmsItemBean shortenMessageIfNeeded(final SmsItemBean smsItem) throws PwmUnrecoverableException {
+    SmsItemBean shortenMessageIfNeeded(
+            final SmsItemBean smsItem
+    )
+            throws PwmUnrecoverableException
+    {
         final Boolean shorten = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.SMS_USE_URL_SHORTENER);
         if (shorten) {
             final String message = smsItem.getMessage();
             final String shortenedMessage = pwmApplication.getUrlShortener().shortenUrlInText(message);
-            return new SmsItemBean(smsItem.getTo(), shortenedMessage);
+            return new SmsItemBean(smsItem.getTo(), shortenedMessage, smsItem.getSessionLabel());
         }
         return smsItem;
     }
@@ -281,31 +282,34 @@ public class SmsQueueManager implements PwmService {
 
 
     protected static String smsDataEncode(final String data, final SmsDataEncoding encoding) {
-        final String returnData;
+        final String normalizedString = data == null ? "" : data;
+
         switch (encoding) {
             case NONE:
-                returnData = data;
-                break;
+                return normalizedString;
+
+            case URL:
+                return StringUtil.urlEncode(normalizedString);
+
             case CSV:
-                returnData = StringUtil.escapeCsv(data);
-                break;
+                return StringUtil.escapeCsv(normalizedString);
+
             case HTML:
-                returnData = StringUtil.escapeHtml(data);
-                break;
+                return StringUtil.escapeHtml(normalizedString);
+
             case JAVA:
-                returnData = StringUtil.escapeJava(data);
-                break;
+                return StringUtil.escapeJava(normalizedString);
+
             case JAVASCRIPT:
-                returnData = StringUtil.escapeJS(data);
-                break;
+                return StringUtil.escapeJS(normalizedString);
+
             case XML:
-                returnData = StringUtil.escapeXml(data);
-                break;
+                return StringUtil.escapeXml(normalizedString);
+
             default:
-                returnData = data == null ? "" : StringUtil.urlEncode(data);
-                break;
+                return normalizedString;
+
         }
-        return returnData;
     }
 
     private static void determineIfResultSuccessful(
@@ -409,38 +413,59 @@ public class SmsQueueManager implements PwmService {
         return returnValue;
     }
 
-
-
     private static class SmsSendEngine {
         private static final PwmLogger LOGGER = PwmLogger.forClass(SmsSendEngine.class);
+        private final PwmApplication pwmApplication;
         private final Configuration config;
         private String lastResponseBody;
 
-        private SmsSendEngine(final Configuration configuration)
+        private SmsSendEngine( final PwmApplication pwmApplication, final Configuration configuration)
         {
+            this.pwmApplication = pwmApplication;
             this.config = configuration;
         }
 
-
-        /**
-         *
-         * @param to
-         * @param message
-         * @throws PwmUnrecoverableException - If operation failed and a retry is unlikely to succeed
-         * @throws PwmOperationalException - If operation failed and should be retried.
-         */
-        protected void sendSms(final String to, final String message)
+        protected void sendSms(final String to, final String message, final SessionLabel sessionLabel)
                 throws PwmUnrecoverableException, PwmOperationalException
         {
             lastResponseBody = null;
 
-            final String gatewayUser = config.readSettingAsString(PwmSetting.SMS_GATEWAY_USER);
-            final PasswordData gatewayPass = config.readSettingAsPassword(PwmSetting.SMS_GATEWAY_PASSWORD);
+            final String requestData = makeRequestData( to, message );
 
-            final String contentType = config.readSettingAsString(PwmSetting.SMS_REQUEST_CONTENT_TYPE);
-            final SmsDataEncoding encoding = SmsDataEncoding.valueOf(config.readSettingAsString(PwmSetting.SMS_REQUEST_CONTENT_ENCODING));
+            LOGGER.trace("preparing to send SMS data: " + requestData);
 
-            final List<String> extraHeaders = config.readSettingAsStringArray(PwmSetting.SMS_GATEWAY_REQUEST_HEADERS);
+            final PwmHttpClientRequest pwmHttpClientRequest = makeRequest( requestData );
+
+            final PwmHttpClientConfiguration pwmHttpClientConfiguration = PwmHttpClientConfiguration.builder()
+                    .certificates( config.readSettingAsCertificate( PwmSetting.SMS_GATEWAY_CERTIFICATES ) )
+                    .build();
+
+            final PwmHttpClient pwmHttpClient = new PwmHttpClient( pwmApplication, sessionLabel, pwmHttpClientConfiguration );
+
+            try {
+                final PwmHttpClientResponse pwmHttpClientResponse = pwmHttpClient.makeRequest( pwmHttpClientRequest );
+                final int resultCode = pwmHttpClientResponse.getStatusCode();
+
+                final String responseBody = pwmHttpClientResponse.getBody();
+                lastResponseBody = responseBody;
+
+                determineIfResultSuccessful(config, resultCode, responseBody);
+                LOGGER.debug("SMS send successful, HTTP status: " + resultCode);
+            } catch (PwmUnrecoverableException e) {
+                final ErrorInformation errorInformation = new ErrorInformation(
+                        PwmError.ERROR_SMS_SEND_ERROR,
+                        "error while sending SMS, discarding message: " + e.getMessage());
+                throw new PwmUnrecoverableException(errorInformation);
+            }
+        }
+
+        private String makeRequestData(
+                final String to,
+                final String message
+        ) {
+            final String gatewayUser = config.readSettingAsString(PwmSetting.SMS_GATEWAY_USER);
+            final PasswordData gatewayPass = config.readSettingAsPassword(PwmSetting.SMS_GATEWAY_PASSWORD);
+            final SmsDataEncoding encoding = config.readSettingAsEnum(PwmSetting.SMS_REQUEST_CONTENT_ENCODING, SmsDataEncoding.class);
 
             String requestData = config.readSettingAsString(PwmSetting.SMS_REQUEST_DATA);
 
@@ -467,69 +492,64 @@ public class SmsQueueManager implements PwmService {
                 requestData = requestData.replaceAll(TOKEN_REQUESTID, smsDataEncode(requestId, encoding));
             }
 
+            return requestData;
+        }
+
+        private PwmHttpClientRequest makeRequest(
+                final String requestData
+        )
+                throws PwmUnrecoverableException
+        {
+            final String gatewayUser = config.readSettingAsString(PwmSetting.SMS_GATEWAY_USER);
+            final PasswordData gatewayPass = config.readSettingAsPassword(PwmSetting.SMS_GATEWAY_PASSWORD);
+            final String contentType = config.readSettingAsString(PwmSetting.SMS_REQUEST_CONTENT_TYPE);
+            final List<String> extraHeaders = config.readSettingAsStringArray(PwmSetting.SMS_GATEWAY_REQUEST_HEADERS);
             final String gatewayUrl = config.readSettingAsString(PwmSetting.SMS_GATEWAY_URL);
             final String gatewayMethod = config.readSettingAsString(PwmSetting.SMS_GATEWAY_METHOD);
             final String gatewayAuthMethod = config.readSettingAsString(PwmSetting.SMS_GATEWAY_AUTHMETHOD);
 
-            LOGGER.trace("preparing to send SMS data: " + requestData);
-            try {
-                final HttpRequestBase httpRequest;
-                if ("POST".equalsIgnoreCase(gatewayMethod)) {
-                    // POST request
-                    httpRequest = new HttpPost(gatewayUrl);
-                    if (contentType != null && contentType.length()>0) {
-                        httpRequest.setHeader("Content-Type", contentType);
-                    }
-                    ((HttpPost) httpRequest).setEntity(new StringEntity(requestData));
-                } else {
-                    // GET request
-                    final String fullUrl = gatewayUrl.endsWith("?") ? gatewayUrl + requestData : gatewayUrl + "?" + requestData;
-                    httpRequest = new HttpGet(fullUrl);
+            final HttpMethod httpMethod = "POST".equalsIgnoreCase(gatewayMethod)
+                    ? HttpMethod.POST
+                    : HttpMethod.GET;
+
+            final Map<String,String> headers = new LinkedHashMap<>(  );
+            {
+                if ( "HTTP".equalsIgnoreCase( gatewayAuthMethod ) && gatewayUser != null && gatewayPass != null ) {
+                    final BasicAuthInfo basicAuthInfo = new BasicAuthInfo( gatewayUser, gatewayPass );
+                    headers.put( HttpHeader.Authorization.getHttpName(), basicAuthInfo.toAuthHeader() );
                 }
 
-                if (extraHeaders != null) {
-                    final Pattern pattern = Pattern.compile("^([A-Za-z0-9_\\.-]+):[ \t]*([^ \t].*)");
-                    for (final String header : extraHeaders) {
-                        final Matcher matcher = pattern.matcher(header);
-                        if (matcher.matches()) {
-                            final String hname = matcher.group(1);
-                            final String hvalue = matcher.group(2);
-                            LOGGER.debug("Adding HTTP header \"" + hname + "\" with value \"" + hvalue + "\"");
-                            httpRequest.addHeader(hname, hvalue);
+
+                if ( !StringUtil.isEmpty( contentType ) && httpMethod == HttpMethod.POST ) {
+                    headers.put( HttpHeader.Content_Type.getHttpName(), contentType );
+                }
+
+                if ( extraHeaders != null ) {
+                    final Pattern pattern = Pattern.compile( "^([A-Za-z0-9_\\.-]+):[ \t]*([^ \t].*)" );
+                    for ( final String header : extraHeaders )
+                    {
+                        final Matcher matcher = pattern.matcher( header );
+                        if ( matcher.matches() ) {
+                            final String headerName = matcher.group( 1 );
+                            final String headerValue = matcher.group( 2 );
+                            headers.put( headerName, headerValue );
                         } else {
-                            LOGGER.warn("Cannot parse HTTP header: " + header);
+                            LOGGER.warn( "Cannot parse HTTP header: " + header );
                         }
                     }
                 }
 
-                if ("HTTP".equalsIgnoreCase(gatewayAuthMethod) && gatewayUser != null && gatewayPass != null) {
-                    LOGGER.debug("Using Basic Authentication");
-                    final BasicAuthInfo ba = new BasicAuthInfo(gatewayUser, gatewayPass);
-                    httpRequest.addHeader(HttpHeader.Authorization.getHttpName(), ba.toAuthHeader());
-                }
+            }
 
-                final HttpClient httpClient = PwmHttpClient.getHttpClient(config);
-                final HttpResponse httpResponse = httpClient.execute(httpRequest);
-                final String responseBody = EntityUtils.toString(httpResponse.getEntity());
-                final int resultCode = httpResponse.getStatusLine().getStatusCode();
-                lastResponseBody = httpResponse.getStatusLine() + "\n" + responseBody;
-                LOGGER.trace("sms send result body: " + httpResponse.getStatusLine().toString() + "\n" + responseBody);
+            final String fullUrl = httpMethod == HttpMethod.POST
+                    ? gatewayUrl
+                    : gatewayUrl.endsWith("?") ? gatewayUrl + requestData : gatewayUrl + "?" + requestData;
 
-                determineIfResultSuccessful(config, resultCode, responseBody);
-                LOGGER.debug("SMS send successful, HTTP status: " + httpResponse.getStatusLine().getStatusCode());
-            } catch (IOException e) {
-                final ErrorInformation errorInformation = new ErrorInformation(
-                        PwmError.ERROR_SMS_SEND_ERROR,
-                        "IO error while sending SMS: " + e.getMessage());
-                throw new PwmOperationalException(errorInformation);
-            } catch (PwmOperationalException e) {
-                throw e;
-            } catch (Exception e) {
-                final ErrorInformation errorInformation = new ErrorInformation(
-                        PwmError.ERROR_SMS_SEND_ERROR,
-                        "unexpected error while sending SMS, discarding message: " + e.getMessage());
-                throw new PwmUnrecoverableException(errorInformation);
-            }
+            final String body = httpMethod == HttpMethod.POST
+                    ? requestData
+                    : null;
+
+            return new PwmHttpClientRequest( httpMethod, fullUrl, body, headers );
         }
 
         public String getLastResponseBody()
@@ -539,14 +559,16 @@ public class SmsQueueManager implements PwmService {
     }
 
     public static String sendDirectMessage(
+            final PwmApplication pwmApplication,
             final Configuration configuration,
+            final SessionLabel sessionLabel,
             final SmsItemBean smsItemBean
 
     )
             throws PwmUnrecoverableException, PwmOperationalException
     {
-        final SmsSendEngine smsSendEngine = new SmsSendEngine(configuration);
-        smsSendEngine.sendSms(smsItemBean.getTo(), smsItemBean.getMessage());
+        final SmsSendEngine smsSendEngine = new SmsSendEngine(pwmApplication, configuration);
+        smsSendEngine.sendSms(smsItemBean.getTo(), smsItemBean.getMessage(), sessionLabel);
         return smsSendEngine.getLastResponseBody();
     }
 

+ 37 - 25
server/src/main/java/password/pwm/ws/server/rest/RestRandomPasswordServer.java

@@ -22,7 +22,6 @@
 
 package password.pwm.ws.server.rest;
 
-import com.novell.ldapchai.exception.ChaiUnavailableException;
 import lombok.Data;
 import password.pwm.PwmConstants;
 import password.pwm.config.option.WebServiceUsage;
@@ -40,9 +39,9 @@ import password.pwm.util.PasswordData;
 import password.pwm.util.RandomPasswordGenerator;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.operations.PasswordUtility;
-import password.pwm.ws.server.RestResultBean;
 import password.pwm.ws.server.RestMethodHandler;
 import password.pwm.ws.server.RestRequest;
+import password.pwm.ws.server.RestResultBean;
 import password.pwm.ws.server.RestServlet;
 import password.pwm.ws.server.RestWebServer;
 
@@ -76,6 +75,7 @@ public class RestRandomPasswordServer extends RestServlet {
         private int maxLength;
         private String chars;
         private boolean noUser;
+
     }
 
     @Override
@@ -156,42 +156,24 @@ public class RestRandomPasswordServer extends RestServlet {
             final RestRequest restRequest,
             final JsonInput jsonInput
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException
+            throws PwmUnrecoverableException
     {
-        final RandomPasswordGenerator.RandomGeneratorConfig.RandomGeneratorConfigBuilder randomConfigBuilder
-                = RandomPasswordGenerator.RandomGeneratorConfig.builder();
-        if (jsonInput.strength > 0 && jsonInput.strength <= 100) {
-            randomConfigBuilder.minimumStrength(jsonInput.strength);
-        }
-        if (jsonInput.minLength > 0 && jsonInput.minLength <= 100 * 1024) {
-            randomConfigBuilder.minimumLength(jsonInput.minLength);
-        }
-        if (jsonInput.maxLength > 0 && jsonInput.maxLength <= 100 * 1024) {
-            randomConfigBuilder.maximumLength(jsonInput.maxLength);
-        }
-        if (jsonInput.chars != null) {
-            final List<String> charValues = new ArrayList<>();
-            for (int i = 0; i < jsonInput.chars.length(); i++) {
-                charValues.add(String.valueOf(jsonInput.chars.charAt(i)));
-            }
-            randomConfigBuilder.seedlistPhrases(charValues);
-        }
+        final PwmPasswordPolicy pwmPasswordPolicy;
 
         if (jsonInput.isNoUser()) {
-            randomConfigBuilder.passwordPolicy(PwmPasswordPolicy.defaultPolicy());
+            pwmPasswordPolicy = PwmPasswordPolicy.defaultPolicy();
         } else {
             final TargetUserIdentity targetUserIdentity = resolveRequestedUsername(restRequest, jsonInput.getUsername());
-            final PwmPasswordPolicy pwmPasswordPolicy = PasswordUtility.readPasswordPolicyForUser(
+            pwmPasswordPolicy = PasswordUtility.readPasswordPolicyForUser(
                     restRequest.getPwmApplication(),
                     restRequest.getSessionLabel(),
                     targetUserIdentity.getUserIdentity(),
                     targetUserIdentity.getChaiUser(),
                     restRequest.getLocale()
             );
-            randomConfigBuilder.passwordPolicy(pwmPasswordPolicy);
         }
 
-        final RandomPasswordGenerator.RandomGeneratorConfig randomConfig = randomConfigBuilder.build();
+        final RandomPasswordGenerator.RandomGeneratorConfig randomConfig = jsonInputToRandomConfig( jsonInput, pwmPasswordPolicy );
         final PasswordData randomPassword = RandomPasswordGenerator.createRandomPassword(restRequest.getSessionLabel(), randomConfig, restRequest.getPwmApplication());
         final JsonOutput outputMap = new JsonOutput();
         outputMap.password = randomPassword.getStringValue();
@@ -200,5 +182,35 @@ public class RestRandomPasswordServer extends RestServlet {
 
         return outputMap;
     }
+
+    public static RandomPasswordGenerator.RandomGeneratorConfig jsonInputToRandomConfig(
+            final JsonInput jsonInput,
+            final PwmPasswordPolicy pwmPasswordPolicy
+    )
+    {
+        final RandomPasswordGenerator.RandomGeneratorConfig.RandomGeneratorConfigBuilder randomConfigBuilder
+                = RandomPasswordGenerator.RandomGeneratorConfig.builder();
+
+        if (jsonInput.getStrength() > 0 && jsonInput.getStrength() <= 100) {
+            randomConfigBuilder.minimumStrength(jsonInput.getStrength());
+        }
+        if (jsonInput.getMinLength() > 0 && jsonInput.getMinLength() <= 100 * 1024) {
+            randomConfigBuilder.minimumLength(jsonInput.getMinLength());
+        }
+        if (jsonInput.getMaxLength() > 0 && jsonInput.getMaxLength() <= 100 * 1024) {
+            randomConfigBuilder.maximumLength(jsonInput.getMaxLength());
+        }
+        if (jsonInput.getChars() != null) {
+            final List<String> charValues = new ArrayList<>();
+            for (int i = 0; i < jsonInput.getChars().length(); i++) {
+                charValues.add(String.valueOf(jsonInput.getChars().charAt(i)));
+            }
+            randomConfigBuilder.seedlistPhrases(charValues);
+        }
+
+        randomConfigBuilder.passwordPolicy( pwmPasswordPolicy );
+
+        return randomConfigBuilder.build();
+    }
 }
 

+ 9 - 3
server/src/main/resources/password/pwm/config/PwmSetting.xml

@@ -867,6 +867,12 @@
             <value />
         </default>
     </setting>
+    <setting hidden="false" key="sms.gatewayCertificates" level="1">
+        <default/>
+        <properties>
+            <property key="Cert_ImportHandler">password.pwm.config.function.SMSGatewayCertImportFunction</property>
+        </properties>
+    </setting>
     <setting hidden="false" key="sms.gatewayPassword" level="1">
     </setting>
     <setting hidden="false" key="sms.gatewayMethod" level="1" required="true">
@@ -1004,7 +1010,7 @@
             <value><![CDATA[Thank you for activating your account.]]></value>
         </default>
     </setting>
-    <setting hidden="false" key="sms.useUrlShortener" level="2">
+    <setting hidden="true" key="sms.useUrlShortener" level="2">
         <default>
             <value>false</value>
         </default>
@@ -3645,12 +3651,12 @@
             <value />
         </default>
     </setting>
-    <setting hidden="false" key="urlshortener.classname" level="2">
+    <setting hidden="true" key="urlshortener.classname" level="2">
         <default>
             <value />
         </default>
     </setting>
-    <setting hidden="false" key="urlshortener.parameters" level="2">
+    <setting hidden="true" key="urlshortener.parameters" level="2">
         <regex>^[a-zA-Z0-9.]+=.+$</regex>
         <default>
             <value />

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

@@ -332,7 +332,7 @@ EventLog_Narrative_HelpdeskAction=%targetID% (%targetDN%) had an action invoked
 EventLog_Narrative_HelpdeskClearResponses=Stored responses for %targetID% (%targetDN%) cleared by help desk operator %perpetratorID% (%perpetratorDN%)
 EventLog_Narrative_HelpdeskClearOtpSecret=OTP secret for %targetID% (%targetDN%) cleared by help desk operator %perpetratorID% (%perpetratorDN%)
 EventLog_Narrative_HelpdeskSetPassword=Password for %targetID% (%targetDN%) set by help desk operator %perpetratorID% (%perpetratorDN%)
-EventLog_Narrative_HelpdeskUnlockPassword=password has be unlocked for %targetID% (%targetDN%) by help desk operator %perpetratorID% (%perpetratorDN%)
+EventLog_Narrative_HelpdeskUnlockPassword=Password has been unlocked for %targetID% (%targetDN%) by help desk operator %perpetratorID% (%perpetratorDN%)
 EventLog_Narrative_HelpdeskDeleteUser=%targetID% (%targetDN%) has been deleted by help desk operator %perpetratorID% (%perpetratorDN%)
 EventLog_Narrative_HelpdeskViewDetail=Details of %targetID% (%targetDN%) have been viewed by help desk operator %perpetratorID% (%perpetratorDN%)
 EventLog_Narrative_HelpdeskVerifyOtp=OTP secret for %targetID% (%targetDN%) verified by help desk operator %perpetratorID% (%perpetratorDN%)

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

@@ -642,6 +642,7 @@ Setting_Description_sms.challenge.token.message=Specify the message text of the
 Setting_Description_sms.defaultCountryCode=Specify the default country code for the SMS phone number. For a list of country codes, see <a href\="http\://countrycode.org/" target\="_blank">http\://countrycode.org/</a>. Set to 0 to disable this option.
 Setting_Description_sms.forgottenUsername.message=Specify the text of the SMS @PwmAppName@ sends upon a successful forgotten user name sequence, if you configured it.
 Setting_Description_sms.gatewayAuthMethod=Select the method @PwmAppName@ uses for authentication to the SMS gateway.
+Setting_Description_sms.gatewayCertificates=Certificate for remote SMS service
 Setting_Description_sms.gatewayMethod=Select the HTTPS protocol method @PwmAppName@ uses for sending the SMS messages.
 Setting_Description_sms.gatewayPassword=Specify the user password for the SMS gateway.
 Setting_Description_sms.gatewayURL=Specify the URL for the SMS gateway.
@@ -1125,6 +1126,7 @@ Setting_Label_sms.challenge.token.message=Forgotten Password SMS Text
 Setting_Label_sms.defaultCountryCode=Default SMS Country Code
 Setting_Label_sms.forgottenUsername.message=Forgotten User Name SMS Text
 Setting_Label_sms.gatewayAuthMethod=SMS Gateway Authentication Method
+Setting_Label_sms.gatewayCertificates=SMS Gateway Certificates
 Setting_Label_sms.gatewayMethod=HTTP(S) Method
 Setting_Label_sms.gatewayPassword=SMS Gateway Password
 Setting_Label_sms.gatewayURL=SMS Gateway

+ 48 - 23
server/src/main/webapp/public/resources/js/changepassword.js

@@ -33,27 +33,25 @@ var PWM_CHANGEPW = PWM_CHANGEPW || {};
 
 // takes password values in the password fields, sends an http request to the servlet
 // and then parses (and displays) the response from the servlet.
-PWM_CHANGEPW.validatePasswords = function(userDN)
+PWM_CHANGEPW.validatePasswords = function(userDN, nextFunction)
 {
-    PWM_MAIN.getObject("password_button").disabled = true;
-    if (PWM_MAIN.getObject("password1").value.length <= 0 && PWM_MAIN.getObject("password2").value.length <= 0) {
-        PWM_CHANGEPW.updateDisplay(null);
-        return;
-    }
-
     if (PWM_GLOBAL['previousP1'] !== PWM_MAIN.getObject("password1").value) {  // if p1 is changing, then clear out p2.
         PWM_MAIN.getObject("password2").value = "";
         PWM_GLOBAL['previousP1'] = PWM_MAIN.getObject("password1").value;
     }
 
-    var validationProps = new Array();
+    var validationProps = {};
+
+    validationProps['completeFunction'] = nextFunction ? nextFunction : function () {};
     validationProps['messageWorking'] = PWM_MAIN.showString('Display_CheckingPassword');
     validationProps['serviceURL'] = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction','checkPassword');
     validationProps['readDataFunction'] = function(){
         var returnObj = {};
         returnObj['password1'] = PWM_MAIN.getObject("password1").value;
         returnObj['password2'] = PWM_MAIN.getObject("password2").value;
-        if (userDN) returnObj['username'] = userDN;
+        if (userDN) {
+            returnObj['username'] = userDN;
+        }
         return returnObj;
     };
     validationProps['processResultsFunction'] = function(data){
@@ -70,10 +68,6 @@ PWM_CHANGEPW.validatePasswords = function(userDN)
 
 PWM_CHANGEPW.updateDisplay = function(resultInfo) {
     if (!resultInfo) {
-        var passwordButton = PWM_MAIN.getObject("password_button");
-        if (passwordButton !== null) {
-            passwordButton.disabled = false;
-        }
         PWM_MAIN.showSuccess(PWM_MAIN.showString('Display_PasswordPrompt'));
         PWM_CHANGEPW.markStrength(0);
         PWM_CHANGEPW.markConfirmationCheck(null);
@@ -88,17 +82,13 @@ PWM_CHANGEPW.updateDisplay = function(resultInfo) {
     }
 
     if (resultInfo["passed"] === true) {
-        //PWM_MAIN.getObject('password2').disabled = false;
         if (resultInfo["match"] === "MATCH") {
-            PWM_MAIN.getObject("password_button").disabled = false;
             PWM_MAIN.showSuccess(message);
+            PWM_MAIN.getObject("password_button").disabled = false;
         } else {
-            PWM_MAIN.getObject("password_button").disabled = true;
             PWM_MAIN.showInfo(message);
         }
     } else {
-        //PWM_MAIN.getObject('password2').disabled = true;
-        PWM_MAIN.getObject("password_button").disabled = true;
         PWM_MAIN.showError(message);
     }
 
@@ -203,9 +193,44 @@ PWM_CHANGEPW.showPasswordGuide=function() {
     });
 };
 
-PWM_CHANGEPW.handleChangePasswordSubmit=function() {
-    PWM_MAIN.showInfo(PWM_MAIN.showString('Display_PleaseWait'));
-    PWM_VAR['dirtyPageLeaveFlag'] = false;
+PWM_CHANGEPW.handleChangePasswordSubmit=function(event) {
+    console.log('intercepted change password submit');
+    PWM_MAIN.cancelEvent(event);
+
+    var nextFunction = function(data) {
+        console.log('post change password submit handler');
+        if (!data || data['data']['passed'] && 'MATCH' === data['data']['match']) {
+            console.log('submitting password form');
+            PWM_VAR['dirtyPageLeaveFlag'] = false;
+            PWM_MAIN.getObject("changePasswordForm").submit();
+        } else {
+            PWM_MAIN.closeWaitDialog();
+            var match = data['data']['match'];
+            if ('MATCH' !== match) {
+                PWM_MAIN.getObject("password2").value = '';
+            }
+            var okFunction = function() {
+                if ('MATCH' === match || 'EMPTY' === match) {
+                    PWM_MAIN.getObject("password1").focus();
+                } else {
+                    PWM_MAIN.getObject("password2").focus();
+                }
+                PWM_CHANGEPW.validatePasswords();
+            };
+            var title = PWM_MAIN.showString('Title_ChangePassword');
+            var message = '<div style="height:20px">' + data['data']['message'] + '.</div>';
+            PWM_MAIN.showDialog({text:message,title:title,okAction:okFunction});
+        }
+    };
+
+    PWM_MAIN.showWaitDialog({
+        loadFunction:function(){
+            PWM_MAIN.showInfo('\xa0');
+            setTimeout(function(){
+                PWM_CHANGEPW.validatePasswords(null, nextFunction);
+            },500);
+        }}
+    );
 };
 
 PWM_CHANGEPW.doRandomGeneration=function(randomConfig) {
@@ -339,8 +364,8 @@ PWM_CHANGEPW.startupChangePasswordPage=function() {
         PWM_CHANGEPW.validatePasswords(null);
     });
     PWM_MAIN.addEventHandler(changePasswordForm,"submit",function(event){
-        PWM_CHANGEPW.handleChangePasswordSubmit();
-        PWM_MAIN.handleFormSubmit(changePasswordForm, event);
+        PWM_CHANGEPW.handleChangePasswordSubmit(event);
+        //PWM_MAIN.handleFormSubmit(changePasswordForm, event);
         return false;
     });
     PWM_MAIN.addEventHandler(changePasswordForm,"reset",function(){

+ 1 - 1
server/src/main/webapp/public/resources/js/configeditor-settings.js

@@ -1275,7 +1275,7 @@ ChangePasswordHandler.generateRandom = function(settingKey) {
     postData.noUser = true;
     PWM_MAIN.getObject('button-storePassword').disabled = true;
 
-    var url = PWM_GLOBAL['url-restservice'] + "/randompassword";
+    var url = PWM_MAIN.addParamToUrl(window.location.href,'processAction','randomPassword');
     var loadFunction = function(data) {
         ChangePasswordHandler.changePasswordPopup(settingKey);
         PWM_MAIN.getObject('password1').value = data['data']['password'];

+ 27 - 26
server/src/main/webapp/public/resources/js/main.js

@@ -1168,7 +1168,7 @@ PWM_MAIN.messageDivFloatHandler = function() {
 };
 
 PWM_MAIN.pwmFormValidator = function(validationProps, reentrant) {
-    var CONSOLE_DEBUG = false;
+    var CONSOLE_DEBUG = true;
 
     var serviceURL = validationProps['serviceURL'];
     var readDataFunction = validationProps['readDataFunction'];
@@ -1181,6 +1181,7 @@ PWM_MAIN.pwmFormValidator = function(validationProps, reentrant) {
 
 
     if (CONSOLE_DEBUG) console.log("pwmFormValidator: beginning...");
+
     //init vars;
     if (!PWM_VAR['validationCache']) {
         PWM_VAR['validationCache'] = {};
@@ -1196,7 +1197,7 @@ PWM_MAIN.pwmFormValidator = function(validationProps, reentrant) {
         if (cachedResult) {
             processResultsFunction(cachedResult);
             if (CONSOLE_DEBUG) console.log('pwmFormValidator: processed cached data, exiting');
-            completeFunction();
+            completeFunction(cachedResult);
             return;
         }
     }
@@ -1218,6 +1219,7 @@ PWM_MAIN.pwmFormValidator = function(validationProps, reentrant) {
 
     //check to see if a validation is already in progress, if it is then ignore keypress.
     if (PWM_VAR['validationInProgress'] === true) {
+        setTimeout(function(){PWM_MAIN.pwmFormValidator(validationProps, true)}, typeWaitTimeMs + 1);
         if (CONSOLE_DEBUG) console.log('pwmFormValidator: waiting for a previous validation to complete, exiting...');
         return;
     }
@@ -1232,30 +1234,29 @@ PWM_MAIN.pwmFormValidator = function(validationProps, reentrant) {
         }, 5);
     }
 
-    require(["dojo"],function(dojo){
-        var formDataString = dojo.toJson(formData);
-        if (CONSOLE_DEBUG) console.log('FormValidator: sending form data to server... ' + formDataString);
-        var loadFunction = function(data) {
-            PWM_VAR['validationInProgress'] = false;
-            delete PWM_VAR['validationLastType'];
-            PWM_VAR['validationCache'][formKey] = data;
-            if (CONSOLE_DEBUG) console.log('pwmFormValidator: successful read, data added to cache');
-            PWM_MAIN.pwmFormValidator(validationProps, true);
-        };
-        var options = {};
-        options['content'] = formData;
-        options['ajaxTimeout'] = ajaxTimeout;
-        options['errorFunction'] = function(error) {
-            PWM_VAR['validationInProgress'] = false;
-            if (showMessage) {
-                PWM_MAIN.showInfo(PWM_MAIN.showString('Display_CommunicationError'));
-            }
-            if (CONSOLE_DEBUG) console.log('pwmFormValidator: error connecting to service: ' + errorObj);
-            processResultsFunction(null);
-            completeFunction();
-        };
-        PWM_MAIN.ajaxRequest(serviceURL,loadFunction,options);
-    });
+    var formDataString = JSON.stringify(formData) ;
+
+    if (CONSOLE_DEBUG) console.log('FormValidator: sending form data to server... ' + formDataString);
+    var loadFunction = function(data) {
+        PWM_VAR['validationInProgress'] = false;
+        delete PWM_VAR['validationLastType'];
+        PWM_VAR['validationCache'][formKey] = data;
+        if (CONSOLE_DEBUG) console.log('pwmFormValidator: successful read, data added to cache');
+        PWM_MAIN.pwmFormValidator(validationProps, true);
+    };
+    var options = {};
+    options['content'] = formData;
+    options['ajaxTimeout'] = ajaxTimeout;
+    options['errorFunction'] = function(error) {
+        PWM_VAR['validationInProgress'] = false;
+        if (showMessage) {
+            PWM_MAIN.showInfo(PWM_MAIN.showString('Display_CommunicationError'));
+        }
+        if (CONSOLE_DEBUG) console.log('pwmFormValidator: error connecting to service: ' + error);
+        processResultsFunction(null);
+        completeFunction(null);
+    };
+    PWM_MAIN.ajaxRequest(serviceURL,loadFunction,options);
 };
 
 PWM_MAIN.preloadImages = function(imgArray){