Bladeren bron

rest service refactoring

Jason Rivard 7 jaren geleden
bovenliggende
commit
705c6e1703
39 gewijzigde bestanden met toevoegingen van 635 en 422 verwijderingen
  1. 5 0
      server/pom.xml
  2. 3 3
      server/src/main/java/password/pwm/PwmApplication.java
  3. 1 1
      server/src/main/java/password/pwm/PwmConstants.java
  4. 2 0
      server/src/main/java/password/pwm/error/PwmError.java
  5. 4 0
      server/src/main/java/password/pwm/error/PwmException.java
  6. 4 0
      server/src/main/java/password/pwm/error/PwmUnrecoverableException.java
  7. 2 2
      server/src/main/java/password/pwm/http/ContextManager.java
  8. 1 1
      server/src/main/java/password/pwm/http/HttpEventManager.java
  9. 1 0
      server/src/main/java/password/pwm/http/HttpHeader.java
  10. 7 1
      server/src/main/java/password/pwm/http/PwmHttpRequestWrapper.java
  11. 12 2
      server/src/main/java/password/pwm/http/PwmURL.java
  12. 6 8
      server/src/main/java/password/pwm/http/filter/ApplicationModeFilter.java
  13. 1 1
      server/src/main/java/password/pwm/http/filter/AuthenticationFilter.java
  14. 1 1
      server/src/main/java/password/pwm/http/filter/AuthorizationFilter.java
  15. 2 2
      server/src/main/java/password/pwm/http/filter/GZIPFilter.java
  16. 1 1
      server/src/main/java/password/pwm/http/filter/ObsoleteUrlFilter.java
  17. 19 12
      server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java
  18. 2 2
      server/src/main/java/password/pwm/http/filter/SessionFilter.java
  19. 18 11
      server/src/main/java/password/pwm/http/servlet/ControlledPwmServlet.java
  20. 2 2
      server/src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java
  21. 1 1
      server/src/main/java/password/pwm/http/servlet/resource/ResourceServletConfiguration.java
  22. 1 1
      server/src/main/java/password/pwm/svc/cluster/DatabaseStoredNodeData.java
  23. 64 0
      server/src/main/java/password/pwm/ws/server/PwmRestServlet.java
  24. 9 2
      server/src/main/java/password/pwm/ws/server/StandaloneRestHelper.java
  25. 2 0
      server/src/main/java/password/pwm/ws/server/StandaloneRestRequestBean.java
  26. 2 18
      server/src/main/java/password/pwm/ws/server/rest/RestAppDataServer.java
  27. 27 29
      server/src/main/java/password/pwm/ws/server/rest/RestFormSigningServer.java
  28. 8 1
      server/src/main/java/password/pwm/ws/server/rest/RestHealthServer.java
  29. 47 0
      server/src/main/java/password/pwm/ws/server/rest/RestPingServer.java
  30. 0 1
      server/src/main/resources/password/pwm/PwmConstants.properties
  31. 1 1
      server/src/main/resources/password/pwm/i18n/PwmSetting.properties
  32. 3 3
      server/src/main/webapp/WEB-INF/jsp/admin-logview.jsp
  33. 6 0
      server/src/main/webapp/WEB-INF/jsp/admin-urlreference.jsp
  34. 1 0
      server/src/main/webapp/WEB-INF/jsp/configeditor.jsp
  35. 19 0
      server/src/main/webapp/WEB-INF/jsp/fragment/debug.jsp
  36. 4 1
      server/src/main/webapp/WEB-INF/jsp/fragment/header-body.jsp
  37. 339 0
      server/src/main/webapp/public/resources/js/configeditor-settings-challenges.js
  38. 0 307
      server/src/main/webapp/public/resources/js/configeditor-settings.js
  39. 7 7
      server/src/main/webapp/public/resources/js/configmanager.js

+ 5 - 0
server/pom.xml

@@ -617,6 +617,11 @@
             <artifactId>commons-lang3</artifactId>
             <version>3.6</version>
         </dependency>
+        <dependency>
+            <groupId>commons-validator</groupId>
+            <artifactId>commons-validator</artifactId>
+            <version>1.6</version>
+        </dependency>
         <dependency>
             <groupId>com.sun.mail</groupId>
             <artifactId>javax.mail</artifactId>

+ 3 - 3
server/src/main/java/password/pwm/PwmApplication.java

@@ -146,7 +146,7 @@ public class PwmApplication {
 
 
     private String instanceID = DEFAULT_INSTANCE_ID;
-    private String instanceNonce = PwmRandom.getInstance().randomUUID().toString();
+    private String runtimeNonce = PwmRandom.getInstance().randomUUID().toString();
 
     private LocalDB localDB;
     private LocalDBLogger localDBLogger;
@@ -730,8 +730,8 @@ public class PwmApplication {
         return pwmEnvironment;
     }
 
-    public String getInstanceNonce() {
-        return instanceNonce;
+    public String getRuntimeNonce() {
+        return runtimeNonce;
     }
 
     public <T extends Serializable> T readAppAttribute(final AppAttribute appAttribute, final Class<T> returnClass) {

+ 1 - 1
server/src/main/java/password/pwm/PwmConstants.java

@@ -111,7 +111,6 @@ public abstract class PwmConstants {
     public static final float JAVA_MINIMUM_VERSION = (float)1.6;
 
     public static final String HTTP_BASIC_AUTH_PREFIX = readPwmConstantsBundle("httpHeaderAuthorizationBasic");
-    public static final String HTTP_HEADER_X_FORWARDED_FOR = readPwmConstantsBundle("httpHeaderXForwardedFor");
     public static final String HTTP_HEADER_REST_CLIENT_KEY = readPwmConstantsBundle("httpRestClientKey");
 
     public static final String DEFAULT_BAD_PASSWORD_ATTEMPT = readPwmConstantsBundle("defaultBadPasswordAttempt");
@@ -146,6 +145,7 @@ public abstract class PwmConstants {
 
     public static final String URL_PREFIX_PRIVATE = "/private";
     public static final String URL_PREFIX_PUBLIC = "/public";
+    public static final String URL_PREFIX_REST = "/rest";
 
 
     public static final String PARAM_ACTION_REQUEST = "processAction";

+ 2 - 0
server/src/main/java/password/pwm/error/PwmError.java

@@ -185,6 +185,8 @@ public enum PwmError {
 
     ERROR_HTTP_404(                 5300, "Error_HTTP_404",                 null),
 
+    ERROR_REST_INVOCATION_ERROR(    7000, "Error_RestInvocationError",      null),
+
     ;
 
     enum ErrorFlag {

+ 4 - 0
server/src/main/java/password/pwm/error/PwmException.java

@@ -38,6 +38,10 @@ public abstract class PwmException extends Exception {
         this.errorInformation = new ErrorInformation(error);
     }
 
+    public PwmException(final PwmError error, final String detailedErrorMsg) {
+        this.errorInformation = new ErrorInformation(error, detailedErrorMsg);
+    }
+
     // --------------------- GETTER / SETTER METHODS ---------------------
 
     public ErrorInformation getErrorInformation() {

+ 4 - 0
server/src/main/java/password/pwm/error/PwmUnrecoverableException.java

@@ -43,6 +43,10 @@ public class PwmUnrecoverableException extends PwmException {
         super(error);
     }
 
+    public PwmUnrecoverableException(final PwmError error, final String detailedErrorMsg) {
+        super(error, detailedErrorMsg);
+    }
+
     public static PwmUnrecoverableException fromChaiException(final ChaiException e) {
         final ErrorInformation errorInformation;
         if (e instanceof ChaiUnavailableException) {

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

@@ -81,7 +81,7 @@ public class ContextManager implements Serializable {
     // -------------------------- STATIC METHODS --------------------------
 
     public static PwmApplication getPwmApplication(final HttpServletRequest request) throws PwmUnrecoverableException {
-        return getPwmApplication(request.getSession());
+        return getPwmApplication(request.getServletContext());
     }
 
     public static PwmApplication getPwmApplication(final HttpSession session) throws PwmUnrecoverableException {
@@ -97,7 +97,7 @@ public class ContextManager implements Serializable {
     }
 
     public static ContextManager getContextManager(final PwmRequest pwmRequest) throws PwmUnrecoverableException {
-        return getContextManager(pwmRequest.getHttpServletRequest().getSession());
+        return getContextManager(pwmRequest.getHttpServletRequest().getServletContext());
     }
 
     public static ContextManager getContextManager(final ServletContext theContext) throws PwmUnrecoverableException {

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

@@ -58,7 +58,7 @@ public class HttpEventManager implements
         try {
             final ContextManager contextManager = ContextManager.getContextManager(httpSession);
             final PwmApplication pwmApplication = contextManager.getPwmApplication();
-            httpSession.setAttribute(PwmConstants.SESSION_ATTR_PWM_APP_NONCE, pwmApplication.getInstanceNonce());
+            httpSession.setAttribute(PwmConstants.SESSION_ATTR_PWM_APP_NONCE, pwmApplication.getRuntimeNonce());
             LOGGER.trace("new http session created");
         } catch (PwmUnrecoverableException e) {
             LOGGER.warn("error during sessionCreated event: " + e.getMessage());

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

@@ -44,6 +44,7 @@ public enum HttpHeader {
     UserAgent("User-Agent"),
     Referer("Referer"),
     Origin("Origin"),
+    XForwardedFor("X-Forwarded-For"),
 
     XFrameOptions("X-Frame-Options"),
     XContentTypeOptions("X-Content-Type-Options"),

+ 7 - 1
server/src/main/java/password/pwm/http/PwmHttpRequestWrapper.java

@@ -92,10 +92,16 @@ public abstract class PwmHttpRequestWrapper {
 
     public String readRequestBodyAsString(final int maxChars)
             throws IOException, PwmUnrecoverableException
+    {
+        return readRequestBodyAsString(this.getHttpServletRequest(), maxChars);
+    }
+
+    public static String readRequestBodyAsString(final HttpServletRequest httpServletRequest, final int maxChars)
+            throws IOException, PwmUnrecoverableException
     {
         final StringWriter stringWriter = new StringWriter();
         final Reader readerStream = new InputStreamReader(
-                getHttpServletRequest().getInputStream(),
+                httpServletRequest.getInputStream(),
                 PwmConstants.DEFAULT_CHARSET
         );
 

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

@@ -154,8 +154,18 @@ public class PwmURL {
 
     }
 
-    public boolean isWebServiceURL() {
-        return checkIfStartsWithURL(PwmConstants.URL_PREFIX_PUBLIC + "/rest/");
+    public boolean isJerseyWebService() {
+        return checkIfStartsWithURL(PwmConstants.URL_PREFIX_PUBLIC + "/rest/")
+                && !isStandaloneWebService();
+    }
+
+    public boolean isStandaloneWebService() {
+        return checkIfStartsWithURL(PwmConstants.URL_PREFIX_PUBLIC + "/rest/")
+                && (
+                checkIfStartsWithURL(PwmConstants.URL_PREFIX_PUBLIC + "/rest/signing")
+                        ||
+                        checkIfStartsWithURL(PwmConstants.URL_PREFIX_PUBLIC + "/rest/ping")
+        );
     }
 
     public boolean isConfigManagerURL() {

+ 6 - 8
server/src/main/java/password/pwm/http/filter/ApplicationModeFilter.java

@@ -31,7 +31,6 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.ContextManager;
 import password.pwm.http.ProcessStatus;
 import password.pwm.http.PwmRequest;
-import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.PwmURL;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.util.logging.PwmLogger;
@@ -51,12 +50,9 @@ public class ApplicationModeFilter extends AbstractPwmFilter {
     )
             throws IOException, ServletException
     {
-        // add request url to request attribute
-        pwmRequest.setAttribute(PwmRequestAttribute.OriginalUri, pwmRequest.getHttpServletRequest().getRequestURI());
-
         // ignore if resource request
         final PwmURL pwmURL = pwmRequest.getURL();
-        if (!pwmURL.isResourceURL() && !pwmURL.isWebServiceURL() && !pwmURL.isReferenceURL()) {
+        if (!pwmURL.isResourceURL() && !pwmURL.isStandaloneWebService() && !pwmURL.isReferenceURL()) {
             // check for valid config
             try {
                 if (checkConfigModes(pwmRequest) == ProcessStatus.Halt) {
@@ -76,7 +72,9 @@ public class ApplicationModeFilter extends AbstractPwmFilter {
 
     @Override
     boolean isInterested(final PwmApplicationMode mode, final PwmURL pwmURL) {
-        return !pwmURL.isResourceURL();
+        return !pwmURL.isJerseyWebService()
+                && !pwmURL.isStandaloneWebService()
+                && !pwmURL.isJerseyWebService();
     }
 
     private static ProcessStatus checkConfigModes(
@@ -91,7 +89,7 @@ public class ApplicationModeFilter extends AbstractPwmFilter {
 
         if (mode == PwmApplicationMode.NEW) {
             // check if current request is actually for the config url, if it is, just do nothing.
-            if (pwmURL.isCommandServletURL() || pwmURL.isWebServiceURL()) {
+            if (pwmURL.isCommandServletURL() || pwmURL.isJerseyWebService()) {
                 return ProcessStatus.Continue;
             }
 
@@ -131,7 +129,7 @@ public class ApplicationModeFilter extends AbstractPwmFilter {
                         || pwmURL.isLogoutURL()
                         || pwmURL.isOauthConsumer()
                         || pwmURL.isAdminUrl()
-                        || pwmURL.isWebServiceURL();
+                        || pwmURL.isJerseyWebService();
 
                 if (!permittedURl) {
                     final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_APPLICATION_NOT_RUNNING);

+ 1 - 1
server/src/main/java/password/pwm/http/filter/AuthenticationFilter.java

@@ -127,7 +127,7 @@ public class AuthenticationFilter extends AbstractPwmFilter {
 
     @Override
     boolean isInterested(final PwmApplicationMode mode, final PwmURL pwmURL) {
-        return !pwmURL.isResourceURL();
+        return !pwmURL.isResourceURL() && !pwmURL.isStandaloneWebService();
     }
 
     private void processAuthenticatedSession(

+ 1 - 1
server/src/main/java/password/pwm/http/filter/AuthorizationFilter.java

@@ -57,7 +57,7 @@ public class AuthorizationFilter extends AbstractPwmFilter {
 
     @Override
     boolean isInterested(final PwmApplicationMode mode, final PwmURL pwmURL) {
-        return true;
+        return !pwmURL.isStandaloneWebService();
     }
 
     public void processFilter(

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

@@ -69,7 +69,7 @@ public class GZIPFilter implements Filter {
         final String acceptEncoding = ((HttpServletRequest)servletRequest).getHeader(HttpHeader.Accept_Encoding.getHttpName());
         if (acceptEncoding != null && acceptEncoding.contains("gzip") && isEnabled(servletRequest)) {
             final GZIPHttpServletResponseWrapper gzipResponse = new GZIPHttpServletResponseWrapper((HttpServletResponse)servletResponse);
-            gzipResponse.addHeader("Content-Encoding", "gzip");
+            gzipResponse.addHeader(HttpHeader.Content_Encoding.getHttpName(), "gzip");
             filterChain.doFilter(servletRequest, gzipResponse);
             gzipResponse.finish();
 
@@ -82,7 +82,7 @@ public class GZIPFilter implements Filter {
 
         try {
             final PwmURL pwmURL = new PwmURL((HttpServletRequest) servletRequest);
-            if (pwmURL.isResourceURL() || pwmURL.isWebServiceURL()) {
+            if (pwmURL.isResourceURL() || pwmURL.isJerseyWebService()) {
                 return false;
             }
         } catch (Exception e) {

+ 1 - 1
server/src/main/java/password/pwm/http/filter/ObsoleteUrlFilter.java

@@ -98,6 +98,6 @@ public class ObsoleteUrlFilter extends AbstractPwmFilter {
 
     @Override
     boolean isInterested(final PwmApplicationMode mode, final PwmURL pwmURL) {
-        return true;
+        return !pwmURL.isStandaloneWebService();
     }
 }

+ 19 - 12
server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java

@@ -22,6 +22,7 @@
 
 package password.pwm.http.filter;
 
+import org.apache.commons.validator.routines.InetAddressValidator;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplicationMode;
@@ -104,6 +105,8 @@ public class RequestInitializationFilter implements Filter {
 
         if (testPwmApplicationLoad == null && pwmURL.isResourceURL()) {
             filterChain.doFilter(req, resp);
+        } else if (pwmURL.isStandaloneWebService() || pwmURL.isJerseyWebService()) {
+            filterChain.doFilter(req, resp);
         } else {
             if (mode == PwmApplicationMode.ERROR) {
                 try {
@@ -220,7 +223,7 @@ public class RequestInitializationFilter implements Filter {
             final HttpSession httpSession = request.getSession(false);
             if (httpSession != null) {
                 final String sessionPwmAppNonce = (String) httpSession.getAttribute(PwmConstants.SESSION_ATTR_PWM_APP_NONCE);
-                if (sessionPwmAppNonce == null || !sessionPwmAppNonce.equals(pwmApplication.getInstanceNonce())) {
+                if (sessionPwmAppNonce == null || !sessionPwmAppNonce.equals(pwmApplication.getRuntimeNonce())) {
                     LOGGER.debug("invalidating http session created with non-current servlet context");
                     httpSession.invalidate();
                 }
@@ -408,20 +411,24 @@ public class RequestInitializationFilter implements Filter {
         String userIP = "";
 
         if (useXForwardedFor) {
-            try {
-                userIP = pwmRequest.readHeaderValueAsString(PwmConstants.HTTP_HEADER_X_FORWARDED_FOR);
-                if (userIP != null) {
-                    final int commaIndex = userIP.indexOf(',');
-                    if (commaIndex > -1) {
-                        userIP = userIP.substring(0, commaIndex);
-                    }
+            userIP = pwmRequest.readHeaderValueAsString(HttpHeader.XForwardedFor);
+            if (!StringUtil.isEmpty(userIP)) {
+                final int commaIndex = userIP.indexOf(',');
+                if (commaIndex > -1) {
+                    userIP = userIP.substring(0, commaIndex);
+                }
+            }
+
+            if (!StringUtil.isEmpty(userIP)) {
+                if (!InetAddressValidator.getInstance().isValid(userIP)) {
+                    LOGGER.warn("discarding bogus network address '" + userIP + "' in "
+                            + HttpHeader.XForwardedFor.getHttpName() + " header");
+                    userIP = null;
                 }
-            } catch (Exception e) {
-                //ip address not in header (no X-Forwarded-For)
             }
         }
 
-        if (userIP == null || userIP.length() < 1) {
+        if (StringUtil.isEmpty(userIP)) {
             userIP = pwmRequest.getHttpServletRequest().getRemoteAddr();
         }
 
@@ -585,7 +592,7 @@ public class RequestInitializationFilter implements Filter {
         if (
                 performCsrfHeaderChecks
                         && !pwmRequest.getMethod().isIdempotent()
-                        && !pwmRequest.getURL().isWebServiceURL()
+                        && !pwmRequest.getURL().isJerseyWebService()
                 )
         {
             final String originValue = pwmRequest.readHeaderValueAsString(HttpHeader.Origin);

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

@@ -79,7 +79,7 @@ public class SessionFilter extends AbstractPwmFilter {
 
     @Override
     boolean isInterested(final PwmApplicationMode mode, final PwmURL pwmURL) {
-        return true;
+        return !pwmURL.isStandaloneWebService();
     }
 
     private static final AtomicInteger REQUEST_COUNTER = new AtomicInteger(0);
@@ -98,7 +98,7 @@ public class SessionFilter extends AbstractPwmFilter {
         pwmRequest.debugHttpRequestToLog("requestID=" + requestID);
 
         final PwmURL pwmURL = pwmRequest.getURL();
-        if (!pwmURL.isWebServiceURL() && !pwmURL.isResourceURL()) {
+        if (!pwmURL.isStandaloneWebService() && !pwmURL.isResourceURL()) {
             if (handleStandardRequestOperations(pwmRequest) == ProcessStatus.Halt) {
                 return;
             }

+ 18 - 11
server/src/main/java/password/pwm/http/servlet/ControlledPwmServlet.java

@@ -41,11 +41,16 @@ import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 
 public abstract class ControlledPwmServlet extends AbstractPwmServlet implements PwmServlet {
 
     private static final PwmLogger LOGGER = PwmLogger.forClass(AbstractPwmServlet.class);
 
+    private Map<String,Method> actionMethodCache;
+
     public String servletUriRemainder(final PwmRequest pwmRequest, final String command) throws PwmUnrecoverableException {
         String uri = pwmRequest.getURLwithoutQueryString();
         if (uri.startsWith(pwmRequest.getContextPath())) {
@@ -163,7 +168,7 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
 
     public abstract ProcessStatus preProcessCheck(PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException;
 
-    void sendOtherRedirect(final PwmRequest pwmRequest, final String location) throws IOException, PwmUnrecoverableException {
+    private void sendOtherRedirect(final PwmRequest pwmRequest, final String location) throws IOException, PwmUnrecoverableException {
         final String protocol = pwmRequest.getHttpServletRequest().getProtocol();
         if (protocol != null && protocol.startsWith("HTTP/1.0")) {
             pwmRequest.sendRedirect(location);
@@ -177,19 +182,21 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
         String action();
     }
 
-    public static Method discoverMethodForAction(final Class clazz, final ProcessAction action) {
-        Method interestedMethod = null;
-        final Collection<Method> methods = JavaHelper.getAllMethodsForClass(clazz);
-        for (final Method method : methods) {
-            if (method.getAnnotation(ActionHandler.class) != null) {
-                final String actionName = method.getAnnotation(ActionHandler.class).action();
-                if (action.toString().equals(actionName)) {
-                    interestedMethod = method;
-                    break;
+    private Method discoverMethodForAction(final Class clazz, final ProcessAction action) {
+        if (actionMethodCache == null) {
+            final Map<String,Method> map = new HashMap<>();
+            final Collection<Method> methods = JavaHelper.getAllMethodsForClass(clazz);
+            for (Method method : methods) {
+                if (method.getAnnotation(ActionHandler.class) != null) {
+                    final String actionName = method.getAnnotation(ActionHandler.class).action();
+                        map.put(actionName, method);
+
                 }
             }
+            actionMethodCache = Collections.unmodifiableMap(map);
         }
-        return interestedMethod;
+
+        return actionMethodCache.get(action.toString());
     }
 }
 

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

@@ -62,7 +62,7 @@ import password.pwm.util.macro.MacroMachine;
 import password.pwm.util.operations.PasswordUtility;
 import password.pwm.ws.server.RestResultBean;
 import password.pwm.ws.server.rest.RestCheckPasswordServer;
-import password.pwm.ws.server.rest.RestSigningServer;
+import password.pwm.ws.server.rest.RestFormSigningServer;
 
 import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
@@ -150,7 +150,7 @@ public class NewUserServlet extends ControlledPwmServlet {
 
         final String signedFormData = pwmRequest.readParameterAsString(PwmConstants.PARAM_SIGNED_FORM, PwmHttpRequestWrapper.Flag.BypassValidation);
         if (!StringUtil.isEmpty(signedFormData)) {
-            final Map<String,String> jsonForm = RestSigningServer.readSignedFormValue(pwmApplication, signedFormData);
+            final Map<String,String> jsonForm = RestFormSigningServer.readSignedFormValue(pwmApplication, signedFormData);
             LOGGER.trace("detected signedForm parameter in request, will read and place in bean; keys=" + JsonUtil.serializeCollection(jsonForm.keySet()));
             newUserBean.setRemoteInputData(jsonForm);
         }

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/resource/ResourceServletConfiguration.java

@@ -75,7 +75,7 @@ class ResourceServletConfiguration {
 
         final String noncePrefix = configuration.readAppProperty(AppProperty.HTTP_RESOURCES_NONCE_PATH_PREFIX);
         noncePattern = Pattern.compile(noncePrefix + "[^/]*?/");
-        nonceValue = pwmApplication.getInstanceNonce();
+        nonceValue = pwmApplication.getRuntimeNonce();
 
         final String zipFileResourceParam = configuration.readAppProperty(AppProperty.HTTP_RESOURCES_ZIP_FILES);
         if (zipFileResourceParam != null && !zipFileResourceParam.isEmpty()) {

+ 1 - 1
server/src/main/java/password/pwm/svc/cluster/DatabaseStoredNodeData.java

@@ -47,7 +47,7 @@ class DatabaseStoredNodeData implements Serializable {
                 Instant.now(),
                 pwmApplication.getStartupTime(),
                 pwmApplication.getInstanceID(),
-                pwmApplication.getInstanceNonce(),
+                pwmApplication.getRuntimeNonce(),
                 pwmApplication.getConfig().configurationHash()
         );
     }

+ 64 - 0
server/src/main/java/password/pwm/ws/server/PwmRestServlet.java

@@ -0,0 +1,64 @@
+package password.pwm.ws.server;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import password.pwm.PwmConstants;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.HttpHeader;
+import password.pwm.http.HttpMethod;
+
+import javax.servlet.ServletException;
+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.util.Set;
+
+public abstract class PwmRestServlet extends HttpServlet{
+
+    protected void service(final HttpServletRequest req, final HttpServletResponse resp)
+            throws ServletException, IOException
+    {
+
+        try {
+            resp.setHeader(HttpHeader.Content_Type.getHttpName(), PwmConstants.ContentTypeValue.json.toString());
+            resp.setHeader(HttpHeader.Server.getHttpName(), PwmConstants.PWM_APP_NAME);
+            checkMethods(req);
+            final StandaloneRestRequestBean restRequestBean = StandaloneRestHelper.initialize(req);
+            final RestResultBean restResultBean = invokeWebService(restRequestBean);
+            try (PrintWriter pw = resp.getWriter()) {
+                pw.write(restResultBean.toJson());
+            }
+        } catch (PwmUnrecoverableException e) {
+            final RestResultBean restResultBean = RestResultBean.fromError(e.getErrorInformation());
+            try (PrintWriter pw = resp.getWriter()) {
+                pw.write(restResultBean.toJson());
+            }
+            resp.sendError(500, e.getMessage());
+        } catch (Throwable e) {
+            resp.sendError(500, e.getMessage());
+        }
+    }
+
+    private void checkMethods(final HttpServletRequest req)
+            throws PwmUnrecoverableException
+    {
+        final Set<HttpMethod> methods = getServiceInfo().getMethods();
+        final HttpMethod methodUsed = HttpMethod.fromString(req.getMethod());
+        if (!methods.contains(methodUsed)) {
+            throw new PwmUnrecoverableException(PwmError.ERROR_REST_INVOCATION_ERROR, "method not supported");
+        }
+    }
+
+    public abstract RestResultBean invokeWebService(StandaloneRestRequestBean standaloneRestRequestBean);
+
+    public abstract ServiceInfo getServiceInfo();
+
+    @Getter
+    @AllArgsConstructor
+    public static class ServiceInfo {
+        private Set<HttpMethod> methods;
+    }
+}

+ 9 - 2
server/src/main/java/password/pwm/ws/server/StandaloneRestHelper.java

@@ -23,17 +23,20 @@
 package password.pwm.ws.server;
 
 import com.novell.ldapchai.util.StringHelper;
+import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
-import password.pwm.config.value.data.NamedSecretData;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.WebServiceUsage;
+import password.pwm.config.value.data.NamedSecretData;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.ContextManager;
+import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.util.BasicAuthInfo;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.logging.PwmLogger;
 
 import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -44,15 +47,19 @@ public class StandaloneRestHelper {
     private static final PwmLogger LOGGER = PwmLogger.forClass(StandaloneRestHelper.class);
 
     public static StandaloneRestRequestBean initialize(final HttpServletRequest httpServletRequest)
-            throws PwmUnrecoverableException
+            throws PwmUnrecoverableException, IOException
     {
         final PwmApplication pwmApplication = ContextManager.getPwmApplication(httpServletRequest.getServletContext());
 
         final Set<WebServiceUsage> usages = readWebServiceSecretAuthorizations(pwmApplication, httpServletRequest);
 
+        final int maxChars = Integer.parseInt(pwmApplication.getConfig().readAppProperty(AppProperty.HTTP_BODY_MAXREAD_LENGTH));
+        final String body = PwmHttpRequestWrapper.readRequestBodyAsString(httpServletRequest, maxChars);
+
         return StandaloneRestRequestBean.builder()
                 .pwmApplication(pwmApplication)
                 .authorizedUsages(Collections.unmodifiableSet(usages))
+                .body(body)
                 .build();
     }
 

+ 2 - 0
server/src/main/java/password/pwm/ws/server/StandaloneRestRequestBean.java

@@ -34,4 +34,6 @@ import java.util.Set;
 public class StandaloneRestRequestBean {
     private final Set<WebServiceUsage> authorizedUsages;
     private final PwmApplication pwmApplication;
+    private final String body;
+
 }

+ 2 - 18
server/src/main/java/password/pwm/ws/server/rest/RestAppDataServer.java

@@ -50,7 +50,6 @@ import password.pwm.svc.event.UserAuditRecord;
 import password.pwm.svc.intruder.RecordType;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.util.LocaleHelper;
-import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
@@ -156,21 +155,6 @@ public class RestAppDataServer extends AbstractRestServer {
         return restResultBean.asJsonResponse();
     }
 
-    @GET
-    @Path("/ping")
-    @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
-    public Response doPingRequest() {
-        final RestRequestBean restRequestBean;
-        try {
-            restRequestBean = RestServerHelper.initializeRestRequest(request, response, ServicePermissions.PUBLIC, null);
-        } catch (PwmUnrecoverableException e) {
-            return RestResultBean.fromError(e.getErrorInformation()).asJsonResponse();
-        }
-
-        final String startupTime = JavaHelper.toIsoDate(restRequestBean.getPwmApplication().getStartupTime());
-        return new RestResultBean(new HashMap<>(Collections.singletonMap("time",startupTime))).asJsonResponse();
-    }
-
     @GET
     @Path("/session")
     @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
@@ -396,7 +380,7 @@ public class RestAppDataServer extends AbstractRestServer {
             settingMap.put("MaxInactiveInterval", idleSeconds);
         }
         settingMap.put("paramName.locale", config.readAppProperty(AppProperty.HTTP_PARAM_NAME_LOCALE));
-        settingMap.put("startupTime",pwmApplication.getStartupTime());
+        settingMap.put("runtimeNonce",pwmApplication.getRuntimeNonce());
         settingMap.put("applicationMode",pwmApplication.getApplicationMode());
 
         final String contextPath = request.getContextPath();
@@ -494,7 +478,7 @@ public class RestAppDataServer extends AbstractRestServer {
         inputString.append(PwmConstants.BUILD_NUMBER);
         inputString.append(pwmApplication.getStartupTime().toEpochMilli());
         inputString.append(httpServletRequest.getSession().getMaxInactiveInterval());
-        inputString.append(pwmApplication.getInstanceNonce());
+        inputString.append(pwmApplication.getRuntimeNonce());
 
         if (pwmSession.getSessionStateBean().getLocale() != null) {
             inputString.append(pwmSession.getSessionStateBean().getLocale());

+ 27 - 29
server/src/main/java/password/pwm/ws/server/rest/RestSigningServer.java → server/src/main/java/password/pwm/ws/server/rest/RestFormSigningServer.java

@@ -26,51 +26,44 @@ import lombok.AllArgsConstructor;
 import lombok.Getter;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
 import password.pwm.config.option.WebServiceUsage;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.HttpMethod;
+import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.secure.SecureService;
+import password.pwm.ws.server.PwmRestServlet;
 import password.pwm.ws.server.RestResultBean;
-import password.pwm.ws.server.StandaloneRestHelper;
 import password.pwm.ws.server.StandaloneRestRequestBean;
 
-import javax.ws.rs.Consumes;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
+import javax.servlet.annotation.WebServlet;
 import java.io.Serializable;
 import java.time.Instant;
+import java.util.Collections;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
-@Path("/signing")
-public class RestSigningServer extends AbstractRestServer {
+@WebServlet(
+        name="RestFormSigningServer",
+        urlPatterns={
+                PwmConstants.URL_PREFIX_PUBLIC + PwmConstants.URL_PREFIX_REST + "/signing/form",
+        }
+)
+public class RestFormSigningServer extends PwmRestServlet {
 
-    @POST
-    @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
-    @Consumes(MediaType.APPLICATION_JSON)
-    @Path("/form")
-    public Response doGetProfileJsonData(
-            final Map<String,String> inputFormData
 
-    )
-            throws PwmUnrecoverableException
-    {
-        final StandaloneRestRequestBean restRequestBean;
-        try {
-            restRequestBean = StandaloneRestHelper.initialize(request);
-        } catch (PwmUnrecoverableException e) {
-            return RestResultBean.fromError(e.getErrorInformation()).asJsonResponse();
-        }
+    @Override
+    public RestResultBean invokeWebService(final StandaloneRestRequestBean restRequestBean) {
+        final Map<String,String> inputFormData = JsonUtil.deserializeStringMap(restRequestBean.getBody());
+
 
         if (!restRequestBean.getAuthorizedUsages().contains(WebServiceUsage.SigningForm)) {
             final String errorMsg = "request is not authenticated with permission for SigningForm";
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED, errorMsg);
-            return RestResultBean.fromError(errorInformation).asJsonResponse();
+            return RestResultBean.fromError(errorInformation);
         }
 
         try {
@@ -79,15 +72,15 @@ public class RestSigningServer extends AbstractRestServer {
                 final SignedFormData signedFormData = new SignedFormData(Instant.now(), inputFormData);
                 final String signedValue = securityService.encryptObjectToString(signedFormData);
                 final RestResultBean restResultBean = new RestResultBean(signedValue);
-                return restResultBean.asJsonResponse();
+                return restResultBean;
             }
-            return RestResultBean.fromError(new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,"no json form in body")).asJsonResponse();
+            return RestResultBean.fromError(new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,"no json form in body"));
         } catch (PwmUnrecoverableException e) {
-            return RestResultBean.fromError(e.getErrorInformation()).asJsonResponse();
+            return RestResultBean.fromError(e.getErrorInformation());
         } catch (Exception e) {
             final String errorMsg = "unexpected error building json response: " + e.getMessage();
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg);
-            return RestResultBean.fromError(errorInformation).asJsonResponse();
+            return RestResultBean.fromError(errorInformation);
         }
     }
 
@@ -114,4 +107,9 @@ public class RestSigningServer extends AbstractRestServer {
         private Instant timestamp;
         private Map<String,String> formData;
     }
+
+    @Override
+    public PwmRestServlet.ServiceInfo getServiceInfo() {
+        return new ServiceInfo(Collections.singleton(HttpMethod.POST));
+    }
 }

+ 8 - 1
server/src/main/java/password/pwm/ws/server/rest/RestHealthServer.java

@@ -72,6 +72,8 @@ public class RestHealthServer extends AbstractRestServer {
         final PwmApplication pwmApplication;
         try {
             pwmApplication = processAuthentication();
+        } catch (IOException e) {
+            return e.getMessage();
         } catch (PwmUnrecoverableException e) {
             RestServerHelper.handleNonJsonErrorResult(e.getErrorInformation());
             return null;
@@ -98,6 +100,11 @@ public class RestHealthServer extends AbstractRestServer {
         final PwmApplication pwmApplication;
         try {
             pwmApplication = processAuthentication();
+        } catch (IOException e) {
+            final RestResultBean restResultBean = new RestResultBean();
+            restResultBean.setError(true);
+            restResultBean.setErrorMessage(e.getMessage());
+            return restResultBean.asJsonResponse();
         } catch (PwmUnrecoverableException e) {
             return RestResultBean.fromError(e.getErrorInformation()).asJsonResponse();
         }
@@ -117,7 +124,7 @@ public class RestHealthServer extends AbstractRestServer {
         }
     }
 
-    private PwmApplication processAuthentication() throws PwmUnrecoverableException
+    private PwmApplication processAuthentication() throws PwmUnrecoverableException, IOException
     {
         final ServicePermissions servicePermissions = figurePermissions();
         {

+ 47 - 0
server/src/main/java/password/pwm/ws/server/rest/RestPingServer.java

@@ -0,0 +1,47 @@
+package password.pwm.ws.server.rest;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import password.pwm.PwmConstants;
+import password.pwm.http.HttpMethod;
+import password.pwm.ws.server.PwmRestServlet;
+import password.pwm.ws.server.RestResultBean;
+import password.pwm.ws.server.StandaloneRestRequestBean;
+
+import javax.servlet.annotation.WebServlet;
+import java.io.Serializable;
+import java.time.Instant;
+import java.util.Collections;
+
+@WebServlet(
+        name="RestPingServer",
+        urlPatterns={
+                PwmConstants.URL_PREFIX_PUBLIC + PwmConstants.URL_PREFIX_REST + "/ping",
+        }
+)
+
+public class RestPingServer extends PwmRestServlet {
+
+    @Getter
+    @Setter
+    @NoArgsConstructor
+    public static class PingResponse implements Serializable {
+        private Instant time;
+        private String runtimeNonce;
+    }
+
+    @Override
+    public RestResultBean invokeWebService(final StandaloneRestRequestBean standaloneRestRequestBean) {
+        final PingResponse pingResponse = new PingResponse();
+        pingResponse.setTime(Instant.now());
+        pingResponse.setRuntimeNonce(standaloneRestRequestBean.getPwmApplication().getRuntimeNonce());
+        return new RestResultBean(pingResponse);
+    }
+
+    @Override
+    public PwmRestServlet.ServiceInfo getServiceInfo() {
+        return new ServiceInfo(Collections.singleton(HttpMethod.GET));
+    }
+
+}

+ 0 - 1
server/src/main/resources/password/pwm/PwmConstants.properties

@@ -28,7 +28,6 @@ locale.defaultLocale=en
 locale.defaultDateTimeFormat=yyyy-MM-dd'T'HH:mm:ss'Z'
 locale.defaultTimeZone=Zulu
 httpHeaderAuthorizationBasic=Basic
-httpHeaderXForwardedFor=X-Forwarded-For
 httpRestClientKey=X-RestClientKey
 defaultBadPasswordAttempt=BADPASSWORDATTEMPT
 log.removedValue=*hidden*

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

@@ -284,7 +284,7 @@ Setting_Description_display.custom.resourceBundle=<p>Upload a custom ZIP file co
 Setting_Description_display.hideConfigHealthWarnings=Enable this option to hide health warnings about configuration issues from the health status monitors.
 Setting_Description_display.homeButton=Enable this option to show a "home" button in the header and other menus as appropriate to authenticated users and administrators.
 Setting_Description_display.idleTimeout=Enable this option to show the user's remaining idle time, and when that time reaches zero, @PwmAppName@ redirects the user to the logout page. 
-Setting_Description_display.js.custom=Specify a custom JavaScript that @PwmAppName@ injects into all pages inside an HTML tag near the bottom of the page.
+Setting_Description_display.js.custom=Enter custom JavaScript that @PwmAppName@ will embed onto all user HTML pages.  The @PwmAppName@ JavaScript environment is not documented and may change from version to version.  Using this feature should be done only in an environment where development resources are available to maintaine the custom JavaScript over time.<br/><br/>A few general tips:<ul><li>The custom JavaScript will execute after the body onload event and after most of the @PwmAppName@ libraries have loaded.</li><li>The custom JavaScript will load on every page view.  Your code can identify the current page by examinng the <code>data-jsp-name</code> attribute of the <code>application-info</code> html element.  This element will appear on all pages.</li><li>Referencing any JavaScript or other URLs externally is not permitted by the default <code>Content-Security-Policy</code>.  Instead include any scripts, images or css files you need locally by using  <code>@PwmSettingReference:display.custom.resourceBundle@.</code></li></ul>
 Setting_Description_display.logoutButton=Enable this option to show a logout button in the header and other menus as appropriate to authenticated users and administrators.
 Setting_Description_display.maskPasswordFields=Enable this option to mask sensitive input fields with standard "password" masking.  If set to false, @PwmAppName@ displays sensitive fields as normal text input fields.
 Setting_Description_display.maskResponseFields=Enable this option to mask Challenge/Response answer input fields with standard "password" masking.  If set to false, @PwmAppName@ displays response fields as normal text input fields.  This setting applies to both setup responses and forgotten password response entry screens.

+ 3 - 3
server/src/main/webapp/WEB-INF/jsp/admin-logview.jsp

@@ -213,9 +213,9 @@
                             rowData.put("timestamp", event.getDate());
                             rowData.put("level", event.getLevel().toString());
                             rowData.put("src", event.getSource());
-                            rowData.put("user", event.getActor());
-                            rowData.put("component",event.getTopTopic());
-                            rowData.put("detail",event.getMessage());
+                            rowData.put("user", StringUtil.escapeJS(event.getActor()));
+                            rowData.put("component",StringUtil.escapeJS(event.getTopTopic()));
+                            rowData.put("detail",StringUtil.escapeJS(event.getMessage()));
                 %>
                 data.push(<%=JsonUtil.serializeMap(rowData)%>);
                 <%

+ 6 - 0
server/src/main/webapp/WEB-INF/jsp/admin-urlreference.jsp

@@ -59,6 +59,12 @@
                 <td class="key">New User Registration</td>
                 <td><a href="<pwm:context/><%=PwmServletDefinition.NewUser.servletUrl()%>"><pwm:context/><%=PwmServletDefinition.NewUser.servletUrl()%></a></td>
             </tr>
+            <% for (final String id : JspUtility.getPwmRequest(pageContext).getConfig().getNewUserProfiles().keySet()) { %>
+            <tr>
+                <td class="key">New User Registration "<%=id%>" profile</td>
+                <td><a href="<pwm:context/><%=PwmServletDefinition.NewUser.servletUrl()%>"><pwm:context/><%=PwmServletDefinition.NewUser.servletUrl()%>/profile/<%=id%></a></td>
+            </tr>
+            <% } %>
             <tr>
                 <td class="key">Public People Search </td>
                 <td><a href="<pwm:context/><%=PwmServletDefinition.PublicPeopleSearch.servletUrl()%>"><pwm:context/><%=PwmServletDefinition.PublicPeopleSearch.servletUrl()%></a></td>

+ 1 - 0
server/src/main/webapp/WEB-INF/jsp/configeditor.jsp

@@ -157,6 +157,7 @@
 <pwm:script-ref url="/public/resources/js/uilibrary.js"/>
 <pwm:script-ref url="/public/resources/js/configeditor.js"/>
 <pwm:script-ref url="/public/resources/js/configeditor-settings.js"/>
+<pwm:script-ref url="/public/resources/js/configeditor-settings-challenges.js"/>
 <pwm:script-ref url="/public/resources/js/configeditor-settings-customlink.js"/>
 <pwm:script-ref url="/public/resources/js/configeditor-settings-remotewebservices.js"/>
 <pwm:script-ref url="/public/resources/js/admin.js"/>

+ 19 - 0
server/src/main/webapp/WEB-INF/jsp/fragment/debug.jsp

@@ -0,0 +1,19 @@
+<%@ page import="password.pwm.http.JspUtility" %>
+<%@ page import="password.pwm.util.java.StringUtil" %>
+<table>
+    <tr>
+        <td>Forward URL</td><td><%=StringUtil.escapeHtml(JspUtility.getPwmRequest(pageContext).getForwardUrl())%></td>
+    </tr>
+    <tr>
+        <td>Logout URL</td><td><%=StringUtil.escapeHtml(JspUtility.getPwmRequest(pageContext).getLogoutURL())%></td>
+    </tr>
+    <tr>
+        <td>Locale</td><td><%=StringUtil.escapeHtml(JspUtility.getPwmRequest(pageContext).getLocale().toString())%></td>
+    </tr>
+    <tr>
+        <td>Theme</td><td><%=StringUtil.escapeHtml(JspUtility.getPwmRequest(pageContext).getPwmSession().getSessionStateBean().getTheme())%></td>
+    </tr>
+    <tr>
+        <td>Instance ID</td><td><%=StringUtil.escapeHtml(JspUtility.getPwmRequest(pageContext).getPwmApplication().getInstanceID())%></td>
+    </tr>
+</table>

+ 4 - 1
server/src/main/webapp/WEB-INF/jsp/fragment/header-body.jsp

@@ -79,4 +79,7 @@
             </div>
         </div>
     </div>
-</div>
+</div>
+<% if (request.getParameter("debug") != null) { %>
+<%@ include file="debug.jsp" %>
+<% } %>

+ 339 - 0
server/src/main/webapp/public/resources/js/configeditor-settings-challenges.js

@@ -0,0 +1,339 @@
+/*
+ * 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
+ */
+
+// -------------------------- challenge handler ------------------------------------
+
+var ChallengeSettingHandler = {};
+ChallengeSettingHandler.defaultItem = {text:'Question',minLength:4,maxLength:200,adminDefined:true,enforceWordlist:true,maxQuestionCharsInAnswer:3};
+
+ChallengeSettingHandler.init = function(settingKey) {
+    var parentDiv = "table_setting_" + settingKey;
+    console.log('ChallengeSettingHandler init for ' + settingKey);
+    PWM_CFGEDIT.clearDivElements(parentDiv, true);
+    PWM_CFGEDIT.readSetting(settingKey, function(resultValue) {
+        PWM_VAR['clientSettingCache'][settingKey] = resultValue;
+        if (PWM_MAIN.JSLibrary.isEmpty(resultValue)) {
+            var htmlBody = '<button class="btn" id="button-addValue-' + settingKey + '">';
+            htmlBody += '<span class="btn-icon pwm-icon pwm-icon-plus-square"></span>Add Value';
+            htmlBody += '</button>';
+
+            var parentDivElement = PWM_MAIN.getObject(parentDiv);
+            parentDivElement.innerHTML = htmlBody;
+
+            PWM_MAIN.addEventHandler('button-addValue-' + settingKey,'click',function(){
+                PWM_VAR['clientSettingCache'][settingKey] = {};
+                PWM_VAR['clientSettingCache'][settingKey][''] = [];
+                PWM_VAR['clientSettingCache'][settingKey][''].push(ChallengeSettingHandler.defaultItem);
+                ChallengeSettingHandler.write(settingKey,function(){
+                    ChallengeSettingHandler.init(settingKey);
+                });
+            });
+        } else {
+            ChallengeSettingHandler.draw(settingKey);
+        }
+    });
+};
+
+ChallengeSettingHandler.draw = function(settingKey) {
+    var parentDiv = "table_setting_" + settingKey;
+    var resultValue = PWM_VAR['clientSettingCache'][settingKey];
+    var parentDivElement = PWM_MAIN.getObject(parentDiv);
+    var bodyText = '<div class="footnote">Click on challenge questions to edit questions and policies</div>';
+    PWM_CFGEDIT.clearDivElements(parentDiv, false);
+    for (var localeName in resultValue) {
+        (function(localeKey) {
+            var multiValues = resultValue[localeKey];
+            var rowCount = PWM_MAIN.JSLibrary.itemCount(multiValues);
+
+            bodyText += '<table class="noborder"><tr><td>';
+            bodyText += '<table class="setting-challenge-question-summary">';
+            var localeLabel = localeName == '' ? 'Default Locale' : PWM_GLOBAL['localeInfo'][localeName] + " (" + localeName + ")";
+            if (PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][settingKey]) > 1) {
+                bodyText += '<tr><td class="title" style="font-size:100%; font-weight:normal">' + localeLabel + '</td></tr>';
+            }
+
+            bodyText += '<tr>';
+            bodyText += '<td style="width:100%" id="button-edit-' + settingKey + '-' + localeKey + '">';
+            if (rowCount > 0) {
+                for (var iteration in multiValues) {
+                    var id = 'panel-value-' + settingKey + '-' + localeKey + '-' + iteration;
+                    bodyText += '<div style="text-overflow:ellipsis; white-space:nowrap; overflow:hidden" id="' + id + '">text</div>';
+                    bodyText += '<div style="font-size: 80%; font-style: italic">'
+                        + '<span style="padding-left: 10px">Min Length: <span id="' + id + '-minLength"></span></span>'
+                        + '<span style="padding-left: 10px">Max Length: <span id="' + id + '-maxLength"></span></span>'
+                        + '<span style="padding-left: 10px">Max Question Chars: <span id="' + id + '-maxQuestions"></span></span>'
+                        + '<span style="padding-left: 10px">Apply Wordlist: <span id="' + id + '-wordlist"></span></span>'
+                        + '</div>';
+                }
+            } else {
+                bodyText += '[No Questions]';
+            }
+            bodyText += '</td></tr>';
+
+            bodyText += '</table></td><td class="noborder" style="width:20px; vertical-align:top">';
+            if (localeName != '' || PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][settingKey]) < 2) { // add remove locale x
+                bodyText += '<div id="button-deleteRow-' + settingKey + '-' + localeKey + '" style="vertical-align:top" class="delete-row-icon action-icon pwm-icon pwm-icon-times"></div>';
+            }
+            bodyText += '</td></tr></table><br/>';
+        }(localeName));
+    }
+    parentDivElement.innerHTML = bodyText;
+
+    var addLocaleFunction = function(localeValue) {
+        if (localeValue in PWM_VAR['clientSettingCache'][settingKey]) {
+            PWM_MAIN.showDialog({title:PWM_MAIN.showString('Title_Error'),text:'Locale <i>' + localeValue + '</i> is already present.'});
+        } else {
+            PWM_VAR['clientSettingCache'][settingKey][localeValue] = [];
+            PWM_VAR['clientSettingCache'][settingKey][localeValue][0] = ChallengeSettingHandler.defaultItem;
+            ChallengeSettingHandler.write(settingKey, function(){
+                ChallengeSettingHandler.init(settingKey);
+            });
+        }
+    };
+    var tableElement = document.createElement("div");
+    parentDivElement.appendChild(tableElement);
+
+    UILibrary.addAddLocaleButtonRow(tableElement, settingKey, addLocaleFunction, Object.keys(resultValue));
+
+    for (var localeName in resultValue) {
+        (function(localeKey) {
+            PWM_MAIN.addEventHandler('button-edit-' + settingKey + '-' + localeKey,'click',function(){
+                ChallengeSettingHandler.editLocale(settingKey,localeKey);
+            });
+
+            var multiValues = resultValue[localeKey];
+            var rowCount = PWM_MAIN.JSLibrary.itemCount(multiValues);
+            if (rowCount > 0) {
+                for (var iteration in multiValues) {
+                    (function (rowKey) {
+                        var id = 'panel-value-' + settingKey + '-' + localeKey + '-' + iteration;
+                        var questionText = multiValues[rowKey]['text'];
+                        var adminDefined = multiValues[rowKey]['adminDefined'];
+                        var output = (adminDefined ? questionText : '[User Defined]');
+                        UILibrary.addTextValueToElement(id,output);
+                        UILibrary.addTextValueToElement(id + '-minLength', multiValues[rowKey]['minLength']);
+                        UILibrary.addTextValueToElement(id + '-maxLength', multiValues[rowKey]['maxLength']);
+                        UILibrary.addTextValueToElement(id + '-maxQuestions', multiValues[rowKey]['maxQuestionCharsInAnswer']);
+                        UILibrary.addTextValueToElement(id + '-wordlist', multiValues[rowKey]['enforceWordlist']);
+                    }(iteration));
+                }
+            }
+
+            PWM_MAIN.addEventHandler('button-deleteRow-' + settingKey + '-' + localeKey,'click',function(){
+                ChallengeSettingHandler.deleteLocale(settingKey, localeKey)
+            });
+        }(localeName));
+    }
+
+};
+
+ChallengeSettingHandler.editLocale = function(keyName, localeKey) {
+    var localeDisplay = localeKey == "" ? "Default" : localeKey;
+    var dialogBody = '<div id="challengeLocaleDialogDiv" style="max-height:500px; overflow-x: auto">';
+
+    var localeName = localeKey;
+
+    var resultValue = PWM_VAR['clientSettingCache'][keyName];
+
+    var multiValues = resultValue[localeName];
+
+    for (var iteration in multiValues) {
+        (function(rowKey) {
+            dialogBody += '<table class="noborder">';
+            dialogBody += '<tr><td style="width: 15px" class="noborder">' + (parseInt(iteration) + 1) + '</td><td class="setting_outline">';
+            dialogBody += '<table class="noborder" style="margin:0"><tr>';
+            dialogBody += '<td colspan="200" style="border-width: 0;">';
+
+            var inputID = "value-" + keyName + "-" + localeName + "-" + rowKey;
+            PWM_MAIN.clearDijitWidget(inputID);
+
+            dialogBody += '<input class="configStringInput" id="' + inputID + '" style="width: 700px" required="required" disabled value="Loading"/>';
+
+            dialogBody += '</td>';
+            dialogBody += '</tr>';
+
+            dialogBody += '<tr><td>';
+
+            dialogBody += '<label class="checkboxWrapper"><input type="checkbox" id="value-adminDefined-' + inputID + '" disabled/>Admin Defined</label>';
+
+            dialogBody += '</td><td>';
+
+            dialogBody += '<input type="number" id="button-minLength-' + inputID + '" style="width:50px" class="configNumericInput" min="1" max="255" value="' + multiValues[rowKey]['minLength'] + '"/>';
+            dialogBody += '<br/>Min Length';
+
+            dialogBody += '</td><td>';
+            dialogBody += '<input type="number" id="button-maxLength-' + inputID + '" style="width:50px" class="configNumericInput" min="1" max="255" value="' + multiValues[rowKey]['maxLength'] + '"/>';
+            dialogBody += '<br/>Max Length';
+
+            dialogBody += '</td><td>';
+            dialogBody += '<input type="number" id="button-maxQuestionCharsInAnswer-' + inputID + '" style="width:50px" class="configNumericInput" min="0" max="100" value="' + multiValues[rowKey]['maxQuestionCharsInAnswer'] + '"/>';
+            dialogBody += '<br/>Max Question Characters';
+
+            dialogBody += '</td><td>';
+            dialogBody += '<label class="checkboxWrapper"><input type="checkbox" id="value-wordlist-' + inputID + '" disabled/>Apply Word List</label>';
+
+            dialogBody += '</td></tr>';
+            dialogBody += '</table></td><td class="noborder" style="vertical-align: top">';
+            if (PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][keyName][localeKey]) > 1) { // add remove locale x
+
+                dialogBody += '<div class="delete-row-icon action-icon pwm-icon pwm-icon-times" id="button-deleteRow-' + inputID + '"/>';
+            }
+
+            dialogBody += '</td></tr></table>';
+            dialogBody += '<br/>';
+
+        }(iteration));
+    }
+
+    dialogBody += '</div>';
+    dialogBody += '<br/>';
+
+    var dialogTitle = PWM_SETTINGS['settings'][keyName]['label'] + ' - ' + localeDisplay + ' Locale';
+    PWM_MAIN.showDialog({
+        title:dialogTitle,
+        buttonHtml:'<button class="btn" id="button-addValue"><span class="btn-icon pwm-icon pwm-icon-plus-square"></span>Add Value</button>',
+        text:dialogBody,
+        showClose:false,
+        dialogClass:'wide',
+        loadFunction:function(){
+            PWM_MAIN.addEventHandler('button-addValue','click',function(){
+                ChallengeSettingHandler.addRow(keyName,localeKey);
+            });
+            //dojoParser.parse(PWM_MAIN.getObject('challengeLocaleDialogDiv'));
+            for (var iteration in multiValues) {
+                (function(rowKey) {
+                    var inputID = "value-" + keyName + "-" + localeName + "-" + rowKey;
+                    UILibrary.manageNumericInput('button-minLength-' + inputID, function(value){
+                        PWM_VAR['clientSettingCache'][keyName][localeKey][rowKey]['minLength'] = value;
+                    })
+                    UILibrary.manageNumericInput('button-maxLength-' + inputID, function(value){
+                        PWM_VAR['clientSettingCache'][keyName][localeKey][rowKey]['maxLength'] = value;
+                    })
+                    UILibrary.manageNumericInput('button-maxQuestionCharsInAnswer-' + inputID, function(value){
+                        PWM_VAR['clientSettingCache'][keyName][localeKey][rowKey]['maxQuestionCharsInAnswer'] = value;
+                    })
+
+                    // question text
+                    var processQuestion = function() {
+                        var isAdminDefined = multiValues[rowKey]['adminDefined'];
+                        PWM_MAIN.getObject(inputID).value = isAdminDefined ? multiValues[rowKey]['text'] : '[User Defined]';
+                        PWM_MAIN.getObject(inputID).disabled = !isAdminDefined;
+                    };
+                    processQuestion();
+                    PWM_MAIN.addEventHandler(inputID, 'input', function () {
+                        //if (!multiValues[rowKey]['adminDefined']) {
+                        PWM_VAR['clientSettingCache'][keyName][localeKey][rowKey]['text'] = PWM_MAIN.getObject(inputID).value;
+                        //}
+                    });
+
+                    // admin defined checkbox
+                    PWM_MAIN.getObject('value-adminDefined-' + inputID).disabled = false;
+                    PWM_MAIN.getObject('value-adminDefined-' + inputID).checked = multiValues[rowKey]['adminDefined'];
+                    PWM_MAIN.addEventHandler('value-adminDefined-' + inputID,'change',function(){
+                        var checked = PWM_MAIN.getObject('value-adminDefined-' + inputID).checked;
+                        multiValues[rowKey]['adminDefined'] = checked;
+                        processQuestion();
+                    });
+
+                    // wordlist checkbox
+                    PWM_MAIN.getObject('value-wordlist-' + inputID).disabled = false;
+                    PWM_MAIN.getObject('value-wordlist-' + inputID).checked = multiValues[rowKey]['enforceWordlist'];
+                    PWM_MAIN.addEventHandler('value-wordlist-' + inputID,'change',function(){
+                        var checked = PWM_MAIN.getObject('value-wordlist-' + inputID).checked;
+                        multiValues[rowKey]['enforceWordlist'] = checked;
+                    });
+
+                    // delete row
+                    PWM_MAIN.addEventHandler('button-deleteRow-' + inputID, 'click', function () {
+                        ChallengeSettingHandler.deleteRow(keyName, localeKey, rowKey);
+                    });
+
+                }(iteration));
+            }
+
+        },okAction:function(){
+            ChallengeSettingHandler.write(keyName);
+            ChallengeSettingHandler.draw(keyName);
+        }});
+
+
+};
+
+ChallengeSettingHandler.deleteLocale = function(keyName,localeKey) {
+    PWM_MAIN.showConfirmDialog({
+        text: 'Are you sure you want to remove all the questions for the <i>' + localeKey + '</i> locale?',
+        okAction:function(){
+            PWM_MAIN.showWaitDialog({loadFunction:function(){
+                delete PWM_VAR['clientSettingCache'][keyName][localeKey];
+                PWM_CFGEDIT.writeSetting(keyName, PWM_VAR['clientSettingCache'][keyName],function(){
+                    PWM_MAIN.closeWaitDialog();
+                    ChallengeSettingHandler.init(keyName);
+                });
+            }});
+        }
+    });
+};
+
+ChallengeSettingHandler.toggleAdminDefinedRow = function(toggleElement,inputID,keyName,localeKey,rowKey) {
+    require(["dojo","dijit/registry"],function(dojo,registry){
+        var currentSetting = toggleElement.checked;
+        PWM_VAR['clientSettingCache'][keyName][localeKey][rowKey]['adminDefined'] = currentSetting;
+        var inputElement = registry.byId(inputID);
+        if (currentSetting) {
+            inputElement.set('disabled',false);
+            inputElement.set('value','Question');
+        } else {
+            inputElement.set('disabled',true);
+            inputElement.set('value','[User Defined]');
+            PWM_VAR['clientSettingCache'][keyName][localeKey][rowKey]['text'] = '';
+        }
+    });
+};
+
+ChallengeSettingHandler.deleteRow = function(keyName, localeKey, rowName) {
+    PWM_MAIN.showConfirmDialog({
+        okAction:function(){
+            PWM_MAIN.showWaitDialog({loadFunction:function(){
+                delete PWM_VAR['clientSettingCache'][keyName][localeKey][rowName];
+                ChallengeSettingHandler.write(keyName,function(){
+                    ChallengeSettingHandler.editLocale(keyName, localeKey);
+                });
+            }})
+        }
+    });
+};
+
+ChallengeSettingHandler.addRow = function(keyName, localeKey) {
+    PWM_MAIN.showWaitDialog({
+        loadFunction:function(){
+            var newValues = PWM_MAIN.copyObject(ChallengeSettingHandler.defaultItem);
+            PWM_VAR['clientSettingCache'][keyName][localeKey].push(newValues);
+            ChallengeSettingHandler.write(keyName,function(){
+                ChallengeSettingHandler.editLocale(keyName, localeKey);
+            });
+        }
+    });
+};
+
+ChallengeSettingHandler.write = function(keyName, nextFunction) {
+    PWM_CFGEDIT.writeSetting(keyName, PWM_VAR['clientSettingCache'][keyName], nextFunction);
+};

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

@@ -1996,313 +1996,6 @@ BooleanHandler.toggle = function(keyName,widget) {
     PWM_CFGEDIT.writeSetting(keyName,widget.checked);
 };
 
-// -------------------------- challenge handler ------------------------------------
-
-var ChallengeSettingHandler = {};
-ChallengeSettingHandler.defaultItem = {text:'Question',minLength:4,maxLength:200,adminDefined:true,enforceWordlist:true,maxQuestionCharsInAnswer:3};
-
-ChallengeSettingHandler.init = function(settingKey) {
-    var parentDiv = "table_setting_" + settingKey;
-    console.log('ChallengeSettingHandler init for ' + settingKey);
-    PWM_CFGEDIT.clearDivElements(parentDiv, true);
-    PWM_CFGEDIT.readSetting(settingKey, function(resultValue) {
-        PWM_VAR['clientSettingCache'][settingKey] = resultValue;
-        if (PWM_MAIN.JSLibrary.isEmpty(resultValue)) {
-            var htmlBody = '<button class="btn" id="button-addValue-' + settingKey + '">';
-            htmlBody += '<span class="btn-icon pwm-icon pwm-icon-plus-square"></span>Add Value';
-            htmlBody += '</button>';
-
-            var parentDivElement = PWM_MAIN.getObject(parentDiv);
-            parentDivElement.innerHTML = htmlBody;
-
-            PWM_MAIN.addEventHandler('button-addValue-' + settingKey,'click',function(){
-                PWM_VAR['clientSettingCache'][settingKey] = {};
-                PWM_VAR['clientSettingCache'][settingKey][''] = [];
-                PWM_VAR['clientSettingCache'][settingKey][''].push(ChallengeSettingHandler.defaultItem);
-                ChallengeSettingHandler.write(settingKey,function(){
-                    ChallengeSettingHandler.init(settingKey);
-                });
-            });
-        } else {
-            ChallengeSettingHandler.draw(settingKey);
-        }
-    });
-};
-
-ChallengeSettingHandler.draw = function(settingKey) {
-    var parentDiv = "table_setting_" + settingKey;
-    var resultValue = PWM_VAR['clientSettingCache'][settingKey];
-    var parentDivElement = PWM_MAIN.getObject(parentDiv);
-    var bodyText = '<div class="footnote">Click on challenge questions to edit questions and policies</div>';
-    PWM_CFGEDIT.clearDivElements(parentDiv, false);
-    for (var localeName in resultValue) {
-        (function(localeKey) {
-            var multiValues = resultValue[localeKey];
-            var rowCount = PWM_MAIN.JSLibrary.itemCount(multiValues);
-
-            bodyText += '<table class="noborder"><tr><td>';
-            bodyText += '<table class="setting-challenge-question-summary">';
-            var localeLabel = localeName == '' ? 'Default Locale' : PWM_GLOBAL['localeInfo'][localeName] + " (" + localeName + ")";
-            if (PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][settingKey]) > 1) {
-                bodyText += '<tr><td class="title" style="font-size:100%; font-weight:normal">' + localeLabel + '</td></tr>';
-            }
-
-            bodyText += '<tr>';
-            bodyText += '<td style="width:100%" id="button-edit-' + settingKey + '-' + localeKey + '">';
-            if (rowCount > 0) {
-                for (var iteration in multiValues) {
-                    var id = 'panel-value-' + settingKey + '-' + localeKey + '-' + iteration;
-                    bodyText += '<div style="text-overflow:ellipsis; white-space:nowrap; overflow:hidden" id="' + id + '">text</div>';
-                }
-            } else {
-                bodyText += '[No Questions]';
-            }
-            bodyText += '</td></tr>';
-
-            bodyText += '</table></td><td class="noborder" style="width:20px; vertical-align:top">';
-            if (localeName != '' || PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][settingKey]) < 2) { // add remove locale x
-                bodyText += '<div id="button-deleteRow-' + settingKey + '-' + localeKey + '" style="vertical-align:top" class="delete-row-icon action-icon pwm-icon pwm-icon-times"></div>';
-            }
-            bodyText += '</td></tr></table><br/>';
-        }(localeName));
-    }
-    parentDivElement.innerHTML = bodyText;
-
-    var addLocaleFunction = function(localeValue) {
-        if (localeValue in PWM_VAR['clientSettingCache'][settingKey]) {
-            PWM_MAIN.showDialog({title:PWM_MAIN.showString('Title_Error'),text:'Locale <i>' + localeValue + '</i> is already present.'});
-        } else {
-            PWM_VAR['clientSettingCache'][settingKey][localeValue] = [];
-            PWM_VAR['clientSettingCache'][settingKey][localeValue][0] = ChallengeSettingHandler.defaultItem;
-            ChallengeSettingHandler.write(settingKey, function(){
-                ChallengeSettingHandler.init(settingKey);
-            });
-        }
-    };
-    var tableElement = document.createElement("div");
-    parentDivElement.appendChild(tableElement);
-
-    UILibrary.addAddLocaleButtonRow(tableElement, settingKey, addLocaleFunction, Object.keys(resultValue));
-
-    for (var localeName in resultValue) {
-        (function(localeKey) {
-            PWM_MAIN.addEventHandler('button-edit-' + settingKey + '-' + localeKey,'click',function(){
-                ChallengeSettingHandler.editLocale(settingKey,localeKey);
-            });
-
-            var multiValues = resultValue[localeKey];
-            var rowCount = PWM_MAIN.JSLibrary.itemCount(multiValues);
-            if (rowCount > 0) {
-                for (var iteration in multiValues) {
-                    (function (rowKey) {
-                        var id = 'panel-value-' + settingKey + '-' + localeKey + '-' + iteration;
-                        var questionText = multiValues[rowKey]['text'];
-                        var adminDefined = multiValues[rowKey]['adminDefined'];
-                        var output = (adminDefined ? questionText : '[User Defined]');
-                        UILibrary.addTextValueToElement(id,output);
-                    }(iteration));
-                }
-            }
-
-            PWM_MAIN.addEventHandler('button-deleteRow-' + settingKey + '-' + localeKey,'click',function(){
-                ChallengeSettingHandler.deleteLocale(settingKey, localeKey)
-            });
-        }(localeName));
-    }
-
-};
-
-ChallengeSettingHandler.editLocale = function(keyName, localeKey) {
-    var localeDisplay = localeKey == "" ? "Default" : localeKey;
-    var dialogBody = '<div id="challengeLocaleDialogDiv" style="max-height:500px; overflow-x: auto">';
-
-    var localeName = localeKey;
-
-    var resultValue = PWM_VAR['clientSettingCache'][keyName];
-
-    var multiValues = resultValue[localeName];
-
-    for (var iteration in multiValues) {
-        (function(rowKey) {
-            dialogBody += '<table class="noborder">';
-            dialogBody += '<tr><td style="width: 15px" class="noborder">' + (parseInt(iteration) + 1) + '</td><td class="setting_outline">';
-            dialogBody += '<table class="noborder" style="margin:0"><tr>';
-            dialogBody += '<td colspan="200" style="border-width: 0;">';
-
-            var inputID = "value-" + keyName + "-" + localeName + "-" + rowKey;
-            PWM_MAIN.clearDijitWidget(inputID);
-
-            dialogBody += '<input class="configStringInput" id="' + inputID + '" style="width: 700px" required="required" disabled value="Loading"/>';
-
-            dialogBody += '</td>';
-            dialogBody += '</tr>';
-
-            dialogBody += '<tr><td>';
-
-            dialogBody += '<label class="checkboxWrapper"><input type="checkbox" id="value-adminDefined-' + inputID + '" disabled/>Admin Defined</label>';
-
-            dialogBody += '</td><td>';
-
-            dialogBody += '<input type="number" id="button-minLength-' + inputID + '" style="width:50px" class="configNumericInput" min="1" max="255" value="' + multiValues[rowKey]['minLength'] + '"/>';
-            dialogBody += '<br/>Min Length';
-
-            dialogBody += '</td><td>';
-            dialogBody += '<input type="number" id="button-maxLength-' + inputID + '" style="width:50px" class="configNumericInput" min="1" max="255" value="' + multiValues[rowKey]['maxLength'] + '"/>';
-            dialogBody += '<br/>Max Length';
-
-            dialogBody += '</td><td>';
-            dialogBody += '<input type="number" id="button-maxQuestionCharsInAnswer-' + inputID + '" style="width:50px" class="configNumericInput" min="0" max="100" value="' + multiValues[rowKey]['maxQuestionCharsInAnswer'] + '"/>';
-            dialogBody += '<br/>Max Question Characters';
-
-            dialogBody += '</td><td>';
-            dialogBody += '<label class="checkboxWrapper"><input type="checkbox" id="value-wordlist-' + inputID + '" disabled/>Apply Word List</label>';
-
-            dialogBody += '</td></tr>';
-            dialogBody += '</table></td><td class="noborder" style="vertical-align: top">';
-            if (PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][keyName][localeKey]) > 1) { // add remove locale x
-
-                dialogBody += '<div class="delete-row-icon action-icon pwm-icon pwm-icon-times" id="button-deleteRow-' + inputID + '"/>';
-            }
-
-            dialogBody += '</td></tr></table>';
-            dialogBody += '<br/>';
-
-        }(iteration));
-    }
-
-    dialogBody += '</div>';
-    dialogBody += '<br/>';
-
-    var dialogTitle = PWM_SETTINGS['settings'][keyName]['label'] + ' - ' + localeDisplay + ' Locale';
-    PWM_MAIN.showDialog({
-        title:dialogTitle,
-        buttonHtml:'<button class="btn" id="button-addValue"><span class="btn-icon pwm-icon pwm-icon-plus-square"></span>Add Value</button>',
-        text:dialogBody,
-        showClose:false,
-        dialogClass:'wide',
-        loadFunction:function(){
-            PWM_MAIN.addEventHandler('button-addValue','click',function(){
-                ChallengeSettingHandler.addRow(keyName,localeKey);
-            });
-            //dojoParser.parse(PWM_MAIN.getObject('challengeLocaleDialogDiv'));
-            for (var iteration in multiValues) {
-                (function(rowKey) {
-                    var inputID = "value-" + keyName + "-" + localeName + "-" + rowKey;
-                    UILibrary.manageNumericInput('button-minLength-' + inputID, function(value){
-                        PWM_VAR['clientSettingCache'][keyName][localeKey][rowKey]['minLength'] = value;
-                    })
-                    UILibrary.manageNumericInput('button-maxLength-' + inputID, function(value){
-                        PWM_VAR['clientSettingCache'][keyName][localeKey][rowKey]['maxLength'] = value;
-                    })
-                    UILibrary.manageNumericInput('button-maxQuestionCharsInAnswer-' + inputID, function(value){
-                        PWM_VAR['clientSettingCache'][keyName][localeKey][rowKey]['maxQuestionCharsInAnswer'] = value;
-                    })
-
-                    // question text
-                    var processQuestion = function() {
-                        var isAdminDefined = multiValues[rowKey]['adminDefined'];
-                        PWM_MAIN.getObject(inputID).value = isAdminDefined ? multiValues[rowKey]['text'] : '[User Defined]';
-                        PWM_MAIN.getObject(inputID).disabled = !isAdminDefined;
-                    };
-                    processQuestion();
-                    PWM_MAIN.addEventHandler(inputID, 'input', function () {
-                        //if (!multiValues[rowKey]['adminDefined']) {
-                        PWM_VAR['clientSettingCache'][keyName][localeKey][rowKey]['text'] = PWM_MAIN.getObject(inputID).value;
-                        //}
-                    });
-
-                    // admin defined checkbox
-                    PWM_MAIN.getObject('value-adminDefined-' + inputID).disabled = false;
-                    PWM_MAIN.getObject('value-adminDefined-' + inputID).checked = multiValues[rowKey]['adminDefined'];
-                    PWM_MAIN.addEventHandler('value-adminDefined-' + inputID,'change',function(){
-                        var checked = PWM_MAIN.getObject('value-adminDefined-' + inputID).checked;
-                        multiValues[rowKey]['adminDefined'] = checked;
-                        processQuestion();
-                    });
-
-                    // wordlist checkbox
-                    PWM_MAIN.getObject('value-wordlist-' + inputID).disabled = false;
-                    PWM_MAIN.getObject('value-wordlist-' + inputID).checked = multiValues[rowKey]['enforceWordlist'];
-                    PWM_MAIN.addEventHandler('value-wordlist-' + inputID,'change',function(){
-                        var checked = PWM_MAIN.getObject('value-wordlist-' + inputID).checked;
-                        multiValues[rowKey]['enforceWordlist'] = checked;
-                    });
-
-                    // delete row
-                    PWM_MAIN.addEventHandler('button-deleteRow-' + inputID, 'click', function () {
-                        ChallengeSettingHandler.deleteRow(keyName, localeKey, rowKey);
-                    });
-
-                }(iteration));
-            }
-
-        },okAction:function(){
-            ChallengeSettingHandler.write(keyName);
-            ChallengeSettingHandler.draw(keyName);
-        }});
-
-
-};
-
-ChallengeSettingHandler.deleteLocale = function(keyName,localeKey) {
-    PWM_MAIN.showConfirmDialog({
-        text: 'Are you sure you want to remove all the questions for the <i>' + localeKey + '</i> locale?',
-        okAction:function(){
-            PWM_MAIN.showWaitDialog({loadFunction:function(){
-                delete PWM_VAR['clientSettingCache'][keyName][localeKey];
-                PWM_CFGEDIT.writeSetting(keyName, PWM_VAR['clientSettingCache'][keyName],function(){
-                    PWM_MAIN.closeWaitDialog();
-                    ChallengeSettingHandler.init(keyName);
-                });
-            }});
-        }
-    });
-};
-
-ChallengeSettingHandler.toggleAdminDefinedRow = function(toggleElement,inputID,keyName,localeKey,rowKey) {
-    require(["dojo","dijit/registry"],function(dojo,registry){
-        var currentSetting = toggleElement.checked;
-        PWM_VAR['clientSettingCache'][keyName][localeKey][rowKey]['adminDefined'] = currentSetting;
-        var inputElement = registry.byId(inputID);
-        if (currentSetting) {
-            inputElement.set('disabled',false);
-            inputElement.set('value','Question');
-        } else {
-            inputElement.set('disabled',true);
-            inputElement.set('value','[User Defined]');
-            PWM_VAR['clientSettingCache'][keyName][localeKey][rowKey]['text'] = '';
-        }
-    });
-};
-
-ChallengeSettingHandler.deleteRow = function(keyName, localeKey, rowName) {
-    PWM_MAIN.showConfirmDialog({
-        okAction:function(){
-            PWM_MAIN.showWaitDialog({loadFunction:function(){
-                delete PWM_VAR['clientSettingCache'][keyName][localeKey][rowName];
-                ChallengeSettingHandler.write(keyName,function(){
-                    ChallengeSettingHandler.editLocale(keyName, localeKey);
-                });
-            }})
-        }
-    });
-};
-
-ChallengeSettingHandler.addRow = function(keyName, localeKey) {
-    PWM_MAIN.showWaitDialog({
-        loadFunction:function(){
-            var newValues = PWM_MAIN.copyObject(ChallengeSettingHandler.defaultItem);
-            PWM_VAR['clientSettingCache'][keyName][localeKey].push(newValues);
-            ChallengeSettingHandler.write(keyName,function(){
-                ChallengeSettingHandler.editLocale(keyName, localeKey);
-            });
-        }
-    });
-};
-
-ChallengeSettingHandler.write = function(keyName, nextFunction) {
-    PWM_CFGEDIT.writeSetting(keyName, PWM_VAR['clientSettingCache'][keyName], nextFunction);
-};
 
 // -------------------------- user permission handler ------------------------------------
 

+ 7 - 7
server/src/main/webapp/public/resources/js/configmanager.js

@@ -46,7 +46,7 @@ PWM_CONFIG.lockConfiguration=function() {
 };
 
 PWM_CONFIG.waitForRestart=function(options) {
-    var pingCycleTimeMs = 3 * 1000;
+    var pingCycleTimeMs = 1000;
     var maxWaitTimeMs = 120 * 1000;
 
     PWM_VAR['cancelHeartbeatCheck'] = true;
@@ -68,7 +68,7 @@ PWM_CONFIG.waitForRestart=function(options) {
         }
     }
 
-    var declaredStartupTime = PWM_GLOBAL['startupTime'];
+    var originalRuntimeNonce = PWM_GLOBAL['runtimeNonce'];
 
     console.log("beginning request to determine application status: ");
     var loadFunction = function(data) {
@@ -76,9 +76,9 @@ PWM_CONFIG.waitForRestart=function(options) {
             if (data['error']) {
                 console.log('data error reading /ping endpoint: ' + JSON.stringify(data));
             } else {
-                var serverStartTime = data['data']['time'];
-                console.log("comparing declared timestamp=" + declaredStartupTime + " and xhr read timestamp=" + serverStartTime);
-                if (serverStartTime !== declaredStartupTime) {
+                var currentNonce = data['data']['runtimeNonce'];
+                console.log("comparing declared nonce=" + originalRuntimeNonce + " and xhr read nonce=" + currentNonce);
+                if (currentNonce !== originalRuntimeNonce) {
                     console.log("change detected, restarting page");
                     restartFunction();
                     return;
@@ -87,7 +87,7 @@ PWM_CONFIG.waitForRestart=function(options) {
                 }
             }
         } catch (e) {
-            console.log("can't read current server startupTime, will retry detection (current error: " + e + ")");
+            console.log("can't read current server nonce, will retry detection (current error: " + e + ")");
         }
         setTimeout(function() {
             PWM_CONFIG.waitForRestart(options)
@@ -99,7 +99,7 @@ PWM_CONFIG.waitForRestart=function(options) {
         }, pingCycleTimeMs);
         console.log('Waiting for server restart, unable to contact server: ' + error);
     };
-    var url = PWM_GLOBAL['url-restservice'] + "/app-data/ping";
+    var url = PWM_GLOBAL['url-restservice'] + "/ping";
     PWM_MAIN.ajaxRequest(url,loadFunction,{errorFunction:errorFunction,method:'GET'});
 };