diff --git a/gluon/tools.py b/gluon/tools.py index 790c172e..6d6c42fe 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -60,7 +60,7 @@ except ImportError: # fallback to pure-Python module import gluon.contrib.simplejson as json_parser -__all__ = ['Mail', 'Auth', 'Recaptcha', 'Crud', 'Service', 'Wiki', +__all__ = ['Mail', 'Auth', 'Recaptcha', 'Recaptcha2', 'Crud', 'Service', 'Wiki', 'PluginManager', 'fetch', 'geocode', 'reverse_geocode', 'prettydate'] ### mind there are two loggers here (logger and crud.settings.logger)! @@ -965,7 +965,142 @@ class Recaptcha(DIV): return XML(captcha).xml() -# this should only be used for catcha and perhaps not even for that +class Recaptcha2(DIV): + """ + Experimental: + Creates a DIV holding the newer Recaptcha from Google (v2) + + Args: + request : the request. If not passed, uses current request + public_key : the public key Google gave you + private_key : the private key Google gave you + error_message : the error message to show if verification fails + label : the label to use + options (dict) : takes these parameters + + - hl + - theme + - type + - tabindex + - callback + - expired-callback + + see https://developers.google.com/recaptcha/docs/display for docs about those + + comment : the comment + + Examples: + Use as:: + + form = FORM(Recaptcha2(public_key='...',private_key='...')) + + or:: + + form = SQLFORM(...) + form.append(Recaptcha2(public_key='...',private_key='...')) + + to protect the login page instead, use:: + + from gluon.tools import Recaptcha2 + auth.settings.captcha = Recaptcha2(request, public_key='...',private_key='...') + + """ + + API_URI = 'https://www.google.com/recaptcha/api.js' + VERIFY_SERVER = 'https://www.google.com/recaptcha/api/siteverify' + + def __init__(self, + request=None, + public_key='', + private_key='', + error_message='invalid', + label='Verify:', + options=None, + comment='', + ): + request = request or current.request + self.request_vars = request and request.vars or current.request.vars + self.remote_addr = request.env.remote_addr + self.public_key = public_key + self.private_key = private_key + self.errors = Storage() + self.error_message = error_message + self.components = [] + self.attributes = {} + self.label = label + self.options = options or {} + self.comment = comment + + def _validate(self): + recaptcha_response_field = self.request_vars.pop('g-recaptcha-response', None) + remoteip = self.remote_addr + if not recaptcha_response_field: + self.errors['captcha'] = self.error_message + return False + params = urllib.urlencode({ + 'secret': self.private_key, + 'remoteip': remoteip, + 'response': recaptcha_response_field, + }) + request = urllib2.Request( + url=self.VERIFY_SERVER, + data=params, + headers={'Content-type': 'application/x-www-form-urlencoded', + 'User-agent': 'reCAPTCHA Python'}) + httpresp = urllib2.urlopen(request) + content = httpresp.read() + httpresp.close() + try: + response_dict = json_parser.loads(content) + except: + self.errors['captcha'] = self.error_message + return False + if response_dict.get('success', False): + self.request_vars.captcha = '' + return True + else: + self.errors['captcha'] = self.error_message + return False + + def xml(self): + api_uri = self.API_URI + hl = self.options.pop('hl', None) + if hl: + api_uri = self.API_URI + '?hl=%s' % hl + public_key = self.public_key + self.options['sitekey'] = public_key + captcha = DIV( + SCRIPT(_src=api_uri, _async='', _defer=''), + DIV(_class="g-recaptcha", data=self.options), + TAG.noscript(XML(""" +
+
+
+ +
+
+ +
+
+
""" % dict(public_key=public_key)) + ) + ) + if not self.errors.captcha: + return XML(captcha).xml() + else: + captcha.append(DIV(self.errors['captcha'], _class='error')) + return XML(captcha).xml() + + +# this should only be used for captcha and perhaps not even for that def addrow(form, a, b, c, style, _id, position=-1): if style == "divs": form[0].insert(position, DIV(DIV(LABEL(a), _class='w2p_fl'), @@ -987,6 +1122,15 @@ def addrow(form, a, b, c, style, _id, position=-1): DIV(b, SPAN(c, _class='inline-help'), _class='controls'), _class='control-group', _id=_id)) + elif style == "bootstrap3_inline": + form[0].insert(position, DIV(LABEL(a, _class='control-label col-sm-3'), + DIV(b, SPAN(c, _class='help-block'), + _class='col-sm-9'), + _class='form-group', _id=_id)) + elif style == "bootstrap3_stacked": + form[0].insert(position, DIV(LABEL(a, _class='control-label'), + b, SPAN(c, _class='help-block'), + _class='form-group', _id=_id)) else: form[0].insert(position, TR(TD(LABEL(a), _class='w2p_fl'), TD(b, _class='w2p_fw'), @@ -1330,8 +1474,7 @@ class Auth(object): logged_url=URL(controller, function, args='profile'), download_url=URL(controller, 'download'), mailer=(mailer is True) and Mail() or mailer, - on_failed_authorization = - URL(controller, function, args='not_authorized'), + on_failed_authorization = URL(controller, function, args='not_authorized'), login_next = url_index, login_onvalidation = [], login_onaccept = [],