233 lines
7.3 KiB
Markdown
233 lines
7.3 KiB
Markdown
I don't necessarily trust all OpenID providers to stop bots. I note that ikiwiki allows [[banned_users]], and that there are other todos such as [[todo/openid_user_filtering]] that would extend this. However, it might be nice to have a CAPTCHA system.
|
|
|
|
I imagine a plugin that modifies the login screen to use <http://recaptcha.net/>. 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 <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 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 a todo.
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
|