Yii 1.1: Understanding "Safe" Validation Rules

45 followers

A common source of confusion among new Yii users is how the 'safe' validator works, how it works with other validators, and why it's necessary in the first place. This article means to clear up this confusion, as well as explain the notion of Massive Assignment.

Summary: A model's validation rules serve two purposes:

  1. Ensure that fields entered in a form are entered properly
  2. Define which form fields are allowed to be assigned to a $model variable

These are related, but not the same, and the distinction is important.

Looking at a set of validation rules

To get started, we'll revisit what validation rules look like in a common model, and our example is taken from the Blog Tutorial "User" model (found in protected/models/User.php).

// protected/models/User.php
    ...
    public function rules()
    {
        return array(
            array('username, password, salt, email', 'required'),
            array('username, password, salt, email', 'length', 'max'=>128),
            array('profile', 'safe'),
        );
    }
    ...

Validation rules are defined with array(...), providing a list of attributes, the name of the validator, and additional parameters as needed by the particular validators. Also common is the 'on' keyword, which specifies scenarios, but we won't address those in this Article.

Validation Rules

The obvious purpose for validators is to ensure that users enter good data into application forms.

If a field should be no more than 16 characters long, if it should reflect a unique value in a table, or it must look like an email address, Yii provides a rich set of validators to help enforce form validation.

The Definitive Guide provides the authoritative reference, but user krillzip has provided an excellent quick reference guide to the available validators.

It's also possible to create your own validators, either as class functions or standalone extensions, but these are beyond the scope of this article.

Massive Assignment

Once your model's validators have approved all the fields, it's time to make use of the data produced by the form, in bulk. This happens during form submission by calling the controller's action.

Here we'll look at the post/update code:

// protected/controllers/CommentController.php
 
    public function actionUpdate()
    {
        $model = $this->loadModel();
 
        if (isset($_POST['Comment']))
        {
            $model->attributes = $_POST['Comment'];  // Massive Assignment
            ....

The key is $model->attributes = $_POST['Comment'];, which is deceptively simple.

In reality this is calling

$model->setAttributes( $_POST['Comment'] ); // this is an array!

Since $_Post['Comment'] is actually an array representing all the fields in the submitted form, Yii is running through them all and assigning the fields to the form one by one. Every field is assigned to the corresponding attribute in the model (after validation, of course), and this produces the final $model variable that can be saved or updated or whatever.

Massive Assignment is really the same as:

$model->author  = $_POST['Comment']['author'];
$model->email   = $_POST['Comment']['email'];
$model->url     = $_POST['Comment']['url'];
$model->content = $_POST['Comment']['content'];

Massive Assignment is very important - your Yii application will not work without it.

Why does Massive Assignment fail?

As "obvious" as Massive Assignment is, it's remarkably common for users to find that their $model variables fail to ->save() due to missing field values. Either the validation is failing outright, or field values are not copied from the form to the $model.

Key Point - Massive Assignment will only be made for fields which have passed some explicit validation rule. The obvious "actual" validators - length, email, required, etc. - all qualify, but some fields are free form and optional, and don't have any format requirements - the user can put whatever he likes, including leaving it blank.

For some fields, there's nothing to validate, right?

Wrong: by only assigning field values that the user has explicitly said are eligible for copying into $model, this limits shenanigans of a bad guy trying to pollute a model.

Even if a field has no particular data-format validations, we still have to tell Yii we want the attribute copied during Massive Assignment. This is done with the 'safe' validator.

Attributes that do not appear in any validation rule are not copied to the $model. Period.

So what's the big deal?

It's a very common question to wonder why this "safe" business is required at all.

After all, if the developer configures the form with certain fields, shouldn't they all just be copied to the $model after validation has passed? Why isn't this good enough?

Because Yii is protecting you from security surprises.

Though it may seem obvious to accept all the fields built into a form, during the controller's action (where Massive Assignment is taking place), Yii has no way of knowing which actual fields were part of a the form. and which are from a bad guy who is synthesizing form input with a contrived POST in order to fool the application.

This is protecting against two scenarios:

  1. Some models have attributes that are legitimate (in general), but not in a specific form. For instance, a change-your-password form for a user should accept the password and passwordRepeat attributes, but not the isAdmin attribute that makes him an administrator of the application. For a changePassword scenario, isAdmin should be marked explicitly 'unsafe'.

  2. All model objects based on CActiveRecord have internal housekeeping attributes that are subject to shenanigans if the bad guy were able to make assignments to them. Some of these include:

  • $model->isnewrecord
  • $model->dbcriteria
  • $model->primarykey
  • $model->tablealias
  • $model->scenario

and perhaps others. It's rather scary to think what could happen if the bad guy were able to manipulate these with malicious input, but because they are not mentioned in any validation rule - 'safe' or otherwise - they are protected.

Yii takes the conservative approach that attributes are assumed to be unsafe unless the developer explicitly makes them so (a "default deny" paradigm), rather than the easier but more dangerous "default allow".

It's wise to review the Rules in your model from time to time to ensure that you're not allowing things you should not (especially when scenarios are in play), because it's not uncommon to wildly mark things as safe during a bout of validation problems without realizing that this actaully reduces the security of the application.

Russian Version: Правило валидации "safe", для тех, кто в танке

Total 11 comments

#18374 report it
NCS_One at 2014/10/22 05:18am
This is not correct.

Hi,

This is not correct:

"Every field is assigned to the corresponding attribute in the model (after validation, of course), and this produces the final $model variable that can be saved or updated or whatever."

You still have to validate the attributes after massively assign. With one of the following:

$model = MyModel;
...
$model->attributes = $_POST['MyModel'];
...
if ($model->save())
{
  ...
}
else
{
  ...
}

or:

$model = MyModel;
...
$model->attributes = $_POST['MyModel'];
...
if ($model->validate())
{
  ...
  $model->save(false);
}
else
{
  ...
}
#14592 report it
CeBe at 2013/08/26 09:37am
Thats not the "Key Point"

@joyal that is not correct.

Massive Assignment will only be made for fields that have at least one validation rule defined. It is not about that it must pass validation.

#14585 report it
joyal at 2013/08/25 10:23am
Thank you very much

Key Point - Massive Assignment will only be made for fields which have passed some explicit validation rule.

Thanks for the Key point.

#13378 report it
capsuline at 2013/05/26 05:10pm
Thanks

This explains why my post was missing some values. Thanks. Very well documented.

#12686 report it
realtebo at 2013/04/05 06:56am
Safe on ... ?

can you add a list of default scenarios ?

#12464 report it
webguru at 2013/03/22 10:12am
thanks

this explained it for me thanks

#9131 report it
Michel Kogan at 2012/07/23 03:41am
Thanks

One never can explain "Safe Validation Rule" better than this ! Well done and thank you!

#6472 report it
bipu at 2012/01/12 10:50am
clean & effective explanation!

it's taking few minutes to understand safe & unsafe....awsome!!!!!

#5879 report it
RedRabbit at 2011/11/22 07:08am
Careful with the validation

I admit I haven't taken the time to confirm this thoroughly, but as far as I remember, you are not correct in lumping the validation in with the assignment. If memory serves correctly, when you do massive assignment ($model->setAttributes($_POST['Model'])), no actual validation takes place. Rather, for each attribute in the POST/GET array, the model checks whether that attribute is safe for assignment (by checking whether there is a validation or safe rule for that attribute, without actually validating it) - if there is then it sets the attribute, if not it ignores it.

Validation only takes place when you call $model->save() (or explicitly $model->validate()), when the attributes that have already been assigned are checked using the validation rules.

It is also worth mentioning that massive assignment and individual assignment are not actually equivalent - massive assignment checks whether there is a validation or safe rule for each attribute, whereas if you make an individual assignment ($model->attribute = $_POST['Model']['attribute'];) no such check is made - Yii assumes that the attribution is trusted.

Also, sorry to be a pedant, but in your summary points, it should be "ensure" rather than "insure" (I don't remember Yii having anything to do with insurance :P ).

Otherwise, good idea to demystify this.

#5374 report it
DryKillLogic at 2011/10/07 12:35pm
thanks

This saved my day, I was struggling with this matter. Thanks

#4663 report it
kcackler at 2011/08/01 07:00pm
EXCELLENT

This is a very well written article and does a great job at explaining the "safe" rule. Thumbs up!

Leave a comment

Please to leave your comment.

Write new article