web-dev-qa-db-fra.com

ReCaptcha ne fonctionne pas correctement sur iPhone

J'ai un site web avec un simple formulaire de contact. La validation est quelque peu minimale car elle ne va pas dans une base de données; juste un email. Le formulaire fonctionne comme tel:

Il y a 5 champs - dont 4 obligatoires. La soumission est désactivée jusqu'à ce que les 4 champs soient valides, puis vous pouvez la soumettre. Ensuite, tout est à nouveau validé sur le serveur, y compris le recaptcha (qui n’est pas validé par le côté client). L'ensemble du processus est effectué avec ajax, et de nombreux tests doivent réussir du côté serveur ou 4 ** en-têtes sont renvoyés et le gestionnaire d'échec de rappel est appelé.

Tout fonctionne comme des gangbusters sur Chrome sur le bureau (je n'ai pas essayé d'autres navigateurs, mais je ne vois pas pourquoi ils seraient différents), mais sur l'iPhone, le reCaptcha est toujours validé même si je ne coche pas la case correspondante. le test.

En d'autres termes: je dois encore renseigner correctement les quatre valeurs pour pouvoir soumettre, mais si je ne coche pas la case correspondant à reCaptcha, la demande aboutit quand même. 

Je peux envoyer du code si quelqu'un pense que cela pourrait être utile, mais il semble que le problème concerne le périphérique et non le code. Quelqu'un a-t-il un aperçu de cela?


Note: Le côté serveur est PHP/Apache si cela est utile.


Mise à jour: 28/05/2015

Je suis toujours en train de résoudre ce problème, mais il semble que Mobile Safari ignore les en-têtes de réponse de mon iPhone. Lorsque je renvoie la réponse à la page, ce que je reçois sur le bureau pour (data,status,xhr) est:

  1. data: ma réponse qui à ce stade dit simplement erreur ou succès -> error

  2. status: error

  3. xhr: {'error',400,'error'}

Sur safari mobile:

  1. data: error

  2. status: success

  3. xhr: {'error',200,'success'}

Donc, il semble que cela ignore simplement les en-têtes de ma réponse. J'ai essayé explicitement de régler {"headers":{"cache-control":"no-cache"}} mais en vain. 


Mise à jour: 6/3/2015

Par demande, voici le code. C'est presque certainement plus que ce dont vous avez besoin. Il est également devenu plus obtus à cause des changements que j'ai apportés pour essayer de le réparer. Notez également que, même s’il peut sembler que certaines variables n’ont pas été définies, elles doivent (devraient) avoir été définies dans d’autres fichiers. 

The client side

 $('#submit').on('click', function(e) {

    $(this).parents('form').find('input').each(function() {
        $(this).trigger('blur');
    })
    var $btn = $(this);
    $btn = $btn.button('loading');
    var dfr = $.Deferred();

    if ($(this).attr('disabled') || $(this).hasClass('disabled')) {

        e.preventDefault();
        e.stopImmediatePropagation();
        dfr.reject();
        return false;

    } else {

        var input = $('form').serializeArray();
        var obj = {},
            j;

        $.each(input, function(i, a) {

            if (a.name === 'person-name') {

                obj.name = a.value;

            } else if (a.name === 'company-name') {

                obj.company_name = a.value;

            } else {

                j = a.name.replace(/(g-)(.*)(-response)/g, '$2');
                obj[j] = a.value;

            }

        });

        obj.action = 'recaptcha-js';
        obj.remoteIp = rc.remoteiP;
        rc.data = obj;

        var request = $.ajax({

            url: rc.ajaxurl,
            type: 'post',
            data: obj,

            headers: {
                'cache-control': 'no-cache'
            }

        });

        var success = function(data) {

            $btn.data('loadingText', 'Success');
            $btn.button('reset');
            $('#submit').addClass('btn-success').removeClass('btn-default');
            $btn.button('loading');
            dfr.resolve(data);


        };
        var fail = function(data) {

            var reason = JSON.parse(data.responseText).reason;
            $btn.delay(1000).button('reset');
            switch (reason) {

                case 'Recaptcha Failed':
                case 'Recaptcha Not Checked':
                case 'One Or more validator fields not valid or not filled out':
                case 'One Or more validator fields is invalid':

                    // reset recaptcha

                    if ($('#submit').data('tries')) {

                        $('#submit').remove();
                        $('.g-recaptcha').parent().addBack().remove();

                        myPopover('Your request is invalid.  Please reload the page to try again.');

                    } else {

                        $('#submit').data('tries', 1);
                        grecaptcha.reset();

                        myPopover('One or more of your entries are invalid.  Please make corrections and try again.');
                    }


                    break;

                default:

                    // reset page
                    $('#submit').remove();
                    $('.g-recaptcha').remove();


                    myPopover('There was a problem with your request.  Please reload the page and try again.');

                    break;
            }
            dfr.reject(data);

        };

        request.done(success);
        request.fail(fail);



    }

The Server:

function _send_email(){

$recaptcha=false;
/* * */
if(isset($_POST['recaptcha'])):

    $gRecaptchaResponse=$_POST['recaptcha'];
    $remoteIp=isset($_POST['remoteIp']) ? $_POST['remoteIp'] : false;

    /* ** */
    if(!$remoteIp):

        $response=array('status_code'=>'409','reason'=>'remoteIP not set');
        echo json_encode($response);
        http_response_code(409);

        exit();

    endif;
    /* ** */

    /* ** */
    if($gRecaptchaResponse==''):

        $response=array('status_code'=>'400','reason'=>'Recaptcha Failed');
        echo json_encode($response);
        http_response_code(400);
        exit();

    endif;
    /* ** */

    if($recaptcha=recaptcha_test($gRecaptchaResponse,$remoteIp)):

        $recaptcha=true;

    /* ** */
    else:

        $response=array('status_code'=>'400','reason'=>'Recaptcha Failed');
        echo json_encode($response);
        http_response_code(400);
        exit();

    endif;
    /* ** */

/* * */
else:

    $response=array('status_code'=>'400','reason'=>'Recaptcha Not Checked');
    echo json_encode($response);
    http_response_code(400);
    exit();

endif;
/* * */

/* * */
if($recaptcha==1):

    $name=isset($_POST['name']) ? $_POST['name'] : false;
    $company_name=isset($_POST['company_name']) ? $_POST['company_name'] : false;
    $phone=isset($_POST['phone']) ? $_POST['phone'] : false;
    $email=isset($_POST['email']) ? $_POST['email'] : false;

    /* ** */
    if(isset($_POST['questions'])):

        $questions=$_POST['questions']=='' ? 1 : $_POST['questions'];

        /* *** */

    if(!$questions=filter_var($questions,FILTER_SANITIZE_SPECIAL_CHARS)):

         $response=array('status_code'=>'400','reason'=>'$questions could not be sanitized');
         echo json_encode($response);
         http_response_code(400);
         exit();

        endif;
       /* *** */

    /* ** */
    else:

      $questions=true;

    endif;
    /* ** */

    /* ** */
    if( count( array_filter( array( $name,$company_name,$phone,$email ),"filter_false" ) ) !=4 ):

        $response=array('status_code'=>'400','reason'=>'One Or more validator fields not valid or not filled out');
        echo json_encode($response);
        http_response_code(400);
        exit();

    endif;
    /* ** */

    $company_name=filter_var($company_name,FILTER_SANITIZE_SPECIAL_CHARS);
    $name=filter_var($name,FILTER_SANITIZE_SPECIAL_CHARS);
    $phone=preg_replace('/[^0-9+-]/', '', $phone);
    $email=filter_var($email,FILTER_VALIDATE_EMAIL);

    /* ** */
    if($company_name && $recaptcha && $name && $phone && $email && $questions):

        $phone_str='Phone:  ' . $phone;
        $company_str='Company:   ' . $company_name;
        $email_str='Email String:  ' . $email;
        $name_str='Name:  '.$name;
        $questions=$questions==1 ? '' : $questions;
        $body="$name_str\r\n\r\n$company_str\r\n\r\n$email_str\r\n\r\n$phone_str\r\n\r\n____________________\r\n\r\n$questions";


        $mymail='[email protected]';
        $headers   = array();
        $headers[] = "MIME-Version: 1.0";
        $headers[] = "Content-type: text/plain; charset=\"utf-8\"";
        $headers[] = "From: $email";
        $headers[] = "X-Mailer: PHP/" . phpversion();

        /* *** */
        if(mail('$mymail', 'Information Request from: ' . $name,$body,implode("\r\n",$headers))):

            $response=array('status_code'=>'200','reason'=>'Sent !');
            echo json_encode($response);
            http_response_code(200);
            exit();

        /* *** */
        else:

            $response=array('status_code'=>'400','reason'=>'One Or more validator fields is invalid');
            echo json_encode($response);
            http_response_code(400);
            exit();

        endif;
        /* *** */

     endif;
    /* ** */

   endif;
  /* * */

     $response=array('status_code'=>'412','reason'=>'There was an unknown error');
     echo json_encode($response);
     http_response_code(412);
     exit();
 }


function recaptcha_test($gRecaptchaResponse,$remoteIp){

    $secret=$itsasecret; //removed for security;

    require TEMPLATE_DIR . '/includes/lib/recaptcha/src/autoload.php';
    $recaptcha = new \ReCaptcha\ReCaptcha($secret);
    $resp = $recaptcha->verify($gRecaptchaResponse, $remoteIp);

    if ($resp->isSuccess()) {
        return true;
            // verified!
    } else {
        $errors = $resp->getErrorCodes();
        return false;
    }
 }
40
dgo

Comme cette question iOS: Authentification à l'aide de XMLHttpRequest - Traitement de la réponse 401 le moyen le plus facile à résoudre consiste à ignorer la validation des en-têtes naturels et, en cas de rappel, à valider avec un indicateur.

J'ai vu des cas comme ça et je ne sens jamais bon. 

1
capcj

J'ai fait un script il y a 2 ou 3 mois qui fonctionne encore parfaitement, essayez ceci:

<?php
$siteKey = ''; // Public Key
$secret = ''; // Private Key
/**
 * This is a PHP library that handles calling reCAPTCHA.
 *    - Documentation and latest version
 *          https://developers.google.com/recaptcha/docs/php
 *    - Get a reCAPTCHA API Key
 *          https://www.google.com/recaptcha/admin/create
 *    - Discussion group
 *          http://groups.google.com/group/recaptcha
 *
 * @copyright Copyright (c) 2014, Google Inc.
 * @link      http://www.google.com/recaptcha
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
/**
 * A ReCaptchaResponse is returned from checkAnswer().
 */
class ReCaptchaResponse
{
    public $success;
    public $errorCodes;
}
class ReCaptcha
{
    private static $_signupUrl = "https://www.google.com/recaptcha/admin";
    private static $_siteVerifyUrl =
        "https://www.google.com/recaptcha/api/siteverify?";
    private $_secret;
    private static $_version = "php_1.0";
    /**
     * Constructor.
     *
     * @param string $secret shared secret between site and ReCAPTCHA server.
     */
    function ReCaptcha($secret)
    {
        if ($secret == null || $secret == "") {
            die("To use reCAPTCHA you must get an API key from <a href='"
                . self::$_signupUrl . "'>" . self::$_signupUrl . "</a>");
        }
        $this->_secret=$secret;
    }
    /**
     * Encodes the given data into a query string format.
     *
     * @param array $data array of string elements to be encoded.
     *
     * @return string - encoded request.
     */
    private function _encodeQS($data)
    {
        $req = "";
        foreach ($data as $key => $value) {
            $req .= $key . '=' . urlencode(stripslashes($value)) . '&';
        }
        // Cut the last '&'
        $req=substr($req, 0, strlen($req)-1);
        return $req;
    }
    /**
     * Submits an HTTP GET to a reCAPTCHA server.
     *
     * @param string $path url path to recaptcha server.
     * @param array  $data array of parameters to be sent.
     *
     * @return array response
     */
    private function _submitHTTPGet($path, $data)
    {
        $req = $this->_encodeQS($data);
        $response = file_get_contents($path . $req);
        return $response;
    }
    /**
     * Calls the reCAPTCHA siteverify API to verify whether the user passes
     * CAPTCHA test.
     *
     * @param string $remoteIp   IP address of end user.
     * @param string $response   response string from recaptcha verification.
     *
     * @return ReCaptchaResponse
     */
    public function verifyResponse($remoteIp, $response)
    {
        // Discard empty solution submissions
        if ($response == null || strlen($response) == 0) {
            $recaptchaResponse = new ReCaptchaResponse();
            $recaptchaResponse->success = false;
            $recaptchaResponse->errorCodes = 'missing-input';
            return $recaptchaResponse;
        }
        $getResponse = $this->_submitHttpGet(
            self::$_siteVerifyUrl,
            array (
                'secret' => $this->_secret,
                'remoteip' => $remoteIp,
                'v' => self::$_version,
                'response' => $response
            )
        );
        $answers = json_decode($getResponse, true);
        $recaptchaResponse = new ReCaptchaResponse();
        if (trim($answers ['success']) == true) {
            $recaptchaResponse->success = true;
        } else {
            $recaptchaResponse->success = false;
            $recaptchaResponse->errorCodes = $answers [error-codes];
        }
        return $recaptchaResponse;
    }
}

$reCaptcha = new ReCaptcha($secret);

if(isset($_POST["g-recaptcha-response"])) {
    $resp = $reCaptcha->verifyResponse(
        $_SERVER["REMOTE_ADDR"],
        $_POST["g-recaptcha-response"]
        );
    if ($resp != null && $resp->success) {echo "OK";}
    else {echo "CAPTCHA incorrect";}
    }
?>

<html>

<head>
<title>Google reCAPTCHA</title>
<script src="https://www.google.com/recaptcha/api.js"></script>
</head>

<body>
<form action="reCAPTCHA.php" method="POST">
<input type="submit" value="Submit">
<div class="g-recaptcha" data-sitekey="<?php echo $siteKey; ?>"></div>
</form>
</body>

</html>

Normalement, cela devrait fonctionner (il suffit d’ajouter votre clé privée et votre clé publique), j’ai testé sur mon iPhone SE il ya 2 secondes et cela a parfaitement fonctionné.

0
Arthur Guiot

Le captcha est conçu pour empêcher clients malveillants (robots), donc théoriquement si un client contourne le captcha, il s’agit d’un problème côté serveur. (Cependant, si un client ne parvient pas à terminer le captcha, il peut s'agir d'un problème côté serveur ou d'un problème côté client.)

Donc, le problème doit être sur le serveur. Même pour des raisons de sécurité, vous devriez utiliser $_SERVER['REMOTE_ADDR'] plutôt que $_POST['remoteIp'] car $_POST['remoteIp'] peut être falsifié (par un client malveillant). En fait, $_SERVER['REMOTE_ADDR'] est beaucoup plus fiable que le $_POST['remoteIp'] côté client.

0
He WenYang

Votre variable "remoteIP" est-elle correctement définie du côté client?

Même si votre requête Ajax envoie une valeur vide ou une valeur false, la fonction isset () de votre script php renverra la valeur true et renseignera donc incorrectement $ remoteIp.

Essayez de faire:

$remoteIp = $_SERVER['REMOTE_ADDR'];

Ajax demande simplement au navigateur de faire la demande, ainsi PHP peut parfaitement saisir l'adresse IP de notre utilisateur.

Je suis sûr que si vous transmettez la mauvaise valeur, ReCaptcha se gâtera d'une manière ou d'une autre.

Il est également plus sûr de ne jamais faire confiance aux variables Javascript au-dessus de Ajax, car celles-ci doivent également être traitées comme des entrées utilisateur.

0
Yani