Tag: cakephp shell

A simple shell to hash your passwords

If you are moving data into a CakePHP environment and the passwords in the existing DB aren’t hashed, you can use the simple shell provided below to hash them according to the cake’s standard.
In case the passwords are already hashed using a different method, this is not going to be of any use. Instead use Security::setHash() to set the method according to your needs.

The code should be simple to understand and usage example is provided in the comments.
(Any questions or feedback, just post a comment…)

Save the following into: app/vendors/shells/password_hash.php

<?php
/**
 * Importing the security component,
 * which is used to hash the passwords
 */

App::import('Core', 'Security');

/**
 * Hashes passwords in the given table
 * Usage: &quot;cake password_hash&quot; (to run against the default users table and password field)
 * Or: &quot;cake password_hash -table some_table -field another_field&quot;
 *
 * Strict mode can be turned off, this way
 * any password even potentially a hashed one already will be re-hashed
 * see below for more info on how that works. From command line:
 * &quot;cake password_hash -strict false&quot;
 * Or: &quot;cake password_hash -strict 0&quot;
 *
 */

class PasswordHashShell extends Shell {

/**
 * True/False to keep the shell from
 * showing any output
 *
 * @var boolean
 */

  private $silent = FALSE;

/**
 * Holds the instance of the current model,
 * where the hashing needs to happen
 * (Defaults to User model)
 *
 * @var object
 */

  private $model = 'User';

/**
 * Field to hash
 * (Defaults to password)
 *
 * @var string
 */

  private $field = 'password';

/**
 * If the strict (mode) is set to TRUE
 * the shell will ignore any 40 char passwords
 * (40 chars is the default length of cake's hashed password)
 *
 * From the shell we can specify the strict mode as:
 * cake password_hash -strict false/0
 *
 * @var boolean
 */

  private $strict = TRUE;

/**
 * Generic initialization function
 *
 * (Always a good idea to call the parent method)
 *
 */

  public function initialize() {
    parent::initialize();
  }

/**
 * Starting up the shell
 * Initializing shell properties based on the passed in params,
 * or falling back to defaults
 */

  public function startup() {
    if (isset($this->params['silent']) && $this->params['silent']) {
      $this->silent = TRUE;
    }

    if (isset($this->params['table']) && $this->params['table']) {
      $modelName = Inflector::classify($this->params['table']);
      $this->model = ClassRegistry::init($modelName);
    } else {
      $this->model = ClassRegistry::init($this->model);
    }

    if (isset($this->params['field']) && $this->params['field']) {
      $this->field = $this->params['field'];
    }

    if (isset($this->params['all']) && $this->params['all']) {
        $this->strict = FALSE;
    }
  }

/**
 * Main method is launched autmoatically
 * Works as a dispatcher.
 */

  public function main() {
    $this->processField();
  }

/**
 * Takes care of finding, hashing and saving the field
 * (Uses standard CakePHP hashing method)
 * If strict mode is enabled all 40 char passwords,
 * which is default hashed length are ignored.
 */

  private function processField() {
    $conditions = 1;
    if($this->strict) {
      $conditions = array(
          'LENGTH(`' . $this->field . '`) <>' => 40
      );
    }
   
    $fieldsToHash = $this->model->find('all', array(
        'recursive' => -1,
        'fields' => array($this->model->primaryKey, $this->field),
        'conditions' => $conditions
    ));

    foreach($fieldsToHash as $field) {
      $fieldValue = $field[$this->model->alias][$this->field];
      $recordId = $field[$this->model->alias][$this->model->primaryKey];

      $fieldValue = Security::hash($fieldValue, NULL, TRUE);
   
      $this->out('Changing password for: ' . $recordId);
      $this->out('Changed to: ' . $fieldValue);
           
      $this->model->create();
      $this->model->id = $recordId;
      $data = array(
        $this->model->alias => array(
            $this->field => $fieldValue
        )
      );
      $this->model->save($data, array(
          'validate' => FALSE,
          'callbacks' => FALSE
      ));
    }  
  }

/**
 * Shell output
 *
 * @param string $string
 * @param boolean $newline
 * @return boolean
 */

  public function out($string, $newline = true) {
    if(!$this->silent) {
      parent::out($string, $newline = true);
    }
    return FALSE;
  }

/**
 * Displays help contents
 *
 * @access public
 */

  public function help() {
    $this->_stop();
  }
}
?>

Keeping your CakePHP shells quiet

Whenever working with shells, you most likely would like to provide some output or feedback to the user. This is especially useful when testing or observing a given shell’s work flow.
However, having your shell provide any output while it runs as a cron job (for example), is useless to say the least.

To me it seems like providing a simple -silent param would be a nice way to keep the shell quiet.

In order to achieve this we’ll override our parent’s out() method.

Let’s build a simple test shell, to see how it works:

class TestShell extends Shell {

    //the shell isn't silent by default...
    private $silent = FALSE;

    function startup () {
      if(isset($this->params['silent']) && $this->params['silent']) {
        $this->silent = TRUE;
      }
    }

    function dostuff() {
      $this->out(__('Does this work?', TRUE));
    }

    function out($string, $newline = TRUE) {
      if(!$this->silent) {
        parent::out($string, $newline = TRUE);
      }

      return FALSE;
    }

}

So, the first time we run the shell, like…

#cake test dostuff

We should see the output: “Does this work?”

Next, when we provide a -silent param, like…

#cake test dostuff -silent

The shell will exit quietly.

Notice that we check $this->params['silent'], which will be automatically set as an index of $this->params array (and set to “TRUE”), if one is passed via the command line.

P.S. Remember, that if you have initialize() method in your shell, the output from it will not be suppressed (given this example) as it runs before the startup() method.