1 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.
3 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.
5 > I hate CAPTCHAs with a passion. Someone else is welcome to write such a
8 > If spam via openid (which I have never ever seen yet) becomes
9 > a problem, a provider whitelist/blacklist seems like a much nicer
10 > solution than a CAPTCHA. --[[Joey]]
12 >> Apparently there has been openid spam (you can google for it). But as for
13 >> white/black lists, were you thinking of listing the openids, or the content?
14 >> Something like the moinmoin global <http://master.moinmo.in/BadContent>
17 Okie - I have a first pass of this. There are still some issues.
19 Currently the code verifies the CAPTCHA. If you get it right then you're fine.
20 If you get the CAPTCHA wrong then the current code tells formbuilder that
21 one of the fields in invalid. This stops the login from going through.
22 Unfortunately, formbuilder is caching this validity somewhere, and I haven't
23 found a way around that yet. This means that if you get the CAPTCHA
24 wrong, it will continue to fail. You need to load the login page again so
25 it doesn't have the error message on the screen, then it'll work again.
27 A second issue is that the OpenID login system resets the 'required' flags
28 of all the other fields, so using OpenID will cause the CAPTCHA to be
34 You need to go to <http://recaptcha.net/api/getkey> and get a key set.
35 The keys are added as options.
37 reCaptchaPubKey => "LONGPUBLICKEYSTRING",
38 reCaptchaPrivKey => "LONGPRIVATEKEYSTRING",
40 You can also use "signInSSL" if you're using ssl for your login screen.
43 The following code is just inline. It will probably not display correctly, and you should just grab it from the page source.
48 # Ikiwiki password authentication.
49 package IkiWiki::Plugin::recaptcha;
56 hook(type => "formbuilder_setup", id => "recaptcha", call => \&formbuilder_setup);
60 eval q{use Getopt::Long};
62 Getopt::Long::Configure('pass_through');
63 GetOptions("reCaptchaPubKey=s" => \$config{reCaptchaPubKey});
64 GetOptions("reCaptchaPrivKey=s" => \$config{reCaptchaPrivKey});
67 sub formbuilder_setup (@) { #{{{
70 my $form=$params{form};
71 my $session=$params{session};
73 my $pubkey=$config{reCaptchaPubKey};
74 my $privkey=$config{reCaptchaPrivKey};
75 debug("Unknown Public Key. To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey")
76 unless defined $config{reCaptchaPubKey};
77 debug("Unknown Private Key. To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey")
78 unless defined $config{reCaptchaPrivKey};
79 my $tagtextPlain=<<EOTAG;
80 <script type="text/javascript"
81 src="http://api.recaptcha.net/challenge?k=$pubkey">
85 <iframe src="http://api.recaptcha.net/noscript?k=$pubkey"
86 height="300" width="500" frameborder="0"></iframe><br>
87 <textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
88 <input type="hidden" name="recaptcha_response_field"
89 value="manual_challenge">
93 my $tagtextSSL=<<EOTAGS;
94 <script type="text/javascript"
95 src="https://api-secure.recaptcha.net/challenge?k=$pubkey">
99 <iframe src="https://api-secure.recaptcha.net/noscript?k=$pubkey"
100 height="300" width="500" frameborder="0"></iframe><br>
101 <textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
102 <input type="hidden" name="recaptcha_response_field"
103 value="manual_challenge">
109 if ($config{signInSSL}) {
110 $tagtext = $tagtextSSL;
112 $tagtext = $tagtextPlain;
115 if ($form->title eq "signin") {
116 # Give up if module is unavailable to avoid
117 # needing to depend on it.
118 eval q{use LWP::UserAgent};
120 debug("unable to load LWP::UserAgent, not enabling reCaptcha");
124 debug("To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey")
126 debug("To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey")
128 debug("To use reCAPTCHA you must know the remote IP address")
129 unless $session->remote_addr();
131 my $extras = $form->keepextras();
133 push ( @$extras, qw(recaptcha_challenge_field recaptcha_response_field) );
135 $extras = [qw(recaptcha_challenge_field recaptcha_response_field)];
137 $form->keepextras($extras);
139 my $challenge = "invalid";
140 my $response = "invalid";
141 my $result = { is_valid => 0, error => 'recaptcha-not-tested' };
149 message => "CAPTCHA verification failed",
152 # validate the captcha.
153 if ($form->submitted && $form->submitted eq "Login" &&
154 defined $form->cgi_param("recaptcha_challenge_field") &&
155 length $form->cgi_param("recaptcha_challenge_field") &&
156 defined $form->cgi_param("recaptcha_response_field") &&
157 length $form->cgi_param("recaptcha_response_field")) {
159 $form->field(name => "recaptcha",
160 message => "CAPTCHA verification failed",
163 if ($challenge ne $form->cgi_param("recaptcha_challenge_field") or
164 $response ne $form->cgi_param("recaptcha_response_field")) {
165 $challenge = $form->cgi_param("recaptcha_challenge_field");
166 $response = $form->cgi_param("recaptcha_response_field");
167 warn("Validating: ".$challenge." ".$response);
168 $result = check_answer($privkey,
169 $session->remote_addr(),
170 $challenge, $response);
172 warn("re-Validating");
174 if ($result->{is_valid}) {
186 # The following function is borrowed with modifications from
187 # Captcha::reCAPTCHA by Andy Armstrong and is under the PERL Artistic License
190 my ( $privkey, $remoteip, $challenge, $response ) = @_;
193 "To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey"
196 die "For security reasons, you must pass the remote ip to reCAPTCHA"
199 if (! ($challenge && $response)) {
200 warn("Challenge or response not set!");
201 return { is_valid => 0, error => 'incorrect-captcha-sol' };
204 my $ua = LWP::UserAgent->new();
206 my $resp = $ua->post(
207 'http://api-verify.recaptcha.net/verify',
209 privatekey => $privkey,
210 remoteip => $remoteip,
211 challenge => $challenge,
212 response => $response
216 if ( $resp->is_success ) {
217 my ( $answer, $message ) = split( /\n/, $resp->content, 2 );
218 if ( $answer =~ /true/ ) {
219 warn("CAPTCHA valid");
220 return { is_valid => 1 };
224 warn("CAPTCHA failed: ".$message);
225 return { is_valid => 0, error => $message };
229 warn("Unable to contact reCaptcha verification host!");
230 return { is_valid => 0, error => 'recaptcha-not-reachable' };