yiisoft / yii2

Yii 2: The Fast, Secure and Professional PHP Framework
http://www.yiiframework.com
BSD 3-Clause "New" or "Revised" License
14.24k stars 6.91k forks source link

captcha is incorrect #6115

Closed shyandsy closed 9 years ago

shyandsy commented 9 years ago

messageBoard model

<?php

namespace frontend\models;

use Yii;

/**
 * This is the model class for table "message_board".
 *
 * @property integer $id
 * @property string $firstname
 * @property string $lastname
 * @property string $telephone
 * @property string $email
 * @property string $content
 * @property string $date_added
 */
class MessageBoard extends \yii\db\ActiveRecord
{
    public $verifyCode;

    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return 'message_board';
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['firstname', 'lastname', 'email', 'content'], 'required'],
            //[['date_added'], 'safe'],
            [['firstname', 'lastname', 'email', 'content'], 'string', 'max' => 255],
            [['telephone'], 'string', 'max' => 10],
            // verifyCode needs to be entered correctly
            ['verifyCode', 'captcha']
        ];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'id' => 'ID',
            'firstname' => 'Firstname',
            'lastname' => 'Lastname',
            'telephone' => 'Telephone',
            'email' => 'Email',
            'content' => 'Content',
            'date_added' => 'Date Added',
            'verifyCode' => 'Verification Code',
        ];
    }

    //get all message in the board
    public function getMessages(){
        return static::find()->all();
    }

}

sitecontroller, the problem is on the actionContact

<?php
namespace frontend\controllers;

use Yii;
use common\models\LoginForm;
use frontend\models\PasswordResetRequestForm;
use frontend\models\ResetPasswordForm;
use frontend\models\SignupForm;
use frontend\models\ContactForm;
use frontend\models\MessageBoard;
use yii\base\InvalidParamException;
use yii\web\BadRequestHttpException;
use yii\web\Controller;
use yii\filters\VerbFilter;
use yii\filters\AccessControl;

/**
 * Site controller
 */
class SiteController extends Controller
{
    /**
     * @inheritdoc
     */
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'only' => ['logout', 'signup'],
                'rules' => [
                    [
                        'actions' => ['signup'],
                        'allow' => true,
                        'roles' => ['?'],
                    ],
                    [
                        'actions' => ['logout'],
                        'allow' => true,
                        'roles' => ['@'],
                    ],
                ],
            ],
            'verbs' => [
                'class' => VerbFilter::className(),
                'actions' => [
                    'logout' => ['post'],
                ],
            ],
        ];
    }

    /**
     * @inheritdoc
     */
    public function actions()
    {
        return [
            'error' => [
                'class' => 'yii\web\ErrorAction',
            ],
            'captcha' => [
                'class' => 'yii\captcha\CaptchaAction',
                // 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
            ],
        ];
    }

    public function actionIndex()
    {
        return $this->render('index');
    }

    public function actionContact()
    {
        $model = new MessageBoard();

        if ($model->load(Yii::$app->request->post()) && $model->validate()) {
            $model->save();

            return $this->refresh();
        } else {
            return $this->render('contact', [
                'model' => $model,
            ]);
        }
    }
}

captcha file: vendor\yiii2soft\yii2\captcha\CaptchaAction.php

when the code run to $model->validate(), the captcha verify return true 11111

but after that, in the process of $model->save(), the captcha is changed. and validate return false. 22222

then I cant save data because the captcha is always incorrect

samdark commented 9 years ago

Do you have both backend and frontend on the same domain?

jafaripur commented 9 years ago

Add Your captcha controller and action to validation rule. ['verifyCode', 'captcha', 'captchaAction' => 'user/captcha', 'caseSensitive' => false,]

shyandsy commented 9 years ago

@samdark the url for frontend http://127.0.0.1/shyclouds/advanced/frontend/web/index.php?r=site%2Fcontact

the url for backend http://127.0.0.1/shyclouds/advanced/backend/web/index.php?r=site%2Fcontact

I haven't do anything on the backend.

shyandsy commented 9 years ago

@jafaripur

I just changed the initial project. You know there is a contactForm in the initial project.

Is that mean I should write a controller for all captcha by my self?

samdark commented 9 years ago

No, you can use yii\captcha\CaptchaAction via actions method of controller.

If the problem is still there, check http://www.yiiframework.com/doc-2.0/guide-tutorial-shared-hosting.html#separate-sessions-and-cookies

shyandsy commented 9 years ago

Hi, i tried that. but it does not work.

I just run the advanced template in the locally XAMPP environment.

and I guess this problem isnot about the request.

It is in same one request. The validate action is called twice. but in the vendor\yiii2soft\yii2\captcha\CaptchaAction.php $code = $this->getVerifyCode();

it get different code for $model->validate() and $model->save();

samdark commented 9 years ago

@shyandsy out of ideas. The best thing to do now is to try reproducing the problem with clean installation of advanced application + minimum amount of modifications for it. If it's there then we'll need clear steps on how to reproduce it locally.

shyandsy commented 9 years ago

the following is the processing to reproding the same problem

1.composer setup the project composer create-project --prefer-dist yiisoft/yii2-app-advanced reproducing

2.execute command cd reproducing init (choose development mode)

3.phpmyadmin create database create database reproducing

4.create table

CREATE TABLE IF NOT EXISTS `message_board` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `firstname` varchar(255) NOT NULL,
  `lastname` varchar(255) NOT NULL,
  `telephone` varchar(128) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  `content` varchar(8192) DEFAULT NULL,
  `date_added` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=8 ;

5.modify common/config/main.php

<?php
return [
    'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
    'components' => [
        'cache' => [
            'class' => 'yii\caching\FileCache',
        ],
        'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => false,
        ]
    ],
];

6.modify frontend/config/main.php

<?php
$params = array_merge(
    require(__DIR__ . '/../../common/config/params.php'),
    require(__DIR__ . '/../../common/config/params-local.php'),
    require(__DIR__ . '/params.php'),
    require(__DIR__ . '/params-local.php')
);

return [
    'id' => 'app-frontend',
    'basePath' => dirname(__DIR__),
    'bootstrap' => ['log'],
    'controllerNamespace' => 'frontend\controllers',
    'components' => [
        'user' => [
            'identityClass' => 'common\models\User',
            'enableAutoLogin' => true,
        ],
        'log' => [
            'traceLevel' => YII_DEBUG ? 3 : 0,
            'targets' => [
                [
                    'class' => 'yii\log\FileTarget',
                    'levels' => ['error', 'warning'],
                ],
            ],
        ],
        'errorHandler' => [
            'errorAction' => 'site/error',
        ],
        'db' => [
            'class' => 'yii\db\Connection',
            'dsn' => 'mysql:host=localhost;dbname=reproducing', // MySQL, MariaDB
            'username' => 'root',
            'password' => '',
            'charset' => 'utf8',
        ],
    ],
    'params' => $params,
];

7.create .htaccess file in \frontend\web

Options +FollowSymLinks
IndexIgnore */*

RewriteEngine on

# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

# otherwise forward it to index.php
RewriteRule . index.php

now the initial website is work correct

8.use gii module to generate frontend\models\MessageBoard

<?php

namespace frontend\models;

use Yii;

/**
 * This is the model class for table "message_board".
 *
 * @property integer $id
 * @property string $firstname
 * @property string $lastname
 * @property string $telephone
 * @property string $email
 * @property string $content
 * @property string $date_added
 */
class MessageBoard extends \yii\db\ActiveRecord
{
    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return 'message_board';
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['firstname', 'lastname'], 'required'],
            [['date_added'], 'safe'],
            [['firstname', 'lastname', 'email'], 'string', 'max' => 255],
            [['telephone'], 'string', 'max' => 128],
            [['content'], 'string', 'max' => 8192]
        ];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'id' => 'ID',
            'firstname' => 'Firstname',
            'lastname' => 'Lastname',
            'telephone' => 'Telephone',
            'email' => 'Email',
            'content' => 'Content',
            'date_added' => 'Date Added',
        ];
    }
}

9.modify the frontend\controller\SiteController.php, function actionContact

use frontend\models\ContactForm;

public function actions()
{
    return [
        'error' => [
            'class' => 'yii\web\ErrorAction',
        ],
        'captcha' => [
            'class' => 'yii\captcha\CaptchaAction',
            //'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
        ],
    ];
}

public function actionContact()
{
    $model = new MessageBoard();
    if ($model->load(Yii::$app->request->post()) && $model->validate()) {
        $model->save();

        return $this->refresh();
    } else {
        return $this->render('contact', [
            'model' => $model,
        ]);
    }
}

10.modify the \frontend\views\site\contact.php

<?php
use yii\helpers\Html;
use yii\bootstrap\ActiveForm;
use yii\captcha\Captcha;

/* @var $this yii\web\View */
/* @var $form yii\bootstrap\ActiveForm */
/* @var $model \frontend\models\ContactForm */

$this->title = 'Contact';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="site-contact">
    <h1><?= Html::encode($this->title) ?></h1>

    <p>
        If you have business inquiries or other questions, please fill out the following form to contact us. Thank you.
    </p>

    <div class="row">
        <div class="col-lg-5">
            <?php $form = ActiveForm::begin(['id' => 'contact-form']); ?>
                <?= $form->field($model, 'firstname') ?>
                <?= $form->field($model, 'lastname') ?>
                <?= $form->field($model, 'email') ?>
                <?= $form->field($model, 'telephone') ?>
                <?= $form->field($model, 'content')->textArea(['rows' => 6]) ?>
                <?= $form->field($model, 'verifyCode')->widget(Captcha::className(), [
                    'template' => '<div class="row"><div class="col-lg-3">{image}</div><div class="col-lg-6">{input}</div></div>',
                ]) ?>
                <div class="form-group">
                    <?= Html::submitButton('Submit', ['class' => 'btn btn-primary', 'name' => 'contact-button']) ?>
                </div>
            <?php ActiveForm::end(); ?>
        </div>
    </div>

</div>

11.modify the \frontend\models\MessageBoard.php

public function rules()
{
    return [
        [['firstname', 'lastname', 'email', 'content'], 'required'],
        //[['date_added'], 'safe'],
        [['firstname', 'lastname', 'email', 'content'], 'string', 'max' => 255],
        [['telephone'], 'string', 'max' => 10],
        // verifyCode needs to be entered correctly
        ['verifyCode', 'captcha']
    ];
}

now, you will see the captcha changed after $model->validate

samdark commented 9 years ago
shyandsy commented 9 years ago

@samdark so what is that exact mean?

this is whole process to create a new project based initial project to show that problem

HLH12321 commented 9 years ago

use frontend\models\ContactForm;

public function actions() { return [ 'error' => [ 'class' => 'yii\web\ErrorAction', ], 'captcha' => [ 'class' => 'yii\captcha\CaptchaAction', //'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, 'TestLimit'=>99, //try it ], ]; }

shyandsy commented 9 years ago

@HLH12321 I haven't use the ContactForm model, instead , I create MessageBoard for myself

samdark commented 9 years ago

I'm trying to isolate the problem by throwing out everything that is most probably not related to the problem.

martingeorg commented 9 years ago

Hi, i ran into the same problem, here are some details i got so far

when i do a

$session = Yii::$app->session;
var_dump($session);

right before i output the captcha widget, i get the following output

object(yii\web\Session)[323]
  public 'flashParam' => string '__flash' (length=7)
  public 'handler' => null
  private '_cookieParams' => 
    array (size=1)
      'httponly' => boolean true
  private '_hasSessionId' => null
  private '_events' (yii\base\Component) => 
    array (size=0)
      empty
  private '_behaviors' (yii\base\Component) => null

The letters on the image are "jasamql", i enter them correctly, the client side validator accepts them

When i submit the form to the server, the result from this code executed in the Controller

$model->validate();
var_dump( $model->errors );
var_dump($_POST);
var_dump($_SESSION);

is the following

array (size=1)
  'verifyCode' => 
    array (size=1)
      0 => string 'The verification code is incorrect.' (length=35)
array (size=2)
  '_csrf' => string 'cjJWc2lMaW85SmY5BiJQKktWCTchfgAKLQQbAT4oLRofXDkKLS0YPQ==' (length=56)
  'NewsComment' => 
    array (size=5)
      'reply_id' => string '' (length=0)
      'name' => string 'asdf' (length=4)
      'email' => string 'asdasd@asd.asd' (length=14)
      'comment' => string 'asdf' (length=4)
      'verifyCode' => string 'jasamql' (length=7)
array (size=5)
  '__flash' => 
    array (size=0)
      empty
  'interests' => string 'sadf,test123,test,minewolf,minewolf ,asdfsadf,mine,asdfasdf,qwerqwrqwrwqrqwrqwasfasdfasd,adsf,qwerqwerwqrqwrqwrwqrqwasfsa,sfdsdvgggggv6e4g6bve56he,sldfghsdlkghsjdhg,hberhybertyhberyebtybeybnjhdbhfg,dgh sdhg h sh gfh fgh ,gdsfghsdhfghsdghsldhgshdgsdgdfg,asdfasdfffffvgfwe,minewolf\,mineowlf,minewlf' (length=297)
  'experts_message' => int 1
  '__captcha/site/captcha' => string 'hevevlu' (length=7)
  '__captcha/site/captchacount' => int 2
{"success":false,"replies_count":"0","parent_id":"","is_reply":false}

as You can see the letters i had displayed in the captcha image are different from the '__captcha/site/captcha' => string 'hevevlu' (length=7)

i am entering the captcha letters right after i refresh tha page, without any other interactions on the page

it is maybe important to say that i submit the form using jquery ajax post (using a method shown as an example here in Github by Qiang Xue) because Pjax was not right for the situation, so the form is submitted via Ajax call. As you can see the data for the captcha field is sent to the server successfully.

At the moment i don't have idea what else to check for , so i'll wait for some response.

We are using Yii 2.0 stable, advanced application and the backend and frontend are on different domains.

Tomorrow i may try to create a simple page with a simple model and form and see what happens then.

Thanks.

shyandsy commented 9 years ago

@martingeorg could you format you code and dump data.

That will make information more clearly..

https://help.github.com/articles/github-flavored-markdown/

cebe commented 9 years ago

could you format you code and dump data.

I have updated it.

martingeorg commented 9 years ago

Hi again, it turned out to be related to that https://github.com/yiisoft/yii2/commit/c8c6882fc4ef8de11d1d5fa402b4303a06ab4079 , i had a call to validate() before the save() and that's what changed the captcha code.

There is actually a documentation about that here http://stuff.cebe.cc/yii2docs/yii-captcha-captchavalidator.html "Note that once CAPTCHA validation succeeds, a new CAPTCHA will be generated automatically. As a result, CAPTCHA validation should not be used in AJAX validation mode because it may fail the validation even if a user enters the same code as shown in the CAPTCHA image which is actually different from the latest CAPTCHA code." maybe it should be with H1 tags :)

By calling save() with false parameter e.g. $model->save(false); resolved the issue.

As i see, @shyandsy code also has a call to validate(), so i'm guessing that using save(false) will resolve his issue as well, hopefully.

I apologize if i wasted Your time :/

If someone can now help me with that question http://www.yiiframework.com/forum/index.php/topic/59588-captcha-custom-template-how-to-get-error-into-the-widgets-template/ it would be great :)

Again Thanks .

shyandsy commented 9 years ago

@samdark @martingeorg thanks for your work. That is the problem.

nageennayak11 commented 6 years ago

Hi @shyandsy, i have same problem. my captcha is always given incorrect when form submit. any solution?? i am very tired.

SOHELAHMED7 commented 1 year ago

@nageennayak11

As mentioned by @martingeorg you might be calling $model->validate() more than once.

$model->save() internally calls $model->validate().

So if you have $model->validate() and $model->save() then replace $model->save() with $model->save(false).