Difference between #4 and #3 of Understanding "Safe" Validation Rules

unchanged
Title
Understanding "Safe" Validation Rules
unchanged
Category
FAQs
unchanged
Tags
Forms, Validation, Safe, Massive Assignment
unchanged
Content
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->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 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.