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 this up this confusion, as well as explain
the notion of Massive Assignment.
Summary: A model's validation rules serve **two** purposes:
1. Insure 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`).
~~~
[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 **scenario**s,
but we won't address those in this Article.
Validation Rules
----------------
The obvious purpose for validators is to insure 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 <a
href="http://www.yiiframework.com/doc/guide/1.1/en/form.model#declaring-validation-rules">the
authoritative reference</a>, but user <a
href="http://www.yiiframework.com/user/390/">krillzip</a> has
provided an excellent <a
href="http://www.yiiframework.com/wiki/56/reference-model-rules-validation">quick
reference guide</a> 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:
~~~
[php]
// 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
~~~
[php]
$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:
~~~
[php]
$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 expilicity `'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->isnewrecord`
* $model->dbcriteria`$model->dbcriteria`
* $model->primarykey`$model->primarykey`
* $model->tablealias`$model->tablealias`
* $model->scenario`$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 insure 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.