- Timestamp:
- 7 Jun 2025, 02:06:59 (3 days ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
main/waeup.kofa/branches/uli-recaptcha/src/waeup/kofa/browser/captcha.py
r15012 r18083 21 21 """ 22 22 import grok 23 import decimal 23 24 import json 25 import logging 24 26 import urllib 25 27 import urllib2 … … 33 35 ICaptchaRequest, ICaptchaResponse, ICaptcha, ICaptchaConfig, 34 36 ICaptchaManager) 35 from waeup.kofa.interfaces import IUniversity 37 from waeup.kofa.interfaces import IUniversity, IReCaptchaConfig 36 38 37 39 # … … 103 105 grok.implements(ICaptcha) 104 106 105 def verify(self, request ):107 def verify(self, request, action="submit"): 106 108 return CaptchaResponse(True, None) 107 109 108 def display(self, error_code=None ):110 def display(self, error_code=None, action="submit"): 109 111 return u'' 110 112 … … 139 141 chal_field = 'challenge' 140 142 141 def verify(self, request ):143 def verify(self, request, action='submit'): 142 144 """Verify that a solution sent equals the challenge. 143 145 """ … … 149 151 return CaptchaResponse(is_valid=False) 150 152 151 def display(self, error_code=None ):153 def display(self, error_code=None, action='submit'): 152 154 """Display challenge and input field for solution as HTML. 153 155 """ … … 164 166 165 167 ## 166 ## ReCaptcha (v 2)168 ## ReCaptcha (v3) 167 169 ## 168 170 class ReCaptcha(StaticCaptcha): … … 170 172 171 173 This is the Kofa implementation to support captchas as provided by 172 http://www.google.com/recaptcha (v 2).174 http://www.google.com/recaptcha (v3). 173 175 174 176 ReCaptcha is widely used and adopted in web applications. See the … … 226 228 - Google will track all activity of our clients 227 229 """ 228 229 230 grok.implements(ICaptcha) 230 231 … … 240 241 VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify' 241 242 242 def verify(self, request): 243 @property 244 def sitekey(self): 245 return self.get_config()['public_key'] 246 247 @property 248 def logger(self): 249 # if there is no site, we are most probably in a testing env 250 return getattr(grok.getSite(), "logger", logging.getLogger("dummy")) 251 252 @property 253 def ob_class(self): 254 return self.__implemented__.__name__.replace('waeup.kofa.', '') 255 256 def get_config(self): 257 if not getattr(self, "config", None): 258 self.config = queryUtility(IReCaptchaConfig, default={ 259 'public_key': self.PUBLIC_KEY, 260 'private_key': self.PRIVATE_KEY, 261 'hostname': u'localhost', 262 'min_score': decimal.Decimal("0.5") 263 }) 264 self.logger.debug(u'%s : getting ReCaptchaConfig: %s' % ( 265 self.ob_class, self.config)) 266 return self.config 267 268 def verify(self, request, action='submit'): 243 269 """Grab challenge/solution from HTTP request and verify it. 244 270 … … 249 275 Returns a :class:`CaptchaResponse` indicating that the 250 276 verification failed or succeeded. 251 """ 277 278 Possible error codes (official Google codes): 279 missing-input-secret The secret parameter is missing. 280 invalid-input-secret The secret parameter is invalid or malformed. 281 missing-input-response The response parameter is missing. 282 invalid-input-response The response parameter is invalid or malformed. 283 bad-request The request is invalid or malformed. 284 timeout-or-duplicate The response is no longer valid: either is too old or has been used previously. 285 We also have our own ones: 286 invalid-action The passed action does not match the one we expected 287 insufficient-score The returned score is too low; talking to a bot 288 invalid-hostname The captcha was solved on a different domain. 289 """ 290 remote_ip = request.get('HTTP_X_FORWARDED_FOR', '127.0.0.1') 252 291 form = getattr(request, 'form', {}) 253 292 token = form.get(self.token_field, None) 293 oc = self.ob_class 294 self.logger.debug(u'%s: get token from form: %s' % (oc, token)) 254 295 if not token: 255 296 # on first-time display, we won't get a token 256 297 return CaptchaResponse(is_valid=False) 298 conf = self.get_config() 257 299 params = urllib.urlencode( 258 300 { 259 'secret': self.PRIVATE_KEY,301 'secret': conf['private_key'], 260 302 'response': token, 261 'remoteip': '127.0.0.1',303 'remoteip': remote_ip, 262 304 }) 263 request = urllib2.Request( 305 self.logger.debug(u'%s: send validation request to Google' % oc) 306 v_request = urllib2.Request( 264 307 url = self.VERIFY_URL, 265 308 data = params, … … 269 312 } 270 313 ) 271 conn = urllib2.urlopen(request) 272 ret_vals = json.loads(conn.read()) 273 conn.close() 314 try: 315 conn = urllib2.urlopen(v_request) 316 ret_vals = json.loads(conn.read()) 317 except: 318 conn.close() 319 return CaptchaResponse(is_valid=False, error_code="could-not-reach-verification-server") 320 self.logger.debug(u'%s: got answer from Google: %s' % (oc, ret_vals)) 321 err_codes = ret_vals.get('error-codes', []) 274 322 if ret_vals.get('success', False) is True: 275 return CaptchaResponse(is_valid=True) 323 r_action = ret_vals.get('action', None) 324 score = ret_vals.get('score', decimal.Decimal("0.0")) 325 hostname = ret_vals.get('hostname', '') 326 if r_action != action: 327 err_codes.append('invalid-action') 328 self.logger.warn( 329 u'%s: invalid action code: %s (expected: %s)' % 330 (oc, r_action, action)) 331 elif score < conf['min_score']: 332 err_codes.append('insufficent-score') 333 self.logger.debug(u'%s: insufficient score: %s' % (oc, score)) 334 elif not hostname.endswith(conf['hostname']): 335 err_codes.append('invalid-hostname') 336 self.logger.warn( 337 u'%s: cought captcha from illegal hostname: %s' % ( 338 oc, hostname)) 339 else: 340 self.logger.debug(u'%s: captcha accepted' % oc) 341 return CaptchaResponse(is_valid=True) 342 self.logger.info(u'%s: captcha invalid/refused, form-data: %s' % ( 343 oc, form)) 276 344 return CaptchaResponse( 277 is_valid=False, error_code="%s" % ret_vals['error-codes'])278 279 def display(self, error_code=None ):345 is_valid=False, error_code="%s" % err_codes) 346 347 def display(self, error_code=None, action='submit'): 280 348 """Display captcha widget snippet. 281 349 … … 290 358 form). 291 359 """ 292 error_param = '' 360 html = u'' 361 # TODO: display errors as warnings 293 362 if error_code: 294 error_param = '&error=%s' % error_code 295 296 html = ( 297 u'<script type="text/javascript" ' 298 u'src="%(ApiServer)s" async defer>' 363 html = u"<p class='warning'>Captcha-Error: Are you a bot?</p><!-- %s -->" % error_code 364 html += ( 365 # Main event handler, triggered when a submit button is clicked. 366 # Requests the captcha token for the form and also copies name and 367 # value of the clicked button to a hidden field `fakebtn` defined 368 # below. 369 u'<script class="kofa-script-recaptcha-v3" ' 370 u' src="https://www.google.com/recaptcha/api.js?render=%(sitekey)s">' 371 u'</script>\n' 372 u'<script class="kofa-script-recaptcha-v3"> ' 373 u' function onClickRecaptcha(e) {' 374 u' e.preventDefault();' 375 u' grecaptcha.ready(function() {' 376 u' grecaptcha.execute("%(sitekey)s", {action: "%(action)s"}).then(' 377 u' function(token) {' 378 u' document.getElementById("g-recaptcha-token").value = token;' 379 u' var fakebtn = document.getElementById("g-recaptcha-fakebtn");' 380 u' fakebtn.name = e.target.name;' 381 u' fakebtn.value = e.target.value;' 382 u' document.querySelector("form").submit();});});}' 383 u'</script>\n' 384 ) % { 385 'sitekey': self.sitekey, 386 'action': action, 387 } 388 html += ( 389 # Form fields we use to (1) embed the captcha token and (2) a field 390 # to embed the name and value of the submit button/input clicked. 391 u'<input id="g-recaptcha-token" type="hidden" name="%(token_field)s"/>\n' 392 u'<input id="g-recaptcha-fakebtn" type="hidden"/>\n' 393 ) % {'token_field': self.token_field} 394 html += ( 395 # install event handler `onClickRecaptcha` defined above, but only 396 # after document (HTML) loaded. 397 u'<script> window.addEventListener("load", function () {' 398 u' if (typeof onClickRecaptcha === "function") {' 399 u'document.querySelector("*[type=\'submit\']").addEventListener(' 400 u'"click", onClickRecaptcha);}})' 299 401 u'</script>' 300 u'<div class="g-recaptcha" data-sitekey="%(SiteKey)s"></div>' % { 301 'ApiServer': "https://www.google.com/recaptcha/api.js", 302 'SiteKey': self.PUBLIC_KEY, 303 } 304 ) 402 ) 305 403 return html 306 404
Note: See TracChangeset for help on using the changeset viewer.