diff --git a/.gitignore b/.gitignore index 2d7c893..8517eef 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ .idea #sass -.sass-cache \ No newline at end of file +.sass-cache + +#OS X +.DS_Store \ No newline at end of file diff --git a/handlers/__init__.py b/handlers/__init__.py index 049c4d2..aa4da4c 100644 --- a/handlers/__init__.py +++ b/handlers/__init__.py @@ -7,12 +7,12 @@ import time from bson.objectid import ObjectId import hashlib -import ayah +from .recaptcha import RecaptchaMixin _MENTION_FINDER_ = re.compile('class="mention">@(\w+)') -class BaseHandler(tornado.web.RequestHandler): +class BaseHandler(tornado.web.RequestHandler, RecaptchaMixin): def prepare(self): if self.request.remote_ip != '127.0.0.1' and \ self.request.host != self.settings['host']: @@ -40,25 +40,6 @@ def get_user_locale(self): def db(self): return self.application.db - def get_ayah_html(self): - ayah_html = '' - if self.settings['use_ayah']: - ayah.configure(self.settings['ayah_public_key'], - self.settings['ayah_scoring_key']) - ayah_html = ayah.get_publisher_html() - return ayah_html - - def verify_ayah(self): - if self.settings['use_ayah']: - ayah.configure(self.settings['ayah_public_key'], - self.settings['ayah_scoring_key']) - session_secret = self.get_argument('session_secret') - passed = ayah.score_result(session_secret) - if not passed: - self.flash('Are you human?') - self.redirect('/') - return True - def get_member(self, name): name = name.lower() member = self.db.members.find_one({'name_lower': name}) diff --git a/handlers/account.py b/handlers/account.py index e23e319..591f496 100644 --- a/handlers/account.py +++ b/handlers/account.py @@ -16,7 +16,7 @@ def get(self): self.render('account/signup.html') def post(self): - self.verify_ayah() + self.recaptcha_validate() username = self.get_argument('username', None) email = self.get_argument('email', '').lower() password = self.get_argument('password', None) @@ -68,7 +68,7 @@ def get(self): self.render('account/signin.html') def post(self): - self.verify_ayah() + self.recaptcha_validate() username = self.get_argument('username', '').lower() password = self.get_argument('password', None) if not (username and password): diff --git a/handlers/ayah/__init__.py b/handlers/ayah/__init__.py deleted file mode 100644 index e5c95a1..0000000 --- a/handlers/ayah/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from ayah import configure, get_publisher_html, score_result - - - diff --git a/handlers/ayah/ayah.py b/handlers/ayah/ayah.py deleted file mode 100644 index 20c090f..0000000 --- a/handlers/ayah/ayah.py +++ /dev/null @@ -1,85 +0,0 @@ -import urllib -import urllib2 - -config = None - -def configure(publisher_key, scoring_key, ws_host='ws.areyouahuman.com'): - """ - Sets the publisher key and scoring key needed to make subsequent calls to the areyouahuman API. Call this function - once when your application initializes. - - publisher_key - Identifies you and your application to areyouahuman.com. - scoring_key - Used to retrieve pass or fail results from areyouahuman.com. - ws_host - Web service host for areyouahuman calls (no trailing slash). Defaults to 'ws.areyouahuman.com'. - """ - publisher_url = ''.join([ - 'https://', - ws_host, - '/ws/script/', - urllib2.quote(publisher_key, safe='')]) - publisher_html = ''.join([ - '
']) - scoring_url = ''.join([ - 'https://', - ws_host, - '/ws/scoreGame']) - global config - config = { 'publisher_key': publisher_key, - 'scoring_key': scoring_key, - 'ws_host': ws_host, - 'publisher_url': publisher_url, - 'publisher_html': publisher_html, - 'scoring_url': scoring_url } - -def check_configuration(): - if config == None: - raise Exception('You must call ayah.configure() before using this API.') - -def get_publisher_html(): - """ - Gets the HTML markup that displays the PlayThru content to the alleged human. When the alleged human finishes - the PlayThru challenge, pass the value of the hidden input field with id='session_secret' to score_result(). - """ - check_configuration() - return config['publisher_html'] - -def score_result(session_secret): - """ - Returns True or False indicating whether the alleged human succeeded in satisfying the PlayThru challenge. - - session_secret - Pass in the value of the hidden input field with id='session_secret'. - """ - check_configuration() - data = { 'scoring_key': config['scoring_key'], - 'session_secret': session_secret } - values = urllib.urlencode(data) - response = urllib2.urlopen(config['scoring_url'], values) - result = False - if response.code == 200: - content = response.readline() - dict = eval(content) - result = (int(dict['status_code']) == 1) - return result - -def record_conversion(session_secret): - """ - Returns the HTML needed to be embedded in the confirmation page after a form submission. - Once the code loads on the page it will record a conversion with our system. - - session_secret - Pass in the value of the hidden input field with id='session_secret' - """ - check_configuration() - conversion_url = ''.join([ - '']) - return conversion_url \ No newline at end of file diff --git a/handlers/node.py b/handlers/node.py index 8297034..3de3ab7 100644 --- a/handlers/node.py +++ b/handlers/node.py @@ -31,7 +31,7 @@ def get(self, node_name): @tornado.web.authenticated def post(self, node_name): - self.verify_ayah() + self.recaptcha_validate() node = self.get_node(node_name) title = self.get_argument('title', '') content = self.get_argument('content', '') diff --git a/handlers/recaptcha.py b/handlers/recaptcha.py new file mode 100644 index 0000000..8a399ad --- /dev/null +++ b/handlers/recaptcha.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2012, Hsiaoming Yang +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the author nor the names of its contributors +# may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import urllib + + +class RecaptchaMixin(object): + """RecaptchaMixin + + You must define some options for this mixin. All information + can be found at http://www.google.com/recaptcha + + A basic example:: + + from tornado.options import define + from tornado.web import RequestHandler, asynchronous + define('recaptcha_key', 'key') + define('recaptcha_secret', 'secret') + define('recaptcha_theme', 'clean') + + class SignupHandler(RequestHandler, RecaptchaMixin): + def get(self): + self.write('
') + self.write(self.xsrf_form_html()) + self.write(self.recaptcha_render()) + self.write('') + self.write('
') + + @asynchronous + def post(self): + self.recaptcha_validate(self._on_validate) + + def _on_validate(self, response): + if response: + self.write('success') + self.finish() + return + self.write('failed') + self.finish() + """ + + RECAPTCHA_VERIFY_URL = "http://www.google.com/recaptcha/api/verify" + + def recaptcha_render(self): + token = self._recaptcha_token() + html = ( + '
' + '' + ) + return html % token + + def recaptcha_validate(self): + if not self.settings['use_recaptcha']: + return + token = self._recaptcha_token() + challenge = self.get_argument('recaptcha_challenge_field', None) + response = self.get_argument('recaptcha_response_field', None) + post_args = { + 'privatekey': token['secret'], + 'remoteip': self.request.remote_ip, + 'challenge': challenge, + 'response': response + } + body = urllib.urlopen(self.RECAPTCHA_VERIFY_URL, + urllib.urlencode(post_args)).read() + verify, message = body.split() + if verify != 'true': + self.flash('Are you human?') + self.redirect('/') + + def _recaptcha_token(self): + token = dict( + key=self.settings['recaptcha_key'], + secret=self.settings['recaptcha_secret'], + theme=self.settings['recaptcha_theme'], + ) + return token diff --git a/locale/zh_CN.csv b/locale/zh_CN.csv index 72ee4bf..0e7406b 100644 --- a/locale/zh_CN.csv +++ b/locale/zh_CN.csv @@ -17,6 +17,7 @@ "Description",简介 "Website",网站 "Language",语言 +"Avatar",头像 "Author",作者 "Code Block Example",代码块示例 "Choose a node",选择一个节点 @@ -111,3 +112,5 @@ "Already have an account?",已有账户? "Don't have an account?",还没有账户? + +"Change your avatar at Gravatar",在Gravatar更改头像 diff --git a/settings.py b/settings.py index 7299211..bdbe376 100644 --- a/settings.py +++ b/settings.py @@ -24,9 +24,10 @@ google_analytics = '' cookie_secret = 'hey reset me!' -use_ayah = False # If you use it,set to True -ayah_public_key = '' -ayah_scoring_key = '' +use_recaptcha = False # If you use it,set to True +recaptcha_key = '' +recaptcha_secret = '' +recaptcha_theme = 'clean' gzip = False debug = True diff --git a/templates/account/settings.html b/templates/account/settings.html index e3170d3..9cf573e 100644 --- a/templates/account/settings.html +++ b/templates/account/settings.html @@ -55,6 +55,10 @@ {% end %} {% block sidebar %} +
+
{{ _("Avatar") }}
+ {{ _("Change your avatar at Gravatar") }} +
{{ _("Password") }}
diff --git a/templates/account/signin.html b/templates/account/signin.html index 1376c5e..e26e20e 100644 --- a/templates/account/signin.html +++ b/templates/account/signin.html @@ -20,7 +20,7 @@
- {% raw handler.get_ayah_html() %} + {% raw handler.recaptcha_render() %}
diff --git a/templates/account/signup.html b/templates/account/signup.html index c1abd91..0f6b66b 100644 --- a/templates/account/signup.html +++ b/templates/account/signup.html @@ -33,7 +33,7 @@ - {% raw handler.get_ayah_html() %} + {% raw handler.recaptcha_render() %}
diff --git a/templates/node/create.html b/templates/node/create.html index c074c75..92d78a1 100644 --- a/templates/node/create.html +++ b/templates/node/create.html @@ -15,7 +15,7 @@ - {% raw handler.get_ayah_html() %} + {% raw handler.recaptcha_render() %}
diff --git a/templates/node/list.html b/templates/node/list.html index 03d1950..afbf9f1 100644 --- a/templates/node/list.html +++ b/templates/node/list.html @@ -18,7 +18,7 @@ {% block sidebar %} {% if handler.check_role(return_bool=True) %}
-
{{ _("Admin") }}
+
{{ _("Settings") }}
{{ _("Add Node") }}
{% end %} diff --git a/templates/node/node.html b/templates/node/node.html index 38cbc7c..8fe374f 100644 --- a/templates/node/node.html +++ b/templates/node/node.html @@ -19,7 +19,7 @@ {% module node_sitebar(node) %} {% if handler.check_role(return_bool=True) %}
-
{{ _("Admin") }}
+
{{ _("Settings") }}
{{ _("Edit Node") }} {{ _("Add Node") }} {{ _("Remove Node") }} diff --git a/templates/topic/topic.html b/templates/topic/topic.html index 9297121..50604a3 100644 --- a/templates/topic/topic.html +++ b/templates/topic/topic.html @@ -102,7 +102,7 @@

{{ topic['title'] }}

{% if handler.check_role(owner_name=topic['author'], return_bool=True) %}
-
{{ _("Admin") }}
+
{{ _("Settings") }}
{{ _("Edit") }} {{ _("Move") }} {{ _("Remove") }}