#!/usr/bin/env perl use strict; use v5.10; # ====================[ recapcha.pl ]==================== =head1 NAME recaptcha - An Oddmuse module for adding footnotes to Oddmuse Wiki pages. =head1 INSTALLATION recaptcha is simply installable; simply: =over =item Move this file into the B directory for your Oddmuse Wiki. =item Register at https://admin.recaptcha.net/recaptcha/createsite/ for a site-specific, public/private key pair to the reCAPTCHA service. =item Set the C<$ReCaptchaPublicKey> and C<$ReCaptchaPrivateKey> configuration variables in your site's configuration file (B) to whatever public and private key strings that registration allotted to you. See L, below. =back =cut AddModuleDescription('recaptcha.pl', 'ReCaptcha Extension'); # ....................{ CONFIGURATION }.................... =head1 CONFIGURATION recaptcha is easily configurable; set these variables in the B file for your Oddmuse Wiki. =cut our ($q, %AdminPages, $LinkPattern, $FreeLinks, $FreeLinkPattern, $WikiLinks, @MyInitVariables, %CookieParameters, @MyFormChanges); our ($ReCaptchaPrivateKey, $ReCaptchaPublicKey, $ReCaptchaTheme, $ReCaptchaTabIndex, $ReCaptchaRememberAnswer, $ReCaptchaSecretKey, $ReCaptchaRequiredList, %ReCaptchaProtectedForms); =head2 $ReCaptchaPublicKey You must set this to the public key that the reCAPTCHA service allots to you on registering for that service. =cut $ReCaptchaPublicKey = 'XXX'; =head2 $ReCaptchaPrivateKey You must set this to the private key that the reCAPTCHA service allots to you on registering for that service. =cut $ReCaptchaPrivateKey = 'YYY'; =head2 $ReCaptchaTheme A string identifying which of the following CSS themes to skin the embedded reCAPTCHA with: string value | notes ---------------+------ 'red' | The default. 'white' | 'blackglass' | 'clean' | This is our recommended theme; see below. 'custom' | This is not recommended; see below. You are recommended to use the 'clean' theme, as that tends to integrate more aesthetically cleanly than the others. This requires some CSS styling on your part, however, and is, therefore, not the default. For details, see: http://wiki.recaptcha.net/index.php/How_to_change_reCAPTCHA_colors You are recommended not to use the 'custom' theme, as this extension does not adequately support that theme, yet. For details, see: http://recaptcha.net/apidocs/captcha/client.html#Custom%20theming =cut $ReCaptchaTheme = undef; =head2 $ReCaptchaTabIndex An unsigned integer indicating the HTML form "tab index" of the embedded reCAPTCHA. (The default should be fine, theoretically.) =cut $ReCaptchaTabIndex = undef; =head2 $ReCaptchaRequiredList The page name for exceptions, if defined. Every page linked to via WikiWord or [[free link]] is considered to be a page which needs questions asked. All other pages do not require questions asked. If not set, then all pages need questions asked. =cut $ReCaptchaRequiredList = ''; =head2 $ReCaptchaRememberAnswer If a user successfully answers the reCAPTCHA correctly, remember this in the cookie and don't ask again. =cut $ReCaptchaRememberAnswer = 1; =head2 $ReCaptchaSecretKey The name of the reCAPTCHA parameter in the Oddmuse cookie. If some spam bot, robot spider, or other malware program begins targetting this module, simply change the name. This offers a "first line of defense." (Changing the value of this secret key forces users to successfully answer a new reCAPTCHA.) =cut $ReCaptchaSecretKey = 'question'; # Forms using one of the following classes are protected. %ReCaptchaProtectedForms = ( 'comment' => 1, 'edit upload' => 1, 'edit text' => 1 ); # ....................{ INITIALIZATION }.................... push(@MyInitVariables, \&ReCaptchaInit); sub ReCaptchaInit { $ReCaptchaRequiredList = FreeToNormal($ReCaptchaRequiredList); $AdminPages{$ReCaptchaRequiredList} = 1; $CookieParameters{$ReCaptchaSecretKey} = ''; } # ....................{ EDITING }.................... push(@MyFormChanges, \&ReCaptchaQuestionAddTo); sub ReCaptchaQuestionAddTo { my ($form, $type, $upload) = @_; if (not $upload and not ReCaptchaException(GetId()) and not $ReCaptchaRememberAnswer && GetParam($ReCaptchaSecretKey, 0) and not UserIsEditor()) { $form =~ s/(\Q

new()->get_html_v2( $ReCaptchaPublicKey, undef, $ENV{'HTTPS'} eq 'on', undef); my $submit_html = $need_button ? $q->submit(-value=> T('Go!')) : ''; my $options_html = ' '; return $options_html.ReCaptchaGetQuestionHtml($captcha_html.$submit_html); } =head2 ReCaptchaGetQuestionHtml Enclose the reCAPTCHA iframe in Oddmuse-specific HTML and CSS. Wiki administrators are encouraged to replace this function with their own, Wiki-specific function by redefining this function in B. =cut sub ReCaptchaGetQuestionHtml { my $question_html = shift; return $q->div({-class=> 'question'}, $ReCaptchaTheme eq 'clean' ? $q->p(T('Please type the following two words:')).$question_html : $q->p(T('Please answer this captcha:' )).$question_html); } # ....................{ POSTING }.................... *OldReCaptchaDoPost = \&DoPost; *DoPost = \&NewReCaptchaDoPost; sub NewReCaptchaDoPost { my(@params) = @_; my $id = FreeToNormal(GetParam('title', undef)); my $preview = GetParam('Preview', undef); # case matters! my $correct = 0; unless (UserIsEditor() or UserIsAdmin() or $ReCaptchaRememberAnswer && GetParam($ReCaptchaSecretKey, 0) or $preview or $correct = ReCaptchaCheckAnswer() # remember this! or ReCaptchaException($id)) { print GetHeader('', T('Edit Denied'), undef, undef, '403 FORBIDDEN'); print $q->start_div({-class=>'error'}); print $q->p(T('You did not answer correctly.')); print GetFormStart(), ReCaptchaGetQuestion(1), (map { $q->input({-type=>'hidden', -name=>$_, -value=>UnquoteHtml(GetParam($_))}) } qw(title text oldtime summary recent_edit aftertext)), $q->end_form; print $q->end_div(); PrintFooter(); # logging to the error log file of the server # warn "Q: '$ReCaptchaQuestions[$question_num][0]', A: '$answer'\n"; return; } if (not GetParam($ReCaptchaSecretKey, 0) and $correct) { SetParam($ReCaptchaSecretKey, 1); } return (OldReCaptchaDoPost(@params)); } sub ReCaptchaCheckAnswer { eval "use Captcha::reCAPTCHA"; my $answer = GetParam('g-recaptcha-response'); return 0 unless $answer; my $result = Captcha::reCAPTCHA->new()->check_answer_v2( $ReCaptchaPrivateKey, $answer, $q->remote_addr() ); return $result->{is_valid}; } # ....................{ ERROR-HANDLING }.................... sub ReCaptchaException { my $id = shift; return 0 unless $ReCaptchaRequiredList and $id; my $data = GetPageContent($ReCaptchaRequiredList); if ($WikiLinks) { while ($data =~ /$LinkPattern/g) { return 0 if FreeToNormal($1) eq $id; } } if ($FreeLinks) { while ($data =~ /\[\[$FreeLinkPattern\]\]/g) { return 0 if FreeToNormal($1) eq $id; } } return 1; } =head1 COPYRIGHT AND LICENSE =encoding utf8 The information below applies to everything in this distribution, except where noted. Copyleft 2008 by B.w.Curry . Copyright 2004–2008 by Brock Wilcox . Copyright 2006–2015 by Alex Schroeder . This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see L. =cut