X-Git-Url: http://git.vanrenterghem.biz/git.ikiwiki.info.git/blobdiff_plain/6d402b5a9ae7a46504015ca58ff8f8f2761e8062..713e114f132096c5aec01265ca9703708b47af4f:/doc/todo/require_CAPTCHA_to_edit.mdwn?ds=sidebyside diff --git a/doc/todo/require_CAPTCHA_to_edit.mdwn b/doc/todo/require_CAPTCHA_to_edit.mdwn index ec70d2a83..110b4167f 100644 --- a/doc/todo/require_CAPTCHA_to_edit.mdwn +++ b/doc/todo/require_CAPTCHA_to_edit.mdwn @@ -2,3 +2,326 @@ 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. +-- [[users/Will]] + +> 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]] + +>>>> Well, OpenID only addresses authentication issues, not authorisation issues. +>>>> Given that it is trivial to set up your own OpenID provider (a full provider, not +>>>> just a forward to another provider), I can't see a +>>>> blacklist working in the long term (it would be like blacklisting email). +>>>> A whitelist might work (it would not be quite as bad as whitelisting email). In any case, +>>>> there is now a captcha plugin for those that want it. It is accessible +>>>> (there is an audio option) and serves a social purpose along with +>>>> keeping bots out (the captcha is used to help digitise hard to read +>>>> words in books for [Carnegie Mellon University](http://www.cs.cmu.edu/) and +>>>> [The Internet Archive](http://www.archive.org/) ). Finally, because the actual captcha is outsourced +>>>> it means that someone else is taking care of keeping it ahead of +>>>> the bot authors. + +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. + +(code snipped as a working [[patch]] is below) + +>> 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]] + +>>> Fixed this. I just modified the OpenID plugin to check if the captcha +>>> succeeded or failed. Seeing as the OpenID plugin is the one that is +>>> abusing the normal validate method, I figured it was best to keep +>>> the fix in the same place. I also added a config switch so you can set if +>>> the captcha is needed for OpenID logins. OpenID defaults to ignoring +>>> the captcha. +>>> Patch is inline below. +>>> I think this whole thing is working now. + +>>>> Ok, glad it's working. Not thrilled that it needs to modify the +>>>> openid plugin, especially as I'm not sure if i I will integrate the +>>>> captcha plugin into mainline. Also because it's not very clean to have +>>>> the oprnid plugin aware of another plugin like that. I'd like to +>>>> prusue my idea of not doing the captcha validation in the validate +>>>> hook. + +--- a/IkiWiki/Plugin/openid.pm ++++ b/IkiWiki/Plugin/openid.pm +@@ -18,6 +18,7 @@ sub getopt () { #{{{ + error($@) if $@; + Getopt::Long::Configure('pass_through'); + GetOptions("openidsignup=s" => \$config{openidsignup}); ++ GetOptions("openidneedscaptcha=s" => \$config{openidneedscaptcha}); + } #}}} + + sub formbuilder_setup (@) { #{{{ +@@ -61,6 +62,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 $config{openidneedscaptcha} && $field eq "recaptcha"; + $form->field(name => $field, required => 0, + validate => '/.*/'); + } +@@ -96,6 +98,18 @@ sub validate ($$$;$) { #{{{ + } + } + ++ if ($config{openidneedscaptcha} && defined $form->field("recaptcha")) { ++ foreach my $field ($form->field) { ++ next unless ($field eq "recaptcha"); ++ if (! $field->validate) { ++ # if they didn't get the captcha right, ++ # then just claim we validated ok so the ++ # captcha can cause a fail ++ return 1; ++ } ++ } ++ } ++ + my $check_url = $claimed_identity->check_url( + return_to => IkiWiki::cgiurl(do => "postsignin"), + trust_root => $config{cgiurl}, + + +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;