Provide a way to define custom CAPTCHA validation

soxofaan - May 26, 2009 - 08:58
Project:CAPTCHA
Version:6.x-2.x-dev
Component:Captcha API (captcha)
Category:feature request
Priority:normal
Assigned:soxofaan
Status:closed
Description

This issue is closely related to #463002: Drop preprocess op of hook_captcha?

Case insensitive validation is now part of CAPTCHA core, but there are other kinds of CAPTCHA validation that go further than a simple equality test. For example:
http://drupal.org/node/440490#comment-1601638 (handling multiple possible responses)
#472450: Right to Left fonts validation

I'm currently thinking along the lines of adding a '#captcha_validate' property to the generate op of hook_captcha like for example

<?php
  
case 'generate':
     if (
$captcha_type == 'Foo CAPTCHA') {
       
$captcha = array();
       
$captcha['solution'] = 'foo';
       
$captcha['form']['captcha_response'] = array(
         
'#type' => 'textfield',
         
'#title' => t('Enter "foo"'),
        );
       
$captcha['form']['#captcha_validate'] = 'foo_custom_validation';
        return
$captcha;
?>

We just can't reuse the '#validate' property because the arguments we should pass differ from a standard Drupal FAPI validate function. e.g.

<?php
function foo_custom_validation($solution, $response) {
}
?>

Maybe we could also pass the CAPTCHA session id? But we could add this later too.

#1

soxofaan - June 13, 2009 - 22:27
Priority:normal» critical

this one should be fixed before 6.x-2.0-rc2

#2

thekevinday - June 15, 2009 - 15:19

poke. subscribing.

#3

soxofaan - June 19, 2009 - 21:51

marked #496806: Do more with $result as duplicate

#4

awolfey - June 19, 2009 - 23:34

subscribe

#5

soxofaan - June 22, 2009 - 11:56
Status:active» needs review

Here is a first patch to implement this.

For challenge implementing modules: custom CAPTCHA validation can be defined as follows:

<?php
function foo_captcha(...) {
...
  case
'generate':
       
$captcha = array();
       
$captcha['solution'] = ...
       
$captcha['form'] = ...
       
$captcha['captcha_validate'] = 'foo_custom_validation';
        return
$captcha;
...

function
foo_custom_validation($solution, $response) {
  return
$response == "foo $solution foo";
}
?>

function foo_custom_validation() will be called instead of the traditional equality test, it should return TRUE/FALSE for correct/wrong response.

Also attached: proof of concept example for the image CAPTCHA module to make it only accept the answer 'foo'.

Also note that the global case sensitive/insensitive option is implemented through this mechanism now.

edit: typo

AttachmentSize
473002_custom_captcha_validation_01.patch 8.64 KB
473002_custom_captcha_validation_01_example.patch 701 bytes

#6

awolfey - June 22, 2009 - 11:42

This works perfectly form me. For validating a response against multiple possible solutions in the riddler module I used:

// Custom captcha validation.
function riddler_captcha_validate($solution, $response) {
  $solution = str_ireplace(',', '', $solution);
  $solution = explode(' ', $solution);
  return in_array($response, $solution);
}

I did see that captcha will pass an empty response to the custom validation, which could require checking there. I'm not sure if there is a case where you would want to see an empty response, so maybe it's better to check for that before response is passed.

Thanks!

#7

soxofaan - June 22, 2009 - 12:03

@awolfey: thanks for testing

I did see that captcha will pass an empty response to the custom validation, which could require checking there.

I'm not sure I understand what you mean, but to block empty responses you can flag a texfield as required, which will make drupal complain about the empty response, even before CAPTCHA kicks in. Take the simple math challenge for example:

<?php
$result
['form']['captcha_response'] = array(
         
'#type' => 'textfield',
         
'#title' => t('Math question'),
         
'#description' => t('Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.'),
         
'#field_prefix' => t('@x + @y = ', array('@x' => $x, '@y' => $y)),
         
'#size' => 4,
         
'#maxlength' => 2,
         
'#required' => TRUE,
        );
?>

note the
<?php
'#required' => TRUE,
?>
as the last property.

#8

awolfey - June 22, 2009 - 12:24

I have required set to TRUE. I first did my test this way:

function riddler_captcha_validate($solution, $response) {
  drupal_set_message('at riddler validate '. $solution . ' response '. $response);
  return stristr($solution, $response);
}

Which, on an empty response, gave me this php warning: warning: stristr() [function.stristr]: Empty delimiter in C:\wamp\www\sandbox\sites\all\modules\riddler\riddler.module on line 180. There is ALSO a message that the field is required.

Returning FALSE on !$response catches it before stristr(). It's not a problem for in_array().

Thanks

#9

soxofaan - June 22, 2009 - 14:09

That warning is because of the behavior of the stristr() function when the second argument is an empty string, it has nothing to do with the CAPTCHA module or even Drupal.
In this case you should indeed catch it before calling stristr().
Note that you are not limited to oneliner validation functions (maybe the examples suggest this): you can add for loops and if test all you want. As long as they return TRUE on success en FALSE on failure.

#10

awolfey - June 22, 2009 - 14:37

Right. I wasn't expecting empty results to get passed to the validation function. Not a big deal.

Anyway, looking forward to this being committed.

Thanks.

#11

soxofaan - July 2, 2009 - 23:49
Priority:critical» normal
Status:needs review» active

The patch from #5 went in with some further tweaks:
http://drupal.org/cvs?commit=232866

Question to the module developers that want to use this functionality:
the custom CAPTCHA validation function are expected to be of the form:

<?php
function foo_custom_validation($solution, $response) {
...
}
?>

Is this enough or would there be interest to receive more input, like $form, $form_state?

#12

awolfey - July 3, 2009 - 12:39

Working for me. Updated the D6 version of riddler to be compatible. For that module there's no need for $form, $form_state that I can see now.

Thanks.

#13

soxofaan - August 27, 2009 - 21:29
Status:active» fixed

added optional $element and $form_state for captcha validate functions as suggested in #11
http://drupal.org/cvs?commit=256822

this issue can finally be closed

#14

System Message - September 10, 2009 - 21:30
Status:fixed» closed

Automatically closed -- issue fixed for 2 weeks with no activity.

 
 

Drupal is a registered trademark of Dries Buytaert.