|
@@ -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;
|
|
|
}
|
|
|
}
|