Bläddra i källkod

Merge branch 'master' into emailregex

pandaholic 6 år sedan
förälder
incheckning
5bab166542
41 ändrade filer med 511 tillägg och 268 borttagningar
  1. 1 0
      .gitignore
  2. 2 0
      build/checkstyle-import.xml
  3. 1 1
      client/package.json
  4. 7 5
      client/src/components/changepassword/success-change-password.component.html
  5. 7 2
      client/src/components/changepassword/success-change-password.controller.ts
  6. 1 0
      client/src/modules/peoplesearch/person-details-dialog.component.scss
  7. 1 1
      docker/pom.xml
  8. 2 2
      pom.xml
  9. 2 0
      server/src/main/java/password/pwm/AppProperty.java
  10. 15 1
      server/src/main/java/password/pwm/PwmApplication.java
  11. 1 0
      server/src/main/java/password/pwm/PwmConstants.java
  12. 1 0
      server/src/main/java/password/pwm/bean/LoginInfoBean.java
  13. 2 0
      server/src/main/java/password/pwm/config/PwmSetting.java
  14. 105 27
      server/src/main/java/password/pwm/http/ContextManager.java
  15. 3 3
      server/src/main/java/password/pwm/http/HttpEventManager.java
  16. 4 1
      server/src/main/java/password/pwm/http/PwmRequestAttribute.java
  17. 8 6
      server/src/main/java/password/pwm/http/PwmSession.java
  18. 3 124
      server/src/main/java/password/pwm/http/bean/SetupResponsesBean.java
  19. 1 1
      server/src/main/java/password/pwm/http/filter/AuthenticationFilter.java
  20. 47 29
      server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java
  21. 35 15
      server/src/main/java/password/pwm/http/servlet/SetupOtpServlet.java
  22. 45 1
      server/src/main/java/password/pwm/http/servlet/SetupResponsesServlet.java
  23. 3 1
      server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java
  24. 29 1
      server/src/main/java/password/pwm/http/servlet/configmanager/DebugItemGenerator.java
  25. 1 1
      server/src/main/java/password/pwm/http/tag/ErrorMessageTag.java
  26. 8 0
      server/src/main/java/password/pwm/svc/PwmServiceManager.java
  27. 1 1
      server/src/main/java/password/pwm/svc/cluster/ClusterMachine.java
  28. 118 0
      server/src/main/java/password/pwm/util/MBeanUtility.java
  29. 4 4
      server/src/main/java/password/pwm/util/RandomPasswordGenerator.java
  30. 4 3
      server/src/main/java/password/pwm/util/secure/PwmRandom.java
  31. 2 0
      server/src/main/resources/password/pwm/AppProperty.properties
  32. 5 0
      server/src/main/resources/password/pwm/config/PwmSetting.xml
  33. 1 0
      server/src/main/resources/password/pwm/i18n/Admin.properties
  34. 2 0
      server/src/main/resources/password/pwm/i18n/PwmSetting.properties
  35. 9 9
      server/src/test/java/password/pwm/http/client/PwmHttpClientTest.java
  36. 12 1
      webapp/src/main/webapp/WEB-INF/jsp/admin-dashboard.jsp
  37. 2 2
      webapp/src/main/webapp/WEB-INF/jsp/admin-logview.jsp
  38. 1 1
      webapp/src/main/webapp/WEB-INF/jsp/fragment/form.jsp
  39. 2 2
      webapp/src/main/webapp/WEB-INF/jsp/newuser-profilechoice.jsp
  40. 2 23
      webapp/src/main/webapp/WEB-INF/jsp/setupotpsecret.jsp
  41. 11 0
      webapp/src/main/webapp/WEB-INF/jsp/setupresponses.jsp

+ 1 - 0
.gitignore

@@ -20,3 +20,4 @@
 */target
 */target
 
 
 
 
+/target

+ 2 - 0
build/checkstyle-import.xml

@@ -60,6 +60,8 @@
     <!-- gson -->
     <!-- gson -->
     <allow pkg="com.google.gson"/>
     <allow pkg="com.google.gson"/>
 
 
+    <allow pkg="javax.management"/>
+
     <!-- to be removed/scoped -->
     <!-- to be removed/scoped -->
     <allow pkg="org.apache.http"/>
     <allow pkg="org.apache.http"/>
     <allow pkg="org.apache.commons"/>
     <allow pkg="org.apache.commons"/>

+ 1 - 1
client/package.json

@@ -13,7 +13,7 @@
         "test": "karma start test/karma.conf.js --mode=development",
         "test": "karma start test/karma.conf.js --mode=development",
         "test-single-run": "karma start test/karma.conf.js --mode=development --singleRun --no-auto-watch",
         "test-single-run": "karma start test/karma.conf.js --mode=development --singleRun --no-auto-watch",
         "start": "webpack-dev-server --mode=development --port 4000 --history-api-fallback --colors",
         "start": "webpack-dev-server --mode=development --port 4000 --history-api-fallback --colors",
-        "sync": "webpack --mode=production --output-path=../server/target/pwm-1.8.0-SNAPSHOT/public/resources/webjars/pwm-client --watch --progress --colors"
+        "sync": "webpack --mode=production --output-path=../webapp/target/pwm-1.8.0-SNAPSHOT/public/resources/webjars/pwm-client --watch --progress --colors"
     },
     },
     "author": "",
     "author": "",
     "license": "ISC",
     "license": "ISC",

+ 7 - 5
client/src/components/changepassword/success-change-password.component.html

@@ -28,11 +28,13 @@
 
 
         <div class="ias-dialog-content">
         <div class="ias-dialog-content">
             <p ng-bind="$ctrl.successMessage"></p>
             <p ng-bind="$ctrl.successMessage"></p>
-            <span ng-bind="'Field_NewPassword' | translate"></span>
-            <ias-button ng-click="$ctrl.togglePasswordMasked()" ng-if="$ctrl.maskPasswords">
-                {{ 'Button_Show' | translate }}
-            </ias-button>
-            <input ng-model="$ctrl.password" ng-hide="$ctrl.passwordMasked" readonly type="text" autofocus>
+            <div ng-if="$ctrl.displayNewPassword">
+                <span ng-bind="'Field_NewPassword' | translate"></span>
+                <ias-button ng-click="$ctrl.togglePasswordMasked()" ng-if="$ctrl.maskPasswords">
+                    {{ 'Button_Show' | translate }}
+                </ias-button>
+                <input ng-model="$ctrl.password" ng-hide="$ctrl.passwordMasked" readonly type="text" autofocus>
+            </div>
         </div>
         </div>
 
 
         <div class="ias-actions">
         <div class="ias-actions">

+ 7 - 2
client/src/components/changepassword/success-change-password.controller.ts

@@ -23,7 +23,7 @@
 
 
 import {IHelpDeskService } from '../../services/helpdesk.service';
 import {IHelpDeskService } from '../../services/helpdesk.service';
 import {IQService} from 'angular';
 import {IQService} from 'angular';
-import {IHelpDeskConfigService} from '../../services/helpdesk-config.service';
+import {IHelpDeskConfigService, PASSWORD_UI_MODES} from '../../services/helpdesk-config.service';
 
 
 export interface IChangePasswordSuccess {
 export interface IChangePasswordSuccess {
     password: string;
     password: string;
@@ -36,6 +36,7 @@ export default class SuccessChangePasswordController {
     password: string;
     password: string;
     passwordMasked: boolean;
     passwordMasked: boolean;
     successMessage: string;
     successMessage: string;
+    displayNewPassword: boolean;
 
 
     static $inject = [
     static $inject = [
         '$q',
         '$q',
@@ -58,12 +59,16 @@ export default class SuccessChangePasswordController {
 
 
         let promise = this.$q.all([
         let promise = this.$q.all([
             this.configService.getClearResponsesSetting(),
             this.configService.getClearResponsesSetting(),
-            this.configService.maskPasswordsEnabled()
+            this.configService.maskPasswordsEnabled(),
+            this.configService.getPasswordUiMode()
         ]);
         ]);
         promise.then((result) => {
         promise.then((result) => {
             this.clearResponsesSetting = result[0];
             this.clearResponsesSetting = result[0];
             this.maskPasswords = result[1];
             this.maskPasswords = result[1];
             this.passwordMasked = this.maskPasswords;
             this.passwordMasked = this.maskPasswords;
+
+            // If it's random, don't display the new password
+            this.displayNewPassword = (result[2] !== PASSWORD_UI_MODES.RANDOM);
         });
         });
     }
     }
 
 

+ 1 - 0
client/src/modules/peoplesearch/person-details-dialog.component.scss

@@ -28,6 +28,7 @@
 
 
 .person-details-dialog {
 .person-details-dialog {
   text-align: left;
   text-align: left;
+  overflow: hidden;
 
 
   .ias-dialog-container {
   .ias-dialog-container {
     padding: 0;
     padding: 0;

+ 1 - 1
docker/pom.xml

@@ -42,7 +42,7 @@
                                     <jvmFlag>-Xms1g</jvmFlag>
                                     <jvmFlag>-Xms1g</jvmFlag>
                                     <jvmFlag>-Xmx1g</jvmFlag>
                                     <jvmFlag>-Xmx1g</jvmFlag>
                                 </jvmFlags>
                                 </jvmFlags>
-                                <mainClass>password.pwm.onejar.TomcatOnejarRunner</mainClass>
+                                <mainClass>password.pwm.onejar.OnejarMain</mainClass>
                                 <args>
                                 <args>
                                     <arg>-applicationPath</arg>
                                     <arg>-applicationPath</arg>
                                     <arg>/config</arg>
                                     <arg>/config</arg>

+ 2 - 2
pom.xml

@@ -240,7 +240,7 @@
                     <dependency>
                     <dependency>
                         <groupId>com.github.spotbugs</groupId>
                         <groupId>com.github.spotbugs</groupId>
                         <artifactId>spotbugs</artifactId>
                         <artifactId>spotbugs</artifactId>
-                        <version>3.1.6</version>
+                        <version>3.1.7</version>
                     </dependency>
                     </dependency>
                 </dependencies>
                 </dependencies>
                 <configuration>
                 <configuration>
@@ -272,7 +272,7 @@
         <dependency>
         <dependency>
             <groupId>com.github.spotbugs</groupId>
             <groupId>com.github.spotbugs</groupId>
             <artifactId>spotbugs-annotations</artifactId>
             <artifactId>spotbugs-annotations</artifactId>
-            <version>3.1.6</version>
+            <version>3.1.7</version>
             <scope>provided</scope>
             <scope>provided</scope>
         </dependency>
         </dependency>
     </dependencies>
     </dependencies>

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

@@ -35,6 +35,8 @@ public enum AppProperty
     ACTIVATE_USER_TOKEN_AUTO_SELECT_DEST            ( "activateUser.token.autoSelectSingleDestination" ),
     ACTIVATE_USER_TOKEN_AUTO_SELECT_DEST            ( "activateUser.token.autoSelectSingleDestination" ),
     APPLICATION_FILELOCK_FILENAME                   ( "application.fileLock.filename" ),
     APPLICATION_FILELOCK_FILENAME                   ( "application.fileLock.filename" ),
     APPLICATION_FILELOCK_WAIT_SECONDS               ( "application.fileLock.waitSeconds" ),
     APPLICATION_FILELOCK_WAIT_SECONDS               ( "application.fileLock.waitSeconds" ),
+    APPLICATION_READ_APP_LOCK_MAX_WAIT_MS           ( "application.readAppLock.maxWaitMs" ),
+    APPLICATION_RESTART_MAX_REQUEST_WAIT_MS         ( "application.restart.maxRequestWaitMs" ),
     APPLICATION_WORDLIST_RETRY_SECONDS              ( "application.wordlistRetryImportSeconds" ),
     APPLICATION_WORDLIST_RETRY_SECONDS              ( "application.wordlistRetryImportSeconds" ),
     AUDIT_EVENTS_EMAILFROM                          ( "audit.events.emailFrom" ),
     AUDIT_EVENTS_EMAILFROM                          ( "audit.events.emailFrom" ),
     AUDIT_EVENTS_EMAILSUBJECT                       ( "audit.events.emailSubject" ),
     AUDIT_EVENTS_EMAILSUBJECT                       ( "audit.events.emailSubject" ),

+ 15 - 1
server/src/main/java/password/pwm/PwmApplication.java

@@ -60,6 +60,7 @@ import password.pwm.svc.token.TokenService;
 import password.pwm.svc.wordlist.SeedlistManager;
 import password.pwm.svc.wordlist.SeedlistManager;
 import password.pwm.svc.wordlist.SharedHistoryManager;
 import password.pwm.svc.wordlist.SharedHistoryManager;
 import password.pwm.svc.wordlist.WordlistManager;
 import password.pwm.svc.wordlist.WordlistManager;
+import password.pwm.util.MBeanUtility;
 import password.pwm.util.PasswordData;
 import password.pwm.util.PasswordData;
 import password.pwm.util.cli.commands.ExportHttpsTomcatConfigCommand;
 import password.pwm.util.cli.commands.ExportHttpsTomcatConfigCommand;
 import password.pwm.util.db.DatabaseAccessor;
 import password.pwm.util.db.DatabaseAccessor;
@@ -96,6 +97,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
 
 
 /**
 /**
  * A repository for objects common to the servlet context.  A singleton
  * A repository for objects common to the servlet context.  A singleton
@@ -152,6 +154,7 @@ public class PwmApplication
     private final Instant startupTime = Instant.now();
     private final Instant startupTime = Instant.now();
     private Instant installTime = Instant.now();
     private Instant installTime = Instant.now();
     private ErrorInformation lastLocalDBFailure;
     private ErrorInformation lastLocalDBFailure;
+    private final AtomicInteger inprogressRequests = new AtomicInteger( 0 );
 
 
     private final PwmEnvironment pwmEnvironment;
     private final PwmEnvironment pwmEnvironment;
 
 
@@ -396,6 +399,8 @@ public class PwmApplication
             }
             }
         }
         }
 
 
+        MBeanUtility.registerMBean( this );
+
         LOGGER.trace( "completed post init tasks in " + TimeDuration.fromCurrent( startTime ).asCompactString() );
         LOGGER.trace( "completed post init tasks in " + TimeDuration.fromCurrent( startTime ).asCompactString() );
     }
     }
 
 
@@ -671,7 +676,9 @@ public class PwmApplication
 
 
         if ( newInstanceID == null || newInstanceID.length() < 1 )
         if ( newInstanceID == null || newInstanceID.length() < 1 )
         {
         {
-            newInstanceID = Long.toHexString( pwmApplication.getSecureService().pwmRandom().nextLong() ).toUpperCase();
+            final PwmRandom pwmRandom = PwmRandom.getInstance();
+            newInstanceID = Long.toHexString( pwmRandom.nextLong() ).toUpperCase();
+
             LOGGER.info( "generated new random instanceID " + newInstanceID );
             LOGGER.info( "generated new random instanceID " + newInstanceID );
 
 
             if ( localDB != null )
             if ( localDB != null )
@@ -775,6 +782,8 @@ public class PwmApplication
             }
             }
         }
         }
 
 
+        MBeanUtility.unregisterMBean( this );
+
         pwmServiceManager.shutdownAllServices();
         pwmServiceManager.shutdownAllServices();
 
 
         if ( localDBLogger != null )
         if ( localDBLogger != null )
@@ -984,6 +993,11 @@ public class PwmApplication
         }
         }
         return false;
         return false;
     }
     }
+
+    public AtomicInteger getInprogressRequests( )
+    {
+        return inprogressRequests;
+    }
 }
 }
 
 
 
 

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

@@ -116,6 +116,7 @@ public abstract class PwmConstants
 
 
     public static final String REQUEST_ATTR_FORGOTTEN_PW_USERINFO_CACHE = "ForgottenPw-UserInfoCache";
     public static final String REQUEST_ATTR_FORGOTTEN_PW_USERINFO_CACHE = "ForgottenPw-UserInfoCache";
     public static final String REQUEST_ATTR_FORGOTTEN_PW_AVAIL_TOKEN_DEST_CACHE = "ForgottenPw-AvailableTokenDestCache";
     public static final String REQUEST_ATTR_FORGOTTEN_PW_AVAIL_TOKEN_DEST_CACHE = "ForgottenPw-AvailableTokenDestCache";
+    public static final String REQUEST_ATTR_PWM_APPLICATION = "PwmApplication";
 
 
     public static final PwmHashAlgorithm SETTING_CHECKSUM_HASH_METHOD = PwmHashAlgorithm.SHA256;
     public static final PwmHashAlgorithm SETTING_CHECKSUM_HASH_METHOD = PwmHashAlgorithm.SHA256;
 
 

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

@@ -55,6 +55,7 @@ public class LoginInfoBean implements Serializable
     {
     {
         skipOtp,
         skipOtp,
         skipNewPw,
         skipNewPw,
+        skipSetupCr,
 
 
         // bypass sso
         // bypass sso
         noSso,
         noSso,

+ 2 - 0
server/src/main/java/password/pwm/config/PwmSetting.java

@@ -1152,6 +1152,8 @@ public enum PwmSetting
     // administration
     // administration
     QUERY_MATCH_PWM_ADMIN(
     QUERY_MATCH_PWM_ADMIN(
             "pwmAdmin.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.ADMINISTRATION ),
             "pwmAdmin.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.ADMINISTRATION ),
+    ADMIN_ALLOW_SKIP_FORCED_ACTIVITIES(
+            "pwmAdmin.allowSkipForcedActivities", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.ADMINISTRATION ),
 
 
 
 
     ENABLE_EXTERNAL_WEBSERVICES(
     ENABLE_EXTERNAL_WEBSERVICES(

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

@@ -41,7 +41,7 @@ import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmRandom;
 import password.pwm.util.secure.PwmRandom;
 
 
 import javax.servlet.ServletContext;
 import javax.servlet.ServletContext;
-import javax.servlet.http.HttpServletRequest;
+import javax.servlet.ServletRequest;
 import javax.servlet.http.HttpSession;
 import javax.servlet.http.HttpSession;
 import java.io.File;
 import java.io.File;
 import java.io.InputStream;
 import java.io.InputStream;
@@ -56,6 +56,8 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 
 public class ContextManager implements Serializable
 public class ContextManager implements Serializable
 {
 {
@@ -70,7 +72,9 @@ public class ContextManager implements Serializable
     private ErrorInformation startupErrorInformation;
     private ErrorInformation startupErrorInformation;
 
 
     private AtomicInteger restartCount = new AtomicInteger( 0 );
     private AtomicInteger restartCount = new AtomicInteger( 0 );
+    private TimeDuration readApplicationLockMaxWait = new TimeDuration( 5, TimeUnit.SECONDS );
     private final String instanceGuid;
     private final String instanceGuid;
+    private final Lock reloadLock = new ReentrantLock();
 
 
     private String contextPath;
     private String contextPath;
 
 
@@ -84,9 +88,19 @@ public class ContextManager implements Serializable
     }
     }
 
 
 
 
-    public static PwmApplication getPwmApplication( final HttpServletRequest request ) throws PwmUnrecoverableException
+    public static PwmApplication getPwmApplication( final ServletRequest request ) throws PwmUnrecoverableException
     {
     {
-        return getPwmApplication( request.getServletContext() );
+        final PwmApplication appInRequest = ( PwmApplication ) request.getAttribute( PwmConstants.REQUEST_ATTR_PWM_APPLICATION );
+        if ( appInRequest != null )
+        {
+            return appInRequest;
+        }
+
+        final PwmApplication pwmApplication = getPwmApplication( request.getServletContext() );
+        request.setAttribute( PwmConstants.REQUEST_ATTR_PWM_APPLICATION, pwmApplication );
+        return pwmApplication;
+
+
     }
     }
 
 
     public static PwmApplication getPwmApplication( final HttpSession session ) throws PwmUnrecoverableException
     public static PwmApplication getPwmApplication( final HttpSession session ) throws PwmUnrecoverableException
@@ -126,20 +140,37 @@ public class ContextManager implements Serializable
     public PwmApplication getPwmApplication( )
     public PwmApplication getPwmApplication( )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        if ( pwmApplication == null )
+        if ( pwmApplication != null )
         {
         {
-            final ErrorInformation errorInformation;
-            if ( startupErrorInformation != null )
+            try
             {
             {
-                errorInformation = startupErrorInformation;
+                final boolean hasLock = reloadLock.tryLock( readApplicationLockMaxWait.getTotalMilliseconds(), TimeUnit.MICROSECONDS );
+                if ( hasLock )
+                {
+                    return pwmApplication;
+                }
             }
             }
-            else
+            catch ( InterruptedException e )
             {
             {
-                errorInformation = new ErrorInformation( PwmError.ERROR_APP_UNAVAILABLE, "application is not yet available, please try again in a moment." );
+                LOGGER.warn( "getPwmApplication restartLock unexpectedly interrupted" );
+            }
+            finally
+            {
+                reloadLock.unlock();
             }
             }
-            throw new PwmUnrecoverableException( errorInformation );
         }
         }
-        return pwmApplication;
+
+        final ErrorInformation errorInformation;
+        if ( startupErrorInformation != null )
+        {
+            errorInformation = startupErrorInformation;
+        }
+        else
+        {
+            final String msg = "application is not yet available, please try again in a moment.";
+            errorInformation = new ErrorInformation( PwmError.ERROR_APP_UNAVAILABLE, msg );
+        }
+        throw new PwmUnrecoverableException( errorInformation );
     }
     }
 
 
     public void initialize( )
     public void initialize( )
@@ -232,6 +263,11 @@ public class ContextManager implements Serializable
             {
             {
                 reloadOnChange = Boolean.parseBoolean( pwmApplication.getConfig().readAppProperty( AppProperty.CONFIG_RELOAD_ON_CHANGE ) );
                 reloadOnChange = Boolean.parseBoolean( pwmApplication.getConfig().readAppProperty( AppProperty.CONFIG_RELOAD_ON_CHANGE ) );
                 fileScanFrequencyMs = Long.parseLong( pwmApplication.getConfig().readAppProperty( AppProperty.CONFIG_FILE_SCAN_FREQUENCY ) );
                 fileScanFrequencyMs = Long.parseLong( pwmApplication.getConfig().readAppProperty( AppProperty.CONFIG_FILE_SCAN_FREQUENCY ) );
+
+                this.readApplicationLockMaxWait = new TimeDuration(
+                        Long.parseLong( pwmApplication.getConfig().readAppProperty( AppProperty.APPLICATION_READ_APP_LOCK_MAX_WAIT_MS ) ),
+                        TimeUnit.MILLISECONDS
+                );
             }
             }
             if ( reloadOnChange )
             if ( reloadOnChange )
             {
             {
@@ -375,31 +411,73 @@ public class ContextManager implements Serializable
                 return;
                 return;
             }
             }
 
 
-            {
-                final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime );
-                LOGGER.info( "beginning application restart (" + timeDuration.asCompactString() + "), restart count=" + restartCount.incrementAndGet() );
-            }
-
+            reloadLock.lock();
             try
             try
             {
             {
-                shutdown();
+
+                waitForRequestsToComplete( pwmApplication );
+
+                {
+                    final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime );
+                    LOGGER.info( "beginning application restart (" + timeDuration.asCompactString() + "), restart count=" + restartCount.incrementAndGet() );
+                }
+
+                final Instant shutdownStartTime = Instant.now();
+                try
+                {
+                    shutdown();
+                }
+                catch ( Exception e )
+                {
+                    LOGGER.fatal( "unexpected error during shutdown: " + e.getMessage(), e );
+                }
+
+                {
+                    final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime );
+                    final TimeDuration shutdownDuration = TimeDuration.fromCurrent( shutdownStartTime );
+                    LOGGER.info( "application restart; shutdown completed, ("
+                            + shutdownDuration.asCompactString()
+                            + ") now starting new application instance ("
+                            + timeDuration.asCompactString() + ")" );
+                }
+                initialize();
+
+                {
+                    final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime );
+                    LOGGER.info( "application restart completed (" + timeDuration.asCompactString() + ")" );
+                }
             }
             }
-            catch ( Exception e )
+            finally
             {
             {
-                LOGGER.fatal( "unexpected error during shutdown: " + e.getMessage(), e );
+                reloadLock.unlock();
             }
             }
+        }
 
 
-            {
-                final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime );
-                LOGGER.info( "application restart; shutdown completed, now starting new application instance ("
-                        + timeDuration.asCompactString() + ")" );
-            }
-            initialize();
+        private void waitForRequestsToComplete( final PwmApplication pwmApplication )
+        {
+            final Instant startTime = Instant.now();
+            final TimeDuration maxRequestWaitTime = TimeDuration.of(
+                    Integer.parseInt( pwmApplication.getConfig().readAppProperty( AppProperty.APPLICATION_RESTART_MAX_REQUEST_WAIT_MS ) ),
+                    TimeUnit.SECONDS );
+            final int startingRequetsInProgress = pwmApplication.getInprogressRequests().get();
 
 
+            if ( startingRequetsInProgress == 0 )
             {
             {
-                final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime );
-                LOGGER.info( "application restart completed (" + timeDuration.asCompactString() + ")" );
+                return;
             }
             }
+
+            LOGGER.trace( "waiting up to " + maxRequestWaitTime.asCompactString()
+                    + " for " + startingRequetsInProgress  + " requests to complete." );
+            JavaHelper.pause(
+                    maxRequestWaitTime.getTotalMilliseconds(),
+                    10,
+                    o -> pwmApplication.getInprogressRequests().get() == 0
+            );
+
+            final int requestsInPrgoress = pwmApplication.getInprogressRequests().get();
+            final TimeDuration waitTime = TimeDuration.fromCurrent( startTime  );
+            LOGGER.trace( "after " + waitTime.asCompactString() + ", " + requestsInPrgoress
+                    + " requests in progress, proceeding with restart" );
         }
         }
     }
     }
 
 

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

@@ -62,7 +62,7 @@ public class HttpEventManager implements
             final PwmApplication pwmApplication = contextManager.getPwmApplication();
             final PwmApplication pwmApplication = contextManager.getPwmApplication();
             httpSession.setAttribute( PwmConstants.SESSION_ATTR_PWM_APP_NONCE, pwmApplication.getRuntimeNonce() );
             httpSession.setAttribute( PwmConstants.SESSION_ATTR_PWM_APP_NONCE, pwmApplication.getRuntimeNonce() );
 
 
-            if ( pwmApplication != null && pwmApplication.getStatisticsManager() != null )
+            if ( pwmApplication.getStatisticsManager() != null )
             {
             {
                 pwmApplication.getStatisticsManager().updateEps( EpsStatistic.SESSIONS, 1 );
                 pwmApplication.getStatisticsManager().updateEps( EpsStatistic.SESSIONS, 1 );
             }
             }
@@ -87,7 +87,7 @@ public class HttpEventManager implements
                 {
                 {
                     pwmSession.unauthenticateUser( null );
                     pwmSession.unauthenticateUser( null );
                 }
                 }
-                final PwmApplication pwmApplication = ContextManager.getPwmApplication( httpSession );
+                final PwmApplication pwmApplication = ContextManager.getPwmApplication( httpSession.getServletContext() );
                 if ( pwmApplication != null )
                 if ( pwmApplication != null )
                 {
                 {
                     pwmApplication.getSessionTrackService().removeSessionData( pwmSession );
                     pwmApplication.getSessionTrackService().removeSessionData( pwmSession );
@@ -169,7 +169,7 @@ public class HttpEventManager implements
             final HttpSession httpSession = event.getSession();
             final HttpSession httpSession = event.getSession();
             final PwmSession pwmSession = PwmSessionWrapper.readPwmSession( httpSession );
             final PwmSession pwmSession = PwmSessionWrapper.readPwmSession( httpSession );
             LOGGER.trace( pwmSession.getLabel(), "activating (de-passivating) session" );
             LOGGER.trace( pwmSession.getLabel(), "activating (de-passivating) session" );
-            final PwmApplication pwmApplication = ContextManager.getPwmApplication( httpSession );
+            final PwmApplication pwmApplication = ContextManager.getPwmApplication( httpSession.getServletContext() );
             if ( pwmApplication != null )
             if ( pwmApplication != null )
             {
             {
                 pwmApplication.getSessionTrackService().addSessionData( pwmSession );
                 pwmApplication.getSessionTrackService().addSessionData( pwmSession );

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

@@ -49,8 +49,11 @@ public enum PwmRequestAttribute
     AccountInfo,
     AccountInfo,
 
 
     SetupResponses_ResponseInfo,
     SetupResponses_ResponseInfo,
+    SetupResponses_AllowSkip,
 
 
     SetupOtp_QrCodeValue,
     SetupOtp_QrCodeValue,
+    SetupOtp_AllowSkip,
+    SetupOtp_UserRecord,
 
 
     HelpdeskDetail,
     HelpdeskDetail,
     HelpdeskObfuscatedDN,
     HelpdeskObfuscatedDN,
@@ -99,5 +102,5 @@ public enum PwmRequestAttribute
 
 
     TokenDestItems,
     TokenDestItems,
 
 
-    GoBackAction,
+    GoBackAction,;
 }
 }

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

@@ -29,7 +29,6 @@ import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.PwmSetting;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
@@ -41,7 +40,6 @@ import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.util.LocaleHelper;
 import password.pwm.util.LocaleHelper;
-import password.pwm.util.PasswordData;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.java.TimeDuration;
@@ -377,12 +375,16 @@ public class PwmSession implements Serializable
 
 
         if ( nonce == null || nonce.length() != length )
         if ( nonce == null || nonce.length() != length )
         {
         {
-            nonce = pwmRequest.getPwmApplication().getSecureService().pwmRandom().alphaNumericString( length );
+            // random value
+            final String random = pwmRequest.getPwmApplication().getSecureService().pwmRandom().alphaNumericString( length );
+
+            // timestamp component for uniqueness
+            final String prefix = Long.toString( System.currentTimeMillis(), Character.MAX_RADIX );
+
+            nonce = random + prefix;
         }
         }
 
 
-        final PasswordData configSecret = pwmRequest.getConfig().readSettingAsPassword( PwmSetting.PWM_SECURITY_KEY );
-        final String concatValue = configSecret.getStringValue() + nonce;
-        final String hashValue = pwmRequest.getPwmApplication().getSecureService().hash( concatValue );
+        final String hashValue = pwmRequest.getPwmApplication().getSecureService().hash( nonce );
         final PwmSecurityKey pwmSecurityKey = new PwmSecurityKey( hashValue );
         final PwmSecurityKey pwmSecurityKey = new PwmSecurityKey( hashValue );
 
 
         pwmRequest.setAttribute( PwmRequestAttribute.CookieNonce, nonce );
         pwmRequest.setAttribute( PwmRequestAttribute.CookieNonce, nonce );

+ 3 - 124
server/src/main/java/password/pwm/http/bean/SetupResponsesBean.java

@@ -24,6 +24,7 @@ package password.pwm.http.bean;
 
 
 import com.novell.ldapchai.cr.Challenge;
 import com.novell.ldapchai.cr.Challenge;
 import com.novell.ldapchai.cr.ChallengeSet;
 import com.novell.ldapchai.cr.ChallengeSet;
+import lombok.Data;
 import password.pwm.config.option.SessionBeanMode;
 import password.pwm.config.option.SessionBeanMode;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
@@ -32,6 +33,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
 import java.util.Set;
 import java.util.Set;
 
 
+@Data
 public class SetupResponsesBean extends PwmSessionBean
 public class SetupResponsesBean extends PwmSessionBean
 {
 {
     private boolean hasExistingResponses;
     private boolean hasExistingResponses;
@@ -47,76 +49,7 @@ public class SetupResponsesBean extends PwmSessionBean
         return Type.AUTHENTICATED;
         return Type.AUTHENTICATED;
     }
     }
 
 
-    public SetupData getResponseData( )
-    {
-        return responseData;
-    }
-
-    public void setResponseData( final SetupData responseData )
-    {
-        this.responseData = responseData;
-    }
-
-    public SetupData getHelpdeskResponseData( )
-    {
-        return helpdeskResponseData;
-    }
-
-    public void setHelpdeskResponseData( final SetupData helpdeskResponseData )
-    {
-        this.helpdeskResponseData = helpdeskResponseData;
-    }
-
-    public boolean isResponsesSatisfied( )
-    {
-        return responsesSatisfied;
-    }
-
-    public void setResponsesSatisfied( final boolean responsesSatisfied )
-    {
-        this.responsesSatisfied = responsesSatisfied;
-    }
-
-    public boolean isHelpdeskResponsesSatisfied( )
-    {
-        return helpdeskResponsesSatisfied;
-    }
-
-    public void setHelpdeskResponsesSatisfied( final boolean helpdeskResponsesSatisfied )
-    {
-        this.helpdeskResponsesSatisfied = helpdeskResponsesSatisfied;
-    }
-
-    public boolean isConfirmed( )
-    {
-        return confirmed;
-    }
-
-    public void setConfirmed( final boolean confirmed )
-    {
-        this.confirmed = confirmed;
-    }
-
-    public Locale getUserLocale( )
-    {
-        return userLocale;
-    }
-
-    public void setUserLocale( final Locale userLocale )
-    {
-        this.userLocale = userLocale;
-    }
-
-    public boolean isHasExistingResponses( )
-    {
-        return hasExistingResponses;
-    }
-
-    public void setHasExistingResponses( final boolean hasExistingResponses )
-    {
-        this.hasExistingResponses = hasExistingResponses;
-    }
-
+    @Data
     public static class SetupData implements Serializable
     public static class SetupData implements Serializable
     {
     {
         private ChallengeSet challengeSet;
         private ChallengeSet challengeSet;
@@ -124,60 +57,6 @@ public class SetupResponsesBean extends PwmSessionBean
         private boolean simpleMode;
         private boolean simpleMode;
         private int minRandomSetup;
         private int minRandomSetup;
         private Map<Challenge, String> responseMap = Collections.emptyMap();
         private Map<Challenge, String> responseMap = Collections.emptyMap();
-
-        public SetupData( )
-        {
-        }
-
-        public ChallengeSet getChallengeSet( )
-        {
-            return challengeSet;
-        }
-
-        public void setChallengeSet( final ChallengeSet challengeSet )
-        {
-            this.challengeSet = challengeSet;
-        }
-
-        public Map<String, Challenge> getIndexedChallenges( )
-        {
-            return indexedChallenges;
-        }
-
-        public void setIndexedChallenges( final Map<String, Challenge> indexedChallenges )
-        {
-            this.indexedChallenges = indexedChallenges;
-        }
-
-        public boolean isSimpleMode( )
-        {
-            return simpleMode;
-        }
-
-        public void setSimpleMode( final boolean simpleMode )
-        {
-            this.simpleMode = simpleMode;
-        }
-
-        public int getMinRandomSetup( )
-        {
-            return minRandomSetup;
-        }
-
-        public void setMinRandomSetup( final int minRandomSetup )
-        {
-            this.minRandomSetup = minRandomSetup;
-        }
-
-        public Map<Challenge, String> getResponseMap( )
-        {
-            return responseMap;
-        }
-
-        public void setResponseMap( final Map<Challenge, String> responseMap )
-        {
-            this.responseMap = responseMap;
-        }
     }
     }
 
 
     @Override
     @Override

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

@@ -452,7 +452,7 @@ public class AuthenticationFilter extends AbstractPwmFilter
         }
         }
 
 
 
 
-        if ( userInfo.isRequiresResponseConfig() )
+        if ( userInfo.isRequiresResponseConfig() && !pwmSession.getLoginInfoBean().isLoginFlag( LoginInfoBean.LoginFlag.skipSetupCr ) )
         {
         {
             if ( !pwmURL.isSetupResponsesURL() )
             if ( !pwmURL.isSetupResponsesURL() )
             {
             {

+ 47 - 29
server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java

@@ -120,51 +120,69 @@ public class RequestInitializationFilter implements Filter
             }
             }
         }
         }
 
 
-        if ( testPwmApplicationLoad == null && pwmURL.isResourceURL() )
-        {
-            filterChain.doFilter( req, resp );
-        }
-        else if ( pwmURL.isRestService() )
-        {
-            filterChain.doFilter( req, resp );
-        }
-        else
+        try
         {
         {
-            if ( mode == PwmApplicationMode.ERROR )
+            if ( testPwmApplicationLoad == null && pwmURL.isResourceURL() )
+            {
+                filterChain.doFilter( req, resp );
+                return;
+            }
+
+            if ( testPwmApplicationLoad != null )
+            {
+                testPwmApplicationLoad.getInprogressRequests().incrementAndGet();
+            }
+
+            if ( pwmURL.isRestService() )
+            {
+                filterChain.doFilter( req, resp );
+            }
+            else
             {
             {
-                try
+                if ( mode == PwmApplicationMode.ERROR )
                 {
                 {
-                    final ContextManager contextManager = ContextManager.getContextManager( req.getServletContext() );
-                    if ( contextManager != null )
+                    try
                     {
                     {
-                        final ErrorInformation startupError = contextManager.getStartupErrorInformation();
-                        servletRequest.setAttribute( PwmRequestAttribute.PwmErrorInfo.toString(), startupError );
+                        final ContextManager contextManager = ContextManager.getContextManager( req.getServletContext() );
+                        if ( contextManager != null )
+                        {
+                            final ErrorInformation startupError = contextManager.getStartupErrorInformation();
+                            servletRequest.setAttribute( PwmRequestAttribute.PwmErrorInfo.toString(), startupError );
+                        }
                     }
                     }
-                }
-                catch ( Exception e )
-                {
-                    if ( pwmURL.isResourceURL() )
+                    catch ( Exception e )
                     {
                     {
-                        filterChain.doFilter( servletRequest, servletResponse );
-                        return;
+                        if ( pwmURL.isResourceURL() )
+                        {
+                            filterChain.doFilter( servletRequest, servletResponse );
+                            return;
+                        }
+
+                        LOGGER.error( "error while trying to detect application status: " + e.getMessage() );
                     }
                     }
 
 
-                    LOGGER.error( "error while trying to detect application status: " + e.getMessage() );
+                    LOGGER.error( "unable to satisfy incoming request, application is not available" );
+                    resp.setStatus( 500 );
+                    final String url = JspUrl.APP_UNAVAILABLE.getPath();
+                    servletRequest.getServletContext().getRequestDispatcher( url ).forward( servletRequest, servletResponse );
+                }
+                else
+                {
+                    initializeServletRequest( req, resp, filterChain );
                 }
                 }
-
-                LOGGER.error( "unable to satisfy incoming request, application is not available" );
-                resp.setStatus( 500 );
-                final String url = JspUrl.APP_UNAVAILABLE.getPath();
-                servletRequest.getServletContext().getRequestDispatcher( url ).forward( servletRequest, servletResponse );
             }
             }
-            else
+        }
+        finally
+        {
+            if ( testPwmApplicationLoad != null )
             {
             {
-                initializeServletRequest( req, resp, filterChain );
+                testPwmApplicationLoad.getInprogressRequests().decrementAndGet();
             }
             }
         }
         }
     }
     }
 
 
 
 
+
     private void initializeServletRequest(
     private void initializeServletRequest(
             final HttpServletRequest req,
             final HttpServletRequest req,
             final HttpServletResponse resp,
             final HttpServletResponse resp,

+ 35 - 15
server/src/main/java/password/pwm/http/servlet/SetupOtpServlet.java

@@ -25,6 +25,7 @@ package password.pwm.http.servlet;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import net.glxn.qrgen.QRCode;
 import net.glxn.qrgen.QRCode;
 import password.pwm.AppProperty;
 import password.pwm.AppProperty;
+import password.pwm.Permission;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.LoginInfoBean;
@@ -232,7 +233,10 @@ public class SetupOtpServlet extends ControlledPwmServlet
         }
         }
         else
         else
         {
         {
+            final boolean allowSkip = checkIfAllowedToSkipSetup( pwmRequest );
             final String qrCodeValue = makeQrCodeDataImageUrl( pwmRequest, otpBean.getOtpUserRecord() );
             final String qrCodeValue = makeQrCodeDataImageUrl( pwmRequest, otpBean.getOtpUserRecord() );
+            pwmRequest.setAttribute(  PwmRequestAttribute.SetupOtp_UserRecord, otpBean.getOtpUserRecord() );
+            pwmRequest.setAttribute( PwmRequestAttribute.SetupOtp_AllowSkip, allowSkip );
             pwmRequest.setAttribute( PwmRequestAttribute.SetupOtp_QrCodeValue, qrCodeValue );
             pwmRequest.setAttribute( PwmRequestAttribute.SetupOtp_QrCodeValue, qrCodeValue );
             pwmRequest.forwardToJsp( JspUrl.SETUP_OTP_SECRET );
             pwmRequest.forwardToJsp( JspUrl.SETUP_OTP_SECRET );
         }
         }
@@ -245,21 +249,7 @@ public class SetupOtpServlet extends ControlledPwmServlet
     )
     )
             throws PwmUnrecoverableException, IOException, ServletException, ChaiUnavailableException
             throws PwmUnrecoverableException, IOException, ServletException, ChaiUnavailableException
     {
     {
-
-        boolean allowSkip = false;
-        if ( !pwmRequest.isForcedPageView() )
-        {
-            allowSkip = true;
-        }
-        else
-        {
-            final SetupOtpProfile setupOtpProfile = getSetupOtpProfile( pwmRequest );
-            final ForceSetupPolicy policy = setupOtpProfile.readSettingAsEnum( PwmSetting.OTP_FORCE_SETUP, ForceSetupPolicy.class );
-            if ( policy == ForceSetupPolicy.FORCE_ALLOW_SKIP )
-            {
-                allowSkip = true;
-            }
-        }
+        final boolean allowSkip = checkIfAllowedToSkipSetup( pwmRequest );
 
 
         if ( allowSkip )
         if ( allowSkip )
         {
         {
@@ -526,4 +516,34 @@ public class SetupOtpServlet extends ControlledPwmServlet
 
 
         return "data:image/png;base64," + StringUtil.base64Encode( imageBytes );
         return "data:image/png;base64," + StringUtil.base64Encode( imageBytes );
     }
     }
+
+    private static boolean checkIfAllowedToSkipSetup( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
+        if ( pwmRequest.isForcedPageView() )
+        {
+            final SetupOtpProfile setupOtpProfile = getSetupOtpProfile( pwmRequest );
+            final ForceSetupPolicy policy = setupOtpProfile.readSettingAsEnum( PwmSetting.OTP_FORCE_SETUP, ForceSetupPolicy.class );
+
+            if ( policy == ForceSetupPolicy.FORCE_ALLOW_SKIP )
+            {
+                LOGGER.trace( pwmRequest, "allowing setup skipping due to setting "
+                        + PwmSetting.OTP_FORCE_SETUP.toMenuLocationDebug( setupOtpProfile.getIdentifier(), pwmRequest.getLocale() ) );
+                return true;
+            }
+
+            final boolean admin = pwmRequest.getPwmSession().getSessionManager().checkPermission( pwmRequest.getPwmApplication(), Permission.PWMADMIN );
+            if ( admin )
+            {
+                if ( pwmRequest.getConfig().readSettingAsBoolean( PwmSetting.ADMIN_ALLOW_SKIP_FORCED_ACTIVITIES ) )
+                {
+                    LOGGER.trace( pwmRequest, "allowing OTP setup skipping due to user being admin and setting "
+                            + PwmSetting.ADMIN_ALLOW_SKIP_FORCED_ACTIVITIES.toMenuLocationDebug( null, pwmRequest.getLocale() ) );
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
 }
 }

+ 45 - 1
server/src/main/java/password/pwm/http/servlet/SetupResponsesServlet.java

@@ -35,6 +35,7 @@ import lombok.Value;
 import password.pwm.Permission;
 import password.pwm.Permission;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
+import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.ResponseInfoBean;
 import password.pwm.bean.ResponseInfoBean;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.ChallengeProfile;
 import password.pwm.config.profile.ChallengeProfile;
@@ -97,7 +98,8 @@ public class SetupResponsesServlet extends ControlledPwmServlet
         setHelpdeskResponses( HttpMethod.POST ),
         setHelpdeskResponses( HttpMethod.POST ),
         confirmResponses( HttpMethod.POST ),
         confirmResponses( HttpMethod.POST ),
         clearExisting( HttpMethod.POST ),
         clearExisting( HttpMethod.POST ),
-        changeResponses( HttpMethod.POST ),;
+        changeResponses( HttpMethod.POST ),
+        skip ( HttpMethod.POST ),;
 
 
         private final HttpMethod method;
         private final HttpMethod method;
 
 
@@ -228,6 +230,26 @@ public class SetupResponsesServlet extends ControlledPwmServlet
         return ProcessStatus.Continue;
         return ProcessStatus.Continue;
     }
     }
 
 
+    @ActionHandler( action = "skip" )
+    private ProcessStatus handleSkip(
+            final PwmRequest pwmRequest
+    )
+            throws PwmUnrecoverableException, ChaiUnavailableException, IOException
+    {
+        LOGGER.trace( pwmRequest, "request for skip received" );
+
+        final boolean allowSkip = checkIfAllowSkipCr( pwmRequest );
+
+        if ( allowSkip )
+        {
+            pwmRequest.getPwmSession().getLoginInfoBean().getLoginFlags().add( LoginInfoBean.LoginFlag.skipSetupCr );
+            pwmRequest.sendRedirectToContinue();
+            return ProcessStatus.Halt;
+        }
+
+        return ProcessStatus.Continue;
+    }
+
     @ActionHandler( action = "validateResponses" )
     @ActionHandler( action = "validateResponses" )
     private ProcessStatus restValidateResponses(
     private ProcessStatus restValidateResponses(
             final PwmRequest pwmRequest
             final PwmRequest pwmRequest
@@ -305,6 +327,8 @@ public class SetupResponsesServlet extends ControlledPwmServlet
 
 
         if ( !setupResponsesBean.isResponsesSatisfied() )
         if ( !setupResponsesBean.isResponsesSatisfied() )
         {
         {
+            final boolean allowskip = checkIfAllowSkipCr( pwmRequest );
+            pwmRequest.setAttribute( PwmRequestAttribute.SetupResponses_AllowSkip, allowskip );
             pwmRequest.forwardToJsp( JspUrl.SETUP_RESPONSES );
             pwmRequest.forwardToJsp( JspUrl.SETUP_RESPONSES );
             return;
             return;
         }
         }
@@ -684,5 +708,25 @@ public class SetupResponsesServlet extends ControlledPwmServlet
         private String message;
         private String message;
         private boolean success;
         private boolean success;
     }
     }
+
+    private static boolean checkIfAllowSkipCr( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
+        if ( pwmRequest.isForcedPageView() )
+        {
+            final boolean admin = pwmRequest.getPwmSession().getSessionManager().checkPermission( pwmRequest.getPwmApplication(), Permission.PWMADMIN );
+            if ( admin )
+            {
+                if ( pwmRequest.getConfig().readSettingAsBoolean( PwmSetting.ADMIN_ALLOW_SKIP_FORCED_ACTIVITIES ) )
+                {
+                    LOGGER.trace( pwmRequest, "allowing c/r answer setup skipping due to user being admin and setting "
+                            + PwmSetting.ADMIN_ALLOW_SKIP_FORCED_ACTIVITIES.toMenuLocationDebug( null, pwmRequest.getLocale() ) );
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
 }
 }
 
 

+ 3 - 1
server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java

@@ -115,9 +115,10 @@ public class AppDashboardData implements Serializable
     private String nodeSummary;
     private String nodeSummary;
     private int ldapConnectionCount;
     private int ldapConnectionCount;
     private int sessionCount;
     private int sessionCount;
+    private int requestsInProgress;
 
 
 
 
-    static AppDashboardData makeDashboardData(
+    public static AppDashboardData makeDashboardData(
             final PwmApplication pwmApplication,
             final PwmApplication pwmApplication,
             final ContextManager contextManager,
             final ContextManager contextManager,
             final Locale locale,
             final Locale locale,
@@ -157,6 +158,7 @@ public class AppDashboardData implements Serializable
 
 
         builder.ldapConnectionCount( ldapConnectionCount( pwmApplication ) );
         builder.ldapConnectionCount( ldapConnectionCount( pwmApplication ) );
         builder.sessionCount( pwmApplication.getSessionTrackService().sessionCount() );
         builder.sessionCount( pwmApplication.getSessionTrackService().sessionCount() );
+        builder.requestsInProgress( pwmApplication.getInprogressRequests().get() );
 
 
         LOGGER.trace( "AppDashboardData bean created in " + TimeDuration.compactFromCurrent( startTime ) );
         LOGGER.trace( "AppDashboardData bean created in " + TimeDuration.compactFromCurrent( startTime ) );
         return builder.build();
         return builder.build();

+ 29 - 1
server/src/main/java/password/pwm/http/servlet/configmanager/DebugItemGenerator.java

@@ -33,7 +33,9 @@ import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthMonitor;
 import password.pwm.health.HealthMonitor;
 import password.pwm.health.HealthRecord;
 import password.pwm.health.HealthRecord;
+import password.pwm.http.ContextManager;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequest;
+import password.pwm.http.servlet.admin.AppDashboardData;
 import password.pwm.http.servlet.admin.UserDebugDataBean;
 import password.pwm.http.servlet.admin.UserDebugDataBean;
 import password.pwm.http.servlet.admin.UserDebugDataReader;
 import password.pwm.http.servlet.admin.UserDebugDataReader;
 import password.pwm.ldap.LdapDebugDataGenerator;
 import password.pwm.ldap.LdapDebugDataGenerator;
@@ -89,6 +91,7 @@ public class DebugItemGenerator
             ConfigurationDebugJsonItemGenerator.class,
             ConfigurationDebugJsonItemGenerator.class,
             ConfigurationDebugTextItemGenerator.class,
             ConfigurationDebugTextItemGenerator.class,
             AboutItemGenerator.class,
             AboutItemGenerator.class,
+            DashboardDataDebugItemGenerator.class,
             SystemEnvironmentItemGenerator.class,
             SystemEnvironmentItemGenerator.class,
             AppPropertiesItemGenerator.class,
             AppPropertiesItemGenerator.class,
             ServicesDebugItemGenerator.class,
             ServicesDebugItemGenerator.class,
@@ -710,9 +713,34 @@ public class DebugItemGenerator
             final Map<String, Serializable> debugOutput = new LinkedHashMap<>( cacheService.debugInfo() );
             final Map<String, Serializable> debugOutput = new LinkedHashMap<>( cacheService.debugInfo() );
             outputStream.write( JsonUtil.serializeMap( debugOutput, JsonUtil.Flag.PrettyPrint ).getBytes( PwmConstants.DEFAULT_CHARSET ) );
             outputStream.write( JsonUtil.serializeMap( debugOutput, JsonUtil.Flag.PrettyPrint ).getBytes( PwmConstants.DEFAULT_CHARSET ) );
         }
         }
-
     }
     }
 
 
+    static class DashboardDataDebugItemGenerator implements Generator
+    {
+        @Override
+        public String getFilename( )
+        {
+            return "dashboard-data.json";
+        }
+
+        @Override
+        public void outputItem(
+                final PwmApplication pwmApplication,
+                final PwmRequest pwmRequest,
+                final OutputStream outputStream
+        )
+                throws Exception
+        {
+            final ContextManager contextManager = ContextManager.getContextManager( pwmRequest );
+            final AppDashboardData appDashboardData = AppDashboardData.makeDashboardData(
+                    pwmApplication,
+                    contextManager,
+                    pwmRequest.getLocale()
+            );
+
+            outputStream.write( JsonUtil.serialize( appDashboardData, JsonUtil.Flag.PrettyPrint ).getBytes( PwmConstants.DEFAULT_CHARSET ) );
+        }
+    }
 
 
     interface Generator
     interface Generator
     {
     {

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

@@ -55,7 +55,7 @@ public class ErrorMessageTag extends PwmAbstractTag
             PwmApplication pwmApplication = null;
             PwmApplication pwmApplication = null;
             try
             try
             {
             {
-                pwmApplication = ContextManager.getPwmApplication( pageContext.getSession() );
+                pwmApplication = ContextManager.getPwmApplication( pageContext.getRequest() );
             }
             }
             catch ( PwmException e )
             catch ( PwmException e )
             { /* noop */ }
             { /* noop */ }

+ 8 - 0
server/src/main/java/password/pwm/svc/PwmServiceManager.java

@@ -61,10 +61,13 @@ public class PwmServiceManager
     public void initAllServices( )
     public void initAllServices( )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
+        final Instant startTime = Instant.now();
 
 
         final boolean internalRuntimeInstance = pwmApplication.getPwmEnvironment().isInternalRuntimeInstance()
         final boolean internalRuntimeInstance = pwmApplication.getPwmEnvironment().isInternalRuntimeInstance()
                 || pwmApplication.getPwmEnvironment().getFlags().contains( PwmEnvironment.ApplicationFlag.CommandLineInstance );
                 || pwmApplication.getPwmEnvironment().getFlags().contains( PwmEnvironment.ApplicationFlag.CommandLineInstance );
 
 
+        int serviceCounter = 0;
+
         for ( final PwmServiceEnum serviceClassEnum : PwmServiceEnum.values() )
         for ( final PwmServiceEnum serviceClassEnum : PwmServiceEnum.values() )
         {
         {
             boolean startService = true;
             boolean startService = true;
@@ -77,9 +80,14 @@ public class PwmServiceManager
                 final Class<? extends PwmService> serviceClass = serviceClassEnum.getPwmServiceClass();
                 final Class<? extends PwmService> serviceClass = serviceClassEnum.getPwmServiceClass();
                 final PwmService newServiceInstance = initService( serviceClass );
                 final PwmService newServiceInstance = initService( serviceClass );
                 runningServices.put( serviceClass, newServiceInstance );
                 runningServices.put( serviceClass, newServiceInstance );
+                serviceCounter++;
             }
             }
         }
         }
+
         initialized = true;
         initialized = true;
+
+        final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime );
+        LOGGER.trace( "started " + serviceCounter + " services in " + timeDuration.asCompactString() );
     }
     }
 
 
     private PwmService initService( final Class<? extends PwmService> serviceClass )
     private PwmService initService( final Class<? extends PwmService> serviceClass )

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

@@ -72,7 +72,7 @@ class ClusterMachine
 
 
         this.executorService.scheduleAtFixedRate(
         this.executorService.scheduleAtFixedRate(
                 new HeartbeatProcess(),
                 new HeartbeatProcess(),
-                1,
+                intervalSeconds,
                 intervalSeconds,
                 intervalSeconds,
                 TimeUnit.SECONDS
                 TimeUnit.SECONDS
         );
         );

+ 118 - 0
server/src/main/java/password/pwm/util/MBeanUtility.java

@@ -0,0 +1,118 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2018 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.util;
+
+import lombok.Getter;
+import password.pwm.PwmAboutProperty;
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
+import password.pwm.util.logging.PwmLogger;
+
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import java.lang.management.ManagementFactory;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MBeanUtility
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( MBeanUtility.class );
+
+    private MBeanUtility( )
+    {
+    }
+
+    public static void registerMBean( final PwmApplication pwmApplication )
+    {
+        try
+        {
+            final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+            final ObjectName name = figureMBeanName( pwmApplication );
+            final Map<PwmAboutProperty, String> aboutMap = PwmAboutProperty.makeInfoBean( pwmApplication );
+            final Map<String, String> outputMap = new HashMap<>(  );
+            final AttributeList attributeList = new AttributeList(  );
+            for ( final Map.Entry<PwmAboutProperty, String> entry : aboutMap.entrySet() )
+            {
+                outputMap.put( entry.getKey().name(), entry.getValue() );
+                attributeList.add( new Attribute( entry.getKey().name(), entry.getValue() ) );
+            }
+            final PwmAbout mbean = new PwmAbout( outputMap );
+            mbs.registerMBean( mbean, name );
+            mbs.setAttributes( name, attributeList );
+        }
+        catch ( Exception e )
+        {
+            LOGGER.error( "error registering mbean: " + e.getMessage() );
+        }
+    }
+
+    public static void unregisterMBean( final PwmApplication pwmApplication )
+    {
+        try
+        {
+            final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+            mbs.unregisterMBean( figureMBeanName( pwmApplication ) );
+        }
+        catch ( Exception e )
+        {
+            LOGGER.error( "error unregistering mbean: " + e.getMessage() );
+        }
+    }
+
+    private static ObjectName figureMBeanName( final PwmApplication pwmApplication )
+            throws MalformedObjectNameException
+    {
+        final String context;
+        if ( pwmApplication.getPwmEnvironment() != null && pwmApplication.getPwmEnvironment().getContextManager() != null )
+        {
+            context = "-" + pwmApplication.getPwmEnvironment().getContextManager().getContextPath();
+        }
+        else
+        {
+            context = "";
+        }
+        final String mbeanName = "password.pwm:type=About" + PwmConstants.PWM_APP_NAME.toUpperCase() + context;
+        return new ObjectName( mbeanName );
+    }
+
+
+    public interface PwmAboutMXBean
+    {
+        Map<String, String> getAboutInfoMap();
+    }
+
+    @Getter
+    public static class PwmAbout implements PwmAboutMXBean
+    {
+        final Map<String, String> aboutInfoMap;
+
+        public PwmAbout( final Map<String, String> aboutInfoMap )
+        {
+            this.aboutInfoMap = aboutInfoMap;
+        }
+    }
+}

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

@@ -527,7 +527,7 @@ public class RandomPasswordGenerator
             if ( numChars == null )
             if ( numChars == null )
             {
             {
                 final StringBuilder sb = new StringBuilder();
                 final StringBuilder sb = new StringBuilder();
-                for ( final Character c : allChars.toCharArray() )
+                for ( final Character c : getAllChars().toCharArray() )
                 {
                 {
                     if ( Character.isDigit( c ) )
                     if ( Character.isDigit( c ) )
                     {
                     {
@@ -545,7 +545,7 @@ public class RandomPasswordGenerator
             if ( specialChars == null )
             if ( specialChars == null )
             {
             {
                 final StringBuilder sb = new StringBuilder();
                 final StringBuilder sb = new StringBuilder();
-                for ( final Character c : allChars.toCharArray() )
+                for ( final Character c : getAllChars().toCharArray() )
                 {
                 {
                     if ( !Character.isLetterOrDigit( c ) )
                     if ( !Character.isLetterOrDigit( c ) )
                     {
                     {
@@ -563,7 +563,7 @@ public class RandomPasswordGenerator
             if ( upperChars == null )
             if ( upperChars == null )
             {
             {
                 final StringBuilder sb = new StringBuilder();
                 final StringBuilder sb = new StringBuilder();
-                for ( final Character c : allChars.toCharArray() )
+                for ( final Character c : getAllChars().toCharArray() )
                 {
                 {
                     if ( Character.isUpperCase( c ) )
                     if ( Character.isUpperCase( c ) )
                     {
                     {
@@ -580,7 +580,7 @@ public class RandomPasswordGenerator
             if ( lowerChars == null )
             if ( lowerChars == null )
             {
             {
                 final StringBuilder sb = new StringBuilder();
                 final StringBuilder sb = new StringBuilder();
-                for ( final Character c : allChars.toCharArray() )
+                for ( final Character c : getAllChars().toCharArray() )
                 {
                 {
                     if ( Character.isLowerCase( c ) )
                     if ( Character.isLowerCase( c ) )
                     {
                     {

+ 4 - 3
server/src/main/java/password/pwm/util/secure/PwmRandom.java

@@ -31,14 +31,15 @@ import java.util.stream.LongStream;
 public class PwmRandom extends SecureRandom
 public class PwmRandom extends SecureRandom
 {
 {
 
 
-    private final SecureRandom internalRand = new SecureRandom();
+    private final SecureRandom internalRand;
 
 
-    private static final PwmRandom SINGLETON = new PwmRandom();
+    private static final PwmRandom SINGLETON = new PwmRandom( new SecureRandom( ) );
 
 
     private static final String ALPHANUMERIC_STRING = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
     private static final String ALPHANUMERIC_STRING = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
 
 
-    private PwmRandom( )
+    public PwmRandom( final SecureRandom internalRand )
     {
     {
+        this.internalRand = internalRand;
     }
     }
 
 
     public static PwmRandom getInstance( )
     public static PwmRandom getInstance( )

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

@@ -26,6 +26,8 @@
 activateUser.token.autoSelectSingleDestination=false
 activateUser.token.autoSelectSingleDestination=false
 application.fileLock.filename=applicationPath.lock
 application.fileLock.filename=applicationPath.lock
 application.fileLock.waitSeconds=120
 application.fileLock.waitSeconds=120
+application.readAppLock.maxWaitMs=5000
+application.restart.maxRequestWaitMs=3000
 application.wordlistRetryImportSeconds=600
 application.wordlistRetryImportSeconds=600
 audit.events.emailFrom=Audit Event Notification <@DefaultEmailFromAddress@>
 audit.events.emailFrom=Audit Event Notification <@DefaultEmailFromAddress@>
 audit.events.emailSubject=@PwmAppName@ - Audit Event - %EVENT%
 audit.events.emailSubject=@PwmAppName@ - Audit Event - %EVENT%

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

@@ -519,6 +519,11 @@
             <value/>
             <value/>
         </default>
         </default>
     </setting>
     </setting>
+    <setting hidden="false" key="pwmAdmin.allowSkipForcedActivities" level="0" required="true">
+        <default>
+            <value>true</value>
+        </default>
+    </setting>
     <setting hidden="false" key="ldap.usernameSearchFilter" level="1" required="true">
     <setting hidden="false" key="ldap.usernameSearchFilter" level="1" required="true">
         <default>
         <default>
             <value><![CDATA[(&(objectClass=person)(cn=%USERNAME%))]]></value>
             <value><![CDATA[(&(objectClass=person)(cn=%USERNAME%))]]></value>

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

@@ -327,6 +327,7 @@ Title_DataAnalysis=Data Analysis
 Title_Dashboard=Dashboard
 Title_Dashboard=Dashboard
 Title_LogViewer=Log Viewer
 Title_LogViewer=Log Viewer
 Title_TokenLookup=Token Search
 Title_TokenLookup=Token Search
+Title_RequestsInProgress=Requests in Progress
 Title_URLReference=URL Reference
 Title_URLReference=URL Reference
 MenuItem_ConfigEditor=Configuration Editor
 MenuItem_ConfigEditor=Configuration Editor
 MenuItem_ConfigManager=Configuration Manager
 MenuItem_ConfigManager=Configuration Manager

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

@@ -596,6 +596,7 @@ Setting_Description_peopleSearch.searchBase=Specify the LDAP search bases for th
 Setting_Description_peopleSearch.searchFilter=Specify the LDAP search filter the People Search module uses to query the directory.  Substitute <i>%USERNAME%</i> for user-supplied user names. If blank, @PwmAppName@ auto-generates the search filter based on the values in the setting <a data-gotoSettingLink\="peopleSearch.searchAttributes">@PwmSettingReference\:peopleSearch.searchAttributes@</a>.\n        <br/><br>\n        Example\: <code>(&(objectClass\=Person)(|(givenName\=*%USERNAME%*)(sn\=*%USERNAME%*)(mail\=*%USERNAME%*)(telephoneNumber\=*%USERNAME%*)))</code>\n\n
 Setting_Description_peopleSearch.searchFilter=Specify the LDAP search filter the People Search module uses to query the directory.  Substitute <i>%USERNAME%</i> for user-supplied user names. If blank, @PwmAppName@ auto-generates the search filter based on the values in the setting <a data-gotoSettingLink\="peopleSearch.searchAttributes">@PwmSettingReference\:peopleSearch.searchAttributes@</a>.\n        <br/><br>\n        Example\: <code>(&(objectClass\=Person)(|(givenName\=*%USERNAME%*)(sn\=*%USERNAME%*)(mail\=*%USERNAME%*)(telephoneNumber\=*%USERNAME%*)))</code>\n\n
 Setting_Description_peopleSearch.useProxy=Enable this option to use the LDAP proxy account to perform searches. For proper security in most environments, do <b>not</b> enable this setting.
 Setting_Description_peopleSearch.useProxy=Enable this option to use the LDAP proxy account to perform searches. For proper security in most environments, do <b>not</b> enable this setting.
 Setting_Description_pwmAdmin.queryMatch=Specify the permissions @PwmAppName@ uses to determine if it grants a user administrator rights.
 Setting_Description_pwmAdmin.queryMatch=Specify the permissions @PwmAppName@ uses to determine if it grants a user administrator rights.
+Setting_Description_pwmAdmin.allowSkipForcedActivities=Allow administrators to skip otherwised forced activities such as setup of challenge/response answers. 
 Setting_Description_pwm.appProperty.overrides=(Troubleshooting only) Specify an override application properties value.  Do not use unless directed to by a support expert.
 Setting_Description_pwm.appProperty.overrides=(Troubleshooting only) Specify an override application properties value.  Do not use unless directed to by a support expert.
 Setting_Description_pwm.forwardURL=Specify a URL that @PwmAppName@ forwards users to after the users complete any activity which does not require a log out.<br/><br/>You can override this setting for any given user session by adding a <i>forwardURL</i> parameter to any HTTP request. If blank, the system forwards the user to the @PwmAppName@ menu.
 Setting_Description_pwm.forwardURL=Specify a URL that @PwmAppName@ forwards users to after the users complete any activity which does not require a log out.<br/><br/>You can override this setting for any given user session by adding a <i>forwardURL</i> parameter to any HTTP request. If blank, the system forwards the user to the @PwmAppName@ menu.
 Setting_Description_pwm.homeURL=Specify the URL to redirect the user to upon clicking the home button. If blank, the home button returns the user to the application context URL.
 Setting_Description_pwm.homeURL=Specify the URL to redirect the user to upon clicking the home button. If blank, the home button returns the user to the application context URL.
@@ -1097,6 +1098,7 @@ Setting_Label_peopleSearch.searchBase=LDAP Search base
 Setting_Label_peopleSearch.searchFilter=People Search LDAP Filter
 Setting_Label_peopleSearch.searchFilter=People Search LDAP Filter
 Setting_Label_peopleSearch.useProxy=Use Proxy Account
 Setting_Label_peopleSearch.useProxy=Use Proxy Account
 Setting_Label_pwmAdmin.queryMatch=Administrator Permission
 Setting_Label_pwmAdmin.queryMatch=Administrator Permission
+Setting_Label_pwmAdmin.allowSkipForcedActivities=Allow Admin to Skip Forced Activities
 Setting_Label_pwm.appProperty.overrides=App Property Overrides
 Setting_Label_pwm.appProperty.overrides=App Property Overrides
 Setting_Label_pwm.forwardURL=Forward URL
 Setting_Label_pwm.forwardURL=Forward URL
 Setting_Label_pwm.homeURL=Home URL
 Setting_Label_pwm.homeURL=Home URL

+ 9 - 9
server/src/test/java/password/pwm/http/client/PwmHttpClientTest.java

@@ -75,7 +75,7 @@ public class PwmHttpClientTest {
         wm.stubFor(get(urlEqualTo("/simpleHello"))
         wm.stubFor(get(urlEqualTo("/simpleHello"))
             .willReturn(aResponse()
             .willReturn(aResponse()
                 .withHeader("Content-Type", "text/plain")
                 .withHeader("Content-Type", "text/plain")
-                .withBody("Hello from the local mock server")));
+                .withBody("PwmAbout from the local mock server")));
 
 
         // Obtain the HTTP client from PWM
         // Obtain the HTTP client from PWM
         HttpClient httpClient = PwmHttpClient.getHttpClient(configuration);
         HttpClient httpClient = PwmHttpClient.getHttpClient(configuration);
@@ -89,7 +89,7 @@ public class PwmHttpClientTest {
         assertThat(responseStatusCode).isEqualTo(200);
         assertThat(responseStatusCode).isEqualTo(200);
 
 
         String responseContent = IOUtils.toString(response.getEntity().getContent());
         String responseContent = IOUtils.toString(response.getEntity().getContent());
-        assertThat(responseContent).startsWith("Hello");
+        assertThat(responseContent).startsWith("PwmAbout");
 
 
         // Verify the HTTP server got called as expected
         // Verify the HTTP server got called as expected
         wm.verify(getRequestedFor(urlEqualTo("/simpleHello"))
         wm.verify(getRequestedFor(urlEqualTo("/simpleHello"))
@@ -105,7 +105,7 @@ public class PwmHttpClientTest {
         wm.stubFor(get(urlEqualTo("/simpleHello"))
         wm.stubFor(get(urlEqualTo("/simpleHello"))
             .willReturn(aResponse()
             .willReturn(aResponse()
                 .withHeader("Content-Type", "text/plain")
                 .withHeader("Content-Type", "text/plain")
-                .withBody("Hello from the local mock server")));
+                .withBody("PwmAbout from the local mock server")));
 
 
         HttpClient httpClient = PwmHttpClient.getHttpClient(configuration);
         HttpClient httpClient = PwmHttpClient.getHttpClient(configuration);
 
 
@@ -124,7 +124,7 @@ public class PwmHttpClientTest {
         wm.stubFor(get(urlEqualTo("/simpleHello"))
         wm.stubFor(get(urlEqualTo("/simpleHello"))
             .willReturn(aResponse()
             .willReturn(aResponse()
                 .withHeader("Content-Type", "text/plain")
                 .withHeader("Content-Type", "text/plain")
-                .withBody("Hello from the local mock server")));
+                .withBody("PwmAbout from the local mock server")));
 
 
         // Stub out some mock object behavior
         // Stub out some mock object behavior
         when(configuration.readAppProperty(AppProperty.SECURITY_HTTP_PROMISCUOUS_ENABLE)).thenReturn("true");
         when(configuration.readAppProperty(AppProperty.SECURITY_HTTP_PROMISCUOUS_ENABLE)).thenReturn("true");
@@ -139,7 +139,7 @@ public class PwmHttpClientTest {
         assertThat(responseStatusCode).isEqualTo(200);
         assertThat(responseStatusCode).isEqualTo(200);
 
 
         String responseContent = IOUtils.toString(response.getEntity().getContent());
         String responseContent = IOUtils.toString(response.getEntity().getContent());
-        assertThat(responseContent).startsWith("Hello");
+        assertThat(responseContent).startsWith("PwmAbout");
     }
     }
 
 
     /**
     /**
@@ -151,7 +151,7 @@ public class PwmHttpClientTest {
         wm.stubFor(get(urlEqualTo("/simpleHello"))
         wm.stubFor(get(urlEqualTo("/simpleHello"))
             .willReturn(aResponse()
             .willReturn(aResponse()
                 .withHeader("Content-Type", "text/plain")
                 .withHeader("Content-Type", "text/plain")
-                .withBody("Hello from the local mock server")));
+                .withBody("PwmAbout from the local mock server")));
 
 
         PwmHttpClientConfiguration pwmHttpClientConfiguration = PwmHttpClientConfiguration.builder()
         PwmHttpClientConfiguration pwmHttpClientConfiguration = PwmHttpClientConfiguration.builder()
                 .certificates(getWireMockSelfSignedCertificate())
                 .certificates(getWireMockSelfSignedCertificate())
@@ -167,7 +167,7 @@ public class PwmHttpClientTest {
         assertThat(responseStatusCode).isEqualTo(200);
         assertThat(responseStatusCode).isEqualTo(200);
 
 
         String responseContent = IOUtils.toString(response.getEntity().getContent());
         String responseContent = IOUtils.toString(response.getEntity().getContent());
-        assertThat(responseContent).startsWith("Hello");
+        assertThat(responseContent).startsWith("PwmAbout");
     }
     }
 
 
     /**
     /**
@@ -179,7 +179,7 @@ public class PwmHttpClientTest {
         wm.stubFor(get(urlEqualTo("/simpleHello"))
         wm.stubFor(get(urlEqualTo("/simpleHello"))
             .willReturn(aResponse()
             .willReturn(aResponse()
                 .withHeader("Content-Type", "text/plain")
                 .withHeader("Content-Type", "text/plain")
-                .withBody("Hello from the local mock server")));
+                .withBody("PwmAbout from the local mock server")));
 
 
         // Stub out some mock object behavior
         // Stub out some mock object behavior
         when(configuration.readSettingAsString(PwmSetting.HTTP_PROXY_URL)).thenReturn(String.format("http://localhost:%d/simpleHello", wm.port()));
         when(configuration.readSettingAsString(PwmSetting.HTTP_PROXY_URL)).thenReturn(String.format("http://localhost:%d/simpleHello", wm.port()));
@@ -195,7 +195,7 @@ public class PwmHttpClientTest {
         assertThat(responseStatusCode).isEqualTo(200);
         assertThat(responseStatusCode).isEqualTo(200);
 
 
         String responseContent = IOUtils.toString(response.getEntity().getContent());
         String responseContent = IOUtils.toString(response.getEntity().getContent());
-        assertThat(responseContent).startsWith("Hello");
+        assertThat(responseContent).startsWith("PwmAbout");
     }
     }
 
 
     private List<X509Certificate> getWireMockSelfSignedCertificate() {
     private List<X509Certificate> getWireMockSelfSignedCertificate() {

+ 12 - 1
webapp/src/main/webapp/WEB-INF/jsp/admin-dashboard.jsp

@@ -69,12 +69,23 @@
                         </td>
                         </td>
                         <td class="key">
                         <td class="key">
                             <pwm:display key="Title_LDAPConnections" bundle="Admin"/>
                             <pwm:display key="Title_LDAPConnections" bundle="Admin"/>
-
                         </td>
                         </td>
                         <td id="LDAPConnectionCount">
                         <td id="LDAPConnectionCount">
                             <%= appDashboardData.getLdapConnectionCount() %>
                             <%= appDashboardData.getLdapConnectionCount() %>
                         </td>
                         </td>
                     </tr>
                     </tr>
+                    <tr>
+                        <td class="key">
+                            <pwm:display key="Title_RequestsInProgress" bundle="Admin"/>
+                        </td>
+                        <td id="RequestsInProgress">
+                            <%= appDashboardData.getRequestsInProgress() %>
+                        </td>
+                        <td>
+                        </td>
+                        <td>
+                        </td>
+                    </tr>
                 </table>
                 </table>
                 <table class="nomargin">
                 <table class="nomargin">
                     <tr>
                     <tr>

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

@@ -107,11 +107,11 @@
                     <p>
                     <p>
                         This page shows the debug log
                         This page shows the debug log
                         history. This records shown here are stored in the LocalDB.  The LocalDB contains <%=JspUtility.friendlyWrite( pageContext, localDBLogger.getStoredEventCount() )%> events. The oldest event stored in the LocalDB is from
                         history. This records shown here are stored in the LocalDB.  The LocalDB contains <%=JspUtility.friendlyWrite( pageContext, localDBLogger.getStoredEventCount() )%> events. The oldest event stored in the LocalDB is from
-                        <span class="timestamp"><%= JspUtility.friendlyWrite( pageContext, ContextManager.getPwmApplication(session).getLocalDBLogger().getTailDate() ) %></span>.
+                        <span class="timestamp"><%= JspUtility.friendlyWrite( pageContext, JspUtility.getPwmRequest( pageContext ).getPwmApplication().getLocalDBLogger().getTailDate() ) %></span>.
                     </p>
                     </p>
                     <p>
                     <p>
                         The LocalDB is configured to capture events of level
                         The LocalDB is configured to capture events of level
-                        <i><%=ContextManager.getPwmApplication(session).getConfig().readSettingAsString(PwmSetting.EVENTS_LOCALDB_LOG_LEVEL)%>
+                        <i><%=JspUtility.getPwmRequest( pageContext ).getConfig().readSettingAsString(PwmSetting.EVENTS_LOCALDB_LOG_LEVEL)%>
                         </i> and higher.
                         </i> and higher.
                     </p>
                     </p>
                 </div>
                 </div>

+ 1 - 1
webapp/src/main/webapp/WEB-INF/jsp/fragment/form.jsp

@@ -253,7 +253,7 @@
             <input type="<pwm:value name="passwordFieldType"/>" name="password1" id="password1" class="changepasswordfield passwordfield" style="margin-left:5px"/>
             <input type="<pwm:value name="passwordFieldType"/>" name="password1" id="password1" class="changepasswordfield passwordfield" style="margin-left:5px"/>
         </td>
         </td>
         <td class="noborder">
         <td class="noborder">
-            <% if (ContextManager.getPwmApplication(session).getConfig() != null && ContextManager.getPwmApplication(session).getConfig().readSettingAsBoolean(PwmSetting.PASSWORD_SHOW_STRENGTH_METER)) { %>
+            <% if (JspUtility.getPwmRequest( pageContext ).getConfig() != null && JspUtility.getPwmRequest( pageContext ).getConfig().readSettingAsBoolean(PwmSetting.PASSWORD_SHOW_STRENGTH_METER)) { %>
             <div id="strengthBox" style="visibility:hidden;">
             <div id="strengthBox" style="visibility:hidden;">
                 <div id="strengthLabel">
                 <div id="strengthLabel">
                     <pwm:display key="Display_StrengthMeter"/>
                     <pwm:display key="Display_StrengthMeter"/>

+ 2 - 2
webapp/src/main/webapp/WEB-INF/jsp/newuser-profilechoice.jsp

@@ -31,7 +31,7 @@
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%
 <%
-    final PwmRequest pwmRequest = PwmRequest.forRequest(request, response);
+    final PwmRequest pwmRequest = JspUtility.getPwmRequest( pageContext );
     final Map<String,String> newUserProfiles = (Map)pwmRequest.getAttribute(PwmRequestAttribute.NewUser_VisibleProfiles);
     final Map<String,String> newUserProfiles = (Map)pwmRequest.getAttribute(PwmRequestAttribute.NewUser_VisibleProfiles);
 %>
 %>
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
@@ -70,7 +70,7 @@
         </table>
         </table>
         <br/>
         <br/>
         <div class="buttonbar">
         <div class="buttonbar">
-            <% if (ContextManager.getPwmApplication(session).getConfig().readSettingAsBoolean(password.pwm.config.PwmSetting.DISPLAY_CANCEL_BUTTON)) { %>
+            <% if (JspUtility.getPwmRequest( pageContext ).getConfig().readSettingAsBoolean(password.pwm.config.PwmSetting.DISPLAY_CANCEL_BUTTON)) { %>
             <form action="<pwm:url url='<%=PwmServletDefinition.PublicCommand.servletUrl()%>' addContext="true"/>" method="get"
             <form action="<pwm:url url='<%=PwmServletDefinition.PublicCommand.servletUrl()%>' addContext="true"/>" method="get"
                   enctype="application/x-www-form-urlencoded" name="search" class="pwm-form">
                   enctype="application/x-www-form-urlencoded" name="search" class="pwm-form">
                 <button class="btn" type="submit" name="submitBtn">
                 <button class="btn" type="submit" name="submitBtn">

+ 2 - 23
webapp/src/main/webapp/WEB-INF/jsp/setupotpsecret.jsp

@@ -20,33 +20,15 @@
  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 --%>
 --%>
 
 
-<%@page import="password.pwm.config.option.ForceSetupPolicy"%>
-<%@page import="password.pwm.http.bean.SetupOtpBean"%>
 <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
 <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
 <%@ page import="password.pwm.util.operations.otp.OTPUserRecord" %>
 <%@ page import="password.pwm.util.operations.otp.OTPUserRecord" %>
 <%@ page import="password.pwm.http.PwmRequestAttribute" %>
 <%@ page import="password.pwm.http.PwmRequestAttribute" %>
-<%@ page import="password.pwm.config.profile.SetupOtpProfile" %>
-<%@ page import="password.pwm.http.servlet.SetupOtpServlet" %>
 <!DOCTYPE html>
 <!DOCTYPE html>
 <%@ page language="java" session="true" isThreadSafe="true"
 <%@ page language="java" session="true" isThreadSafe="true"
          contentType="text/html" %>
          contentType="text/html" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
-<%
-    OTPUserRecord otpUserRecord = null;
-    boolean allowSkip = false;
-    boolean forcedPageView = false;
-    try {
-        final PwmRequest pwmRequest = JspUtility.getPwmRequest( pageContext );
-        final SetupOtpBean setupOtpBean = JspUtility.getSessionBean(pageContext, SetupOtpBean.class);
-        final SetupOtpProfile setupOtpProfile = SetupOtpServlet.getSetupOtpProfile( pwmRequest );
-        otpUserRecord = setupOtpBean.getOtpUserRecord();
-        allowSkip = setupOtpProfile.readSettingAsEnum(PwmSetting.OTP_FORCE_SETUP, ForceSetupPolicy.class) == ForceSetupPolicy.FORCE_ALLOW_SKIP;
-        forcedPageView = pwmRequest.isForcedPageView();
-    } catch (PwmUnrecoverableException e) {
-        /* application must be unavailable */
-    }
-
-%>
+<% OTPUserRecord otpUserRecord = (OTPUserRecord) JspUtility.getAttribute( pageContext, PwmRequestAttribute.SetupOtp_UserRecord ); %>
+<% boolean allowSkip = JspUtility.getBooleanAttribute( pageContext, PwmRequestAttribute.SetupOtp_AllowSkip ); %>
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
 <%@ include file="fragment/header.jsp" %>
 <%@ include file="fragment/header.jsp" %>
 <body class="nihilo">
 <body class="nihilo">
@@ -107,14 +89,12 @@
             <form action="<pwm:current-url/>" method="post" name="setupOtpSecret-skip" enctype="application/x-www-form-urlencoded" id="setupOtpSecret-skip" class="pwm-form">
             <form action="<pwm:current-url/>" method="post" name="setupOtpSecret-skip" enctype="application/x-www-form-urlencoded" id="setupOtpSecret-skip" class="pwm-form">
                 <input type="hidden" name="processAction" value="skip"/>
                 <input type="hidden" name="processAction" value="skip"/>
                 <input type="hidden" name="pwmFormID" value="<pwm:FormID/>"/>
                 <input type="hidden" name="pwmFormID" value="<pwm:FormID/>"/>
-                <% if (forcedPageView) { %>
                 <% if (allowSkip) { %>
                 <% if (allowSkip) { %>
                 <button type="submit" name="continue" class="btn" id="skipbutton">
                 <button type="submit" name="continue" class="btn" id="skipbutton">
                     <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-fighter-jet"></span></pwm:if>
                     <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-fighter-jet"></span></pwm:if>
                     <pwm:display key="Button_Skip"/>
                     <pwm:display key="Button_Skip"/>
                 </button>
                 </button>
                 <% } %>
                 <% } %>
-                <% } else { %>
                 <pwm:if test="<%=PwmIfTest.showCancel%>">
                 <pwm:if test="<%=PwmIfTest.showCancel%>">
                     <pwm:if test="<%=PwmIfTest.forcedPageView%>" negate="true">
                     <pwm:if test="<%=PwmIfTest.forcedPageView%>" negate="true">
                         <button type="submit" name="button" class="btn" id="button-cancel">
                         <button type="submit" name="button" class="btn" id="button-cancel">
@@ -123,7 +103,6 @@
                         </button>
                         </button>
                     </pwm:if>
                     </pwm:if>
                 </pwm:if>
                 </pwm:if>
-                <% } %>
             </form>
             </form>
         </div>
         </div>
     </div>
     </div>

+ 11 - 0
webapp/src/main/webapp/WEB-INF/jsp/setupresponses.jsp

@@ -26,6 +26,7 @@
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <% final SetupResponsesBean responseBean = (SetupResponsesBean)JspUtility.getAttribute(pageContext, PwmRequestAttribute.ModuleBean); %>
 <% final SetupResponsesBean responseBean = (SetupResponsesBean)JspUtility.getAttribute(pageContext, PwmRequestAttribute.ModuleBean); %>
+<% final boolean allowSkip = JspUtility.getBooleanAttribute( pageContext, PwmRequestAttribute.SetupResponses_AllowSkip ); %>
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
 <%@ include file="fragment/header.jsp" %>
 <%@ include file="fragment/header.jsp" %>
 <body class="nihilo">
 <body class="nihilo">
@@ -50,11 +51,21 @@
                 </button>
                 </button>
                 <%@ include file="/WEB-INF/jsp/fragment/cancel-button.jsp" %>
                 <%@ include file="/WEB-INF/jsp/fragment/cancel-button.jsp" %>
                 <input type="hidden" id="pwmFormID" name="pwmFormID" value="<pwm:FormID/>"/>
                 <input type="hidden" id="pwmFormID" name="pwmFormID" value="<pwm:FormID/>"/>
+                <% if (allowSkip) { %>
+                <button type="submit" name="skip" class="btn" id="skipbutton" form="skipForm">
+                    <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-fighter-jet"></span></pwm:if>
+                    <pwm:display key="Button_Skip"/>
+                </button>
+                <% } %>
             </div>
             </div>
         </form>
         </form>
     </div>
     </div>
     <div class="push"></div>
     <div class="push"></div>
 </div>
 </div>
+<form class="hidden" action="<pwm:current-url/>" method="post" name="skipForm" id="skipForm" enctype="application/x-www-form-urlencoded" class="pwmForm">
+    <input type="hidden" name="<%=PwmConstants.PARAM_ACTION_REQUEST%>" value="skip"/>
+    <input type="hidden" name="<%=PwmConstants.PARAM_FORM_ID%>" value="<pwm:FormID/>"/>
+</form>
 <pwm:script>
 <pwm:script>
     <script type="text/javascript">
     <script type="text/javascript">
         PWM_GLOBAL['responseMode'] = "user";
         PWM_GLOBAL['responseMode'] = "user";