summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/todo/require_CAPTCHA_to_edit.mdwn220
1 files changed, 220 insertions, 0 deletions
diff --git a/doc/todo/require_CAPTCHA_to_edit.mdwn b/doc/todo/require_CAPTCHA_to_edit.mdwn
index 4ada16f38..313d016f0 100644
--- a/doc/todo/require_CAPTCHA_to_edit.mdwn
+++ b/doc/todo/require_CAPTCHA_to_edit.mdwn
@@ -13,3 +13,223 @@ I imagine a plugin that modifies the login screen to use <http://recaptcha.net/>
>> white/black lists, were you thinking of listing the openids, or the content?
>> Something like the moinmoin global <http://master.moinmo.in/BadContent>
>> list?
+
+Okie - I have a first pass of this. There are still some issues.
+
+Currently the code verifies the CAPTCHA. If you get it right then you're fine.
+If you get the CAPTCHA wrong then the current code tells formbuilder that
+one of the fields in invalid. This stops the login from going through.
+Unfortunately, formbuilder is caching this validity somewhere, and I haven't
+found a way around that yet. This means that if you get the CAPTCHA
+wrong, it will continue to fail. You need to load the login page again so
+it doesn't have the error message on the screen, then it'll work again.
+
+A second issue is that the OpenID login system resets the 'required' flags
+of all the other fields, so using OpenID will cause the CAPTCHA to be
+ignored.
+
+Instructions
+=====
+
+You need to go to <http://recaptcha.net/api/getkey> and get a key set.
+The keys are added as options.
+
+ reCaptchaPubKey => "LONGPUBLICKEYSTRING",
+ reCaptchaPrivKey => "LONGPRIVATEKEYSTRING",
+
+You can also use "signInSSL" if you're using ssl for your login screen.
+
+
+The following code is just inline. It will probably not display correctly, and you should just grab it from the page source.
+
+----------
+
+#!/usr/bin/perl
+# Ikiwiki password authentication.
+package IkiWiki::Plugin::recaptcha;
+
+use warnings;
+use strict;
+use IkiWiki 2.00;
+
+sub import { #{{{
+ hook(type => "formbuilder_setup", id => "recaptcha", call => \&formbuilder_setup);
+} # }}}
+
+sub getopt () { #{{{
+ eval q{use Getopt::Long};
+ error($@) if $@;
+ Getopt::Long::Configure('pass_through');
+ GetOptions("reCaptchaPubKey=s" => \$config{reCaptchaPubKey});
+ GetOptions("reCaptchaPrivKey=s" => \$config{reCaptchaPrivKey});
+} #}}}
+
+sub formbuilder_setup (@) { #{{{
+ my %params=@_;
+
+ my $form=$params{form};
+ my $session=$params{session};
+ my $cgi=$params{cgi};
+ my $pubkey=$config{reCaptchaPubKey};
+ my $privkey=$config{reCaptchaPrivKey};
+ debug("Unknown Public Key. To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey")
+ unless defined $config{reCaptchaPubKey};
+ debug("Unknown Private Key. To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey")
+ unless defined $config{reCaptchaPrivKey};
+ my $tagtextPlain=<<EOTAG;
+ <script type="text/javascript"
+ src="http://api.recaptcha.net/challenge?k=$pubkey">
+ </script>
+
+ <noscript>
+ <iframe src="http://api.recaptcha.net/noscript?k=$pubkey"
+ height="300" width="500" frameborder="0"></iframe><br>
+ <textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
+ <input type="hidden" name="recaptcha_response_field"
+ value="manual_challenge">
+ </noscript>
+EOTAG
+
+ my $tagtextSSL=<<EOTAGS;
+ <script type="text/javascript"
+ src="https://api-secure.recaptcha.net/challenge?k=$pubkey">
+ </script>
+
+ <noscript>
+ <iframe src="https://api-secure.recaptcha.net/noscript?k=$pubkey"
+ height="300" width="500" frameborder="0"></iframe><br>
+ <textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
+ <input type="hidden" name="recaptcha_response_field"
+ value="manual_challenge">
+ </noscript>
+EOTAGS
+
+ my $tagtext;
+
+ if ($config{signInSSL}) {
+ $tagtext = $tagtextSSL;
+ } else {
+ $tagtext = $tagtextPlain;
+ }
+
+ if ($form->title eq "signin") {
+ # Give up if module is unavailable to avoid
+ # needing to depend on it.
+ eval q{use LWP::UserAgent};
+ if ($@) {
+ debug("unable to load LWP::UserAgent, not enabling reCaptcha");
+ return;
+ }
+
+ debug("To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey")
+ unless $pubkey;
+ debug("To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey")
+ unless $privkey;
+ debug("To use reCAPTCHA you must know the remote IP address")
+ unless $session->remote_addr();
+
+ my $extras = $form->keepextras();
+ if ($extras) {
+ push ( @$extras, qw(recaptcha_challenge_field recaptcha_response_field) );
+ } else {
+ $extras = [qw(recaptcha_challenge_field recaptcha_response_field)];
+ }
+ $form->keepextras($extras);
+
+ my $challenge = "invalid";
+ my $response = "invalid";
+ my $result = { is_valid => 0, error => 'recaptcha-not-tested' };
+
+ $form->field(
+ name => "recaptcha",
+ label => "",
+ type => 'static',
+ comment => $tagtext,
+ required => 1,
+ message => "CAPTCHA verification failed",
+ );
+
+ # validate the captcha.
+ if ($form->submitted && $form->submitted eq "Login" &&
+ defined $form->cgi_param("recaptcha_challenge_field") &&
+ length $form->cgi_param("recaptcha_challenge_field") &&
+ defined $form->cgi_param("recaptcha_response_field") &&
+ length $form->cgi_param("recaptcha_response_field")) {
+
+ $form->field(name => "recaptcha",
+ message => "CAPTCHA verification failed",
+ required => 1,
+ validate => sub {
+ if ($challenge ne $form->cgi_param("recaptcha_challenge_field") or
+ $response ne $form->cgi_param("recaptcha_response_field")) {
+ $challenge = $form->cgi_param("recaptcha_challenge_field");
+ $response = $form->cgi_param("recaptcha_response_field");
+ warn("Validating: ".$challenge." ".$response);
+ $result = check_answer($privkey,
+ $session->remote_addr(),
+ $challenge, $response);
+ } else {
+ warn("re-Validating");
+ }
+ if ($result->{is_valid}) {
+ warn("valid");
+ return 1;
+ } else {
+ warn("invalid");
+ return 0;
+ }
+ });
+ }
+ }
+} # }}}
+
+# The following function is borrowed with modifications from
+# Captcha::reCAPTCHA by Andy Armstrong and is under the PERL Artistic License
+
+sub check_answer {
+ my ( $privkey, $remoteip, $challenge, $response ) = @_;
+
+ die
+ "To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey"
+ unless $privkey;
+
+ die "For security reasons, you must pass the remote ip to reCAPTCHA"
+ unless $remoteip;
+
+ if (! ($challenge && $response)) {
+ warn("Challenge or response not set!");
+ return { is_valid => 0, error => 'incorrect-captcha-sol' };
+ }
+
+ my $ua = LWP::UserAgent->new();
+
+ my $resp = $ua->post(
+ 'http://api-verify.recaptcha.net/verify',
+ {
+ privatekey => $privkey,
+ remoteip => $remoteip,
+ challenge => $challenge,
+ response => $response
+ }
+ );
+
+ if ( $resp->is_success ) {
+ my ( $answer, $message ) = split( /\n/, $resp->content, 2 );
+ if ( $answer =~ /true/ ) {
+ warn("CAPTCHA valid");
+ return { is_valid => 1 };
+ }
+ else {
+ chomp $message;
+ warn("CAPTCHA failed: ".$message);
+ return { is_valid => 0, error => $message };
+ }
+ }
+ else {
+ warn("Unable to contact reCaptcha verification host!");
+ return { is_valid => 0, error => 'recaptcha-not-reachable' };
+ }
+}
+
+1;
+