Parcourir la source

Merge remote-tracking branch 'origin/feature/advanced-search' into feature/advanced-search

jalbr74 il y a 6 ans
Parent
commit
1f97a14b0b
33 fichiers modifiés avec 269 ajouts et 149 suppressions
  1. 1 1
      build/spotbugs-exclude.xml
  2. 0 5
      data-service/pom.xml
  3. 3 3
      pom.xml
  4. 3 3
      rest-test-service/pom.xml
  5. 4 41
      server/pom.xml
  6. 2 0
      server/src/main/java/password/pwm/config/PwmSetting.java
  7. 10 10
      server/src/main/java/password/pwm/config/PwmSettingTemplate.java
  8. 9 3
      server/src/main/java/password/pwm/http/PwmSession.java
  9. 6 0
      server/src/main/java/password/pwm/http/filter/AuthenticationFilter.java
  10. 1 1
      server/src/main/java/password/pwm/http/servlet/SetupOtpServlet.java
  11. 6 0
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskSearchRequestBean.java
  12. 3 2
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java
  13. 28 8
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServletUtil.java
  14. 4 28
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java
  15. 19 0
      server/src/main/java/password/pwm/http/servlet/peoplesearch/SearchRequestBean.java
  16. 18 0
      server/src/main/java/password/pwm/util/CaptchaUtility.java
  17. 8 1
      server/src/main/java/password/pwm/util/db/JDBCDriverLoader.java
  18. 28 23
      server/src/main/java/password/pwm/util/java/XmlUtil.java
  19. 4 2
      server/src/main/java/password/pwm/util/operations/OtpService.java
  20. 2 0
      server/src/main/java/password/pwm/util/operations/otp/DbOtpOperator.java
  21. 4 5
      server/src/main/java/password/pwm/util/operations/otp/LdapOtpOperator.java
  22. 2 0
      server/src/main/java/password/pwm/util/operations/otp/LocalDbOtpOperator.java
  23. 2 0
      server/src/main/java/password/pwm/util/operations/otp/OtpOperator.java
  24. 9 0
      server/src/main/resources/password/pwm/config/PwmSetting.xml
  25. 6 4
      server/src/main/resources/password/pwm/i18n/PwmSetting.properties
  26. 36 0
      webapp/pom.xml
  27. 1 1
      webapp/src/main/webapp/WEB-INF/jsp/activateuser.jsp
  28. 1 1
      webapp/src/main/webapp/WEB-INF/jsp/forgottenpassword-search.jsp
  29. 1 1
      webapp/src/main/webapp/WEB-INF/jsp/forgottenusername-search.jsp
  30. 45 3
      webapp/src/main/webapp/WEB-INF/jsp/fragment/captcha-embed.jsp
  31. 1 1
      webapp/src/main/webapp/WEB-INF/jsp/login.jsp
  32. 1 1
      webapp/src/main/webapp/WEB-INF/jsp/newuser.jsp
  33. 1 1
      webapp/src/main/webapp/public/resources/js/main.js

+ 1 - 1
build/spotbugs-exclude.xml

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <FindBugsFilter>
     <Match>
-        <Bug pattern="SE_NO_SERIALVERSIONID,IC_INIT_CIRCULARITY,RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE,DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED,SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING"/>
+        <Bug pattern="SE_NO_SERIALVERSIONID,RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE,SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING"/>
     </Match>
     <Match>
         <!-- due to bug https://github.com/spotbugs/spotbugs/issues/493 in spotbugs 3.1.3 -->

+ 0 - 5
data-service/pom.xml

@@ -157,11 +157,6 @@
             <artifactId>log4j</artifactId>
             <version>1.2.17</version>
         </dependency>
-        <dependency>
-            <groupId>org.apache.axis</groupId>
-            <artifactId>axis</artifactId>
-            <version>1.4</version>
-        </dependency>
         <dependency>
             <groupId>org.jdom</groupId>
             <artifactId>jdom2</artifactId>

+ 3 - 3
pom.xml

@@ -235,12 +235,12 @@
             <plugin>
                 <groupId>com.github.spotbugs</groupId>
                 <artifactId>spotbugs-maven-plugin</artifactId>
-                <version>3.1.6</version>
+                <version>3.1.7</version>
                 <dependencies>
                     <dependency>
                         <groupId>com.github.spotbugs</groupId>
                         <artifactId>spotbugs</artifactId>
-                        <version>3.1.7</version>
+                        <version>3.1.8</version>
                     </dependency>
                 </dependencies>
                 <configuration>
@@ -289,7 +289,7 @@
         <dependency>
             <groupId>com.github.spotbugs</groupId>
             <artifactId>spotbugs-annotations</artifactId>
-            <version>3.1.7</version>
+            <version>3.1.8</version>
             <scope>provided</scope>
         </dependency>
     </dependencies>

+ 3 - 3
rest-test-service/pom.xml

@@ -12,7 +12,7 @@
     <artifactId>rest-test-service</artifactId>
     <packaging>war</packaging>
 
-    <name>PWM Password Self Service: Web Service Test Server</name>
+    <name>PWM Password Self Service: REST Test Server</name>
 
     <properties>
         <project.root.basedir>${project.basedir}/..</project.root.basedir>
@@ -69,13 +69,13 @@
         <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
-            <version>2.13.0</version>
+            <version>2.23.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.assertj</groupId>
             <artifactId>assertj-core</artifactId>
-            <version>3.9.1</version>
+            <version>3.11.1</version>
             <scope>test</scope>
         </dependency>
         <dependency>

+ 4 - 41
server/pom.xml

@@ -80,35 +80,8 @@
                     </execution>
                 </executions>
                 <configuration>
-                    <outputFile>${project.build.directory}/classes/attribution.xml</outputFile>
+                    <outputFile>${project.build.directory}/classes/server-attribution.xml</outputFile>
                     <dependencyOverrides>
-                        <dependencyOverride>
-                            <forDependency>org.apache.axis:axis</forDependency>
-                            <projectUrl>https://axis.apache.org/axis/</projectUrl>
-                            <license>
-                                <name>Apache License, Version 2.0</name>
-                                <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
-                            </license>
-                        </dependencyOverride>
-                        <dependencyOverride>
-                            <forDependency>net.iharder:base64</forDependency>
-                            <license>
-                                <name>Public Domain (any license you desire)</name>
-                                <url>http://iharder.sourceforge.net/current/java/base64/</url>
-                            </license>
-                        </dependencyOverride>
-                        <dependencyOverride>
-                            <forDependency>javax.xml:jaxrpc-api</forDependency>
-                            <projectUrl>https://java.net/projects/jax-rpc/</projectUrl>
-                            <license>
-                                <name>CDDL-1.0</name>
-                                <url>https://opensource.org/licenses/cddl1.php</url>
-                            </license>
-                        </dependencyOverride>
-                        <dependencyOverride>
-                            <forDependency>org.hamcrest:hamcrest-core</forDependency>
-                            <projectUrl>http://hamcrest.org/JavaHamcrest/</projectUrl>
-                        </dependencyOverride>
                         <dependencyOverride>
                             <forDependency>jaxen:jaxen</forDependency>
                             <license>
@@ -223,13 +196,13 @@
         <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
-            <version>2.21.0</version>
+            <version>2.23.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.assertj</groupId>
             <artifactId>assertj-core</artifactId>
-            <version>3.10.0</version>
+            <version>3.11.1</version>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -296,7 +269,7 @@
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-text</artifactId>
-            <version>1.4</version>
+            <version>1.6</version>
         </dependency>
         <dependency>
             <groupId>org.apache.commons</groupId>
@@ -338,11 +311,6 @@
             <artifactId>log4j</artifactId>
             <version>1.2.17</version>
         </dependency>
-        <dependency>
-            <groupId>org.apache.axis</groupId>
-            <artifactId>axis</artifactId>
-            <version>1.4</version>
-        </dependency>
         <dependency>
             <groupId>org.jasig.cas.client</groupId>
             <artifactId>cas-client-core</artifactId>
@@ -363,11 +331,6 @@
             <artifactId>bcpkix-jdk15on</artifactId>
             <version>1.60</version>
         </dependency>
-        <dependency>
-            <groupId>javax.xml</groupId>
-            <artifactId>jaxrpc-api</artifactId>
-            <version>1.1</version>
-        </dependency>
         <dependency>
             <groupId>jaxen</groupId>
             <artifactId>jaxen</artifactId>

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

@@ -581,6 +581,8 @@ public enum PwmSetting
             "captcha.skip.cookie", PwmSettingSyntax.STRING, PwmSettingCategory.CAPTCHA ),
     CAPTCHA_INTRUDER_COUNT_TRIGGER(
             "captcha.intruderAttemptTrigger", PwmSettingSyntax.NUMERIC, PwmSettingCategory.CAPTCHA ),
+    CAPTCHA_RECAPTCHA_MODE(
+            "captcha.recaptcha.mode", PwmSettingSyntax.SELECT, PwmSettingCategory.CAPTCHA ),
 
     // intruder detection
     INTRUDER_ENABLE(

+ 10 - 10
server/src/main/java/password/pwm/config/PwmSettingTemplate.java

@@ -26,6 +26,9 @@ import org.jdom2.Attribute;
 import org.jdom2.Element;
 import password.pwm.util.java.JavaHelper;
 
+import java.util.EnumMap;
+import java.util.Map;
+
 public enum PwmSettingTemplate
 {
     NOVL( Type.LDAP_VENDOR ),
@@ -83,18 +86,15 @@ public enum PwmSettingTemplate
         STORAGE,
         DB_VENDOR,;
 
-        static
-        {
-            LDAP_VENDOR.defaultValue = DEFAULT;
-            STORAGE.defaultValue = LDAP;
-            DB_VENDOR.defaultValue = DB_OTHER;
-        }
-
-        private PwmSettingTemplate defaultValue;
-
+        // done using map instead of static values to avoid initialization circularity bug
         public PwmSettingTemplate getDefaultValue( )
         {
-            return defaultValue;
+            final Map<Type, PwmSettingTemplate> defaultValueMap = new EnumMap<>( Type.class );
+            defaultValueMap.put( LDAP_VENDOR, DEFAULT );
+            defaultValueMap.put( STORAGE, LDAP );
+            defaultValueMap.put( DB_VENDOR, DB_OTHER );
+
+            return defaultValueMap.get( this );
         }
     }
 }

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

@@ -267,9 +267,10 @@ public class PwmSession implements Serializable
 
         if ( pwmRequest != null )
         {
+
             final String nonceCookieName = pwmRequest.getConfig().readAppProperty( AppProperty.HTTP_COOKIE_NONCE_NAME );
-            pwmRequest.getPwmResponse().removeCookie( nonceCookieName, PwmHttpResponseWrapper.CookiePath.Application );
             pwmRequest.setAttribute( PwmRequestAttribute.CookieNonce, null );
+            pwmRequest.getPwmResponse().removeCookie( nonceCookieName, PwmHttpResponseWrapper.CookiePath.Application );
 
             try
             {
@@ -377,6 +378,7 @@ public class PwmSession implements Serializable
             nonce = pwmRequest.readCookie( cookieName );
         }
 
+        boolean newNonce = false;
         if ( nonce == null || nonce.length() < length )
         {
             // random value
@@ -386,6 +388,7 @@ public class PwmSession implements Serializable
             final String prefix = Long.toString( System.currentTimeMillis(), Character.MAX_RADIX );
 
             nonce = random + prefix;
+            newNonce = true;
         }
 
         final PwmSecurityKey securityKey = pwmRequest.getConfig().getSecurityKey();
@@ -393,8 +396,11 @@ public class PwmSession implements Serializable
         final String hashValue = pwmRequest.getPwmApplication().getSecureService().hash( concatValue );
         final PwmSecurityKey pwmSecurityKey = new PwmSecurityKey( hashValue );
 
-        pwmRequest.setAttribute( PwmRequestAttribute.CookieNonce, nonce );
-        pwmRequest.getPwmResponse().writeCookie( cookieName, nonce, -1, PwmHttpResponseWrapper.CookiePath.Application );
+        if ( newNonce )
+        {
+            pwmRequest.setAttribute( PwmRequestAttribute.CookieNonce, nonce );
+            pwmRequest.getPwmResponse().writeCookie( cookieName, nonce, -1, PwmHttpResponseWrapper.CookiePath.Application );
+        }
 
         return pwmSecurityKey;
     }

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

@@ -322,6 +322,12 @@ public class AuthenticationFilter extends AbstractPwmFilter
             return;
         }
 
+        if ( pwmRequest.isJsonRequest() )
+        {
+            pwmRequest.respondWithError( new ErrorInformation( PwmError.ERROR_AUTHENTICATION_REQUIRED ) );
+            return;
+        }
+
         //user is not authenticated so forward to LoginPage.
         LOGGER.trace( pwmSession.getLabel(),
                 "user requested resource requiring authentication (" + req.getRequestURI()

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

@@ -330,7 +330,7 @@ public class SetupOtpServlet extends ControlledPwmServlet
         final UserIdentity theUser = pwmSession.getUserInfo().getUserIdentity();
         try
         {
-            service.clearOTPUserConfiguration( pwmSession, theUser );
+            service.clearOTPUserConfiguration( pwmSession, theUser, pwmSession.getSessionManager().getActor( pwmApplication ) );
         }
         catch ( PwmOperationalException e )
         {

+ 6 - 0
server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskSearchRequestBean.java

@@ -38,4 +38,10 @@ public class HelpdeskSearchRequestBean implements Serializable
 
     private String username;
     private List<SearchRequestBean.SearchValue> searchValues;
+
+    public List<SearchRequestBean.SearchValue> nonEmptySearchValues()
+    {
+        return SearchRequestBean.filterNonEmptySearchValues( getSearchValues() );
+    }
+
 }

+ 3 - 2
server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java

@@ -478,7 +478,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
 
             case advanced:
             {
-                if ( JavaHelper.isEmpty( searchRequest.getSearchValues() ) )
+                if ( JavaHelper.isEmpty( searchRequest.nonEmptySearchValues() ) )
                 {
                     return HelpdeskSearchResultsBean.emptyResult();
                 }
@@ -988,7 +988,8 @@ public class HelpdeskServlet extends ControlledPwmServlet
         {
 
             final OtpService service = pwmRequest.getPwmApplication().getOtpService();
-            service.clearOTPUserConfiguration( pwmRequest.getPwmSession(), userIdentity );
+            final ChaiUser chaiUser = getChaiUser( pwmRequest, helpdeskProfile, userIdentity );
+            service.clearOTPUserConfiguration( pwmRequest.getPwmSession(), userIdentity, chaiUser );
             {
                 // mark the event log
                 final HelpdeskAuditRecord auditRecord = new AuditRecordFactory( pwmRequest ).createHelpdeskAuditRecord(

+ 28 - 8
server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServletUtil.java

@@ -55,7 +55,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
-class HelpdeskServletUtil
+public class HelpdeskServletUtil
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( HelpdeskServletUtil.class );
 
@@ -106,7 +106,16 @@ class HelpdeskServletUtil
     )
     {
         final List<String> defaultObjectClasses = configuration.readSettingAsStringArray( PwmSetting.DEFAULT_OBJECT_CLASSES );
-        final List<FormConfiguration> searchAttributes = helpdeskProfile.readSettingAsForm( PwmSetting.HELPDESK_SEARCH_RESULT_FORM );
+        final List<FormConfiguration> searchAttributes = helpdeskProfile.readSettingAsForm( PwmSetting.HELPDESK_SEARCH_FORM );
+        return makeAdvancedSearchFilter( defaultObjectClasses, searchAttributes, attributesInSearchRequest );
+    }
+
+    public static String makeAdvancedSearchFilter(
+            final List<String> defaultObjectClasses,
+            final List<FormConfiguration> searchAttributes,
+            final Map<String, String> attributesInSearchRequest
+    )
+    {
         final StringBuilder filter = new StringBuilder();
 
         //open AND clause for objectclasses and attributes
@@ -128,7 +137,23 @@ class HelpdeskServletUtil
                 final String value = attributesInSearchRequest.get( searchAttribute );
                 if ( !StringUtil.isEmpty( value ) )
                 {
-                    filter.append( "(" ).append( searchAttribute ).append( "=*%" ).append( searchAttribute ).append( "%*)" );
+                    filter.append( "(" ).append( searchAttribute ).append( "=" );
+
+                    switch ( formConfiguration.getType() )
+                    {
+                        case select:
+                        {
+                            // value is specified by admin, so wildcards are not required
+                            filter.append( "%" ).append( searchAttribute ).append( "%)" );
+                        }
+                        break;
+
+                        default:
+                        {
+                            filter.append( "*%" ).append( searchAttribute ).append( "%*)" );
+                        }
+                        break;
+                    }
                 }
             }
         }
@@ -142,11 +167,6 @@ class HelpdeskServletUtil
     }
 
 
-
-
-
-
-
     static void checkIfUserIdentityViewable(
             final PwmRequest pwmRequest,
             final HelpdeskProfile helpdeskProfile,

+ 4 - 28
server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java

@@ -41,6 +41,7 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmURL;
+import password.pwm.http.servlet.helpdesk.HelpdeskServletUtil;
 import password.pwm.i18n.Display;
 import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.LdapPermissionTester;
@@ -706,34 +707,9 @@ class PeopleSearchDataReader
     private String makeAdvancedFilter( final Map<String, String> attributesInSearchRequest )
     {
         final List<String> defaultObjectClasses = pwmRequest.getConfig().readSettingAsStringArray( PwmSetting.DEFAULT_OBJECT_CLASSES );
-        final Set<String> searchAttributes = peopleSearchConfiguration.getSearchAttributes();
-        final StringBuilder filter = new StringBuilder();
-
-        //open AND clause for objectclasses and attributes
-        filter.append( "(&" );
-        for ( final String objectClass : defaultObjectClasses )
-        {
-            filter.append( "(objectClass=" ).append( objectClass ).append( ")" );
-        }
-
-        // open AND clause for attributes
-        filter.append( "(&" );
+        final List<FormConfiguration> searchAttributes = peopleSearchConfiguration.getSearchForm();
 
-        for ( final String searchAttribute : searchAttributes )
-        {
-            final String value = attributesInSearchRequest.get( searchAttribute );
-            if ( !StringUtil.isEmpty( value ) )
-            {
-                filter.append( "(" ).append( searchAttribute ).append( "=*%" ).append( searchAttribute ).append( "%*)" );
-            }
-        }
-
-        // close attribute clause
-        filter.append( ")" );
-
-        // close AND clause
-        filter.append( ")" );
-        return filter.toString();
+        return HelpdeskServletUtil.makeAdvancedSearchFilter( defaultObjectClasses, searchAttributes, attributesInSearchRequest );
     }
 
     private boolean useProxy( )
@@ -841,7 +817,7 @@ class PeopleSearchDataReader
 
                 case advanced:
                 {
-                    if ( JavaHelper.isEmpty( searchRequest.getSearchValues() ) )
+                    if ( JavaHelper.isEmpty( searchRequest.nonEmptySearchValues() ) )
                     {
                         return SearchResultBean.builder().searchResults( Collections.emptyList() ).build();
                     }

+ 19 - 0
server/src/main/java/password/pwm/http/servlet/peoplesearch/SearchRequestBean.java

@@ -24,8 +24,10 @@ package password.pwm.http.servlet.peoplesearch;
 
 import lombok.Builder;
 import lombok.Value;
+import password.pwm.util.java.StringUtil;
 
 import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -64,4 +66,21 @@ public class SearchRequestBean implements Serializable
         }
         return Collections.unmodifiableMap( returnMap );
     }
+
+    public List<SearchValue> nonEmptySearchValues()
+    {
+        return filterNonEmptySearchValues( getSearchValues() );
+    }
+
+    public static List<SearchValue> filterNonEmptySearchValues( final List<SearchValue> input )
+    {
+        final List<SearchValue> returnList = input == null
+                ? new ArrayList<>()
+                : new ArrayList<>( input );
+
+        returnList.removeIf( searchValue -> StringUtil.isEmpty( searchValue.getKey() )
+                || StringUtil.isEmpty( searchValue.getValue() ) );
+
+        return Collections.unmodifiableList( returnList );
+    }
 }

+ 18 - 0
server/src/main/java/password/pwm/util/CaptchaUtility.java

@@ -48,6 +48,7 @@ import password.pwm.svc.intruder.IntruderManager;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 
 import javax.servlet.ServletException;
@@ -66,6 +67,16 @@ public class CaptchaUtility
 
     private static final String COOKIE_SKIP_INSTANCE_VALUE = "INSTANCEID";
 
+    public enum CaptchaMode
+    {
+        V3,
+        V3_INVISIBLE,
+    }
+
+    public static CaptchaMode readCaptchaMode( final PwmRequest pwmRequest )
+    {
+        return pwmRequest.getConfig().readSettingAsEnum( PwmSetting.CAPTCHA_RECAPTCHA_MODE, CaptchaMode.class );
+    }
 
     /**
      * Verify a reCaptcha request.  The reCaptcha request API is documented at
@@ -95,6 +106,13 @@ public class CaptchaUtility
             return true;
         }
 
+        if ( StringUtil.isEmpty( recaptchaResponse ) )
+        {
+            final String msg = "missing recaptcha validation response";
+            final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_CAPTCHA_API_ERROR, msg );
+            throw new PwmUnrecoverableException( errorInfo );
+        }
+
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final PasswordData privateKey = pwmApplication.getConfig().readSettingAsPassword( PwmSetting.RECAPTCHA_KEY_PRIVATE );
 

+ 8 - 1
server/src/main/java/password/pwm/util/db/JDBCDriverLoader.java

@@ -41,6 +41,8 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.net.URL;
 import java.net.URLClassLoader;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
 import java.sql.Driver;
 import java.util.ArrayList;
 import java.util.List;
@@ -149,6 +151,7 @@ public class JDBCDriverLoader
 
         private static final PwmLogger LOGGER = PwmLogger.forClass( XeusJarClassDriverLoader.class, true );
 
+
         @Override
         public Driver loadDriver( final PwmApplication pwmApplication, final DBConfiguration dbConfiguration )
                 throws DatabaseException
@@ -158,7 +161,11 @@ public class JDBCDriverLoader
             try
             {
                 LOGGER.debug( "loading JDBC database driver stored in configuration" );
-                final JarClassLoader jarClassLoader = new JarClassLoader();
+
+                final JarClassLoader jarClassLoader = AccessController.doPrivileged(
+                        ( PrivilegedAction<JarClassLoader> ) JarClassLoader::new
+                );
+
                 jarClassLoader.add( new ByteArrayInputStream( jdbcDriverBytes ) );
                 final JclObjectFactory jclObjectFactory = JclObjectFactory.getInstance( true );
 

+ 28 - 23
server/src/main/java/password/pwm/util/java/XmlUtil.java

@@ -42,6 +42,7 @@ import java.io.Reader;
 import java.io.Writer;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -119,40 +120,44 @@ public class XmlUtil
 
     public static List<DependencyInfo> getLicenseInfos( ) throws PwmUnrecoverableException
     {
+        final List<String> attributionFiles = Arrays.asList( "/server-attribution.xml", "/webapp-attribution.xml" );
         final List<DependencyInfo> returnList = new ArrayList<>();
 
-        final InputStream attributionInputStream = XmlUtil.class.getResourceAsStream( "/attribution.xml" );
-
-        if ( attributionInputStream != null )
+        for ( final String attributionFile : attributionFiles )
         {
-            final Document document = XmlUtil.parseXml( attributionInputStream );
-            final Element dependencies = document.getRootElement().getChild( "dependencies" );
+            final InputStream attributionInputStream = XmlUtil.class.getResourceAsStream( attributionFile );
 
-            for ( final Element dependency : dependencies.getChildren( "dependency" ) )
+            if ( attributionInputStream != null )
             {
-                final String projectUrl = dependency.getChildText( "projectUrl" );
-                final String name = dependency.getChildText( "name" );
-                final String artifactId = dependency.getChildText( "artifactId" );
-                final String version = dependency.getChildText( "version" );
-                final String type = dependency.getChildText( "type" );
+                final Document document = XmlUtil.parseXml( attributionInputStream );
+                final Element dependencies = document.getRootElement().getChild( "dependencies" );
 
-                final List<LicenseInfo> licenseInfos = new ArrayList<>();
+                for ( final Element dependency : dependencies.getChildren( "dependency" ) )
                 {
-                    final Element licenses = dependency.getChild( "licenses" );
-                    final List<Element> licenseList = licenses.getChildren( "license" );
-                    for ( final Element license : licenseList )
+                    final String projectUrl = dependency.getChildText( "projectUrl" );
+                    final String name = dependency.getChildText( "name" );
+                    final String artifactId = dependency.getChildText( "artifactId" );
+                    final String version = dependency.getChildText( "version" );
+                    final String type = dependency.getChildText( "type" );
+
+                    final List<LicenseInfo> licenseInfos = new ArrayList<>();
                     {
-                        final String licenseUrl = license.getChildText( "url" );
-                        final String licenseName = license.getChildText( "name" );
-                        final LicenseInfo licenseInfo = new LicenseInfo( licenseUrl, licenseName );
-                        licenseInfos.add( licenseInfo );
+                        final Element licenses = dependency.getChild( "licenses" );
+                        final List<Element> licenseList = licenses.getChildren( "license" );
+                        for ( final Element license : licenseList )
+                        {
+                            final String licenseUrl = license.getChildText( "url" );
+                            final String licenseName = license.getChildText( "name" );
+                            final LicenseInfo licenseInfo = new LicenseInfo( licenseUrl, licenseName );
+                            licenseInfos.add( licenseInfo );
+                        }
                     }
-                }
 
-                final DependencyInfo dependencyInfo = new DependencyInfo( projectUrl, name, artifactId, version, type,
-                        Collections.unmodifiableList( licenseInfos ) );
+                    final DependencyInfo dependencyInfo = new DependencyInfo( projectUrl, name, artifactId, version, type,
+                            Collections.unmodifiableList( licenseInfos ) );
 
-                returnList.add( dependencyInfo );
+                    returnList.add( dependencyInfo );
+                }
             }
         }
         return Collections.unmodifiableList( returnList );

+ 4 - 2
server/src/main/java/password/pwm/util/operations/OtpService.java

@@ -22,6 +22,7 @@
 
 package password.pwm.util.operations;
 
+import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import lombok.Getter;
 import org.apache.commons.codec.binary.Base32;
@@ -425,7 +426,8 @@ public class OtpService implements PwmService
 
     public void clearOTPUserConfiguration(
             final PwmSession pwmSession,
-            final UserIdentity userIdentity
+            final UserIdentity userIdentity,
+            final ChaiUser chaiUser
     )
             throws PwmOperationalException, ChaiUnavailableException, PwmUnrecoverableException
     {
@@ -450,7 +452,7 @@ public class OtpService implements PwmService
                 {
                     try
                     {
-                        operator.clearOtpUserConfiguration( pwmSession, userIdentity, userGUID );
+                        operator.clearOtpUserConfiguration( pwmSession, userIdentity, chaiUser, userGUID );
                         successes++;
                     }
                     catch ( PwmUnrecoverableException e )

+ 2 - 0
server/src/main/java/password/pwm/util/operations/otp/DbOtpOperator.java

@@ -28,6 +28,7 @@
 
 package password.pwm.util.operations.otp;
 
+import com.novell.ldapchai.ChaiUser;
 import password.pwm.PwmApplication;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
@@ -135,6 +136,7 @@ public class DbOtpOperator extends AbstractOtpOperator
     public void clearOtpUserConfiguration(
             final PwmSession pwmSession,
             final UserIdentity theUser,
+            final ChaiUser chaiUser,
             final String userGUID
     )
             throws PwmUnrecoverableException

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

@@ -159,8 +159,10 @@ public class LdapOtpOperator extends AbstractOtpOperator
     public void clearOtpUserConfiguration(
             final PwmSession pwmSession,
             final UserIdentity userIdentity,
+            final ChaiUser chaiUser,
             final String userGuid
-    ) throws PwmUnrecoverableException
+    )
+            throws PwmUnrecoverableException
     {
         final Configuration config = pwmApplication.getConfig();
 
@@ -174,10 +176,7 @@ public class LdapOtpOperator extends AbstractOtpOperator
         }
         try
         {
-            final ChaiUser theUser = pwmSession == null
-                    ? pwmApplication.getProxiedChaiUser( userIdentity )
-                    : pwmSession.getSessionManager().getActor( pwmApplication, userIdentity );
-            theUser.deleteAttribute( ldapStorageAttribute, null );
+            chaiUser.deleteAttribute( ldapStorageAttribute, null );
             LOGGER.info( "cleared OTP secret for user to chai-ldap format" );
         }
         catch ( ChaiOperationException e )

+ 2 - 0
server/src/main/java/password/pwm/util/operations/otp/LocalDbOtpOperator.java

@@ -28,6 +28,7 @@
 
 package password.pwm.util.operations.otp;
 
+import com.novell.ldapchai.ChaiUser;
 import password.pwm.PwmApplication;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
@@ -163,6 +164,7 @@ public class LocalDbOtpOperator extends AbstractOtpOperator
     public void clearOtpUserConfiguration(
             final PwmSession pwmSession,
             final UserIdentity theUser,
+            final ChaiUser chaiUser,
             final String userGUID
     )
             throws PwmUnrecoverableException

+ 2 - 0
server/src/main/java/password/pwm/util/operations/otp/OtpOperator.java

@@ -27,6 +27,7 @@
 
 package password.pwm.util.operations.otp;
 
+import com.novell.ldapchai.ChaiUser;
 import password.pwm.bean.UserIdentity;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmSession;
@@ -54,6 +55,7 @@ public interface OtpOperator
     void clearOtpUserConfiguration(
             PwmSession pwmSession,
             UserIdentity theUser,
+            ChaiUser chaiUser,
             String userGuid
     )
             throws PwmUnrecoverableException;

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

@@ -1487,6 +1487,15 @@
             <value>0</value>
         </default>
     </setting>
+    <setting hidden="false" key="captcha.recaptcha.mode" level="2">
+        <default>
+            <value>V3</value>
+        </default>
+        <options>
+            <option value="V3">reCaptcha Version 3</option>
+            <option value="V3_INVISIBLE">reCaptcha Version 3 - Invisible</option>
+        </options>
+    </setting>
     <setting hidden="false" key="security.formNonce.enable" level="2" required="true">
         <default>
             <value>true</value>

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

@@ -237,6 +237,7 @@ Setting_Description_audit.userEvent.toAddress=Specify one or more email addresse
 Setting_Description_basicAuth.enable=Enables Basic Authentication.
 Setting_Description_captcha.intruderAttemptTrigger=Specify a number of intruder attempts before @PwmAppName@ requires CAPTCHA.  If set to 0, @PwmAppName@ ignores the intruder attempt count and it always requires CAPTCHA.   @PwmAppName@ considers intruder attempts for the current session and for the source network address.<br/><br/>The recommended value for this setting is 0.  Determined network attackers might be able to bypass the CAPTCHA verification altogether if you use this setting.
 Setting_Description_captcha.protectedPages=Select the pages @PwmAppName@ protects with CAPTCHA.  @PwmAppName@ requires the CAPTCHA validation only once per session.  Thus, after a user passes the CAPTCHA validation during a session, @PwmAppName@ does not force the user to pass the CAPTCHA again despite the user accessing a second module enabled here.
+Setting_Description_captcha.recaptcha.mode=Select the reCaptcha mode to use.
 Setting_Description_captcha.recaptcha.privateKey=Add a private reCAPTCHA key.  If blank, @PwmAppName@ does not perform the CAPTCHA verification.
 Setting_Description_captcha.recaptcha.publicKey=Add a public reCAPTCHA key.  If blank, @PwmAppName@ does not perform the CAPTCHA verification.
 Setting_Description_captcha.skip.cookie=Specify a known browser cookie value in a cookie named 'captcha-key'.  This allows @PwmAppName@ to skip the CAPTCHA request if the value of the browser cookie is correct.  @PwmAppName@ stores the cookie value in the browser after a successful CAPTCHA check.<br/><br/>If blank, then @PwmAppName@ does not store nor read the browser cookie.  If set to 'INSTANCEID', then @PwmAppName@ uses the instanceID.  If set to any other value, then @PwmAppName@ uses the literal value.
@@ -393,7 +394,7 @@ Setting_Description_helpdesk.displayName=Specify the display name you use to ide
 Setting_Description_helpdesk.displayName.cardLabels=Specify the display labels for the user panel in the Help Desk Search detail.  You can use LDAP attribute value such as <code>@LDAP\:givenName@</code> macros.
 Setting_Description_helpdesk.enable=Enable this option to enable the Help Desk module.
 Setting_Description_helpdesk.enablePhotos=Enable photos in helpdesk search screen 
-Setting_Description_helpdesk.enableUnlock=Enable this option to enable the Help Desk module users to unlock an (intruder) locked account.
+Setting_Description_helpdesk.enableUnlock=Enable this option to enable the Help Desk module users to unlock an LDAP intruder locked account.
 Setting_Description_helpdesk.enforcePasswordPolicy=Enable this option to require that the passwords set by Help Desk must meet the same password policy that normally constrains the user.
 Setting_Description_helpdesk.filter=Specify the LDAP search filter to query the directory.  Substitute <i>%USERNAME%</i> for user supplied user name.  If not specified, @PwmAppName@ auto calculates a search filter based on the Help Desk Search Results.<p>Examples<ul><li>Edirectory\: <code>(&(objectClass\=Person)(|((cn\=*%USERNAME%*)(uid\=*%USERNAME%*)(givenName\=*%USERNAME%*)(sn\=*%USERNAME%*))))</code></li><li>Active Directory\: <code>(&(objectClass\=Person)(|((cn\=*%USERNAME%*)(uid\=*%USERNAME%*)(sAMAccountName\=*%USERNAME%*)(userprincipalname\=*%USERNAME%*)(givenName\=*%USERNAME%*)(sn\=*%USERNAME%*))))</code></li></ul>
 Setting_Description_helpdesk.forcePwExpiration=Enable this option to force the system to expire the password for the users when the help desk operator sets a user's password.
@@ -743,12 +744,13 @@ Setting_Label_audit.system.eventList=System Audit Event Types
 Setting_Label_audit.user.eventList=User Audit Event Types
 Setting_Label_audit.userEvent.toAddress=User Audit Event Email Alerts
 Setting_Label_basicAuth.enable=Enable Basic Authentication
-Setting_Label_captcha.intruderAttemptTrigger=Captcha Intruder Attempt Trigger
+Setting_Label_captcha.intruderAttemptTrigger=CAPTCHA Intruder Attempt Trigger
 Setting_Label_captcha.protectedPages=CAPTCHA Protected Pages
+Setting_Label_captcha.recaptcha.mode=reCAPTCHA Mode
 Setting_Label_captcha.recaptcha.privateKey=reCAPTCHA Secret
 Setting_Label_captcha.recaptcha.publicKey=reCAPTCHA Site Key
-Setting_Label_captcha.skip.cookie=Captcha Skip Cookie
-Setting_Label_captcha.skip.param=Captcha Skip Parameter Value
+Setting_Label_captcha.skip.cookie=CAPTCHA Skip Cookie
+Setting_Label_captcha.skip.param=CAPTCHA Skip Parameter Value
 Setting_Label_cas.clearPassUrl=CAS ClearPass URL
 Setting_Label_cas.clearPass.key=CAS ClearPass Encryption Key
 Setting_Label_cas.clearPass.alg=CAS ClearPass Algorithm

+ 36 - 0
webapp/pom.xml

@@ -155,6 +155,42 @@
                     </execution>
                 </executions>
             </plugin>
+            <plugin>
+                <!-- creates the classes directory early in the build so the attribution plugin doesn't fail -->
+                <artifactId>maven-antrun-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>generate-sources</id>
+                        <phase>generate-sources</phase>
+                        <configuration>
+                            <tasks>
+                                <mkdir dir="${project.build.directory}/classes"/>
+                            </tasks>
+                        </configuration>
+                        <goals>
+                            <goal>run</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <!-- builds xml file of dependencies and licenses for use in about page -->
+                <groupId>com.github.jinnovations</groupId>
+                <artifactId>attribution-maven-plugin</artifactId>
+                <version>0.9.5</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>generate-attribution-file</goal>
+                        </goals>
+                        <phase>generate-resources</phase>
+                    </execution>
+                </executions>
+                <configuration>
+                    <outputFile>${project.build.directory}/classes/webapp-attribution.xml</outputFile>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
 

+ 1 - 1
webapp/src/main/webapp/WEB-INF/jsp/activateuser.jsp

@@ -35,7 +35,7 @@
     <div id="centerbody">
         <h1 id="page-content-title"><pwm:display key="Title_ActivateUser" displayIfMissing="true"/></h1>
         <p><pwm:display key="Display_ActivateUser"/></p>
-        <form action="<pwm:current-url/>" method="post" name="activateUser" enctype="application/x-www-form-urlencoded" class="pwm-form">
+        <form action="<pwm:current-url/>" method="post" name="activateUser" enctype="application/x-www-form-urlencoded" class="pwm-form pwm-form-captcha">
             <%@ include file="fragment/message.jsp" %>
             <%@ include file="/WEB-INF/jsp/fragment/ldap-selector.jsp" %>
             <jsp:include page="fragment/form.jsp"/>

+ 1 - 1
webapp/src/main/webapp/WEB-INF/jsp/forgottenpassword-search.jsp

@@ -37,7 +37,7 @@
         <h1 id="page-content-title"><pwm:display key="Title_ForgottenPassword" displayIfMissing="true"/></h1>
         <p><pwm:display key="Display_ForgottenPassword"/></p>
         <form action="<pwm:current-url/>" method="post" enctype="application/x-www-form-urlencoded" autocomplete="off"
-              name="searchForm" class="pwm-form" id="searchForm">
+              name="searchForm" class="pwm-form pwm-form-captcha" id="searchForm">
             <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
             <%@ include file="/WEB-INF/jsp/fragment/ldap-selector.jsp" %>
             <br/>

+ 1 - 1
webapp/src/main/webapp/WEB-INF/jsp/forgottenusername-search.jsp

@@ -37,7 +37,7 @@
         <p><pwm:display key="Display_ForgottenUsername"/></p>
         <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
         <br/>
-        <form action="<pwm:current-url/>" method="post" enctype="application/x-www-form-urlencoded" name="searchForm" class="pwm-form" id="searchForm" autocomplete="off">
+        <form action="<pwm:current-url/>" method="post" enctype="application/x-www-form-urlencoded" name="searchForm" class="pwm-form pwm-form-captcha" id="searchForm" autocomplete="off">
             <%@ include file="/WEB-INF/jsp/fragment/ldap-selector.jsp" %>
             <jsp:include page="fragment/form.jsp"/>
 

+ 45 - 3
webapp/src/main/webapp/WEB-INF/jsp/fragment/captcha-embed.jsp

@@ -21,12 +21,13 @@
 --%>
 
 <%@ page import="password.pwm.http.JspUtility" %>
-<%@ page import="password.pwm.http.PwmRequest" %>
+<%@ page import="password.pwm.http.PwmRequestAttribute" %>
 <%@ page import="password.pwm.http.tag.value.PwmValue" %>
 <%@ page import="password.pwm.util.CaptchaUtility" %>
-<%@ page import="password.pwm.http.PwmRequestAttribute" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
-<% if (CaptchaUtility.captchaEnabledForRequest(JspUtility.getPwmRequest(pageContext))) { %>
+<% final CaptchaUtility.CaptchaMode captchaMode = CaptchaUtility.readCaptchaMode( JspUtility.getPwmRequest( pageContext ) ); %>
+<% final boolean captchaEnabled = CaptchaUtility.captchaEnabledForRequest( JspUtility.getPwmRequest(pageContext) ); %>
+<% if (captchaEnabled ) { %>
 <% CaptchaUtility.prepareCaptchaDisplay(JspUtility.getPwmRequest(pageContext)); %>
 <div id="recaptcha-container">
 </div>
@@ -34,6 +35,7 @@
     <span><pwm:display key="Display_JavascriptRequired"/></span>
     <a href="<pwm:context/>"><pwm:display key="Title_MainPage"/></a>
 </noscript>
+<% if ( captchaMode == CaptchaUtility.CaptchaMode.V3 ) { %>
 <%-- begin reCaptcha section (http://code.google.com/apis/recaptcha/docs/display.html) --%>
 <pwm:script>
     <script type="text/javascript">
@@ -49,3 +51,43 @@
 </pwm:script>
 <script nonce="<pwm:value name="<%=PwmValue.cspNonce%>"/>" src="<%=(String)JspUtility.getAttribute(pageContext,PwmRequestAttribute.CaptchaClientUrl)%>?onload=onloadCallback&render=explicit" defer async></script>
 <% } %>
+<% if ( captchaMode == CaptchaUtility.CaptchaMode.V3_INVISIBLE ) { %>
+<!-- captcha v3-invisible 1.0 -->
+<input type="hidden" name="g-recaptcha-response" id="g-recaptcha-response"/>
+<pwm:script>
+    <script type="text/javascript">
+        PWM_GLOBAL['startupFunctions'].push(function() {
+            PWM_MAIN.doQuery('.pwm-form-captcha',function(formElement) {
+                PWM_MAIN.addEventHandler(formElement, "submit", function(event){
+                    console.log('entering handleCaptchaFormSubmit');
+
+                    PWM_VAR['captcha-form-element'] = formElement;
+                    PWM_MAIN.cancelEvent(event);
+
+                    grecaptcha.execute();
+                });
+            });
+
+
+        });
+
+        var onloadCaptcha = function() {
+            console.log('entering onloadCaptcha');
+        };
+
+        var postCaptchaFormSubmit = function(response) {
+            console.log('entering postCaptchaFormSubmit, response=' + response);
+            var form = PWM_VAR['captcha-form-element'];
+            PWM_MAIN.getObject('g-recaptcha-response').value = response;
+            PWM_MAIN.handleFormSubmit(form);
+        };
+    </script>
+</pwm:script>
+<div class="g-recaptcha"
+     data-sitekey="<%=JspUtility.getAttribute(pageContext,PwmRequestAttribute.CaptchaPublicKey)%>"
+     data-callback="postCaptchaFormSubmit"
+     data-size="invisible">
+</div>
+<script nonce="<pwm:value name="<%=PwmValue.cspNonce%>"/>" src="<%=(String)JspUtility.getAttribute(pageContext,PwmRequestAttribute.CaptchaClientUrl)%>?onload=onloadCaptcha" async defer></script>
+<% } %>
+<% } %>

+ 1 - 1
webapp/src/main/webapp/WEB-INF/jsp/login.jsp

@@ -48,7 +48,7 @@
         <p>
             <span class="panel-login-display-message"><pwm:display key="Display_Login"/></span>
         </p>
-        <form action="<pwm:current-url/>" method="post" name="login" enctype="application/x-www-form-urlencoded" id="login" autocomplete="off">
+        <form action="<pwm:current-url/>" method="post" name="login" enctype="application/x-www-form-urlencoded" id="login" autocomplete="off" class="pwm-form-captcha">
             <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
             <div class="sign-in">
                 <%@ include file="/WEB-INF/jsp/fragment/ldap-selector.jsp" %>

+ 1 - 1
webapp/src/main/webapp/WEB-INF/jsp/newuser.jsp

@@ -42,7 +42,7 @@
         <%@ include file="fragment/message.jsp" %>
         <br/>
         <form action="<pwm:current-url/>" method="post" name="newUser" enctype="application/x-www-form-urlencoded" autocomplete="off"
-              id="newUserForm" class="pwm-form">
+              id="newUserForm" class="pwm-form pwm-form-captcha">
             <jsp:include page="fragment/form.jsp"/>
 
             <%@ include file="/WEB-INF/jsp/fragment/captcha-embed.jsp"%>

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

@@ -256,7 +256,7 @@ PWM_MAIN.applyFormAttributes = function() {
         formElement.setAttribute('autocorrect', 'off');
     });
 
-    PWM_MAIN.doQuery('.pwm-form',function(formElement) {
+    PWM_MAIN.doQuery('.pwm-form:not(.pwm-form-captcha)',function(formElement) {
         PWM_MAIN.addEventHandler(formElement, "submit", function(event){
             PWM_MAIN.handleFormSubmit(formElement, event);
         });