ResetBehavior and HazardableBehavior

ResetBehavior

Reset is a new behavior I recently had to write to update some geocoded entries as well as records with processed titles/slugs – via beforeSave() – that had to be re-saved with a corrected processing method. Basically, it is a batch update that works even with huge tables as it processes the data in steps.

Example: Resetting slugs

Either via shell or via controller action you can trigger an update of all slugs that have been generated via Tools plugin Slugged behavior:

// First we need to re-load the Slugged behavior to enable "overwrite" mode
$this->Post->Behaviors->load('Tools.Slugged', array('label' => 'title', 'overwite' => true));
// Load the Reset behavior with only the title and slug field to read and modify.
$this->Post->Behaviors->load('Tools.Reset', array('fields' => array('title', 'slug')));
$res = $this->Post->resetRecords();
// flash message with number of records modified in $res

You should make a shell command for this and execute this migration code once on deploy of the modified code or SQL schema.
If you are not using CLI, make sure you set the time limit in your controller action accordingly (HOUR for example).

Example: Retrigger/Init geocoding

If you got records that now need to be geocoded, you probably added a lat and lng field (decimal 6,2 etc) to your table.
You probably also attached the Tools plugin Geocoder behavior to this model.
In order to quickly geocode you just have to use Reset:

$this->Post->Behaviors->load('Tools.Reset', array('fields' => array('address', 'lat', 'lng'), 'timeout' => 3));
$res = $this->Post->resetRecords();

Since all lat/lng fields are still null it will geocode the records and populate those fields. It will skip already geocoded ones. If you
want to skip those completely (not even read them), just set the scope to 'NOT' => array('lat' => null) etc.

Note that in this case we also use a timeout to avoid getting a penalty by Google for geocoding too many records per minute.

Advanced example: Resetting composite cache field

In this case we added a new cache field to our messages in order to make the search faster with >> 100000 records. The data was containing
all the info we needed – in serialized format.
We needed a callback here as there was some logic involved. So we simply made a shell containing both callback method and shell command:

$this->Message->Behaviors->load('Tools.Reset', array(
    'fields' => array('data'), 'updateFields' => array('guest_name'),
    'scope' => array('data LIKE' => '{%'), 'callback' => 'UpdateShell::prepMessage'));
$res = $this->Message->resetRecords();
$this->out('Done: ' . $res);

The callback method (in this case just statically, as we didnt want to mess with the model itself):

public static function prepMessage(array $row) {
    if (empty($row['Message']['data_array']['GUEST_FIRST_NAME'])) {
        return array();
    }

    $row['Message']['guest_name'] = $row['Message']['data_array']['GUEST_FIRST_NAME'] . ' ' . $row['Message']['data_array']['GUEST_LAST_NAME'];
    return $row;
}

See the test cases for more ways to use callbacks – including adjusting the updateFields list.

So as you can see, everything that involves a complete "re-save" including triggering of important callbacks (in model and behaviors) of all or most records can leverage this behavior in a DRY, quick and reusable way.

HazardableBehavior

This behavior is a very useful tool to test vulnerability against XSS or unescaped html output (especially accidental one).
The basic idea is to test all views that output data from varchar or text fields for proper escaping. Even if it not user input, it is still vital to properly escape.
Admin input can have chars like <, > etc in there, as well. Without the use of h() it can destroy the layout or worse. So it is always a good idea to cover all
views.

Just attach it temporarily (!) to any of your models and quickly fill your table with hazardous strings. Those strings can potentially end up there via Form input, of course.
This just automates it.
Then you can browse your site and see if an alert or other strange behavior occurs. This tells you that you forgot to use h() or other measures to secure your output properly.

You can also apply this behavior globally to overwrite all strings in the find result.
This way you don’t need to modify the database. On output it will just inject the hazardous strings and you can browse your website just as if they were actually stored in your db.
Just add it to some models or even the AppModel (temporarily!) as $actsAs = array('Tools.Hazardable' => array('replaceFind' => true)).
A known limitation of Cake behaviors in 2.x, though, is, that this would only apply for first-level records (not related data). So it is usually better to insert some hazardous strings into all your tables and make your tests then as closely to the reality as possible.

You can use skipFields to blacklist certain stringish fields from being overwritten and populated with hazardous strings.

Note: In 3.x the behavior callback issue regarding non-primary records will be solved 🙂 I am really looking forward to that.

CakePHP 3

This article is 2.x only.
For CakePHP 3 please see the 3.0 Tools Plugin documentation on the Reset behavior.

5.00 avg. rating (93% score) - 1 vote

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.