Browse Source

invisible recaptcha

jrivard@gmail.com 6 years ago
parent
commit
ce93d49fda

+ 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(

+ 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 );
 

+ 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>

+ 5 - 3
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.
@@ -739,12 +740,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

+ 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);
         });