Back to index

moin  1.9.0~rc2
textcha.py
Go to the documentation of this file.
00001 # -*- coding: iso-8859-1 -*-
00002 """
00003     MoinMoin - Text CAPTCHAs
00004 
00005     This is just asking some (admin configured) questions and
00006     checking if the answer is as expected. It is up to the wiki
00007     admin to setup questions that a bot can not easily answer, but
00008     humans can. It is recommended to setup SITE SPECIFIC questions
00009     and not to share the questions with other sites (if everyone
00010     asks the same questions / expects the same answers, spammers
00011     could adapt to that).
00012 
00013     TODO:
00014     * roundtrip the question in some other way:
00015      * use safe encoding / encryption for the q
00016      * make sure a q/a pair in the POST is for the q in the GET before
00017     * make some nice CSS
00018     * make similar changes to GUI editor
00019 
00020     @copyright: 2007 by MoinMoin:ThomasWaldmann
00021     @license: GNU GPL, see COPYING for details.
00022 """
00023 
00024 import re
00025 import random
00026 
00027 from MoinMoin import log
00028 logging = log.getLogger(__name__)
00029 
00030 from MoinMoin import wikiutil
00031 
00032 class TextCha(object):
00033     """ Text CAPTCHA support """
00034 
00035     def __init__(self, request, question=None):
00036         """ Initialize the TextCha.
00037 
00038             @param request: the request object
00039             @param question: see _init_qa()
00040         """
00041         self.request = request
00042         self.user_info = request.user.valid and request.user.name or request.remote_addr
00043         self.textchas = self._get_textchas()
00044         self._init_qa(question)
00045 
00046     def _get_textchas(self):
00047         """ get textchas from the wiki config for the user's language (or default_language or en) """
00048         request = self.request
00049         groups = request.groups
00050         cfg = request.cfg
00051         user = request.user
00052         disabled_group = cfg.textchas_disabled_group
00053         if disabled_group and user.name and user.name in groups.get(disabled_group, []):
00054             return None
00055         textchas = cfg.textchas
00056         if textchas:
00057             lang = user.language or request.lang
00058             logging.debug(u"TextCha: user.language == '%s'." % lang)
00059             if lang not in textchas:
00060                 lang = cfg.language_default
00061                 logging.debug(u"TextCha: fallback to language_default == '%s'." % lang)
00062                 if lang not in textchas:
00063                     logging.error(u"TextCha: The textchas do not have content for language_default == '%s'! Falling back to English." % lang)
00064                     lang = 'en'
00065                     if lang not in textchas:
00066                         logging.error(u"TextCha: The textchas do not have content for 'en', auto-disabling textchas!")
00067                         cfg.textchas = None
00068                         lang = None
00069         else:
00070             lang = None
00071         if lang is None:
00072             return None
00073         else:
00074             logging.debug(u"TextCha: using lang = '%s'" % lang)
00075             return textchas[lang]
00076 
00077     def _init_qa(self, question=None):
00078         """ Initialize the question / answer.
00079 
00080          @param question: If given, the given question will be used.
00081                           If None, a new question will be generated.
00082         """
00083         if self.is_enabled():
00084             if question is None:
00085                 self.question = random.choice(self.textchas.keys())
00086             else:
00087                 self.question = question
00088             try:
00089                 self.answer_regex = self.textchas[self.question]
00090                 self.answer_re = re.compile(self.answer_regex, re.U|re.I)
00091             except KeyError:
00092                 # this question does not exist, thus there is no answer
00093                 self.answer_regex = ur"[Never match for cheaters]"
00094                 self.answer_re = None
00095                 logging.warning(u"TextCha: Non-existing question '%s'. User '%s' trying to cheat?" % (
00096                                 self.question, self.user_info))
00097             except re.error:
00098                 logging.error(u"TextCha: Invalid regex in answer for question '%s'" % self.question)
00099                 self._init_qa()
00100 
00101     def is_enabled(self):
00102         """ check if textchas are enabled.
00103 
00104             They can be disabled for all languages if you use textchas = None or = {},
00105             also they can be disabled for some specific language, like:
00106             textchas = {
00107                 'en': {
00108                     'some question': 'some answer',
00109                     # ...
00110                 },
00111                 'de': {}, # having no questions for 'de' means disabling textchas for 'de'
00112                 # ...
00113             }
00114         """
00115         return not not self.textchas # we don't want to return the dict
00116 
00117     def check_answer(self, given_answer):
00118         """ check if the given answer to the question is correct """
00119         if self.is_enabled():
00120             if self.answer_re is not None:
00121                 success = self.answer_re.match(given_answer.strip()) is not None
00122             else:
00123                 # someone trying to cheat!?
00124                 success = False
00125             success_status = success and u"success" or u"failure"
00126             logging.info(u"TextCha: %s (u='%s', a='%s', re='%s', q='%s')" % (
00127                              success_status,
00128                              self.user_info,
00129                              given_answer,
00130                              self.answer_regex,
00131                              self.question,
00132                              ))
00133             return success
00134         else:
00135             return True
00136 
00137     def _make_form_values(self, question, given_answer):
00138         question_form = wikiutil.escape(question, True)
00139         given_answer_form = wikiutil.escape(given_answer, True)
00140         return question_form, given_answer_form
00141 
00142     def _extract_form_values(self, form=None):
00143         if form is None:
00144             form = self.request.form
00145         question = form.get('textcha-question')
00146         given_answer = form.get('textcha-answer', u'')
00147         return question, given_answer
00148 
00149     def render(self, form=None):
00150         """ Checks if textchas are enabled and returns HTML for one,
00151             or an empty string if they are not enabled.
00152 
00153             @return: unicode result html
00154         """
00155         if self.is_enabled():
00156             question, given_answer = self._extract_form_values(form)
00157             if question is None:
00158                 question = self.question
00159             question_form, given_answer_form = self._make_form_values(question, given_answer)
00160             result = u"""
00161 <div id="textcha">
00162 <span id="textcha-question">%s</span>
00163 <input type="hidden" name="textcha-question" value="%s">
00164 <input id="textcha-answer" type="text" name="textcha-answer" value="%s" size="20" maxlength="80">
00165 </div>
00166 """ % (wikiutil.escape(question), question_form, given_answer_form)
00167         else:
00168             result = u''
00169         return result
00170 
00171     def check_answer_from_form(self, form=None):
00172         if self.is_enabled():
00173             question, given_answer = self._extract_form_values(form)
00174             self._init_qa(question)
00175             return self.check_answer(given_answer)
00176         else:
00177             return True
00178