瀏覽代碼

Merge branch 'master' of github.com:jedwardhawkins/pwm

Joe Hawkins 8 年之前
父節點
當前提交
b16ce65a12
共有 32 個文件被更改,包括 325 次插入463 次删除
  1. 0 1
      src/main/java/password/pwm/PwmConstants.java
  2. 0 18
      src/main/java/password/pwm/bean/LocalSessionStateBean.java
  3. 2 0
      src/main/java/password/pwm/config/PwmSetting.java
  4. 0 4
      src/main/java/password/pwm/http/PwmURL.java
  5. 0 1
      src/main/java/password/pwm/http/filter/ApplicationModeFilter.java
  6. 0 149
      src/main/java/password/pwm/http/filter/CaptchaFilter.java
  7. 0 10
      src/main/java/password/pwm/http/filter/SessionFilter.java
  8. 9 0
      src/main/java/password/pwm/http/servlet/ActivateUserServlet.java
  9. 9 0
      src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java
  10. 21 19
      src/main/java/password/pwm/http/servlet/LoginServlet.java
  11. 0 1
      src/main/java/password/pwm/http/servlet/PwmServletDefinition.java
  12. 10 0
      src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java
  13. 9 0
      src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java
  14. 2 2
      src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchConfiguration.java
  15. 13 0
      src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java
  16. 15 0
      src/main/java/password/pwm/svc/intruder/IntruderManager.java
  17. 131 128
      src/main/java/password/pwm/util/CaptchaUtility.java
  18. 9 0
      src/main/resources/password/pwm/config/PwmSetting.xml
  19. 4 10
      src/main/resources/password/pwm/i18n/PwmSetting.properties
  20. 3 0
      src/main/webapp/WEB-INF/jsp/activateuser.jsp
  21. 0 95
      src/main/webapp/WEB-INF/jsp/captcha.jsp
  22. 2 0
      src/main/webapp/WEB-INF/jsp/forgottenpassword-search.jsp
  23. 3 0
      src/main/webapp/WEB-INF/jsp/forgottenusername-search.jsp
  24. 50 0
      src/main/webapp/WEB-INF/jsp/fragment/captcha-embed.jsp
  25. 9 2
      src/main/webapp/WEB-INF/jsp/login.jsp
  26. 3 0
      src/main/webapp/WEB-INF/jsp/newuser.jsp
  27. 0 8
      src/main/webapp/WEB-INF/web.xml
  28. 5 0
      src/main/webapp/private/index.jsp
  29. 1 1
      src/main/webapp/public/resources/js/configeditor.js
  30. 10 11
      src/main/webapp/public/resources/js/configmanager.js
  31. 5 0
      src/main/webapp/public/resources/js/main.js
  32. 0 3
      src/main/webapp/public/resources/style.css

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

@@ -223,7 +223,6 @@ public abstract class PwmConstants {
         GUEST_UPDATE_SEARCH("guest-search.jsp"),
         ACCOUNT_INFORMATION("accountinformation.jsp"),
         SHORTCUT("shortcut.jsp"),
-        CAPTCHA("captcha.jsp"),
         PEOPLE_SEARCH("peoplesearch.jsp"),
         CONFIG_MANAGER_EDITOR("configeditor.jsp"),
         CONFIG_MANAGER_EDITOR_SUMMARY("configmanager-summary.jsp"),

+ 0 - 18
src/main/java/password/pwm/bean/LocalSessionStateBean.java

@@ -40,7 +40,6 @@ import java.util.Locale;
 public class LocalSessionStateBean implements Serializable {
 // ------------------------------ FIELDS ------------------------------
 
-    private String preCaptchaRequestURL;
     private String srcAddress;
     private String srcHostname;
     private String forwardURL;
@@ -53,7 +52,6 @@ public class LocalSessionStateBean implements Serializable {
     private String sessionVerificationKey = "key";
     private String restClientKey;
 
-    private boolean passedCaptcha;
     private boolean debugInitialized;
     private boolean sessionVerified;
 
@@ -119,14 +117,6 @@ public class LocalSessionStateBean implements Serializable {
         this.logoutURL = logoutURL;
     }
 
-    public String getPreCaptchaRequestURL() {
-        return preCaptchaRequestURL;
-    }
-
-    public void setPreCaptchaRequestURL(final String preCaptchaRequestURL) {
-        this.preCaptchaRequestURL = preCaptchaRequestURL;
-    }
-
     public String getSessionID() {
         return sessionID;
     }
@@ -151,14 +141,6 @@ public class LocalSessionStateBean implements Serializable {
         this.srcHostname = srcHostname;
     }
 
-    public boolean isPassedCaptcha() {
-        return passedCaptcha;
-    }
-
-    public void setPassedCaptcha(final boolean passedCaptcha) {
-        this.passedCaptcha = passedCaptcha;
-    }
-
     public String getSessionVerificationKey() {
         return sessionVerificationKey;
     }

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

@@ -540,6 +540,8 @@ public enum PwmSetting {
             "captcha.skip.param", PwmSettingSyntax.STRING, PwmSettingCategory.CAPTCHA),
     CAPTCHA_SKIP_COOKIE(
             "captcha.skip.cookie", PwmSettingSyntax.STRING, PwmSettingCategory.CAPTCHA),
+    CAPTCHA_INTRUDER_COUNT_TRIGGER(
+            "captcha.intruderAttemptTrigger", PwmSettingSyntax.NUMERIC, PwmSettingCategory.CAPTCHA),
 
     // intruder detection
     INTRUDER_ENABLE(

+ 0 - 4
src/main/java/password/pwm/http/PwmURL.java

@@ -71,10 +71,6 @@ public class PwmURL {
         return isPwmServletURL(PwmServletDefinition.Logout);
     }
 
-    public boolean isCaptchaURL() {
-        return isPwmServletURL(PwmServletDefinition.Captcha);
-    }
-
     public boolean isForgottenPasswordServlet() {
         return isPwmServletURL(PwmServletDefinition.ForgottenPassword);
     }

+ 0 - 1
src/main/java/password/pwm/http/filter/ApplicationModeFilter.java

@@ -125,7 +125,6 @@ public class ApplicationModeFilter extends AbstractPwmFilter {
                         || pwmURL.isConfigGuideURL()
                         || pwmURL.isCommandServletURL()
                         || pwmURL.isReferenceURL()
-                        || pwmURL.isCaptchaURL()
                         || pwmURL.isLoginServlet()
                         || pwmURL.isLogoutURL()
                         || pwmURL.isOauthConsumer()

+ 0 - 149
src/main/java/password/pwm/http/filter/CaptchaFilter.java

@@ -1,149 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2016 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.http.filter;
-
-import password.pwm.PwmApplicationMode;
-import password.pwm.bean.LocalSessionStateBean;
-import password.pwm.config.Configuration;
-import password.pwm.config.PwmSetting;
-import password.pwm.config.option.ApplicationPage;
-import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.http.PwmRequest;
-import password.pwm.http.PwmURL;
-import password.pwm.http.servlet.PwmServletDefinition;
-import password.pwm.util.PasswordData;
-import password.pwm.util.logging.PwmLogger;
-
-import javax.servlet.ServletException;
-import java.io.IOException;
-import java.util.Set;
-
-public class CaptchaFilter extends AbstractPwmFilter {
-
-    private static final PwmLogger LOGGER = PwmLogger.forClass(CaptchaFilter.class);
-
-    @Override
-    boolean isInterested(final PwmApplicationMode mode, final PwmURL pwmURL) {
-        return true;
-    }
-
-    public void processFilter(
-            final PwmApplicationMode mode,
-            final PwmRequest pwmRequest,
-            final PwmFilterChain chain
-    )
-            throws IOException, ServletException
-    {
-        try {
-            final boolean redirectPerformed = performRedirectIfRequired(pwmRequest);
-            if (!redirectPerformed) {
-                chain.doFilter();
-            }
-        } catch (PwmUnrecoverableException e) {
-            LOGGER.fatal("unexpected error processing captcha filter: " + e.getMessage(), e );
-        }
-    }
-
-    private boolean performRedirectIfRequired(
-            final PwmRequest pwmRequest
-    )
-            throws PwmUnrecoverableException, IOException
-    {
-
-        //if captcha is done then stop
-        if (pwmRequest.getPwmSession().getSessionStateBean().isPassedCaptcha()) {
-            return false;
-        }
-
-        //if captcha not enabled then stop
-        if (!checkIfCaptchaEnabled(pwmRequest)) {
-            LOGGER.debug(pwmRequest,"reCaptcha private or public key not configured, skipping captcha check");
-            pwmRequest.getPwmSession().getSessionStateBean().setPassedCaptcha(true);
-            return false;
-        }
-
-        final PwmURL pwmURL = pwmRequest.getURL();
-
-        //if on captcha page, then stop.
-        if (pwmURL.isCaptchaURL()) {
-            return false;
-        }
-
-
-        final Set<ApplicationPage> protectedModules = pwmRequest.getConfig().readSettingAsOptionList(
-                PwmSetting.CAPTCHA_PROTECTED_PAGES,
-                ApplicationPage.class
-        );
-
-        boolean redirectNeeded = false;
-        if (protectedModules != null) {
-            if (protectedModules.contains(ApplicationPage.LOGIN) && pwmURL.isLoginServlet()) {
-                redirectNeeded = true;
-            } else if (protectedModules.contains(ApplicationPage.FORGOTTEN_PASSWORD) && pwmURL.isForgottenPasswordServlet()) {
-                redirectNeeded = true;
-            } else if (protectedModules.contains(ApplicationPage.FORGOTTEN_USERNAME) && pwmURL.isForgottenUsernameServlet()) {
-                redirectNeeded = true;
-            } else if (protectedModules.contains(ApplicationPage.USER_ACTIVATION) && pwmURL.isUserActivationServlet()) {
-                redirectNeeded = true;
-            } else if (protectedModules.contains(ApplicationPage.NEW_USER_REGISTRATION) && pwmURL.isNewUserRegistrationServlet()) {
-                redirectNeeded = true;
-            }
-        }
-
-        if (redirectNeeded) {
-            LOGGER.debug(pwmRequest, "session requires captcha verification, redirecting to Captcha servlet");
-            redirectToCaptchaServlet(pwmRequest);
-            return true;
-        }
-
-        return false;
-    }
-
-    private void redirectToCaptchaServlet(
-            final PwmRequest pwmRequest
-    )
-            throws IOException, PwmUnrecoverableException
-    {
-        final LocalSessionStateBean sessionStateBean = pwmRequest.getPwmSession().getSessionStateBean();
-
-        // store the original requested url
-        if (sessionStateBean.getPreCaptchaRequestURL() == null) {
-            final String urlToStore = pwmRequest.getURLwithQueryString();
-            sessionStateBean.setPreCaptchaRequestURL(urlToStore);
-        }
-
-        pwmRequest.sendRedirect(PwmServletDefinition.Captcha);
-    }
-
-    private boolean checkIfCaptchaEnabled(
-            final PwmRequest pwmRequest
-    )
-            throws PwmUnrecoverableException
-    {
-        final Configuration config = pwmRequest.getPwmApplication().getConfig();
-        final PasswordData privateKey = config.readSettingAsPassword(PwmSetting.RECAPTCHA_KEY_PRIVATE);
-        final String publicKey = config.readSettingAsString(PwmSetting.RECAPTCHA_KEY_PUBLIC);
-
-        return (privateKey != null && publicKey != null && !publicKey.isEmpty());
-    }
-}

+ 0 - 10
src/main/java/password/pwm/http/filter/SessionFilter.java

@@ -215,16 +215,6 @@ public class SessionFilter extends AbstractPwmFilter {
             }
         }
 
-        final String skipCaptcha = pwmRequest.readParameterAsString(PwmConstants.PARAM_SKIP_CAPTCHA);
-        if (skipCaptcha != null && skipCaptcha.length() > 0) {
-            final String configValue = config.readSettingAsString(PwmSetting.CAPTCHA_SKIP_PARAM);
-            if (configValue != null && configValue.equals(skipCaptcha)) {
-                LOGGER.trace(pwmSession, "valid skipCaptcha value in request, skipping captcha check for this session");
-                ssBean.setPassedCaptcha(true);
-            } else {
-                LOGGER.error(pwmSession, "skipCaptcha value is in request, however value '" + skipCaptcha + "' does not match configured value");
-            }
-        }
 
         if ("true".equalsIgnoreCase(pwmRequest.readParameterAsString(
                 pwmRequest.getConfig().readAppProperty(AppProperty.HTTP_PARAM_NAME_PASSWORD_EXPIRED)))) {

+ 9 - 0
src/main/java/password/pwm/http/servlet/ActivateUserServlet.java

@@ -66,6 +66,7 @@ import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.token.TokenPayload;
 import password.pwm.svc.token.TokenService;
 import password.pwm.svc.token.TokenType;
+import password.pwm.util.CaptchaUtility;
 import password.pwm.util.Helper;
 import password.pwm.util.PostChangePasswordAction;
 import password.pwm.util.logging.PwmLogger;
@@ -208,6 +209,14 @@ public class ActivateUserServlet extends AbstractPwmServlet {
         final Configuration config = pwmApplication.getConfig();
         final LocalSessionStateBean ssBean = pwmSession.getSessionStateBean();
 
+        if (!CaptchaUtility.verifyReCaptcha(pwmRequest)) {
+            final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_BAD_CAPTCHA_RESPONSE);
+            LOGGER.debug(pwmRequest, errorInfo);
+            pwmRequest.setResponseError(errorInfo);
+            return;
+        }
+
+
         pwmApplication.getSessionStateService().clearBean(pwmRequest, ActivateUserBean.class);
         final List<FormConfiguration> configuredActivationForm = config.readSettingAsForm(PwmSetting.ACTIVATE_USER_FORM);
 

+ 9 - 0
src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java

@@ -47,6 +47,7 @@ import password.pwm.ldap.UserDataReader;
 import password.pwm.ldap.UserSearchEngine;
 import password.pwm.ldap.UserStatusReader;
 import password.pwm.svc.stats.Statistic;
+import password.pwm.util.CaptchaUtility;
 import password.pwm.util.Helper;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
@@ -128,6 +129,14 @@ public class ForgottenUsernameServlet extends AbstractPwmServlet {
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final LocalSessionStateBean ssBean = pwmSession.getSessionStateBean();
 
+        if (!CaptchaUtility.verifyReCaptcha(pwmRequest)) {
+            final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_BAD_CAPTCHA_RESPONSE);
+            LOGGER.debug(pwmRequest, errorInfo);
+            pwmRequest.setResponseError(errorInfo);
+            forwardToFormJsp(pwmRequest);
+            return;
+        }
+
         final String contextParam = pwmRequest.readParameterAsString(PwmConstants.PARAM_CONTEXT);
         final String ldapProfile = pwmRequest.readParameterAsString(PwmConstants.PARAM_LDAP_PROFILE);
 

+ 21 - 19
src/main/java/password/pwm/http/servlet/LoginServlet.java

@@ -36,6 +36,7 @@ import password.pwm.http.PwmURL;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
 import password.pwm.ldap.auth.SessionAuthenticator;
+import password.pwm.util.CaptchaUtility;
 import password.pwm.util.Helper;
 import password.pwm.util.PasswordData;
 import password.pwm.util.Validator;
@@ -135,13 +136,9 @@ public class LoginServlet extends AbstractPwmServlet {
     private void processLogin(final PwmRequest pwmRequest, final boolean passwordOnly)
             throws PwmUnrecoverableException, ServletException, IOException, ChaiUnavailableException
     {
-        final String username = pwmRequest.readParameterAsString(PwmConstants.PARAM_USERNAME);
-        final PasswordData password = pwmRequest.readParameterAsPassword(PwmConstants.PARAM_PASSWORD);
-        final String context = pwmRequest.readParameterAsString(PwmConstants.PARAM_CONTEXT);
-        final String ldapProfile = pwmRequest.readParameterAsString(PwmConstants.PARAM_LDAP_PROFILE);
-
+        final Map<String,String> valueMap = pwmRequest.readParametersAsMap();
         try {
-            handleLoginRequest(pwmRequest, username, password, context, ldapProfile, passwordOnly);
+            handleLoginRequest(pwmRequest, valueMap, passwordOnly);
         } catch (PwmOperationalException e) {
             pwmRequest.setResponseError(e.getErrorInformation());
             forwardToJSP(pwmRequest, passwordOnly);
@@ -166,16 +163,8 @@ public class LoginServlet extends AbstractPwmServlet {
             return;
         }
 
-        final String username = valueMap.get(PwmConstants.PARAM_USERNAME);
-        final String passwordStr = valueMap.get(PwmConstants.PARAM_PASSWORD);
-        final PasswordData password = passwordStr != null && passwordStr.length() > 0
-                ? new PasswordData(passwordStr)
-                : null;
-        final String context = valueMap.get(PwmConstants.PARAM_CONTEXT);
-        final String ldapProfile = valueMap.get(PwmConstants.PARAM_LDAP_PROFILE);
-
         try {
-            handleLoginRequest(pwmRequest, username, password, context, ldapProfile, passwordOnly);
+            handleLoginRequest(pwmRequest, valueMap, passwordOnly);
         } catch (PwmOperationalException e) {
             final ErrorInformation errorInformation = e.getErrorInformation();
             LOGGER.trace(pwmRequest, "returning rest login error to client: " + errorInformation.toDebugStr());
@@ -183,6 +172,8 @@ public class LoginServlet extends AbstractPwmServlet {
             return;
         }
 
+        pwmRequest.readParametersAsMap();
+
         // login has succeeded
         final String nextLoginUrl = determinePostLoginUrl(pwmRequest, valueMap.get(PwmConstants.PARAM_POST_LOGIN_URL));
         final RestResultBean restResultBean = new RestResultBean();
@@ -194,14 +185,21 @@ public class LoginServlet extends AbstractPwmServlet {
 
     private void handleLoginRequest(
             final PwmRequest pwmRequest,
-            final String username,
-            final PasswordData password,
-            final String context,
-            final String ldapProfile,
+            final Map<String,String> valueMap,
             final boolean passwordOnly
     )
             throws PwmOperationalException, ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
     {
+        final String username = valueMap.get(PwmConstants.PARAM_USERNAME);
+        final String passwordStr = valueMap.get(PwmConstants.PARAM_PASSWORD);
+        final PasswordData password = passwordStr != null && passwordStr.length() > 0
+                ? new PasswordData(passwordStr)
+                : null;
+        final String context = valueMap.get(PwmConstants.PARAM_CONTEXT);
+        final String ldapProfile = valueMap.get(PwmConstants.PARAM_LDAP_PROFILE);
+        final String recaptchaResponse = valueMap.get("g-recaptcha-response");
+
+
         if (!passwordOnly && (username == null || username.isEmpty())) {
             throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,"missing username parameter"));
         }
@@ -210,6 +208,10 @@ public class LoginServlet extends AbstractPwmServlet {
             throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,"missing password parameter"));
         }
 
+        if (!CaptchaUtility.verifyReCaptcha(pwmRequest, recaptchaResponse)) {
+            throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_BAD_CAPTCHA_RESPONSE, "captcha incorrect"));
+        }
+
         final SessionAuthenticator sessionAuthenticator = new SessionAuthenticator(
                 pwmRequest.getPwmApplication(),
                 pwmRequest.getPwmSession(),

+ 0 - 1
src/main/java/password/pwm/http/servlet/PwmServletDefinition.java

@@ -39,7 +39,6 @@ import java.lang.annotation.Annotation;
 public enum PwmServletDefinition {
     Login(password.pwm.http.servlet.LoginServlet.class),
     Logout(password.pwm.http.servlet.LogoutServlet.class),
-    Captcha(password.pwm.http.servlet.CaptchaServlet.class),
     OAuthConsumer(OAuthConsumerServlet.class),
     Command(password.pwm.http.servlet.CommandServlet.class),
     //Resource(password.pwm.http.servlet.ResourceFileServlet.class),

+ 10 - 0
src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java

@@ -63,6 +63,7 @@ import password.pwm.http.PwmSession;
 import password.pwm.http.bean.ForgottenPasswordBean;
 import password.pwm.http.filter.AuthenticationFilter;
 import password.pwm.http.servlet.AbstractPwmServlet;
+import password.pwm.util.CaptchaUtility;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.http.servlet.oauth.OAuthForgottenPasswordResults;
 import password.pwm.http.servlet.oauth.OAuthMachine;
@@ -377,6 +378,15 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
         // clear the bean
         clearForgottenPasswordBean(pwmRequest);
 
+        if (!CaptchaUtility.verifyReCaptcha(pwmRequest)) {
+            final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_BAD_CAPTCHA_RESPONSE);
+            LOGGER.debug(pwmRequest, errorInfo);
+            pwmRequest.setResponseError(errorInfo);
+            forwardToSearchPage(pwmRequest);
+            return;
+        }
+
+
         final List<FormConfiguration> forgottenPasswordForm = pwmApplication.getConfig().readSettingAsForm(
                 PwmSetting.FORGOTTEN_PASSWORD_SEARCH_FORM);
 

+ 9 - 0
src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java

@@ -58,6 +58,7 @@ import password.pwm.http.PwmSession;
 import password.pwm.http.PwmURL;
 import password.pwm.http.bean.NewUserBean;
 import password.pwm.http.servlet.AbstractPwmServlet;
+import password.pwm.util.CaptchaUtility;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.i18n.Message;
 import password.pwm.ldap.UserDataReader;
@@ -541,6 +542,14 @@ public class NewUserServlet extends AbstractPwmServlet {
     private void handleProcessFormRequest(final PwmRequest pwmRequest, final NewUserBean newUserBean)
             throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException
     {
+        if (!CaptchaUtility.verifyReCaptcha(pwmRequest)) {
+            final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_BAD_CAPTCHA_RESPONSE);
+            LOGGER.debug(pwmRequest, errorInfo);
+            pwmRequest.setResponseError(errorInfo);
+            forwardToFormPage(pwmRequest, newUserBean);
+            return;
+        }
+
         newUserBean.setFormPassed(false);
         newUserBean.setNewUserForm(null);
 

+ 2 - 2
src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchConfiguration.java

@@ -25,7 +25,7 @@ package password.pwm.http.servlet.peoplesearch;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 
-class PeopleSearchConfiguration {
+public class PeopleSearchConfiguration {
     private final String photoAttribute;
     private final String photoUrlOverride;
     private final boolean photosEnabled;
@@ -33,7 +33,7 @@ class PeopleSearchConfiguration {
     private final String orgChartParentAttr;
     private final String orgChartChildAttr;
 
-    PeopleSearchConfiguration(final Configuration configuration) {
+    public PeopleSearchConfiguration(final Configuration configuration) {
         photoAttribute = configuration.readSettingAsString(PwmSetting.PEOPLE_SEARCH_PHOTO_ATTRIBUTE);
         photoUrlOverride = configuration.readSettingAsString(PwmSetting.PEOPLE_SEARCH_PHOTO_URL_OVERRIDE);
         photosEnabled = (photoAttribute != null && !photoAttribute.isEmpty())

+ 13 - 0
src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java

@@ -35,6 +35,7 @@ import password.pwm.health.HealthMonitor;
 import password.pwm.health.HealthStatus;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestFlag;
+import password.pwm.http.servlet.peoplesearch.PeopleSearchConfiguration;
 import password.pwm.svc.PwmService;
 import password.pwm.util.Helper;
 
@@ -57,6 +58,8 @@ public enum PwmIfTest {
     setupChallengeEnabled(new BooleanPwmSettingTest(PwmSetting.CHALLENGE_ENABLE)),
     shortcutsEnabled(new BooleanPwmSettingTest(PwmSetting.SHORTCUT_ENABLE)),
     peopleSearchEnabled(new BooleanPwmSettingTest(PwmSetting.PEOPLE_SEARCH_ENABLE)),
+    orgChartEnabled(new OrgChartEnabled()),
+
     accountInfoEnabled(new BooleanPwmSettingTest(PwmSetting.ACCOUNT_INFORMATION_ENABLED)),
 
     forgottenPasswordEnabled(new BooleanPwmSettingTest(PwmSetting.FORGOTTEN_PASSWORD_ENABLE)),
@@ -399,4 +402,14 @@ public enum PwmIfTest {
 
     }
 
+    private static class OrgChartEnabled implements Test {
+        @Override
+        public boolean test(final PwmRequest pwmRequest, final PwmIfOptions options) throws ChaiUnavailableException, PwmUnrecoverableException {
+            if (!pwmRequest.getConfig().readSettingAsBoolean(PwmSetting.PEOPLE_SEARCH_ENABLE)) {
+                return false;
+            }
+
+            return new PeopleSearchConfiguration(pwmRequest.getConfig()).isOrgChartEnabled();
+        }
+    }
 }

+ 15 - 0
src/main/java/password/pwm/svc/intruder/IntruderManager.java

@@ -42,6 +42,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthRecord;
 import password.pwm.health.HealthStatus;
 import password.pwm.health.HealthTopic;
+import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.ldap.LdapUserDataReader;
 import password.pwm.ldap.UserStatusReader;
@@ -601,4 +602,18 @@ public class IntruderManager implements Serializable, PwmService {
     {
         return serviceInfo;
     }
+
+    public int countForNetworkEndpointInRequest(final PwmRequest pwmRequest) {
+        final String srcAddress = pwmRequest.getPwmSession().getSessionStateBean().getSrcAddress();
+        if (srcAddress == null || srcAddress.isEmpty()) {
+            return 0;
+        }
+
+        final IntruderRecord intruderRecord = recordManagers.get(RecordType.ADDRESS).readIntruderRecord(srcAddress);
+        if (intruderRecord == null) {
+            return 0;
+        }
+
+        return intruderRecord.getAttemptCount();
+    }
 }

+ 131 - 128
src/main/java/password/pwm/http/servlet/CaptchaServlet.java → src/main/java/password/pwm/util/CaptchaUtility.java

@@ -20,151 +20,70 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-package password.pwm.http.servlet;
+package password.pwm.util;
 
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
-import com.novell.ldapchai.exception.ChaiUnavailableException;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
-import password.pwm.bean.LocalSessionStateBean;
+import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
+import password.pwm.config.option.ApplicationPage;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
-import password.pwm.http.PwmSession;
+import password.pwm.http.PwmURL;
 import password.pwm.http.client.PwmHttpClient;
 import password.pwm.http.client.PwmHttpClientRequest;
 import password.pwm.http.client.PwmHttpClientResponse;
+import password.pwm.svc.PwmService;
+import password.pwm.svc.intruder.IntruderManager;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
-import password.pwm.util.Helper;
-import password.pwm.util.JsonUtil;
-import password.pwm.util.PasswordData;
-import password.pwm.util.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 
 import javax.servlet.ServletException;
-import javax.servlet.annotation.WebServlet;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 
-@WebServlet(
-        name="CaptchaServlet",
-        urlPatterns = {
-                PwmConstants.URL_PREFIX_PUBLIC + "/captcha",
-                PwmConstants.URL_PREFIX_PUBLIC + "/Captcha"
-        }
-)
-public class CaptchaServlet extends AbstractPwmServlet {
+public class CaptchaUtility {
 
-    private static final PwmLogger LOGGER = PwmLogger.getLogger(CaptchaServlet.class.getName());
+    private static final PwmLogger LOGGER = PwmLogger.getLogger(CaptchaUtility.class.getName());
 
     private static final String COOKIE_SKIP_INSTANCE_VALUE = "INSTANCEID";
 
-    public enum CaptchaAction implements AbstractPwmServlet.ProcessAction {
-        doVerify,
-        ;
-
-        public Collection<HttpMethod> permittedMethods()
-        {
-            return Collections.singletonList(HttpMethod.POST);
-        }
-    }
-
-    protected CaptchaAction readProcessAction(final PwmRequest request)
-            throws PwmUnrecoverableException
-    {
-        try {
-            return CaptchaAction.valueOf(request.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST));
-        } catch (IllegalArgumentException e) {
-            return null;
-        }
-    }
-
-    protected void processAction(final PwmRequest pwmRequest)
-            throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException
-    {
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
-
-        if (checkRequestForCaptchaSkipCookie(pwmRequest)) {
-            pwmSession.getSessionStateBean().setPassedCaptcha(true);
-            forwardToOriginalLocation(pwmRequest);
-            return;
-        }
-
-        final CaptchaAction action = readProcessAction(pwmRequest);
-
-        if (action != null) {
-            pwmRequest.validatePwmFormID();
-            switch (action) {
-                case doVerify:
-                    handleVerify(pwmRequest);
-                    break;
-
-                default:
-                    Helper.unhandledSwitchStatement(action);
-
-            }
-        }
-
-        if (!pwmRequest.getPwmResponse().isCommitted()) {
-            forwardToCaptchaPage(pwmRequest);
-        }
-    }
-
-    private void handleVerify(final PwmRequest pwmRequest)
-            throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException
-    {
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
-        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-
-        final long startTime = System.currentTimeMillis();
-        final boolean verified;
-        try {
-            verified = verifyReCaptcha(pwmRequest);
-        } catch (PwmUnrecoverableException e) {
-            LOGGER.fatal(
-                    "error " + e.getCause().getClass().getName() + " during recaptcha api validation: " + e.getMessage());
-            pwmRequest.respondWithError(e.getErrorInformation());
-            return;
-        }
-
-        if (verified) { // passed captcha
-            pwmSession.getSessionStateBean().setPassedCaptcha(true);
-            pwmApplication.getStatisticsManager().incrementValue(Statistic.CAPTCHA_SUCCESSES);
-
-            LOGGER.debug(pwmSession, "captcha passcode verified (" + TimeDuration.fromCurrent(startTime).asCompactString() + ")");
-            pwmApplication.getIntruderManager().convenience().clearAddressAndSession(pwmSession);
-            writeCaptchaSkipCookie(pwmRequest);
-            forwardToOriginalLocation(pwmRequest);
-        } else { //failed captcha
-            pwmSession.getSessionStateBean().setPassedCaptcha(false);
-            pwmApplication.getStatisticsManager().incrementValue(Statistic.CAPTCHA_FAILURES);
-            LOGGER.debug(pwmSession, "incorrect captcha passcode");
-            pwmApplication.getIntruderManager().convenience().markAddressAndSession(pwmSession);
-            pwmRequest.setResponseError(PwmError.ERROR_BAD_CAPTCHA_RESPONSE.toInfo());
-            forwardToCaptchaPage(pwmRequest);
-        }
-    }
 
     /**
      * Verify a reCaptcha request.  The reCaptcha request API is documented at <a href="http://recaptcha.net/apidocs/captcha/">reCaptcha API.
      */
-    private boolean verifyReCaptcha(
+    public static boolean verifyReCaptcha(
             final PwmRequest pwmRequest
     )
             throws PwmUnrecoverableException
     {
+        final String recaptchaResponse = pwmRequest.readParameterAsString("g-recaptcha-response");
+        return verifyReCaptcha(pwmRequest, recaptchaResponse);
+    }
+
+    public static boolean verifyReCaptcha(
+            final PwmRequest pwmRequest,
+            final String recaptchaResponse
+    )
+            throws PwmUnrecoverableException
+    {
+        if (!captchaEnabledForRequest(pwmRequest)) {
+            return true;
+        }
+
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final PasswordData privateKey = pwmApplication.getConfig().readSettingAsPassword(PwmSetting.RECAPTCHA_KEY_PRIVATE);
 
@@ -173,7 +92,7 @@ public class CaptchaServlet extends AbstractPwmServlet {
         bodyText.append("&");
         bodyText.append("remoteip=").append(pwmRequest.getSessionLabel().getSrcAddress());
         bodyText.append("&");
-        bodyText.append("response=").append(pwmRequest.readParameterAsString("g-recaptcha-response"));
+        bodyText.append("response=").append(recaptchaResponse);
 
         try {
             final PwmHttpClientRequest clientRequest = new PwmHttpClientRequest(
@@ -211,6 +130,8 @@ public class CaptchaServlet extends AbstractPwmServlet {
                 }
             }
 
+            writeCaptchaSkipCookie(pwmRequest);
+
         } catch (Exception e) {
             final String errorMsg = "unexpected error during reCaptcha API execution: " + e.getMessage();
             LOGGER.error(errorMsg,e);
@@ -223,26 +144,6 @@ public class CaptchaServlet extends AbstractPwmServlet {
         return false;
     }
 
-    private void forwardToOriginalLocation(
-            final PwmRequest pwmRequest
-    )
-            throws IOException, ServletException {
-        try {
-            final PwmSession pwmSession = pwmRequest.getPwmSession();
-            final LocalSessionStateBean ssBean = pwmSession.getSessionStateBean();
-
-            String destURL = ssBean.getPreCaptchaRequestURL();
-            ssBean.setPreCaptchaRequestURL(null);
-
-            if (pwmRequest.getURL().isLoginServlet()) { // fallback, shouldn't need to be used.
-                destURL = pwmRequest.getHttpServletRequest().getContextPath();
-            }
-
-            pwmRequest.sendRedirect(destURL);
-        } catch (PwmUnrecoverableException e) {
-            LOGGER.error("unexpected error forwarding user to original request url: " + e.toString());
-        }
-    }
 
     private static void writeCaptchaSkipCookie(
             final PwmRequest pwmRequest
@@ -291,7 +192,66 @@ public class CaptchaServlet extends AbstractPwmServlet {
         return false;
     }
 
-    private void forwardToCaptchaPage(final PwmRequest pwmRequest) throws ServletException, PwmUnrecoverableException, IOException {
+    public static boolean captchaEnabledForRequest(
+            final PwmRequest pwmRequest
+    )
+            throws PwmUnrecoverableException
+    {
+        if (!checkIfCaptchaConfigEnabled(pwmRequest)) {
+            return false;
+        }
+
+        if (checkIfCaptchaParamPresent(pwmRequest)) {
+            return false;
+        }
+
+        if (checkRequestForCaptchaSkipCookie(pwmRequest)) {
+            return false;
+        }
+
+        if (!checkIntruderCount(pwmRequest)) {
+            return false;
+        }
+
+        final Set<ApplicationPage> protectedModules = pwmRequest.getConfig().readSettingAsOptionList(
+                PwmSetting.CAPTCHA_PROTECTED_PAGES,
+                ApplicationPage.class
+        );
+
+        final PwmURL pwmURL = pwmRequest.getURL();
+
+        boolean enabled = false;
+
+        if (protectedModules != null) {
+            if (protectedModules.contains(ApplicationPage.LOGIN) && pwmURL.isLoginServlet()) {
+                enabled = true;
+            } else if (protectedModules.contains(ApplicationPage.FORGOTTEN_PASSWORD) && pwmURL.isForgottenPasswordServlet()) {
+                enabled = true;
+            } else if (protectedModules.contains(ApplicationPage.FORGOTTEN_USERNAME) && pwmURL.isForgottenUsernameServlet()) {
+                enabled = true;
+            } else if (protectedModules.contains(ApplicationPage.USER_ACTIVATION) && pwmURL.isUserActivationServlet()) {
+                enabled = true;
+            } else if (protectedModules.contains(ApplicationPage.NEW_USER_REGISTRATION) && pwmURL.isNewUserRegistrationServlet()) {
+                enabled = true;
+            }
+        }
+
+        return enabled;
+    }
+
+    public static boolean checkIfCaptchaConfigEnabled(
+            final PwmRequest pwmRequest
+    )
+            throws PwmUnrecoverableException
+    {
+        final Configuration config = pwmRequest.getPwmApplication().getConfig();
+        final PasswordData privateKey = config.readSettingAsPassword(PwmSetting.RECAPTCHA_KEY_PRIVATE);
+        final String publicKey = config.readSettingAsString(PwmSetting.RECAPTCHA_KEY_PUBLIC);
+
+        return (privateKey != null && publicKey != null && !publicKey.isEmpty());
+    }
+
+    public static void prepareCaptchaDisplay(final PwmRequest pwmRequest) throws ServletException, PwmUnrecoverableException, IOException {
         StatisticsManager.incrementStat(pwmRequest, Statistic.CAPTCHA_PRESENTATIONS);
 
         final String reCaptchaPublicKey = pwmRequest.getConfig().readSettingAsString(PwmSetting.RECAPTCHA_KEY_PUBLIC);
@@ -305,6 +265,49 @@ public class CaptchaServlet extends AbstractPwmServlet {
             final String url = configuredUrl + "?k=" + reCaptchaPublicKey + "&hl=" + pwmRequest.getLocale().toString();
             pwmRequest.setAttribute(PwmRequest.Attribute.CaptchaIframeUrl,url);
         }
-        pwmRequest.forwardToJsp(PwmConstants.JspUrl.CAPTCHA);
+    }
+
+    private static boolean checkIfCaptchaParamPresent(final PwmRequest pwmRequest)
+        throws PwmUnrecoverableException
+    {
+        final String skipCaptcha = pwmRequest.readParameterAsString(PwmConstants.PARAM_SKIP_CAPTCHA);
+        if (skipCaptcha != null && skipCaptcha.length() > 0) {
+            final String configValue = pwmRequest.getConfig().readSettingAsString(PwmSetting.CAPTCHA_SKIP_PARAM);
+            if (configValue != null && configValue.equals(skipCaptcha)) {
+                LOGGER.trace(pwmRequest, "valid skipCaptcha value in request, skipping captcha check for this session");
+                return true;
+            } else {
+                LOGGER.error(pwmRequest, "skipCaptcha value is in request, however value '" + skipCaptcha + "' does not match configured value");
+            }
+        }
+
+        return false;
+    }
+
+    private static boolean checkIntruderCount(final PwmRequest pwmRequest) {
+        final long maxIntruderCount = pwmRequest.getConfig().readSettingAsLong(PwmSetting.CAPTCHA_INTRUDER_COUNT_TRIGGER);
+
+        if (maxIntruderCount == 0) {
+            return false;
+        }
+
+        final int currentSessionAttempts = pwmRequest.getPwmSession().getSessionStateBean().getIntruderAttempts();
+        if (currentSessionAttempts >= maxIntruderCount) {
+            LOGGER.debug(pwmRequest, "session intruder attempt count '" + currentSessionAttempts + "', therefore captcha will be required");
+            return true;
+        }
+
+        final IntruderManager intruderManager = pwmRequest.getPwmApplication().getIntruderManager();
+        if (intruderManager == null || intruderManager.status() != PwmService.STATUS.OPEN) {
+            return false;
+        }
+
+        final int intruderAttemptCount = intruderManager.countForNetworkEndpointInRequest(pwmRequest);
+        if (intruderAttemptCount >= maxIntruderCount) {
+            LOGGER.debug(pwmRequest, "network intruder attempt count '" + intruderAttemptCount + "', therefore captcha will be required");
+            return true;
+        }
+
+        return false;
     }
 }

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

@@ -1365,6 +1365,15 @@
             <value />
         </default>
     </setting>
+    <setting hidden="false" key="captcha.intruderAttemptTrigger" level="2">
+        <properties>
+            <property key="Minimum">0</property>
+            <property key="Maximum">10</property>
+        </properties>
+        <default>
+            <value>0</value>
+        </default>
+    </setting>
     <setting hidden="false" key="security.page.enableRequestSequence" level="2" required="true">
         <default>
             <value>true</value>

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

@@ -21,7 +21,7 @@
 #
 
 Category_Description_ACCOUNT_INFO=Show account information to the logged in user.
-Category_Description_ACTIVATION=The user activation module enables users to activate an account they have not previously authenticated. The user does not need to know the password to activate the account. Configure settings so that a user may only execute this function once. Existing users should not be able to use this function.
+Category_Description_ACTIVATION=The user activation module enables users to activate an account they have not previously authenticated. The user does not need to know the password to activate the account. Configure settings so that users can only execute this function once. Existing users cannot use this function.
 Category_Description_ACTIVE_DIRECTORY=Active Directory specific settings
 Category_Description_ADMINISTRATION=Administration
 Category_Description_APPLICATION=Application
@@ -170,7 +170,7 @@ Category_Label_LOGGING=Logging
 Category_Label_MODULES=Modules
 Category_Label_MODULES_PRIVATE=Authenticated
 Category_Label_MODULES_PUBLIC=Public
-Category_Label_NAAF=NAAF
+Category_Label_NAAF=NAAF (Deprecated)
 Category_Label_NEWUSER=New User Registration
 Category_Label_NEWUSER_PROFILE=New User Profiles
 Category_Label_NEWUSER_SETTINGS=New User Settings
@@ -210,14 +210,6 @@ Category_Label_USER_INTERFACE=User Interface
 Category_Label_WEB_SECURITY=Web Security
 Category_Label_WEB_SERVICES=Web Services
 Category_Label_WORDLISTS=Word Lists
-# Copyright (c) 2006-2009 Novell, Inc.
-# Copyright (c) 2009-2016 The PWM Project
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-# GNU General Public License for more details.
-# http://www.pwm-project.org
-# it under the terms of the GNU General Public License as published by
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# Password Management Servlets (PWM)
 Setting_Description_accountInfo.view.form=LDAP attributes to show to user on account information page.
 Setting_Description_accountInfo.viewStatusValues=Select the fields that are available for user's to be able to view about their own account.
 Setting_Description_activateUser.allowUnlock=Try to unlock the user account during activation.  If true, and if the user's account is locked the user account will be unlocked.
@@ -234,6 +226,7 @@ Setting_Description_audit.system.eventList=Event types to record and act upon.
 Setting_Description_audit.user.eventList=Event types to record and act upon.
 Setting_Description_audit.userEvent.toAddress=Send an email User Audit events occur to these email addresses.
 Setting_Description_basicAuth.enable=Enable Basic Authentication
+Setting_Description_captcha.intruderAttemptTrigger=Number of intruder attempts before CAPTCHA is required.  If set to 0, intruder attempt count is ignored and captcha is always required.   Intruder attempts for the current session and for the source network address is considered.<br/><br/>The recommended value for this setting is 0.  Determined network attackers may be able to bypass the CAPTCHA verification altogether if this setting is used.
 Setting_Description_captcha.protectedPages=Pages protected by CAPTCHA.  CAPTCHA validation is required only once per session.  Thus, after a user passes captcha validation during a session, the user will not be forced to pass the captcha again despite accessing a second module enabled here.
 Setting_Description_captcha.recaptcha.privateKey=Private reCAPTCHA key.  If blank no captcha verification will be performed.
 Setting_Description_captcha.recaptcha.publicKey=Public reCAPTCHA key.  If blank no captcha verification will be performed.
@@ -704,6 +697,7 @@ 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.protectedPages=CAPTCHA Protected Pages
 Setting_Label_captcha.recaptcha.privateKey=reCAPTCHA Private Key
 Setting_Label_captcha.recaptcha.publicKey=reCAPTCHA Public Key

+ 3 - 0
src/main/webapp/WEB-INF/jsp/activateuser.jsp

@@ -38,6 +38,9 @@
             <%@ include file="fragment/message.jsp" %>
             <%@ include file="/WEB-INF/jsp/fragment/ldap-selector.jsp" %>
             <jsp:include page="fragment/form.jsp"/>
+
+            <%@ include file="/WEB-INF/jsp/fragment/captcha-embed.jsp"%>
+
             <div class="buttonbar">
                 <button type="submit" name="button" class="btn" id="submitBtn">
                     <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-forward"></span></pwm:if>

+ 0 - 95
src/main/webapp/WEB-INF/jsp/captcha.jsp

@@ -1,95 +0,0 @@
-<%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
-<%@ page import="password.pwm.http.tag.value.PwmValue" %>
-<%--
-  ~ Password Management Servlets (PWM)
-  ~ http://www.pwm-project.org
-  ~
-  ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2016 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
-  --%>
-
-<!DOCTYPE html>
-<%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
-<%@ taglib uri="pwm" prefix="pwm" %>
-<html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
-<%@ include file="fragment/header.jsp" %>
-<body class="nihilo">
-<%-- begin reCaptcha section (http://code.google.com/apis/recaptcha/docs/display.html) --%>
-<pwm:script>
-    <script type="text/javascript">
-        function onloadCallback() {
-            var recaptchaCallback = function() {
-                console.log('captcha completed, submitting form');
-                PWM_MAIN.handleFormSubmit(PWM_MAIN.getObject('verifyCaptcha'));
-            };
-
-            console.log('reached google recaptcha onload callback');
-            PWM_MAIN.setStyle('captcha-loading','display','none');
-            grecaptcha.render('recaptcha-container',{callback:recaptchaCallback,sitekey:'<%=JspUtility.getAttribute(pageContext,PwmRequest.Attribute.CaptchaPublicKey)%>'});
-        }
-    </script>
-</pwm:script>
-<script nonce="<pwm:value name="<%=PwmValue.cspNonce%>"/>" src="<%=(String)JspUtility.getAttribute(pageContext,PwmRequest.Attribute.CaptchaClientUrl)%>?onload=onloadCallback&render=explicit" defer async></script>
-<div id="wrapper">
-    <jsp:include page="fragment/header-body.jsp">
-        <jsp:param name="pwm.PageName" value="Title_Captcha"/>
-    </jsp:include>
-    <div id="centerbody">
-        <div id="page-content-title"><pwm:display key="Title_Captcha" displayIfMissing="true"/></div>
-        <p><pwm:display key="Display_Captcha"/></p>
-        <%@ include file="fragment/message.jsp" %>
-        <br/>
-        <div id="captcha-loading" class="WaitDialogBlank"></div>
-        <form action="<pwm:current-url/>" method="post" enctype="application/x-www-form-urlencoded" id="verifyCaptcha" name="verifyCaptcha" class="pwm-form">
-            <input type="hidden" id="pwmFormID" name="pwmFormID" value="<pwm:FormID/>"/>
-
-            <center>
-                <div id="recaptcha-container">
-                </div>
-            </center>
-            <noscript>
-                <span><pwm:display key="Display_JavascriptRequired"/></span>
-                <a href="<pwm:context/>"><pwm:display key="Title_MainPage"/></a>
-            </noscript>
-            <div class="buttonbar">
-                <input type="hidden" name="processAction" value="doVerify"/>
-                <button type="submit" name="verify" class="btn" id="verify_button">
-                    <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-check"></span></pwm:if>
-                    <pwm:display key="Button_Verify"/>
-                </button>
-                <input type="hidden" name="pwmFormID" value="<pwm:FormID/>"/>
-                <%@ include file="/WEB-INF/jsp/fragment/cancel-button.jsp" %>
-            </div>
-        </form>
-    </div>
-    <div class="push"></div>
-</div>
-<pwm:script>
-    <script type="text/javascript">
-        PWM_GLOBAL['startupFunctions'].push(function(){
-            try {
-                document.forms.verifyCaptcha.recaptcha_response_field.focus()
-            } catch (e) {
-                /* noop */
-            }
-        });
-    </script>
-</pwm:script>
-<%@ include file="/WEB-INF/jsp/fragment/cancel-form.jsp" %>
-<%@ include file="fragment/footer.jsp" %>
-</body>
-</html>

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

@@ -42,6 +42,8 @@
             <br/>
             <jsp:include page="fragment/form.jsp"/>
 
+            <%@ include file="/WEB-INF/jsp/fragment/captcha-embed.jsp"%>
+
             <div class="buttonbar">
                 <input type="hidden" name="processAction" value="<%=ForgottenPasswordServlet.ForgottenPasswordAction.search%>"/>
                 <button type="submit" class="btn" name="search" id="submitBtn">

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

@@ -39,6 +39,9 @@
         <form action="<pwm:current-url/>" method="post" enctype="application/x-www-form-urlencoded" name="searchForm" class="pwm-form" id="searchForm" autocomplete="off">
             <%@ include file="/WEB-INF/jsp/fragment/ldap-selector.jsp" %>
             <jsp:include page="fragment/form.jsp"/>
+
+            <%@ include file="/WEB-INF/jsp/fragment/captcha-embed.jsp"%>
+
             <div class="buttonbar">
                 <input type="hidden" name="processAction" value="search"/>
                 <button type="submit" class="btn" name="search" id="submitBtn">

+ 50 - 0
src/main/webapp/WEB-INF/jsp/fragment/captcha-embed.jsp

@@ -0,0 +1,50 @@
+<%--
+  ~ Password Management Servlets (PWM)
+  ~ http://www.pwm-project.org
+  ~
+  ~ Copyright (c) 2006-2009 Novell, Inc.
+  ~ Copyright (c) 2009-2016 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
+  --%>
+
+<%@ page import="password.pwm.http.JspUtility" %>
+<%@ page import="password.pwm.http.PwmRequest" %>
+<%@ page import="password.pwm.http.tag.value.PwmValue" %>
+<%@ page import="password.pwm.util.CaptchaUtility" %>
+<%@ taglib uri="pwm" prefix="pwm" %>
+<% if (CaptchaUtility.captchaEnabledForRequest(JspUtility.getPwmRequest(pageContext))) { %>
+<% CaptchaUtility.prepareCaptchaDisplay(JspUtility.getPwmRequest(pageContext)); %>
+<div id="recaptcha-container">
+</div>
+<noscript>
+    <span><pwm:display key="Display_JavascriptRequired"/></span>
+    <a href="<pwm:context/>"><pwm:display key="Title_MainPage"/></a>
+</noscript>
+<%-- begin reCaptcha section (http://code.google.com/apis/recaptcha/docs/display.html) --%>
+<pwm:script>
+    <script type="text/javascript">
+        function onloadCallback() {
+            var recaptchaCallback = function() {
+                console.log('captcha completed, passed');
+            };
+
+            console.log('reached google recaptcha onload callback');
+            grecaptcha.render('recaptcha-container',{callback:recaptchaCallback,sitekey:'<%=JspUtility.getAttribute(pageContext,PwmRequest.Attribute.CaptchaPublicKey)%>'});
+        }
+    </script>
+</pwm:script>
+<script nonce="<pwm:value name="<%=PwmValue.cspNonce%>"/>" src="<%=(String)JspUtility.getAttribute(pageContext,PwmRequest.Attribute.CaptchaClientUrl)%>?onload=onloadCallback&render=explicit" defer async></script>
+<% } %>

+ 9 - 2
src/main/webapp/WEB-INF/jsp/login.jsp

@@ -41,10 +41,15 @@
             <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
             <%@ include file="/WEB-INF/jsp/fragment/ldap-selector.jsp" %>
             <div class="sign-in">
-                <div style="margin-top: 15px;"><input type="text" name="username" id="username" placeholder="<pwm:display key="Field_Username"/>" class="inputfield" <pwm:autofocus/> required="required"></div>
-                <div style="margin-top: 15px;"><input type="<pwm:value name="<%=PwmValue.passwordFieldType%>"/>" name="password" id="password" placeholder="<pwm:display key="Field_Password"/>" required="required" class="inputfield passwordfield"/></div>
+                <div class="formFieldWrapper">
+                    <input type="text" name="username" id="username" placeholder="<pwm:display key="Field_Username"/>" class="inputfield" <pwm:autofocus/> required="required">
+                </div>
+                <div class="formFieldWrapper">
+                    <input type="<pwm:value name="<%=PwmValue.passwordFieldType%>"/>" name="password" id="password" placeholder="<pwm:display key="Field_Password"/>" required="required" class="inputfield passwordfield"/>
+                </div>
                 <input type="hidden" id="<%=PwmConstants.PARAM_POST_LOGIN_URL%>" name="<%=PwmConstants.PARAM_POST_LOGIN_URL%>"
                        value="<%=StringUtil.escapeHtml(JspUtility.getPwmRequest(pageContext).readParameterAsString(PwmConstants.PARAM_POST_LOGIN_URL))%>"/>
+                <%@ include file="/WEB-INF/jsp/fragment/captcha-embed.jsp"%>
                 <div class="buttonbar">
                     <button type="submit" class="btn" <pwm:autofocus/> name="button" id="submitBtn">
                         <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-sign-in"></span></pwm:if>
@@ -122,6 +127,7 @@
 <pwm:if test="<%=PwmIfTest.forwardUrlDefined%>">
     <%@ include file="/WEB-INF/jsp/fragment/cancel-form.jsp" %>
 </pwm:if>
+<% if (!CaptchaUtility.checkIfCaptchaConfigEnabled(JspUtility.getPwmRequest(pageContext))) { %>
 <pwm:script>
     <script type="text/javascript">
         PWM_GLOBAL['startupFunctions'].push(function(){
@@ -131,6 +137,7 @@
         });
     </script>
 </pwm:script>
+<% } %>
 <%@ include file="fragment/footer.jsp" %>
 </body>
 </html>

+ 3 - 0
src/main/webapp/WEB-INF/jsp/newuser.jsp

@@ -42,6 +42,9 @@
         <form action="<pwm:current-url/>" method="post" name="newUser" enctype="application/x-www-form-urlencoded" autocomplete="off"
               id="newUserForm" class="pwm-form">
             <jsp:include page="fragment/form.jsp"/>
+
+            <%@ include file="/WEB-INF/jsp/fragment/captcha-embed.jsp"%>
+
             <div class="buttonbar">
                 <input type="hidden" name="processAction" value="processForm"/>
                 <button type="submit" name="Create" class="btn" id="submitBtn">

+ 0 - 8
src/main/webapp/WEB-INF/web.xml

@@ -136,10 +136,6 @@
         <filter-name>SessionFilter</filter-name>
         <filter-class>password.pwm.http.filter.SessionFilter</filter-class>
     </filter>
-    <filter>
-        <filter-name>CaptchaFilter</filter-name>
-        <filter-class>password.pwm.http.filter.CaptchaFilter</filter-class>
-    </filter>
     <filter>
         <filter-name>AuthenticationFilter</filter-name>
         <filter-class>password.pwm.http.filter.AuthenticationFilter</filter-class>
@@ -168,10 +164,6 @@
         <filter-name>SessionFilter</filter-name>
         <url-pattern>/*</url-pattern>
     </filter-mapping>
-    <filter-mapping>
-        <filter-name>CaptchaFilter</filter-name>
-        <url-pattern>/*</url-pattern>
-    </filter-mapping>
     <filter-mapping>
         <filter-name>AuthenticationFilter</filter-name>
         <url-pattern>/private/*</url-pattern>

+ 5 - 0
src/main/webapp/private/index.jsp

@@ -65,6 +65,11 @@
                             </div>
                         </div>
                     </a>
+                </pwm:if>
+            </pwm:if>
+
+            <pwm:if test="<%=PwmIfTest.orgChartEnabled%>">
+                <pwm:if test="<%=PwmIfTest.permission%>" permission="<%=Permission.PEOPLE_SEARCH%>">
                     <a id="button_PeopleSearch" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.PeopleSearch.servletUrl()%>'/>/orgchart">
                         <div class="tile">
                             <div class="tile-content">

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

@@ -852,7 +852,7 @@ PWM_CFGEDIT.displaySettingsCategory = function(category) {
             }
         })(loopSetting);
     }
-    if (category == 'LDAP_PROFILE') {
+    if (category == 'LDAP_BASE') {
         PWM_MAIN.addEventHandler('button-test-LDAP_PROFILE', 'click', function(){PWM_CFGEDIT.ldapHealthCheck();});
     } else if (category == 'DATABASE_SETTINGS') {
         PWM_MAIN.addEventHandler('button-test-DATABASE_SETTINGS', 'click', function(){PWM_CFGEDIT.databaseHealthCheck();});

+ 10 - 11
src/main/webapp/public/resources/js/configmanager.js

@@ -136,9 +136,9 @@ PWM_CONFIG.closeHeaderWarningPanel = function() {
     console.log('action closeHeader');
     PWM_CONFIG.headerResizeListener.pause();
 
-    PWM_MAIN.setStyle('header-warning','display','none');
-    PWM_MAIN.setStyle('header-warning-backdrop','display','none');
-    PWM_MAIN.setStyle('button-openHeader','display','inherit');
+    PWM_MAIN.addCssClass('header-warning','nodisplay');
+    PWM_MAIN.addCssClass('header-warning-backdrop','nodisplay');
+    //PWM_MAIN.removeCssClass('button-openHeader','nodisplay');
 };
 
 PWM_CONFIG.openHeaderWarningPanel = function() {
@@ -148,15 +148,14 @@ PWM_CONFIG.openHeaderWarningPanel = function() {
     }
 
     require(['dojo/dom','dijit/place','dojo/on'], function(dom, place, on) {
-        place.around(dom.byId("header-warning"), dom.byId("header-username-caret"), ["below-alt"], false);
+        PWM_MAIN.removeCssClass('header-warning-backdrop','nodisplay');
+        PWM_MAIN.removeCssClass('header-warning','nodisplay');
+        //PWM_MAIN.addCssClass('button-openHeader','nodisplay');
+        place.around(PWM_MAIN.getObject("header-warning"), PWM_MAIN.getObject("header-username-caret"), ["below-alt"], false);
 
-        on.once(dom.byId("header-warning-backdrop"), "click", function(event) {
+        on.once(PWM_MAIN.getObject("header-warning-backdrop"), "click", function(event) {
             PWM_CONFIG.closeHeaderWarningPanel();
         });
-
-        PWM_MAIN.setStyle('header-warning-backdrop','display','inherit');
-        PWM_MAIN.setStyle('header-warning','display','inherit');
-        PWM_MAIN.setStyle('button-openHeader','display','none');
     });
 };
 
@@ -324,10 +323,10 @@ PWM_CONFIG.initConfigHeader = function() {
     });
 
     require(["dojo/dom-construct", "dojo/_base/window", "dojo/dom", "dijit/place", "dojo/on"], function(domConstruct, win, dom, place, on){
-        domConstruct.create("div", { id: "header-warning-backdrop" }, win.body());
+        domConstruct.create("div", { id: "header-warning-backdrop", class:"nodisplay" }, win.body());
 
         PWM_CONFIG.headerResizeListener = on.pausable(window, "resize", function () {
-            place.around(dom.byId("header-warning"), dom.byId("header-menu-wrapper"), ["below-alt"], false);
+            place.around(dom.byId("header-warning"), dom.byId("header-username-caret"), ["below-alt"], false);
         });
 
         PWM_CONFIG.headerResizeListener.pause();

+ 5 - 0
src/main/webapp/public/resources/js/main.js

@@ -400,6 +400,11 @@ PWM_MAIN.handleLoginFormSubmit = function(form, event) {
                 }
             };
             PWM_MAIN.ajaxRequest(url,loadFunction,options);
+            try {
+                grecaptcha.reset(); // reset the
+            } catch (e) {
+                console.log("error resetting the captcha: " + e)
+            }
         }});
     });
 };

+ 0 - 3
src/main/webapp/public/resources/style.css

@@ -91,8 +91,6 @@ table {
 }
 
 table.nomargin {
-    border-collapse: collapse;
-    border: 1px solid #dae1e1;
     width: 100%;
     margin:0;
     padding: 0;
@@ -529,7 +527,6 @@ div.progress-container > div {
 
 #header-warning-backdrop {
     bottom: 0;
-    display: none;
     left: 0;
     position: absolute;
     right: 0;