
commit c440d91e7254183239d32528bcb9d00090e57fb0 Author: aagbsn <aagbsn@extc.org> Date: Tue Mar 26 01:47:07 2013 +0000 Add CaptchaProtectedResource Wraps Resource objects with a CAPTCHA. Uses cookies with max_age 60. --- lib/bridgedb/HTTPServer.py | 150 ++++++++++++++++++++++++++++---------------- 1 files changed, 97 insertions(+), 53 deletions(-) diff --git a/lib/bridgedb/HTTPServer.py b/lib/bridgedb/HTTPServer.py index 2887ed4..dcd1dcd 100644 --- a/lib/bridgedb/HTTPServer.py +++ b/lib/bridgedb/HTTPServer.py @@ -38,15 +38,6 @@ from twisted.python.components import registerAdapter template_root = os.path.join(os.path.dirname(__file__),'templates') lookup = TemplateLookup(directories=[template_root], output_encoding='utf-8') -class ICaptchaState(Interface): - value = Attribute("A bool that indicates whether a Captcha has been solved") - -class CaptchaState(object): - implements(ICaptchaState) - def __init__(self, session): - self.value = False - -registerAdapter(CaptchaState, Session, ICaptchaState) try: import GeoIP @@ -58,36 +49,68 @@ except: geoip = None logging.warn("GeoIP database not found") -class WebRoot(twisted.web.resource.Resource): - isLeaf = True - def render_GET(self, request): - return lookup.get_template('index.html').render() +class ICaptchaState(Interface): + captchaSolved = Attribute("A bool that indicates whether a Captcha has been solved") + clientIP = Attribute("The IP address of the client") -class Captcha(twisted.web.resource.Resource): - isLeaf = True +class CaptchaState(object): + implements(ICaptchaState) + def __init__(self, session): + self.captchaSolved = False + self.clientIP = "" - def __init__(self, useRecaptcha=False, recaptchaPrivKey='', recaptchaPubKey=''): +class CaptchaProtectedResource(twisted.web.resource.Resource): + def __init__(self, useRecaptcha=False, recaptchaPrivKey='', recaptchaPubKey='', + useForwardedHeader=False, resource=None): + self.isLeaf = resource.isLeaf + self.useForwardedHeader = useForwardedHeader self.recaptchaPrivKey = recaptchaPrivKey self.recaptchaPubKey = recaptchaPubKey + self.resource = resource - def render_GET(self, request): - if not ICaptchaState(request.getSession()).value: - # get a captcha - c = Raptcha(self.recaptchaPubKey, self.recaptchaPrivKey) - c.get() - - # TODO: this does not work for versions of IE < 8.0 - imgstr = 'data:image/jpeg;base64,%s' % base64.b64encode(c.image) - return lookup.get_template('captcha.html').render() + def getClientIP(self, request): + ip = None + if self.useForwardedHeader: + h = request.getHeader("X-Forwarded-For") + if h: + ip = h.split(",")[-1].strip() + if not bridgedb.Bridges.is_valid_ip(ip): + logging.warn("Got weird forwarded-for value %r",h) + ip = None else: - return redirectTo("", request) + ip = request.getClientIP() + return ip + def render_GET(self, request): + if self.captchaSolved(request): + getSession(request).expire() + return self.resource.render(request) + + # get a captcha + c = Raptcha(self.recaptchaPubKey, self.recaptchaPrivKey) + c.get() + + # TODO: this does not work for versions of IE < 8.0 + imgstr = 'data:image/jpeg;base64,%s' % base64.b64encode(c.image) + return lookup.get_template('captcha.html').render(imgstr=imgstr, challenge_field=c.challenge) + + def captchaSolved(self, request): + s = ICaptchaState(getSession(request)) + ip = self.getClientIP(request) + if s.captchaSolved and ip == s.clientIP: + return True + return False + def render_POST(self, request): + if self.captchaSolved(request): + getSession(request).expire() + return self.resource.render(request) + try: challenge = request.args['recaptcha_challenge_field'][0] response = request.args['recaptcha_response_field'][0] except: - return redirectTo('captcha', request) + return redirectTo(request.URLPath(), request) # generate a random IP for the captcha submission remote_ip = '%d.%d.%d.%d' % (randint(1,255),randint(1,255), @@ -99,14 +122,14 @@ class Captcha(twisted.web.resource.Resource): logging.info("Valid recaptcha from %s. Parameters were %r", remote_ip, request.args) # set a valid captcha solved for this session! - captcha = ICaptchaState(request.getSession()) - captcha.value = True - return redirectTo('', request) + c = ICaptchaState(getSession(request)) + c.captchaSolved = True + c.clientIP = self.getClientIP(request) else: logging.info("Invalid recaptcha from %s. Parameters were %r", remote_ip, request.args) logging.info("Recaptcha error code: %s", recaptcha_response.error_code) - return redirectTo('captcha', request) + return redirectTo(request.URLPath(), request) class WebResource(twisted.web.resource.Resource): """This resource is used by Twisted Web to give a web page with some @@ -135,12 +158,8 @@ class WebResource(twisted.web.resource.Resource): def render_GET(self, request): return self.getBridgeRequestAnswer(request) - def render_POST(self, request): - return self.getBridgeRequestAnswer(request) - def getBridgeRequestAnswer(self, request): """ returns a response to a bridge request """ - interval = self.schedule.getInterval(time.time()) bridges = ( ) ip = None @@ -208,6 +227,7 @@ class WebResource(twisted.web.resource.Resource): countryCode, bridgeFilterRules=rules) + answer = None if bridges: answer = "".join(" %s\n" % b.getConfigLine( includeFingerprint=self.includeFingerprints, @@ -215,8 +235,6 @@ class WebResource(twisted.web.resource.Resource): transport=transport, request=bridgedb.Dist.uniformMap(ip) ) for b in bridges) - else: - answer = t.gettext(I18n.BRIDGEDB_TEXT[7]) logging.info("Replying to web request from %s. Parameters were %r", ip, request.args) @@ -225,7 +243,32 @@ class WebResource(twisted.web.resource.Resource): return answer else: request.setHeader("Content-Type", "text/html") - return lookup.get_template('bridges.html').render() + return lookup.get_template('bridges.html').render(answer=answer) + +class WebRoot(twisted.web.resource.Resource): + isLeaf = True + def render_GET(self, request): + return lookup.get_template('index.html').render() + +def getSession(self, sessionInterface = None): + # Session management + if not self.session: + cookiename = b"_".join([b'TWISTED_SESSION'] + self.sitepath) + sessionCookie = self.getCookie(cookiename) + if sessionCookie: + try: + self.session = self.site.getSession(sessionCookie) + except KeyError: + pass + # if it still hasn't been set, fix it up. + if not self.session: + self.session = self.site.makeSession() + #XXX: secure cookies + self.addCookie(cookiename, self.session.uid, path=b'/', secure=True, max_age=60) + self.session.touch() + if sessionInterface: + return self.session.getComponent(sessionInterface) + return self.session def addWebServer(cfg, dist, sched): """Set up a web server. @@ -248,19 +291,26 @@ def addWebServer(cfg, dist, sched): httpdist.putChild('', WebRoot()) httpdist.putChild('assets', static.File(os.path.join(template_root, 'assets/'))) + resource = WebResource(dist, sched, cfg.HTTPS_N_BRIDGES_PER_ANSWER, + cfg.HTTP_USE_IP_FROM_FORWARDED_HEADER, + includeFingerprints=cfg.HTTPS_INCLUDE_FINGERPRINTS, + domains=cfg.EMAIL_DOMAINS) + if cfg.RECAPTCHA_ENABLED: - httpdist.putChild('captcha', Captcha(recaptchaPrivKey=cfg.RECAPTCHA_PRIV_KEY, - recaptchaPubKey=cfg.RECAPTCHA_PUB_KEY)) - #XXX add a session guard + registerAdapter(CaptchaState, Session, ICaptchaState) + protected = CaptchaProtectedResource( + recaptchaPrivKey=cfg.RECAPTCHA_PRIV_KEY, + recaptchaPubKey=cfg.RECAPTCHA_PUB_KEY, + useForwardedHeader=cfg.HTTP_USE_IP_FROM_FORWARDED_HEADER, + resource=resource) + httpdist.putChild('bridges', protected) + else: + httpdist.putChild('bridges', resource) + + site = Site(httpdist) if cfg.HTTP_UNENCRYPTED_PORT: ip = cfg.HTTP_UNENCRYPTED_BIND_IP or "" - resource = WebResource(dist, sched, cfg.HTTPS_N_BRIDGES_PER_ANSWER, - cfg.HTTP_USE_IP_FROM_FORWARDED_HEADER, - includeFingerprints=cfg.HTTPS_INCLUDE_FINGERPRINTS, - domains=cfg.EMAIL_DOMAINS) - httpdist.putChild('bridges', resource) - site = Site(httpdist) reactor.listenTCP(cfg.HTTP_UNENCRYPTED_PORT, site, interface=ip) if cfg.HTTPS_PORT: @@ -269,12 +319,6 @@ def addWebServer(cfg, dist, sched): ip = cfg.HTTPS_BIND_IP or "" factory = DefaultOpenSSLContextFactory(cfg.HTTPS_KEY_FILE, cfg.HTTPS_CERT_FILE) - resource = WebResource(dist, sched, cfg.HTTPS_N_BRIDGES_PER_ANSWER, - cfg.HTTPS_USE_IP_FROM_FORWARDED_HEADER, - includeFingerprints=cfg.HTTPS_INCLUDE_FINGERPRINTS, - domains=cfg.EMAIL_DOMAINS) - httpdist.putChild('bridges', resource) - site = Site(httpdist) reactor.listenSSL(cfg.HTTPS_PORT, site, factory, interface=ip) return site