X-Git-Url: https://sipb.mit.edu/gitweb.cgi/ikiwiki.git/blobdiff_plain/c4c31ea48f9118fd0ea29c40074a3e911c63b145..c509834f011c082d39d5e3afa2f0f2c8d210f98e:/doc/todo/require_CAPTCHA_to_edit.mdwn diff --git a/doc/todo/require_CAPTCHA_to_edit.mdwn b/doc/todo/require_CAPTCHA_to_edit.mdwn index ec70d2a83..64f0a38d8 100644 --- a/doc/todo/require_CAPTCHA_to_edit.mdwn +++ b/doc/todo/require_CAPTCHA_to_edit.mdwn @@ -2,3 +2,266 @@ I don't necessarily trust all OpenID providers to stop bots. I note that ikiwik I imagine a plugin that modifies the login screen to use . You would then be required to fill in the captcha as well as log in in the normal way. +> I hate CAPTCHAs with a passion. Someone else is welcome to write such a +> plugin. +> +> If spam via openid (which I have never ever seen yet) becomes +> a problem, a provider whitelist/blacklist seems like a much nicer +> solution than a CAPTCHA. --[[Joey]] + +>> Apparently there has been openid spam (you can google for it). But as for +>> white/black lists, were you thinking of listing the openids, or the content? +>> Something like the moinmoin global +>> list? + +>>> OpenID can be thought of as pushing the problem of determining if +>>> someone is a human or a spambot back from the openid consumer to the +>>> openid provider. So, providers that make it possible for spambots to +>>> use their openids, or that are even set up explicitly for use in +>>> spamming, would be the ones to block. Or, providers that are known to +>>> use very good screening for humans would be the ones to allow. +>>> (Openid delegation makes it a bit harder than just looking at the +>>> openid url though.) --[[Joey]] + +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 is 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. + +> fixed this - updated code is attached. + +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. + +> This is still not fixed. I would have thought the following patch would +> have fixed this second issue, but it doesn't. + +--- a/IkiWiki/Plugin/openid.pm ++++ b/IkiWiki/Plugin/openid.pm +@@ -61,6 +61,7 @@ sub formbuilder_setup (@) { #{{{ + # Skip all other required fields in this case. + foreach my $field ($form->field) { + next if $field eq "openid_url"; ++ next if $field eq "recaptcha"; + $form->field(name => $field, required => 0, + validate => '/.*/'); + } + +>> What seems to be happing here is that the openid plugin defines a +>> validate hook for openid_url that calls validate(). validate() in turn +>> redirects the user to the openid server for validation, and exits. If +>> the openid plugins' validate hook is called before your recaptcha +>> validator, your code never gets a chance to run. I don't know how to +>> control the other that FormBuilder validates fields, but the only fix I +>> can see is to somehow influence that order. +>> +>> Hmm, maybe you need to move your own validation code out of the validate +>> hook. Instead, just validate the captcha in the formbuilder_setup hook. +>> The problem with this approach is that if validation fails, you can't +>> just flag it as invalid and let formbuilder handle that. Instead, you'd +>> have to hack something in to redisplay the captcha by hand. --[[Joey]] + +Instructions +===== + +You need to go to 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 + + my $tagtextSSL=< + + + +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; + } + + die("To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey") + unless $pubkey; + die("To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey") + unless $privkey; + die("To use reCAPTCHA you must know the remote IP address") + unless $session->remote_addr(); + + $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")) { + + my $challenge = "invalid"; + my $response = "invalid"; + my $result = { is_valid => 0, error => 'recaptcha-not-tested' }; + + $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"); + debug("Validating: ".$challenge." ".$response); + $result = check_answer($privkey, + $session->remote_addr(), + $challenge, $response); + } else { + debug("re-Validating"); + } + + if ($result->{is_valid}) { + debug("valid"); + return 1; + } else { + debug("invalid"); + return 0; + } + }); + } + } +} # }}} + +# The following function is borrowed from +# Captcha::reCAPTCHA by Andy Armstrong and are 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)) { + debug("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/ ) { + debug("CAPTCHA valid"); + return { is_valid => 1 }; + } + else { + chomp $message; + debug("CAPTCHA failed: ".$message); + return { is_valid => 0, error => $message }; + } + } + else { + debug("Unable to contact reCaptcha verification host!"); + return { is_valid => 0, error => 'recaptcha-not-reachable' }; + } +} + +1; +